Skip to content

Commit 8fdaf16

Browse files
authored
WasmFS JS API: Implement mkdev (#19978)
This PR implements FS.mkdev and it's helpers (registerDevice, makedev, major, and minor). Existing tests were modified to pass with WasmFS. In syscalls.cpp also fix a minor bug with poll to get the test passing.
1 parent 2aa1471 commit 8fdaf16

File tree

11 files changed

+158
-19
lines changed

11 files changed

+158
-19
lines changed

.circleci/config.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -547,6 +547,10 @@ jobs:
547547
wasmfs.test_readdir
548548
wasmfs.test_readdir_unlink
549549
wasmfs.test_unistd_pipe
550+
wasmfs.test_unistd_io
551+
wasmfs.test_unistd_curdir
552+
wasmfs.test_poll
553+
wasmfs.test_fs_64bit
550554
wasmfs.test_fs_write
551555
wasmfs.test_fs_writev
552556
wasmfs.test_fs_writev_rawfs

emcc.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2358,6 +2358,7 @@ def phase_linker_setup(options, state, newargs):
23582358
# included, as the entire JS library can refer to things that require
23592359
# these exports.)
23602360
settings.REQUIRED_EXPORTS += [
2361+
'wasmfs_create_file',
23612362
'_wasmfs_mount',
23622363
'_wasmfs_unmount',
23632364
'_wasmfs_read_file',

src/library_wasmfs.js

Lines changed: 104 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ addToLibrary({
1818
// data needs to be preloaded (and it would be invalid to do so, as any
1919
// further additions to wasmFSPreloadedFiles|Dirs would be ignored).
2020
$wasmFSPreloadingFlushed: false,
21+
$wasmFSDevices: {},
22+
$wasmFSDeviceStreams: {},
2123

2224
$FS__postset: `
2325
FS.init();
@@ -59,6 +61,10 @@ FS.createPreloadedFile = FS_createPreloadedFile;
5961
#endif
6062
'malloc',
6163
'free',
64+
'wasmfs_create_jsimpl_backend',
65+
'$wasmFS$backends',
66+
'$wasmFSDevices',
67+
'$wasmFSDeviceStreams'
6268
#endif
6369
],
6470
$FS : {
@@ -392,7 +398,104 @@ FS.createPreloadedFile = FS_createPreloadedFile;
392398
return __wasmfs_mknod(pathBuffer, mode, dev);
393399
}));
394400
},
395-
// TODO: mkdev
401+
makedev: (ma, mi) => ((ma) << 8 | (mi)),
402+
registerDevice(dev, ops) {
403+
var backendPointer = _wasmfs_create_jsimpl_backend();
404+
var definedOps = {
405+
userRead: ops.read,
406+
userWrite: ops.write,
407+
408+
allocFile: (file) => {
409+
wasmFSDeviceStreams[file] = {}
410+
},
411+
freeFile: (file) => {
412+
wasmFSDeviceStreams[file] = undefined;
413+
},
414+
getSize: (file) => {},
415+
read: (file, buffer, length, offset) => {
416+
var bufferArray = Module.HEAP8.subarray(buffer, buffer + length);
417+
try {
418+
var bytesRead = definedOps.userRead(wasmFSDeviceStreams[file], bufferArray, 0, length, offset);
419+
} catch (e) {
420+
return -e.errno;
421+
}
422+
Module.HEAP8.set(bufferArray, buffer);
423+
return bytesRead;
424+
},
425+
write: (file, buffer, length, offset) => {
426+
var bufferArray = Module.HEAP8.subarray(buffer, buffer + length);
427+
try {
428+
var bytesWritten = definedOps.userWrite(wasmFSDeviceStreams[file], bufferArray, 0, length, offset);
429+
} catch (e) {
430+
return -e.errno;
431+
}
432+
Module.HEAP8.set(bufferArray, buffer);
433+
return bytesWritten;
434+
},
435+
};
436+
437+
wasmFS$backends[backendPointer] = definedOps;
438+
wasmFSDevices[dev] = backendPointer;
439+
},
440+
createDevice(parent, name, input, output) {
441+
if (typeof parent != 'string') {
442+
// The old API allowed parents to be objects, which do not exist in WasmFS.
443+
throw new Error("Only string paths are accepted");
444+
}
445+
var path = PATH.join2(parent, name);
446+
var mode = FS_getMode(!!input, !!output);
447+
if (!FS.createDevice.major) FS.createDevice.major = 64;
448+
var dev = FS.makedev(FS.createDevice.major++, 0);
449+
// Create a fake device with a set of stream ops to emulate
450+
// the old API's createDevice().
451+
FS.registerDevice(dev, {
452+
read(stream, buffer, offset, length, pos /* ignored */) {
453+
var bytesRead = 0;
454+
for (var i = 0; i < length; i++) {
455+
var result;
456+
try {
457+
result = input();
458+
} catch (e) {
459+
throw new FS.ErrnoError({{{ cDefs.EIO }}});
460+
}
461+
if (result === undefined && bytesRead === 0) {
462+
throw new FS.ErrnoError({{{ cDefs.EAGAIN }}});
463+
}
464+
if (result === null || result === undefined) break;
465+
bytesRead++;
466+
buffer[offset+i] = result;
467+
}
468+
return bytesRead;
469+
},
470+
write(stream, buffer, offset, length, pos) {
471+
for (var i = 0; i < length; i++) {
472+
try {
473+
output(buffer[offset+i]);
474+
} catch (e) {
475+
throw new FS.ErrnoError({{{ cDefs.EIO }}});
476+
}
477+
}
478+
return i;
479+
}
480+
});
481+
return FS.mkdev(path, mode, dev);
482+
},
483+
// mode is an optional argument, which will be set to 0666 if not passed in.
484+
mkdev(path, mode, dev) {
485+
if (typeof dev === 'undefined') {
486+
dev = mode;
487+
mode = 438 /* 0666 */;
488+
}
489+
490+
var deviceBackend = wasmFSDevices[dev];
491+
if (!deviceBackend) {
492+
throw new Error("Invalid device ID.");
493+
}
494+
495+
return FS.handleError(withStackSave(() => (
496+
_wasmfs_create_file(stringToUTF8OnStack(path), mode, deviceBackend)
497+
)));
498+
},
396499
rename(oldPath, newPath) {
397500
return FS.handleError(withStackSave(() => {
398501
var oldPathBuffer = stringToUTF8OnStack(oldPath);

src/library_wasmfs_jsimpl.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ addToLibrary({
2929
#if ASSERTIONS
3030
assert(wasmFS$backends[backend]);
3131
#endif
32+
if (!wasmFS$backends[backend].write) {
33+
return -{{{ cDefs.EINVAL }}};
34+
}
3235
return wasmFS$backends[backend].write(file, buffer, length, offset);
3336
},
3437

@@ -37,6 +40,9 @@ addToLibrary({
3740
#if ASSERTIONS
3841
assert(wasmFS$backends[backend]);
3942
#endif
43+
if (!wasmFS$backends[backend].read) {
44+
return -{{{ cDefs.EINVAL }}};
45+
}
4046
return wasmFS$backends[backend].read(file, buffer, length, offset);
4147
},
4248

system/include/emscripten/wasmfs.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,9 @@ backend_t wasmfs_create_node_backend(const char* root __attribute__((nonnull)));
7272
// thread.
7373
backend_t wasmfs_create_opfs_backend(void);
7474

75+
// Creates a generic JSIMPL backend in the new file system.
76+
backend_t wasmfs_create_jsimpl_backend(void);
77+
7578
backend_t wasmfs_create_icase_backend(backend_t backend);
7679

7780
// Similar to fflush(0), but also flushes all internal buffers inside WasmFS.

system/lib/wasmfs/js_impl_backend.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,4 +125,12 @@ class JSImplBackend : public Backend {
125125
}
126126
};
127127

128+
extern "C" {
129+
130+
backend_t wasmfs_create_jsimpl_backend(void) {
131+
return wasmFS.addBackend(std::make_unique<JSImplBackend>());
132+
}
133+
134+
} // extern "C"
135+
128136
} // namespace wasmfs

system/lib/wasmfs/syscalls.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1379,12 +1379,13 @@ int __syscall_poll(intptr_t fds_, int nfds, int timeout) {
13791379
if (openFile) {
13801380
mask = 0;
13811381
auto flags = openFile->locked().getFlags();
1382+
auto accessMode = flags & O_ACCMODE;
13821383
auto readBit = pollfd->events & POLLOUT;
1383-
if (readBit && (flags == O_WRONLY || flags == O_RDWR)) {
1384+
if (readBit && (accessMode == O_WRONLY || accessMode == O_RDWR)) {
13841385
mask |= readBit;
13851386
}
13861387
auto writeBit = pollfd->events & POLLIN;
1387-
if (writeBit && (flags == O_RDONLY || flags == O_RDWR)) {
1388+
if (writeBit && (accessMode == O_RDONLY || accessMode == O_RDWR)) {
13881389
// If there is data in the file, then there is also the ability to read.
13891390
// TODO: Does this need to consider the position as well? That is, if
13901391
// the position is at the end, we can't read from the current position

test/core/test_poll.c

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,23 @@
99
#include <errno.h>
1010
#include <fcntl.h>
1111
#include <poll.h>
12+
#include <emscripten.h>
1213

1314
int main() {
15+
EM_ASM(
16+
var dummy_device = FS.makedev(64, 0);
17+
FS.registerDevice(dummy_device, {});
18+
19+
FS.createDataFile('/', 'file', 'abcdef', true, true, false);
20+
FS.mkdev('/device', dummy_device);
21+
);
22+
1423
struct pollfd multi[5];
15-
multi[0].fd = open("/file", O_RDONLY, 0777);
16-
multi[1].fd = open("/device", O_RDONLY, 0777);
24+
multi[0].fd = open("/file", O_RDWR, 0777);
25+
multi[1].fd = open("/device", O_RDWR, 0777);
1726
multi[2].fd = 123;
18-
multi[3].fd = open("/file", O_RDONLY, 0777);
19-
multi[4].fd = open("/file", O_RDONLY, 0777);
27+
multi[3].fd = open("/file", O_RDWR, 0777);
28+
multi[4].fd = open("/file", O_RDWR, 0777);
2029
multi[0].events = POLLIN | POLLOUT | POLLNVAL | POLLERR;
2130
multi[1].events = POLLIN | POLLOUT | POLLNVAL | POLLERR;
2231
multi[2].events = POLLIN | POLLOUT | POLLNVAL | POLLERR;
@@ -26,7 +35,13 @@ int main() {
2635
printf("ret: %d\n", poll(multi, 5, 123));
2736
printf("errno: %d\n", errno);
2837
printf("multi[0].revents: %d\n", multi[0].revents == (POLLIN | POLLOUT));
38+
#if WASMFS
39+
// TODO: Add support for POLLIN. The size of the device is 0 in WasmFS, so
40+
// devices cannot POLLIN.
41+
printf("multi[1].revents: %d\n", multi[1].revents == POLLOUT);
42+
#else
2943
printf("multi[1].revents: %d\n", multi[1].revents == (POLLIN | POLLOUT));
44+
#endif
3045
printf("multi[2].revents: %d\n", multi[2].revents == POLLNVAL);
3146
printf("multi[3].revents: %d\n", multi[3].revents == 0);
3247
printf("multi[4].revents: %d\n", multi[4].revents == POLLOUT);

test/fs/test_64bit.c

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,14 @@
88
#include <emscripten.h>
99
#include <stdio.h>
1010
#include <assert.h>
11+
#include <errno.h>
1112

1213
int main(int argc, char *argv[])
1314
{
1415
EM_ASM({
1516
var counter = FS.makedev(64, 0);
1617

1718
FS.registerDevice(counter, {
18-
open: function(stream) {},
19-
close: function(stream) {},
2019
read: function(stream, buffer, offset, length, position) {
2120
for (var i = 0; i < length; ++i) {
2221
buffer[offset + i] = position + i;

test/test_core.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5808,13 +5808,8 @@ def test_fcntl_misc(self):
58085808
self.do_run_in_out_file_test('fcntl/test_fcntl_misc.c')
58095809

58105810
def test_poll(self):
5811-
self.add_pre_run('''
5812-
var dummy_device = FS.makedev(64, 0);
5813-
FS.registerDevice(dummy_device, {});
5814-
5815-
FS.createDataFile('/', 'file', 'abcdef', true, true, false);
5816-
FS.mkdev('/device', dummy_device);
5817-
''')
5811+
if self.get_setting('WASMFS'):
5812+
self.set_setting('FORCE_FILESYSTEM')
58185813
self.do_core_test('test_poll.c')
58195814

58205815
def test_statvfs(self):
@@ -6079,6 +6074,8 @@ def test_fs_writev(self):
60796074
self.do_runf(test_file('fs/test_writev.c'), 'success')
60806075

60816076
def test_fs_64bit(self):
6077+
if self.get_setting('WASMFS'):
6078+
self.set_setting('FORCE_FILESYSTEM')
60826079
self.do_runf(test_file('fs/test_64bit.c'), 'success')
60836080

60846081
def test_sigalrm(self):
@@ -6113,6 +6110,8 @@ def test_unistd_access(self):
61136110

61146111
def test_unistd_curdir(self):
61156112
self.uses_es6 = True
6113+
if self.get_setting('WASMFS'):
6114+
self.set_setting('FORCE_FILESYSTEM')
61166115
self.do_run_in_out_file_test('unistd/curdir.c')
61176116

61186117
@also_with_noderawfs

0 commit comments

Comments
 (0)