first check-in of feisty meow codebase. many things broken still due to recent
[feisty_meow.git] / core / library / processes / launch_process.cpp
1
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 \*****************************************************************************/
12
13 #include "launch_process.h"
14
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>
23
24 #include <stdlib.h>
25 #ifdef __UNIX__
26   #include <signal.h>
27   #include <sys/types.h>
28   #include <sys/wait.h>
29   #include <unistd.h>
30 #endif
31 #ifdef __WIN32__
32   #include <process.h>
33   #include <shellapi.h>
34   #include <shlobj.h>
35 #endif
36
37 //#define DEBUG_LAUNCH_PROCESS
38   // uncomment for noisier debugging info.
39
40 using namespace basis;
41 using namespace configuration;
42 using namespace loggers;
43 using namespace structures;
44 using namespace timely;
45
46 #undef LOG
47 #define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), s);
48
49 namespace processes {
50
51 //hmmm: some of these should probably be safe statics.
52
53 mutex &__process_synchronizer() {
54   static mutex __hidden_synch;
55   return __hidden_synch;
56 }
57
58 int_set __our_kids() {
59   static int_set __hidden_kids;
60   return __hidden_kids;
61 }
62
63 #ifdef __WIN32__
64 bool launch_process::event_poll(MSG &message)
65 {
66   message.hwnd = 0;
67   message.message = 0;
68   message.wParam = 0;
69   message.lParam = 0;
70   if (!PeekMessage(&message, NIL, 0, 0, PM_REMOVE))
71     return false;
72   TranslateMessage(&message);
73   DispatchMessage(&message);
74   return true;
75 }
76 #endif
77
78 #define SUPPORT_SHELL_EXECUTE
79   // if this is not commented out, then the ShellExecute version of launch_
80   // -process() is available.  when commented out, ShellExecute is turned off.
81   // disabling this support is the most common because the ShellExecute method
82   // in win32 was only supported for wk203 and wxp, that is only after
83   // windows2000 was already available.  since nt and w2k don't support this,
84   // we just usually don't mess with it.  it didn't answer a single one of our
85   // issues on windows vista (wfista) anyway, so it's not helping.
86
87 //const int MAXIMUM_COMMAND_LINE = 32 * KILOBYTE;
88   // maximum command line that we'll deal with here.
89
90 #ifdef __UNIX__
91 void launch_process::exiting_child_signal_handler(int sig_num)
92 {
93   FUNCDEF("exiting_child_signal_handler");
94   if (sig_num != SIGCHLD) {
95     // uhhh, this seems wrong.
96   }
97   auto_synchronizer l(__process_synchronizer());
98   for (int i = 0; i < __our_kids().length(); i++) {
99     int status;
100     pid_t exited = waitpid(__our_kids()[i], &status, WNOHANG);
101     if ( (exited == -1) || (exited == __our_kids()[i]) ) {
102       // negative one denotes an error, which we are going to assume means the
103       // process has exited via some other method than our wait.  if the value
104       // is the same as the process we waited for, that means it exited.
105       __our_kids().zap(i, i);
106       i--;
107     } else if (exited != 0) {
108       // zero would be okay; this result we do not understand.
109 #ifdef DEBUG_LAUNCH_PROCESS
110       LOG(a_sprintf("unknown result %d waiting for process %d", exited, __our_kids()[i]));
111 #endif
112     }
113   }
114 }
115 #endif
116
117 //hmmm: this doesn't seem to account for quoting properly at all?
118 char_star_array launch_process::break_line(astring &app, const astring &parameters)
119 {
120   FUNCDEF("break_line");
121   char_star_array to_return;
122   int_array posns;
123   int num = 0;
124   // find the positions of the spaces and count them.
125   for (int j = 0; j < parameters.length(); j++) {
126     if (parameters[j] == ' ') {
127       num++;
128       posns += j;
129     }
130   }
131   // first, add the app name to the list of parms.
132   to_return += new char[app.length() + 1];
133   app.stuff(to_return[0], app.length());
134   int last_posn = 0;
135   // now add each space-separated parameter to the list.
136   for (int i = 0; i < num; i++) {
137     int len = posns[i] - last_posn;
138     to_return += new char[len + 1];
139     parameters.substring(last_posn, posns[i] - 1).stuff(to_return[i + 1], len);
140     last_posn = posns[i] + 1;
141   }
142   // catch anything left after last separator.
143   if (last_posn < parameters.length() - 1) {
144     int len = parameters.length() - last_posn;
145     to_return += new char[len + 1];
146     parameters.substring(last_posn, parameters.length() - 1)
147         .stuff(to_return[to_return.last()], len);
148   }
149   // add the sentinel to the list of strings.
150   to_return += NIL;
151 #ifdef DEBUG_LAUNCH_PROCESS
152   for (int q = 0; to_return[q]; q++) {
153     LOG(a_sprintf("%d: %s\n", q, to_return[q]));
154   }
155 #endif
156   // now a special detour; fix the app name to remove quotes, which are
157   // not friendly to pass to exec.
158   if (app[0] == '"') app.zap(0, 0);
159   if (app[app.end()] == '"') app.zap(app.end(), app.end());
160   return to_return;
161 }
162
163 basis::un_int launch_process::run(const astring &app_name_in, const astring &command_line,
164     int flag, basis::un_int &child_id)
165 {
166 #ifdef DEBUG_LAUNCH_PROCESS
167   FUNCDEF("run");
168 #endif
169   child_id = 0;
170   astring app_name = app_name_in;
171   if (app_name[0] != '"')
172     app_name.insert(0, "\"");
173   if (app_name[app_name.end()] != '"')
174     app_name += "\"";
175 #ifdef __UNIX__
176   // unix / linux implementation.
177   if (flag & RETURN_IMMEDIATELY) {
178     // they want to get back right away.
179     pid_t kid_pid = fork();
180 #ifdef DEBUG_LAUNCH_PROCESS
181     LOG(a_sprintf("launch fork returned %d\n", kid_pid));
182 #endif
183     if (!kid_pid) {
184       // this is the child; we now need to launch into what we were asked for.
185 #ifdef DEBUG_LAUNCH_PROCESS
186       LOG(a_sprintf("process %d execing ", application_configuration::process_id()) + app_name
187           + " parms " + command_line + "\n");
188 #endif
189       char_star_array parms = break_line(app_name, command_line);
190       execv(app_name.s(), parms.observe());
191       // oops.  failed to exec if we got to here.
192 #ifdef DEBUG_LAUNCH_PROCESS
193       LOG(a_sprintf("child of fork (pid %d) failed to exec, error is ",
194           application_configuration::process_id())
195           + critical_events::system_error_text(critical_events::system_error())
196           + "\n");
197 #endif
198       exit(0);  // leave since this is a failed child process.
199     } else {
200       // this is the parent.  let's see if the launch worked.
201       if (kid_pid == -1) {
202         // failure.
203         basis::un_int to_return = critical_events::system_error();
204 #ifdef DEBUG_LAUNCH_PROCESS
205         LOG(a_sprintf("parent %d is returning after failing to create, "
206             "error is ", application_configuration::process_id())
207             + critical_events::system_error_text(to_return)
208             + "\n");
209 #endif
210         return to_return;
211       } else {
212         // yes, launch worked okay.
213         child_id = kid_pid;
214         {
215           auto_synchronizer l(__process_synchronizer());
216           __our_kids() += kid_pid;
217         }
218         // hook in our child exit signal handler.
219         signal(SIGCHLD, exiting_child_signal_handler);
220
221 #ifdef DEBUG_LAUNCH_PROCESS
222         LOG(a_sprintf("parent %d is returning after successfully "
223             "creating %d ", application_configuration::process_id(), kid_pid) + app_name
224             + " parms " + command_line + "\n");
225 #endif
226         return 0;
227       }
228     }
229   } else {
230     // assume they want to wait.
231     return system((app_name + " " + command_line).s());
232   }
233 #elif defined(__WIN32__)
234
235 //checking on whether we have admin rights for the launch.
236 //if (IsUserAnAdmin()) {
237 //  MessageBox(0, (astring("IS admin with ") + app_name_in + " " + command_line).s(), "launch process", MB_OK);
238 //} else {
239 //  MessageBox(0, (astring("NOT admin for ") + app_name_in + " " + command_line).s(), "launch process", MB_OK);
240 //}
241
242   PROCESS_INFORMATION process_info;
243   ZeroMemory(&process_info, sizeof(PROCESS_INFORMATION));
244
245 #ifdef SUPPORT_SHELL_EXECUTE
246   if (flag & SHELL_EXECUTE) {
247     // new code for using shell execute method--required on vista for proper
248     // launching with the right security tokens.
249     int show_cmd = 0;
250     if (flag & HIDE_APP_WINDOW) {
251       // magic that hides a console window for mswindows.
252       show_cmd = SW_HIDE;
253     } else {
254       show_cmd = SW_SHOWNORMAL;
255     }
256
257     SHELLEXECUTEINFO exec_info;
258     ZeroMemory(&exec_info, sizeof(SHELLEXECUTEINFO));
259     exec_info.cbSize = sizeof(SHELLEXECUTEINFO);
260     exec_info.fMask = SEE_MASK_NOCLOSEPROCESS  // get the process info.
261         | SEE_MASK_FLAG_NO_UI;  // turn off any visible error dialogs.
262     exec_info.hwnd = GetDesktopWindow();
263 //hmmm: is get desktop window always appropriate?
264     to_unicode_persist(temp_verb, "open");
265 //does "runas" work on xp also?  or anywhere?
266     exec_info.lpVerb = temp_verb;
267     to_unicode_persist(temp_file, app_name);
268     exec_info.lpFile = temp_file;
269     to_unicode_persist(temp_parms, command_line);
270     exec_info.lpParameters = temp_parms;
271     exec_info.nShow = show_cmd;
272 //    exec_info.hProcess = &process_info;
273
274     BOOL worked = ShellExecuteEx(&exec_info);
275     if (!worked)
276       return critical_events::system_error();
277         // copy out the returned process handle.
278     process_info.hProcess = exec_info.hProcess;
279     process_info.dwProcessId = GetProcessId(exec_info.hProcess);
280   } else {
281 #endif //shell exec
282     // standard windows implementation using CreateProcess.
283     STARTUPINFO startup_info;
284     ZeroMemory(&startup_info, sizeof(STARTUPINFO));
285     startup_info.cb = sizeof(STARTUPINFO);
286     int create_flag = 0;
287     if (flag & HIDE_APP_WINDOW) {
288       // magic that hides a console window for mswindows.
289 //      version ver = portable::get_OS_version();
290 //      version vista_version(6, 0);
291 //      if (ver < vista_version) {
292 //        // we suspect that this flag is hosing us in vista.
293         create_flag = CREATE_NO_WINDOW;
294 //      }
295     }
296     astring parms = app_name + " " + command_line;
297     bool success = CreateProcess(NIL, to_unicode_temp(parms), NIL, NIL, false,
298         create_flag, NIL, NIL, &startup_info, &process_info);
299     if (!success)
300       return critical_events::system_error();
301     // success then, merge back into stream.
302
303 #ifdef SUPPORT_SHELL_EXECUTE
304   }
305 #endif //shell exec
306
307   // common handling for CreateProcess and ShellExecuteEx.
308   child_id = process_info.dwProcessId;
309   basis::un_long retval = 0;
310   if (flag & AWAIT_VIA_POLLING) {
311     // this type of waiting is done without blocking on the process.
312     while (true) {
313       MSG msg;
314       event_poll(msg);
315       // check if the process is gone yet.
316       BOOL ret = GetExitCodeProcess(process_info.hProcess, &retval);
317       if (!ret) {
318         break;
319       } else {
320         // if they aren't saying it's still active, then we will leave.
321         if (retval != STILL_ACTIVE)
322           break;
323       }
324       time_control::sleep_ms(14);
325     }
326   } else if (flag & AWAIT_APP_EXIT) {
327     // they want to wait for the process to exit.
328     WaitForInputIdle(process_info.hProcess, INFINITE); 
329     WaitForSingleObject(process_info.hProcess, INFINITE);
330     GetExitCodeProcess(process_info.hProcess, &retval);
331   }
332   // drop the process and thread handles.
333   if (process_info.hProcess)
334     CloseHandle(process_info.hProcess);
335   if (process_info.hThread)
336         CloseHandle(process_info.hThread);
337   return (basis::un_int)retval;
338 #else
339   #pragma error("hmmm: launch_process: no implementation for this OS.")
340 #endif
341   return 0;
342 }
343
344 } // namespace.
345