From: Lucian Petrut Date: Thu, 7 Nov 2019 18:12:48 +0000 (+0000) Subject: common: implement win32 subprocess helpers X-Git-Tag: v16.1.0~1226^2~3 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=be155684b8d554fc4adb1cd741517652f92485a9;p=ceph.git common: implement win32 subprocess helpers This change adds a Windows implementation for the SubProcess class. We're also implementing "run_command" for Windows, re-using the SubProcess class. It might be seen as just a convenience wrapper on top of that. At the moment, the Linux bits are left unchanged, although the same implementation might be used for all platforms. Signed-off-by: Lucian Petrut --- diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 470ad46234a3..66cadf782bb9 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -33,7 +33,6 @@ set(common_srcs Readahead.cc RefCountedObj.cc SloppyCRCMap.cc - SubProcess.cc Thread.cc Throttle.cc Timer.cc @@ -89,6 +88,7 @@ set(common_srcs pick_address.cc random_string.cc reverse.c + run_cmd.cc scrub_types.cc shared_mutex_debug.cc signal.cc @@ -106,14 +106,15 @@ set(common_srcs if(WIN32) list(APPEND common_srcs blkdev_win32.cc - dns_resolve_win32.cc) + dns_resolve_win32.cc + SubProcess_win32.cc) else() list(APPEND common_srcs blkdev.cc dns_resolve.cc ipaddr.cc linux_version.c - run_cmd.cc) + SubProcess.cc) endif() set_source_files_properties(${CMAKE_SOURCE_DIR}/src/common/version.cc diff --git a/src/common/SubProcess.cc b/src/common/SubProcess.cc index ab4ecd180210..1faf33e36eee 100644 --- a/src/common/SubProcess.cc +++ b/src/common/SubProcess.cc @@ -102,7 +102,6 @@ void SubProcess::close_stderr() { close(stderr_pipe_in_fd); } -#ifndef _WIN32 void SubProcess::kill(int signo) const { ceph_assert(is_spawned()); @@ -274,7 +273,6 @@ int SubProcess::join() { errstr << cmd << ": waitpid: unknown status returned\n"; return EXIT_FAILURE; } -#endif /* _WIN32 */ SubProcessTimed::SubProcessTimed(const char *cmd, std_fd_op stdin_op, std_fd_op stdout_op, std_fd_op stderr_op, @@ -290,7 +288,6 @@ void timeout_sighandler(int sig) { } static void dummy_sighandler(int sig) {} -#ifndef _WIN32 void SubProcessTimed::exec() { ceph_assert(is_child()); @@ -395,22 +392,3 @@ void SubProcessTimed::exec() { fail_exit: _exit(EXIT_FAILURE); } - -#else -int SubProcess::join() { - return EXIT_FAILURE; -} - -void SubProcess::kill(int signo) const { -} - -int SubProcess::spawn() { - return EXIT_FAILURE; -} - -void SubProcess::exec() { -} - -void SubProcessTimed::exec() { -} -#endif /* _WIN32 */ diff --git a/src/common/SubProcess.h b/src/common/SubProcess.h index 829749284fc6..ea81000d6f3a 100644 --- a/src/common/SubProcess.h +++ b/src/common/SubProcess.h @@ -27,6 +27,8 @@ #include #include +#include "include/compat.h" + /** * SubProcess: * A helper class to spawn a subprocess. @@ -85,9 +87,12 @@ protected: bool is_child() const { return pid == 0; } virtual void exec(); -private: void close(int &fd); +#ifdef _WIN32 + void close_h(HANDLE &handle); +#endif + protected: std::string cmd; std::vector cmd_args; @@ -99,6 +104,10 @@ protected: int stderr_pipe_in_fd; int pid; std::ostringstream errstr; + +#ifdef _WIN32 + HANDLE proc_handle = INVALID_HANDLE_VALUE; +#endif }; class SubProcessTimed : public SubProcess { @@ -107,12 +116,21 @@ public: std_fd_op stdout_op = CLOSE, std_fd_op stderr_op = CLOSE, int timeout = 0, int sigkill = SIGKILL); +#ifdef _WIN32 + int spawn() override; + int join() override; +#endif + protected: void exec() override; private: int timeout; int sigkill; + +#ifdef _WIN32 + std::thread waiter; +#endif }; void timeout_sighandler(int sig); diff --git a/src/common/SubProcess_win32.cc b/src/common/SubProcess_win32.cc new file mode 100644 index 000000000000..0e4d13dd71a6 --- /dev/null +++ b/src/common/SubProcess_win32.cc @@ -0,0 +1,293 @@ +#include "SubProcess.h" + +#include +#include +#include +#include +#include + +#include "common/errno.h" +#include "include/ceph_assert.h" +#include "include/compat.h" + +SubProcess::SubProcess(const char *cmd_, std_fd_op stdin_op_, std_fd_op stdout_op_, std_fd_op stderr_op_) : + cmd(cmd_), + cmd_args(), + stdin_op(stdin_op_), + stdout_op(stdout_op_), + stderr_op(stderr_op_), + stdin_pipe_out_fd(-1), + stdout_pipe_in_fd(-1), + stderr_pipe_in_fd(-1), + pid(0), + errstr() { +} + +SubProcess::~SubProcess() { + ceph_assert(!is_spawned()); + ceph_assert(stdin_pipe_out_fd == -1); + ceph_assert(stdout_pipe_in_fd == -1); + ceph_assert(stderr_pipe_in_fd == -1); +} + +void SubProcess::add_cmd_args(const char *arg, ...) { + ceph_assert(!is_spawned()); + + va_list ap; + va_start(ap, arg); + const char *p = arg; + do { + add_cmd_arg(p); + p = va_arg(ap, const char*); + } while (p != NULL); + va_end(ap); +} + +void SubProcess::add_cmd_arg(const char *arg) { + ceph_assert(!is_spawned()); + + cmd_args.push_back(arg); +} + +int SubProcess::get_stdin() const { + ceph_assert(is_spawned()); + ceph_assert(stdin_op == PIPE); + + return stdin_pipe_out_fd; +} + +int SubProcess::get_stdout() const { + ceph_assert(is_spawned()); + ceph_assert(stdout_op == PIPE); + + return stdout_pipe_in_fd; +} + +int SubProcess::get_stderr() const { + ceph_assert(is_spawned()); + ceph_assert(stderr_op == PIPE); + + return stderr_pipe_in_fd; +} + +void SubProcess::close(int &fd) { + if (fd == -1) + return; + + ::close(fd); + fd = -1; +} + +void SubProcess::close_stdin() { + ceph_assert(is_spawned()); + ceph_assert(stdin_op == PIPE); + + close(stdin_pipe_out_fd); +} + +void SubProcess::close_stdout() { + ceph_assert(is_spawned()); + ceph_assert(stdout_op == PIPE); + + close(stdout_pipe_in_fd); +} + +void SubProcess::close_stderr() { + ceph_assert(is_spawned()); + ceph_assert(stderr_op == PIPE); + + close(stderr_pipe_in_fd); +} + +const std::string SubProcess::err() const { + return errstr.str(); +} + +SubProcessTimed::SubProcessTimed(const char *cmd, std_fd_op stdin_op, + std_fd_op stdout_op, std_fd_op stderr_op, + int timeout_, int sigkill_) : + SubProcess(cmd, stdin_op, stdout_op, stderr_op), + timeout(timeout_), + sigkill(sigkill_) { +} + +static bool timedout = false; +void timeout_sighandler(int sig) { + timedout = true; +} + +void SubProcess::close_h(HANDLE &handle) { + if (handle == INVALID_HANDLE_VALUE) + return; + + CloseHandle(handle); + handle = INVALID_HANDLE_VALUE; +} + +int SubProcess::join() { + ceph_assert(is_spawned()); + + close(stdin_pipe_out_fd); + close(stdout_pipe_in_fd); + close(stderr_pipe_in_fd); + + DWORD status = 0; + + if (WaitForSingleObject(proc_handle, INFINITE) != WAIT_FAILED) { + if (!GetExitCodeProcess(proc_handle, &status)) { + errstr << cmd << ": Could not get exit code: " << pid + << ". Error code: " << GetLastError(); + } else if (status) { + errstr << cmd << ": exit status: " << status; + } + } else { + errstr << cmd << ": Waiting for child process failed: " << pid + << ". Error code: " << GetLastError(); + } + + close_h(proc_handle); + pid = 0; + return status; +} + +void SubProcess::kill(int signo) const { + ceph_assert(is_spawned()); + ceph_assert(TerminateProcess(proc_handle, 128 + SIGTERM)); +} + +int SubProcess::spawn() { + std::ostringstream cmdline; + cmdline << cmd; + for (auto& arg : cmd_args) { + cmdline << " " << std::quoted(arg); + } + + STARTUPINFO si = {0}; + PROCESS_INFORMATION pi = {0}; + SECURITY_ATTRIBUTES sa = {0}; + + sa.nLength = sizeof(SECURITY_ATTRIBUTES); + sa.bInheritHandle = TRUE; + sa.lpSecurityDescriptor = NULL; + + HANDLE stdin_r = INVALID_HANDLE_VALUE, stdin_w = INVALID_HANDLE_VALUE, + stdout_r = INVALID_HANDLE_VALUE, stdout_w = INVALID_HANDLE_VALUE, + stderr_r = INVALID_HANDLE_VALUE, stderr_w = INVALID_HANDLE_VALUE; + + if ((stdin_op == PIPE && !CreatePipe(&stdin_r, &stdin_w, &sa, 0)) || + (stdout_op == PIPE && !CreatePipe(&stdout_r, &stdout_w, &sa, 0)) || + (stderr_op == PIPE && !CreatePipe(&stderr_r, &stderr_w, &sa, 0))) { + errstr << cmd << ": CreatePipe failed: " << GetLastError(); + return -1; + } + + // The following handles will be used by the parent process and + // must be marked as non-inheritable. + if ((stdin_op == PIPE && !SetHandleInformation(stdin_w, HANDLE_FLAG_INHERIT, 0)) || + (stdout_op == PIPE && !SetHandleInformation(stdout_r, HANDLE_FLAG_INHERIT, 0)) || + (stderr_op == PIPE && !SetHandleInformation(stderr_r, HANDLE_FLAG_INHERIT, 0))) { + errstr << cmd << ": SetHandleInformation failed: " + << GetLastError(); + goto fail; + } + + si.cb = sizeof(STARTUPINFO); + si.hStdInput = stdin_op == KEEP ? GetStdHandle(STD_INPUT_HANDLE) : stdin_r; + si.hStdOutput = stdout_op == KEEP ? GetStdHandle(STD_OUTPUT_HANDLE) : stdout_w; + si.hStdError = stderr_op == KEEP ? GetStdHandle(STD_ERROR_HANDLE) : stderr_w; + si.dwFlags |= STARTF_USESTDHANDLES; + + stdin_pipe_out_fd = stdin_op == PIPE ? _open_osfhandle((intptr_t)stdin_w, 0) : -1; + stdout_pipe_in_fd = stdout_op == PIPE ? _open_osfhandle((intptr_t)stdout_r, _O_RDONLY) : - 1; + stderr_pipe_in_fd = stderr_op == PIPE ? _open_osfhandle((intptr_t)stderr_r, _O_RDONLY) : -1; + + if (stdin_op == PIPE && stdin_pipe_out_fd == -1 || + stdout_op == PIPE && stdout_pipe_in_fd == -1 || + stderr_op == PIPE && stderr_pipe_in_fd == -1) { + errstr << cmd << ": _open_osfhandle failed: " << GetLastError(); + goto fail; + } + + // We've transfered ownership from those handles. + stdin_w = stdout_r = stderr_r = INVALID_HANDLE_VALUE; + + if (!CreateProcess( + NULL, const_cast(cmdline.str().c_str()), + NULL, NULL, /* No special security attributes */ + 1, /* Inherit handles marked as inheritable */ + 0, /* No special flags */ + NULL, /* Use the same environment variables */ + NULL, /* use the same cwd */ + &si, &pi)) { + errstr << cmd << ": CreateProcess failed: " << GetLastError(); + goto fail; + } + + proc_handle = pi.hProcess; + pid = GetProcessId(proc_handle); + if (!pid) { + errstr << cmd << ": Could not get child process id."; + goto fail; + } + + // The following are used by the subprocess. + CloseHandle(stdin_r); + CloseHandle(stdout_w); + CloseHandle(stderr_w); + CloseHandle(pi.hThread); + return 0; + +fail: + // fd copies + close(stdin_pipe_out_fd); + close(stdout_pipe_in_fd); + close(stderr_pipe_in_fd); + + // the original handles + close_h(stdin_r); + close_h(stdin_w); + close_h(stdout_r); + close_h(stdout_w); + close_h(stderr_r); + close_h(stderr_w); + + // We may consider mapping some of the Windows errors. + return -1; +} + +void SubProcess::exec() { +} + +int SubProcessTimed::spawn() { + if (auto ret = SubProcess::spawn(); ret < 0) { + return ret; + } + + if (timeout > 0) { + waiter = std::thread([&](){ + DWORD wait_status = WaitForSingleObject(proc_handle, timeout * 1000); + ceph_assert(wait_status != WAIT_FAILED); + if (wait_status == WAIT_TIMEOUT) { + // 128 + sigkill is just the return code, which is expected by + // the unit tests and possibly by other code. We can't pick a + // termination signal unless we use window events. + ceph_assert(TerminateProcess(proc_handle, 128 + sigkill)); + timedout = 1; + } + }); + } + return 0; +} + +int SubProcessTimed::join() { + ceph_assert(is_spawned()); + + if (waiter.joinable()) { + waiter.join(); + } + + return SubProcess::join();; +} + +void SubProcessTimed::exec() { +} diff --git a/src/common/run_cmd.cc b/src/common/run_cmd.cc index a84f68aeaed4..855b6e537b78 100644 --- a/src/common/run_cmd.cc +++ b/src/common/run_cmd.cc @@ -14,14 +14,19 @@ #include "common/errno.h" +#ifndef _WIN32 #include #include #include #include #include +#else +#include "common/SubProcess.h" +#endif /* _WIN32 */ using std::ostringstream; +#ifndef _WIN32 std::string run_cmd(const char *cmd, ...) { std::vector arr; @@ -78,3 +83,25 @@ std::string run_cmd(const char *cmd, ...) oss << "run_cmd(" << cmd << "): terminated by unknown mechanism"; return oss.str(); } +#else +std::string run_cmd(const char *cmd, ...) +{ + SubProcess p(cmd, SubProcess::CLOSE, SubProcess::PIPE, SubProcess::CLOSE); + + va_list ap; + va_start(ap, cmd); + const char *c = cmd; + c = va_arg(ap, const char*); + while (c != NULL) { + p.add_cmd_arg(c); + c = va_arg(ap, const char*); + } + va_end(ap); + + if (p.spawn() == 0) { + p.join(); + } + + return p.err(); +} +#endif /* _WIN32 */