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