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";
27 #include "getopt++/BoolOption.h"
28 #include "csu_util/MD5Sum.h"
29 #include "LogSingleton.h"
37 #include <sys/types.h>
51 #include "filemanip.h"
52 #include "io_stream.h"
54 #include "compress_gz.h"
56 #include "archive_tar.h"
59 #include "package_db.h"
60 #include "package_meta.h"
61 #include "package_version.h"
62 #include "package_source.h"
65 #include "Exception.h"
69 extern ThreeBarProgressPage Progress
;
71 static int total_bytes
= 0;
72 static int total_bytes_sofar
= 0;
73 static int package_bytes
= 0;
75 static BoolOption
NoReplaceOnReboot (false, 'r', "no-replaceonreboot",
76 "Disable replacing in-use files on next "
87 static std_dirs_t StandardDirs
[];
90 void progress (int bytes
);
91 void preremoveOne (packagemeta
&);
92 void uninstallOne (packagemeta
&);
93 void replaceOnRebootFailed (const std::string
& fn
);
94 void replaceOnRebootSucceeded (const std::string
& fn
, bool &rebootneeded
);
95 void installOne (packagemeta
&pkg
, const packageversion
&ver
,
96 packagesource
&source
,
97 const std::string
& , const std::string
&, HWND
);
101 Installer::Installer() : errors(0)
106 Installer::initDialog()
108 Progress
.SetText2 ("");
109 Progress
.SetText3 ("");
113 Installer::progress (int bytes
)
115 if (package_bytes
> 0)
116 Progress
.SetBar1 (bytes
, package_bytes
);
119 Progress
.SetBar2 (total_bytes_sofar
+ bytes
, total_bytes
);
123 Installer::StandardDirs
[] = {
130 { "/usr/bin", 0755 },
131 { "/usr/lib", 0755 },
132 { "/usr/src", 0755 },
133 { "/usr/local", 0755 },
134 { "/usr/local/bin", 0755 },
135 { "/usr/local/etc", 0755 },
136 { "/usr/local/lib", 0755 },
137 { "/usr/tmp", 01777 },
138 { "/var/log", 01777 },
139 { "/var/run", 01777 },
140 { "/var/tmp", 01777 },
144 static int num_installs
, num_uninstalls
;
145 static void md5_one (const packagesource
& source
);
148 Installer::preremoveOne (packagemeta
& pkg
)
150 Progress
.SetText1 ("Running preremove script...");
151 Progress
.SetText2 (pkg
.name
.c_str());
152 log (LOG_PLAIN
) << "Running preremove script for " << pkg
.name
<< endLog
;
153 try_run_script ("/etc/preremove/", pkg
.name
, ".sh");
154 try_run_script ("/etc/preremove/", pkg
.name
, ".bat");
158 Installer::uninstallOne (packagemeta
& pkg
)
160 Progress
.SetText1 ("Uninstalling...");
161 Progress
.SetText2 (pkg
.name
.c_str());
162 log (LOG_PLAIN
) << "Uninstalling " << pkg
.name
<< endLog
;
167 /* log failed scheduling of replace-on-reboot of a given file. */
168 /* also increment errors. */
170 Installer::replaceOnRebootFailed (const std::string
& fn
)
172 log (LOG_TIMESTAMP
) << "Unable to schedule reboot replacement of file "
173 << cygpath("/" + fn
) << " with " << cygpath("/" + fn
+ ".new")
174 << " (Win32 Error " << GetLastError() << ")" << endLog
;
178 /* log successful scheduling of replace-on-reboot of a given file. */
179 /* also set rebootneeded. */
181 Installer::replaceOnRebootSucceeded (const std::string
& fn
, bool &rebootneeded
)
183 log (LOG_TIMESTAMP
) << "Scheduled reboot replacement of file "
184 << cygpath("/" + fn
) << " with " << cygpath("/" + fn
+ ".new") << endLog
;
188 #define MB_RETRYCONTINUE 7
189 #if !defined(IDCONTINUE)
190 #define IDCONTINUE IDCANCEL
193 static HHOOK hMsgBoxHook
;
194 LRESULT CALLBACK
CBTProc(int nCode
, WPARAM wParam
, LPARAM lParam
) {
199 if (GetDlgItem(hWnd
, IDCANCEL
) != NULL
)
200 SetDlgItemText(hWnd
, IDCANCEL
, "Continue");
201 UnhookWindowsHookEx(hMsgBoxHook
);
203 return CallNextHookEx(hMsgBoxHook
, nCode
, wParam
, lParam
);
206 int _custom_MessageBox(HWND hWnd
, LPCTSTR szText
, LPCTSTR szCaption
, UINT uType
) {
208 bool retry_continue
= (uType
& MB_TYPEMASK
) == MB_RETRYCONTINUE
;
209 if (retry_continue
) {
210 uType
&= ~MB_TYPEMASK
; uType
|= MB_RETRYCANCEL
;
211 // Install a window hook, so we can intercept the message-box
212 // creation, and customize it
213 // Only install for THIS thread!!!
214 hMsgBoxHook
= SetWindowsHookEx(WH_CBT
, CBTProc
, NULL
, GetCurrentThreadId());
216 retval
= MessageBox(hWnd
, szText
, szCaption
, uType
);
217 // Intercept the return value for less confusing results
218 if (retry_continue
&& retval
== IDCANCEL
)
224 #define MessageBox _custom_MessageBox
226 static char all_null
[512];
228 /* install one source at a given prefix. */
230 Installer::installOne (packagemeta
&pkgm
, const packageversion
&ver
,
231 packagesource
&source
,
232 const std::string
& prefixURL
,
233 const std::string
& prefixPath
,
236 if (!source
.Canonical())
238 Progress
.SetText1 ("Installing");
239 Progress
.SetText2 (source
.Base () ? source
.Base () : "(unknown)");
241 io_stream
*pkgfile
= NULL
;
243 if (!source
.Cached() || !io_stream::exists (source
.Cached ())
244 || !(pkgfile
= io_stream::open (source
.Cached (), "rb", 0)))
246 note (NULL
, IDS_ERR_OPEN_READ
, source
.Cached (), "No such file");
252 /* At this point pkgfile is an opened io_stream to either a .tar.bz2 file,
253 a .tar.gz file, a .tar.lzma file, or just a .tar file. Try it first as
254 a compressed file and if that fails try opening it as a tar directly.
257 Note on io_stream pointer management:
259 Both the archive and decompress classes take ownership of the io_stream
260 pointer they were opened with, meaning they delete it in their dtor. So
261 deleting tarstream should also result in deleting the underlying
262 try_decompress and pkgfile io_streams as well. */
264 archive
*tarstream
= NULL
;
265 io_stream
*try_decompress
= NULL
;
267 if ((try_decompress
= compress::decompress (pkgfile
)) != NULL
)
269 if ((tarstream
= archive::extract (try_decompress
)) == NULL
)
271 /* Decompression succeeded but we couldn't grok it as a valid tar
275 if ((len
= try_decompress
->peek (c
, 512)) < 0
276 || !memcmp (c
, all_null
, len
))
277 /* In some cases, maintainers have uploaded bzipped
278 0-byte files as dummy packages instead of empty tar files.
279 This is common enough that we should not treat this as an
281 Same goes for tar archives consisting of a big block of
282 all zero bytes (the famous 46 bytes tar archives). */
286 note (NULL
, IDS_ERR_OPEN_READ
, source
.Cached (),
287 "Invalid or unsupported tar format");
290 delete try_decompress
;
294 else if ((tarstream
= archive::extract (pkgfile
)) == NULL
)
296 /* Not a compressed tarball, not a plain tarball, give up. */
298 note (NULL
, IDS_ERR_OPEN_READ
, source
.Cached (),
299 "Unrecognisable file format");
304 /* For binary packages, create a manifest in /etc/setup/ that lists the
305 filename of each file that was unpacked. */
307 io_stream
*lst
= NULL
;
308 if (ver
.Type () == package_binary
)
310 std::string lstfn
= "cygfile:///etc/setup/" + pkgm
.name
+ ".lst.gz";
313 if ((tmp
= io_stream::open (lstfn
, "wb", 0644)) == NULL
)
314 log (LOG_PLAIN
) << "Warning: Unable to create lst file " + lstfn
+
315 " - uninstall of this package will leave orphaned files." << endLog
;
318 lst
= new compress_gz (tmp
, "w9");
323 log (LOG_PLAIN
) << "Warning: gzip unable to write to lst file " +
324 lstfn
+ " - uninstall of this package will leave orphaned files."
330 bool error_in_this_package
= false;
331 bool ignoreExtractErrors
= unattended_mode
;
333 package_bytes
= source
.size
;
334 log (LOG_PLAIN
) << "Extracting from " << source
.Cached () << endLog
;
337 while ((fn
= tarstream
->next_file_name ()).size ())
339 std::string canonicalfn
= prefixPath
+ fn
;
340 Progress
.SetText3 (canonicalfn
.c_str ());
341 log (LOG_BABBLE
) << "Installing file " << prefixURL
<< prefixPath
345 std::string tmp
= fn
+ "\n";
346 lst
->write (tmp
.c_str(), tmp
.size());
348 if (Script::isAScript (fn
))
349 pkgm
.desired
.addScript (Script (canonicalfn
));
351 bool firstIteration
= true;
352 while (archive::extract_file (tarstream
, prefixURL
, prefixPath
) != 0)
354 if (!ignoreExtractErrors
)
356 char msg
[fn
.size() + 300];
358 "%snable to extract /%s -- the file is in use.\r\n"
359 "Please stop %s Cygwin processes and select \"Retry\", or\r\n"
360 "select \"Continue\" to go on anyway (you will need to reboot).\r\n",
361 firstIteration
?"U":"Still u", fn
.c_str(), firstIteration
?"all":"ALL");
362 switch (MessageBox (owner
, msg
, "In-use files detected",
363 MB_RETRYCONTINUE
| MB_ICONWARNING
| MB_TASKMODAL
))
367 firstIteration
= false;
370 ignoreExtractErrors
= true;
375 // fall through to previous functionality
377 if (NoReplaceOnReboot
)
380 error_in_this_package
= true;
381 log (LOG_PLAIN
) << "Not replacing in-use file " << prefixURL
382 << prefixPath
<< fn
<< endLog
;
386 /* Extract a copy of the file with extension .new appended and
387 indicate it should be replaced on the next reboot. */
388 if (archive::extract_file (tarstream
, prefixURL
, prefixPath
,
391 log (LOG_PLAIN
) << "Unable to install file " << prefixURL
392 << prefixPath
<< fn
<< ".new" << endLog
;
394 error_in_this_package
= true;
398 std::string s
= cygpath ("/" + fn
+ ".new"),
399 d
= cygpath ("/" + fn
);
403 /* Get the short file names */
404 char s2
[MAX_PATH
], d2
[MAX_PATH
];
406 GetShortPathName (s
.c_str (), s2
, MAX_PATH
),
407 dlen
= GetShortPathName (d
.c_str (), d2
, MAX_PATH
);
409 if (!slen
|| slen
> MAX_PATH
|| !dlen
|| dlen
> MAX_PATH
)
410 replaceOnRebootFailed(fn
);
412 if (!WritePrivateProfileString ("rename", d2
, s2
,
414 replaceOnRebootFailed (fn
);
416 replaceOnRebootSucceeded (fn
, rebootneeded
);
420 /* XXX FIXME: prefix may not be / for in use files -
421 * although it most likely is
422 * - we need a io method to get win32 paths
423 * or to wrap this system call
425 WCHAR sname
[s
.size () + 7];
426 WCHAR dname
[d
.size () + 7];
427 /* Windows 2000 has a bug: Prepending \\?\ does not
428 * work in conjunction with MOVEFILE_DELAY_UNTIL_REBOOT.
429 * So in case of Windows 2000 we just convert the path
430 * to wide char and hope for the best. */
431 if (OSMajorVersion () == 5 && OSMinorVersion () == 0)
433 mbstowcs (sname
, s
.c_str (), s
.size () + 7);
434 mbstowcs (dname
, d
.c_str (), d
.size () + 7);
438 mklongpath (sname
, s
.c_str (), s
.size () + 7);
439 mklongpath (dname
, d
.c_str (), d
.size () + 7);
441 if (!MoveFileExW (sname
, dname
,
442 MOVEFILE_DELAY_UNTIL_REBOOT
|
443 MOVEFILE_REPLACE_EXISTING
))
444 replaceOnRebootFailed (fn
);
446 replaceOnRebootSucceeded (fn
, rebootneeded
);
450 // We're done with this file
453 progress (pkgfile
->tell ());
461 total_bytes_sofar
+= package_bytes
;
464 int df
= diskfull (get_root_dir ().c_str ());
465 Progress
.SetBar3 (df
);
467 if (ver
.Type () == package_binary
&& !error_in_this_package
)
468 pkgm
.installed
= ver
;
472 check_for_old_cygwin (HWND owner
)
474 char buf
[MAX_PATH
+ sizeof ("\\cygwin1.dll")];
475 if (!GetSystemDirectory (buf
, sizeof (buf
)))
477 strcat (buf
, "\\cygwin1.dll");
478 if (_access (buf
, 0) != 0)
481 char msg
[sizeof (buf
) + 132];
483 "An old version of cygwin1.dll was found here:\r\n%s\r\nDelete?",
486 (owner
, msg
, "What's that doing there?",
487 MB_YESNO
| MB_ICONQUESTION
| MB_TASKMODAL
))
490 if (!DeleteFile (buf
))
492 sprintf (msg
, "Couldn't delete file %s.\r\n"
493 "Is the DLL in use by another application?\r\n"
494 "You should delete the old version of cygwin1.dll\r\n"
495 "at your earliest convenience.", buf
);
496 MessageBox (owner
, buf
, "Couldn't delete file",
497 MB_OK
| MB_ICONEXCLAMATION
| MB_TASKMODAL
);
508 do_install_thread (HINSTANCE h
, HWND owner
)
512 num_installs
= 0, num_uninstalls
= 0;
513 rebootneeded
= false;
515 io_stream::mkpath_p (PATH_TO_DIR
,
516 std::string("file://") + std::string(get_root_dir()),
519 for (i
= 0; Installer::StandardDirs
[i
].name
; i
++)
521 std::string p
= cygpath (Installer::StandardDirs
[i
].name
);
523 io_stream::mkpath_p (PATH_TO_DIR
, "file://" + p
,
524 Installer::StandardDirs
[i
].mode
);
527 /* Create /var/run/utmp */
528 io_stream
*utmp
= io_stream::open ("cygfile:///var/run/utmp", "wb", 0666);
531 Installer myInstaller
;
532 myInstaller
.initDialog();
535 total_bytes_sofar
= 0;
537 int df
= diskfull (get_root_dir ().c_str());
538 Progress
.SetBar3 (df
);
540 /* Writes Cygwin/setup/rootdir registry value */
542 create_install_root ();
545 int istext
= (root_text
== IDC_ROOT_TEXT
) ? 1 : 0;
546 int issystem
= (root_scope
== IDC_ROOT_SYSTEM
) ? 1 : 0;
548 create_mount ("/", get_root_dir (), istext
, issystem
);
549 create_mount ("/usr/bin", cygpath ("/bin"), istext
, issystem
);
550 create_mount ("/usr/lib", cygpath ("/lib"), istext
, issystem
);
551 set_cygdrive_flags (istext
, issystem
);
554 /* Let's hope people won't uninstall packages before installing [b]ash */
557 vector
<packagemeta
*> install_q
, uninstall_q
, sourceinstall_q
;
560 for (vector
<packagemeta
*>::iterator i
= db
.packages
.begin ();
561 i
!= db
.packages
.end (); ++i
)
563 packagemeta
& pkg
= **i
;
565 if (pkg
.desired
.picked())
569 md5_one (*pkg
.desired
.source ());
573 if (yesno (owner
, IDS_SKIP_PACKAGE
, e
->what()) == IDYES
)
574 pkg
.desired
.pick (false, &pkg
);
576 if (pkg
.desired
.picked())
578 total_bytes
+= pkg
.desired
.source()->size
;
579 install_q
.push_back (&pkg
);
583 if (pkg
.desired
.sourcePackage ().picked())
587 md5_one (*pkg
.desired
.sourcePackage ().source ());
591 if (yesno (owner
, IDS_SKIP_PACKAGE
, e
->what()) == IDYES
)
592 pkg
.desired
.sourcePackage ().pick (false, &pkg
);
594 if (pkg
.desired
.sourcePackage().picked())
596 total_bytes
+= pkg
.desired
.sourcePackage ().source()->size
;
597 sourceinstall_q
.push_back (&pkg
);
601 if ((pkg
.installed
&& pkg
.desired
!= pkg
.installed
)
602 || pkg
.installed
.picked ())
604 uninstall_q
.push_back (&pkg
);
608 /* start with uninstalls - remove files that new packages may replace */
609 for (vector
<packagemeta
*>::iterator i
= uninstall_q
.begin ();
610 i
!= uninstall_q
.end (); ++i
)
612 myInstaller
.preremoveOne (**i
);
614 for (vector
<packagemeta
*>::iterator i
= uninstall_q
.begin ();
615 i
!= uninstall_q
.end (); ++i
)
617 myInstaller
.uninstallOne (**i
);
620 for (vector
<packagemeta
*>::iterator i
= install_q
.begin ();
621 i
!= install_q
.end (); ++i
)
623 packagemeta
& pkg
= **i
;
625 myInstaller
.installOne (pkg
, pkg
.desired
, *pkg
.desired
.source(),
626 "cygfile://", "/", owner
);
630 if (yesno (owner
, IDS_INSTALL_ERROR
, e
->what()) != IDYES
)
633 << "User cancelled setup after install error" << endLog
;
634 LogSingleton::GetInstance().exit (1);
640 for (vector
<packagemeta
*>::iterator i
= sourceinstall_q
.begin ();
641 i
!= sourceinstall_q
.end (); ++i
)
643 packagemeta
& pkg
= **i
;
644 myInstaller
.installOne (pkg
, pkg
.desired
.sourcePackage(),
645 *pkg
.desired
.sourcePackage().source(),
646 "cygfile://", "/usr/src/", owner
);
650 note (owner
, IDS_REBOOT_REQUIRED
);
653 if ((temperr
= db
.flush ()))
655 const char *err
= strerror (temperr
);
657 err
= "(unknown error)";
658 fatal (owner
, IDS_ERR_OPEN_WRITE
, "Package Database",
662 if (!myInstaller
.errors
)
663 check_for_old_cygwin (owner
);
664 if (num_installs
== 0 && num_uninstalls
== 0)
666 if (!unattended_mode
) exit_msg
= IDS_NOTHING_INSTALLED
;
669 if (num_installs
== 0)
671 if (!unattended_mode
) exit_msg
= IDS_UNINSTALL_COMPLETE
;
675 if (myInstaller
.errors
)
676 exit_msg
= IDS_INSTALL_INCOMPLETE
;
677 else if (!unattended_mode
)
678 exit_msg
= IDS_INSTALL_COMPLETE
;
681 exit_msg
= IDS_REBOOT_REQUIRED
;
685 do_install_reflector (void *p
)
688 context
= (HANDLE
*) p
;
692 do_install_thread ((HINSTANCE
) context
[0], (HWND
) context
[1]);
694 // Tell the progress page that we're done downloading
695 Progress
.PostMessage (WM_APP_INSTALL_THREAD_COMPLETE
);
697 TOPLEVEL_CATCH("install");
702 static HANDLE context
[2];
705 do_install (HINSTANCE h
, HWND owner
)
711 CreateThread (NULL
, 0, do_install_reflector
, context
, 0, &threadID
);
714 void md5_one (const packagesource
& pkgsource
)
716 if (pkgsource
.md5
.isSet() && pkgsource
.Cached ())
718 std::string
fullname (pkgsource
.Cached ());
720 io_stream
*thefile
= io_stream::open (fullname
, "rb", 0);
722 throw new Exception (TOSTRING (__LINE__
) " " __FILE__
,
723 std::string ("IO Error opening ") + fullname
,
728 log (LOG_BABBLE
) << "Checking MD5 for " << fullname
<< endLog
;
730 Progress
.SetText1 ((std::string ("Checking MD5 for ")
731 + pkgsource
.Base ()).c_str ());
732 Progress
.SetText4 ("Progress:");
733 Progress
.SetBar1 (0);
735 unsigned char buffer
[16384];
737 while ((count
= thefile
->read (buffer
, sizeof (buffer
))) > 0)
739 tempMD5
.append (buffer
, count
);
740 Progress
.SetBar1 (thefile
->tell (), thefile
->get_size ());
744 throw new Exception (TOSTRING(__LINE__
) " " __FILE__
,
745 "IO Error reading " + fullname
,
750 if (pkgsource
.md5
!= tempMD5
)
752 log (LOG_BABBLE
) << "INVALID PACKAGE: " << fullname
753 << " - MD5 mismatch: Ini-file: " << pkgsource
.md5
.str()
754 << " != On-disk: " << tempMD5
.str() << endLog
;
755 throw new Exception (TOSTRING(__LINE__
) " " __FILE__
,
756 "MD5 failure for " + fullname
,
757 APPERR_CORRUPT_PACKAGE
);
760 log (LOG_BABBLE
) << "MD5 verified OK: " << fullname
<< " "
761 << pkgsource
.md5
.str() << endLog
;