X-Git-Url: https://feistymeow.org/gitweb/?a=blobdiff_plain;ds=sidebyside;f=nucleus%2Flibrary%2Fprocesses%2Flaunch_process.cpp;fp=nucleus%2Flibrary%2Fprocesses%2Flaunch_process.cpp;h=bb8d7ada002cb60c7028d862f37ae772f8388ac5;hb=457b128b77b5b4a0b7dd3094de543de2ce1477ad;hp=0000000000000000000000000000000000000000;hpb=32d7caf45d886d0d24e69eea00511c7815ac15d0;p=feisty_meow.git diff --git a/nucleus/library/processes/launch_process.cpp b/nucleus/library/processes/launch_process.cpp new file mode 100644 index 00000000..bb8d7ada --- /dev/null +++ b/nucleus/library/processes/launch_process.cpp @@ -0,0 +1,345 @@ + +// Name : launch_process +// Author : Chris Koeritz +/****************************************************************************** +* Copyright (c) 1994-$now By Author. This program is free software; you can * +* redistribute it and/or modify it under the terms of the GNU General Public * +* License as published by the Free Software Foundation; either version 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "launch_process.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#ifdef __UNIX__ + #include + #include + #include + #include +#endif +#ifdef __WIN32__ + #include + #include + #include +#endif + +//#define DEBUG_LAUNCH_PROCESS + // uncomment for noisier debugging info. + +using namespace basis; +using namespace configuration; +using namespace loggers; +using namespace structures; +using namespace timely; + +#undef LOG +#define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), s); + +namespace processes { + +//hmmm: some of these should probably be safe statics. + +mutex &__process_synchronizer() { + static mutex __hidden_synch; + return __hidden_synch; +} + +int_set __our_kids() { + static int_set __hidden_kids; + return __hidden_kids; +} + +#ifdef __WIN32__ +bool launch_process::event_poll(MSG &message) +{ + message.hwnd = 0; + message.message = 0; + message.wParam = 0; + message.lParam = 0; + if (!PeekMessage(&message, NIL, 0, 0, PM_REMOVE)) + return false; + TranslateMessage(&message); + DispatchMessage(&message); + return true; +} +#endif + +#define SUPPORT_SHELL_EXECUTE + // if this is not commented out, then the ShellExecute version of launch_ + // -process() is available. when commented out, ShellExecute is turned off. + // disabling this support is the most common because the ShellExecute method + // in win32 was only supported for wk203 and wxp, that is only after + // windows2000 was already available. since nt and w2k don't support this, + // we just usually don't mess with it. it didn't answer a single one of our + // issues on windows vista (wfista) anyway, so it's not helping. + +//const int MAXIMUM_COMMAND_LINE = 32 * KILOBYTE; + // maximum command line that we'll deal with here. + +#ifdef __UNIX__ +void launch_process::exiting_child_signal_handler(int sig_num) +{ + FUNCDEF("exiting_child_signal_handler"); + if (sig_num != SIGCHLD) { + // uhhh, this seems wrong. + } + auto_synchronizer l(__process_synchronizer()); + for (int i = 0; i < __our_kids().length(); i++) { + int status; + pid_t exited = waitpid(__our_kids()[i], &status, WNOHANG); + if ( (exited == -1) || (exited == __our_kids()[i]) ) { + // negative one denotes an error, which we are going to assume means the + // process has exited via some other method than our wait. if the value + // is the same as the process we waited for, that means it exited. + __our_kids().zap(i, i); + i--; + } else if (exited != 0) { + // zero would be okay; this result we do not understand. +#ifdef DEBUG_LAUNCH_PROCESS + LOG(a_sprintf("unknown result %d waiting for process %d", exited, __our_kids()[i])); +#endif + } + } +} +#endif + +//hmmm: this doesn't seem to account for quoting properly at all? +char_star_array launch_process::break_line(astring &app, const astring ¶meters) +{ + FUNCDEF("break_line"); + char_star_array to_return; + int_array posns; + int num = 0; + // find the positions of the spaces and count them. + for (int j = 0; j < parameters.length(); j++) { + if (parameters[j] == ' ') { + num++; + posns += j; + } + } + // first, add the app name to the list of parms. + to_return += new char[app.length() + 1]; + app.stuff(to_return[0], app.length()); + int last_posn = 0; + // now add each space-separated parameter to the list. + for (int i = 0; i < num; i++) { + int len = posns[i] - last_posn; + to_return += new char[len + 1]; + parameters.substring(last_posn, posns[i] - 1).stuff(to_return[i + 1], len); + last_posn = posns[i] + 1; + } + // catch anything left after last separator. + if (last_posn < parameters.length() - 1) { + int len = parameters.length() - last_posn; + to_return += new char[len + 1]; + parameters.substring(last_posn, parameters.length() - 1) + .stuff(to_return[to_return.last()], len); + } + // add the sentinel to the list of strings. + to_return += NIL; +#ifdef DEBUG_LAUNCH_PROCESS + for (int q = 0; to_return[q]; q++) { + LOG(a_sprintf("%d: %s\n", q, to_return[q])); + } +#endif + // now a special detour; fix the app name to remove quotes, which are + // not friendly to pass to exec. + if (app[0] == '"') app.zap(0, 0); + if (app[app.end()] == '"') app.zap(app.end(), app.end()); + return to_return; +} + +basis::un_int launch_process::run(const astring &app_name_in, const astring &command_line, + int flag, basis::un_int &child_id) +{ +#ifdef DEBUG_LAUNCH_PROCESS + FUNCDEF("run"); +#endif + child_id = 0; + astring app_name = app_name_in; + if (app_name[0] != '"') + app_name.insert(0, "\""); + if (app_name[app_name.end()] != '"') + app_name += "\""; +#ifdef __UNIX__ + // unix / linux implementation. + if (flag & RETURN_IMMEDIATELY) { + // they want to get back right away. + pid_t kid_pid = fork(); +#ifdef DEBUG_LAUNCH_PROCESS + LOG(a_sprintf("launch fork returned %d\n", kid_pid)); +#endif + if (!kid_pid) { + // this is the child; we now need to launch into what we were asked for. +#ifdef DEBUG_LAUNCH_PROCESS + LOG(a_sprintf("process %d execing ", application_configuration::process_id()) + app_name + + " parms " + command_line + "\n"); +#endif + char_star_array parms = break_line(app_name, command_line); + execv(app_name.s(), parms.observe()); + // oops. failed to exec if we got to here. +#ifdef DEBUG_LAUNCH_PROCESS + LOG(a_sprintf("child of fork (pid %d) failed to exec, error is ", + application_configuration::process_id()) + + critical_events::system_error_text(critical_events::system_error()) + + "\n"); +#endif + exit(0); // leave since this is a failed child process. + } else { + // this is the parent. let's see if the launch worked. + if (kid_pid == -1) { + // failure. + basis::un_int to_return = critical_events::system_error(); +#ifdef DEBUG_LAUNCH_PROCESS + LOG(a_sprintf("parent %d is returning after failing to create, " + "error is ", application_configuration::process_id()) + + critical_events::system_error_text(to_return) + + "\n"); +#endif + return to_return; + } else { + // yes, launch worked okay. + child_id = kid_pid; + { + auto_synchronizer l(__process_synchronizer()); + __our_kids() += kid_pid; + } + // hook in our child exit signal handler. + signal(SIGCHLD, exiting_child_signal_handler); + +#ifdef DEBUG_LAUNCH_PROCESS + LOG(a_sprintf("parent %d is returning after successfully " + "creating %d ", application_configuration::process_id(), kid_pid) + app_name + + " parms " + command_line + "\n"); +#endif + return 0; + } + } + } else { + // assume they want to wait. + return system((app_name + " " + command_line).s()); + } +#elif defined(__WIN32__) + +//checking on whether we have admin rights for the launch. +//if (IsUserAnAdmin()) { +// MessageBox(0, (astring("IS admin with ") + app_name_in + " " + command_line).s(), "launch process", MB_OK); +//} else { +// MessageBox(0, (astring("NOT admin for ") + app_name_in + " " + command_line).s(), "launch process", MB_OK); +//} + + PROCESS_INFORMATION process_info; + ZeroMemory(&process_info, sizeof(PROCESS_INFORMATION)); + +#ifdef SUPPORT_SHELL_EXECUTE + if (flag & SHELL_EXECUTE) { + // new code for using shell execute method--required on vista for proper + // launching with the right security tokens. + int show_cmd = 0; + if (flag & HIDE_APP_WINDOW) { + // magic that hides a console window for mswindows. + show_cmd = SW_HIDE; + } else { + show_cmd = SW_SHOWNORMAL; + } + + SHELLEXECUTEINFO exec_info; + ZeroMemory(&exec_info, sizeof(SHELLEXECUTEINFO)); + exec_info.cbSize = sizeof(SHELLEXECUTEINFO); + exec_info.fMask = SEE_MASK_NOCLOSEPROCESS // get the process info. + | SEE_MASK_FLAG_NO_UI; // turn off any visible error dialogs. + exec_info.hwnd = GetDesktopWindow(); +//hmmm: is get desktop window always appropriate? + to_unicode_persist(temp_verb, "open"); +//does "runas" work on xp also? or anywhere? + exec_info.lpVerb = temp_verb; + to_unicode_persist(temp_file, app_name); + exec_info.lpFile = temp_file; + to_unicode_persist(temp_parms, command_line); + exec_info.lpParameters = temp_parms; + exec_info.nShow = show_cmd; +// exec_info.hProcess = &process_info; + + BOOL worked = ShellExecuteEx(&exec_info); + if (!worked) + return critical_events::system_error(); + // copy out the returned process handle. + process_info.hProcess = exec_info.hProcess; + process_info.dwProcessId = GetProcessId(exec_info.hProcess); + } else { +#endif //shell exec + // standard windows implementation using CreateProcess. + STARTUPINFO startup_info; + ZeroMemory(&startup_info, sizeof(STARTUPINFO)); + startup_info.cb = sizeof(STARTUPINFO); + int create_flag = 0; + if (flag & HIDE_APP_WINDOW) { + // magic that hides a console window for mswindows. +// version ver = portable::get_OS_version(); +// version vista_version(6, 0); +// if (ver < vista_version) { +// // we suspect that this flag is hosing us in vista. + create_flag = CREATE_NO_WINDOW; +// } + } + astring parms = app_name + " " + command_line; + bool success = CreateProcess(NIL, to_unicode_temp(parms), NIL, NIL, false, + create_flag, NIL, NIL, &startup_info, &process_info); + if (!success) + return critical_events::system_error(); + // success then, merge back into stream. + +#ifdef SUPPORT_SHELL_EXECUTE + } +#endif //shell exec + + // common handling for CreateProcess and ShellExecuteEx. + child_id = process_info.dwProcessId; + basis::un_long retval = 0; + if (flag & AWAIT_VIA_POLLING) { + // this type of waiting is done without blocking on the process. + while (true) { + MSG msg; + event_poll(msg); + // check if the process is gone yet. + BOOL ret = GetExitCodeProcess(process_info.hProcess, &retval); + if (!ret) { + break; + } else { + // if they aren't saying it's still active, then we will leave. + if (retval != STILL_ACTIVE) + break; + } + time_control::sleep_ms(14); + } + } else if (flag & AWAIT_APP_EXIT) { + // they want to wait for the process to exit. + WaitForInputIdle(process_info.hProcess, INFINITE); + WaitForSingleObject(process_info.hProcess, INFINITE); + GetExitCodeProcess(process_info.hProcess, &retval); + } + // drop the process and thread handles. + if (process_info.hProcess) + CloseHandle(process_info.hProcess); + if (process_info.hThread) + CloseHandle(process_info.hThread); + return (basis::un_int)retval; +#else + #pragma error("hmmm: launch_process: no implementation for this OS.") +#endif + return 0; +} + +} // namespace. +