]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
common: implement win32 subprocess helpers
authorLucian Petrut <lpetrut@cloudbasesolutions.com>
Thu, 7 Nov 2019 18:12:48 +0000 (18:12 +0000)
committerLucian Petrut <lpetrut@cloudbasesolutions.com>
Mon, 31 Aug 2020 12:11:55 +0000 (12:11 +0000)
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 <lpetrut@cloudbasesolutions.com>
src/common/CMakeLists.txt
src/common/SubProcess.cc
src/common/SubProcess.h
src/common/SubProcess_win32.cc [new file with mode: 0644]
src/common/run_cmd.cc

index 470ad46234a3a0b29a9fc9242ed023a49dec96e6..66cadf782bb9b3472699d4be2e378e86e56bc2bb 100644 (file)
@@ -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
index ab4ecd180210afb89b46b8122632025babcd1a4a..1faf33e36eeeb45af57068185edeacc2407bd10a 100644 (file)
@@ -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 */
index 829749284fc6b1fbe0e6cd8527d2ddc19575f12b..ea81000d6f3a1af4d2453a8cdf61663fcc4bf3e7 100644 (file)
@@ -27,6 +27,8 @@
 #include <sstream>
 #include <vector>
 
+#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<std::string> 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 (file)
index 0000000..0e4d13d
--- /dev/null
@@ -0,0 +1,293 @@
+#include "SubProcess.h"
+
+#include <stdarg.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <iostream>
+#include <iomanip>
+
+#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<char*>(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() {
+}
index a84f68aeaed42ebbfed13b22ebbd2e872f0514a0..855b6e537b7826ed94df5e23f53f1296b5fb3808 100644 (file)
 
 #include "common/errno.h"
 
+#ifndef _WIN32
 #include <sstream>
 #include <stdarg.h>
 #include <sys/wait.h>
 #include <unistd.h>
 #include <vector>
+#else
+#include "common/SubProcess.h"
+#endif /* _WIN32 */
 
 using std::ostringstream;
 
+#ifndef _WIN32
 std::string run_cmd(const char *cmd, ...)
 {
   std::vector <const char *> 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 */