]> cygwin.com Git - cygwin-apps/setup.git/blob - install.cc
Also run stratum 'z' perpetual preremove scripts
[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 #include "getopt++/BoolOption.h"
24 #include "LogFile.h"
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 #include <algorithm>
37
38 #include "ini.h"
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 "mount.h"
47 #include "filemanip.h"
48 #include "io_stream.h"
49 #include "compress.h"
50 #include "compress_gz.h"
51 #include "archive.h"
52 #include "archive_tar.h"
53 #include "script.h"
54 #include "find.h"
55 #include "FindVisitor.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 "threebar.h"
63 #include "Exception.h"
64 #include "processlist.h"
65
66 extern ThreeBarProgressPage Progress;
67
68 static long long int total_bytes = 0;
69 static long long int total_bytes_sofar = 0;
70 static int package_bytes = 0;
71
72 static BoolOption NoReplaceOnReboot (false, 'r', "no-replaceonreboot", IDS_HELPTEXT_NO_REPLACEONREBOOT);
73
74 struct std_dirs_t {
75 const char *name;
76 mode_t mode;
77 };
78
79 class PerpetualRemoveFindVisitor : public FindVisitor
80 {
81 public:
82 PerpetualRemoveFindVisitor (std::vector<Script> *scripts, const std::string& stratum)
83 : _scripts(scripts),
84 stratum(stratum)
85 {}
86 virtual void visitFile(const std::string& basePath,
87 const WIN32_FIND_DATA *theFile)
88 {
89 std::string fn = std::string("/etc/preremove/") + theFile->cFileName;
90 Script script(fn);
91 if (script.is_p(stratum))
92 _scripts->push_back(Script (fn));
93 }
94 virtual ~ PerpetualRemoveFindVisitor () {}
95 protected:
96 PerpetualRemoveFindVisitor (PerpetualRemoveFindVisitor const &);
97 PerpetualRemoveFindVisitor & operator= (PerpetualRemoveFindVisitor const &);
98 private:
99 std::vector<Script> *_scripts;
100 const std::string stratum;
101 };
102
103 class Installer
104 {
105 public:
106 static std_dirs_t StandardDirs[];
107 Installer();
108 void initDialog();
109 void progress (int bytes);
110 void preremovePerpetual (const std::string& stratum);
111 void preremoveOne (packagemeta &);
112 void uninstallOne (packagemeta &);
113 void replaceOnRebootFailed (const std::string& fn);
114 void replaceOnRebootSucceeded (const std::string& fn, bool &rebootneeded);
115 void installOne (packagemeta &pkg, const packageversion &ver,
116 packagesource &source,
117 const std::string& , const std::string&, HWND );
118 int errors;
119 private:
120 bool extract_replace_on_reboot(archive *, const std::string&,
121 const std::string&, std::string);
122 bool _installOne (packagemeta &pkgm,
123 const std::string& prefixURL,
124 const std::string& prefixPath,
125 HWND owner,
126 io_stream *pkgfile,
127 archive *tarstream,
128 io_stream *lst,
129 bool symlink_phase);
130 };
131
132 Installer::Installer() : errors(0)
133 {
134 }
135
136 void
137 Installer::initDialog()
138 {
139 Progress.SetText2 ("");
140 Progress.SetText3 ("");
141 }
142
143 void
144 Installer::progress (int bytes)
145 {
146 if (package_bytes > 0)
147 Progress.SetBar1 (bytes, package_bytes);
148
149 if (total_bytes > 0)
150 Progress.SetBar2 (total_bytes_sofar + bytes, total_bytes);
151 }
152
153 std_dirs_t
154 Installer::StandardDirs[] = {
155 { "/bin", 0755 },
156 { "/dev", 0755 },
157 { "/dev/mqueue", 01777 },
158 { "/dev/shm", 01777 },
159 { "/etc", 0755 },
160 { "/etc/fstab.d", 01777 },
161 { "/home", 01777 },
162 { "/lib", 0755 },
163 { "/tmp", 01777 },
164 { "/usr", 0755 },
165 { "/usr/bin", 0755 },
166 { "/usr/lib", 0755 },
167 { "/usr/local", 0755 },
168 { "/usr/local/bin", 0755 },
169 { "/usr/local/etc", 0755 },
170 { "/usr/local/lib", 0755 },
171 { "/usr/src", 0755 },
172 { "/usr/tmp", 01777 },
173 { "/var/log", 01777 },
174 { "/var/run", 01777 },
175 { "/var/tmp", 01777 },
176 { NULL, 0 }
177 };
178
179 static int num_installs, num_uninstalls;
180
181 void
182 Installer::preremovePerpetual (const std::string& stratum)
183 {
184 std::vector<Script> perpetual;
185 PerpetualRemoveFindVisitor visitor (&perpetual, stratum);
186 Find (cygpath ("/etc/preremove")).accept (visitor);
187 if (perpetual.empty())
188 return;
189
190 Progress.SetText1 (IDS_PROGRESS_PREREMOVE);
191 Progress.SetText2 ((stratum + "/Perpetual").c_str ());
192 std::sort (perpetual.begin(), perpetual.end());
193 for (std::vector<Script>::iterator i = perpetual.begin (); i != perpetual.end (); ++i) {
194 Progress.SetText3 (i->fullName ().c_str());
195 i->run();
196 }
197 }
198
199 void
200 Installer::preremoveOne (packagemeta & pkg)
201 {
202 Progress.SetText1 (IDS_PROGRESS_PREREMOVE);
203 Progress.SetText2 (pkg.name.c_str());
204 Log (LOG_BABBLE) << "Running preremove script for " << pkg.name << endLog;
205 const unsigned numexts = 4;
206 const char* exts[numexts] = { ".dash", ".sh", ".bat", ".cmd" };
207 for (unsigned i = 0; i < numexts; i++)
208 try_run_script ("/etc/preremove/", pkg.name, exts[i]);
209 }
210
211 void
212 Installer::uninstallOne (packagemeta & pkg)
213 {
214 if (!pkg.installed)
215 return;
216
217 Progress.SetText1 (IDS_PROGRESS_UNINSTALL);
218 Progress.SetText2 (pkg.name.c_str());
219 Log (LOG_PLAIN) << "Uninstalling " << pkg.name << endLog;
220
221 std::set<std::string> dirs;
222
223 io_stream *listfile = io_stream::open ("cygfile:///etc/setup/" + pkg.name + ".lst.gz", "rb", 0);
224 io_stream *listdata = compress::decompress (listfile);
225
226 while (listdata)
227 {
228 char getfilenamebuffer[CYG_PATH_MAX];
229 const char *sz = listdata->gets (getfilenamebuffer, sizeof (getfilenamebuffer));
230 if (sz == NULL)
231 break;
232
233 std::string line(sz);
234
235 /* Insert the paths of all parent directories of line into dirs. */
236 size_t idx = line.length();
237 while ((idx = line.find_last_of('/', idx-1)) != std::string::npos)
238 {
239 std::string dir_path = line.substr(0, idx);
240 bool was_new = dirs.insert(dir_path).second;
241 /* If the path was already present in dirs, then all parent paths
242 * must necessarily be present also, so don't do any further work.
243 * */
244 if (!was_new) break;
245 }
246
247 std::string d = cygpath ("/" + line);
248 WCHAR wname[d.size () + 11]; /* Prefix + ".lnk". */
249 mklongpath (wname, d.c_str (), d.size () + 11);
250 DWORD dw = GetFileAttributesW (wname);
251 if (dw != INVALID_FILE_ATTRIBUTES
252 && !(dw & FILE_ATTRIBUTE_DIRECTORY))
253 {
254 Log (LOG_BABBLE) << "unlink " << d << endLog;
255 SetFileAttributesW (wname, dw & ~FILE_ATTRIBUTE_READONLY);
256 DeleteFileW (wname);
257 }
258 /* Check for Windows shortcut of same name. */
259 d += ".lnk";
260 wcscat (wname, L".lnk");
261 dw = GetFileAttributesW (wname);
262 if (dw != INVALID_FILE_ATTRIBUTES
263 && !(dw & FILE_ATTRIBUTE_DIRECTORY))
264 {
265 Log (LOG_BABBLE) << "unlink " << d << endLog;
266 SetFileAttributesW (wname, dw & ~FILE_ATTRIBUTE_READONLY);
267 DeleteFileW (wname);
268 }
269 }
270
271 /* Remove the listing file */
272 delete listdata;
273 io_stream::remove ("cygfile:///etc/setup/" + pkg.name + ".lst.gz");
274
275 /* An STL set maintains itself in sorted order. Thus, iterating over it
276 * in reverse order will ensure we process directories depth-first. */
277 std::set<std::string>::const_iterator it = dirs.end();
278 while (it != dirs.begin())
279 {
280 it--;
281 std::string d = cygpath("/" + *it);
282 WCHAR wname[d.size () + 11];
283 mklongpath (wname, d.c_str (), d.size () + 11);
284 if (RemoveDirectoryW (wname))
285 Log (LOG_BABBLE) << "rmdir " << d << endLog;
286 }
287
288 pkg.installed = packageversion();
289 num_uninstalls++;
290 }
291
292 /* log failed scheduling of replace-on-reboot of a given file. */
293 /* also increment errors. */
294 void
295 Installer::replaceOnRebootFailed (const std::string& fn)
296 {
297 Log (LOG_TIMESTAMP) << "Unable to schedule reboot replacement of file "
298 << cygpath("/" + fn) << " with " << cygpath("/" + fn + ".new")
299 << " (Win32 Error " << GetLastError() << ")" << endLog;
300 ++errors;
301 }
302
303 /* log successful scheduling of replace-on-reboot of a given file. */
304 /* also set rebootneeded. */
305 void
306 Installer::replaceOnRebootSucceeded (const std::string& fn, bool &rebootneeded)
307 {
308 Log (LOG_TIMESTAMP) << "Scheduled reboot replacement of file "
309 << cygpath("/" + fn) << " with " << cygpath("/" + fn + ".new") << endLog;
310 rebootneeded = true;
311 }
312
313 typedef struct
314 {
315 const wchar_t *msg;
316 const char *processlist;
317 int iteration;
318 } FileInuseDlgData;
319
320 static INT_PTR CALLBACK
321 FileInuseDlgProc (HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
322 {
323 switch (uMsg)
324 {
325 case WM_INITDIALOG:
326 {
327 FileInuseDlgData *dlg_data = (FileInuseDlgData *)lParam;
328
329 SetDlgItemTextW (hwndDlg, IDC_FILE_INUSE_MSG, dlg_data->msg);
330 SetDlgItemText (hwndDlg, IDC_FILE_INUSE_EDIT, dlg_data->processlist);
331
332 switch (dlg_data->iteration)
333 {
334 case 0:
335 ShowWindow (GetDlgItem(hwndDlg, IDC_FILE_INUSE_HELP_0), SW_SHOW);
336 ShowWindow (GetDlgItem(hwndDlg, IDC_FILE_INUSE_HELP_1), SW_HIDE);
337 ShowWindow (GetDlgItem(hwndDlg, IDC_FILE_INUSE_HELP_2), SW_HIDE);
338 break;
339
340 case 1:
341 ShowWindow (GetDlgItem(hwndDlg, IDC_FILE_INUSE_HELP_0), SW_HIDE);
342 ShowWindow (GetDlgItem(hwndDlg, IDC_FILE_INUSE_HELP_1), SW_SHOW);
343 ShowWindow (GetDlgItem(hwndDlg, IDC_FILE_INUSE_HELP_2), SW_HIDE);
344 SetDlgItemTextW (hwndDlg, IDRETRY, LoadStringW(IDS_FILE_INUSE_KILL).c_str());
345 break;
346
347 default:
348 case 2:
349 ShowWindow (GetDlgItem(hwndDlg, IDC_FILE_INUSE_HELP_0), SW_HIDE);
350 ShowWindow (GetDlgItem(hwndDlg, IDC_FILE_INUSE_HELP_1), SW_HIDE);
351 ShowWindow (GetDlgItem(hwndDlg, IDC_FILE_INUSE_HELP_2), SW_SHOW);
352 SetDlgItemTextW (hwndDlg, IDRETRY, LoadStringW(IDS_FILE_INUSE_KILL).c_str());
353 }
354 }
355 return TRUE; // automatically set focus, please
356
357 case WM_COMMAND:
358 if (HIWORD (wParam) == BN_CLICKED)
359 {
360 switch (LOWORD (wParam))
361 {
362 case IDIGNORE:
363 case IDRETRY:
364 case IDCONTINUE:
365 EndDialog (hwndDlg, LOWORD (wParam));
366 return TRUE;
367 }
368 }
369 }
370
371 return FALSE;
372 }
373
374 /* Helper function to create the registry value "AllowProtectedRenames",
375 which is required to make MOVEFILE_DELAY_UNTIL_REBOOT to work on WFP
376 protected files. By default, the entire system drive is WFP protected,
377 so a Cygwin installation on this drive sufferes from the WFP problem.
378 Even though this value is only required since Windows Server 2003 SP1,
379 we just set it here unconditionally since it doesn't hurt at all on
380 older systems. */
381 void
382 create_allow_protected_renames ()
383 {
384 HKEY key;
385 DWORD val = 1;
386
387 if (RegOpenKeyEx (HKEY_LOCAL_MACHINE,
388 "System\\CurrentControlSet\\Control\\Session Manager",
389 0, KEY_ALL_ACCESS | SETUP_KEY_WOW64, &key) == ERROR_SUCCESS)
390 RegSetValueEx (key, "AllowProtectedRenames", 0, REG_DWORD,
391 (BYTE *) &val, sizeof (DWORD));
392 RegCloseKey (key);
393 }
394
395 bool
396 Installer::extract_replace_on_reboot (archive *tarstream, const std::string& prefixURL,
397 const std::string& prefixPath, std::string fn)
398 {
399 /* Extract a copy of the file with extension .new appended and
400 indicate it should be replaced on the next reboot. */
401 if (archive::extract_file (tarstream, prefixURL, prefixPath,
402 ".new") != 0)
403 {
404 Log (LOG_PLAIN) << "Unable to install file " << prefixURL
405 << prefixPath << fn << ".new" << endLog;
406 ++errors;
407 return true;
408 }
409 else
410 {
411 std::string s = cygpath ("/" + fn + ".new"),
412 d = cygpath ("/" + fn);
413
414 WCHAR sname[s.size () + 7];
415 WCHAR dname[d.size () + 7];
416
417 mklongpath (sname, s.c_str (), s.size () + 7);
418 mklongpath (dname, d.c_str (), d.size () + 7);
419 if (!MoveFileExW (sname, dname,
420 MOVEFILE_DELAY_UNTIL_REBOOT |
421 MOVEFILE_REPLACE_EXISTING))
422 replaceOnRebootFailed (fn);
423 else
424 {
425 create_allow_protected_renames ();
426 replaceOnRebootSucceeded (fn, rebootneeded);
427 }
428 }
429 return false;
430 }
431
432 static char all_null[512];
433
434 /* install one source at a given prefix. */
435 void
436 Installer::installOne (packagemeta &pkgm, const packageversion &ver,
437 packagesource &source,
438 const std::string& prefixURL,
439 const std::string& prefixPath,
440 HWND owner)
441 {
442 if (!source.Canonical())
443 return;
444 Progress.SetText1 (IDS_PROGRESS_INSTALL);
445 Progress.SetText2 ((pkgm.name + "-" + ver.Canonical_version()).c_str());
446
447 io_stream *pkgfile = NULL;
448
449 if (!source.Cached())
450 {
451 note (NULL, IDS_ERR_OPEN_READ, source.Canonical (), "Unknown filename");
452 ++errors;
453 return;
454 }
455
456 if (!io_stream::exists (source.Cached ())
457 || !(pkgfile = io_stream::open (source.Cached (), "rb", 0)))
458 {
459 note (NULL, IDS_ERR_OPEN_READ, source.Cached (), "No such file");
460 ++errors;
461 return;
462 }
463
464
465 /* At this point pkgfile is an opened io_stream to either a .tar.bz2 file,
466 a .tar.gz file, a .tar.lzma file, or just a .tar file. Try it first as
467 a compressed file and if that fails try opening it as a tar directly.
468 If both fail, abort.
469
470 Note on io_stream pointer management:
471
472 Both the archive and decompress classes take ownership of the io_stream
473 pointer they were opened with, meaning they delete it in their dtor. So
474 deleting tarstream should also result in deleting the underlying
475 try_decompress and pkgfile io_streams as well. */
476
477 archive *tarstream = NULL;
478 io_stream *try_decompress = NULL;
479
480 if ((try_decompress = compress::decompress (pkgfile)) != NULL)
481 {
482 if ((tarstream = archive::extract (try_decompress)) == NULL)
483 {
484 /* Decompression succeeded but we couldn't grok it as a valid tar
485 archive. */
486 char c[512];
487 ssize_t len;
488 if ((len = try_decompress->peek (c, 512)) < 0
489 || !memcmp (c, all_null, len))
490 /* In some cases, maintainers have uploaded bzipped
491 0-byte files as dummy packages instead of empty tar files.
492 This is common enough that we should not treat this as an
493 error condition.
494 Same goes for tar archives consisting of a big block of
495 all zero bytes (the famous 46 bytes tar archives). */
496 {
497 if (ver.Type () == package_binary)
498 pkgm.installed = ver;
499 }
500 else
501 {
502 note (NULL, IDS_ERR_OPEN_READ, source.Cached (),
503 "Invalid or unsupported tar format");
504 ++errors;
505 }
506 delete try_decompress;
507 return;
508 }
509 }
510 else if ((tarstream = archive::extract (pkgfile)) == NULL)
511 {
512 /* Not a compressed tarball, not a plain tarball, give up. */
513 delete pkgfile;
514 note (NULL, IDS_ERR_OPEN_READ, source.Cached (),
515 "Unrecognisable file format");
516 ++errors;
517 return;
518 }
519
520 /* For binary packages, create a manifest in /etc/setup/ that lists the
521 filename of each file that was unpacked. */
522
523 io_stream *lst = NULL;
524 if (ver.Type () == package_binary)
525 {
526 std::string lstfn = "cygfile:///etc/setup/" + pkgm.name + ".lst.gz";
527
528 io_stream *tmp;
529 if ((tmp = io_stream::open (lstfn, "wb", 0644)) == NULL)
530 Log (LOG_PLAIN) << "Warning: Unable to create lst file " + lstfn +
531 " - uninstall of this package will leave orphaned files." << endLog;
532 else
533 {
534 lst = new compress_gz (tmp, "w9");
535 if (lst->error ())
536 {
537 delete lst;
538 lst = NULL;
539 Log (LOG_PLAIN) << "Warning: gzip unable to write to lst file " +
540 lstfn + " - uninstall of this package will leave orphaned files."
541 << endLog;
542 }
543 }
544 }
545
546 package_bytes = source.size;
547 Log (LOG_PLAIN) << "Extracting from " << source.Cached () << endLog;
548
549 bool error_in_this_package = _installOne(pkgm, prefixURL, prefixPath, owner,
550 pkgfile, tarstream, lst, false);
551 if (tarstream->seek(0, IO_SEEK_SET) == -1)
552 Log (LOG_PLAIN) << "Error rewinding to extract symlinks" << source.Cached () << endLog;
553
554 error_in_this_package |= _installOne(pkgm, prefixURL, prefixPath, owner,
555 pkgfile, tarstream, lst, true);
556
557 if (lst)
558 delete lst;
559 delete tarstream;
560
561 total_bytes_sofar += package_bytes;
562 progress (0);
563
564 int df = diskfull (get_root_dir ().c_str ());
565 Progress.SetBar3 (df);
566
567 if (ver.Type () == package_binary && !error_in_this_package)
568 pkgm.installed = ver;
569 }
570
571 bool
572 Installer::_installOne (packagemeta &pkgm,
573 const std::string& prefixURL,
574 const std::string& prefixPath,
575 HWND owner,
576 io_stream *pkgfile,
577 archive *tarstream,
578 io_stream *lst,
579 bool symlink_phase)
580 {
581 bool error_in_this_package = false;
582 bool ignoreInUseErrors = false;
583 bool ignoreExtractErrors = unattended_mode;
584
585 std::string fn;
586 while ((fn = tarstream->next_file_name ()).size ())
587 {
588 if (symlink_phase != (tarstream->next_file_type () == ARCHIVE_FILE_SYMLINK))
589 {
590 tarstream->skip_file();
591 continue;
592 }
593
594 std::string canonicalfn = prefixPath + fn;
595
596 // pathnames starting "." (i.e. dotfiles in the root directory) are
597 // reserved for package metadata. Don't extract them.
598 if (fn[0] == '.')
599 {
600 tarstream->skip_file ();
601 continue;
602 }
603
604 Progress.SetText3 (canonicalfn.c_str ());
605 Log (LOG_BABBLE) << "Installing file " << prefixURL << prefixPath
606 << fn << endLog;
607 if (lst)
608 {
609 std::string tmp = fn + "\n";
610 lst->write (tmp.c_str(), tmp.size());
611 }
612 if (Script::isAScript (fn))
613 pkgm.addScript (Script (canonicalfn));
614
615 int iteration = 0;
616 archive::extract_results extres;
617 while ((extres = archive::extract_file (tarstream, prefixURL, prefixPath)) != archive::extract_ok)
618 {
619 bool error_in_this_file = false;
620
621 switch (extres)
622 {
623 case archive::extract_inuse: // in use
624 {
625 if (!ignoreInUseErrors)
626 {
627 // convert the file name to long UNC form
628 std::string s = backslash (cygpath ("/" + fn));
629 WCHAR sname[s.size () + 7];
630 mklongpath (sname, s.c_str (), s.size () + 7);
631
632 // find any process which has that file loaded into it
633 // (note that this doesn't find when the file is un-writeable because the process has
634 // that file opened exclusively)
635 ProcessList processes = Process::listProcessesWithModuleLoaded (sname);
636
637 std::string plm;
638 for (ProcessList::iterator i = processes.begin (); i != processes.end (); i++)
639 {
640 if (i != processes.begin ()) plm += "\r\n";
641
642 std::string processName = i->getName ();
643 Log (LOG_BABBLE) << processName << endLog;
644 plm += processName;
645 }
646
647 INT_PTR rc = (iteration < 3) ? IDRETRY : IDCONTINUE;
648 if (unattended_mode == attended)
649 {
650 if (!processes.empty())
651 {
652 // Use the IDD_FILE_INUSE dialog to ask the user if we should try to kill the
653 // listed processes, or just ignore the problem and schedule the file to be
654 // replaced after a reboot
655 FileInuseDlgData dlg_data;
656 std::wstring msg = LoadStringW(IDS_FILE_INUSE_MSG) + L" /" + string_to_wstring(fn);
657 dlg_data.msg = msg.c_str ();
658 dlg_data.processlist = plm.c_str ();
659 dlg_data.iteration = iteration;
660
661 rc = DialogBoxParam(hinstance, MAKEINTRESOURCE (IDD_FILE_INUSE), owner, FileInuseDlgProc, (LPARAM)&dlg_data);
662 }
663 else
664 {
665 // We couldn't enumerate any processes which have this file loaded into it
666 // either the cause of the error is something else, or something (e.g security
667 // policy) prevents us from listing those processes.
668 // All we can offer the user is a generic "retry or ignore" choice and a chance
669 // to fix the problem themselves
670 rc = mbox (owner, IDS_EXTRACTION_INUSE,
671 MB_RETRYCONTINUE | MB_ICONWARNING | MB_TASKMODAL,
672 fn.c_str());
673 }
674 }
675
676 switch (rc)
677 {
678 case IDIGNORE:
679 // manual intervention may have fixed the problem, retry
680 continue;
681 case IDRETRY:
682 if (!processes.empty())
683 {
684 // try to stop all the processes
685 for (ProcessList::iterator i = processes.begin (); i != processes.end (); i++)
686 {
687 i->kill (iteration);
688 }
689
690 // wait up to 15 seconds for processes to stop
691 for (unsigned int i = 0; i < 15; i++)
692 {
693 processes = Process::listProcessesWithModuleLoaded (sname);
694 if (processes.size () == 0)
695 break;
696
697 Sleep (1000);
698 }
699 }
700 // else, manual intervention may have fixed the problem
701
702 // retry
703 iteration++;
704 continue;
705 case IDCONTINUE:
706 // ignore this in-use error, and any subsequent in-use errors for other files in the same package
707 ignoreInUseErrors = true;
708 break;
709 default:
710 break;
711 }
712 // fall through to previous functionality
713 }
714
715 if (NoReplaceOnReboot)
716 {
717 ++errors;
718 error_in_this_file = true;
719 Log (LOG_PLAIN) << "Not replacing in-use file " << prefixURL
720 << prefixPath << fn << endLog;
721 }
722 else
723 {
724 error_in_this_file = extract_replace_on_reboot(tarstream, prefixURL, prefixPath, fn);
725 }
726 }
727 break;
728 case archive::extract_other: // extract failed
729 {
730 if (!ignoreExtractErrors)
731 {
732 // XXX: We should offer the option to retry,
733 // continue without extracting this particular archive,
734 // or ignore all extraction errors.
735 // Unfortunately, we don't currently know how to rewind
736 // the archive, so we can't retry at present,
737 // and ignore all errors is mis-implemented at present
738 // to only apply to errors arising from a single archive,
739 // so we degenerate to the continue option.
740 mbox (owner, IDS_EXTRACTION_FAILED,
741 MB_OK | MB_ICONWARNING | MB_TASKMODAL,
742 fn.c_str());
743 }
744
745 error_in_this_file = true;
746 }
747 break;
748 case archive::extract_ok:
749 break;
750 }
751
752 // We're done with this file
753
754 // if an error occured ...
755 if (error_in_this_file)
756 {
757 // skip to next file in archive
758 tarstream->skip_file();
759 // don't mark this package as successfully installed
760 error_in_this_package = true;
761 }
762
763 break;
764 }
765 progress (pkgfile->tell ());
766 num_installs++;
767 }
768
769 return error_in_this_package;
770 }
771
772 static void
773 check_for_old_cygwin (HWND owner)
774 {
775 /* Paths within system dir expected to be always < MAX_PATH. */
776 char buf[MAX_PATH + sizeof ("\\cygwin1.dll")];
777 if (!GetSystemDirectory (buf, sizeof (buf)))
778 return;
779 strcat (buf, "\\cygwin1.dll");
780 if (_access (buf, 0) != 0)
781 return;
782
783 switch (mbox(owner, IDS_INSTALL_OLD_CYGWIN,
784 MB_YESNO | MB_ICONQUESTION | MB_TASKMODAL,
785 buf))
786 {
787 case IDYES:
788 if (!DeleteFile (buf))
789 {
790 mbox (owner, IDS_INSTALL_DELETE_OLD_CYGWIN_FAILED,
791 MB_OK | MB_ICONEXCLAMATION | MB_TASKMODAL,
792 buf);
793 }
794 break;
795 default:
796 break;
797 }
798
799 return;
800 }
801
802 static void
803 do_install_thread (HINSTANCE h, HWND owner)
804 {
805 int i;
806
807 num_installs = 0, num_uninstalls = 0;
808 rebootneeded = false;
809
810 io_stream::mkpath_p (PATH_TO_DIR,
811 std::string("file://") + std::string(get_root_dir()),
812 0755);
813
814 for (i = 0; Installer::StandardDirs[i].name; i++)
815 {
816 std::string p = cygpath (Installer::StandardDirs[i].name);
817 if (p.size())
818 io_stream::mkpath_p (PATH_TO_DIR, "file://" + p,
819 Installer::StandardDirs[i].mode);
820 }
821
822 /* Create /var/run/utmp */
823 io_stream *utmp = io_stream::open ("cygfile:///var/run/utmp", "wb", 0666);
824 delete utmp;
825
826 Installer myInstaller;
827 myInstaller.initDialog();
828
829 total_bytes = 0;
830 total_bytes_sofar = 0;
831
832 int df = diskfull (get_root_dir ().c_str());
833 Progress.SetBar3 (df);
834
835 /* Writes Cygwin/setup/rootdir registry value */
836 create_install_root ();
837
838 std::vector <packageversion> install_q, uninstall_q, sourceinstall_q;
839
840 packagedb db;
841 const SolverTransactionList &t = db.solution.transactions();
842
843 /* Calculate the amount of data to md5sum */
844 Progress.SetText1(IDS_PROGRESS_CALCULATING);
845 long long int md5sum_total_bytes = 0;
846 for (SolverTransactionList::const_iterator i = t.begin (); i != t.end (); ++i)
847 {
848 packageversion version = i->version;
849
850 if (i->type == SolverTransaction::transInstall)
851 {
852 md5sum_total_bytes += version.source()->size;
853 }
854 }
855
856 /* md5sum the packages, build lists of packages to install and uninstall
857 and calculate the total amount of data to install.
858 The hash checking is relevant only for local installs. For a
859 net install, the hashes will have already been verified at download
860 time, and all calls to check_hash() below should instantly return. */
861 long long int md5sum_total_bytes_sofar = 0;
862 for (SolverTransactionList::const_iterator i = t.begin (); i != t.end (); ++i)
863 {
864 packageversion version = i->version;
865
866 if (i->type == SolverTransaction::transInstall)
867 {
868 try
869 {
870 (*version.source ()).check_hash ();
871 }
872 catch (Exception *e)
873 {
874 // We used to give the user a yes/no option to skip this
875 // package (with "no" meaning install it even though the
876 // archive is corrupt), but both options could damage the
877 // user's system. In the absence of a safe way to recover, we
878 // just bail out.
879 if (e->errNo() == APPERR_CORRUPT_PACKAGE)
880 fatal (owner, IDS_CORRUPT_PACKAGE, version.Name().c_str());
881 // Unexpected exception.
882 throw e;
883 }
884 {
885 md5sum_total_bytes_sofar += version.source()->size;
886 total_bytes += version.source()->size;
887
888 // source packages are kept in a separate queue as they are installed
889 // differently: root is /usr/src, install isn't recorded, etc.
890 if (version.Type() == package_source)
891 sourceinstall_q.push_back (version);
892 else
893 install_q.push_back (version);
894 }
895 }
896
897 /* Uninstall, upgrade or reinstall */
898 if (i->type == SolverTransaction::transErase)
899 {
900 uninstall_q.push_back (version);
901 }
902
903 if (md5sum_total_bytes > 0)
904 Progress.SetBar2 (md5sum_total_bytes_sofar, md5sum_total_bytes);
905 }
906
907 /* start with uninstalls - remove files that new packages may replace */
908 Progress.SetBar2(0);
909 myInstaller.preremovePerpetual ("0");
910
911 Progress.SetBar2(0);
912 for (std::vector <packageversion>::iterator i = uninstall_q.begin ();
913 i != uninstall_q.end (); ++i)
914 {
915 packagemeta *pkgm = db.findBinary (PackageSpecification(i->Name()));
916 if (pkgm)
917 myInstaller.preremoveOne (*pkgm);
918 Progress.SetBar2(std::distance(uninstall_q.begin(), i) + 1, uninstall_q.size());
919 }
920
921 Progress.SetBar2(0);
922 myInstaller.preremovePerpetual ("z");
923
924 Progress.SetBar2(0);
925 for (std::vector <packageversion>::iterator i = uninstall_q.begin ();
926 i != uninstall_q.end (); ++i)
927 {
928 packagemeta *pkgm = db.findBinary (PackageSpecification(i->Name()));
929 if (pkgm)
930 myInstaller.uninstallOne (*pkgm);
931 Progress.SetBar2(std::distance(uninstall_q.begin(), i) + 1, uninstall_q.size());
932 }
933
934 for (std::vector <packageversion>::iterator i = install_q.begin ();
935 i != install_q.end (); ++i)
936 {
937 packageversion & pkg = *i;
938 packagemeta *pkgm = db.findBinary (PackageSpecification(i->Name()));
939
940 try {
941 myInstaller.installOne (*pkgm, pkg, *pkg.source(),
942 "cygfile://", "/", owner);
943 }
944 catch (std::exception *e)
945 {
946 if (yesno (owner, IDS_INSTALL_ERROR, e->what()) != IDYES)
947 {
948 Log (LOG_TIMESTAMP)
949 << "User cancelled setup after install error" << endLog;
950 Logger ().exit (1);
951 return;
952 }
953 }
954 }
955
956 for (std::vector <packageversion>::iterator i = sourceinstall_q.begin ();
957 i != sourceinstall_q.end (); ++i)
958 {
959 packagemeta *pkgm = db.findSource (PackageSpecification(i->Name()));
960 packageversion & pkg = *i;
961 myInstaller.installOne (*pkgm, pkg, *pkg.source(),
962 "cygfile://", "/usr/src/", owner);
963 }
964
965 if (rebootneeded)
966 note (owner, IDS_REBOOT_REQUIRED);
967
968 int temperr;
969 if ((temperr = db.flush ()))
970 {
971 const char *err = strerror (temperr);
972 if (!err)
973 err = "(unknown error)";
974 fatal (owner, IDS_ERR_OPEN_WRITE, "Package Database",
975 err);
976 }
977
978 if (!myInstaller.errors)
979 check_for_old_cygwin (owner);
980 if (num_installs == 0 && num_uninstalls == 0)
981 {
982 if (!unattended_mode)
983 Logger ().setExitMsg (IDS_NOTHING_INSTALLED);
984 return;
985 }
986 if (num_installs == 0)
987 {
988 if (!unattended_mode)
989 Logger ().setExitMsg (IDS_UNINSTALL_COMPLETE);
990 return;
991 }
992
993 if (myInstaller.errors)
994 Logger ().setExitMsg (IDS_INSTALL_INCOMPLETE);
995 else if (!unattended_mode)
996 Logger ().setExitMsg (IDS_INSTALL_COMPLETE);
997
998 if (rebootneeded)
999 Logger ().setExitMsg (IDS_REBOOT_REQUIRED);
1000 }
1001
1002 static DWORD WINAPI
1003 do_install_reflector (void *p)
1004 {
1005 HANDLE *context;
1006 context = (HANDLE *) p;
1007
1008 SetThreadUILanguage(langid);
1009
1010 try
1011 {
1012 do_install_thread ((HINSTANCE) context[0], (HWND) context[1]);
1013
1014 // Tell the progress page that we're done downloading
1015 Progress.PostMessageNow (WM_APP_INSTALL_THREAD_COMPLETE);
1016 }
1017 TOPLEVEL_CATCH((HWND) context[1], "install");
1018
1019 ExitThread (0);
1020 }
1021
1022 static HANDLE context[2];
1023
1024 void
1025 do_install (HINSTANCE h, HWND owner)
1026 {
1027 context[0] = h;
1028 context[1] = owner;
1029
1030 DWORD threadID;
1031 CreateThread (NULL, 0, do_install_reflector, context, 0, &threadID);
1032 }
This page took 0.101997 seconds and 5 git commands to generate.