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