process.cpp raw

   1  // Copyright (c) 2021-2022 The Limenka developers
   2  // Distributed under the MIT software license, see the accompanying
   3  // file COPYING or http://www.opensource.org/licenses/mit-license.php.
   4  
   5  #include <ipc/process.h>
   6  #include <ipc/protocol.h>
   7  #include <logging.h>
   8  #include <mp/util.h>
   9  #include <tinyformat.h>
  10  #include <util/fs.h>
  11  #include <util/strencodings.h>
  12  #include <util/syserror.h>
  13  
  14  #include <cstdint>
  15  #include <cstdlib>
  16  #include <errno.h>
  17  #include <exception>
  18  #include <iostream>
  19  #include <stdexcept>
  20  #include <string.h>
  21  #include <sys/socket.h>
  22  #include <sys/un.h>
  23  #include <unistd.h>
  24  #include <utility>
  25  #include <vector>
  26  
  27  using util::RemovePrefixView;
  28  
  29  namespace ipc {
  30  namespace {
  31  class ProcessImpl : public Process
  32  {
  33  public:
  34      int spawn(const std::string& new_exe_name, const fs::path& argv0_path, int& pid) override
  35      {
  36          return mp::SpawnProcess(pid, [&](int fd) {
  37              fs::path path = argv0_path;
  38              path.remove_filename();
  39              path /= fs::PathFromString(new_exe_name);
  40              return std::vector<std::string>{fs::PathToString(path), "-ipcfd", strprintf("%i", fd)};
  41          });
  42      }
  43      int waitSpawned(int pid) override { return mp::WaitProcess(pid); }
  44      bool checkSpawned(int argc, char* argv[], int& fd) override
  45      {
  46          // If this process was not started with a single -ipcfd argument, it is
  47          // not a process spawned by the spawn() call above, so return false and
  48          // do not try to serve requests.
  49          if (argc != 3 || strcmp(argv[1], "-ipcfd") != 0) {
  50              return false;
  51          }
  52          // If a single -ipcfd argument was provided, return true and get the
  53          // file descriptor so Protocol::serve() can be called to handle
  54          // requests from the parent process. The -ipcfd argument is not valid
  55          // in combination with other arguments because the parent process
  56          // should be able to control the child process through the IPC protocol
  57          // without passing information out of band.
  58          if (!ParseInt32(argv[2], &fd) || fd < 0) {
  59              throw std::runtime_error(strprintf("Invalid -ipcfd number '%s'", argv[2]));
  60          }
  61          return true;
  62      }
  63      int connect(const fs::path& data_dir,
  64                  const std::string& dest_exe_name,
  65                  std::string& address) override;
  66      int bind(const fs::path& data_dir, const std::string& exe_name, std::string& address) override;
  67  };
  68  
  69  static bool ParseAddress(std::string& address,
  70                    const fs::path& data_dir,
  71                    const std::string& dest_exe_name,
  72                    struct sockaddr_un& addr,
  73                    std::string& error)
  74  {
  75      if (address == "unix" || address.starts_with("unix:")) {
  76          fs::path path;
  77          if (address.size() <= 5) {
  78              path = data_dir / fs::PathFromString(strprintf("%s.sock", RemovePrefixView(dest_exe_name, "limenka-")));
  79          } else {
  80              path = data_dir / fs::PathFromString(address.substr(5));
  81          }
  82          std::string path_str = fs::PathToString(path);
  83          address = strprintf("unix:%s", path_str);
  84          if (path_str.size() >= sizeof(addr.sun_path)) {
  85              error = strprintf("Unix address path %s exceeded maximum socket path length", fs::quoted(fs::PathToString(path)));
  86              return false;
  87          }
  88          memset(&addr, 0, sizeof(addr));
  89          addr.sun_family = AF_UNIX;
  90          strncpy(addr.sun_path, path_str.c_str(), sizeof(addr.sun_path)-1);
  91          return true;
  92      }
  93  
  94      error = strprintf("Unrecognized address '%s'", address);
  95      return false;
  96  }
  97  
  98  int ProcessImpl::connect(const fs::path& data_dir,
  99                           const std::string& dest_exe_name,
 100                           std::string& address)
 101  {
 102      struct sockaddr_un addr;
 103      std::string error;
 104      if (!ParseAddress(address, data_dir, dest_exe_name, addr, error)) {
 105          throw std::invalid_argument(error);
 106      }
 107  
 108      int fd;
 109      if ((fd = ::socket(addr.sun_family, SOCK_STREAM, 0)) == -1) {
 110          throw std::system_error(errno, std::system_category());
 111      }
 112      if (::connect(fd, (struct sockaddr*)&addr, sizeof(addr)) == 0) {
 113          return fd;
 114      }
 115      int connect_error = errno;
 116      if (::close(fd) != 0) {
 117          LogWarning("Error closing file descriptor %i '%s': %s", fd, address, SysErrorString(errno));
 118      }
 119      throw std::system_error(connect_error, std::system_category());
 120  }
 121  
 122  int ProcessImpl::bind(const fs::path& data_dir, const std::string& exe_name, std::string& address)
 123  {
 124      struct sockaddr_un addr;
 125      std::string error;
 126      if (!ParseAddress(address, data_dir, exe_name, addr, error)) {
 127          throw std::invalid_argument(error);
 128      }
 129  
 130      if (addr.sun_family == AF_UNIX) {
 131          fs::path path = addr.sun_path;
 132          if (path.has_parent_path()) fs::create_directories(path.parent_path());
 133          if (fs::symlink_status(path).type() == fs::file_type::socket) {
 134              fs::remove(path);
 135          }
 136      }
 137  
 138      int fd;
 139      if ((fd = ::socket(addr.sun_family, SOCK_STREAM, 0)) == -1) {
 140          throw std::system_error(errno, std::system_category());
 141      }
 142  
 143      if (::bind(fd, (struct sockaddr*)&addr, sizeof(addr)) == 0) {
 144          // Restrict socket to owner-only for security (no peer credential check in Cap'n Proto layer)
 145          if (addr.sun_family == AF_UNIX) {
 146              fs::permissions(path, fs::perms::owner_read | fs::perms::owner_write);
 147          }
 148          return fd;
 149      }
 150      int bind_error = errno;
 151      if (::close(fd) != 0) {
 152          LogWarning("Error closing file descriptor %i: %s", fd, SysErrorString(errno));
 153      }
 154      throw std::system_error(bind_error, std::system_category());
 155  }
 156  } // namespace
 157  
 158  std::unique_ptr<Process> MakeProcess() { return std::make_unique<ProcessImpl>(); }
 159  } // namespace ipc
 160