Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
emscripten-core
GitHub Repository: emscripten-core/emscripten
Path: blob/main/system/lib/wasmfs/wasmfs.cpp
6174 views
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
6
// This file defines the global state.
7
8
#include <emscripten/threading.h>
9
10
#include "memory_backend.h"
11
#include "paths.h"
12
#include "special_files.h"
13
#include "wasmfs.h"
14
#include "wasmfs_internal.h"
15
16
namespace wasmfs {
17
18
#ifdef WASMFS_CASE_INSENSITIVE
19
backend_t createIgnoreCaseBackend(std::function<backend_t()> createBackend);
20
#endif
21
22
// The below lines are included to make the compiler believe that the global
23
// constructor is part of a system header, which is necessary to work around a
24
// compilation error about using a reserved init priority less than 101. This
25
// ensures that the global state of the file system is constructed before
26
// anything else. ATTENTION: No other static global objects should be defined
27
// besides wasmFS. Due to # define _LIBCPP_INIT_PRIORITY_MAX
28
// __attribute__((init_priority(101))), we must use init priority 100 (reserved
29
// system priority) since wasmFS is a system level component.
30
// TODO: consider instead adding this in libc's startup code.
31
// WARNING: Maintain # n + 1 "wasmfs.cpp" 3 where n = line number.
32
# 29 "wasmfs.cpp" 3
33
__attribute__((init_priority(100))) WasmFS wasmFS;
34
# 31 "wasmfs.cpp"
35
36
// If the user does not implement this hook, do nothing.
37
__attribute__((weak)) extern "C" void wasmfs_before_preload(void) {}
38
39
// Set up global data structures and preload files.
40
WasmFS::WasmFS() : rootDirectory(initRootDirectory()), cwd(rootDirectory) {
41
wasmfs_before_preload();
42
preloadFiles();
43
}
44
45
// Manual integration with LSan. LSan installs itself during startup at the
46
// first allocation, which happens inside WasmFS code (since the WasmFS global
47
// object creates some data structures). As a result LSan's atexit() destructor
48
// will be called last, after WasmFS is cleaned up, since atexit() calls work
49
// are LIFO (like a stack). But that is a problem, since if WasmFS has shut
50
// down and deallocated itself then the leak code cannot actually print any of
51
// its findings, if it has any. To avoid that, define the LSan entry point as a
52
// weak symbol, and call it; if LSan is not enabled this can be optimized out,
53
// and if LSan is enabled then we'll check for leaks right at the start of the
54
// WasmFS destructor, which is the last time during which it is valid to print.
55
// (Note that this means we can't find leaks inside WasmFS code itself, but that
56
// seems fundamentally impossible for the above reasons, unless we made LSan log
57
// its findings in a way that does not depend on normal file I/O.)
58
__attribute__((weak)) extern "C" void __lsan_do_leak_check(void) {}
59
60
extern "C" void wasmfs_flush(void) {
61
// Flush musl libc streams.
62
fflush(0);
63
64
// Flush our own streams. TODO: flush all backends.
65
(void)SpecialFiles::getStdout()->locked().flush();
66
(void)SpecialFiles::getStderr()->locked().flush();
67
}
68
69
WasmFS::~WasmFS() {
70
// See comment above on this function.
71
//
72
// Note that it is ok for us to call it here, as LSan internally makes all
73
// calls after the first into no-ops. That is, calling it here makes the one
74
// time that leak checks are run be done here, or potentially earlier, but not
75
// later; and as mentioned in the comment above, this is the latest possible
76
// time for the checks to run (since right after this nothing can be printed).
77
__lsan_do_leak_check();
78
79
// TODO: Integrate musl exit() which would flush the libc part for us. That
80
// might also help with destructor priority - we need to happen last.
81
// (But we would still need to flush the internal WasmFS buffers, see
82
// wasmfs_flush() and the comment on it in the header.)
83
wasmfs_flush();
84
85
// Break the reference cycle caused by the root directory being its own
86
// parent.
87
rootDirectory->locked().setParent(nullptr);
88
}
89
90
// Special backends that want to install themselves as the root use this hook.
91
// Otherwise, we use the default backends.
92
__attribute__((weak)) extern "C" backend_t wasmfs_create_root_dir(void) {
93
#ifdef WASMFS_CASE_INSENSITIVE
94
return createIgnoreCaseBackend([]() { return createMemoryBackend(); });
95
#else
96
return createMemoryBackend();
97
#endif
98
}
99
100
std::shared_ptr<Directory> WasmFS::initRootDirectory() {
101
auto rootBackend = wasmfs_create_root_dir();
102
auto rootDirectory =
103
rootBackend->createDirectory(S_IRUGO | S_IXUGO | S_IWUGO);
104
auto lockedRoot = rootDirectory->locked();
105
106
// The root directory is its own parent.
107
lockedRoot.setParent(rootDirectory);
108
109
// If the /dev/ directory does not already exist, create it. (It may already
110
// exist in NODERAWFS mode, or if those files have been preloaded.)
111
auto devDir = lockedRoot.insertDirectory("dev", S_IRUGO | S_IXUGO);
112
if (devDir) {
113
auto lockedDev = devDir->locked();
114
lockedDev.mountChild("null", SpecialFiles::getNull());
115
lockedDev.mountChild("stdin", SpecialFiles::getStdin());
116
lockedDev.mountChild("stdout", SpecialFiles::getStdout());
117
lockedDev.mountChild("stderr", SpecialFiles::getStderr());
118
lockedDev.mountChild("random", SpecialFiles::getRandom());
119
lockedDev.mountChild("urandom", SpecialFiles::getURandom());
120
}
121
122
// As with the /dev/ directory, it is not an error for /tmp/ to already
123
// exist.
124
lockedRoot.insertDirectory("tmp", S_IRWXUGO);
125
126
return rootDirectory;
127
}
128
129
// Initialize files specified by the --preload-file option.
130
// Set up directories and files in wasmFS$preloadedDirs and
131
// wasmFS$preloadedFiles from JS. This function will be called before any file
132
// operation to ensure any preloaded files are eagerly available for use.
133
void WasmFS::preloadFiles() {
134
// Debug builds only: add check to ensure preloadFiles() is called once.
135
#ifndef NDEBUG
136
static std::atomic<int> timesCalled;
137
timesCalled++;
138
assert(timesCalled == 1);
139
#endif
140
141
// Ensure that files are preloaded from the main thread.
142
assert(emscripten_is_main_runtime_thread());
143
144
auto numFiles = _wasmfs_get_num_preloaded_files();
145
auto numDirs = _wasmfs_get_num_preloaded_dirs();
146
147
// If there are no preloaded files, exit early.
148
if (numDirs == 0 && numFiles == 0) {
149
return;
150
}
151
152
// Iterate through wasmFS$preloadedDirs to obtain a parent and child pair.
153
// Ex. Module['FS_createPath']("/foo/parent", "child", true, true);
154
for (int i = 0; i < numDirs; i++) {
155
char parentPath[PATH_MAX] = {};
156
_wasmfs_get_preloaded_parent_path(i, parentPath);
157
158
auto parsed = path::parseFile(parentPath);
159
std::shared_ptr<Directory> parentDir;
160
if (parsed.getError() ||
161
!(parentDir = parsed.getFile()->dynCast<Directory>())) {
162
emscripten_err(
163
"Fatal error during directory creation in file preloading.");
164
abort();
165
}
166
167
char childName[PATH_MAX] = {};
168
_wasmfs_get_preloaded_child_path(i, childName);
169
170
auto lockedParentDir = parentDir->locked();
171
if (lockedParentDir.getChild(childName)) {
172
// The child already exists, so we don't need to do anything here.
173
continue;
174
}
175
176
auto inserted =
177
lockedParentDir.insertDirectory(childName, S_IRUGO | S_IXUGO);
178
assert(inserted && "TODO: handle preload insertion errors");
179
}
180
181
for (int i = 0; i < numFiles; i++) {
182
char fileName[PATH_MAX] = {};
183
_wasmfs_get_preloaded_path_name(i, fileName);
184
185
auto mode = _wasmfs_get_preloaded_file_mode(i);
186
187
auto parsed = path::parseParent(fileName);
188
if (parsed.getError()) {
189
emscripten_err("Fatal error during file preloading");
190
abort();
191
}
192
auto& [parent, childName] = parsed.getParentChild();
193
auto created =
194
parent->locked().insertDataFile(std::string(childName), (mode_t)mode);
195
assert(created && "TODO: handle preload insertion errors");
196
created->locked().preloadFromJS(i);
197
}
198
}
199
200
} // namespace wasmfs
201
202