]>
Commit | Line | Data |
---|---|---|
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 | ||
20 | #include "win32.h" | |
21 | #include <stdlib.h> | |
22 | #include <unistd.h> | |
23 | #include <stdio.h> | |
24 | #include "LogSingleton.h" | |
25 | #include "filemanip.h" | |
26 | #include "mount.h" | |
27 | #include "io_stream.h" | |
28 | #include "script.h" | |
29 | #include "mkdir.h" | |
30 | #include "state.h" | |
31 | #include "resource.h" | |
32 | #if HAVE_ALLOCA_H | |
33 | #include <alloca.h> | |
34 | #else | |
35 | #ifndef alloca | |
36 | #define alloca __builtin_alloca | |
37 | #endif | |
38 | #endif | |
39 | #include "mklink2.h" | |
40 | #include "desktop.h" | |
41 | ||
42 | static std::string sh, dash; | |
43 | static const char *cmd; | |
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")); | |
55 | len = (UINT) GetSystemWindowsDirectory (&dummy, 0); | |
56 | char *system_root = (char *) alloca (len + 2); | |
57 | GetSystemWindowsDirectory (system_root, len--); | |
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 | ||
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 | } | |
110 | ||
111 | void | |
112 | init_run_script () | |
113 | { | |
114 | static bool initialized; | |
115 | if (initialized) | |
116 | return; | |
117 | ||
118 | initialized = true; | |
119 | ||
120 | char *env = GetEnvironmentStrings (); | |
121 | if (env) | |
122 | { | |
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 | |
128 | && strcasecmp (p, "cygwin") != 0 | |
129 | && strcasecmp (p, "path") != 0 | |
130 | && strcasecmp (p, "programdata") != 0 | |
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); | |
138 | } | |
139 | ||
140 | modify_CYGWIN(); | |
141 | SetEnvironmentVariable ("CYGWINROOT", get_root_dir ().c_str()); | |
142 | SetEnvironmentVariable ("CYGWINFORALL", | |
143 | (root_scope == IDC_ROOT_SYSTEM) ? "-A" : NULL); | |
144 | ||
145 | const char *sms = startmenusuffix(); | |
146 | if (strlen(sms) > 0) | |
147 | SetEnvironmentVariable ("CYGWIN_START_MENU_SUFFIX", sms); | |
148 | ||
149 | if (NoStartMenuOption || NoShortcutsOption) | |
150 | SetEnvironmentVariable ("CYGWIN_SETUP_OPTIONS", "no-startmenu"); | |
151 | ||
152 | sanitize_PATH (); | |
153 | SetEnvironmentVariable ("SHELL", "/bin/bash"); | |
154 | SetEnvironmentVariable ("TEMP", backslash (cygpath ("/tmp")).c_str ()); | |
155 | SetEnvironmentVariable ("TERM", "dumb"); | |
156 | SetEnvironmentVariable ("TMP", "/tmp"); | |
157 | ||
158 | sh = backslash (cygpath ("/bin/bash.exe")); | |
159 | dash = backslash (cygpath ("/bin/dash.exe")); | |
160 | cmd = "cmd.exe"; | |
161 | } | |
162 | ||
163 | class OutputLog | |
164 | { | |
165 | public: | |
166 | OutputLog (const std::string& filename); | |
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; | |
175 | std::string _filename; | |
176 | void out_to(std::ostream &); | |
177 | }; | |
178 | ||
179 | OutputLog::OutputLog (const std::string& filename) | |
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 | ||
191 | if (mkdir_p (0, backslash (cygpath (_filename)).c_str(), 0755)) | |
192 | return; | |
193 | ||
194 | _handle = CreateFile (backslash (cygpath (_filename)).c_str(), | |
195 | GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, | |
196 | &sa, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS, | |
197 | NULL); | |
198 | ||
199 | if (_handle == INVALID_HANDLE_VALUE) | |
200 | { | |
201 | Log (LOG_PLAIN) << "error: Unable to redirect output to '" << _filename | |
202 | << "'; using console" << endLog; | |
203 | } | |
204 | } | |
205 | ||
206 | OutputLog::~OutputLog () | |
207 | { | |
208 | if (_handle != INVALID_HANDLE_VALUE) | |
209 | CloseHandle (_handle); | |
210 | if (_filename.size() && | |
211 | !DeleteFile(backslash (cygpath (_filename)).c_str())) | |
212 | { | |
213 | Log (LOG_PLAIN) << "error: Unable to remove temporary file '" << _filename | |
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); | |
232 | ||
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 | ||
242 | int | |
243 | run (const char *cmdline) | |
244 | { | |
245 | ||
246 | STARTUPINFO si; | |
247 | PROCESS_INFORMATION pi; | |
248 | DWORD flags = CREATE_NEW_CONSOLE; | |
249 | DWORD exitCode = 0; | |
250 | BOOL inheritHandles = FALSE; | |
251 | BOOL exitCodeValid = FALSE; | |
252 | ||
253 | Log (LOG_PLAIN) << "running: " << cmdline << endLog; | |
254 | ||
255 | char tmp_pat[] = "/var/log/setup.log.runXXXXXXX"; | |
256 | OutputLog file_out = std::string (mktemp (tmp_pat)); | |
257 | ||
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 | ||
264 | if (file_out.isValid ()) | |
265 | { | |
266 | inheritHandles = TRUE; | |
267 | si.dwFlags |= STARTF_USESTDHANDLES; | |
268 | si.hStdInput = INVALID_HANDLE_VALUE; | |
269 | si.hStdOutput = file_out.handle (); | |
270 | si.hStdError = file_out.handle (); | |
271 | si.dwFlags |= STARTF_USESHOWWINDOW; | |
272 | si.wShowWindow = SW_HIDE; | |
273 | flags = CREATE_NO_WINDOW; | |
274 | } | |
275 | ||
276 | BOOL createSucceeded = CreateProcess (0, (char *)cmdline, 0, 0, inheritHandles, | |
277 | flags, 0, get_root_dir ().c_str(), | |
278 | &si, &pi); | |
279 | ||
280 | if (createSucceeded) | |
281 | { | |
282 | WaitForSingleObject (pi.hProcess, INFINITE); | |
283 | exitCodeValid = GetExitCodeProcess(pi.hProcess, &exitCode); | |
284 | } | |
285 | CloseHandle(pi.hProcess); | |
286 | CloseHandle(pi.hThread); | |
287 | ||
288 | if (!file_out.isEmpty ()) | |
289 | Log (LOG_BABBLE) << file_out << endLog; | |
290 | ||
291 | if (exitCodeValid) | |
292 | return exitCode; | |
293 | return -GetLastError(); | |
294 | } | |
295 | ||
296 | int | |
297 | Script::run() const | |
298 | { | |
299 | if ("done" == scriptExtension) | |
300 | return NO_ERROR; | |
301 | if (0 == scriptExtension.size()) | |
302 | return -ERROR_INVALID_DATA; | |
303 | ||
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 | |
308 | return here we end up erroneously deleting this .done file. */ | |
309 | std::string windowsName = backslash (cygpath (scriptName)); | |
310 | if (_access (windowsName.c_str(), 0) == -1) | |
311 | { | |
312 | Log (LOG_PLAIN) << "can't run " << scriptName << ": No such file" | |
313 | << endLog; | |
314 | return -ERROR_INVALID_DATA; | |
315 | } | |
316 | ||
317 | int retval; | |
318 | char cmdline[CYG_PATH_MAX]; | |
319 | ||
320 | if (sh.size() && ("sh" == scriptExtension)) | |
321 | { | |
322 | sprintf (cmdline, "%s %s \"%s\"", sh.c_str(), "--norc --noprofile", scriptName.c_str()); | |
323 | retval = ::run (cmdline); | |
324 | } | |
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))) | |
332 | { | |
333 | sprintf (cmdline, "%s %s \"%s\"", cmd, "/c", windowsName.c_str()); | |
334 | retval = ::run (cmdline); | |
335 | } | |
336 | else | |
337 | return -ERROR_INVALID_DATA; | |
338 | ||
339 | if (retval) | |
340 | Log (LOG_PLAIN) << "abnormal exit: exit code=" << retval << endLog; | |
341 | ||
342 | /* if .done file exists then delete it otherwise just ignore no file error */ | |
343 | io_stream::remove ("cygfile://" + scriptName + ".done"); | |
344 | ||
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)) | |
348 | io_stream::move ("cygfile://" + scriptName, | |
349 | "cygfile://" + scriptName + ".done"); | |
350 | ||
351 | return retval; | |
352 | } | |
353 | ||
354 | int | |
355 | try_run_script (const std::string& dir, | |
356 | const std::string& fname, | |
357 | const std::string& ext) | |
358 | { | |
359 | if (io_stream::exists ("cygfile://" + dir + fname + ext)) | |
360 | return Script (dir + fname + ext).run (); | |
361 | return NO_ERROR; | |
362 | } | |
363 | ||
364 | bool | |
365 | Script::isAScript (const std::string& file) | |
366 | { | |
367 | // is a directory path | |
368 | if ('/' == file[file.size()-1]) | |
369 | return false; | |
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; | |
376 | } | |
377 | ||
378 | bool | |
379 | Script::match (const std::string& stratum, const std::string& type) | |
380 | { | |
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 (); | |
431 | } | |
432 | ||
433 | std::string | |
434 | Script::baseName() const | |
435 | { | |
436 | return scriptBaseName; | |
437 | } | |
438 | ||
439 | std::string | |
440 | Script::fullName() const | |
441 | { | |
442 | return scriptName; | |
443 | } | |
444 | ||
445 | char const Script::ETCPostinstall[] = "/etc/postinstall/"; | |
446 | char const Script::allowedStrata[] = "0_z"; | |
447 | char const Script::allowedTypes[] = "pr"; |