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
40 static std::string sh
, dash
;
41 static const char *cmd
;
47 DWORD len
= GetEnvironmentVariable ("PATH", &dummy
, 0);
48 char *path
= (char *) alloca (len
+ 1);
49 GetEnvironmentVariable ("PATH", path
, len
);
50 std::string newpath
= backslash (cygpath ("/bin") + ";"
51 + cygpath ("/usr/sbin") + ";"
53 len
= (UINT
) GetSystemWindowsDirectory (&dummy
, 0);
54 char *system_root
= (char *) alloca (len
+ 2);
55 GetSystemWindowsDirectory (system_root
, len
--);
56 if (system_root
[len
- 1] != '\\')
58 system_root
[len
] = '\\';
59 system_root
[++len
] = '\0';
61 for (char *p
= strtok (path
, ";"); p
; p
= strtok (NULL
, ";"))
63 size_t plen
= strlen (p
);
64 size_t cmplen
= plen
== (len
- 1) ? plen
: len
;
65 if (strncasecmp (system_root
, p
, cmplen
) == 0)
71 SetEnvironmentVariable ("PATH", newpath
.c_str());
78 static bool initialized
;
84 char *env
= GetEnvironmentStrings ();
87 for (char *p
= env
; *p
; p
= strchr (p
, '\0') + 1)
89 char *eq
= strchr (p
, '=');
91 if (strcasecmp (p
, "comspec") != 0
92 && strcasecmp (p
, "path") != 0
93 && strncasecmp (p
, "system", 7) != 0
94 && strncasecmp (p
, "user", 4) != 0
95 && strcasecmp (p
, "windir") != 0)
96 SetEnvironmentVariable (p
, NULL
);
99 FreeEnvironmentStrings (env
);
102 SetEnvironmentVariable ("CYGWINROOT", get_root_dir ().c_str());
103 SetEnvironmentVariable ("CYGWINFORALL",
104 (root_scope
== IDC_ROOT_SYSTEM
) ? "-A" : NULL
);
106 SetEnvironmentVariable ("SHELL", "/bin/bash");
107 SetEnvironmentVariable ("TEMP", backslash (cygpath ("/tmp")).c_str ());
108 SetEnvironmentVariable ("TERM", "dumb");
109 SetEnvironmentVariable ("TMP", "/tmp");
111 sh
= backslash (cygpath ("/bin/bash.exe"));
112 dash
= backslash (cygpath ("/bin/dash.exe"));
119 OutputLog (const std::string
& filename
);
121 HANDLE
handle () { return _handle
; }
122 BOOL
isValid () { return _handle
!= INVALID_HANDLE_VALUE
; }
123 BOOL
isEmpty () { return GetFileSize (_handle
, NULL
) == 0; }
124 friend std::ostream
&operator<< (std::ostream
&, OutputLog
&);
126 enum { BUFLEN
= 1000 };
128 std::string _filename
;
129 void out_to(std::ostream
&);
132 OutputLog::OutputLog (const std::string
& filename
)
133 : _handle(INVALID_HANDLE_VALUE
), _filename(filename
)
135 if (!_filename
.size())
138 SECURITY_ATTRIBUTES sa
;
139 memset (&sa
, 0, sizeof (sa
));
140 sa
.nLength
= sizeof (sa
);
141 sa
.bInheritHandle
= TRUE
;
142 sa
.lpSecurityDescriptor
= NULL
;
144 if (mkdir_p (0, backslash (cygpath (_filename
)).c_str(), 0755))
147 _handle
= CreateFile (backslash (cygpath (_filename
)).c_str(),
148 GENERIC_READ
|GENERIC_WRITE
, FILE_SHARE_READ
|FILE_SHARE_WRITE
,
149 &sa
, CREATE_ALWAYS
, FILE_ATTRIBUTE_NORMAL
| FILE_FLAG_BACKUP_SEMANTICS
,
152 if (_handle
== INVALID_HANDLE_VALUE
)
154 Log (LOG_PLAIN
) << "error: Unable to redirect output to '" << _filename
155 << "'; using console" << endLog
;
159 OutputLog::~OutputLog ()
161 if (_handle
!= INVALID_HANDLE_VALUE
)
162 CloseHandle (_handle
);
163 if (_filename
.size() &&
164 !DeleteFile(backslash (cygpath (_filename
)).c_str()))
166 Log (LOG_PLAIN
) << "error: Unable to remove temporary file '" << _filename
172 operator<< (std::ostream
&out
, OutputLog
&log
)
179 OutputLog::out_to(std::ostream
&out
)
183 FlushFileBuffers (_handle
);
184 SetFilePointer(_handle
, 0, NULL
, FILE_BEGIN
);
186 while (ReadFile(_handle
, buf
, BUFLEN
-1, &num
, NULL
) && num
!= 0)
192 SetFilePointer(_handle
, 0, NULL
, FILE_END
);
196 run (const char *cmdline
)
200 PROCESS_INFORMATION pi
;
201 DWORD flags
= CREATE_NEW_CONSOLE
;
203 BOOL inheritHandles
= FALSE
;
204 BOOL exitCodeValid
= FALSE
;
206 Log (LOG_PLAIN
) << "running: " << cmdline
<< endLog
;
208 char tmp_pat
[] = "/var/log/setup.log.runXXXXXXX";
209 OutputLog file_out
= std::string (mktemp (tmp_pat
));
211 memset (&pi
, 0, sizeof (pi
));
212 memset (&si
, 0, sizeof (si
));
214 si
.lpTitle
= (char *) "Cygwin Setup Post-Install Script";
215 si
.dwFlags
= STARTF_USEPOSITION
;
217 if (file_out
.isValid ())
219 inheritHandles
= TRUE
;
220 si
.dwFlags
|= STARTF_USESTDHANDLES
;
221 si
.hStdInput
= INVALID_HANDLE_VALUE
;
222 si
.hStdOutput
= file_out
.handle ();
223 si
.hStdError
= file_out
.handle ();
224 si
.dwFlags
|= STARTF_USESHOWWINDOW
;
225 si
.wShowWindow
= SW_HIDE
;
226 flags
= CREATE_NO_WINDOW
;
229 BOOL createSucceeded
= CreateProcess (0, (char *)cmdline
, 0, 0, inheritHandles
,
230 flags
, 0, get_root_dir ().c_str(),
235 WaitForSingleObject (pi
.hProcess
, INFINITE
);
236 exitCodeValid
= GetExitCodeProcess(pi
.hProcess
, &exitCode
);
238 CloseHandle(pi
.hProcess
);
239 CloseHandle(pi
.hThread
);
241 if (!file_out
.isEmpty ())
242 Log (LOG_BABBLE
) << file_out
<< endLog
;
246 return -GetLastError();
252 if ("done" == scriptExtension
)
254 if (0 == scriptExtension
.size())
255 return -ERROR_INVALID_DATA
;
257 /* Bail here if the script file does not exist. This can happen for
258 example in the case of tetex-* where two or more packages contain a
259 postinstall script by the same name. When we are called the second
260 time the file has already been renamed to .done, and if we don't
261 return here we end up erroneously deleting this .done file. */
262 std::string windowsName
= backslash (cygpath (scriptName
));
263 if (_access (windowsName
.c_str(), 0) == -1)
265 Log (LOG_PLAIN
) << "can't run " << scriptName
<< ": No such file"
267 return -ERROR_INVALID_DATA
;
271 char cmdline
[CYG_PATH_MAX
];
273 if (sh
.size() && ("sh" == scriptExtension
))
275 sprintf (cmdline
, "%s %s \"%s\"", sh
.c_str(), "--norc --noprofile", scriptName
.c_str());
276 retval
= ::run (cmdline
);
278 else if (dash
.size() && ("dash" == scriptExtension
))
280 sprintf (cmdline
, "%s \"%s\"", dash
.c_str(), scriptName
.c_str());
281 retval
= ::run (cmdline
);
283 else if (cmd
&& (("bat" == scriptExtension
) ||
284 ("cmd" == scriptExtension
)))
286 sprintf (cmdline
, "%s %s \"%s\"", cmd
, "/c", windowsName
.c_str());
287 retval
= ::run (cmdline
);
290 return -ERROR_INVALID_DATA
;
293 Log (LOG_PLAIN
) << "abnormal exit: exit code=" << retval
<< endLog
;
295 /* if .done file exists then delete it otherwise just ignore no file error */
296 io_stream::remove ("cygfile://" + scriptName
+ ".done");
298 /* don't rename the script as .done if it didn't run successfully or
299 if this script is marked to be always run */
300 if (!retval
&& ("p" != scriptType
))
301 io_stream::move ("cygfile://" + scriptName
,
302 "cygfile://" + scriptName
+ ".done");
308 try_run_script (const std::string
& dir
,
309 const std::string
& fname
,
310 const std::string
& ext
)
312 if (io_stream::exists ("cygfile://" + dir
+ fname
+ ext
))
313 return Script (dir
+ fname
+ ext
).run ();
318 Script::isAScript (const std::string
& file
)
320 // is a directory path
321 if ('/' == file
[file
.size()-1])
323 // file may start with /etc/postinstall/ or etc/postinstall/
324 std::size_t found
= file
.find(ETCPostinstall
+1);
326 ((found
== 1) && (0 == file
.find('/'))))
332 Script::match (const std::string
& stratum
, const std::string
& type
)
334 // empty string for each parameter always matches
335 bool matchedStratum
, matchedType
;
336 if ("done" == scriptExtension
)
339 matchedStratum
= (std::string::npos
!= stratum
.find(scriptStratum
));
341 matchedStratum
= true;
343 matchedType
= (std::string::npos
!= type
.find(scriptType
));
346 return matchedStratum
&& matchedType
;
349 Script::is_p (const std::string
& stratum
)
351 return match( stratum
, "p");
354 Script::not_p (const std::string
& stratum
)
356 return match( stratum
, allowedTypes
+1);
359 Script::Script (const std::string
& fileName
)
360 : scriptName (fileName
),
362 scriptExtension (""),
367 found
= fileName
.rfind('/');
368 if (found
!= std::string::npos
)
369 scriptBaseName
= fileName
.substr(found
+ 1);
370 found
= fileName
.rfind('.');
371 if (found
!= std::string::npos
)
372 scriptExtension
= fileName
.substr(found
+ 1);
373 if ( "_" == scriptBaseName
.substr(2,1) &&
374 0 == scriptBaseName
.substr(1,1).find_first_of(allowedTypes
) &&
375 "_" != scriptBaseName
.substr(0,1) && // default stratum cannot be explicitly named
376 0 == scriptBaseName
.substr(0,1).find_first_of(allowedStrata
))
379 scriptStratum
= scriptBaseName
.substr(0,1);
380 scriptType
= scriptBaseName
.substr(1,1);
382 // Let's hope people won't uninstall packages before installing bash
387 Script::baseName() const
389 return scriptBaseName
;
393 Script::fullName() const
398 char const Script::ETCPostinstall
[] = "/etc/postinstall/";
399 char const Script::allowedStrata
[] = "0_z";
400 char const Script::allowedTypes
[] = "pr";