Add a more generic mbox wrapper
[cygwin-setup.git] / main.cc
blobf3444ff69e5c5ba7588dd66ae5d8d6cfb56700ca
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 * Written by DJ Delorie <dj@cygnus.com>
14 * Robert Collins <rbtcollins@hotmail.com>
19 /* OK, here's how this works. Each of the steps needed for install -
20 dialogs, downloads, installs - are in their own files and have some
21 "do_*" function (prototype in dialog.h) and a resource id (IDD_* or
22 IDD_S_* in resource.h) for that step. Each step is responsible for
23 selecting the next step! See the NEXT macro in dialog.h. Note
24 that the IDD_S_* ids are fake; those are for steps that don't
25 really have a controlling dialog (some have progress dialogs, but
26 those don't count, although they could). Replace the IDD_S_* with
27 IDD_* if you create a real dialog for those steps. */
29 #include "win32.h"
30 #include <commctrl.h>
31 #include <shellapi.h>
32 #include "shlobj.h"
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include "resource.h"
37 #include "dialog.h"
38 #include "state.h"
39 #include "msg.h"
40 #include "find.h"
41 #include "mount.h"
42 #include "LogFile.h"
43 #include "setup_version.h"
45 #include "proppage.h"
46 #include "propsheet.h"
48 // Page class headers
49 #include "splash.h"
50 #include "AntiVirus.h"
51 #include "source.h"
52 #include "root.h"
53 #include "localdir.h"
54 #include "net.h"
55 #include "site.h"
56 #include "choose.h"
57 #include "prereq.h"
58 #include "confirm.h"
59 #include "threebar.h"
60 #include "desktop.h"
61 #include "postinstallresults.h"
63 #include "getopt++/GetOption.h"
64 #include "getopt++/BoolOption.h"
65 #include "getopt++/StringOption.h"
66 #include "getopt++/StringChoiceOption.h"
67 #include "mklink2.h"
69 #include "Exception.h"
70 #include <stdexcept>
72 #include "UserSettings.h"
73 #include "SourceSetting.h"
74 #include "ConnectionSetting.h"
75 #include "KeysSetting.h"
77 #include <wincon.h>
78 #include <fstream>
80 #ifdef __MINGW64_VERSION_MAJOR
81 extern char **_argv;
82 #endif
84 bool is_64bit;
85 bool is_new_install = false;
86 std::string SetupArch;
87 std::string SetupIniDir;
89 HINSTANCE hinstance;
91 static StringChoiceOption::StringChoices symlink_types({
92 {"native", SymlinkTypeNative},
93 {"lnk", SymlinkTypeShortcut},
94 {"sys", SymlinkTypeMagic},
95 {"wsl", SymlinkTypeWsl},
96 });
98 static StringOption Arch ("", 'a', "arch", "Architecture to install (x86_64 or x86)", false);
99 static BoolOption UnattendedOption (false, 'q', "quiet-mode", "Unattended setup mode");
100 static BoolOption PackageManagerOption (false, 'M', "package-manager", "Semi-attended chooser-only mode");
101 static BoolOption NoAdminOption (false, 'B', "no-admin", "Do not check for and enforce running as Administrator");
102 static BoolOption WaitOption (false, 'W', "wait", "When elevating, wait for elevated child process");
103 static BoolOption HelpOption (false, 'h', "help", "Print help");
104 static BoolOption VersionOption (false, 'V', "version", "Show version");
105 static StringOption SetupBaseNameOpt ("setup", 'i', "ini-basename", "Use a different basename, e.g. \"foo\", instead of \"setup\"", false);
106 BoolOption UnsupportedOption (false, '\0', "allow-unsupported-windows", "Allow old, unsupported Windows versions");
107 static StringChoiceOption SymlinkTypeOption(symlink_types, '\0', "symlink-type", "Symlink type (lnk, native, sys, wsl)", false, SymlinkTypeMagic);
109 std::string SetupBaseName;
111 static void inline
112 set_cout ()
114 HANDLE my_stdout = GetStdHandle (STD_OUTPUT_HANDLE);
115 if (my_stdout != INVALID_HANDLE_VALUE && GetFileType (my_stdout) != FILE_TYPE_UNKNOWN)
116 return;
118 if (AttachConsole ((DWORD) -1))
120 std::ofstream *conout = new std::ofstream ("conout$");
121 std::cout.rdbuf (conout->rdbuf ());
122 std::cout.flush ();
126 // Other threads talk to these pages, so we need to have it externable.
127 ThreeBarProgressPage Progress;
128 PostInstallResultsPage PostInstallResults;
130 static inline void
131 main_display ()
133 /* nondisplay classes */
134 LocalDirSetting localDir;
135 SourceSetting SourceSettings;
136 ConnectionSetting ConnectionSettings;
137 SiteSetting ChosenSites;
138 ExtraKeysSetting ExtraKeys;
140 SplashPage Splash;
141 AntiVirusPage AntiVirus;
142 SourcePage Source;
143 RootPage Root;
144 LocalDirPage LocalDir;
145 NetPage Net;
146 SitePage Site;
147 ChooserPage Chooser;
148 PrereqPage Prereq;
149 ConfirmPage Confirm;
150 DesktopSetupPage Desktop;
151 PropSheet MainWindow;
153 Log (LOG_TIMESTAMP) << "Current Directory: " << local_dir << endLog;
155 // Initialize common controls
156 INITCOMMONCONTROLSEX icce = { sizeof (INITCOMMONCONTROLSEX),
157 ICC_WIN95_CLASSES | ICC_LISTVIEW_CLASSES };
158 InitCommonControlsEx (&icce);
160 // Initialize COM and ShellLink instance here. For some reason
161 // Windows 7 fails to create the ShellLink instance if this is
162 // done later, in the thread which actually creates the shortcuts.
163 extern IShellLink *sl;
164 CoInitializeEx (NULL, COINIT_APARTMENTTHREADED);
165 HRESULT res = CoCreateInstance (CLSID_ShellLink, NULL,
166 CLSCTX_INPROC_SERVER, IID_IShellLink,
167 (LPVOID *) & sl);
168 if (res)
170 char buf[256];
171 sprintf (buf, "CoCreateInstance failed with error 0x%x.\n"
172 "Setup will not be able to create Cygwin Icons\n"
173 "in the Start Menu or on the Desktop.", (int) res);
174 mbox (NULL, buf, "Cygwin Setup", MB_OK);
177 // Init window class lib
178 Window::SetAppInstance (hinstance);
180 // Create pages
181 Splash.Create ();
182 AntiVirus.Create ();
183 Source.Create ();
184 Root.Create ();
185 LocalDir.Create ();
186 Net.Create ();
187 Site.Create ();
188 Chooser.Create ();
189 Prereq.Create ();
190 Confirm.Create ();
191 Progress.Create ();
192 PostInstallResults.Create ();
193 Desktop.Create ();
195 // Add pages to sheet
196 MainWindow.AddPage (&Splash);
197 MainWindow.AddPage (&AntiVirus);
198 MainWindow.AddPage (&Source);
199 MainWindow.AddPage (&Root);
200 MainWindow.AddPage (&LocalDir);
201 MainWindow.AddPage (&Net);
202 MainWindow.AddPage (&Site);
203 MainWindow.AddPage (&Chooser);
204 MainWindow.AddPage (&Prereq);
205 MainWindow.AddPage (&Confirm);
206 MainWindow.AddPage (&Progress);
207 MainWindow.AddPage (&PostInstallResults);
208 MainWindow.AddPage (&Desktop);
210 // Create the PropSheet main window
211 MainWindow.Create ();
213 // Uninitalize COM
214 if (sl)
215 sl->Release ();
216 CoUninitialize ();
219 int WINAPI
220 WinMain (HINSTANCE h,
221 HINSTANCE hPrevInstance, LPSTR command_line, int cmd_show)
224 hinstance = h;
226 // Make sure the C runtime functions use the same codepage as the GUI
227 char locale[12];
228 snprintf(locale, sizeof locale, ".%u", GetACP());
229 setlocale(LC_ALL, locale);
231 char **_argv;
232 int argc;
233 for (argc = 0, _argv = __argv; *_argv; _argv++)
234 ++argc;
235 _argv = __argv;
237 try {
238 bool help_option = false;
239 bool invalid_option = false;
240 char cwd[MAX_PATH];
241 GetCurrentDirectory (MAX_PATH, cwd);
242 local_dir = std::string (cwd);
244 if (!GetOption::GetInstance ().Process (argc,_argv, NULL))
245 help_option = invalid_option = true;
246 else if (HelpOption)
247 help_option = true;
249 if (!((std::string) Arch).size ())
251 #ifdef __x86_64__
252 is_64bit = true;
253 #else
254 is_64bit = false;
255 #endif
257 else if (((std::string) Arch).find ("64") != std::string::npos)
258 is_64bit = true;
259 else if (((std::string) Arch).find ("32") != std::string::npos
260 || ((std::string) Arch).find ("x86") != std::string::npos)
261 is_64bit = false;
262 else
264 char buff[80 + ((std::string) Arch).size ()];
265 sprintf (buff, "Invalid option for --arch: \"%s\"",
266 ((std::string) Arch).c_str ());
267 fprintf (stderr, "*** %s\n", buff);
268 exit (1);
271 unattended_mode = PackageManagerOption ? chooseronly
272 : (UnattendedOption ? unattended : attended);
274 bool output_only = help_option || VersionOption;
276 SetupBaseName = SetupBaseNameOpt;
277 SetupArch = is_64bit ? "x86_64" : "x86";
278 SetupIniDir = SetupArch+"/";
280 /* Initialize well known SIDs. We need the admin SID to test if we're
281 supposed to elevate. */
282 nt_sec.initialiseWellKnownSIDs ();
283 /* Check if we have to elevate. */
284 bool elevate = !output_only && OSMajorVersion () >= 6
285 && !NoAdminOption && !nt_sec.isRunAsAdmin ();
286 std::string elevate_extra_args;
288 if (unattended_mode || output_only || !elevate)
289 set_cout ();
291 /* Start logging only if we don't elevate. Same for setting default
292 security settings. */
293 LogSingleton::SetInstance (*LogFile::createLogFile ());
294 const char *sep = isdirsep (local_dir[local_dir.size () - 1])
295 ? "" : "\\";
296 /* Don't create log files for help or version output only. */
297 if (!elevate && !output_only)
299 Logger ().setFile (LOG_BABBLE, local_dir + sep + "setup.log.full",
300 false);
301 Logger ().setFile (0, local_dir + sep + "setup.log", true);
302 Log (LOG_PLAIN) << "Starting cygwin install, version "
303 << setup_version << endLog;
306 if (help_option)
308 if (invalid_option)
309 Log (LOG_PLAIN) << "\nError during option processing.\n" << endLog;
310 Log (LOG_PLAIN) << "Cygwin setup " << setup_version << endLog;
311 Log (LOG_PLAIN) << "\nCommand Line Options:\n" << endLog;
312 GetOption::GetInstance ().ParameterUsage (Log (LOG_PLAIN));
313 Log (LOG_PLAIN) << endLog;
314 Log (LOG_PLAIN) << "The default is to both download and install packages, unless either --download or --local-install is specified." << endLog;
315 Logger ().exit (invalid_option ? 1 : 0, false);
316 goto finish_up;
319 if (VersionOption)
321 Log (LOG_PLAIN) << "Cygwin setup " << setup_version << endLog;
322 Logger ().exit (0, false);
323 goto finish_up;
326 /* Check if Cygwin works on this Windows version */
327 if (!UnsupportedOption && (OSMajorVersion () < 6))
329 mbox (NULL, "Cygwin is not supported on this Windows version",
330 "Cygwin Setup", MB_ICONEXCLAMATION | MB_OK);
331 Logger ().exit (1, false);
334 /* Set default DACL and Group. */
335 nt_sec.setDefaultSecurity ((root_scope == IDC_ROOT_SYSTEM));
338 If --symlink-type option isn't given, look for winsymlinks in CYGWIN
339 env var for a default
341 Since the current environment doesn't get passed to the process started
342 with with ShellExecuteEx, we need to convert the env var into an option
343 for that elevated instance.
345 if (!SymlinkTypeOption.isPresent()) {
346 std::string cygwin;
347 DWORD len = GetEnvironmentVariable ("CYGWIN", &cygwin[0], 0);
348 cygwin.resize(len);
349 GetEnvironmentVariable ("CYGWIN", &cygwin[0], len);
351 if (cygwin.find("winsymlinks:native") != std::string::npos)
353 symlinkType = SymlinkTypeNative;
354 elevate_extra_args.append("--symlink-type native");
356 else if (cygwin.find("winsymlinks:wsl") != std::string::npos)
358 symlinkType = SymlinkTypeWsl;
359 elevate_extra_args.append("--symlink-type wsl");
361 else if (cygwin.find("winsymlinks:sys") != std::string::npos)
363 symlinkType = SymlinkTypeMagic;
364 elevate_extra_args.append("--symlink-type sys");
366 else if (cygwin.find("winsymlinks:lnk") != std::string::npos)
368 symlinkType = SymlinkTypeShortcut;
369 elevate_extra_args.append("--symlink-type lnk");
372 else
374 symlinkType = (SymlinkTypeEnum)(int)SymlinkTypeOption;
377 if (symlinkType == SymlinkTypeWsl)
379 VersionInfo v = GetVer();
380 if ((v.major() < 10) ||
381 ((v.major() == 10) && (v.buildNumber() < 14393)))
383 fprintf (stderr, "*** --symlink-type wsl requires Windows 10 1607 or later\n");
384 exit(1);
387 else if (symlinkType == SymlinkTypeNative)
389 if (!(elevate || is_developer_mode() || nt_sec.hasSymlinkCreationRights()))
391 fprintf (stderr, "*** --symlink-type native requires SeCreateSymbolicLink privilege or 'Developer Mode'\n");
392 exit(1);
395 else if (symlinkType == SymlinkTypeShortcut)
397 fprintf (stderr, "*** --symlink-type lnk is not implemented\n");
398 exit(1);
401 if (elevate)
403 char exe_path[MAX_PATH];
404 if (!GetModuleFileName(NULL, exe_path, ARRAYSIZE(exe_path)))
405 goto finish_up;
407 SHELLEXECUTEINFO sei = { sizeof(sei) };
408 sei.lpVerb = "runas";
409 sei.lpFile = exe_path;
410 sei.nShow = SW_NORMAL;
411 if (WaitOption)
412 sei.fMask |= SEE_MASK_NOCLOSEPROCESS;
414 // Avoid another isRunAsAdmin check in the child.
415 std::string command_line_cs (command_line);
416 command_line_cs += " -";
417 command_line_cs += NoAdminOption.shortOption();
418 command_line_cs += " ";
419 command_line_cs += elevate_extra_args;
420 sei.lpParameters = command_line_cs.c_str ();
422 if (ShellExecuteEx(&sei))
424 DWORD exitcode = 0;
425 /* Wait until child process is finished. */
426 if (WaitOption && sei.hProcess != NULL)
427 if (!WaitForSingleObject (sei.hProcess, INFINITE))
428 GetExitCodeProcess (sei.hProcess, &exitcode);
429 Logger ().setExitMsg (IDS_ELEVATED);
430 Logger ().exit (exitcode, false);
432 Log (LOG_PLAIN) << "Starting elevated child process failed" << endLog;
433 Logger ().exit (1, false);
435 else
437 UserSettings Settings;
438 UserSettings::instance().load (local_dir);
439 main_display ();
440 Settings.save (); // Clean exit.. save user options.
441 if (rebootneeded)
442 Logger ().setExitMsg (IDS_REBOOT_REQUIRED);
443 Logger ().exit (rebootneeded ? IDS_REBOOT_REQUIRED : 0);
445 finish_up:
448 TOPLEVEL_CATCH(NULL, "main");
450 // Never reached
451 return 0;