Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
emscripten-core
GitHub Repository: emscripten-core/emscripten
Path: blob/main/src/lib/libmemfs.js
4150 views
1
/**
2
* @license
3
* Copyright 2013 The Emscripten Authors
4
* SPDX-License-Identifier: MIT
5
*/
6
7
addToLibrary({
8
$MEMFS__deps: ['$FS', '$mmapAlloc'],
9
$MEMFS: {
10
ops_table: null,
11
mount(mount) {
12
return MEMFS.createNode(null, '/', {{{ cDefs.S_IFDIR | 0o777 }}}, 0);
13
},
14
createNode(parent, name, mode, dev) {
15
if (FS.isBlkdev(mode) || FS.isFIFO(mode)) {
16
// no supported
17
throw new FS.ErrnoError({{{ cDefs.EPERM }}});
18
}
19
MEMFS.ops_table ||= {
20
dir: {
21
node: {
22
getattr: MEMFS.node_ops.getattr,
23
setattr: MEMFS.node_ops.setattr,
24
lookup: MEMFS.node_ops.lookup,
25
mknod: MEMFS.node_ops.mknod,
26
rename: MEMFS.node_ops.rename,
27
unlink: MEMFS.node_ops.unlink,
28
rmdir: MEMFS.node_ops.rmdir,
29
readdir: MEMFS.node_ops.readdir,
30
symlink: MEMFS.node_ops.symlink
31
},
32
stream: {
33
llseek: MEMFS.stream_ops.llseek
34
}
35
},
36
file: {
37
node: {
38
getattr: MEMFS.node_ops.getattr,
39
setattr: MEMFS.node_ops.setattr
40
},
41
stream: {
42
llseek: MEMFS.stream_ops.llseek,
43
read: MEMFS.stream_ops.read,
44
write: MEMFS.stream_ops.write,
45
mmap: MEMFS.stream_ops.mmap,
46
msync: MEMFS.stream_ops.msync
47
}
48
},
49
link: {
50
node: {
51
getattr: MEMFS.node_ops.getattr,
52
setattr: MEMFS.node_ops.setattr,
53
readlink: MEMFS.node_ops.readlink
54
},
55
stream: {}
56
},
57
chrdev: {
58
node: {
59
getattr: MEMFS.node_ops.getattr,
60
setattr: MEMFS.node_ops.setattr
61
},
62
stream: FS.chrdev_stream_ops
63
}
64
};
65
var node = FS.createNode(parent, name, mode, dev);
66
if (FS.isDir(node.mode)) {
67
node.node_ops = MEMFS.ops_table.dir.node;
68
node.stream_ops = MEMFS.ops_table.dir.stream;
69
node.contents = {};
70
} else if (FS.isFile(node.mode)) {
71
node.node_ops = MEMFS.ops_table.file.node;
72
node.stream_ops = MEMFS.ops_table.file.stream;
73
node.usedBytes = 0; // The actual number of bytes used in the typed array, as opposed to contents.length which gives the whole capacity.
74
// When the byte data of the file is populated, this will point to either a typed array, or a normal JS array. Typed arrays are preferred
75
// for performance, and used by default. However, typed arrays are not resizable like normal JS arrays are, so there is a small disk size
76
// penalty involved for appending file writes that continuously grow a file similar to std::vector capacity vs used -scheme.
77
node.contents = null;
78
} else if (FS.isLink(node.mode)) {
79
node.node_ops = MEMFS.ops_table.link.node;
80
node.stream_ops = MEMFS.ops_table.link.stream;
81
} else if (FS.isChrdev(node.mode)) {
82
node.node_ops = MEMFS.ops_table.chrdev.node;
83
node.stream_ops = MEMFS.ops_table.chrdev.stream;
84
}
85
node.atime = node.mtime = node.ctime = Date.now();
86
// add the new node to the parent
87
if (parent) {
88
parent.contents[name] = node;
89
parent.atime = parent.mtime = parent.ctime = node.atime;
90
}
91
return node;
92
},
93
94
// Given a file node, returns its file data converted to a typed array.
95
getFileDataAsTypedArray(node) {
96
if (!node.contents) return new Uint8Array(0);
97
if (node.contents.subarray) return node.contents.subarray(0, node.usedBytes); // Make sure to not return excess unused bytes.
98
return new Uint8Array(node.contents);
99
},
100
101
// Allocates a new backing store for the given node so that it can fit at least newSize amount of bytes.
102
// May allocate more, to provide automatic geometric increase and amortized linear performance appending writes.
103
// Never shrinks the storage.
104
expandFileStorage(node, newCapacity) {
105
var prevCapacity = node.contents ? node.contents.length : 0;
106
if (prevCapacity >= newCapacity) return; // No need to expand, the storage was already large enough.
107
// Don't expand strictly to the given requested limit if it's only a very small increase, but instead geometrically grow capacity.
108
// For small filesizes (<1MB), perform size*2 geometric increase, but for large sizes, do a much more conservative size*1.125 increase to
109
// avoid overshooting the allocation cap by a very large margin.
110
var CAPACITY_DOUBLING_MAX = 1024 * 1024;
111
newCapacity = Math.max(newCapacity, (prevCapacity * (prevCapacity < CAPACITY_DOUBLING_MAX ? 2.0 : 1.125)) >>> 0);
112
if (prevCapacity != 0) newCapacity = Math.max(newCapacity, 256); // At minimum allocate 256b for each file when expanding.
113
var oldContents = node.contents;
114
node.contents = new Uint8Array(newCapacity); // Allocate new storage.
115
if (node.usedBytes > 0) node.contents.set(oldContents.subarray(0, node.usedBytes), 0); // Copy old data over to the new storage.
116
},
117
118
// Performs an exact resize of the backing file storage to the given size, if the size is not exactly this, the storage is fully reallocated.
119
resizeFileStorage(node, newSize) {
120
if (node.usedBytes == newSize) return;
121
if (newSize == 0) {
122
node.contents = null; // Fully decommit when requesting a resize to zero.
123
node.usedBytes = 0;
124
} else {
125
var oldContents = node.contents;
126
node.contents = new Uint8Array(newSize); // Allocate new storage.
127
if (oldContents) {
128
node.contents.set(oldContents.subarray(0, Math.min(newSize, node.usedBytes))); // Copy old data over to the new storage.
129
}
130
node.usedBytes = newSize;
131
}
132
},
133
134
node_ops: {
135
getattr(node) {
136
var attr = {};
137
// device numbers reuse inode numbers.
138
attr.dev = FS.isChrdev(node.mode) ? node.id : 1;
139
attr.ino = node.id;
140
attr.mode = node.mode;
141
attr.nlink = 1;
142
attr.uid = 0;
143
attr.gid = 0;
144
attr.rdev = node.rdev;
145
if (FS.isDir(node.mode)) {
146
attr.size = 4096;
147
} else if (FS.isFile(node.mode)) {
148
attr.size = node.usedBytes;
149
} else if (FS.isLink(node.mode)) {
150
attr.size = node.link.length;
151
} else {
152
attr.size = 0;
153
}
154
attr.atime = new Date(node.atime);
155
attr.mtime = new Date(node.mtime);
156
attr.ctime = new Date(node.ctime);
157
// NOTE: In our implementation, st_blocks = Math.ceil(st_size/st_blksize),
158
// but this is not required by the standard.
159
attr.blksize = 4096;
160
attr.blocks = Math.ceil(attr.size / attr.blksize);
161
return attr;
162
},
163
setattr(node, attr) {
164
for (const key of ["mode", "atime", "mtime", "ctime"]) {
165
if (attr[key] != null) {
166
node[key] = attr[key];
167
}
168
}
169
if (attr.size !== undefined) {
170
MEMFS.resizeFileStorage(node, attr.size);
171
}
172
},
173
lookup(parent, name) {
174
#if ASSERTIONS
175
throw new FS.ErrnoError({{{ cDefs.ENOENT }}});
176
#else
177
// This error may happen quite a bit. To avoid overhead we reuse it (and
178
// suffer a lack of stack info).
179
if (!MEMFS.doesNotExistError) {
180
MEMFS.doesNotExistError = new FS.ErrnoError({{{ cDefs.ENOENT }}});
181
/** @suppress {checkTypes} */
182
MEMFS.doesNotExistError.stack = '<generic error, no stack>';
183
}
184
throw MEMFS.doesNotExistError;
185
#endif
186
},
187
mknod(parent, name, mode, dev) {
188
return MEMFS.createNode(parent, name, mode, dev);
189
},
190
rename(old_node, new_dir, new_name) {
191
var new_node;
192
try {
193
new_node = FS.lookupNode(new_dir, new_name);
194
} catch (e) {}
195
if (new_node) {
196
if (FS.isDir(old_node.mode)) {
197
// if we're overwriting a directory at new_name, make sure it's empty.
198
for (var i in new_node.contents) {
199
throw new FS.ErrnoError({{{ cDefs.ENOTEMPTY }}});
200
}
201
}
202
FS.hashRemoveNode(new_node);
203
}
204
// do the internal rewiring
205
delete old_node.parent.contents[old_node.name];
206
new_dir.contents[new_name] = old_node;
207
old_node.name = new_name;
208
new_dir.ctime = new_dir.mtime = old_node.parent.ctime = old_node.parent.mtime = Date.now();
209
},
210
unlink(parent, name) {
211
delete parent.contents[name];
212
parent.ctime = parent.mtime = Date.now();
213
},
214
rmdir(parent, name) {
215
var node = FS.lookupNode(parent, name);
216
for (var i in node.contents) {
217
throw new FS.ErrnoError({{{ cDefs.ENOTEMPTY }}});
218
}
219
delete parent.contents[name];
220
parent.ctime = parent.mtime = Date.now();
221
},
222
readdir(node) {
223
return ['.', '..', ...Object.keys(node.contents)];
224
},
225
symlink(parent, newname, oldpath) {
226
var node = MEMFS.createNode(parent, newname, 0o777 | {{{ cDefs.S_IFLNK }}}, 0);
227
node.link = oldpath;
228
return node;
229
},
230
readlink(node) {
231
if (!FS.isLink(node.mode)) {
232
throw new FS.ErrnoError({{{ cDefs.EINVAL }}});
233
}
234
return node.link;
235
},
236
},
237
stream_ops: {
238
read(stream, buffer, offset, length, position) {
239
var contents = stream.node.contents;
240
if (position >= stream.node.usedBytes) return 0;
241
var size = Math.min(stream.node.usedBytes - position, length);
242
#if ASSERTIONS
243
assert(size >= 0);
244
#endif
245
if (size > 8 && contents.subarray) { // non-trivial, and typed array
246
buffer.set(contents.subarray(position, position + size), offset);
247
} else {
248
for (var i = 0; i < size; i++) buffer[offset + i] = contents[position + i];
249
}
250
return size;
251
},
252
253
// Writes the byte range (buffer[offset], buffer[offset+length]) to offset 'position' into the file pointed by 'stream'
254
// canOwn: A boolean that tells if this function can take ownership of the passed in buffer from the subbuffer portion
255
// that the typed array view 'buffer' points to. The underlying ArrayBuffer can be larger than that, but
256
// canOwn=true will not take ownership of the portion outside the bytes addressed by the view. This means that
257
// with canOwn=true, creating a copy of the bytes is avoided, but the caller shouldn't touch the passed in range
258
// of bytes anymore since their contents now represent file data inside the filesystem.
259
write(stream, buffer, offset, length, position, canOwn) {
260
#if ASSERTIONS
261
// The data buffer should be a typed array view
262
assert(!(buffer instanceof ArrayBuffer));
263
#endif
264
#if ALLOW_MEMORY_GROWTH
265
// If the buffer is located in main memory (HEAP), and if
266
// memory can grow, we can't hold on to references of the
267
// memory buffer, as they may get invalidated. That means we
268
// need to do copy its contents.
269
if (buffer.buffer === HEAP8.buffer) {
270
canOwn = false;
271
}
272
#endif // ALLOW_MEMORY_GROWTH
273
274
if (!length) return 0;
275
var node = stream.node;
276
node.mtime = node.ctime = Date.now();
277
278
if (buffer.subarray && (!node.contents || node.contents.subarray)) { // This write is from a typed array to a typed array?
279
if (canOwn) {
280
#if ASSERTIONS
281
assert(position === 0, 'canOwn must imply no weird position inside the file');
282
#endif
283
node.contents = buffer.subarray(offset, offset + length);
284
node.usedBytes = length;
285
return length;
286
} else if (node.usedBytes === 0 && position === 0) { // If this is a simple first write to an empty file, do a fast set since we don't need to care about old data.
287
node.contents = buffer.slice(offset, offset + length);
288
node.usedBytes = length;
289
return length;
290
} else if (position + length <= node.usedBytes) { // Writing to an already allocated and used subrange of the file?
291
node.contents.set(buffer.subarray(offset, offset + length), position);
292
return length;
293
}
294
}
295
296
// Appending to an existing file and we need to reallocate, or source data did not come as a typed array.
297
MEMFS.expandFileStorage(node, position+length);
298
if (node.contents.subarray && buffer.subarray) {
299
// Use typed array write which is available.
300
node.contents.set(buffer.subarray(offset, offset + length), position);
301
} else {
302
for (var i = 0; i < length; i++) {
303
node.contents[position + i] = buffer[offset + i]; // Or fall back to manual write if not.
304
}
305
}
306
node.usedBytes = Math.max(node.usedBytes, position + length);
307
return length;
308
},
309
310
llseek(stream, offset, whence) {
311
var position = offset;
312
if (whence === {{{ cDefs.SEEK_CUR }}}) {
313
position += stream.position;
314
} else if (whence === {{{ cDefs.SEEK_END }}}) {
315
if (FS.isFile(stream.node.mode)) {
316
position += stream.node.usedBytes;
317
}
318
}
319
if (position < 0) {
320
throw new FS.ErrnoError({{{ cDefs.EINVAL }}});
321
}
322
return position;
323
},
324
mmap(stream, length, position, prot, flags) {
325
if (!FS.isFile(stream.node.mode)) {
326
throw new FS.ErrnoError({{{ cDefs.ENODEV }}});
327
}
328
var ptr;
329
var allocated;
330
var contents = stream.node.contents;
331
// Only make a new copy when MAP_PRIVATE is specified.
332
if (!(flags & {{{ cDefs.MAP_PRIVATE }}}) && contents && contents.buffer === HEAP8.buffer) {
333
// We can't emulate MAP_SHARED when the file is not backed by the
334
// buffer we're mapping to (e.g. the HEAP buffer).
335
allocated = false;
336
ptr = contents.byteOffset;
337
} else {
338
allocated = true;
339
ptr = mmapAlloc(length);
340
if (!ptr) {
341
throw new FS.ErrnoError({{{ cDefs.ENOMEM }}});
342
}
343
if (contents) {
344
// Try to avoid unnecessary slices.
345
if (position > 0 || position + length < contents.length) {
346
if (contents.subarray) {
347
contents = contents.subarray(position, position + length);
348
} else {
349
contents = Array.prototype.slice.call(contents, position, position + length);
350
}
351
}
352
HEAP8.set(contents, ptr);
353
}
354
}
355
return { ptr, allocated };
356
},
357
msync(stream, buffer, offset, length, mmapFlags) {
358
MEMFS.stream_ops.write(stream, buffer, 0, length, offset, false);
359
// should we check if bytesWritten and length are the same?
360
return 0;
361
}
362
}
363
}
364
});
365
366
367