bool process::launch(const std::string &cmd, const std::vector<std::string> &args) { PROCESS_INFORMATION proc_info; STARTUPINFO startup_info; ZeroMemory(&proc_info, sizeof(PROCESS_INFORMATION)); ZeroMemory(&startup_info, sizeof(STARTUPINFO)); char *c_arglist = convert_args(cmd, args); BOOL ret; // For Windows, we include the cmd with the arguments so that a search path // is used for any executable without a full path ret = CreateProcess(NULL, c_arglist, NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, NULL, &startup_info, &proc_info); if(!ret) { auto err = GetLastError(); logstream(LOG_ERROR) << "Failed to launch process: " << get_last_err_str(err) << std::endl; delete[] c_arglist; return false; } else { // Don't need to have a thread handle. We'll just cancel the process if // need be CloseHandle(proc_info.hThread); m_launched = true; } // Used for killing the process m_proc_handle = proc_info.hProcess; m_pid = proc_info.dwProcessId; logstream(LOG_INFO) << "Launched process with pid: " << m_pid << std::endl; return true; }
ssize_t process::read_from_child(void *buf, size_t count) { if(!m_launched) log_and_throw("No process launched!"); if(!m_launched_with_popen || m_read_handle == NULL) log_and_throw("Cannot read from child, no pipe initialized. " "Launch with popen to do this."); // Keep from overflowing if(count > std::numeric_limits<DWORD>::max()) { count = std::numeric_limits<DWORD>::max(); } DWORD bytes_read; BOOL ret = ReadFile(m_read_handle, (LPWORD)buf, count, &bytes_read, NULL); if(!ret) { logstream(LOG_ERROR) << "ReadFile failed: " << get_last_err_str(GetLastError()) << std::endl; } return ret ? ssize_t(bytes_read) : ssize_t(-1); }
bool process::kill(bool async) { if(!m_launched) log_and_throw("No process launched!"); if(m_proc_handle != NULL) { BOOL ret = TerminateProcess(m_proc_handle, 1); auto err_code = GetLastError(); if(!async) WaitForSingleObject(m_proc_handle, 10000); CloseHandle(m_proc_handle); m_proc_handle = NULL; if(!ret) { logstream(LOG_INFO) << get_last_err_str(err_code); return false; } return true; } return false; }
bool process::launch(const std::string &cmd, const std::vector<std::string> &args) { PROCESS_INFORMATION proc_info; STARTUPINFO startup_info; ZeroMemory(&proc_info, sizeof(PROCESS_INFORMATION)); ZeroMemory(&startup_info, sizeof(STARTUPINFO)); startup_info.cb = sizeof(startup_info); char *c_arglist = convert_args(cmd, args); logstream(LOG_INFO) << "Launching process using command: >>> " << c_arglist << " <<< " << std::endl; // Set up the proper handlers. We are duplicating the handlers as // the given handles may or may not be inheritable. DuplicateHandle // is (supposedly) the safest way to do this. startup_info.dwFlags |= STARTF_USESTDHANDLES; BOOL ret; // First, set up redirection for stdout. ret = DuplicateHandle( GetCurrentProcess(), GetStdHandle(STD_OUTPUT_HANDLE), GetCurrentProcess(), &m_stdout_handle, 0, TRUE, DUPLICATE_SAME_ACCESS); if(!ret) { auto err = GetLastError(); logstream(LOG_WARNING) << "Failed to duplicate stdout file handle: " << get_last_err_str(err) << "; continuing with default handle." << std::endl; m_stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE); } startup_info.hStdOutput = m_stdout_handle; // Second, set up redirection for stderr. ret = DuplicateHandle( GetCurrentProcess(), GetStdHandle(STD_ERROR_HANDLE), GetCurrentProcess(), &m_stderr_handle, 0, TRUE, DUPLICATE_SAME_ACCESS); if(!ret) { auto err = GetLastError(); logstream(LOG_WARNING) << "Failed to duplicate stderr file handle: " << get_last_err_str(err) << "; continuing with default handle." << std::endl; m_stderr_handle = GetStdHandle(STD_ERROR_HANDLE); } startup_info.hStdError = m_stderr_handle; // For Windows, we include the cmd with the arguments so that a search path // is used for any executable without a full path ret = CreateProcess(NULL, c_arglist, // command line NULL, // process security attributes NULL, // primary thread security attributes TRUE, // handles are inherited CREATE_NO_WINDOW, // creation flags NULL, // use parent's environment NULL, // use parent's current directory &startup_info, // use parent's current directory &proc_info); // use parent's current directory if(!ret) { auto err = GetLastError(); logstream(LOG_ERROR) << "Failed to launch process: " << get_last_err_str(err) << std::endl; delete[] c_arglist; return false; } else { // Don't need to have a thread handle. We'll just cancel the process if // need be. CloseHandle(proc_info.hThread); m_launched = true; } // Used for killing the process m_proc_handle = proc_info.hProcess; m_pid = proc_info.dwProcessId; logstream(LOG_INFO) << "Launched process with pid: " << m_pid << std::endl; // Wait up to 100 milliseconds before querying the process for it's status. DWORD dwMillisec = 100; DWORD dwWaitStatus = WaitForSingleObject(m_proc_handle, dwMillisec ); if(dwWaitStatus == WAIT_FAILED) { auto err = GetLastError(); logstream(LOG_WARNING) << "Error in WaitForSingleObject after CreateProcess: " << get_last_err_str(err) << std::endl; } // Query the status of the process. Will return STILL_ACTIVE if all // is good. DWORD potential_exit_code; ret = GetExitCodeProcess(m_proc_handle, &potential_exit_code); if(!ret) { auto err = GetLastError(); logstream(LOG_WARNING) << "Error querying process status code: " << get_last_err_str(err) << std::endl; } logstream(LOG_INFO) << "Process status of " << m_pid << " = " << potential_exit_code << std::endl; if(potential_exit_code != STILL_ACTIVE) { logstream(LOG_ERROR) << "Launched process " << m_pid << " exited immediately with error code " << potential_exit_code << std::endl; return false; } return true; }
bool process::popen(const std::string &cmd, const std::vector<std::string> &args, int child_write_fd) { // We will only support stdout and stderr in Windows if(child_write_fd != STDOUT_FILENO && child_write_fd != STDERR_FILENO) { logstream(LOG_ERROR) << "Cannot read anything other than stdout or stderr " "from child on Windows." << std::endl; return false; } SECURITY_ATTRIBUTES sa_attr; sa_attr.nLength=sizeof(SECURITY_ATTRIBUTES); // Allow handles to be inherited when process created sa_attr.bInheritHandle = TRUE; sa_attr.lpSecurityDescriptor = NULL; if(!CreatePipe(&m_read_handle, &m_write_handle, &sa_attr, 0)) { //TODO: Figure out how to get error string on Windows logstream(LOG_ERROR) << "Failed to create pipe: " << get_last_err_str(GetLastError()) << std::endl; return false; } // Make sure the parent end of the pipe is NOT inherited if(!SetHandleInformation(m_read_handle, HANDLE_FLAG_INHERIT, 0)) { logstream(LOG_ERROR) << "Failed to set handle information: " << get_last_err_str(GetLastError()) << std::endl; return false; } PROCESS_INFORMATION proc_info; STARTUPINFO startup_info; ZeroMemory(&proc_info, sizeof(PROCESS_INFORMATION)); ZeroMemory(&startup_info, sizeof(STARTUPINFO)); if(m_read_handle != NULL) { startup_info.cb = sizeof(STARTUPINFO); if(child_write_fd == STDOUT_FILENO) { startup_info.hStdOutput = m_write_handle; } else if(child_write_fd == STDERR_FILENO) { startup_info.hStdError = m_write_handle; } startup_info.dwFlags |= STARTF_USESTDHANDLES; } else { logstream(LOG_ERROR) << "Read handle NULL after pipe created." << std::endl; return false; } char *c_arglist = convert_args(cmd, args); BOOL ret; // For Windows, we include the cmd with the arguments so that a search path // is used for any executable without a full path ret = CreateProcess(NULL, c_arglist, NULL, NULL, TRUE, 0, NULL, NULL, &startup_info, &proc_info); if(!ret) { auto err = GetLastError(); logstream(LOG_ERROR) << "Failed to launch process: " << get_last_err_str(err) << std::endl; delete[] c_arglist; return false; } else { // Don't need to have a thread handle. We'll just cancel the process if // need be CloseHandle(proc_info.hThread); // Now that the process has been created, close the handle that was // inherited by the child. Apparently if you DON'T do this, reading from // the child will never report an error when the child is done writing, and // you'll hang forever waiting for an EOF. There goes a few hours of my // life. CloseHandle(m_write_handle); m_write_handle = NULL; m_launched = TRUE; m_launched_with_popen = TRUE; } // Used for killing the process m_proc_handle = proc_info.hProcess; m_pid = proc_info.dwProcessId; logstream(LOG_INFO) << "Launched process with pid: " << m_pid << std::endl; return true; }
std::string unity_global::load_toolkit(std::string soname, std::string module_subpath) { // rewrite "local" protocol std::string protocol = fileio::get_protocol(soname); if (protocol == "local") { soname = fileio::remove_protocol(soname); } so_registration_list regentry; regentry.original_soname = soname; logstream(LOG_INFO) << "Attempt loading of " << sanitize_url(soname) << std::endl; // see if the file exists and whether we need to donwnload it if (fileio::try_to_open_file(soname) == false) { return "Unable to open file " + sanitize_url(soname); } if (protocol != "") { // there is a protocol associated. We need to copy this file to local // issue a copy to copy it to the local temp directory std::string tempname = get_temp_name(); fileio::copy(soname, tempname); soname = tempname; } if (!file_contains_substring(soname, "get_toolkit_function_registration") && !file_contains_substring(soname, "get_toolkit_class_registration")) { return soname + " is not a valid extension"; } // get the base name of the shared library (without the .so) std::string modulename = fileio::get_filename(regentry.original_soname); std::vector<std::string> split_names; boost::algorithm::split(split_names, modulename, boost::is_any_of(".")); if (split_names.size() == 0) return "Invalid filename"; if (module_subpath.empty()) { regentry.modulename = split_names[0]; } else if (module_subpath == "..") { regentry.modulename = ""; } else { regentry.modulename = module_subpath + "." + split_names[0]; } // goody. now for the dl loading #ifndef _WIN32 void* dl = dlopen(soname.c_str(), RTLD_NOW | RTLD_LOCAL); #else void *dl = (void *)LoadLibrary(soname.c_str()); #endif logstream(LOG_INFO) << "Library load of " << sanitize_url(soname) << std::endl; regentry.effective_soname = soname; regentry.dl = dl; // check for failure if (dl == NULL) { #ifndef _WIN32 char* err = dlerror(); // I think we need to copy this out early std::string ret = err; logstream(LOG_ERROR) << "Unable to load " << sanitize_url(soname) << ": " << ret << std::endl; if (err) return ret; else return "dlopen failed due to an unknown error"; #else std::string ret = get_last_err_str(GetLastError()); logstream(LOG_ERROR) << "Unable to load " << sanitize_url(soname) << ": " << ret << std::endl; if (!ret.empty()) return ret; else return "LoadLibrary failed due to an unknown error"; #endif } /**************************************************************************/ /* */ /* Function Registration */ /* */ /**************************************************************************/ // get the registration symbols std::vector<std::string> toolkit_function_reg_names {"get_toolkit_function_registration", "_Z33get_toolkit_function_registrationv", "__Z33get_toolkit_function_registrationv"}; get_toolkit_function_registration_type get_toolkit_function_registration = nullptr; for (auto reg_name : toolkit_function_reg_names) { get_toolkit_function_registration = reinterpret_cast<get_toolkit_function_registration_type> ( #ifndef _WIN32 dlsym(dl, reg_name.c_str()) #else (void *)GetProcAddress((HMODULE)dl, reg_name.c_str()) #endif ); if (get_toolkit_function_registration != nullptr) break; } // register functions if (get_toolkit_function_registration) { auto functions = (*get_toolkit_function_registration)(); for (auto& fn: functions) { if (!regentry.modulename.empty()) { fn.name = regentry.modulename + "." + fn.name; } fn.description["file"] = regentry.original_soname; logstream(LOG_INFO) << "Adding function: " << fn.name << std::endl; regentry.functions.push_back(fn.name); } toolkit_functions->register_toolkit_function(functions); } /**************************************************************************/ /* */ /* Class Registration */ /* */ /**************************************************************************/ std::vector<std::string> toolkit_class_reg_names {"get_toolkit_class_registration", "_Z30get_toolkit_class_registrationv", "__Z30get_toolkit_class_registrationv"}; get_toolkit_class_registration_type get_toolkit_class_registration = nullptr; for (auto reg_name : toolkit_class_reg_names) { get_toolkit_class_registration = reinterpret_cast<get_toolkit_class_registration_type> ( #ifndef _WIN32 dlsym(dl, reg_name.c_str()) #else (void *)GetProcAddress((HMODULE)dl, reg_name.c_str()) #endif ); if (get_toolkit_class_registration != nullptr) break; } // register classes if (get_toolkit_class_registration) { auto class_reg = (*get_toolkit_class_registration)(); for (auto& cl: class_reg) { if (!regentry.modulename.empty()) { cl.name = regentry.modulename + "." + cl.name; } cl.description["file"] = regentry.original_soname; logstream(LOG_INFO) << "Adding class : " << cl.name << std::endl; regentry.functions.push_back(cl.name); } classes->register_toolkit_class(class_reg); } if (regentry.functions.empty() && regentry.classes.empty()) { // nothing has been registered! unload the dl #ifndef _WIN32 dlclose(dl); #else FreeLibrary((HMODULE)dl); #endif return "No functions or classes registered by " + sanitize_url(soname); } // note that it is possible to load a toolkit multiple times. // It is not safe to unload previously loaded toolkits since I may have // a reference to it (for instance a class). We just keep loading over // and hope for the best. // store and remember the dlhandle and what was registered; dynamic_loaded_toolkits[regentry.original_soname] = regentry; return std::string(); }