| 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 | } |