]> cygwin.com Git - cygwin-apps/setup.git/blob - install.cc
2003-10-23 Jerry D. Hedden <jerry@hedden.us>
[cygwin-apps/setup.git] / install.cc
1 /*
2 * Copyright (c) 2000, Red Hat, Inc.
3 * Copyright (c) 2003, Robert Collins <rbtcollins@hotmail.com>
4 *
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.
9 *
10 * A copy of the GNU General Public License can be found at
11 * http://www.gnu.org/
12 *
13 * Originally Written by DJ Delorie <dj@cygnus.com>
14 *
15 */
16
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. */
22
23 #if 0
24 static const char *cvsid = "\n%%% $Id$\n";
25 #endif
26
27 #include "win32.h"
28 #include "commctrl.h"
29
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <unistd.h>
33 #include <sys/types.h>
34 #include <sys/stat.h>
35 #include <errno.h>
36 #include <process.h>
37
38 #include "zlib/zlib.h"
39
40 #include "resource.h"
41 #include "dialog.h"
42 #include "geturl.h"
43 #include "state.h"
44 #include "diskfull.h"
45 #include "msg.h"
46 #include "mount.h"
47 #include "log.h"
48 #include "mount.h"
49 #include "filemanip.h"
50 #include "io_stream.h"
51 #include "compress.h"
52 #include "compress_gz.h"
53 #include "archive.h"
54 #include "archive_tar.h"
55 #include "script.h"
56
57 #include "package_db.h"
58 #include "package_meta.h"
59 #include "package_version.h"
60 #include "package_source.h"
61
62 #include "port.h"
63
64 #include "threebar.h"
65
66 #include "md5.h"
67
68 #include "Exception.h"
69 #include "getopt++/BoolOption.h"
70
71 using namespace std;
72
73 extern ThreeBarProgressPage Progress;
74
75 static int total_bytes = 0;
76 static int total_bytes_sofar = 0;
77 static int package_bytes = 0;
78
79 static BoolOption NoReplaceOnReboot (false, 'r', "no-replaceonreboot",
80 "Disable replacing in-use files on next "
81 "reboot.");
82
83 class Installer
84 {
85 public:
86 static const char *StandardDirs[];
87 Installer();
88 void initDialog();
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);
95 int errors;
96 };
97
98 Installer::Installer() : errors(0)
99 {
100 }
101
102 void
103 Installer::initDialog()
104 {
105 Progress.SetText2 ("");
106 Progress.SetText3 ("");
107 }
108
109 void
110 Installer::progress (int bytes)
111 {
112 if (package_bytes > 0)
113 Progress.SetBar1 (bytes, package_bytes);
114
115 if (total_bytes > 0)
116 Progress.SetBar2 (total_bytes_sofar + bytes, total_bytes);
117 }
118
119 const char *
120 Installer::StandardDirs[] = {
121 "/bin",
122 "/etc",
123 "/lib",
124 "/tmp",
125 "/usr",
126 "/usr/bin",
127 "/usr/lib",
128 "/usr/src",
129 "/usr/local",
130 "/usr/local/bin",
131 "/usr/local/etc",
132 "/usr/local/lib",
133 "/usr/tmp",
134 "/var/run",
135 "/var/tmp",
136 0
137 };
138
139 static int num_installs, num_replacements, num_uninstalls;
140 static void md5_one (const packagesource& source);
141 static bool rebootneeded;
142
143 void
144 Installer::uninstallOne (packagemeta & pkgm)
145 {
146 Progress.SetText1 ("Uninstalling...");
147 Progress.SetText2 (pkgm.name.cstr_oneuse());
148 log (LOG_PLAIN, String("Uninstalling ") + pkgm.name);
149 pkgm.uninstall ();
150 num_uninstalls++;
151 }
152
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.
158 */
159 int
160 Installer::replaceOne (packagemeta &pkg)
161 {
162 int errors = 0;
163 Progress.SetText1 ("Replacing...");
164 Progress.SetText2 (pkg.name.cstr_oneuse());
165 Progress.SetText3 ("");
166 log (LOG_PLAIN, String( "Replacing ") + pkg.name);
167 pkg.uninstall ();
168
169 errors +=
170 installOneSource (pkg, *pkg.desired.source(), "cygfile://","/", package_binary);
171 if (!errors)
172 pkg.installed = pkg.desired;
173 num_replacements++;
174 return errors;
175 }
176
177 /* log failed scheduling of replace-on-reboot of a given file. */
178 /* also increment errors. */
179 void
180 Installer::replaceOnRebootFailed (String const &fn)
181 {
182 log (LOG_TIMESTAMP,
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(),
186 GetLastError ());
187 ++errors;
188 }
189
190 /* log successful scheduling of replace-on-reboot of a given file. */
191 /* also set rebootneeded. */
192 void
193 Installer::replaceOnRebootSucceeded (String const &fn, bool &rebootneeded)
194 {
195 log (LOG_TIMESTAMP,
196 "Scheduled reboot replacement of file %s with %s",
197 cygpath (String ("/") + fn).cstr_oneuse(),
198 cygpath (String ("/") + fn + ".new").cstr_oneuse());
199 rebootneeded = true;
200 }
201
202 /* install one source at a given prefix. */
203 int
204 Installer::installOneSource (packagemeta & pkgm, packagesource & source,
205 String const &prefixURL, String const &prefixPath, package_type_t type)
206 {
207 Progress.SetText2 (source.Base ());
208 if (!source.Cached () || !io_stream::exists (source.Cached ()))
209 {
210 note (NULL, IDS_ERR_OPEN_READ, source.Cached (), "No such file");
211 return 1;
212 }
213 io_stream *lst = 0;
214
215 package_bytes = source.size;
216
217 char msg[64];
218 strcpy (msg, "Installing");
219 Progress.SetText1 (msg);
220 log (LOG_PLAIN, String (msg) + " " + source.Cached ());
221
222 io_stream *tmp = io_stream::open (source.Cached (), "rb");
223 archive *thefile = 0;
224 if (tmp)
225 {
226 io_stream *tmp2 = compress::decompress (tmp);
227 if (tmp2)
228 thefile = archive::extract (tmp2);
229 else
230 thefile = archive::extract (tmp);
231 }
232
233 /* FIXME: potential leak of either *tmp or *tmp2 */
234 if (thefile)
235 {
236 String fn;
237 if (type == package_binary)
238 {
239 io_stream *tmp = io_stream::open (String ("cygfile:///etc/setup/") +
240 pkgm.name + ".lst.gz", "wb");
241 lst = new compress_gz (tmp, "w9");
242 if (lst->error ())
243 {
244 delete lst;
245 lst = NULL;
246 }
247 }
248 while ((fn = thefile->next_file_name ()).size())
249 {
250 if (lst)
251 {
252 String tmp=fn + "\n";
253 lst->write (tmp.cstr_oneuse(), tmp.size());
254 }
255
256 String canonicalfn = prefixPath + fn;
257 if (Script::isAScript (fn))
258 pkgm.desired.addScript (Script (canonicalfn));
259
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)
263 {
264 if (NoReplaceOnReboot)
265 {
266 ++errors;
267 log (LOG_PLAIN, String("Not replacing in-use file ") +
268 prefixURL + prefixPath + fn);
269 }
270 else
271 //extract to temp location
272 if (archive::extract_file (thefile, prefixURL, prefixPath, ".new") != 0)
273 {
274 log (LOG_PLAIN,
275 String("Unable to install file ") +
276 prefixURL + prefixPath + fn);
277 ++errors;
278 }
279 else
280 //switch Win32::OS
281 {
282 switch (Win32::OS ())
283 {
284 case Win32::Win9x:{
285 /* Get the short file names */
286 char source[MAX_PATH];
287 unsigned int len =
288 GetShortPathName (cygpath (String ("/") + fn +
289 ".new").cstr_oneuse(),
290 source, MAX_PATH);
291 if (!len || len > MAX_PATH)
292 {
293 replaceOnRebootFailed(fn);
294 }
295 else
296 {
297 char dest[MAX_PATH];
298 len =
299 GetShortPathName (cygpath (String ("/") +
300 fn).cstr_oneuse(),
301 dest, MAX_PATH);
302 if (!len || len > MAX_PATH)
303 replaceOnRebootFailed (fn);
304 else
305 /* trigger a replacement on reboot */
306 if (!WritePrivateProfileString
307 ("rename", dest, source, "WININIT.INI"))
308 replaceOnRebootFailed (fn);
309 else
310 replaceOnRebootSucceeded (fn, rebootneeded);
311 }
312 }
313 break;
314 case Win32::WinNT:
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
319 */
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))
325 {
326 replaceOnRebootFailed (fn);
327 }
328 else
329 {
330 replaceOnRebootSucceeded (fn, rebootneeded);
331 }
332 break;
333 }
334 }
335 }
336 progress (tmp->tell ());
337 num_installs++;
338 }
339 delete thefile;
340
341 total_bytes_sofar += package_bytes;
342 }
343
344 progress (0);
345
346 int df = diskfull (get_root_dir ().cstr_oneuse());
347 Progress.SetBar3 (df);
348
349 if (lst)
350 delete lst;
351
352 return errors;
353 }
354
355 /* install a package, install both the binary and source aspects if needed */
356 static int
357 install_one (packagemeta & pkg)
358 {
359 int errors = 0;
360
361 Installer myInstaller;
362 if (pkg.installed != pkg.desired && pkg.desired.picked())
363 {
364 errors +=
365 myInstaller.installOneSource (pkg, *pkg.desired.source(), "cygfile://","/",
366 package_binary);
367 if (!errors)
368 pkg.installed = pkg.desired;
369 }
370 if (pkg.desired.sourcePackage().picked())
371 errors +=
372 myInstaller.installOneSource (pkg, *pkg.desired.sourcePackage().source(), "cygfile://","/usr/src/",
373 package_source);
374
375 /* FIXME: make a upgrade method and reinstate this */
376 #if 0
377 String msg;
378 if (!pkg->installed)
379 msg = "Installing";
380 else
381 {
382 int n = strcmp (pi->version, pkg->installed->version);
383 if (n < 0)
384 msg = "Reverting";
385 else if (n == 0)
386 msg = "Reinstalling";
387 else
388 msg = "Upgrading";
389 }
390
391 switch (pkg->action)
392 {
393 case ACTION_PREV:
394 msg += " previous version...";
395 break;
396 case ACTION_CURR:
397 msg += "...";
398 break;
399 case ACTION_TEST:
400 msg += " test version...";
401 break;
402 default:
403 /* FIXME: log this somehow */
404 break;
405 }
406 SetWindowText (ins_action, msg.cstr_oneuse());
407 log (LOG_PLAIN, msg + " " + file);
408 #endif
409
410 return errors;
411 }
412
413 static void
414 check_for_old_cygwin ()
415 {
416 char buf[_MAX_PATH + sizeof ("\\cygwin1.dll")];
417 if (!GetSystemDirectory (buf, sizeof (buf)))
418 return;
419 strcat (buf, "\\cygwin1.dll");
420 if (_access (buf, 0) != 0)
421 return;
422
423 char msg[sizeof (buf) + 132];
424 sprintf (msg,
425 "An old version of cygwin1.dll was found here:\r\n%s\r\nDelete?",
426 buf);
427 switch (MessageBox
428 (NULL, msg, "What's that doing there?",
429 MB_YESNO | MB_ICONQUESTION | MB_TASKMODAL))
430 {
431 case IDYES:
432 if (!DeleteFile (buf))
433 {
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);
440 }
441 break;
442 default:
443 break;
444 }
445
446 return;
447 }
448
449 static void
450 do_install_thread (HINSTANCE h, HWND owner)
451 {
452 int i;
453 int errors = 0;
454
455 num_installs = 0, num_uninstalls = 0, num_replacements = 0;
456 rebootneeded = false;
457
458 io_stream::mkpath_p (PATH_TO_DIR, String ("file://") + get_root_dir ());
459
460 for (i = 0; Installer::StandardDirs[i]; i++)
461 {
462 String p = cygpath (Installer::StandardDirs[i]);
463 if (p.size())
464 io_stream::mkpath_p (PATH_TO_DIR, String ("file://") + p);
465 }
466
467 /* Create /var/run/utmp */
468 io_stream *utmp = io_stream::open ("cygfile:///var/run/utmp", "wb");
469 delete utmp;
470
471 Installer myInstaller;
472 myInstaller.initDialog();
473
474 total_bytes = 0;
475 total_bytes_sofar = 0;
476
477 int df = diskfull (get_root_dir ().cstr_oneuse());
478 Progress.SetBar3 (df);
479
480 int istext = (root_text == IDC_ROOT_TEXT) ? 1 : 0;
481 int issystem = (root_scope == IDC_ROOT_SYSTEM) ? 1 : 0;
482
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);
487
488 /* Let's hope people won't uninstall packages before installing [b]ash */
489 init_run_script ();
490
491 packagedb db;
492 for (vector <packagemeta *>::iterator i = db.packages.begin ();
493 i != db.packages.end (); ++i)
494 {
495 packagemeta & pkg = **i;
496
497 if (pkg.desired.changeRequested())
498 {
499 if (pkg.desired.picked())
500 {
501 try
502 {
503 md5_one (*pkg.desired.source ());
504 }
505 catch (Exception *e)
506 {
507 if (yesno (owner, IDS_SKIP_PACKAGE, e->what()) == IDYES)
508 pkg.desired.pick (false);
509 }
510 if (pkg.desired.picked())
511 total_bytes += pkg.desired.source()->size;
512 }
513 if (pkg.desired.sourcePackage ().picked())
514 {
515 try
516 {
517 md5_one (*pkg.desired.sourcePackage ().source ());
518 }
519 catch (Exception *e)
520 {
521 if (yesno (owner, IDS_SKIP_PACKAGE, e->what()) == IDYES)
522 pkg.desired.sourcePackage ().pick (false);
523 }
524 if (pkg.desired.sourcePackage().picked())
525 total_bytes += pkg.desired.sourcePackage ().source()->size;
526 }
527 }
528 }
529
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)
533 {
534 packagemeta & pkg = **i;
535 if (pkg.installed && (!pkg.desired || (pkg.desired != pkg.installed &&
536 pkg.desired.picked ())))
537 myInstaller.uninstallOne (pkg);
538 }
539
540 /* now in-place binary upgrades/reinstalls, as these may remove fils
541 * that have been moved into new packages
542 */
543
544 for (vector <packagemeta *>::iterator i = db.packages.begin ();
545 i != db.packages.end (); ++i)
546 {
547 packagemeta & pkg = **i;
548 if (pkg.installed && pkg.desired.picked())
549 {
550 try {
551 int e = 0;
552 e += myInstaller.replaceOne (pkg);
553 if (e)
554 errors++;
555 }
556 catch (exception *e)
557 {
558 if (yesno (owner, IDS_INSTALL_ERROR, e->what()) != IDYES)
559 {
560 log (LOG_TIMESTAMP, String ("User cancelled setup after install error"));
561 LogSingleton::GetInstance().exit (1);
562 return;
563 }
564 }
565 }
566 }
567
568 for (vector <packagemeta *>::iterator i = db.packages.begin ();
569 i != db.packages.end (); ++i)
570 {
571 packagemeta & pkg = **i;
572
573 if (pkg.desired && pkg.desired.changeRequested())
574 {
575 try
576 {
577 int e = 0;
578 e += install_one (pkg);
579 if (e)
580 errors++;
581 }
582 catch (exception *e)
583 {
584 if (yesno (owner, IDS_INSTALL_ERROR, e->what()) != IDYES)
585 {
586 log (LOG_TIMESTAMP, String ("User cancelled setup after install error"));
587 LogSingleton::GetInstance().exit (1);
588 return;
589 }
590 }
591 }
592 } // end of big package loop
593
594 if (rebootneeded)
595 note (owner, IDS_REBOOT_REQUIRED);
596
597 int temperr;
598 if ((temperr = db.flush ()))
599 {
600 const char *err = strerror (temperr);
601 if (!err)
602 err = "(unknown error)";
603 fatal (owner, IDS_ERR_OPEN_WRITE, "Package Database",
604 err);
605 }
606
607 if (!errors)
608 check_for_old_cygwin ();
609 if (num_installs == 0 && num_uninstalls == 0)
610 {
611 if (!unattended_mode) exit_msg = IDS_NOTHING_INSTALLED;
612 return;
613 }
614 if (num_installs == 0)
615 {
616 if (!unattended_mode) exit_msg = IDS_UNINSTALL_COMPLETE;
617 return;
618 }
619
620 if (errors)
621 exit_msg = IDS_INSTALL_INCOMPLETE;
622 else if (!unattended_mode)
623 exit_msg = IDS_INSTALL_COMPLETE;
624 }
625
626 static DWORD WINAPI
627 do_install_reflector (void *p)
628 {
629 HANDLE *context;
630 context = (HANDLE *) p;
631
632 do_install_thread ((HINSTANCE) context[0], (HWND) context[1]);
633
634 // Tell the progress page that we're done downloading
635 Progress.PostMessage (WM_APP_INSTALL_THREAD_COMPLETE);
636
637 ExitThread (0);
638 }
639
640 static HANDLE context[2];
641
642 void
643 do_install (HINSTANCE h, HWND owner)
644 {
645 context[0] = h;
646 context[1] = owner;
647
648 DWORD threadID;
649 CreateThread (NULL, 0, do_install_reflector, context, 0, &threadID);
650 }
651
652 void md5_one (const packagesource& source)
653 {
654 if (source.md5.isSet())
655 {
656 // check the MD5 sum of the cached file here
657 io_stream *thefile = io_stream::open (source.Cached (), "rb");
658 if (!thefile)
659 throw new Exception (TOSTRING(__LINE__) " " __FILE__, String ("IO Error opening ") + source.Cached(), APPERR_IO_ERROR);
660 md5_state_t pns;
661 md5_init (&pns);
662
663 unsigned char buffer[16384];
664 ssize_t count;
665 while ((count = thefile->read (buffer, 16384)) > 0)
666 md5_append (&pns, buffer, count);
667 delete thefile;
668 if (count < 0)
669 throw new Exception (TOSTRING(__LINE__) " " __FILE__, String ("IO Error reading ") + source.Cached(), APPERR_IO_ERROR);
670
671 md5_byte_t tempdigest[16];
672 md5_finish(&pns, tempdigest);
673 md5 tempMD5;
674 tempMD5.set (tempdigest);
675
676 log (LOG_BABBLE, String ("For file ") + source.Cached() + " ini digest is " + source.md5.print() + " file digest is " + tempMD5.print());
677
678 if (source.md5 != tempMD5)
679 throw new Exception (TOSTRING(__LINE__) " " __FILE__, String ("Checksum failure for ") + source.Cached(), APPERR_CORRUPT_PACKAGE);
680 }
681 }
This page took 0.095048 seconds and 5 git commands to generate.