Skip to content

Commit c62ac9b

Browse files
authored
[WASMFS] Implement Open File Table, dup and dup2 syscalls (#15181)
Relevant Issue: #15041 - Implement Open File Table for WASMFS - Implement dup and dup2 syscalls - File Handle locking mechanism - Add test to verify that dup and dup2 work and that printing to stdout is still functional using the new open file table data structure
1 parent 39b44b6 commit c62ac9b

File tree

9 files changed

+419
-34
lines changed

9 files changed

+419
-34
lines changed

system/lib/wasmfs/file.h

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// Copyright 2021 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+
// This file defines the file object of the new file system.
6+
// Current Status: Work in Progress.
7+
// See https://github.com/emscripten-core/emscripten/issues/15041.
8+
9+
#pragma once
10+
11+
#include <emscripten/html5.h>
12+
#include <mutex>
13+
#include <vector>
14+
#include <wasi/api.h>
15+
16+
namespace wasmfs {
17+
18+
class File {
19+
// TODO: Add other File properties later.
20+
21+
// A mutex is needed for multiple accesses to the same file.
22+
std::mutex mutex;
23+
24+
virtual __wasi_errno_t read(const uint8_t* buf, __wasi_size_t len) = 0;
25+
virtual __wasi_errno_t write(const uint8_t* buf, __wasi_size_t len) = 0;
26+
27+
public:
28+
virtual ~File() = default;
29+
class Handle {
30+
File& file;
31+
std::unique_lock<std::mutex> lock;
32+
33+
public:
34+
Handle(File& file) : file(file), lock(file.mutex) {}
35+
Handle(Handle&&) = default;
36+
37+
__wasi_errno_t read(const uint8_t* buf, __wasi_size_t len) { return file.read(buf, len); }
38+
__wasi_errno_t write(const uint8_t* buf, __wasi_size_t len) { return file.write(buf, len); }
39+
};
40+
41+
Handle get() { return Handle(*this); }
42+
};
43+
44+
} // namespace wasmfs

system/lib/wasmfs/file_table.cpp

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
// Copyright 2021 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+
// This file defines the open file table of the new file system.
6+
// Current Status: Work in Progress.
7+
// See https://github.com/emscripten-core/emscripten/issues/15041.
8+
9+
#include "file_table.h"
10+
11+
namespace wasmfs {
12+
13+
std::vector<std::shared_ptr<OpenFileState>> FileTable::entries;
14+
15+
static __wasi_errno_t writeStdBuffer(const uint8_t* buf, __wasi_size_t len,
16+
void (*console_write)(const char*), std::vector<char>& fd_write_buffer) {
17+
for (__wasi_size_t j = 0; j < len; j++) {
18+
uint8_t current = buf[j];
19+
if (current == '\0' || current == '\n') {
20+
fd_write_buffer.push_back('\0'); // for null-terminated C strings
21+
console_write(&fd_write_buffer[0]);
22+
fd_write_buffer.clear();
23+
} else {
24+
fd_write_buffer.push_back(current);
25+
}
26+
}
27+
return __WASI_ERRNO_SUCCESS;
28+
}
29+
30+
class StdinFile : public File {
31+
32+
__wasi_errno_t write(const uint8_t* buf, __wasi_size_t len) override {
33+
return __WASI_ERRNO_INVAL;
34+
}
35+
36+
__wasi_errno_t read(const uint8_t* buf, __wasi_size_t len) override {
37+
return __WASI_ERRNO_INVAL;
38+
};
39+
};
40+
41+
class StdoutFile : public File {
42+
std::vector<char> writeBuffer;
43+
44+
__wasi_errno_t write(const uint8_t* buf, __wasi_size_t len) override {
45+
return writeStdBuffer(buf, len, &emscripten_console_log, writeBuffer);
46+
}
47+
48+
__wasi_errno_t read(const uint8_t* buf, __wasi_size_t len) override {
49+
return __WASI_ERRNO_INVAL;
50+
};
51+
};
52+
53+
class StderrFile : public File {
54+
std::vector<char> writeBuffer;
55+
56+
// TODO: May not want to proxy stderr (fd == 2) to the main thread.
57+
// This will not show in HTML - a console.warn in a worker is sufficient.
58+
// This would be a change from the current FS.
59+
__wasi_errno_t write(const uint8_t* buf, __wasi_size_t len) override {
60+
return writeStdBuffer(buf, len, &emscripten_console_error, writeBuffer);
61+
}
62+
63+
__wasi_errno_t read(const uint8_t* buf, __wasi_size_t len) override {
64+
return __WASI_ERRNO_INVAL;
65+
};
66+
};
67+
68+
FileTable::FileTable() {
69+
entries.push_back(std::make_shared<OpenFileState>(0, std::make_shared<StdinFile>()));
70+
entries.push_back(std::make_shared<OpenFileState>(0, std::make_shared<StdoutFile>()));
71+
entries.push_back(std::make_shared<OpenFileState>(0, std::make_shared<StderrFile>()));
72+
}
73+
74+
FileTable::Handle FileTable::get() {
75+
static FileTable fileTable;
76+
return FileTable::Handle(fileTable);
77+
}
78+
79+
// Operator Overloading for FileTable::Handle::Entry
80+
FileTable::Handle::Entry::operator std::shared_ptr<OpenFileState>() const {
81+
if (fd >= fileTableHandle.fileTable.entries.size() || fd < 0) {
82+
return nullptr;
83+
}
84+
85+
return fileTableHandle.fileTable.entries[fd];
86+
}
87+
88+
FileTable::Handle::Entry& FileTable::Handle::Entry::operator=(std::shared_ptr<OpenFileState> ptr) {
89+
assert(fd >= 0);
90+
91+
if (fd >= fileTableHandle.fileTable.entries.size()) {
92+
fileTableHandle.fileTable.entries.resize(fd + 1);
93+
}
94+
fileTableHandle.fileTable.entries[fd] = ptr;
95+
96+
return *this;
97+
}
98+
99+
std::shared_ptr<OpenFileState>& FileTable::Handle::Entry::operator->() {
100+
assert(fd < fileTableHandle.fileTable.entries.size() && fd >= 0);
101+
102+
return fileTableHandle.fileTable.entries[fd];
103+
}
104+
105+
FileTable::Handle::Entry::operator bool() const {
106+
if (fd >= fileTableHandle.fileTable.entries.size() || fd < 0) {
107+
return false;
108+
}
109+
110+
return fileTableHandle.fileTable.entries[fd] != nullptr;
111+
}
112+
} // namespace wasmfs

system/lib/wasmfs/file_table.h

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
// Copyright 2021 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+
// This file defines the open file table of the new file system.
6+
// Current Status: Work in Progress.
7+
// See https://github.com/emscripten-core/emscripten/issues/15041.
8+
9+
#pragma once
10+
11+
#include "file.h"
12+
#include <assert.h>
13+
#include <mutex>
14+
#include <utility>
15+
#include <vector>
16+
#include <wasi/api.h>
17+
18+
namespace wasmfs {
19+
20+
class OpenFileState {
21+
std::shared_ptr<File> file;
22+
__wasi_filedelta_t offset;
23+
std::mutex mutex;
24+
25+
public:
26+
OpenFileState(uint32_t offset, std::shared_ptr<File> file) : offset(offset), file(file) {}
27+
28+
class Handle {
29+
OpenFileState& openFileState;
30+
std::unique_lock<std::mutex> lock;
31+
32+
public:
33+
Handle(OpenFileState& openFileState)
34+
: openFileState(openFileState), lock(openFileState.mutex) {}
35+
36+
std::shared_ptr<File>& getFile() { return openFileState.file; };
37+
};
38+
39+
Handle get() { return Handle(*this); }
40+
};
41+
42+
class FileTable {
43+
static std::vector<std::shared_ptr<OpenFileState>> entries;
44+
std::mutex mutex;
45+
46+
FileTable();
47+
48+
public:
49+
// Handle represents an RAII wrapper object. Access to the global FileTable must go through a
50+
// Handle. A Handle holds the single global FileTable's lock for the duration of its lifetime.
51+
// This is necessary because a FileTable may have atomic operations where the lock must be held
52+
// across multiple methods. By providing access through the handle, callers of file table methods
53+
// do not need to remember to take a lock for every access.
54+
class Handle {
55+
FileTable& fileTable;
56+
std::unique_lock<std::mutex> lock;
57+
58+
public:
59+
Handle(FileTable& fileTable) : fileTable(fileTable), lock(fileTable.mutex) {}
60+
61+
// The Entry class abstracts over the list of entries, providing a simple and safe interface
62+
// that looks much like accessing a std::map, in that table[x] = y will allocate a new entry if
63+
// one is not already present there. One minor difference from std::map is that table[x] does
64+
// not return a reference, and can be used to check for the lack of an item there without
65+
// allocation (similar to how table[x] works on a JS object), which keeps syntax concise.
66+
struct Entry {
67+
// Need to store a reference to the single global filetable, which is a local static variable.
68+
Handle& fileTableHandle;
69+
__wasi_fd_t fd;
70+
71+
operator std::shared_ptr<OpenFileState>() const;
72+
73+
Entry& operator=(std::shared_ptr<OpenFileState> ptr);
74+
75+
Entry& operator=(Entry& entry) { return *this = std::shared_ptr<OpenFileState>(entry); }
76+
77+
std::shared_ptr<OpenFileState>& operator->();
78+
79+
// Check whether the entry exists (i.e. contains an OpenFileState).
80+
operator bool() const;
81+
};
82+
83+
Entry operator[](__wasi_fd_t fd) { return Entry{*this, fd}; };
84+
};
85+
86+
// This get method is responsible for lazily initializing the FileTable.
87+
// There is only ever one FileTable in the system.
88+
static Handle get();
89+
};
90+
} // namespace wasmfs

system/lib/wasmfs/wasmfs.cpp

Lines changed: 85 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -6,47 +6,87 @@
66
// Current Status: Work in Progress.
77
// See https://github.com/emscripten-core/emscripten/issues/15041.
88

9+
#include "file.h"
10+
#include "file_table.h"
911
#include <emscripten/emscripten.h>
1012
#include <emscripten/html5.h>
13+
#include <errno.h>
14+
#include <mutex>
1115
#include <stdlib.h>
16+
#include <utility>
1217
#include <vector>
1318
#include <wasi/api.h>
1419

1520
extern "C" {
1621

17-
static std::vector<char> fd_write_stdstream_buffer;
22+
using namespace wasmfs;
23+
24+
long __syscall_dup2(long oldfd, long newfd) {
25+
FileTable::Handle fileTable = FileTable::get();
26+
27+
auto oldOpenFile = fileTable[oldfd];
28+
// If oldfd is not a valid file descriptor, then the call fails,
29+
// and newfd is not closed.
30+
if (!oldOpenFile) {
31+
return -(EBADF);
32+
}
33+
34+
if (newfd < 0) {
35+
return -(EBADF);
36+
}
37+
38+
if (oldfd == newfd) {
39+
return oldfd;
40+
}
41+
42+
// If the file descriptor newfd was previously open, it will just be overwritten silently.
43+
fileTable[newfd] = oldOpenFile;
44+
return newfd;
45+
}
46+
47+
long __syscall_dup(long fd) {
48+
49+
FileTable::Handle fileTable = FileTable::get();
50+
51+
// Check that an open file exists corresponding to the given fd.
52+
auto currentOpenFile = fileTable[fd];
53+
if (!currentOpenFile) {
54+
return -(EBADF);
55+
}
56+
57+
// Adds given OpenFileState to FileTable entries. Returns fd (insertion index in entries).
58+
// If no free space is found, currentOpenFile will be appended to the back of the entries.
59+
for (__wasi_fd_t i = 0;; i++) {
60+
if (!fileTable[i]) {
61+
// Free open file entry.
62+
fileTable[i] = currentOpenFile;
63+
return i;
64+
}
65+
}
66+
67+
return -(EBADF);
68+
}
1869

1970
__wasi_errno_t __wasi_fd_write(
2071
__wasi_fd_t fd, const __wasi_ciovec_t* iovs, size_t iovs_len, __wasi_size_t* nwritten) {
21-
// FD 1 = STDOUT and FD 2 = STDERR.
22-
// Temporary hardcoding of filedescriptor values.
23-
// TODO: May not want to proxy stderr (fd == 2) to the main thread.
24-
// This will not show in HTML - a console.warn in a worker is suffficient.
25-
// This would be a change from the current FS.
26-
if (fd == 1 || fd == 2) {
27-
__wasi_size_t num = 0;
28-
for (size_t i = 0; i < iovs_len; i++) {
29-
const uint8_t* buf = iovs[i].buf;
30-
__wasi_size_t len = iovs[i].buf_len;
31-
for (__wasi_size_t j = 0; j < len; j++) {
32-
uint8_t current = buf[j];
33-
if (current == 0 || current == 10) {
34-
fd_write_stdstream_buffer.push_back('\0'); // for null-terminated C strings
35-
if (fd == 1) {
36-
emscripten_console_log(&fd_write_stdstream_buffer[0]);
37-
} else if (fd == 2) {
38-
emscripten_console_error(&fd_write_stdstream_buffer[0]);
39-
}
40-
fd_write_stdstream_buffer.clear();
41-
} else {
42-
fd_write_stdstream_buffer.push_back(current);
43-
}
44-
}
45-
num += len;
46-
}
47-
*nwritten = num;
72+
FileTable::Handle fileTable = FileTable::get();
73+
if (!fileTable[fd]) {
74+
return __WASI_ERRNO_BADF;
75+
}
76+
77+
auto file = fileTable[fd]->get().getFile()->get();
78+
79+
__wasi_size_t num = 0;
80+
for (size_t i = 0; i < iovs_len; i++) {
81+
const uint8_t* buf = iovs[i].buf;
82+
__wasi_size_t len = iovs[i].buf_len;
83+
84+
file.write(buf, len);
85+
num += len;
4886
}
49-
return 0;
87+
*nwritten = num;
88+
89+
return __WASI_ERRNO_SUCCESS;
5090
}
5191

5292
__wasi_errno_t __wasi_fd_seek(
@@ -62,7 +102,21 @@ __wasi_errno_t __wasi_fd_close(__wasi_fd_t fd) {
62102

63103
__wasi_errno_t __wasi_fd_read(
64104
__wasi_fd_t fd, const __wasi_iovec_t* iovs, size_t iovs_len, __wasi_size_t* nread) {
65-
emscripten_console_log("__wasi_fd_read has been temporarily stubbed and is inert");
66-
abort();
105+
FileTable::Handle fileTable = FileTable::get();
106+
if (!fileTable[fd]) {
107+
return __WASI_ERRNO_BADF;
108+
}
109+
110+
auto file = fileTable[fd]->get().getFile()->get();
111+
__wasi_size_t num = 0;
112+
for (size_t i = 0; i < iovs_len; i++) {
113+
const uint8_t* buf = iovs[i].buf;
114+
__wasi_size_t len = iovs[i].buf_len;
115+
116+
file.read(buf, len);
117+
num += len;
118+
}
119+
*nread = num;
120+
return __WASI_ERRNO_INVAL;
67121
}
68122
}

tests/common.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -556,8 +556,6 @@ def build(self, filename, libraries=[], includes=[], force_c=False, js_outfile=T
556556
suffix = '.js' if js_outfile else '.wasm'
557557
compiler = [compiler_for(filename, force_c)]
558558
if compiler[0] == EMCC:
559-
# TODO change test behaviour in the future when WASMFS becomes default file system
560-
# WASMFS is excluded here since it currently requires stdlib++ functions
561559
# TODO(https://github.com/emscripten-core/emscripten/issues/11121)
562560
# For historical reasons emcc compiles and links as C++ by default.
563561
# However we want to run our tests in a more strict manner. We can

0 commit comments

Comments
 (0)