2 * Copyright (c) 2000, Red Hat, Inc.
3 * Copyright (c) 2003, Robert Collins <rbtcollins@hotmail.com>
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * A copy of the GNU General Public License can be found at
13 * Originally Written by DJ Delorie <dj@cygnus.com>
17 /* The purpose of this file is to install all the packages selected in
18 the install list (in ini.h). Note that we use a separate thread to
19 maintain the progress dialog, so we avoid the complexity of
20 handling two tasks in one thread. We also create or update all the
21 files in /etc/setup/\* and create the mount points. */
24 static const char *cvsid
= "\n%%% $Id$\n";
33 #include <sys/types.h>
38 #include "zlib/zlib.h"
49 #include "filemanip.h"
50 #include "io_stream.h"
52 #include "compress_gz.h"
54 #include "archive_tar.h"
57 #include "package_db.h"
58 #include "package_meta.h"
59 #include "package_version.h"
60 #include "package_source.h"
68 #include "Exception.h"
69 #include "getopt++/BoolOption.h"
73 extern ThreeBarProgressPage Progress
;
75 static int total_bytes
= 0;
76 static int total_bytes_sofar
= 0;
77 static int package_bytes
= 0;
79 static BoolOption
NoReplaceOnReboot (false, 'r', "no-replaceonreboot",
80 "Disable replacing in-use files on next "
86 static const char *StandardDirs
[];
89 void progress (int bytes
);
90 void uninstallOne (packagemeta
&);
91 int replaceOne (packagemeta
&);
92 void replaceOnRebootFailed (String
const &fn
);
93 void replaceOnRebootSucceeded (String
const &fn
, bool &rebootneeded
);
94 int installOneSource (packagemeta
&, packagesource
&, String
const &, String
const &, package_type_t
);
98 Installer::Installer() : errors(0)
103 Installer::initDialog()
105 Progress
.SetText2 ("");
106 Progress
.SetText3 ("");
110 Installer::progress (int bytes
)
112 if (package_bytes
> 0)
113 Progress
.SetBar1 (bytes
, package_bytes
);
116 Progress
.SetBar2 (total_bytes_sofar
+ bytes
, total_bytes
);
120 Installer::StandardDirs
[] = {
139 static int num_installs
, num_replacements
, num_uninstalls
;
140 static void md5_one (const packagesource
& source
);
141 static bool rebootneeded
;
144 Installer::uninstallOne (packagemeta
& pkgm
)
146 Progress
.SetText1 ("Uninstalling...");
147 Progress
.SetText2 (pkgm
.name
.cstr_oneuse());
148 log (LOG_PLAIN
, String("Uninstalling ") + pkgm
.name
);
153 /* uninstall and install a package, preserving configuration
154 * files and the like.
155 * This method should also know about replacing in-use file.
156 * ASSUMPTIONS: pkgm is installed.
157 * pkgm has a desired package.
160 Installer::replaceOne (packagemeta
&pkg
)
163 Progress
.SetText1 ("Replacing...");
164 Progress
.SetText2 (pkg
.name
.cstr_oneuse());
165 Progress
.SetText3 ("");
166 log (LOG_PLAIN
, String( "Replacing ") + pkg
.name
);
170 installOneSource (pkg
, *pkg
.desired
.source(), "cygfile://","/", package_binary
);
172 pkg
.installed
= pkg
.desired
;
177 /* log failed scheduling of replace-on-reboot of a given file. */
178 /* also increment errors. */
180 Installer::replaceOnRebootFailed (String
const &fn
)
183 "Unable to schedule reboot replacement of file %s with %s (Win32 Error %ld)",
184 cygpath (String ("/") + fn
).cstr_oneuse(),
185 cygpath (String ("/") + fn
+ ".new").cstr_oneuse(),
190 /* log successful scheduling of replace-on-reboot of a given file. */
191 /* also set rebootneeded. */
193 Installer::replaceOnRebootSucceeded (String
const &fn
, bool &rebootneeded
)
196 "Scheduled reboot replacement of file %s with %s",
197 cygpath (String ("/") + fn
).cstr_oneuse(),
198 cygpath (String ("/") + fn
+ ".new").cstr_oneuse());
202 /* install one source at a given prefix. */
204 Installer::installOneSource (packagemeta
& pkgm
, packagesource
& source
,
205 String
const &prefixURL
, String
const &prefixPath
, package_type_t type
)
207 Progress
.SetText2 (source
.Base ());
208 if (!source
.Cached () || !io_stream::exists (source
.Cached ()))
210 note (NULL
, IDS_ERR_OPEN_READ
, source
.Cached (), "No such file");
215 package_bytes
= source
.size
;
218 strcpy (msg
, "Installing");
219 Progress
.SetText1 (msg
);
220 log (LOG_PLAIN
, String (msg
) + " " + source
.Cached ());
222 io_stream
*tmp
= io_stream::open (source
.Cached (), "rb");
223 archive
*thefile
= 0;
226 io_stream
*tmp2
= compress::decompress (tmp
);
228 thefile
= archive::extract (tmp2
);
230 thefile
= archive::extract (tmp
);
233 /* FIXME: potential leak of either *tmp or *tmp2 */
237 if (type
== package_binary
)
239 io_stream
*tmp
= io_stream::open (String ("cygfile:///etc/setup/") +
240 pkgm
.name
+ ".lst.gz", "wb");
241 lst
= new compress_gz (tmp
, "w9");
248 while ((fn
= thefile
->next_file_name ()).size())
252 String tmp
=fn
+ "\n";
253 lst
->write (tmp
.cstr_oneuse(), tmp
.size());
256 String canonicalfn
= prefixPath
+ fn
;
257 if (Script::isAScript (fn
))
258 pkgm
.desired
.addScript (Script (canonicalfn
));
260 Progress
.SetText3 (canonicalfn
.cstr_oneuse());
261 log (LOG_BABBLE
, String("Installing file ") + prefixURL
+ prefixPath
+ fn
);
262 if (archive::extract_file (thefile
, prefixURL
, prefixPath
) != 0)
264 if (NoReplaceOnReboot
)
267 log (LOG_PLAIN
, String("Not replacing in-use file ") +
268 prefixURL
+ prefixPath
+ fn
);
271 //extract to temp location
272 if (archive::extract_file (thefile
, prefixURL
, prefixPath
, ".new") != 0)
275 String("Unable to install file ") +
276 prefixURL
+ prefixPath
+ fn
);
282 switch (Win32::OS ())
285 /* Get the short file names */
286 char source
[MAX_PATH
];
288 GetShortPathName (cygpath (String ("/") + fn
+
289 ".new").cstr_oneuse(),
291 if (!len
|| len
> MAX_PATH
)
293 replaceOnRebootFailed(fn
);
299 GetShortPathName (cygpath (String ("/") +
302 if (!len
|| len
> MAX_PATH
)
303 replaceOnRebootFailed (fn
);
305 /* trigger a replacement on reboot */
306 if (!WritePrivateProfileString
307 ("rename", dest
, source
, "WININIT.INI"))
308 replaceOnRebootFailed (fn
);
310 replaceOnRebootSucceeded (fn
, rebootneeded
);
315 /* XXX FIXME: prefix may not be / for in use files -
316 * although it most likely is
317 * - we need a io method to get win32 paths
318 * or to wrap this system call
320 if (!MoveFileEx (cygpath (String ("/") + fn
+
321 ".new").cstr_oneuse(),
322 cygpath (String ("/") + fn
).cstr_oneuse(),
323 MOVEFILE_DELAY_UNTIL_REBOOT
|
324 MOVEFILE_REPLACE_EXISTING
))
326 replaceOnRebootFailed (fn
);
330 replaceOnRebootSucceeded (fn
, rebootneeded
);
336 progress (tmp
->tell ());
341 total_bytes_sofar
+= package_bytes
;
346 int df
= diskfull (get_root_dir ().cstr_oneuse());
347 Progress
.SetBar3 (df
);
355 /* install a package, install both the binary and source aspects if needed */
357 install_one (packagemeta
& pkg
)
361 Installer myInstaller
;
362 if (pkg
.installed
!= pkg
.desired
&& pkg
.desired
.picked())
365 myInstaller
.installOneSource (pkg
, *pkg
.desired
.source(), "cygfile://","/",
368 pkg
.installed
= pkg
.desired
;
370 if (pkg
.desired
.sourcePackage().picked())
372 myInstaller
.installOneSource (pkg
, *pkg
.desired
.sourcePackage().source(), "cygfile://","/usr/src/",
375 /* FIXME: make a upgrade method and reinstate this */
382 int n
= strcmp (pi
->version
, pkg
->installed
->version
);
386 msg
= "Reinstalling";
394 msg
+= " previous version...";
400 msg
+= " test version...";
403 /* FIXME: log this somehow */
406 SetWindowText (ins_action
, msg
.cstr_oneuse());
407 log (LOG_PLAIN
, msg
+ " " + file
);
414 check_for_old_cygwin ()
416 char buf
[_MAX_PATH
+ sizeof ("\\cygwin1.dll")];
417 if (!GetSystemDirectory (buf
, sizeof (buf
)))
419 strcat (buf
, "\\cygwin1.dll");
420 if (_access (buf
, 0) != 0)
423 char msg
[sizeof (buf
) + 132];
425 "An old version of cygwin1.dll was found here:\r\n%s\r\nDelete?",
428 (NULL
, msg
, "What's that doing there?",
429 MB_YESNO
| MB_ICONQUESTION
| MB_TASKMODAL
))
432 if (!DeleteFile (buf
))
434 sprintf (msg
, "Couldn't delete file %s.\r\n"
435 "Is the DLL in use by another application?\r\n"
436 "You should delete the old version of cygwin1.dll\r\n"
437 "at your earliest convenience.", buf
);
438 MessageBox (NULL
, buf
, "Couldn't delete file",
439 MB_OK
| MB_ICONEXCLAMATION
| MB_TASKMODAL
);
450 do_install_thread (HINSTANCE h
, HWND owner
)
455 num_installs
= 0, num_uninstalls
= 0, num_replacements
= 0;
456 rebootneeded
= false;
458 io_stream::mkpath_p (PATH_TO_DIR
, String ("file://") + get_root_dir ());
460 for (i
= 0; Installer::StandardDirs
[i
]; i
++)
462 String p
= cygpath (Installer::StandardDirs
[i
]);
464 io_stream::mkpath_p (PATH_TO_DIR
, String ("file://") + p
);
467 /* Create /var/run/utmp */
468 io_stream
*utmp
= io_stream::open ("cygfile:///var/run/utmp", "wb");
471 Installer myInstaller
;
472 myInstaller
.initDialog();
475 total_bytes_sofar
= 0;
477 int df
= diskfull (get_root_dir ().cstr_oneuse());
478 Progress
.SetBar3 (df
);
480 int istext
= (root_text
== IDC_ROOT_TEXT
) ? 1 : 0;
481 int issystem
= (root_scope
== IDC_ROOT_SYSTEM
) ? 1 : 0;
483 create_mount ("/", get_root_dir (), istext
, issystem
);
484 create_mount ("/usr/bin", cygpath ("/bin"), istext
, issystem
);
485 create_mount ("/usr/lib", cygpath ("/lib"), istext
, issystem
);
486 set_cygdrive_flags (istext
, issystem
);
488 /* Let's hope people won't uninstall packages before installing [b]ash */
492 for (vector
<packagemeta
*>::iterator i
= db
.packages
.begin ();
493 i
!= db
.packages
.end (); ++i
)
495 packagemeta
& pkg
= **i
;
497 if (pkg
.desired
.changeRequested())
499 if (pkg
.desired
.picked())
503 md5_one (*pkg
.desired
.source ());
507 if (yesno (owner
, IDS_SKIP_PACKAGE
, e
->what()) == IDYES
)
508 pkg
.desired
.pick (false);
510 if (pkg
.desired
.picked())
511 total_bytes
+= pkg
.desired
.source()->size
;
513 if (pkg
.desired
.sourcePackage ().picked())
517 md5_one (*pkg
.desired
.sourcePackage ().source ());
521 if (yesno (owner
, IDS_SKIP_PACKAGE
, e
->what()) == IDYES
)
522 pkg
.desired
.sourcePackage ().pick (false);
524 if (pkg
.desired
.sourcePackage().picked())
525 total_bytes
+= pkg
.desired
.sourcePackage ().source()->size
;
530 /* start with uninstalls - remove files that new packages may replace */
531 for (vector
<packagemeta
*>::iterator i
= db
.packages
.begin ();
532 i
!= db
.packages
.end (); ++i
)
534 packagemeta
& pkg
= **i
;
535 if (pkg
.installed
&& (!pkg
.desired
|| (pkg
.desired
!= pkg
.installed
&&
536 pkg
.desired
.picked ())))
537 myInstaller
.uninstallOne (pkg
);
540 /* now in-place binary upgrades/reinstalls, as these may remove fils
541 * that have been moved into new packages
544 for (vector
<packagemeta
*>::iterator i
= db
.packages
.begin ();
545 i
!= db
.packages
.end (); ++i
)
547 packagemeta
& pkg
= **i
;
548 if (pkg
.installed
&& pkg
.desired
.picked())
552 e
+= myInstaller
.replaceOne (pkg
);
558 if (yesno (owner
, IDS_INSTALL_ERROR
, e
->what()) != IDYES
)
560 log (LOG_TIMESTAMP
, String ("User cancelled setup after install error"));
561 LogSingleton::GetInstance().exit (1);
568 for (vector
<packagemeta
*>::iterator i
= db
.packages
.begin ();
569 i
!= db
.packages
.end (); ++i
)
571 packagemeta
& pkg
= **i
;
573 if (pkg
.desired
&& pkg
.desired
.changeRequested())
578 e
+= install_one (pkg
);
584 if (yesno (owner
, IDS_INSTALL_ERROR
, e
->what()) != IDYES
)
586 log (LOG_TIMESTAMP
, String ("User cancelled setup after install error"));
587 LogSingleton::GetInstance().exit (1);
592 } // end of big package loop
595 note (owner
, IDS_REBOOT_REQUIRED
);
598 if ((temperr
= db
.flush ()))
600 const char *err
= strerror (temperr
);
602 err
= "(unknown error)";
603 fatal (owner
, IDS_ERR_OPEN_WRITE
, "Package Database",
608 check_for_old_cygwin ();
609 if (num_installs
== 0 && num_uninstalls
== 0)
611 if (!unattended_mode
) exit_msg
= IDS_NOTHING_INSTALLED
;
614 if (num_installs
== 0)
616 if (!unattended_mode
) exit_msg
= IDS_UNINSTALL_COMPLETE
;
621 exit_msg
= IDS_INSTALL_INCOMPLETE
;
622 else if (!unattended_mode
)
623 exit_msg
= IDS_INSTALL_COMPLETE
;
627 do_install_reflector (void *p
)
630 context
= (HANDLE
*) p
;
632 do_install_thread ((HINSTANCE
) context
[0], (HWND
) context
[1]);
634 // Tell the progress page that we're done downloading
635 Progress
.PostMessage (WM_APP_INSTALL_THREAD_COMPLETE
);
640 static HANDLE context
[2];
643 do_install (HINSTANCE h
, HWND owner
)
649 CreateThread (NULL
, 0, do_install_reflector
, context
, 0, &threadID
);
652 void md5_one (const packagesource
& source
)
654 if (source
.md5
.isSet())
656 // check the MD5 sum of the cached file here
657 io_stream
*thefile
= io_stream::open (source
.Cached (), "rb");
659 throw new Exception (TOSTRING(__LINE__
) " " __FILE__
, String ("IO Error opening ") + source
.Cached(), APPERR_IO_ERROR
);
663 unsigned char buffer
[16384];
665 while ((count
= thefile
->read (buffer
, 16384)) > 0)
666 md5_append (&pns
, buffer
, count
);
669 throw new Exception (TOSTRING(__LINE__
) " " __FILE__
, String ("IO Error reading ") + source
.Cached(), APPERR_IO_ERROR
);
671 md5_byte_t tempdigest
[16];
672 md5_finish(&pns
, tempdigest
);
674 tempMD5
.set (tempdigest
);
676 log (LOG_BABBLE
, String ("For file ") + source
.Cached() + " ini digest is " + source
.md5
.print() + " file digest is " + tempMD5
.print());
678 if (source
.md5
!= tempMD5
)
679 throw new Exception (TOSTRING(__LINE__
) " " __FILE__
, String ("Checksum failure for ") + source
.Cached(), APPERR_CORRUPT_PACKAGE
);