]> cygwin.com Git - cygwin-apps/setup.git/blob - install.cc
def8b207e519634917583ee752cbd4f0cee27983
[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 next_dialog = IDD_DESKTOP;
459
460 io_stream::mkpath_p (PATH_TO_DIR, String ("file://") + get_root_dir ());
461
462 for (i = 0; Installer::StandardDirs[i]; i++)
463 {
464 String p = cygpath (Installer::StandardDirs[i]);
465 if (p.size())
466 io_stream::mkpath_p (PATH_TO_DIR, String ("file://") + p);
467 }
468
469 /* Create /var/run/utmp */
470 io_stream *utmp = io_stream::open ("cygfile:///var/run/utmp", "wb");
471 delete utmp;
472
473 Installer myInstaller;
474 myInstaller.initDialog();
475
476 total_bytes = 0;
477 total_bytes_sofar = 0;
478
479 int df = diskfull (get_root_dir ().cstr_oneuse());
480 Progress.SetBar3 (df);
481
482 int istext = (root_text == IDC_ROOT_TEXT) ? 1 : 0;
483 int issystem = (root_scope == IDC_ROOT_SYSTEM) ? 1 : 0;
484
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);
489
490 /* Let's hope people won't uninstall packages before installing [b]ash */
491 init_run_script ();
492
493 packagedb db;
494 for (vector <packagemeta *>::iterator i = db.packages.begin ();
495 i != db.packages.end (); ++i)
496 {
497 packagemeta & pkg = **i;
498
499 if (pkg.desired.changeRequested())
500 {
501 if (pkg.desired.picked())
502 {
503 try
504 {
505 md5_one (*pkg.desired.source ());
506 }
507 catch (Exception *e)
508 {
509 if (yesno (owner, IDS_SKIP_PACKAGE, e->what()) == IDYES)
510 pkg.desired.pick (false);
511 }
512 if (pkg.desired.picked())
513 total_bytes += pkg.desired.source()->size;
514 }
515 if (pkg.desired.sourcePackage ().picked())
516 {
517 try
518 {
519 md5_one (*pkg.desired.sourcePackage ().source ());
520 }
521 catch (Exception *e)
522 {
523 if (yesno (owner, IDS_SKIP_PACKAGE, e->what()) == IDYES)
524 pkg.desired.sourcePackage ().pick (false);
525 }
526 if (pkg.desired.sourcePackage().picked())
527 total_bytes += pkg.desired.sourcePackage ().source()->size;
528 }
529 }
530 }
531
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)
535 {
536 packagemeta & pkg = **i;
537 if (pkg.installed && (!pkg.desired || (pkg.desired != pkg.installed &&
538 pkg.desired.picked ())))
539 myInstaller.uninstallOne (pkg);
540 }
541
542 /* now in-place binary upgrades/reinstalls, as these may remove fils
543 * that have been moved into new packages
544 */
545
546 for (vector <packagemeta *>::iterator i = db.packages.begin ();
547 i != db.packages.end (); ++i)
548 {
549 packagemeta & pkg = **i;
550 if (pkg.installed && pkg.desired.picked())
551 {
552 try {
553 int e = 0;
554 e += myInstaller.replaceOne (pkg);
555 if (e)
556 errors++;
557 }
558 catch (exception *e)
559 {
560 if (yesno (owner, IDS_INSTALL_ERROR, e->what()) != IDYES)
561 {
562 log (LOG_TIMESTAMP, String ("User cancelled setup after install error"));
563 LogSingleton::GetInstance().exit (1);
564 return;
565 }
566 }
567 }
568 }
569
570 for (vector <packagemeta *>::iterator i = db.packages.begin ();
571 i != db.packages.end (); ++i)
572 {
573 packagemeta & pkg = **i;
574
575 if (pkg.desired && pkg.desired.changeRequested())
576 {
577 try
578 {
579 int e = 0;
580 e += install_one (pkg);
581 if (e)
582 errors++;
583 }
584 catch (exception *e)
585 {
586 if (yesno (owner, IDS_INSTALL_ERROR, e->what()) != IDYES)
587 {
588 log (LOG_TIMESTAMP, String ("User cancelled setup after install error"));
589 LogSingleton::GetInstance().exit (1);
590 return;
591 }
592 }
593 }
594 } // end of big package loop
595
596 if (rebootneeded)
597 note (owner, IDS_REBOOT_REQUIRED);
598
599 int temperr;
600 if ((temperr = db.flush ()))
601 {
602 const char *err = strerror (temperr);
603 if (!err)
604 err = "(unknown error)";
605 fatal (owner, IDS_ERR_OPEN_WRITE, "Package Database",
606 err);
607 }
608
609 if (!errors)
610 check_for_old_cygwin ();
611 if (num_installs == 0 && num_uninstalls == 0)
612 {
613 if (!unattended_mode) exit_msg = IDS_NOTHING_INSTALLED;
614 return;
615 }
616 if (num_installs == 0)
617 {
618 if (!unattended_mode) exit_msg = IDS_UNINSTALL_COMPLETE;
619 return;
620 }
621
622 if (errors)
623 exit_msg = IDS_INSTALL_INCOMPLETE;
624 else if (!unattended_mode)
625 exit_msg = IDS_INSTALL_COMPLETE;
626 }
627
628 static DWORD WINAPI
629 do_install_reflector (void *p)
630 {
631 HANDLE *context;
632 context = (HANDLE *) p;
633
634 do_install_thread ((HINSTANCE) context[0], (HWND) context[1]);
635
636 // Tell the progress page that we're done downloading
637 Progress.PostMessage (WM_APP_INSTALL_THREAD_COMPLETE);
638
639 ExitThread (0);
640 }
641
642 static HANDLE context[2];
643
644 void
645 do_install (HINSTANCE h, HWND owner)
646 {
647 context[0] = h;
648 context[1] = owner;
649
650 DWORD threadID;
651 CreateThread (NULL, 0, do_install_reflector, context, 0, &threadID);
652 }
653
654 void md5_one (const packagesource& source)
655 {
656 if (source.md5.isSet())
657 {
658 // check the MD5 sum of the cached file here
659 io_stream *thefile = io_stream::open (source.Cached (), "rb");
660 if (!thefile)
661 throw new Exception ("__LINE__ __FILE__", String ("IO Error opening ") + source.Cached(), APPERR_IO_ERROR);
662 md5_state_t pns;
663 md5_init (&pns);
664
665 unsigned char buffer[16384];
666 ssize_t count;
667 while ((count = thefile->read (buffer, 16384)) > 0)
668 md5_append (&pns, buffer, count);
669 delete thefile;
670 if (count < 0)
671 throw new Exception ("__LINE__ __FILE__", String ("IO Error reading ") + source.Cached(), APPERR_IO_ERROR);
672
673 md5_byte_t tempdigest[16];
674 md5_finish(&pns, tempdigest);
675 md5 tempMD5;
676 tempMD5.set (tempdigest);
677
678 log (LOG_BABBLE, String ("For file ") + source.Cached() + " ini digest is " + source.md5.print() + " file digest is " + tempMD5.print());
679
680 if (source.md5 != tempMD5)
681 throw new Exception ("__LINE__ __FILE__", String ("Checksum failure for ") + source.Cached(), APPERR_CORRUPT_PACKAGE);
682 }
683 }
This page took 0.061936 seconds and 4 git commands to generate.