]>
Commit | Line | Data |
---|---|---|
bc78a6d5 RC |
1 | /* |
2 | * Copyright (c) 2001, Jan Nieuwenhuizen. | |
3 | * | |
4 | * This program is free software; you can redistribute it and/or modify | |
5 | * it under the terms of the GNU General Public License as published by | |
6 | * the Free Software Foundation; either version 2 of the License, or | |
7 | * (at your option) any later version. | |
8 | * | |
9 | * A copy of the GNU General Public License can be found at | |
10 | * http://www.gnu.org/ | |
11 | * | |
12 | * Written by DJ Delorie <dj@cygnus.com> | |
13 | * Jan Nieuwenhuizen <janneke@gnu.org> | |
14 | * | |
15 | */ | |
16 | ||
17 | /* The purpose of this file is to provide functions for the invocation | |
18 | of install scripts. */ | |
19 | ||
bc78a6d5 RC |
20 | #include "win32.h" |
21 | #include <stdlib.h> | |
22 | #include <unistd.h> | |
23 | #include <stdio.h> | |
1069407c | 24 | #include "LogSingleton.h" |
3c054baf | 25 | #include "filemanip.h" |
bc78a6d5 RC |
26 | #include "mount.h" |
27 | #include "io_stream.h" | |
ad646f43 | 28 | #include "script.h" |
1069407c | 29 | #include "mkdir.h" |
c3d07c13 AK |
30 | #include "state.h" |
31 | #include "resource.h" | |
5a85457b CF |
32 | #if HAVE_ALLOCA_H |
33 | #include <alloca.h> | |
34 | #else | |
35 | #ifndef alloca | |
36 | #define alloca __builtin_alloca | |
37 | #endif | |
38 | #endif | |
34b58a2e | 39 | #include "mklink2.h" |
c8ea8d6c | 40 | #include "desktop.h" |
bc78a6d5 | 41 | |
390a9146 | 42 | static std::string sh, dash; |
b56d411d | 43 | static const char *cmd; |
5a85457b CF |
44 | |
45 | static void | |
46 | sanitize_PATH () | |
47 | { | |
48 | char dummy; | |
49 | DWORD len = GetEnvironmentVariable ("PATH", &dummy, 0); | |
50 | char *path = (char *) alloca (len + 1); | |
51 | GetEnvironmentVariable ("PATH", path, len); | |
52 | std::string newpath = backslash (cygpath ("/bin") + ";" | |
53 | + cygpath ("/usr/sbin") + ";" | |
54 | + cygpath ("/sbin")); | |
7cc4d95e | 55 | len = (UINT) GetSystemWindowsDirectory (&dummy, 0); |
5a85457b | 56 | char *system_root = (char *) alloca (len + 2); |
7cc4d95e | 57 | GetSystemWindowsDirectory (system_root, len--); |
5a85457b CF |
58 | if (system_root[len - 1] != '\\') |
59 | { | |
60 | system_root[len] = '\\'; | |
61 | system_root[++len] = '\0'; | |
62 | } | |
63 | for (char *p = strtok (path, ";"); p; p = strtok (NULL, ";")) | |
64 | { | |
65 | size_t plen = strlen (p); | |
66 | size_t cmplen = plen == (len - 1) ? plen : len; | |
67 | if (strncasecmp (system_root, p, cmplen) == 0) | |
68 | { | |
69 | newpath += ";"; | |
70 | newpath += p; | |
71 | } | |
72 | } | |
73 | SetEnvironmentVariable ("PATH", newpath.c_str()); | |
74 | } | |
75 | ||
34b58a2e JT |
76 | static void |
77 | modify_CYGWIN () | |
78 | { | |
79 | std::string cygwin; | |
80 | DWORD len = GetEnvironmentVariable ("CYGWIN", &cygwin[0], 0); | |
81 | if (len > 0) | |
82 | { | |
83 | cygwin.resize(len); | |
84 | GetEnvironmentVariable ("CYGWIN", &cygwin[0], len); | |
85 | cygwin.resize(len-1); // trim terminating null | |
86 | cygwin.append(" "); | |
87 | } | |
88 | ||
89 | switch (symlinkType) | |
90 | { | |
91 | case SymlinkTypeNative: | |
92 | cygwin.append("winsymlinks:native"); | |
93 | break; | |
94 | ||
95 | case SymlinkTypeWsl: | |
96 | cygwin.append("winsymlinks:wsl"); | |
97 | break; | |
98 | ||
99 | case SymlinkTypeMagic: | |
100 | cygwin.append("winsymlinks:sys"); | |
101 | break; | |
102 | ||
103 | case SymlinkTypeShortcut: /* not yet implemented */ | |
104 | default: | |
105 | break; | |
106 | } | |
107 | ||
108 | SetEnvironmentVariable ("CYGWIN", cygwin.c_str()); | |
109 | } | |
bc78a6d5 RC |
110 | |
111 | void | |
112 | init_run_script () | |
113 | { | |
5a85457b CF |
114 | static bool initialized; |
115 | if (initialized) | |
116 | return; | |
117 | ||
118 | initialized = true; | |
119 | ||
120 | char *env = GetEnvironmentStrings (); | |
121 | if (env) | |
bc78a6d5 | 122 | { |
5a85457b CF |
123 | for (char *p = env; *p; p = strchr (p, '\0') + 1) |
124 | { | |
125 | char *eq = strchr (p, '='); | |
126 | *eq = '\0'; | |
127 | if (strcasecmp (p, "comspec") != 0 | |
1473bfd3 | 128 | && strcasecmp (p, "cygwin") != 0 |
5a85457b | 129 | && strcasecmp (p, "path") != 0 |
4d1bc244 | 130 | && strcasecmp (p, "programdata") != 0 |
5a85457b CF |
131 | && strncasecmp (p, "system", 7) != 0 |
132 | && strncasecmp (p, "user", 4) != 0 | |
133 | && strcasecmp (p, "windir") != 0) | |
134 | SetEnvironmentVariable (p, NULL); | |
135 | p = eq + 1; | |
136 | } | |
137 | FreeEnvironmentStrings (env); | |
bc78a6d5 | 138 | } |
5a85457b | 139 | |
34b58a2e | 140 | modify_CYGWIN(); |
d2a3615c | 141 | SetEnvironmentVariable ("CYGWINROOT", get_root_dir ().c_str()); |
c3d07c13 AK |
142 | SetEnvironmentVariable ("CYGWINFORALL", |
143 | (root_scope == IDC_ROOT_SYSTEM) ? "-A" : NULL); | |
c8ea8d6c JT |
144 | |
145 | const char *sms = startmenusuffix(); | |
146 | if (strlen(sms) > 0) | |
147 | SetEnvironmentVariable ("CYGWIN_START_MENU_SUFFIX", sms); | |
148 | ||
7b640934 JT |
149 | if (NoStartMenuOption || NoShortcutsOption) |
150 | SetEnvironmentVariable ("CYGWIN_SETUP_OPTIONS", "no-startmenu"); | |
151 | ||
5a85457b CF |
152 | sanitize_PATH (); |
153 | SetEnvironmentVariable ("SHELL", "/bin/bash"); | |
154 | SetEnvironmentVariable ("TEMP", backslash (cygpath ("/tmp")).c_str ()); | |
155 | SetEnvironmentVariable ("TERM", "dumb"); | |
156 | SetEnvironmentVariable ("TMP", "/tmp"); | |
bc78a6d5 | 157 | |
390a9146 AG |
158 | sh = backslash (cygpath ("/bin/bash.exe")); |
159 | dash = backslash (cygpath ("/bin/dash.exe")); | |
160 | cmd = "cmd.exe"; | |
bc78a6d5 RC |
161 | } |
162 | ||
1069407c MB |
163 | class OutputLog |
164 | { | |
165 | public: | |
2bba98e8 | 166 | OutputLog (const std::string& filename); |
1069407c MB |
167 | ~OutputLog (); |
168 | HANDLE handle () { return _handle; } | |
169 | BOOL isValid () { return _handle != INVALID_HANDLE_VALUE; } | |
170 | BOOL isEmpty () { return GetFileSize (_handle, NULL) == 0; } | |
171 | friend std::ostream &operator<< (std::ostream &, OutputLog &); | |
172 | private: | |
173 | enum { BUFLEN = 1000 }; | |
174 | HANDLE _handle; | |
2bba98e8 | 175 | std::string _filename; |
1069407c MB |
176 | void out_to(std::ostream &); |
177 | }; | |
178 | ||
2bba98e8 | 179 | OutputLog::OutputLog (const std::string& filename) |
1069407c MB |
180 | : _handle(INVALID_HANDLE_VALUE), _filename(filename) |
181 | { | |
182 | if (!_filename.size()) | |
183 | return; | |
184 | ||
185 | SECURITY_ATTRIBUTES sa; | |
186 | memset (&sa, 0, sizeof (sa)); | |
187 | sa.nLength = sizeof (sa); | |
188 | sa.bInheritHandle = TRUE; | |
189 | sa.lpSecurityDescriptor = NULL; | |
190 | ||
b41c2908 | 191 | if (mkdir_p (0, backslash (cygpath (_filename)).c_str(), 0755)) |
1069407c MB |
192 | return; |
193 | ||
d2a3615c | 194 | _handle = CreateFile (backslash (cygpath (_filename)).c_str(), |
1069407c | 195 | GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, |
b41c2908 CV |
196 | &sa, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS, |
197 | NULL); | |
1069407c MB |
198 | |
199 | if (_handle == INVALID_HANDLE_VALUE) | |
200 | { | |
157dc2b8 | 201 | Log (LOG_PLAIN) << "error: Unable to redirect output to '" << _filename |
1069407c MB |
202 | << "'; using console" << endLog; |
203 | } | |
204 | } | |
205 | ||
206 | OutputLog::~OutputLog () | |
207 | { | |
208 | if (_handle != INVALID_HANDLE_VALUE) | |
209 | CloseHandle (_handle); | |
210 | if (_filename.size() && | |
d2a3615c | 211 | !DeleteFile(backslash (cygpath (_filename)).c_str())) |
1069407c | 212 | { |
157dc2b8 | 213 | Log (LOG_PLAIN) << "error: Unable to remove temporary file '" << _filename |
1069407c MB |
214 | << "'" << endLog; |
215 | } | |
216 | } | |
217 | ||
218 | std::ostream & | |
219 | operator<< (std::ostream &out, OutputLog &log) | |
220 | { | |
221 | log.out_to(out); | |
222 | return out; | |
223 | } | |
224 | ||
225 | void | |
226 | OutputLog::out_to(std::ostream &out) | |
227 | { | |
228 | char buf[BUFLEN]; | |
229 | DWORD num; | |
230 | FlushFileBuffers (_handle); | |
231 | SetFilePointer(_handle, 0, NULL, FILE_BEGIN); | |
54978a88 | 232 | |
1069407c MB |
233 | while (ReadFile(_handle, buf, BUFLEN-1, &num, NULL) && num != 0) |
234 | { | |
235 | buf[num] = '\0'; | |
236 | out << buf; | |
237 | } | |
238 | ||
239 | SetFilePointer(_handle, 0, NULL, FILE_END); | |
240 | } | |
241 | ||
f2952a6c JT |
242 | int |
243 | run (const char *cmdline) | |
bc78a6d5 | 244 | { |
f2952a6c | 245 | |
bc78a6d5 RC |
246 | STARTUPINFO si; |
247 | PROCESS_INFORMATION pi; | |
1069407c | 248 | DWORD flags = CREATE_NEW_CONSOLE; |
534c25cc | 249 | DWORD exitCode = 0; |
1069407c | 250 | BOOL inheritHandles = FALSE; |
534c25cc | 251 | BOOL exitCodeValid = FALSE; |
bc78a6d5 | 252 | |
157dc2b8 | 253 | Log (LOG_PLAIN) << "running: " << cmdline << endLog; |
f2952a6c JT |
254 | |
255 | char tmp_pat[] = "/var/log/setup.log.runXXXXXXX"; | |
256 | OutputLog file_out = std::string (mktemp (tmp_pat)); | |
257 | ||
bc78a6d5 RC |
258 | memset (&pi, 0, sizeof (pi)); |
259 | memset (&si, 0, sizeof (si)); | |
260 | si.cb = sizeof (si); | |
261 | si.lpTitle = (char *) "Cygwin Setup Post-Install Script"; | |
262 | si.dwFlags = STARTF_USEPOSITION; | |
263 | ||
1069407c MB |
264 | if (file_out.isValid ()) |
265 | { | |
266 | inheritHandles = TRUE; | |
267 | si.dwFlags |= STARTF_USESTDHANDLES; | |
5524909e | 268 | si.hStdInput = INVALID_HANDLE_VALUE; |
1069407c MB |
269 | si.hStdOutput = file_out.handle (); |
270 | si.hStdError = file_out.handle (); | |
271 | si.dwFlags |= STARTF_USESHOWWINDOW; | |
1069407c | 272 | si.wShowWindow = SW_HIDE; |
c6263b96 | 273 | flags = CREATE_NO_WINDOW; |
1069407c MB |
274 | } |
275 | ||
f2952a6c | 276 | BOOL createSucceeded = CreateProcess (0, (char *)cmdline, 0, 0, inheritHandles, |
d2a3615c | 277 | flags, 0, get_root_dir ().c_str(), |
1069407c | 278 | &si, &pi); |
bc78a6d5 | 279 | |
92f9402a | 280 | if (createSucceeded) |
534c25cc BD |
281 | { |
282 | WaitForSingleObject (pi.hProcess, INFINITE); | |
283 | exitCodeValid = GetExitCodeProcess(pi.hProcess, &exitCode); | |
284 | } | |
92f9402a RC |
285 | CloseHandle(pi.hProcess); |
286 | CloseHandle(pi.hThread); | |
f2952a6c JT |
287 | |
288 | if (!file_out.isEmpty ()) | |
157dc2b8 | 289 | Log (LOG_BABBLE) << file_out << endLog; |
f2952a6c | 290 | |
534c25cc BD |
291 | if (exitCodeValid) |
292 | return exitCode; | |
293 | return -GetLastError(); | |
bc78a6d5 RC |
294 | } |
295 | ||
534c25cc | 296 | int |
34ea5b6d | 297 | Script::run() const |
a0e56f67 | 298 | { |
54978a88 AG |
299 | if ("done" == scriptExtension) |
300 | return NO_ERROR; | |
301 | if (0 == scriptExtension.size()) | |
534c25cc | 302 | return -ERROR_INVALID_DATA; |
bc78a6d5 | 303 | |
a875b471 BD |
304 | /* Bail here if the script file does not exist. This can happen for |
305 | example in the case of tetex-* where two or more packages contain a | |
306 | postinstall script by the same name. When we are called the second | |
307 | time the file has already been renamed to .done, and if we don't | |
54978a88 | 308 | return here we end up erroneously deleting this .done file. */ |
a875b471 BD |
309 | std::string windowsName = backslash (cygpath (scriptName)); |
310 | if (_access (windowsName.c_str(), 0) == -1) | |
1069407c | 311 | { |
157dc2b8 | 312 | Log (LOG_PLAIN) << "can't run " << scriptName << ": No such file" |
a875b471 BD |
313 | << endLog; |
314 | return -ERROR_INVALID_DATA; | |
1069407c | 315 | } |
1069407c | 316 | |
a875b471 | 317 | int retval; |
7cc4d95e | 318 | char cmdline[CYG_PATH_MAX]; |
f2952a6c | 319 | |
54978a88 | 320 | if (sh.size() && ("sh" == scriptExtension)) |
bc78a6d5 | 321 | { |
f2952a6c JT |
322 | sprintf (cmdline, "%s %s \"%s\"", sh.c_str(), "--norc --noprofile", scriptName.c_str()); |
323 | retval = ::run (cmdline); | |
bc78a6d5 | 324 | } |
390a9146 AG |
325 | else if (dash.size() && ("dash" == scriptExtension)) |
326 | { | |
327 | sprintf (cmdline, "%s \"%s\"", dash.c_str(), scriptName.c_str()); | |
328 | retval = ::run (cmdline); | |
329 | } | |
330 | else if (cmd && (("bat" == scriptExtension) || | |
331 | ("cmd" == scriptExtension))) | |
bc78a6d5 | 332 | { |
f2952a6c JT |
333 | sprintf (cmdline, "%s %s \"%s\"", cmd, "/c", windowsName.c_str()); |
334 | retval = ::run (cmdline); | |
bc78a6d5 RC |
335 | } |
336 | else | |
534c25cc | 337 | return -ERROR_INVALID_DATA; |
bc78a6d5 | 338 | |
b2b35a6e | 339 | if (retval) |
157dc2b8 | 340 | Log (LOG_PLAIN) << "abnormal exit: exit code=" << retval << endLog; |
534c25cc | 341 | |
0707786e | 342 | /* if .done file exists then delete it otherwise just ignore no file error */ |
2bba98e8 | 343 | io_stream::remove ("cygfile://" + scriptName + ".done"); |
bc78a6d5 | 344 | |
54978a88 AG |
345 | /* don't rename the script as .done if it didn't run successfully or |
346 | if this script is marked to be always run */ | |
347 | if (!retval && ("p" != scriptType)) | |
0707786e JT |
348 | io_stream::move ("cygfile://" + scriptName, |
349 | "cygfile://" + scriptName + ".done"); | |
534c25cc BD |
350 | |
351 | return retval; | |
bc78a6d5 RC |
352 | } |
353 | ||
534c25cc | 354 | int |
2bba98e8 MB |
355 | try_run_script (const std::string& dir, |
356 | const std::string& fname, | |
357 | const std::string& ext) | |
bc78a6d5 | 358 | { |
2bba98e8 | 359 | if (io_stream::exists ("cygfile://" + dir + fname + ext)) |
534c25cc BD |
360 | return Script (dir + fname + ext).run (); |
361 | return NO_ERROR; | |
bc78a6d5 RC |
362 | } |
363 | ||
ad646f43 | 364 | bool |
2bba98e8 | 365 | Script::isAScript (const std::string& file) |
ad646f43 | 366 | { |
54978a88 AG |
367 | // is a directory path |
368 | if ('/' == file[file.size()-1]) | |
ad646f43 | 369 | return false; |
54978a88 AG |
370 | // file may start with /etc/postinstall/ or etc/postinstall/ |
371 | std::size_t found = file.find(ETCPostinstall+1); | |
372 | if (( found == 0) || | |
373 | ((found == 1) && (0 == file.find('/')))) | |
374 | return true; | |
375 | return false; | |
ad646f43 RC |
376 | } |
377 | ||
54978a88 AG |
378 | bool |
379 | Script::match (const std::string& stratum, const std::string& type) | |
ad646f43 | 380 | { |
54978a88 AG |
381 | // empty string for each parameter always matches |
382 | bool matchedStratum, matchedType; | |
383 | if ("done" == scriptExtension) | |
384 | return false; | |
385 | if (stratum.size()) | |
386 | matchedStratum = (std::string::npos != stratum.find(scriptStratum)); | |
387 | else | |
388 | matchedStratum = true; | |
389 | if (type.size()) | |
390 | matchedType = (std::string::npos != type.find(scriptType)); | |
391 | else | |
392 | matchedType = true; | |
393 | return matchedStratum && matchedType; | |
394 | } | |
395 | bool | |
396 | Script::is_p (const std::string& stratum) | |
397 | { | |
398 | return match( stratum, "p"); | |
399 | } | |
400 | bool | |
401 | Script::not_p (const std::string& stratum) | |
402 | { | |
403 | return match( stratum, allowedTypes+1); | |
404 | } | |
405 | ||
406 | Script::Script (const std::string& fileName) | |
407 | : scriptName (fileName), | |
408 | scriptBaseName (""), | |
409 | scriptExtension (""), | |
410 | scriptStratum ("_"), | |
411 | scriptType ("r") | |
412 | { | |
413 | std::size_t found; | |
414 | found = fileName.rfind('/'); | |
415 | if (found != std::string::npos) | |
416 | scriptBaseName = fileName.substr(found + 1); | |
417 | found = fileName.rfind('.'); | |
418 | if (found != std::string::npos) | |
419 | scriptExtension = fileName.substr(found + 1); | |
420 | if ( "_" == scriptBaseName.substr(2,1) && | |
421 | 0 == scriptBaseName.substr(1,1).find_first_of(allowedTypes) && | |
422 | "_" != scriptBaseName.substr(0,1) && // default stratum cannot be explicitly named | |
423 | 0 == scriptBaseName.substr(0,1).find_first_of(allowedStrata)) | |
424 | { | |
425 | // stratified script | |
426 | scriptStratum = scriptBaseName.substr(0,1); | |
427 | scriptType = scriptBaseName.substr(1,1); | |
428 | } | |
429 | // Let's hope people won't uninstall packages before installing bash | |
430 | init_run_script (); | |
ad646f43 RC |
431 | } |
432 | ||
470f4928 | 433 | std::string |
39ba3555 | 434 | Script::baseName() const |
ad646f43 | 435 | { |
54978a88 | 436 | return scriptBaseName; |
ad646f43 | 437 | } |
39ba3555 | 438 | |
2bba98e8 | 439 | std::string |
39ba3555 MB |
440 | Script::fullName() const |
441 | { | |
442 | return scriptName; | |
443 | } | |
54978a88 AG |
444 | |
445 | char const Script::ETCPostinstall[] = "/etc/postinstall/"; | |
446 | char const Script::allowedStrata[] = "0_z"; | |
447 | char const Script::allowedTypes[] = "pr"; |