Clear filename on GUI after running perpetual preremove scripts
[cygwin-setup.git] / install.cc
blob1fdc699fb7199bf9c80dac3ce332cd8a0daa63ac
1 /*
2 * Copyright (c) 2000, Red Hat, Inc.
3 * Copyright (c) 2003, Robert Collins <rbtcollins@hotmail.com>
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.
10 * A copy of the GNU General Public License can be found at
11 * http://www.gnu.org/
13 * Originally Written by DJ Delorie <dj@cygnus.com>
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. */
23 #include "getopt++/BoolOption.h"
24 #include "LogFile.h"
26 #include "win32.h"
27 #include "commctrl.h"
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>
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"
57 #include "package_db.h"
58 #include "package_meta.h"
59 #include "package_version.h"
60 #include "package_source.h"
62 #include "threebar.h"
63 #include "Exception.h"
64 #include "processlist.h"
66 extern ThreeBarProgressPage Progress;
68 static long long int total_bytes = 0;
69 static long long int total_bytes_sofar = 0;
70 static int package_bytes = 0;
72 static BoolOption NoReplaceOnReboot (false, 'r', "no-replaceonreboot", IDS_HELPTEXT_NO_REPLACEONREBOOT);
74 struct std_dirs_t {
75 const char *name;
76 mode_t mode;
79 class PerpetualRemoveFindVisitor : public FindVisitor
81 public:
82 PerpetualRemoveFindVisitor (std::vector<Script> *scripts, const std::string& stratum)
83 : _scripts(scripts),
84 stratum(stratum)
86 virtual void visitFile(const std::string& basePath,
87 const WIN32_FIND_DATA *theFile)
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));
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;
103 class Installer
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);
132 Installer::Installer() : errors(0)
136 void
137 Installer::initDialog()
139 Progress.SetText2 ("");
140 Progress.SetText3 ("");
143 void
144 Installer::progress (int bytes)
146 if (package_bytes > 0)
147 Progress.SetBar1 (bytes, package_bytes);
149 if (total_bytes > 0)
150 Progress.SetBar2 (total_bytes_sofar + bytes, total_bytes);
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 }
179 static int num_installs, num_uninstalls;
181 void
182 Installer::preremovePerpetual (const std::string& stratum)
184 std::vector<Script> perpetual;
185 PerpetualRemoveFindVisitor visitor (&perpetual, stratum);
186 Find (cygpath ("/etc/preremove")).accept (visitor);
187 if (perpetual.empty())
188 return;
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();
197 Progress.SetText3 ("");
200 void
201 Installer::preremoveOne (packagemeta & pkg)
203 Progress.SetText1 (IDS_PROGRESS_PREREMOVE);
204 Progress.SetText2 (pkg.name.c_str());
205 Log (LOG_BABBLE) << "Running preremove script for " << pkg.name << endLog;
206 const unsigned numexts = 4;
207 const char* exts[numexts] = { ".dash", ".sh", ".bat", ".cmd" };
208 for (unsigned i = 0; i < numexts; i++)
209 try_run_script ("/etc/preremove/", pkg.name, exts[i]);
212 void
213 Installer::uninstallOne (packagemeta & pkg)
215 if (!pkg.installed)
216 return;
218 Progress.SetText1 (IDS_PROGRESS_UNINSTALL);
219 Progress.SetText2 (pkg.name.c_str());
220 Log (LOG_PLAIN) << "Uninstalling " << pkg.name << endLog;
222 std::set<std::string> dirs;
224 io_stream *listfile = io_stream::open ("cygfile:///etc/setup/" + pkg.name + ".lst.gz", "rb", 0);
225 io_stream *listdata = compress::decompress (listfile);
227 while (listdata)
229 char getfilenamebuffer[CYG_PATH_MAX];
230 const char *sz = listdata->gets (getfilenamebuffer, sizeof (getfilenamebuffer));
231 if (sz == NULL)
232 break;
234 std::string line(sz);
236 /* Insert the paths of all parent directories of line into dirs. */
237 size_t idx = line.length();
238 while ((idx = line.find_last_of('/', idx-1)) != std::string::npos)
240 std::string dir_path = line.substr(0, idx);
241 bool was_new = dirs.insert(dir_path).second;
242 /* If the path was already present in dirs, then all parent paths
243 * must necessarily be present also, so don't do any further work.
244 * */
245 if (!was_new) break;
248 std::string d = cygpath ("/" + line);
249 WCHAR wname[d.size () + 11]; /* Prefix + ".lnk". */
250 mklongpath (wname, d.c_str (), d.size () + 11);
251 DWORD dw = GetFileAttributesW (wname);
252 if (dw != INVALID_FILE_ATTRIBUTES
253 && !(dw & FILE_ATTRIBUTE_DIRECTORY))
255 Log (LOG_BABBLE) << "unlink " << d << endLog;
256 SetFileAttributesW (wname, dw & ~FILE_ATTRIBUTE_READONLY);
257 DeleteFileW (wname);
259 /* Check for Windows shortcut of same name. */
260 d += ".lnk";
261 wcscat (wname, L".lnk");
262 dw = GetFileAttributesW (wname);
263 if (dw != INVALID_FILE_ATTRIBUTES
264 && !(dw & FILE_ATTRIBUTE_DIRECTORY))
266 Log (LOG_BABBLE) << "unlink " << d << endLog;
267 SetFileAttributesW (wname, dw & ~FILE_ATTRIBUTE_READONLY);
268 DeleteFileW (wname);
272 /* Remove the listing file */
273 delete listdata;
274 io_stream::remove ("cygfile:///etc/setup/" + pkg.name + ".lst.gz");
276 /* An STL set maintains itself in sorted order. Thus, iterating over it
277 * in reverse order will ensure we process directories depth-first. */
278 std::set<std::string>::const_iterator it = dirs.end();
279 while (it != dirs.begin())
281 it--;
282 std::string d = cygpath("/" + *it);
283 WCHAR wname[d.size () + 11];
284 mklongpath (wname, d.c_str (), d.size () + 11);
285 if (RemoveDirectoryW (wname))
286 Log (LOG_BABBLE) << "rmdir " << d << endLog;
289 pkg.installed = packageversion();
290 num_uninstalls++;
293 /* log failed scheduling of replace-on-reboot of a given file. */
294 /* also increment errors. */
295 void
296 Installer::replaceOnRebootFailed (const std::string& fn)
298 Log (LOG_TIMESTAMP) << "Unable to schedule reboot replacement of file "
299 << cygpath("/" + fn) << " with " << cygpath("/" + fn + ".new")
300 << " (Win32 Error " << GetLastError() << ")" << endLog;
301 ++errors;
304 /* log successful scheduling of replace-on-reboot of a given file. */
305 /* also set rebootneeded. */
306 void
307 Installer::replaceOnRebootSucceeded (const std::string& fn, bool &rebootneeded)
309 Log (LOG_TIMESTAMP) << "Scheduled reboot replacement of file "
310 << cygpath("/" + fn) << " with " << cygpath("/" + fn + ".new") << endLog;
311 rebootneeded = true;
314 typedef struct
316 const wchar_t *msg;
317 const char *processlist;
318 int iteration;
319 } FileInuseDlgData;
321 static INT_PTR CALLBACK
322 FileInuseDlgProc (HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
324 switch (uMsg)
326 case WM_INITDIALOG:
328 FileInuseDlgData *dlg_data = (FileInuseDlgData *)lParam;
330 SetDlgItemTextW (hwndDlg, IDC_FILE_INUSE_MSG, dlg_data->msg);
331 SetDlgItemText (hwndDlg, IDC_FILE_INUSE_EDIT, dlg_data->processlist);
333 switch (dlg_data->iteration)
335 case 0:
336 ShowWindow (GetDlgItem(hwndDlg, IDC_FILE_INUSE_HELP_0), SW_SHOW);
337 ShowWindow (GetDlgItem(hwndDlg, IDC_FILE_INUSE_HELP_1), SW_HIDE);
338 ShowWindow (GetDlgItem(hwndDlg, IDC_FILE_INUSE_HELP_2), SW_HIDE);
339 break;
341 case 1:
342 ShowWindow (GetDlgItem(hwndDlg, IDC_FILE_INUSE_HELP_0), SW_HIDE);
343 ShowWindow (GetDlgItem(hwndDlg, IDC_FILE_INUSE_HELP_1), SW_SHOW);
344 ShowWindow (GetDlgItem(hwndDlg, IDC_FILE_INUSE_HELP_2), SW_HIDE);
345 SetDlgItemTextW (hwndDlg, IDRETRY, LoadStringW(IDS_FILE_INUSE_KILL).c_str());
346 break;
348 default:
349 case 2:
350 ShowWindow (GetDlgItem(hwndDlg, IDC_FILE_INUSE_HELP_0), SW_HIDE);
351 ShowWindow (GetDlgItem(hwndDlg, IDC_FILE_INUSE_HELP_1), SW_HIDE);
352 ShowWindow (GetDlgItem(hwndDlg, IDC_FILE_INUSE_HELP_2), SW_SHOW);
353 SetDlgItemTextW (hwndDlg, IDRETRY, LoadStringW(IDS_FILE_INUSE_KILL).c_str());
356 return TRUE; // automatically set focus, please
358 case WM_COMMAND:
359 if (HIWORD (wParam) == BN_CLICKED)
361 switch (LOWORD (wParam))
363 case IDIGNORE:
364 case IDRETRY:
365 case IDCONTINUE:
366 EndDialog (hwndDlg, LOWORD (wParam));
367 return TRUE;
372 return FALSE;
375 /* Helper function to create the registry value "AllowProtectedRenames",
376 which is required to make MOVEFILE_DELAY_UNTIL_REBOOT to work on WFP
377 protected files. By default, the entire system drive is WFP protected,
378 so a Cygwin installation on this drive sufferes from the WFP problem.
379 Even though this value is only required since Windows Server 2003 SP1,
380 we just set it here unconditionally since it doesn't hurt at all on
381 older systems. */
382 void
383 create_allow_protected_renames ()
385 HKEY key;
386 DWORD val = 1;
388 if (RegOpenKeyEx (HKEY_LOCAL_MACHINE,
389 "System\\CurrentControlSet\\Control\\Session Manager",
390 0, KEY_ALL_ACCESS | SETUP_KEY_WOW64, &key) == ERROR_SUCCESS)
391 RegSetValueEx (key, "AllowProtectedRenames", 0, REG_DWORD,
392 (BYTE *) &val, sizeof (DWORD));
393 RegCloseKey (key);
396 bool
397 Installer::extract_replace_on_reboot (archive *tarstream, const std::string& prefixURL,
398 const std::string& prefixPath, std::string fn)
400 /* Extract a copy of the file with extension .new appended and
401 indicate it should be replaced on the next reboot. */
402 if (archive::extract_file (tarstream, prefixURL, prefixPath,
403 ".new") != 0)
405 Log (LOG_PLAIN) << "Unable to install file " << prefixURL
406 << prefixPath << fn << ".new" << endLog;
407 ++errors;
408 return true;
410 else
412 std::string s = cygpath ("/" + fn + ".new"),
413 d = cygpath ("/" + fn);
415 WCHAR sname[s.size () + 7];
416 WCHAR dname[d.size () + 7];
418 mklongpath (sname, s.c_str (), s.size () + 7);
419 mklongpath (dname, d.c_str (), d.size () + 7);
420 if (!MoveFileExW (sname, dname,
421 MOVEFILE_DELAY_UNTIL_REBOOT |
422 MOVEFILE_REPLACE_EXISTING))
423 replaceOnRebootFailed (fn);
424 else
426 create_allow_protected_renames ();
427 replaceOnRebootSucceeded (fn, rebootneeded);
430 return false;
433 static char all_null[512];
435 /* install one source at a given prefix. */
436 void
437 Installer::installOne (packagemeta &pkgm, const packageversion &ver,
438 packagesource &source,
439 const std::string& prefixURL,
440 const std::string& prefixPath,
441 HWND owner)
443 if (!source.Canonical())
444 return;
445 Progress.SetText1 (IDS_PROGRESS_INSTALL);
446 Progress.SetText2 ((pkgm.name + "-" + ver.Canonical_version()).c_str());
448 io_stream *pkgfile = NULL;
450 if (!source.Cached())
452 note (NULL, IDS_ERR_OPEN_READ, source.Canonical (), "Unknown filename");
453 ++errors;
454 return;
457 if (!io_stream::exists (source.Cached ())
458 || !(pkgfile = io_stream::open (source.Cached (), "rb", 0)))
460 note (NULL, IDS_ERR_OPEN_READ, source.Cached (), "No such file");
461 ++errors;
462 return;
466 /* At this point pkgfile is an opened io_stream to either a .tar.bz2 file,
467 a .tar.gz file, a .tar.lzma file, or just a .tar file. Try it first as
468 a compressed file and if that fails try opening it as a tar directly.
469 If both fail, abort.
471 Note on io_stream pointer management:
473 Both the archive and decompress classes take ownership of the io_stream
474 pointer they were opened with, meaning they delete it in their dtor. So
475 deleting tarstream should also result in deleting the underlying
476 try_decompress and pkgfile io_streams as well. */
478 archive *tarstream = NULL;
479 io_stream *try_decompress = NULL;
481 if ((try_decompress = compress::decompress (pkgfile)) != NULL)
483 if ((tarstream = archive::extract (try_decompress)) == NULL)
485 /* Decompression succeeded but we couldn't grok it as a valid tar
486 archive. */
487 char c[512];
488 ssize_t len;
489 if ((len = try_decompress->peek (c, 512)) < 0
490 || !memcmp (c, all_null, len))
491 /* In some cases, maintainers have uploaded bzipped
492 0-byte files as dummy packages instead of empty tar files.
493 This is common enough that we should not treat this as an
494 error condition.
495 Same goes for tar archives consisting of a big block of
496 all zero bytes (the famous 46 bytes tar archives). */
498 if (ver.Type () == package_binary)
499 pkgm.installed = ver;
501 else
503 note (NULL, IDS_ERR_OPEN_READ, source.Cached (),
504 "Invalid or unsupported tar format");
505 ++errors;
507 delete try_decompress;
508 return;
511 else if ((tarstream = archive::extract (pkgfile)) == NULL)
513 /* Not a compressed tarball, not a plain tarball, give up. */
514 delete pkgfile;
515 note (NULL, IDS_ERR_OPEN_READ, source.Cached (),
516 "Unrecognisable file format");
517 ++errors;
518 return;
521 /* For binary packages, create a manifest in /etc/setup/ that lists the
522 filename of each file that was unpacked. */
524 io_stream *lst = NULL;
525 if (ver.Type () == package_binary)
527 std::string lstfn = "cygfile:///etc/setup/" + pkgm.name + ".lst.gz";
529 io_stream *tmp;
530 if ((tmp = io_stream::open (lstfn, "wb", 0644)) == NULL)
531 Log (LOG_PLAIN) << "Warning: Unable to create lst file " + lstfn +
532 " - uninstall of this package will leave orphaned files." << endLog;
533 else
535 lst = new compress_gz (tmp, "w9");
536 if (lst->error ())
538 delete lst;
539 lst = NULL;
540 Log (LOG_PLAIN) << "Warning: gzip unable to write to lst file " +
541 lstfn + " - uninstall of this package will leave orphaned files."
542 << endLog;
547 package_bytes = source.size;
548 Log (LOG_PLAIN) << "Extracting from " << source.Cached () << endLog;
550 bool error_in_this_package = _installOne(pkgm, prefixURL, prefixPath, owner,
551 pkgfile, tarstream, lst, false);
552 if (tarstream->seek(0, IO_SEEK_SET) == -1)
553 Log (LOG_PLAIN) << "Error rewinding to extract symlinks" << source.Cached () << endLog;
555 error_in_this_package |= _installOne(pkgm, prefixURL, prefixPath, owner,
556 pkgfile, tarstream, lst, true);
558 if (lst)
559 delete lst;
560 delete tarstream;
562 total_bytes_sofar += package_bytes;
563 progress (0);
565 int df = diskfull (get_root_dir ().c_str ());
566 Progress.SetBar3 (df);
568 if (ver.Type () == package_binary && !error_in_this_package)
569 pkgm.installed = ver;
572 bool
573 Installer::_installOne (packagemeta &pkgm,
574 const std::string& prefixURL,
575 const std::string& prefixPath,
576 HWND owner,
577 io_stream *pkgfile,
578 archive *tarstream,
579 io_stream *lst,
580 bool symlink_phase)
582 bool error_in_this_package = false;
583 bool ignoreInUseErrors = false;
584 bool ignoreExtractErrors = unattended_mode;
586 std::string fn;
587 while ((fn = tarstream->next_file_name ()).size ())
589 if (symlink_phase != (tarstream->next_file_type () == ARCHIVE_FILE_SYMLINK))
591 tarstream->skip_file();
592 continue;
595 std::string canonicalfn = prefixPath + fn;
597 // pathnames starting "." (i.e. dotfiles in the root directory) are
598 // reserved for package metadata. Don't extract them.
599 if (fn[0] == '.')
601 tarstream->skip_file ();
602 continue;
605 Progress.SetText3 (canonicalfn.c_str ());
606 Log (LOG_BABBLE) << "Installing file " << prefixURL << prefixPath
607 << fn << endLog;
608 if (lst)
610 std::string tmp = fn + "\n";
611 lst->write (tmp.c_str(), tmp.size());
613 if (Script::isAScript (fn))
614 pkgm.addScript (Script (canonicalfn));
616 int iteration = 0;
617 archive::extract_results extres;
618 while ((extres = archive::extract_file (tarstream, prefixURL, prefixPath)) != archive::extract_ok)
620 bool error_in_this_file = false;
622 switch (extres)
624 case archive::extract_inuse: // in use
626 if (!ignoreInUseErrors)
628 // convert the file name to long UNC form
629 std::string s = backslash (cygpath ("/" + fn));
630 WCHAR sname[s.size () + 7];
631 mklongpath (sname, s.c_str (), s.size () + 7);
633 // find any process which has that file loaded into it
634 // (note that this doesn't find when the file is un-writeable because the process has
635 // that file opened exclusively)
636 ProcessList processes = Process::listProcessesWithModuleLoaded (sname);
638 std::string plm;
639 for (ProcessList::iterator i = processes.begin (); i != processes.end (); i++)
641 if (i != processes.begin ()) plm += "\r\n";
643 std::string processName = i->getName ();
644 Log (LOG_BABBLE) << processName << endLog;
645 plm += processName;
648 INT_PTR rc = (iteration < 3) ? IDRETRY : IDCONTINUE;
649 if (unattended_mode == attended)
651 if (!processes.empty())
653 // Use the IDD_FILE_INUSE dialog to ask the user if we should try to kill the
654 // listed processes, or just ignore the problem and schedule the file to be
655 // replaced after a reboot
656 FileInuseDlgData dlg_data;
657 std::wstring msg = LoadStringW(IDS_FILE_INUSE_MSG) + L" /" + string_to_wstring(fn);
658 dlg_data.msg = msg.c_str ();
659 dlg_data.processlist = plm.c_str ();
660 dlg_data.iteration = iteration;
662 rc = DialogBoxParam(hinstance, MAKEINTRESOURCE (IDD_FILE_INUSE), owner, FileInuseDlgProc, (LPARAM)&dlg_data);
664 else
666 // We couldn't enumerate any processes which have this file loaded into it
667 // either the cause of the error is something else, or something (e.g security
668 // policy) prevents us from listing those processes.
669 // All we can offer the user is a generic "retry or ignore" choice and a chance
670 // to fix the problem themselves
671 rc = mbox (owner, IDS_EXTRACTION_INUSE,
672 MB_RETRYCONTINUE | MB_ICONWARNING | MB_TASKMODAL,
673 fn.c_str());
677 switch (rc)
679 case IDIGNORE:
680 // manual intervention may have fixed the problem, retry
681 continue;
682 case IDRETRY:
683 if (!processes.empty())
685 // try to stop all the processes
686 for (ProcessList::iterator i = processes.begin (); i != processes.end (); i++)
688 i->kill (iteration);
691 // wait up to 15 seconds for processes to stop
692 for (unsigned int i = 0; i < 15; i++)
694 processes = Process::listProcessesWithModuleLoaded (sname);
695 if (processes.size () == 0)
696 break;
698 Sleep (1000);
701 // else, manual intervention may have fixed the problem
703 // retry
704 iteration++;
705 continue;
706 case IDCONTINUE:
707 // ignore this in-use error, and any subsequent in-use errors for other files in the same package
708 ignoreInUseErrors = true;
709 break;
710 default:
711 break;
713 // fall through to previous functionality
716 if (NoReplaceOnReboot)
718 ++errors;
719 error_in_this_file = true;
720 Log (LOG_PLAIN) << "Not replacing in-use file " << prefixURL
721 << prefixPath << fn << endLog;
723 else
725 error_in_this_file = extract_replace_on_reboot(tarstream, prefixURL, prefixPath, fn);
728 break;
729 case archive::extract_other: // extract failed
731 if (!ignoreExtractErrors)
733 // XXX: We should offer the option to retry,
734 // continue without extracting this particular archive,
735 // or ignore all extraction errors.
736 // Unfortunately, we don't currently know how to rewind
737 // the archive, so we can't retry at present,
738 // and ignore all errors is mis-implemented at present
739 // to only apply to errors arising from a single archive,
740 // so we degenerate to the continue option.
741 mbox (owner, IDS_EXTRACTION_FAILED,
742 MB_OK | MB_ICONWARNING | MB_TASKMODAL,
743 fn.c_str());
746 error_in_this_file = true;
748 break;
749 case archive::extract_ok:
750 break;
753 // We're done with this file
755 // if an error occured ...
756 if (error_in_this_file)
758 // skip to next file in archive
759 tarstream->skip_file();
760 // don't mark this package as successfully installed
761 error_in_this_package = true;
764 break;
766 progress (pkgfile->tell ());
767 num_installs++;
770 return error_in_this_package;
773 static void
774 check_for_old_cygwin (HWND owner)
776 /* Paths within system dir expected to be always < MAX_PATH. */
777 char buf[MAX_PATH + sizeof ("\\cygwin1.dll")];
778 if (!GetSystemDirectory (buf, sizeof (buf)))
779 return;
780 strcat (buf, "\\cygwin1.dll");
781 if (_access (buf, 0) != 0)
782 return;
784 switch (mbox(owner, IDS_INSTALL_OLD_CYGWIN,
785 MB_YESNO | MB_ICONQUESTION | MB_TASKMODAL,
786 buf))
788 case IDYES:
789 if (!DeleteFile (buf))
791 mbox (owner, IDS_INSTALL_DELETE_OLD_CYGWIN_FAILED,
792 MB_OK | MB_ICONEXCLAMATION | MB_TASKMODAL,
793 buf);
795 break;
796 default:
797 break;
800 return;
803 static void
804 do_install_thread (HINSTANCE h, HWND owner)
806 int i;
808 num_installs = 0, num_uninstalls = 0;
809 rebootneeded = false;
811 io_stream::mkpath_p (PATH_TO_DIR,
812 std::string("file://") + std::string(get_root_dir()),
813 0755);
815 for (i = 0; Installer::StandardDirs[i].name; i++)
817 std::string p = cygpath (Installer::StandardDirs[i].name);
818 if (p.size())
819 io_stream::mkpath_p (PATH_TO_DIR, "file://" + p,
820 Installer::StandardDirs[i].mode);
823 /* Create /var/run/utmp */
824 io_stream *utmp = io_stream::open ("cygfile:///var/run/utmp", "wb", 0666);
825 delete utmp;
827 Installer myInstaller;
828 myInstaller.initDialog();
830 total_bytes = 0;
831 total_bytes_sofar = 0;
833 int df = diskfull (get_root_dir ().c_str());
834 Progress.SetBar3 (df);
836 /* Writes Cygwin/setup/rootdir registry value */
837 create_install_root ();
839 std::vector <packageversion> install_q, uninstall_q, sourceinstall_q;
841 packagedb db;
842 const SolverTransactionList &t = db.solution.transactions();
844 /* Calculate the amount of data to md5sum */
845 Progress.SetText1(IDS_PROGRESS_CALCULATING);
846 long long int md5sum_total_bytes = 0;
847 for (SolverTransactionList::const_iterator i = t.begin (); i != t.end (); ++i)
849 packageversion version = i->version;
851 if (i->type == SolverTransaction::transInstall)
853 md5sum_total_bytes += version.source()->size;
857 /* md5sum the packages, build lists of packages to install and uninstall
858 and calculate the total amount of data to install.
859 The hash checking is relevant only for local installs. For a
860 net install, the hashes will have already been verified at download
861 time, and all calls to check_hash() below should instantly return. */
862 long long int md5sum_total_bytes_sofar = 0;
863 for (SolverTransactionList::const_iterator i = t.begin (); i != t.end (); ++i)
865 packageversion version = i->version;
867 if (i->type == SolverTransaction::transInstall)
871 (*version.source ()).check_hash ();
873 catch (Exception *e)
875 // We used to give the user a yes/no option to skip this
876 // package (with "no" meaning install it even though the
877 // archive is corrupt), but both options could damage the
878 // user's system. In the absence of a safe way to recover, we
879 // just bail out.
880 if (e->errNo() == APPERR_CORRUPT_PACKAGE)
881 fatal (owner, IDS_CORRUPT_PACKAGE, version.Name().c_str());
882 // Unexpected exception.
883 throw e;
886 md5sum_total_bytes_sofar += version.source()->size;
887 total_bytes += version.source()->size;
889 // source packages are kept in a separate queue as they are installed
890 // differently: root is /usr/src, install isn't recorded, etc.
891 if (version.Type() == package_source)
892 sourceinstall_q.push_back (version);
893 else
894 install_q.push_back (version);
898 /* Uninstall, upgrade or reinstall */
899 if (i->type == SolverTransaction::transErase)
901 uninstall_q.push_back (version);
904 if (md5sum_total_bytes > 0)
905 Progress.SetBar2 (md5sum_total_bytes_sofar, md5sum_total_bytes);
908 /* start with uninstalls - remove files that new packages may replace */
909 Progress.SetBar2(0);
910 myInstaller.preremovePerpetual ("0");
912 Progress.SetBar2(0);
913 for (std::vector <packageversion>::iterator i = uninstall_q.begin ();
914 i != uninstall_q.end (); ++i)
916 packagemeta *pkgm = db.findBinary (PackageSpecification(i->Name()));
917 if (pkgm)
918 myInstaller.preremoveOne (*pkgm);
919 Progress.SetBar2(std::distance(uninstall_q.begin(), i) + 1, uninstall_q.size());
922 Progress.SetBar2(0);
923 myInstaller.preremovePerpetual ("z");
925 Progress.SetBar2(0);
926 for (std::vector <packageversion>::iterator i = uninstall_q.begin ();
927 i != uninstall_q.end (); ++i)
929 packagemeta *pkgm = db.findBinary (PackageSpecification(i->Name()));
930 if (pkgm)
931 myInstaller.uninstallOne (*pkgm);
932 Progress.SetBar2(std::distance(uninstall_q.begin(), i) + 1, uninstall_q.size());
935 for (std::vector <packageversion>::iterator i = install_q.begin ();
936 i != install_q.end (); ++i)
938 packageversion & pkg = *i;
939 packagemeta *pkgm = db.findBinary (PackageSpecification(i->Name()));
941 try {
942 myInstaller.installOne (*pkgm, pkg, *pkg.source(),
943 "cygfile://", "/", owner);
945 catch (std::exception *e)
947 if (yesno (owner, IDS_INSTALL_ERROR, e->what()) != IDYES)
949 Log (LOG_TIMESTAMP)
950 << "User cancelled setup after install error" << endLog;
951 Logger ().exit (1);
952 return;
957 for (std::vector <packageversion>::iterator i = sourceinstall_q.begin ();
958 i != sourceinstall_q.end (); ++i)
960 packagemeta *pkgm = db.findSource (PackageSpecification(i->Name()));
961 packageversion & pkg = *i;
962 myInstaller.installOne (*pkgm, pkg, *pkg.source(),
963 "cygfile://", "/usr/src/", owner);
966 if (rebootneeded)
967 note (owner, IDS_REBOOT_REQUIRED);
969 int temperr;
970 if ((temperr = db.flush ()))
972 const char *err = strerror (temperr);
973 if (!err)
974 err = "(unknown error)";
975 fatal (owner, IDS_ERR_OPEN_WRITE, "Package Database",
976 err);
979 if (!myInstaller.errors)
980 check_for_old_cygwin (owner);
981 if (num_installs == 0 && num_uninstalls == 0)
983 if (!unattended_mode)
984 Logger ().setExitMsg (IDS_NOTHING_INSTALLED);
985 return;
987 if (num_installs == 0)
989 if (!unattended_mode)
990 Logger ().setExitMsg (IDS_UNINSTALL_COMPLETE);
991 return;
994 if (myInstaller.errors)
995 Logger ().setExitMsg (IDS_INSTALL_INCOMPLETE);
996 else if (!unattended_mode)
997 Logger ().setExitMsg (IDS_INSTALL_COMPLETE);
999 if (rebootneeded)
1000 Logger ().setExitMsg (IDS_REBOOT_REQUIRED);
1003 static DWORD WINAPI
1004 do_install_reflector (void *p)
1006 HANDLE *context;
1007 context = (HANDLE *) p;
1009 SetThreadUILanguage(langid);
1013 do_install_thread ((HINSTANCE) context[0], (HWND) context[1]);
1015 // Tell the progress page that we're done downloading
1016 Progress.PostMessageNow (WM_APP_INSTALL_THREAD_COMPLETE);
1018 TOPLEVEL_CATCH((HWND) context[1], "install");
1020 ExitThread (0);
1023 static HANDLE context[2];
1025 void
1026 do_install (HINSTANCE h, HWND owner)
1028 context[0] = h;
1029 context[1] = owner;
1031 DWORD threadID;
1032 CreateThread (NULL, 0, do_install_reflector, context, 0, &threadID);