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 next_dialog
= IDD_DESKTOP
;
460 io_stream::mkpath_p (PATH_TO_DIR
, String ("file://") + get_root_dir ());
462 for (i
= 0; Installer::StandardDirs
[i
]; i
++)
464 String p
= cygpath (Installer::StandardDirs
[i
]);
466 io_stream::mkpath_p (PATH_TO_DIR
, String ("file://") + p
);
469 /* Create /var/run/utmp */
470 io_stream
*utmp
= io_stream::open ("cygfile:///var/run/utmp", "wb");
473 Installer myInstaller
;
474 myInstaller
.initDialog();
477 total_bytes_sofar
= 0;
479 int df
= diskfull (get_root_dir ().cstr_oneuse());
480 Progress
.SetBar3 (df
);
482 int istext
= (root_text
== IDC_ROOT_TEXT
) ? 1 : 0;
483 int issystem
= (root_scope
== IDC_ROOT_SYSTEM
) ? 1 : 0;
485 create_mount ("/", get_root_dir (), istext
, issystem
);
486 create_mount ("/usr/bin", cygpath ("/bin"), istext
, issystem
);
487 create_mount ("/usr/lib", cygpath ("/lib"), istext
, issystem
);
488 set_cygdrive_flags (istext
, issystem
);
490 /* Let's hope people won't uninstall packages before installing [b]ash */
494 for (vector
<packagemeta
*>::iterator i
= db
.packages
.begin ();
495 i
!= db
.packages
.end (); ++i
)
497 packagemeta
& pkg
= **i
;
499 if (pkg
.desired
.changeRequested())
501 if (pkg
.desired
.picked())
505 md5_one (*pkg
.desired
.source ());
509 if (yesno (owner
, IDS_SKIP_PACKAGE
, e
->what()) == IDYES
)
510 pkg
.desired
.pick (false);
512 if (pkg
.desired
.picked())
513 total_bytes
+= pkg
.desired
.source()->size
;
515 if (pkg
.desired
.sourcePackage ().picked())
519 md5_one (*pkg
.desired
.sourcePackage ().source ());
523 if (yesno (owner
, IDS_SKIP_PACKAGE
, e
->what()) == IDYES
)
524 pkg
.desired
.sourcePackage ().pick (false);
526 if (pkg
.desired
.sourcePackage().picked())
527 total_bytes
+= pkg
.desired
.sourcePackage ().source()->size
;
532 /* start with uninstalls - remove files that new packages may replace */
533 for (vector
<packagemeta
*>::iterator i
= db
.packages
.begin ();
534 i
!= db
.packages
.end (); ++i
)
536 packagemeta
& pkg
= **i
;
537 if (pkg
.installed
&& (!pkg
.desired
|| (pkg
.desired
!= pkg
.installed
&&
538 pkg
.desired
.picked ())))
539 myInstaller
.uninstallOne (pkg
);
542 /* now in-place binary upgrades/reinstalls, as these may remove fils
543 * that have been moved into new packages
546 for (vector
<packagemeta
*>::iterator i
= db
.packages
.begin ();
547 i
!= db
.packages
.end (); ++i
)
549 packagemeta
& pkg
= **i
;
550 if (pkg
.installed
&& pkg
.desired
.picked())
554 e
+= myInstaller
.replaceOne (pkg
);
560 if (yesno (owner
, IDS_INSTALL_ERROR
, e
->what()) != IDYES
)
562 log (LOG_TIMESTAMP
, String ("User cancelled setup after install error"));
563 LogSingleton::GetInstance().exit (1);
570 for (vector
<packagemeta
*>::iterator i
= db
.packages
.begin ();
571 i
!= db
.packages
.end (); ++i
)
573 packagemeta
& pkg
= **i
;
575 if (pkg
.desired
&& pkg
.desired
.changeRequested())
580 e
+= install_one (pkg
);
586 if (yesno (owner
, IDS_INSTALL_ERROR
, e
->what()) != IDYES
)
588 log (LOG_TIMESTAMP
, String ("User cancelled setup after install error"));
589 LogSingleton::GetInstance().exit (1);
594 } // end of big package loop
597 note (owner
, IDS_REBOOT_REQUIRED
);
600 if ((temperr
= db
.flush ()))
602 const char *err
= strerror (temperr
);
604 err
= "(unknown error)";
605 fatal (owner
, IDS_ERR_OPEN_WRITE
, "Package Database",
610 check_for_old_cygwin ();
611 if (num_installs
== 0 && num_uninstalls
== 0)
613 if (!unattended_mode
) exit_msg
= IDS_NOTHING_INSTALLED
;
616 if (num_installs
== 0)
618 if (!unattended_mode
) exit_msg
= IDS_UNINSTALL_COMPLETE
;
623 exit_msg
= IDS_INSTALL_INCOMPLETE
;
624 else if (!unattended_mode
)
625 exit_msg
= IDS_INSTALL_COMPLETE
;
629 do_install_reflector (void *p
)
632 context
= (HANDLE
*) p
;
634 do_install_thread ((HINSTANCE
) context
[0], (HWND
) context
[1]);
636 // Tell the progress page that we're done downloading
637 Progress
.PostMessage (WM_APP_INSTALL_THREAD_COMPLETE
);
642 static HANDLE context
[2];
645 do_install (HINSTANCE h
, HWND owner
)
651 CreateThread (NULL
, 0, do_install_reflector
, context
, 0, &threadID
);
654 void md5_one (const packagesource
& source
)
656 if (source
.md5
.isSet())
658 // check the MD5 sum of the cached file here
659 io_stream
*thefile
= io_stream::open (source
.Cached (), "rb");
661 throw new Exception ("__LINE__ __FILE__", String ("IO Error opening ") + source
.Cached(), APPERR_IO_ERROR
);
665 unsigned char buffer
[16384];
667 while ((count
= thefile
->read (buffer
, 16384)) > 0)
668 md5_append (&pns
, buffer
, count
);
671 throw new Exception ("__LINE__ __FILE__", String ("IO Error reading ") + source
.Cached(), APPERR_IO_ERROR
);
673 md5_byte_t tempdigest
[16];
674 md5_finish(&pns
, tempdigest
);
676 tempMD5
.set (tempdigest
);
678 log (LOG_BABBLE
, String ("For file ") + source
.Cached() + " ini digest is " + source
.md5
.print() + " file digest is " + tempMD5
.print());
680 if (source
.md5
!= tempMD5
)
681 throw new Exception ("__LINE__ __FILE__", String ("Checksum failure for ") + source
.Cached(), APPERR_CORRUPT_PACKAGE
);