Add support for creating WSL symlinks
[cygwin-setup.git] / install.cc
blob51ec4b5711ef177116969896f72c7648e4ff3183
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>
37 #include "ini.h"
38 #include "resource.h"
39 #include "dialog.h"
40 #include "geturl.h"
41 #include "state.h"
42 #include "diskfull.h"
43 #include "msg.h"
44 #include "mount.h"
45 #include "mount.h"
46 #include "filemanip.h"
47 #include "io_stream.h"
48 #include "compress.h"
49 #include "compress_gz.h"
50 #include "archive.h"
51 #include "archive_tar.h"
52 #include "script.h"
54 #include "package_db.h"
55 #include "package_meta.h"
56 #include "package_version.h"
57 #include "package_source.h"
59 #include "threebar.h"
60 #include "Exception.h"
61 #include "processlist.h"
63 extern ThreeBarProgressPage Progress;
65 static long long int total_bytes = 0;
66 static long long int total_bytes_sofar = 0;
67 static int package_bytes = 0;
69 static BoolOption NoReplaceOnReboot (false, 'r', "no-replaceonreboot",
70 "Disable replacing in-use files on next "
71 "reboot.");
73 struct std_dirs_t {
74 const char *name;
75 mode_t mode;
78 class Installer
80 public:
81 static std_dirs_t StandardDirs[];
82 Installer();
83 void initDialog();
84 void progress (int bytes);
85 void preremoveOne (packagemeta &);
86 void uninstallOne (packagemeta &);
87 void replaceOnRebootFailed (const std::string& fn);
88 void replaceOnRebootSucceeded (const std::string& fn, bool &rebootneeded);
89 void installOne (packagemeta &pkg, const packageversion &ver,
90 packagesource &source,
91 const std::string& , const std::string&, HWND );
92 int errors;
93 private:
94 bool extract_replace_on_reboot(archive *, const std::string&,
95 const std::string&, std::string);
99 Installer::Installer() : errors(0)
103 void
104 Installer::initDialog()
106 Progress.SetText2 ("");
107 Progress.SetText3 ("");
110 void
111 Installer::progress (int bytes)
113 if (package_bytes > 0)
114 Progress.SetBar1 (bytes, package_bytes);
116 if (total_bytes > 0)
117 Progress.SetBar2 (total_bytes_sofar + bytes, total_bytes);
120 std_dirs_t
121 Installer::StandardDirs[] = {
122 { "/bin", 0755 },
123 { "/dev", 0755 },
124 { "/dev/mqueue", 01777 },
125 { "/dev/shm", 01777 },
126 { "/etc", 0755 },
127 { "/etc/fstab.d", 01777 },
128 { "/home", 01777 },
129 { "/lib", 0755 },
130 { "/tmp", 01777 },
131 { "/usr", 0755 },
132 { "/usr/bin", 0755 },
133 { "/usr/lib", 0755 },
134 { "/usr/local", 0755 },
135 { "/usr/local/bin", 0755 },
136 { "/usr/local/etc", 0755 },
137 { "/usr/local/lib", 0755 },
138 { "/usr/src", 0755 },
139 { "/usr/tmp", 01777 },
140 { "/var/log", 01777 },
141 { "/var/run", 01777 },
142 { "/var/tmp", 01777 },
143 { NULL, 0 }
146 static int num_installs, num_uninstalls;
148 void
149 Installer::preremoveOne (packagemeta & pkg)
151 Progress.SetText1 (IDS_PROGRESS_PREREMOVE);
152 Progress.SetText2 (pkg.name.c_str());
153 Log (LOG_BABBLE) << "Running preremove script for " << pkg.name << endLog;
154 const unsigned numexts = 4;
155 const char* exts[numexts] = { ".dash", ".sh", ".bat", ".cmd" };
156 for (unsigned i = 0; i < numexts; i++)
157 try_run_script ("/etc/preremove/", pkg.name, exts[i]);
160 void
161 Installer::uninstallOne (packagemeta & pkg)
163 if (!pkg.installed)
164 return;
166 Progress.SetText1 (IDS_PROGRESS_UNINSTALL);
167 Progress.SetText2 (pkg.name.c_str());
168 Log (LOG_PLAIN) << "Uninstalling " << pkg.name << endLog;
170 std::set<std::string> dirs;
172 io_stream *listfile = io_stream::open ("cygfile:///etc/setup/" + pkg.name + ".lst.gz", "rb", 0);
173 io_stream *listdata = compress::decompress (listfile);
175 while (listdata)
177 char getfilenamebuffer[CYG_PATH_MAX];
178 const char *sz = listdata->gets (getfilenamebuffer, sizeof (getfilenamebuffer));
179 if (sz == NULL)
180 break;
182 std::string line(sz);
184 /* Insert the paths of all parent directories of line into dirs. */
185 size_t idx = line.length();
186 while ((idx = line.find_last_of('/', idx-1)) != std::string::npos)
188 std::string dir_path = line.substr(0, idx);
189 bool was_new = dirs.insert(dir_path).second;
190 /* If the path was already present in dirs, then all parent paths
191 * must necessarily be present also, so don't do any further work.
192 * */
193 if (!was_new) break;
196 std::string d = cygpath ("/" + line);
197 WCHAR wname[d.size () + 11]; /* Prefix + ".lnk". */
198 mklongpath (wname, d.c_str (), d.size () + 11);
199 DWORD dw = GetFileAttributesW (wname);
200 if (dw != INVALID_FILE_ATTRIBUTES
201 && !(dw & FILE_ATTRIBUTE_DIRECTORY))
203 Log (LOG_BABBLE) << "unlink " << d << endLog;
204 SetFileAttributesW (wname, dw & ~FILE_ATTRIBUTE_READONLY);
205 DeleteFileW (wname);
207 /* Check for Windows shortcut of same name. */
208 d += ".lnk";
209 wcscat (wname, L".lnk");
210 dw = GetFileAttributesW (wname);
211 if (dw != INVALID_FILE_ATTRIBUTES
212 && !(dw & FILE_ATTRIBUTE_DIRECTORY))
214 Log (LOG_BABBLE) << "unlink " << d << endLog;
215 SetFileAttributesW (wname, dw & ~FILE_ATTRIBUTE_READONLY);
216 DeleteFileW (wname);
220 /* Remove the listing file */
221 delete listdata;
222 io_stream::remove ("cygfile:///etc/setup/" + pkg.name + ".lst.gz");
224 /* An STL set maintains itself in sorted order. Thus, iterating over it
225 * in reverse order will ensure we process directories depth-first. */
226 std::set<std::string>::const_iterator it = dirs.end();
227 while (it != dirs.begin())
229 it--;
230 std::string d = cygpath("/" + *it);
231 WCHAR wname[d.size () + 11];
232 mklongpath (wname, d.c_str (), d.size () + 11);
233 if (RemoveDirectoryW (wname))
234 Log (LOG_BABBLE) << "rmdir " << d << endLog;
237 pkg.installed = packageversion();
238 num_uninstalls++;
241 /* log failed scheduling of replace-on-reboot of a given file. */
242 /* also increment errors. */
243 void
244 Installer::replaceOnRebootFailed (const std::string& fn)
246 Log (LOG_TIMESTAMP) << "Unable to schedule reboot replacement of file "
247 << cygpath("/" + fn) << " with " << cygpath("/" + fn + ".new")
248 << " (Win32 Error " << GetLastError() << ")" << endLog;
249 ++errors;
252 /* log successful scheduling of replace-on-reboot of a given file. */
253 /* also set rebootneeded. */
254 void
255 Installer::replaceOnRebootSucceeded (const std::string& fn, bool &rebootneeded)
257 Log (LOG_TIMESTAMP) << "Scheduled reboot replacement of file "
258 << cygpath("/" + fn) << " with " << cygpath("/" + fn + ".new") << endLog;
259 rebootneeded = true;
262 #define MB_RETRYCONTINUE 7
263 #if !defined(IDCONTINUE)
264 #define IDCONTINUE IDCANCEL
265 #endif
267 static HHOOK hMsgBoxHook;
268 LRESULT CALLBACK CBTProc(int nCode, WPARAM wParam, LPARAM lParam) {
269 HWND hWnd;
270 switch (nCode) {
271 case HCBT_ACTIVATE:
272 hWnd = (HWND)wParam;
273 if (GetDlgItem(hWnd, IDCANCEL) != NULL)
274 SetDlgItemText(hWnd, IDCANCEL, "Continue");
275 UnhookWindowsHookEx(hMsgBoxHook);
277 return CallNextHookEx(hMsgBoxHook, nCode, wParam, lParam);
280 int _custom_MessageBox(HWND hWnd, LPCTSTR szText, LPCTSTR szCaption, UINT uType) {
281 int retval;
282 bool retry_continue = (uType & MB_TYPEMASK) == MB_RETRYCONTINUE;
283 if (retry_continue) {
284 uType &= ~MB_TYPEMASK; uType |= MB_RETRYCANCEL;
285 // Install a window hook, so we can intercept the message-box
286 // creation, and customize it
287 // Only install for THIS thread!!!
288 hMsgBoxHook = SetWindowsHookEx(WH_CBT, CBTProc, NULL, GetCurrentThreadId());
290 retval = MessageBox(hWnd, szText, szCaption, uType);
291 // Intercept the return value for less confusing results
292 if (retry_continue && retval == IDCANCEL)
293 return IDCONTINUE;
294 return retval;
297 #undef MessageBox
298 #define MessageBox _custom_MessageBox
300 typedef struct
302 const char *msg;
303 const char *processlist;
304 int iteration;
305 } FileInuseDlgData;
307 static INT_PTR CALLBACK
308 FileInuseDlgProc (HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
310 switch (uMsg)
312 case WM_INITDIALOG:
314 FileInuseDlgData *dlg_data = (FileInuseDlgData *)lParam;
316 SetDlgItemText (hwndDlg, IDC_FILE_INUSE_MSG, dlg_data->msg);
317 SetDlgItemText (hwndDlg, IDC_FILE_INUSE_EDIT, dlg_data->processlist);
319 switch (dlg_data->iteration)
321 case 0:
322 break; // show the dialog the way it is in the resource
324 case 1:
325 SetDlgItemText (hwndDlg, IDRETRY, "&Kill Processes");
326 SetDlgItemText (hwndDlg, IDC_FILE_INUSE_HELP,
327 "Select 'Retry' to retry, "
328 "Select 'Kill' to kill processes and retry, or "
329 "select 'Continue' to go on anyway (the file will be updated after a reboot).");
330 break;
332 default:
333 case 2:
334 SetDlgItemText (hwndDlg, IDRETRY, "&Kill Processes");
335 SetDlgItemText (hwndDlg, IDC_FILE_INUSE_HELP,
336 "Select 'Retry' to retry, "
337 "select 'Kill' to forcibly kill all processes and retry, or "
338 "select 'Continue' to go on anyway (the file will be updated after a reboot).");
341 return TRUE; // automatically set focus, please
343 case WM_COMMAND:
344 if (HIWORD (wParam) == BN_CLICKED)
346 switch (LOWORD (wParam))
348 case IDIGNORE:
349 case IDRETRY:
350 case IDCONTINUE:
351 EndDialog (hwndDlg, LOWORD (wParam));
352 return TRUE;
357 return FALSE;
360 /* Helper function to create the registry value "AllowProtectedRenames",
361 which is required to make MOVEFILE_DELAY_UNTIL_REBOOT to work on WFP
362 protected files. By default, the entire system drive is WFP protected,
363 so a Cygwin installation on this drive sufferes from the WFP problem.
364 Even though this value is only required since Windows Server 2003 SP1,
365 we just set it here unconditionally since it doesn't hurt at all on
366 older systems. */
367 void
368 create_allow_protected_renames ()
370 HKEY key;
371 DWORD val = 1;
373 if (RegOpenKeyEx (HKEY_LOCAL_MACHINE,
374 "System\\CurrentControlSet\\Control\\Session Manager",
375 0, KEY_ALL_ACCESS | SETUP_KEY_WOW64, &key) == ERROR_SUCCESS)
376 RegSetValueEx (key, "AllowProtectedRenames", 0, REG_DWORD,
377 (BYTE *) &val, sizeof (DWORD));
378 RegCloseKey (key);
381 bool
382 Installer::extract_replace_on_reboot (archive *tarstream, const std::string& prefixURL,
383 const std::string& prefixPath, std::string fn)
385 /* Extract a copy of the file with extension .new appended and
386 indicate it should be replaced on the next reboot. */
387 if (archive::extract_file (tarstream, prefixURL, prefixPath,
388 ".new") != 0)
390 Log (LOG_PLAIN) << "Unable to install file " << prefixURL
391 << prefixPath << fn << ".new" << endLog;
392 ++errors;
393 return true;
395 else
397 std::string s = cygpath ("/" + fn + ".new"),
398 d = cygpath ("/" + fn);
400 WCHAR sname[s.size () + 7];
401 WCHAR dname[d.size () + 7];
403 mklongpath (sname, s.c_str (), s.size () + 7);
404 mklongpath (dname, d.c_str (), d.size () + 7);
405 if (!MoveFileExW (sname, dname,
406 MOVEFILE_DELAY_UNTIL_REBOOT |
407 MOVEFILE_REPLACE_EXISTING))
408 replaceOnRebootFailed (fn);
409 else
411 create_allow_protected_renames ();
412 replaceOnRebootSucceeded (fn, rebootneeded);
415 return false;
418 static char all_null[512];
420 /* install one source at a given prefix. */
421 void
422 Installer::installOne (packagemeta &pkgm, const packageversion &ver,
423 packagesource &source,
424 const std::string& prefixURL,
425 const std::string& prefixPath,
426 HWND owner)
428 if (!source.Canonical())
429 return;
430 Progress.SetText1 (IDS_PROGRESS_INSTALL);
431 Progress.SetText2 ((pkgm.name + "-" + ver.Canonical_version()).c_str());
433 io_stream *pkgfile = NULL;
435 if (!source.Cached())
437 note (NULL, IDS_ERR_OPEN_READ, source.Canonical (), "Unknown filename");
438 ++errors;
439 return;
442 if (!io_stream::exists (source.Cached ())
443 || !(pkgfile = io_stream::open (source.Cached (), "rb", 0)))
445 note (NULL, IDS_ERR_OPEN_READ, source.Cached (), "No such file");
446 ++errors;
447 return;
451 /* At this point pkgfile is an opened io_stream to either a .tar.bz2 file,
452 a .tar.gz file, a .tar.lzma file, or just a .tar file. Try it first as
453 a compressed file and if that fails try opening it as a tar directly.
454 If both fail, abort.
456 Note on io_stream pointer management:
458 Both the archive and decompress classes take ownership of the io_stream
459 pointer they were opened with, meaning they delete it in their dtor. So
460 deleting tarstream should also result in deleting the underlying
461 try_decompress and pkgfile io_streams as well. */
463 archive *tarstream = NULL;
464 io_stream *try_decompress = NULL;
466 if ((try_decompress = compress::decompress (pkgfile)) != NULL)
468 if ((tarstream = archive::extract (try_decompress)) == NULL)
470 /* Decompression succeeded but we couldn't grok it as a valid tar
471 archive. */
472 char c[512];
473 ssize_t len;
474 if ((len = try_decompress->peek (c, 512)) < 0
475 || !memcmp (c, all_null, len))
476 /* In some cases, maintainers have uploaded bzipped
477 0-byte files as dummy packages instead of empty tar files.
478 This is common enough that we should not treat this as an
479 error condition.
480 Same goes for tar archives consisting of a big block of
481 all zero bytes (the famous 46 bytes tar archives). */
483 if (ver.Type () == package_binary)
484 pkgm.installed = ver;
486 else
488 note (NULL, IDS_ERR_OPEN_READ, source.Cached (),
489 "Invalid or unsupported tar format");
490 ++errors;
492 delete try_decompress;
493 return;
496 else if ((tarstream = archive::extract (pkgfile)) == NULL)
498 /* Not a compressed tarball, not a plain tarball, give up. */
499 delete pkgfile;
500 note (NULL, IDS_ERR_OPEN_READ, source.Cached (),
501 "Unrecognisable file format");
502 ++errors;
503 return;
506 /* For binary packages, create a manifest in /etc/setup/ that lists the
507 filename of each file that was unpacked. */
509 io_stream *lst = NULL;
510 if (ver.Type () == package_binary)
512 std::string lstfn = "cygfile:///etc/setup/" + pkgm.name + ".lst.gz";
514 io_stream *tmp;
515 if ((tmp = io_stream::open (lstfn, "wb", 0644)) == NULL)
516 Log (LOG_PLAIN) << "Warning: Unable to create lst file " + lstfn +
517 " - uninstall of this package will leave orphaned files." << endLog;
518 else
520 lst = new compress_gz (tmp, "w9");
521 if (lst->error ())
523 delete lst;
524 lst = NULL;
525 Log (LOG_PLAIN) << "Warning: gzip unable to write to lst file " +
526 lstfn + " - uninstall of this package will leave orphaned files."
527 << endLog;
532 bool error_in_this_package = false;
533 bool ignoreInUseErrors = false;
534 bool ignoreExtractErrors = unattended_mode;
536 package_bytes = source.size;
537 Log (LOG_PLAIN) << "Extracting from " << source.Cached () << endLog;
539 std::string fn;
540 while ((fn = tarstream->next_file_name ()).size ())
542 std::string canonicalfn = prefixPath + fn;
544 // pathnames starting "." (i.e. dotfiles in the root directory) are
545 // reserved for package metadata. Don't extract them.
546 if (fn[0] == '.')
548 tarstream->skip_file ();
549 continue;
552 Progress.SetText3 (canonicalfn.c_str ());
553 Log (LOG_BABBLE) << "Installing file " << prefixURL << prefixPath
554 << fn << endLog;
555 if (lst)
557 std::string tmp = fn + "\n";
558 lst->write (tmp.c_str(), tmp.size());
560 if (Script::isAScript (fn))
561 pkgm.addScript (Script (canonicalfn));
563 int iteration = 0;
564 archive::extract_results extres;
565 while ((extres = archive::extract_file (tarstream, prefixURL, prefixPath)) != archive::extract_ok)
567 bool error_in_this_file = false;
569 switch (extres)
571 case archive::extract_inuse: // in use
573 if (!ignoreInUseErrors)
575 // convert the file name to long UNC form
576 std::string s = backslash (cygpath ("/" + fn));
577 WCHAR sname[s.size () + 7];
578 mklongpath (sname, s.c_str (), s.size () + 7);
580 // find any process which has that file loaded into it
581 // (note that this doesn't find when the file is un-writeable because the process has
582 // that file opened exclusively)
583 ProcessList processes = Process::listProcessesWithModuleLoaded (sname);
585 std::string plm;
586 for (ProcessList::iterator i = processes.begin (); i != processes.end (); i++)
588 if (i != processes.begin ()) plm += "\r\n";
590 std::string processName = i->getName ();
591 Log (LOG_BABBLE) << processName << endLog;
592 plm += processName;
595 INT_PTR rc = (iteration < 3) ? IDRETRY : IDCONTINUE;
596 if (unattended_mode == attended)
598 if (!processes.empty())
600 // Use the IDD_FILE_INUSE dialog to ask the user if we should try to kill the
601 // listed processes, or just ignore the problem and schedule the file to be
602 // replaced after a reboot
603 FileInuseDlgData dlg_data;
604 std::string msg = "Unable to extract /" + fn;
605 dlg_data.msg = msg.c_str ();
606 dlg_data.processlist = plm.c_str ();
607 dlg_data.iteration = iteration;
609 rc = DialogBoxParam(hinstance, MAKEINTRESOURCE (IDD_FILE_INUSE), owner, FileInuseDlgProc, (LPARAM)&dlg_data);
611 else
613 // We couldn't enumerate any processes which have this file loaded into it
614 // either the cause of the error is something else, or something (e.g security
615 // policy) prevents us from listing those processes.
616 // All we can offer the user is a generic "retry or ignore" choice and a chance
617 // to fix the problem themselves
618 char msg[fn.size() + 300];
619 sprintf (msg,
620 "Unable to extract /%s\r\n\r\n"
621 "The file is in use or some other error occurred.\r\n\r\n"
622 "Please stop all Cygwin processes and select \"Retry\", or "
623 "select \"Continue\" to go on anyway (the file will be updated after a reboot).\r\n",
624 fn.c_str());
626 rc = MessageBox (owner, msg, "Error writing file",
627 MB_RETRYCONTINUE | MB_ICONWARNING | MB_TASKMODAL);
631 switch (rc)
633 case IDIGNORE:
634 // manual intervention may have fixed the problem, retry
635 continue;
636 case IDRETRY:
637 if (!processes.empty())
639 // try to stop all the processes
640 for (ProcessList::iterator i = processes.begin (); i != processes.end (); i++)
642 i->kill (iteration);
645 // wait up to 15 seconds for processes to stop
646 for (unsigned int i = 0; i < 15; i++)
648 processes = Process::listProcessesWithModuleLoaded (sname);
649 if (processes.size () == 0)
650 break;
652 Sleep (1000);
655 // else, manual intervention may have fixed the problem
657 // retry
658 iteration++;
659 continue;
660 case IDCONTINUE:
661 // ignore this in-use error, and any subsequent in-use errors for other files in the same package
662 ignoreInUseErrors = true;
663 break;
664 default:
665 break;
667 // fall through to previous functionality
670 if (NoReplaceOnReboot)
672 ++errors;
673 error_in_this_file = true;
674 Log (LOG_PLAIN) << "Not replacing in-use file " << prefixURL
675 << prefixPath << fn << endLog;
677 else
679 error_in_this_file = extract_replace_on_reboot(tarstream, prefixURL, prefixPath, fn);
682 break;
683 case archive::extract_other: // extract failed
685 if (!ignoreExtractErrors)
687 char msg[fn.size() + 300];
688 sprintf (msg,
689 "Unable to extract /%s -- corrupt package?\r\n",
690 fn.c_str());
692 // XXX: We should offer the option to retry,
693 // continue without extracting this particular archive,
694 // or ignore all extraction errors.
695 // Unfortunately, we don't currently know how to rewind
696 // the archive, so we can't retry at present,
697 // and ignore all errors is mis-implemented at present
698 // to only apply to errors arising from a single archive,
699 // so we degenerate to the continue option.
700 mbox (owner, msg, "File extraction error",
701 MB_OK | MB_ICONWARNING | MB_TASKMODAL);
704 error_in_this_file = true;
706 break;
707 case archive::extract_ok:
708 break;
711 // We're done with this file
713 // if an error occured ...
714 if (error_in_this_file)
716 // skip to next file in archive
717 tarstream->skip_file();
718 // don't mark this package as successfully installed
719 error_in_this_package = true;
722 break;
724 progress (pkgfile->tell ());
725 num_installs++;
728 if (lst)
729 delete lst;
730 delete tarstream;
732 total_bytes_sofar += package_bytes;
733 progress (0);
735 int df = diskfull (get_root_dir ().c_str ());
736 Progress.SetBar3 (df);
738 if (ver.Type () == package_binary && !error_in_this_package)
739 pkgm.installed = ver;
742 static void
743 check_for_old_cygwin (HWND owner)
745 /* Paths within system dir expected to be always < MAX_PATH. */
746 char buf[MAX_PATH + sizeof ("\\cygwin1.dll")];
747 if (!GetSystemDirectory (buf, sizeof (buf)))
748 return;
749 strcat (buf, "\\cygwin1.dll");
750 if (_access (buf, 0) != 0)
751 return;
753 char msg[sizeof (buf) + 132];
754 sprintf (msg,
755 "An old version of cygwin1.dll was found here:\r\n%s\r\nDelete?",
756 buf);
757 switch (MessageBox
758 (owner, msg, "What's that doing there?",
759 MB_YESNO | MB_ICONQUESTION | MB_TASKMODAL))
761 case IDYES:
762 if (!DeleteFile (buf))
764 sprintf (msg, "Couldn't delete file %s.\r\n"
765 "Is the DLL in use by another application?\r\n"
766 "You should delete the old version of cygwin1.dll\r\n"
767 "at your earliest convenience.", buf);
768 mbox (owner, buf, "Couldn't delete file",
769 MB_OK | MB_ICONEXCLAMATION | MB_TASKMODAL);
771 break;
772 default:
773 break;
776 return;
779 static void
780 do_install_thread (HINSTANCE h, HWND owner)
782 int i;
784 num_installs = 0, num_uninstalls = 0;
785 rebootneeded = false;
787 io_stream::mkpath_p (PATH_TO_DIR,
788 std::string("file://") + std::string(get_root_dir()),
789 0755);
791 for (i = 0; Installer::StandardDirs[i].name; i++)
793 std::string p = cygpath (Installer::StandardDirs[i].name);
794 if (p.size())
795 io_stream::mkpath_p (PATH_TO_DIR, "file://" + p,
796 Installer::StandardDirs[i].mode);
799 /* Create /var/run/utmp */
800 io_stream *utmp = io_stream::open ("cygfile:///var/run/utmp", "wb", 0666);
801 delete utmp;
803 Installer myInstaller;
804 myInstaller.initDialog();
806 total_bytes = 0;
807 total_bytes_sofar = 0;
809 int df = diskfull (get_root_dir ().c_str());
810 Progress.SetBar3 (df);
812 /* Writes Cygwin/setup/rootdir registry value */
813 create_install_root ();
815 std::vector <packageversion> install_q, uninstall_q, sourceinstall_q;
817 packagedb db;
818 const SolverTransactionList &t = db.solution.transactions();
820 /* Calculate the amount of data to md5sum */
821 Progress.SetText1(IDS_PROGRESS_CALCULATING);
822 long long int md5sum_total_bytes = 0;
823 for (SolverTransactionList::const_iterator i = t.begin (); i != t.end (); ++i)
825 packageversion version = i->version;
827 if (i->type == SolverTransaction::transInstall)
829 md5sum_total_bytes += version.source()->size;
833 /* md5sum the packages, build lists of packages to install and uninstall
834 and calculate the total amount of data to install.
835 The hash checking is relevant only for local installs. For a
836 net install, the hashes will have already been verified at download
837 time, and all calls to check_hash() below should instantly return. */
838 long long int md5sum_total_bytes_sofar = 0;
839 for (SolverTransactionList::const_iterator i = t.begin (); i != t.end (); ++i)
841 packageversion version = i->version;
843 if (i->type == SolverTransaction::transInstall)
847 (*version.source ()).check_hash ();
849 catch (Exception *e)
851 // We used to give the user a yes/no option to skip this
852 // package (with "no" meaning install it even though the
853 // archive is corrupt), but both options could damage the
854 // user's system. In the absence of a safe way to recover, we
855 // just bail out.
856 if (e->errNo() == APPERR_CORRUPT_PACKAGE)
857 fatal (owner, IDS_CORRUPT_PACKAGE, version.Name().c_str());
858 // Unexpected exception.
859 throw e;
862 md5sum_total_bytes_sofar += version.source()->size;
863 total_bytes += version.source()->size;
865 // source packages are kept in a separate queue as they are installed
866 // differently: root is /usr/src, install isn't recorded, etc.
867 if (version.Type() == package_source)
868 sourceinstall_q.push_back (version);
869 else
870 install_q.push_back (version);
874 /* Uninstall, upgrade or reinstall */
875 if (i->type == SolverTransaction::transErase)
877 uninstall_q.push_back (version);
880 if (md5sum_total_bytes > 0)
881 Progress.SetBar2 (md5sum_total_bytes_sofar, md5sum_total_bytes);
884 /* start with uninstalls - remove files that new packages may replace */
885 Progress.SetBar2(0);
886 for (std::vector <packageversion>::iterator i = uninstall_q.begin ();
887 i != uninstall_q.end (); ++i)
889 packagemeta *pkgm = db.findBinary (PackageSpecification(i->Name()));
890 if (pkgm)
891 myInstaller.preremoveOne (*pkgm);
892 Progress.SetBar2(std::distance(uninstall_q.begin(), i) + 1, uninstall_q.size());
895 Progress.SetBar2(0);
896 for (std::vector <packageversion>::iterator i = uninstall_q.begin ();
897 i != uninstall_q.end (); ++i)
899 packagemeta *pkgm = db.findBinary (PackageSpecification(i->Name()));
900 if (pkgm)
901 myInstaller.uninstallOne (*pkgm);
902 Progress.SetBar2(std::distance(uninstall_q.begin(), i) + 1, uninstall_q.size());
905 for (std::vector <packageversion>::iterator i = install_q.begin ();
906 i != install_q.end (); ++i)
908 packageversion & pkg = *i;
909 packagemeta *pkgm = db.findBinary (PackageSpecification(i->Name()));
911 try {
912 myInstaller.installOne (*pkgm, pkg, *pkg.source(),
913 "cygfile://", "/", owner);
915 catch (std::exception *e)
917 if (yesno (owner, IDS_INSTALL_ERROR, e->what()) != IDYES)
919 Log (LOG_TIMESTAMP)
920 << "User cancelled setup after install error" << endLog;
921 Logger ().exit (1);
922 return;
927 for (std::vector <packageversion>::iterator i = sourceinstall_q.begin ();
928 i != sourceinstall_q.end (); ++i)
930 packagemeta *pkgm = db.findSource (PackageSpecification(i->Name()));
931 packageversion & pkg = *i;
932 myInstaller.installOne (*pkgm, pkg, *pkg.source(),
933 "cygfile://", "/usr/src/", owner);
936 if (rebootneeded)
937 note (owner, IDS_REBOOT_REQUIRED);
939 int temperr;
940 if ((temperr = db.flush ()))
942 const char *err = strerror (temperr);
943 if (!err)
944 err = "(unknown error)";
945 fatal (owner, IDS_ERR_OPEN_WRITE, "Package Database",
946 err);
949 if (!myInstaller.errors)
950 check_for_old_cygwin (owner);
951 if (num_installs == 0 && num_uninstalls == 0)
953 if (!unattended_mode)
954 Logger ().setExitMsg (IDS_NOTHING_INSTALLED);
955 return;
957 if (num_installs == 0)
959 if (!unattended_mode)
960 Logger ().setExitMsg (IDS_UNINSTALL_COMPLETE);
961 return;
964 if (myInstaller.errors)
965 Logger ().setExitMsg (IDS_INSTALL_INCOMPLETE);
966 else if (!unattended_mode)
967 Logger ().setExitMsg (IDS_INSTALL_COMPLETE);
969 if (rebootneeded)
970 Logger ().setExitMsg (IDS_REBOOT_REQUIRED);
973 static DWORD WINAPI
974 do_install_reflector (void *p)
976 HANDLE *context;
977 context = (HANDLE *) p;
981 do_install_thread ((HINSTANCE) context[0], (HWND) context[1]);
983 // Tell the progress page that we're done downloading
984 Progress.PostMessageNow (WM_APP_INSTALL_THREAD_COMPLETE);
986 TOPLEVEL_CATCH((HWND) context[1], "install");
988 ExitThread (0);
991 static HANDLE context[2];
993 void
994 do_install (HINSTANCE h, HWND owner)
996 context[0] = h;
997 context[1] = owner;
999 DWORD threadID;
1000 CreateThread (NULL, 0, do_install_reflector, context, 0, &threadID);