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