2 // Name : launch_process
3 // Author : Chris Koeritz
4 /******************************************************************************
5 * Copyright (c) 1994-$now By Author. This program is free software; you can *
6 * redistribute it and/or modify it under the terms of the GNU General Public *
7 * License as published by the Free Software Foundation; either version 2 of *
8 * the License or (at your option) any later version. This is online at: *
9 * http://www.fsf.org/copyleft/gpl.html *
10 * Please send any updates to: fred@gruntose.com *
11 \*****************************************************************************/
13 #include "launch_process.h"
15 #include <application/windoze_helper.h>
16 #include <basis/utf_conversion.h>
17 #include <basis/mutex.h>
18 #include <configuration/application_configuration.h>
19 #include <loggers/critical_events.h>
20 #include <loggers/program_wide_logger.h>
21 #include <structures/set.h>
22 #include <timely/time_control.h>
27 #include <sys/types.h>
36 //#define DEBUG_LAUNCH_PROCESS
37 // uncomment for noisier debugging info.
39 using namespace basis;
40 using namespace configuration;
41 using namespace loggers;
42 using namespace structures;
43 using namespace timely;
46 #define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), s);
50 //hmmm: some of these should probably be safe statics.
52 mutex &__process_synchronizer() {
53 static mutex __hidden_synch;
54 return __hidden_synch;
57 int_set __our_kids() {
58 static int_set __hidden_kids;
63 bool launch_process::event_poll(MSG &message)
69 if (!PeekMessage(&message, NULL_POINTER, 0, 0, PM_REMOVE))
71 TranslateMessage(&message);
72 DispatchMessage(&message);
77 #define SUPPORT_SHELL_EXECUTE
78 // if this is not commented out, then the ShellExecute version of launch_
79 // -process() is available. when commented out, ShellExecute is turned off.
80 // disabling this support is the most common because the ShellExecute method
81 // in win32 was only supported for wk203 and wxp, that is only after
82 // windows2000 was already available. since nt and w2k don't support this,
83 // we just usually don't mess with it. it didn't answer a single one of our
84 // issues on windows vista (wfista) anyway, so it's not helping.
86 //const int MAXIMUM_COMMAND_LINE = 32 * KILOBYTE;
87 // maximum command line that we'll deal with here.
90 void launch_process::exiting_child_signal_handler(int sig_num)
92 FUNCDEF("exiting_child_signal_handler");
93 if (sig_num != SIGCHLD) {
94 // uhhh, this seems wrong.
96 auto_synchronizer l(__process_synchronizer());
97 for (int i = 0; i < __our_kids().length(); i++) {
99 pid_t exited = waitpid(__our_kids()[i], &status, WNOHANG);
100 if ( (exited == -1) || (exited == __our_kids()[i]) ) {
101 // negative one denotes an error, which we are going to assume means the
102 // process has exited via some other method than our wait. if the value
103 // is the same as the process we waited for, that means it exited.
104 __our_kids().zap(i, i);
106 } else if (exited != 0) {
107 // zero would be okay; this result we do not understand.
108 #ifdef DEBUG_LAUNCH_PROCESS
109 LOG(a_sprintf("unknown result %d waiting for process %d", exited, __our_kids()[i]));
116 //hmmm: this doesn't seem to account for quoting properly at all?
117 char_star_array launch_process::break_line(astring &app, const astring ¶meters)
119 FUNCDEF("break_line");
120 char_star_array to_return;
123 // find the positions of the spaces and count them.
124 for (int j = 0; j < parameters.length(); j++) {
125 if (parameters[j] == ' ') {
130 // first, add the app name to the list of parms.
131 to_return += new char[app.length() + 1];
132 app.stuff(to_return[0], app.length());
134 // now add each space-separated parameter to the list.
135 for (int i = 0; i < num; i++) {
136 int len = posns[i] - last_posn;
137 to_return += new char[len + 1];
138 parameters.substring(last_posn, posns[i] - 1).stuff(to_return[i + 1], len);
139 last_posn = posns[i] + 1;
141 // catch anything left after last separator.
142 if (last_posn < parameters.length() - 1) {
143 int len = parameters.length() - last_posn;
144 to_return += new char[len + 1];
145 parameters.substring(last_posn, parameters.length() - 1)
146 .stuff(to_return[to_return.last()], len);
148 // add the sentinel to the list of strings.
149 to_return += NULL_POINTER;
150 #ifdef DEBUG_LAUNCH_PROCESS
151 for (int q = 0; to_return[q]; q++) {
152 LOG(a_sprintf("%d: %s\n", q, to_return[q]));
155 // now a special detour; fix the app name to remove quotes, which are
156 // not friendly to pass to exec.
157 if (app[0] == '"') app.zap(0, 0);
158 if (app[app.end()] == '"') app.zap(app.end(), app.end());
162 basis::un_int launch_process::run(const astring &app_name_in, const astring &command_line,
163 int flag, basis::un_int &child_id)
165 #ifdef DEBUG_LAUNCH_PROCESS
169 astring app_name = app_name_in;
170 if (app_name[0] != '"')
171 app_name.insert(0, "\"");
172 if (app_name[app_name.end()] != '"')
174 #if defined(__UNIX__) || defined(__GNU_WINDOWS__)
175 // unix / linux implementation.
176 if (flag & RETURN_IMMEDIATELY) {
177 // they want to get back right away.
178 pid_t kid_pid = fork();
179 #ifdef DEBUG_LAUNCH_PROCESS
180 LOG(a_sprintf("launch fork returned %d\n", kid_pid));
183 // this is the child; we now need to launch into what we were asked for.
184 #ifdef DEBUG_LAUNCH_PROCESS
185 LOG(a_sprintf("process %d execing ", application_configuration::process_id()) + app_name
186 + " parms " + command_line + "\n");
188 char_star_array parms = break_line(app_name, command_line);
189 execv(app_name.s(), parms.observe());
190 // oops. failed to exec if we got to here.
191 #ifdef DEBUG_LAUNCH_PROCESS
192 LOG(a_sprintf("child of fork (pid %d) failed to exec, error is ",
193 application_configuration::process_id())
194 + critical_events::system_error_text(critical_events::system_error())
197 exit(0); // leave since this is a failed child process.
199 // this is the parent. let's see if the launch worked.
202 basis::un_int to_return = critical_events::system_error();
203 #ifdef DEBUG_LAUNCH_PROCESS
204 LOG(a_sprintf("parent %d is returning after failing to create, "
205 "error is ", application_configuration::process_id())
206 + critical_events::system_error_text(to_return)
211 // yes, launch worked okay.
214 auto_synchronizer l(__process_synchronizer());
215 __our_kids() += kid_pid;
217 // hook in our child exit signal handler.
218 signal(SIGCHLD, exiting_child_signal_handler);
220 #ifdef DEBUG_LAUNCH_PROCESS
221 LOG(a_sprintf("parent %d is returning after successfully "
222 "creating %d ", application_configuration::process_id(), kid_pid) + app_name
223 + " parms " + command_line + "\n");
229 // assume they want to wait.
230 return system((app_name + " " + command_line).s());
232 #elif defined(_MSC_VER)
234 //checking on whether we have admin rights for the launch.
235 //if (IsUserAnAdmin()) {
236 // MessageBox(0, (astring("IS admin with ") + app_name_in + " " + command_line).s(), "launch process", MB_OK);
238 // MessageBox(0, (astring("NOT admin for ") + app_name_in + " " + command_line).s(), "launch process", MB_OK);
241 PROCESS_INFORMATION process_info;
242 ZeroMemory(&process_info, sizeof(PROCESS_INFORMATION));
244 #ifdef SUPPORT_SHELL_EXECUTE
245 if (flag & SHELL_EXECUTE) {
246 // new code for using shell execute method--required on vista for proper
247 // launching with the right security tokens.
249 if (flag & HIDE_APP_WINDOW) {
250 // magic that hides a console window for mswindows.
253 show_cmd = SW_SHOWNORMAL;
256 SHELLEXECUTEINFO exec_info;
257 ZeroMemory(&exec_info, sizeof(SHELLEXECUTEINFO));
258 exec_info.cbSize = sizeof(SHELLEXECUTEINFO);
259 exec_info.fMask = SEE_MASK_NOCLOSEPROCESS // get the process info.
260 | SEE_MASK_FLAG_NO_UI; // turn off any visible error dialogs.
261 exec_info.hwnd = GetDesktopWindow();
262 //hmmm: is get desktop window always appropriate?
263 to_unicode_persist(temp_verb, "open");
264 //does "runas" work on xp also? or anywhere?
265 exec_info.lpVerb = temp_verb;
266 to_unicode_persist(temp_file, app_name);
267 exec_info.lpFile = temp_file;
268 to_unicode_persist(temp_parms, command_line);
269 exec_info.lpParameters = temp_parms;
270 exec_info.nShow = show_cmd;
271 // exec_info.hProcess = &process_info;
273 BOOL worked = ShellExecuteEx(&exec_info);
275 return critical_events::system_error();
276 // copy out the returned process handle.
277 process_info.hProcess = exec_info.hProcess;
278 process_info.dwProcessId = GetProcessId(exec_info.hProcess);
281 // standard windows implementation using CreateProcess.
282 STARTUPINFO startup_info;
283 ZeroMemory(&startup_info, sizeof(STARTUPINFO));
284 startup_info.cb = sizeof(STARTUPINFO);
286 if (flag & HIDE_APP_WINDOW) {
287 // magic that hides a console window for mswindows.
288 // version ver = portable::get_OS_version();
289 // version vista_version(6, 0);
290 // if (ver < vista_version) {
291 // // we suspect that this flag is hosing us in vista.
292 create_flag = CREATE_NO_WINDOW;
295 astring parms = app_name + " " + command_line;
296 bool success = CreateProcess(NULL_POINTER, to_unicode_temp(parms), NULL_POINTER, NULL_POINTER, false,
297 create_flag, NULL_POINTER, NULL_POINTER, &startup_info, &process_info);
299 return critical_events::system_error();
300 // success then, merge back into stream.
302 #ifdef SUPPORT_SHELL_EXECUTE
306 // common handling for CreateProcess and ShellExecuteEx.
307 child_id = process_info.dwProcessId;
308 basis::un_long retval = 0;
309 if (flag & AWAIT_VIA_POLLING) {
310 // this type of waiting is done without blocking on the process.
314 // check if the process is gone yet.
315 BOOL ret = GetExitCodeProcess(process_info.hProcess, &retval);
319 // if they aren't saying it's still active, then we will leave.
320 if (retval != STILL_ACTIVE)
323 time_control::sleep_ms(14);
325 } else if (flag & AWAIT_APP_EXIT) {
326 // they want to wait for the process to exit.
327 WaitForInputIdle(process_info.hProcess, INFINITE);
328 WaitForSingleObject(process_info.hProcess, INFINITE);
329 GetExitCodeProcess(process_info.hProcess, &retval);
331 // drop the process and thread handles.
332 if (process_info.hProcess)
333 CloseHandle(process_info.hProcess);
334 if (process_info.hThread)
335 CloseHandle(process_info.hThread);
336 return (basis::un_int)retval;
338 #pragma error("hmmm: launch_process: no implementation for this OS.")