master
cpp 328 lines 10.7 KB
Raw
1 // Copyright (C) Microsoft Corporation. All rights reserved.
2 #include "common.h"
3 #include <memory>
4 #include <string>
5 #include <string_view>
6
7 #include <sys/resource.h>
8 #include <sys/socket.h>
9
10 #include <lxwil.h>
11 #include <p9fs.h>
12 #include <p9tracelogging.h>
13 #include <optional>
14
15 #include "wslpath.h"
16
17 #include "util.h"
18 #include "SocketChannel.h"
19 #include "WslDistributionConfig.h"
20
21 namespace {
22
23 // Callback used if the Plan 9 server encounters an exception.
24 void LogPlan9Exception(const char* message, const char* exceptionDescription) noexcept
25 {
26 LogException(message, exceptionDescription);
27
28 // Also log the message to the tracelogging output, if that is enabled.
29 p9fs::Plan9TraceLoggingProvider::LogException(message, exceptionDescription);
30 }
31
32 // C++ helper for translating Windows paths to Linux paths.
33 std::string TranslatePath(char* windowsPath)
34 {
35 std::string translatedPath = WslPathTranslate(windowsPath, TRANSLATE_FLAG_ABSOLUTE, TRANSLATE_MODE_UNIX);
36 THROW_ERRNO_IF(EINVAL, translatedPath.empty());
37
38 return translatedPath;
39 }
40
41 // Create a unix socket and bind it to the specified path.
42 wil::unique_fd CreateUnixServerSocket(const char* path)
43 {
44 // Set up so the old working directory will be restored if it needs to be changed below.
45 char oldCwdBuffer[PATH_MAX];
46 char* oldCwd{};
47 auto restoreCwd = wil::scope_exit([&oldCwd]() {
48 if (oldCwd != nullptr)
49 {
50 chdir(oldCwd);
51 }
52 });
53
54 // Check if the path will fit in a sockaddr_un (with room for null terminator).
55 std::string_view pathView{path};
56 if (pathView.length() >= sizeof(sockaddr_un::sun_path))
57 {
58 // It won't, so split the parent path and child name.
59 auto index = pathView.find_last_of('/');
60
61 // This really shouldn't happen unless the WSL service has a bug.
62 THROW_ERRNO_IF(EINVAL, index == std::string_view::npos);
63
64 const std::string parent{pathView.substr(0, index)};
65 pathView = pathView.substr(index + 1);
66
67 // Ensure the child name fits in sun_path (with null terminator).
68 THROW_ERRNO_IF(ENAMETOOLONG, pathView.length() >= sizeof(sockaddr_un::sun_path));
69
70 // Get the current working directory to restore it later, and change to the socket's parent
71 // path.
72 oldCwd = getcwd(oldCwdBuffer, sizeof(oldCwdBuffer));
73 THROW_LAST_ERROR_IF(oldCwd == nullptr);
74 THROW_LAST_ERROR_IF(chdir(parent.c_str()) < 0);
75 }
76
77 // Create the socket.
78 wil::unique_fd server{socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0)};
79 THROW_LAST_ERROR_IF(!server);
80
81 // Delete the socket file if an old instance left it behind (e.g. if a crash occurred).
82 if (unlink(path) < 0)
83 {
84 THROW_LAST_ERROR_IF(errno != ENOENT);
85 }
86
87 // Bind to the path.
88 sockaddr_un address{};
89 address.sun_family = AF_UNIX;
90 memcpy(address.sun_path, pathView.data(), pathView.length());
91 THROW_LAST_ERROR_IF(bind(server.get(), reinterpret_cast<sockaddr*>(&address), sizeof(address)) < 0);
92
93 return server;
94 }
95
96 // Opens the log file, if one is specified, and sets the log level.
97 wil::unique_fd EnableLogging(const char* logFile, int logLevel, bool truncateLog)
98 {
99 // Don't enable logging if no log file was specified.
100 if (logFile == nullptr || strlen(logFile) == 0)
101 {
102 return {};
103 }
104
105 int flags = O_CREAT | O_WRONLY | O_APPEND;
106 WI_SetFlagIf(flags, O_TRUNC, truncateLog);
107 wil::unique_fd logFd{open(logFile, flags, 0600)};
108 if (!logFd)
109 {
110 LOG_ERROR("FS: Could not open log file {}: {}", logFile, errno);
111 return {};
112 }
113
114 p9fs::Plan9TraceLoggingProvider::SetLevel(logLevel);
115 p9fs::Plan9TraceLoggingProvider::SetLogFileDescriptor(logFd.get());
116
117 return logFd;
118 }
119
120 // Shut down the server, optionally only if there are no clients.
121 // Returns true if the server was stopped, false if there were clients preventing it from stopping.
122 bool StopPlan9Server(p9fs::IPlan9FileSystem& fileSystem, bool force)
123 try
124 {
125 if (!force)
126 {
127 if (fileSystem.HasConnections())
128 {
129 // Can't shut down because there are connections.
130 return false;
131 }
132 }
133
134 // Disable exception logging to ignore expected errors from the server
135 // shutting down.
136 wil::g_LogExceptionCallback = nullptr;
137
138 // Close all connections and stop listening.
139 fileSystem.Pause();
140
141 // Tear down the socket.
142 fileSystem.Teardown();
143
144 return true;
145 }
146 catch (...)
147 {
148 LOG_CAUGHT_EXCEPTION_MSG("Could not stop file system server.");
149
150 // Allow instance termination on failure to stop.
151 return true;
152 }
153
154 void RunPlan9ControlFile(p9fs::IPlan9FileSystem& fileSystem, wsl::shared::SocketChannel& channel)
155 try
156 {
157 std::vector<gsl::byte> Buffer;
158 for (;;)
159 {
160 auto transaction = channel.ReceiveTransaction();
161 auto [Message, _] = transaction.ReceiveOrClosed<LX_INIT_STOP_PLAN9_SERVER>();
162 if (Message == nullptr)
163 {
164 _exit(0);
165 }
166
167 transaction.SendResultMessage<bool>(StopPlan9Server(fileSystem, Message->Force));
168 }
169 }
170 CATCH_LOG();
171
172 } // namespace
173
174 void RunPlan9Server(const char* socketPath, const char* logFile, int logLevel, bool truncateLog, int controlSocket, int serverFd, wil::unique_fd& pipeFd)
175 {
176 // Initialize logging.
177 InitializeLogging(false, LogPlan9Exception);
178 auto logFd = EnableLogging(logFile, logLevel, truncateLog);
179
180 // Increase the limit for number of open file descriptors to the max allowed.
181 rlimit limit{};
182 THROW_LAST_ERROR_IF(getrlimit(RLIMIT_NOFILE, &limit) < 0);
183
184 limit.rlim_cur = limit.rlim_max;
185 if (setrlimit(RLIMIT_NOFILE, &limit) < 0)
186 {
187 LOG_ERROR("setrlimit(RLIMIT_NOFILE, {}, {}) failed {}", limit.rlim_cur, limit.rlim_max, errno);
188 }
189
190 // Open the root.
191 wil::unique_fd rootFd{open("/", O_PATH | O_DIRECTORY | O_CLOEXEC)};
192 THROW_LAST_ERROR_IF(!rootFd);
193
194 {
195 // Create the file system server.
196 auto fileSystem = p9fs::CreateFileSystem(serverFd);
197
198 // Add the share (the share takes ownership of the fd).
199 fileSystem->AddShare("", rootFd.get());
200 rootFd.release();
201
202 fileSystem->Resume();
203
204 // Close the pipe to signal the parent process that the plan9 server is started.
205 pipeFd.reset();
206
207 wsl::shared::SocketChannel channel({controlSocket}, "Plan9Control");
208 RunPlan9ControlFile(*fileSystem, channel);
209 }
210
211 // Unlink the socket path (don't care about failure).
212 if (socketPath != nullptr)
213 {
214 unlink(socketPath);
215 }
216 }
217
218 // Start listening for Plan 9 file server clients.
219 std::pair<unsigned int, wsl::shared::SocketChannel> StartPlan9Server(const char* socketWindowsPath, const wsl::linux::WslDistributionConfig& Config)
220 try
221 {
222 unsigned int result = LX_INIT_UTILITY_VM_INVALID_PORT;
223
224 // Don't run the server if no socket was specified by init.
225 // N.B. This is used to prevent the server from running when disabled with feature staging.
226 // N.B. VM mode does not use a socket path.
227 if (!UtilIsUtilityVm() && strlen(socketWindowsPath) == 0)
228 {
229 return {LX_INIT_UTILITY_VM_INVALID_PORT, wsl::shared::SocketChannel{}};
230 }
231
232 int sockets[] = {-1, -1};
233 THROW_LAST_ERROR_IF(socketpair(PF_LOCAL, SOCK_STREAM, 0, sockets) < 0);
234
235 wil::unique_fd parentSocket{sockets[0]};
236 wil::unique_fd childSocket{sockets[1]};
237
238 THROW_LAST_ERROR_IF(fcntl(parentSocket.get(), F_SETFD, FD_CLOEXEC) < 0);
239
240 // Set the umask to the default.
241 umask(Config.Umask);
242
243 std::string translatedSocketPath;
244 wil::unique_fd server;
245 if (UtilIsUtilityVm())
246 {
247 sockaddr_vm address;
248 server.reset(UtilBindVsockAnyPort(&address, (SOCK_STREAM | SOCK_NONBLOCK)));
249 THROW_LAST_ERROR_IF(!server);
250
251 // Increase the vsock send/receive buffers to increase throughput.
252 int bufferSize = LX_INIT_UTILITY_VM_PLAN9_BUFFER_SIZE;
253 THROW_LAST_ERROR_IF(setsockopt(server.get(), SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize)) < 0);
254 THROW_LAST_ERROR_IF(setsockopt(server.get(), SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize)) < 0);
255 result = address.svm_port;
256 }
257 else
258 {
259 // Translate the socket path (store a copy for unlinking on shutdown).
260 translatedSocketPath = TranslatePath(const_cast<char*>(socketWindowsPath));
261
262 // Create the server socket.
263 server = CreateUnixServerSocket(translatedSocketPath.c_str());
264 }
265
266 wil::unique_pipe pipe = wil::unique_pipe::create(0);
267 THROW_LAST_ERROR_IF(fcntl(pipe.read().get(), F_SETFD, FD_CLOEXEC) < 0)
268
269 const int childPid = UtilCreateChildProcess(
270 "Plan9",
271 [&translatedSocketPath, localChildSocket = std::move(childSocket), &Config, server = std::move(server), pipe = std::move(pipe.write())]() {
272 const std::string controlFdStr = std::to_string(localChildSocket.get());
273 const std::string logLevelStr = std::to_string(Config.Plan9LogLevel);
274 const std::string serverFdStr = std::to_string(server.get());
275 const std::string pipeFdStr = std::to_string(pipe.get());
276 std::vector<const char*> Arguments{
277 LX_INIT_PLAN9,
278 LX_INIT_PLAN9_CONTROL_SOCKET_ARG,
279 controlFdStr.c_str(),
280 LX_INIT_PLAN9_LOG_LEVEL_ARG,
281 logLevelStr.c_str(),
282 LX_INIT_PLAN9_SERVER_FD_ARG,
283 serverFdStr.c_str(),
284 LX_INIT_PLAN9_PIPE_FD_ARG,
285 pipeFdStr.c_str()};
286
287 if (!translatedSocketPath.empty())
288 {
289 Arguments.emplace_back(LX_INIT_PLAN9_SOCKET_PATH_ARG);
290 Arguments.emplace_back(translatedSocketPath.c_str());
291 }
292
293 if (Config.Plan9LogTruncate)
294 {
295 Arguments.emplace_back(LX_INIT_PLAN9_TRUNCATE_LOG_ARG);
296 }
297
298 if (Config.Plan9LogFile.has_value())
299 {
300 Arguments.emplace_back(LX_INIT_PLAN9_LOG_FILE_ARG);
301 Arguments.emplace_back(Config.Plan9LogFile->c_str());
302 }
303
304 Arguments.emplace_back(nullptr);
305
306 if (execv(LX_INIT_PATH, (char* const*)(Arguments.data())) < 0)
307 {
308 LOG_ERROR("execv failed {}", errno);
309 }
310
311 _exit(0);
312 });
313
314 THROW_LAST_ERROR_IF(childPid < 0);
315
316 // The child will close the pipe once the plan9 server has been started.
317 // This wait is necessary because we want to make sure that no connection request
318 // comes before the plan9 server is ready to accept it.
319 char readBuf = 0;
320 THROW_LAST_ERROR_IF(read(pipe.read().get(), &readBuf, 1) != 0);
321
322 return {result, wsl::shared::SocketChannel{std::move(parentSocket), "Plan9Control"}};
323 }
324 catch (...)
325 {
326 LOG_CAUGHT_EXCEPTION_MSG("Could not start file system server.")
327 return {LX_INIT_UTILITY_VM_INVALID_PORT, wsl::shared::SocketChannel{}};
328 }