Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
emscripten-core
GitHub Repository: emscripten-core/emscripten
Path: blob/main/system/lib/wasmfs/backends/node_backend.cpp
6175 views
1
// Copyright 2022 The Emscripten Authors. All rights reserved.
2
// Emscripten is available under two separate licenses, the MIT license and the
3
// University of Illinois/NCSA Open Source License. Both these licenses can be
4
// found in the LICENSE file.
5
6
#include <memory>
7
8
#include "backend.h"
9
#include "file.h"
10
#include "node_backend.h"
11
#include "support.h"
12
#include "wasmfs.h"
13
14
namespace wasmfs {
15
16
class NodeBackend;
17
18
// The state of a file on the underlying Node file system.
19
class NodeState {
20
// Map all separate WasmFS opens of a file to a single underlying fd.
21
size_t openCount = 0;
22
oflags_t openFlags = 0;
23
int fd = -1;
24
25
public:
26
std::string path;
27
28
NodeState(std::string path) : path(path) {}
29
30
int getFD() {
31
assert(openCount > 0);
32
return fd;
33
}
34
35
bool isOpen() { return openCount > 0; }
36
37
// Attempt to open the file with the given flags, returning 0 on success or an
38
// error code.
39
int open(oflags_t flags) {
40
int result = 0;
41
if (openCount == 0) {
42
// No existing fd, so open a fresh one.
43
switch (flags) {
44
case O_RDONLY:
45
result = _wasmfs_node_open(path.c_str(), "r");
46
break;
47
case O_WRONLY:
48
// TODO(sbc): Specific handling of O_WRONLY.
49
// There is no simple way to map O_WRONLY to an fopen-style
50
// mode string since the only two modes that are write only
51
// are `w` and `a`. The problem with the former is that it
52
// truncates to file. The problem with the latter is that it
53
// opens for appending. For now simply opening in O_RDWR
54
// mode is enough to pass all our tests.
55
case O_RDWR:
56
result = _wasmfs_node_open(path.c_str(), "r+");
57
break;
58
default:
59
WASMFS_UNREACHABLE("Unexpected open access mode");
60
}
61
if (result < 0) {
62
return result;
63
}
64
// Fall through to update our state with the new result.
65
} else if ((openFlags == O_RDONLY &&
66
(flags == O_WRONLY || flags == O_RDWR)) ||
67
(openFlags == O_WRONLY &&
68
(flags == O_RDONLY || flags == O_RDWR))) {
69
// We already have a file descriptor, but we need to replace it with a new
70
// fd with more access.
71
result = _wasmfs_node_open(path.c_str(), "r+");
72
if (result < 0) {
73
return result;
74
}
75
// Success! Close the old fd before updating it.
76
(void)_wasmfs_node_close(fd);
77
// Fall through to update our state with the new result.
78
} else {
79
// Reuse the existing file descriptor.
80
++openCount;
81
return 0;
82
}
83
// Update our state for the new fd.
84
fd = result;
85
openFlags = flags;
86
++openCount;
87
return 0;
88
}
89
90
int close() {
91
int ret = 0;
92
if (--openCount == 0) {
93
ret = _wasmfs_node_close(fd);
94
*this = NodeState(path);
95
}
96
return ret;
97
}
98
};
99
100
class NodeFile : public DataFile {
101
public:
102
NodeState state;
103
104
NodeFile(mode_t mode, backend_t backend, std::string path)
105
: DataFile(mode, backend), state(path) {}
106
107
private:
108
off_t getSize() override {
109
// TODO: This should really be using a 64-bit file size type.
110
uint32_t size;
111
if (state.isOpen()) {
112
if (_wasmfs_node_fstat_size(state.getFD(), &size)) {
113
// TODO: Make this fallible.
114
return 0;
115
}
116
} else {
117
if (_wasmfs_node_stat_size(state.path.c_str(), &size)) {
118
// TODO: Make this fallible.
119
return 0;
120
}
121
}
122
return off_t(size);
123
}
124
125
int setSize(off_t size) override {
126
if (state.isOpen()) {
127
return _wasmfs_node_ftruncate(state.getFD(), size);
128
}
129
return _wasmfs_node_truncate(state.path.c_str(), size);
130
}
131
132
int open(oflags_t flags) override { return state.open(flags); }
133
134
int close() override { return state.close(); }
135
136
ssize_t read(uint8_t* buf, size_t len, off_t offset) override {
137
uint32_t nread;
138
if (auto err = _wasmfs_node_read(state.getFD(), buf, len, offset, &nread)) {
139
return -err;
140
}
141
return nread;
142
}
143
144
ssize_t write(const uint8_t* buf, size_t len, off_t offset) override {
145
uint32_t nwritten;
146
if (auto err =
147
_wasmfs_node_write(state.getFD(), buf, len, offset, &nwritten)) {
148
return -err;
149
}
150
return nwritten;
151
}
152
153
int flush() override {
154
WASMFS_UNREACHABLE("TODO: implement NodeFile::flush");
155
}
156
};
157
158
class NodeSymlink : public Symlink {
159
public:
160
std::string path;
161
162
NodeSymlink(backend_t backend, std::string path)
163
: Symlink(backend), path(path) {}
164
165
virtual std::string getTarget() const {
166
char buf[PATH_MAX];
167
if (_wasmfs_node_readlink(path.c_str(), buf, PATH_MAX) < 0) {
168
WASMFS_UNREACHABLE("getTarget cannot fail");
169
}
170
return std::string(buf);
171
}
172
};
173
174
class NodeDirectory : public Directory {
175
public:
176
NodeState state;
177
178
NodeDirectory(mode_t mode, backend_t backend, std::string path)
179
: Directory(mode, backend), state(path) {}
180
181
private:
182
std::string getChildPath(const std::string& name) {
183
return state.path + '/' + name;
184
}
185
186
std::shared_ptr<File> getChild(const std::string& name) override {
187
static_assert(std::is_same_v<mode_t, unsigned int>);
188
// TODO: also retrieve and set ctime, atime, ino, etc.
189
auto childPath = getChildPath(name);
190
mode_t mode;
191
if (_wasmfs_node_get_mode(childPath.c_str(), &mode)) {
192
return nullptr;
193
}
194
if (S_ISREG(mode)) {
195
return std::make_shared<NodeFile>(mode, getBackend(), childPath);
196
} else if (S_ISDIR(mode)) {
197
return std::make_shared<NodeDirectory>(mode, getBackend(), childPath);
198
} else if (S_ISLNK(mode)) {
199
return std::make_shared<NodeSymlink>(getBackend(), childPath);
200
} else {
201
// Unrecognized file kind not made visible to WasmFS.
202
return nullptr;
203
}
204
}
205
206
int removeChild(const std::string& name) override {
207
auto childPath = getChildPath(name);
208
// Try both `unlink` and `rmdir`.
209
if (auto err = _wasmfs_node_unlink(childPath.c_str())) {
210
if (err == EISDIR) {
211
err = _wasmfs_node_rmdir(childPath.c_str());
212
}
213
return -err;
214
}
215
return 0;
216
}
217
218
std::shared_ptr<DataFile> insertDataFile(const std::string& name,
219
mode_t mode) override {
220
auto childPath = getChildPath(name);
221
if (_wasmfs_node_insert_file(childPath.c_str(), mode)) {
222
return nullptr;
223
}
224
return std::make_shared<NodeFile>(mode, getBackend(), childPath);
225
}
226
227
std::shared_ptr<Directory> insertDirectory(const std::string& name,
228
mode_t mode) override {
229
auto childPath = getChildPath(name);
230
if (_wasmfs_node_insert_directory(childPath.c_str(), mode)) {
231
return nullptr;
232
}
233
return std::make_shared<NodeDirectory>(mode, getBackend(), childPath);
234
}
235
236
std::shared_ptr<Symlink> insertSymlink(const std::string& name,
237
const std::string& target) override {
238
auto childPath = getChildPath(name);
239
if (_wasmfs_node_symlink(target.c_str(), childPath.c_str())) {
240
return nullptr;
241
}
242
return std::make_shared<NodeSymlink>(getBackend(), childPath);
243
}
244
245
int insertMove(const std::string& name, std::shared_ptr<File> file) override {
246
std::string fromPath;
247
248
if (file->is<DataFile>()) {
249
auto nodeFile = std::static_pointer_cast<NodeFile>(file);
250
fromPath = nodeFile->state.path;
251
} else {
252
auto nodeDir = std::static_pointer_cast<NodeDirectory>(file);
253
fromPath = nodeDir->state.path;
254
}
255
256
auto childPath = getChildPath(name);
257
return _wasmfs_node_rename(fromPath.c_str(), childPath.c_str());
258
}
259
260
ssize_t getNumEntries() override {
261
// TODO: optimize this?
262
auto entries = getEntries();
263
if (int err = entries.getError()) {
264
return err;
265
}
266
return entries->size();
267
}
268
269
Directory::MaybeEntries getEntries() override {
270
std::vector<Directory::Entry> entries;
271
int err = _wasmfs_node_readdir(state.path.c_str(), &entries);
272
if (err) {
273
return {-err};
274
}
275
return {entries};
276
}
277
};
278
279
class NodeBackend : public Backend {
280
// The underlying Node FS path of this backend's mount points.
281
std::string mountPath;
282
283
public:
284
NodeBackend(const std::string& mountPath) : mountPath(mountPath) {}
285
286
std::shared_ptr<DataFile> createFile(mode_t mode) override {
287
return std::make_shared<NodeFile>(mode, this, mountPath);
288
}
289
290
std::shared_ptr<Directory> createDirectory(mode_t mode) override {
291
return std::make_shared<NodeDirectory>(mode, this, mountPath);
292
}
293
294
std::shared_ptr<Symlink> createSymlink(std::string target) override {
295
WASMFS_UNREACHABLE("TODO: implement NodeBackend::createSymlink");
296
}
297
};
298
299
// TODO: symlink
300
301
extern "C" {
302
303
backend_t wasmfs_create_node_backend(const char* root) {
304
return wasmFS.addBackend(std::make_unique<NodeBackend>(root));
305
}
306
307
void EMSCRIPTEN_KEEPALIVE _wasmfs_node_record_dirent(
308
std::vector<Directory::Entry>* entries, const char* name, int type) {
309
entries->push_back({name, File::FileKind(type), 0});
310
}
311
312
} // extern "C"
313
314
} // namespace wasmfs
315
316