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