cdk/extra/process_launcher/process_launcher.cc (394 lines of code) (raw):

/* * Copyright (c) 2014, 2024, Oracle and/or its affiliates. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2.0, as * published by the Free Software Foundation. * * This program is designed to work with certain software (including * but not limited to OpenSSL) that is licensed under separate terms, as * designated in a particular file or component or in included license * documentation. The authors of MySQL hereby grant you an additional * permission to link the program and your derivative works with the * separately licensed software that they have either included with * the program or referenced in the documentation. * * Without limiting anything contained in the foregoing, this file, * which is part of Connector/C++, is also subject to the * Universal FOSS Exception, version 1.0, a copy of which can be found at * https://oss.oracle.com/licenses/universal-foss-exception. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU General Public License, version 2.0, for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "process_launcher.h" #include "exception.h" #include <string> #include <sstream> #ifdef WIN32 # include <windows.h> # include <tchar.h> # include <stdio.h> #else # include <stdio.h> # include <unistd.h> # include <sys/types.h> # include <stdlib.h> # include <string.h> # include <sys/wait.h> # include <string.h> # include <poll.h> # include <errno.h> # include <signal.h> # include <fcntl.h> #endif #ifdef LINUX # include <sys/prctl.h> #endif using namespace ngcommon; #ifdef WIN32 void Process_launcher::start() { SECURITY_ATTRIBUTES saAttr; saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); saAttr.bInheritHandle = TRUE; saAttr.lpSecurityDescriptor = NULL; if (!CreatePipe(&child_out_rd, &child_out_wr, &saAttr, 0)) report_error("Failed to create child_out_rd"); if (!SetHandleInformation(child_out_rd, HANDLE_FLAG_INHERIT, 0)) report_error("Failed to create child_out_rd"); // force non blocking IO in Windows DWORD mode = PIPE_NOWAIT; //BOOL res = SetNamedPipeHandleState(child_out_rd, &mode, NULL, NULL); if (!CreatePipe(&child_in_rd, &child_in_wr, &saAttr, 0)) report_error("Failed to create child_in_rd"); if (!SetHandleInformation(child_in_wr, HANDLE_FLAG_INHERIT, 0)) report_error("Failed to created child_in_wr"); // Create Process std::string s = this->cmd_line; const char **pc = args; while (*++pc != NULL) { s += " "; s += *pc; } char *sz_cmd_line = ( char *)malloc(s.length() + 1); if (!sz_cmd_line) report_error("Cannot assign memory for command line in Process_launcher::start"); _tcscpy(sz_cmd_line, s.c_str()); BOOL bSuccess = FALSE; ZeroMemory(&pi, sizeof(PROCESS_INFORMATION)); ZeroMemory(&si, sizeof(STARTUPINFO)); si.cb = sizeof(STARTUPINFO); if (redirect_stderr) si.hStdError = child_out_wr; si.hStdOutput = child_out_wr; si.hStdInput = child_in_rd; si.dwFlags |= STARTF_USESTDHANDLES; bSuccess = CreateProcess( NULL, // lpApplicationName sz_cmd_line, // lpCommandLine NULL, // lpProcessAttributes NULL, // lpThreadAttributes TRUE, // bInheritHandles 0, // dwCreationFlags NULL, // lpEnvironment NULL, // lpCurrentDirectory &si, // lpStartupInfo &pi); // lpProcessInformation if (!bSuccess) report_error(NULL); CloseHandle(child_out_wr); CloseHandle(child_in_rd); //DWORD res1 = WaitForInputIdle(pi.hProcess, 100); //res1 = WaitForSingleObject(pi.hThread, 100); free(sz_cmd_line); } uint64_t Process_launcher::get_pid() { return (uint64_t)pi.hProcess; } int Process_launcher::wait() { DWORD dwExit = 0; if (GetExitCodeProcess(pi.hProcess, &dwExit)) { if (dwExit == STILL_ACTIVE) { WaitForSingleObject(pi.hProcess, INFINITE); } } else { DWORD dwError = GetLastError(); if (dwError != ERROR_INVALID_HANDLE) // not closed already? report_error(NULL); } return dwExit; } void Process_launcher::close() { DWORD dwExit; if (GetExitCodeProcess(pi.hProcess, &dwExit)) { if (dwExit == STILL_ACTIVE) { if (!TerminateProcess(pi.hProcess, 0)) report_error(NULL); // TerminateProcess is async, wait for process to end. WaitForSingleObject(pi.hProcess, INFINITE); } } else { report_error(NULL); } if (!CloseHandle(pi.hProcess)) report_error(NULL); if (!CloseHandle(pi.hThread)) report_error(NULL); if (!CloseHandle(child_out_rd)) report_error(NULL); if (!CloseHandle(child_in_wr)) report_error(NULL); is_alive = false; } int Process_launcher::read_one_char() { char buf[1]; BOOL bSuccess = FALSE; DWORD dwBytesRead, dwCode; while (!(bSuccess = ReadFile(child_out_rd, buf, 1, &dwBytesRead, NULL))) { dwCode = GetLastError(); if (dwCode == ERROR_NO_DATA) continue; if (dwCode == ERROR_BROKEN_PIPE) return EOF; else report_error(NULL); } return buf[0]; } int Process_launcher::read(char *buf, size_t count) { BOOL bSuccess = FALSE; DWORD dwBytesRead, dwCode; int i = 0; while (!(bSuccess = ReadFile(child_out_rd, buf, count, &dwBytesRead, NULL))) { dwCode = GetLastError(); if (dwCode == ERROR_NO_DATA) continue; if (dwCode == ERROR_BROKEN_PIPE) return EOF; else report_error(NULL); } return dwBytesRead; } int Process_launcher::write_one_char(int c) { CHAR buf[1]; BOOL bSuccess = FALSE; DWORD dwBytesWritten; bSuccess = WriteFile(child_in_wr, buf, 1, &dwBytesWritten, NULL); if (!bSuccess) { if (GetLastError() != ERROR_NO_DATA) // otherwise child process just died. report_error(NULL); } else { return 1; } return 0; // so the compiler does not cry } int Process_launcher::write(const char *buf, size_t count) { DWORD dwBytesWritten; BOOL bSuccess = FALSE; bSuccess = WriteFile(child_in_wr, buf, count, &dwBytesWritten, NULL); if (!bSuccess) { if (GetLastError() != ERROR_NO_DATA) // otherwise child process just died. report_error(NULL); } else { // When child input buffer is full, this returns zero in NO_WAIT mode. return dwBytesWritten; } return 0; // so the compiler does not cry } void Process_launcher::report_error(const char *msg) { DWORD dwCode = GetLastError(); LPTSTR lpMsgBuf; if (msg != NULL) { throw Exception::runtime_error(msg); } else { FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, dwCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&lpMsgBuf, 0, NULL); std::ostringstream msgerr; msgerr << "SystemError: "; msgerr << lpMsgBuf; msgerr << " with error code " << dwCode << "."; throw Exception::runtime_error(msgerr.str()); } } uint64_t Process_launcher::get_fd_write() { return (uint64_t)child_in_wr; } uint64_t Process_launcher::get_fd_read() { return (uint64_t)child_out_rd; } #else void Process_launcher::start() { if( pipe(fd_in) < 0 ) { report_error(NULL); } if( pipe(fd_out) < 0 ) { report_error(NULL); } // Ignore broken pipe signal signal(SIGPIPE, SIG_IGN); childpid = fork(); if(childpid == -1) { report_error(NULL); } if(childpid == 0) { #ifdef LINUX prctl(PR_SET_PDEATHSIG, SIGHUP); #endif ::close(fd_out[0]); ::close(fd_in[1]); while( dup2(fd_out[1], STDOUT_FILENO) == -1 ) { if(errno == EINTR) continue; else report_error(NULL); } if(redirect_stderr) { while( dup2(fd_out[1], STDERR_FILENO) == -1 ) { if(errno == EINTR) continue; else report_error(NULL); } } while( dup2(fd_in[0], STDIN_FILENO) == -1 ) { if(errno == EINTR) continue; else report_error(NULL); } fcntl(fd_out[1], F_SETFD, FD_CLOEXEC); fcntl(fd_in[0], F_SETFD, FD_CLOEXEC); execvp(cmd_line, (char * const *)args); // if exec returns, there is an error. // TODO: Use explain_execvp if available exit(errno); } else { // int status; ::close(fd_out[1]); ::close(fd_in[0]); /* _s_pollfd[0].fd = fd_out[0]; _s_pollfd[0].events = POLLIN; _s_pollfd[1].fd = fd_in[1]; _s_pollfd[1].events = POLLOUT; */ } } void Process_launcher::close() { if(::kill(childpid, SIGTERM) < 0 && errno != ESRCH) report_error(NULL); if(errno != ESRCH) { sleep(1); if(::kill(childpid, SIGKILL) < 0 && errno != ESRCH) report_error(NULL); } /* while(::close(fd_out[0]) == -1) { if(errno == EINTR) continue; else report_error(NULL); } while(::close(fd_in[1]) == -1) { if( errno == EINTR ) continue; else report_error(NULL); }*/ ::close(fd_out[0]); ::close(fd_in[1]); is_alive = false; } int Process_launcher::read_one_char() { int c; do { if((c = ::read(fd_out[0], &c, 1)) >= 0) return c; if(errno == EAGAIN) continue; if(errno == EPIPE) return 0; if(errno) report_error(NULL); } while(true); return 0; } int Process_launcher::read(char *buf, size_t count) { int n; do { if((n = ::read(fd_out[0], buf, count)) >= 0) return n; if(errno == EAGAIN) continue; if(errno == EPIPE) return 0; if(errno) report_error(NULL); } while(true); return 0; } int Process_launcher::write_one_char(int c) { if(::write(fd_in[1], &c, 1) < 0) return 1; if(errno == EPIPE) return 0; report_error(NULL); return 0; } int Process_launcher::write(const char *buf, size_t count) { int n; if((n = ::write(fd_in[1], buf, count)) >= 0) return n; if(errno == EPIPE) return 0; report_error(NULL); return 0; } void Process_launcher::report_error(const char *msg) { char sys_err[ 64 ]; int errnum = errno; if(msg == NULL) { strerror_r(errno, sys_err, sizeof(sys_err)); std::ostringstream msgerr; msgerr << "SystemError: "; msgerr << sys_err; msgerr << " with error code " << errnum << "."; throw Exception::runtime_error(msgerr.str()); } else { throw Exception::runtime_error(msg); } } uint64_t Process_launcher::get_pid() { return (uint64_t)childpid; } /* * Waits for the child process to finish. * throws an error if the wait fails, or the child return code if wait syscall does not fail. */ int Process_launcher::wait() { int status; int exited; int exitstatus; pid_t ret; do { ret = ::wait(&status); exited = WIFEXITED(status); exitstatus = WEXITSTATUS(status); if(ret == -1) { if(errno == ECHILD) break; // no children left if((exited == 0) || (exitstatus != 0)) { report_error(NULL); } } } while(ret == -1); return exitstatus; } uint64_t Process_launcher::get_fd_write() { return (uint64_t)fd_in[1]; } uint64_t Process_launcher::get_fd_read() { return (uint64_t)fd_out[0]; } #endif void Process_launcher::kill() { close(); }