2 * Copyright (c) 2001, Jan Nieuwenhuizen.
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.
9 * A copy of the GNU General Public License can be found at
12 * Written by DJ Delorie <dj@cygnus.com>
13 * Jan Nieuwenhuizen <janneke@gnu.org>
17 /* The purpose of this file is to provide functions for the invocation
18 of install scripts. */
24 #include "LogSingleton.h"
25 #include "filemanip.h"
27 #include "io_stream.h"
36 #define alloca __builtin_alloca
41 static std::string sh
, dash
;
42 static const char *cmd
;
48 DWORD len
= GetEnvironmentVariable ("PATH", &dummy
, 0);
49 char *path
= (char *) alloca (len
+ 1);
50 GetEnvironmentVariable ("PATH", path
, len
);
51 std::string newpath
= backslash (cygpath ("/bin") + ";"
52 + cygpath ("/usr/sbin") + ";"
54 len
= (UINT
) GetSystemWindowsDirectory (&dummy
, 0);
55 char *system_root
= (char *) alloca (len
+ 2);
56 GetSystemWindowsDirectory (system_root
, len
--);
57 if (system_root
[len
- 1] != '\\')
59 system_root
[len
] = '\\';
60 system_root
[++len
] = '\0';
62 for (char *p
= strtok (path
, ";"); p
; p
= strtok (NULL
, ";"))
64 size_t plen
= strlen (p
);
65 size_t cmplen
= plen
== (len
- 1) ? plen
: len
;
66 if (strncasecmp (system_root
, p
, cmplen
) == 0)
72 SetEnvironmentVariable ("PATH", newpath
.c_str());
79 DWORD len
= GetEnvironmentVariable ("CYGWIN", &cygwin
[0], 0);
83 GetEnvironmentVariable ("CYGWIN", &cygwin
[0], len
);
84 cygwin
.resize(len
-1); // trim terminating null
90 case SymlinkTypeNative
:
91 cygwin
.append("winsymlinks:native");
95 cygwin
.append("winsymlinks:wsl");
98 case SymlinkTypeMagic
:
99 cygwin
.append("winsymlinks:sys");
102 case SymlinkTypeShortcut
: /* not yet implemented */
107 SetEnvironmentVariable ("CYGWIN", cygwin
.c_str());
113 static bool initialized
;
119 char *env
= GetEnvironmentStrings ();
122 for (char *p
= env
; *p
; p
= strchr (p
, '\0') + 1)
124 char *eq
= strchr (p
, '=');
126 if (strcasecmp (p
, "comspec") != 0
127 && strcasecmp (p
, "cygwin") != 0
128 && strcasecmp (p
, "path") != 0
129 && strncasecmp (p
, "system", 7) != 0
130 && strncasecmp (p
, "user", 4) != 0
131 && strcasecmp (p
, "windir") != 0)
132 SetEnvironmentVariable (p
, NULL
);
135 FreeEnvironmentStrings (env
);
139 SetEnvironmentVariable ("CYGWINROOT", get_root_dir ().c_str());
140 SetEnvironmentVariable ("CYGWINFORALL",
141 (root_scope
== IDC_ROOT_SYSTEM
) ? "-A" : NULL
);
143 SetEnvironmentVariable ("SHELL", "/bin/bash");
144 SetEnvironmentVariable ("TEMP", backslash (cygpath ("/tmp")).c_str ());
145 SetEnvironmentVariable ("TERM", "dumb");
146 SetEnvironmentVariable ("TMP", "/tmp");
148 sh
= backslash (cygpath ("/bin/bash.exe"));
149 dash
= backslash (cygpath ("/bin/dash.exe"));
156 OutputLog (const std::string
& filename
);
158 HANDLE
handle () { return _handle
; }
159 BOOL
isValid () { return _handle
!= INVALID_HANDLE_VALUE
; }
160 BOOL
isEmpty () { return GetFileSize (_handle
, NULL
) == 0; }
161 friend std::ostream
&operator<< (std::ostream
&, OutputLog
&);
163 enum { BUFLEN
= 1000 };
165 std::string _filename
;
166 void out_to(std::ostream
&);
169 OutputLog::OutputLog (const std::string
& filename
)
170 : _handle(INVALID_HANDLE_VALUE
), _filename(filename
)
172 if (!_filename
.size())
175 SECURITY_ATTRIBUTES sa
;
176 memset (&sa
, 0, sizeof (sa
));
177 sa
.nLength
= sizeof (sa
);
178 sa
.bInheritHandle
= TRUE
;
179 sa
.lpSecurityDescriptor
= NULL
;
181 if (mkdir_p (0, backslash (cygpath (_filename
)).c_str(), 0755))
184 _handle
= CreateFile (backslash (cygpath (_filename
)).c_str(),
185 GENERIC_READ
|GENERIC_WRITE
, FILE_SHARE_READ
|FILE_SHARE_WRITE
,
186 &sa
, CREATE_ALWAYS
, FILE_ATTRIBUTE_NORMAL
| FILE_FLAG_BACKUP_SEMANTICS
,
189 if (_handle
== INVALID_HANDLE_VALUE
)
191 Log (LOG_PLAIN
) << "error: Unable to redirect output to '" << _filename
192 << "'; using console" << endLog
;
196 OutputLog::~OutputLog ()
198 if (_handle
!= INVALID_HANDLE_VALUE
)
199 CloseHandle (_handle
);
200 if (_filename
.size() &&
201 !DeleteFile(backslash (cygpath (_filename
)).c_str()))
203 Log (LOG_PLAIN
) << "error: Unable to remove temporary file '" << _filename
209 operator<< (std::ostream
&out
, OutputLog
&log
)
216 OutputLog::out_to(std::ostream
&out
)
220 FlushFileBuffers (_handle
);
221 SetFilePointer(_handle
, 0, NULL
, FILE_BEGIN
);
223 while (ReadFile(_handle
, buf
, BUFLEN
-1, &num
, NULL
) && num
!= 0)
229 SetFilePointer(_handle
, 0, NULL
, FILE_END
);
233 run (const char *cmdline
)
237 PROCESS_INFORMATION pi
;
238 DWORD flags
= CREATE_NEW_CONSOLE
;
240 BOOL inheritHandles
= FALSE
;
241 BOOL exitCodeValid
= FALSE
;
243 Log (LOG_PLAIN
) << "running: " << cmdline
<< endLog
;
245 char tmp_pat
[] = "/var/log/setup.log.runXXXXXXX";
246 OutputLog file_out
= std::string (mktemp (tmp_pat
));
248 memset (&pi
, 0, sizeof (pi
));
249 memset (&si
, 0, sizeof (si
));
251 si
.lpTitle
= (char *) "Cygwin Setup Post-Install Script";
252 si
.dwFlags
= STARTF_USEPOSITION
;
254 if (file_out
.isValid ())
256 inheritHandles
= TRUE
;
257 si
.dwFlags
|= STARTF_USESTDHANDLES
;
258 si
.hStdInput
= INVALID_HANDLE_VALUE
;
259 si
.hStdOutput
= file_out
.handle ();
260 si
.hStdError
= file_out
.handle ();
261 si
.dwFlags
|= STARTF_USESHOWWINDOW
;
262 si
.wShowWindow
= SW_HIDE
;
263 flags
= CREATE_NO_WINDOW
;
266 BOOL createSucceeded
= CreateProcess (0, (char *)cmdline
, 0, 0, inheritHandles
,
267 flags
, 0, get_root_dir ().c_str(),
272 WaitForSingleObject (pi
.hProcess
, INFINITE
);
273 exitCodeValid
= GetExitCodeProcess(pi
.hProcess
, &exitCode
);
275 CloseHandle(pi
.hProcess
);
276 CloseHandle(pi
.hThread
);
278 if (!file_out
.isEmpty ())
279 Log (LOG_BABBLE
) << file_out
<< endLog
;
283 return -GetLastError();
289 if ("done" == scriptExtension
)
291 if (0 == scriptExtension
.size())
292 return -ERROR_INVALID_DATA
;
294 /* Bail here if the script file does not exist. This can happen for
295 example in the case of tetex-* where two or more packages contain a
296 postinstall script by the same name. When we are called the second
297 time the file has already been renamed to .done, and if we don't
298 return here we end up erroneously deleting this .done file. */
299 std::string windowsName
= backslash (cygpath (scriptName
));
300 if (_access (windowsName
.c_str(), 0) == -1)
302 Log (LOG_PLAIN
) << "can't run " << scriptName
<< ": No such file"
304 return -ERROR_INVALID_DATA
;
308 char cmdline
[CYG_PATH_MAX
];
310 if (sh
.size() && ("sh" == scriptExtension
))
312 sprintf (cmdline
, "%s %s \"%s\"", sh
.c_str(), "--norc --noprofile", scriptName
.c_str());
313 retval
= ::run (cmdline
);
315 else if (dash
.size() && ("dash" == scriptExtension
))
317 sprintf (cmdline
, "%s \"%s\"", dash
.c_str(), scriptName
.c_str());
318 retval
= ::run (cmdline
);
320 else if (cmd
&& (("bat" == scriptExtension
) ||
321 ("cmd" == scriptExtension
)))
323 sprintf (cmdline
, "%s %s \"%s\"", cmd
, "/c", windowsName
.c_str());
324 retval
= ::run (cmdline
);
327 return -ERROR_INVALID_DATA
;
330 Log (LOG_PLAIN
) << "abnormal exit: exit code=" << retval
<< endLog
;
332 /* if .done file exists then delete it otherwise just ignore no file error */
333 io_stream::remove ("cygfile://" + scriptName
+ ".done");
335 /* don't rename the script as .done if it didn't run successfully or
336 if this script is marked to be always run */
337 if (!retval
&& ("p" != scriptType
))
338 io_stream::move ("cygfile://" + scriptName
,
339 "cygfile://" + scriptName
+ ".done");
345 try_run_script (const std::string
& dir
,
346 const std::string
& fname
,
347 const std::string
& ext
)
349 if (io_stream::exists ("cygfile://" + dir
+ fname
+ ext
))
350 return Script (dir
+ fname
+ ext
).run ();
355 Script::isAScript (const std::string
& file
)
357 // is a directory path
358 if ('/' == file
[file
.size()-1])
360 // file may start with /etc/postinstall/ or etc/postinstall/
361 std::size_t found
= file
.find(ETCPostinstall
+1);
363 ((found
== 1) && (0 == file
.find('/'))))
369 Script::match (const std::string
& stratum
, const std::string
& type
)
371 // empty string for each parameter always matches
372 bool matchedStratum
, matchedType
;
373 if ("done" == scriptExtension
)
376 matchedStratum
= (std::string::npos
!= stratum
.find(scriptStratum
));
378 matchedStratum
= true;
380 matchedType
= (std::string::npos
!= type
.find(scriptType
));
383 return matchedStratum
&& matchedType
;
386 Script::is_p (const std::string
& stratum
)
388 return match( stratum
, "p");
391 Script::not_p (const std::string
& stratum
)
393 return match( stratum
, allowedTypes
+1);
396 Script::Script (const std::string
& fileName
)
397 : scriptName (fileName
),
399 scriptExtension (""),
404 found
= fileName
.rfind('/');
405 if (found
!= std::string::npos
)
406 scriptBaseName
= fileName
.substr(found
+ 1);
407 found
= fileName
.rfind('.');
408 if (found
!= std::string::npos
)
409 scriptExtension
= fileName
.substr(found
+ 1);
410 if ( "_" == scriptBaseName
.substr(2,1) &&
411 0 == scriptBaseName
.substr(1,1).find_first_of(allowedTypes
) &&
412 "_" != scriptBaseName
.substr(0,1) && // default stratum cannot be explicitly named
413 0 == scriptBaseName
.substr(0,1).find_first_of(allowedStrata
))
416 scriptStratum
= scriptBaseName
.substr(0,1);
417 scriptType
= scriptBaseName
.substr(1,1);
419 // Let's hope people won't uninstall packages before installing bash
424 Script::baseName() const
426 return scriptBaseName
;
430 Script::fullName() const
435 char const Script::ETCPostinstall
[] = "/etc/postinstall/";
436 char const Script::allowedStrata
[] = "0_z";
437 char const Script::allowedTypes
[] = "pr";