Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
emscripten-core
GitHub Repository: emscripten-core/emscripten
Path: blob/main/src/lib/libfs.js
6161 views
1
/**
2
* @license
3
* Copyright 2013 The Emscripten Authors
4
* SPDX-License-Identifier: MIT
5
*/
6
7
var LibraryFS = {
8
$FS__deps: ['$randomFill', '$PATH', '$PATH_FS', '$TTY', '$MEMFS',
9
'$FS_modeStringToFlags',
10
'$FS_getMode',
11
'$intArrayFromString',
12
#if LibraryManager.has('libidbfs.js')
13
'$IDBFS',
14
#endif
15
#if LibraryManager.has('libnodefs.js')
16
'$NODEFS',
17
#endif
18
#if LibraryManager.has('libworkerfs.js')
19
'$WORKERFS',
20
#endif
21
#if LibraryManager.has('libnoderawfs.js')
22
'$NODERAWFS',
23
#endif
24
#if LibraryManager.has('libproxyfs.js')
25
'$PROXYFS',
26
#endif
27
#if ASSERTIONS
28
'$strError', '$ERRNO_CODES',
29
#endif
30
#if !MINIMAL_RUNTIME
31
'$FS_createPreloadedFile',
32
#endif
33
],
34
$FS__postset: () => {
35
// TODO: do we need noFSInit?
36
addAtInit(`if (!Module['noFSInit'] && !FS.initialized) FS.init();`);
37
addAtPostCtor('FS.ignorePermissions = false;');
38
addAtExit('FS.quit();');
39
return `
40
#if !MINIMAL_RUNTIME
41
FS.createPreloadedFile = FS_createPreloadedFile;
42
FS.preloadFile = FS_preloadFile;
43
#endif
44
FS.staticInit();`;
45
},
46
$FS: {
47
root: null,
48
mounts: [],
49
devices: {},
50
streams: [],
51
nextInode: 1,
52
nameTable: null,
53
currentPath: '/',
54
initialized: false,
55
// Whether we are currently ignoring permissions. Useful when preparing the
56
// filesystem and creating files inside read-only folders.
57
// This is set to false during `preInit`, allowing you to modify the
58
// filesystem freely up until that point (e.g. during `preRun`).
59
ignorePermissions: true,
60
#if FS_DEBUG
61
trackingDelegate: {},
62
#endif
63
filesystems: null,
64
syncFSRequests: 0, // we warn if there are multiple in flight at once
65
#if expectToReceiveOnModule('logReadFiles')
66
readFiles: {},
67
#endif
68
#if ASSERTIONS
69
ErrnoError: class extends Error {
70
#else
71
ErrnoError: class {
72
#endif
73
name = 'ErrnoError';
74
// We set the `name` property to be able to identify `FS.ErrnoError`
75
// - the `name` is a standard ECMA-262 property of error objects. Kind of good to have it anyway.
76
// - when using PROXYFS, an error can come from an underlying FS
77
// as different FS objects have their own FS.ErrnoError each,
78
// the test `err instanceof FS.ErrnoError` won't detect an error coming from another filesystem, causing bugs.
79
// we'll use the reliable test `err.name == "ErrnoError"` instead
80
constructor(errno) {
81
#if ASSERTIONS
82
super(runtimeInitialized ? strError(errno) : '');
83
#endif
84
this.errno = errno;
85
#if ASSERTIONS
86
for (var key in ERRNO_CODES) {
87
if (ERRNO_CODES[key] === errno) {
88
this.code = key;
89
break;
90
}
91
}
92
#endif
93
}
94
},
95
96
FSStream: class {
97
shared = {};
98
#if USE_CLOSURE_COMPILER
99
// Closure compiler requires us to declare all properties ahead of time
100
node = null;
101
#endif
102
get object() {
103
return this.node;
104
}
105
set object(val) {
106
this.node = val;
107
}
108
get isRead() {
109
return (this.flags & {{{ cDefs.O_ACCMODE }}}) !== {{{ cDefs.O_WRONLY }}};
110
}
111
get isWrite() {
112
return (this.flags & {{{ cDefs.O_ACCMODE }}}) !== {{{ cDefs.O_RDONLY }}};
113
}
114
get isAppend() {
115
return (this.flags & {{{ cDefs.O_APPEND }}});
116
}
117
get flags() {
118
return this.shared.flags;
119
}
120
set flags(val) {
121
this.shared.flags = val;
122
}
123
get position() {
124
return this.shared.position;
125
}
126
set position(val) {
127
this.shared.position = val;
128
}
129
},
130
FSNode: class {
131
node_ops = {};
132
stream_ops = {};
133
readMode = {{{ cDefs.S_IRUGO }}} | {{{ cDefs.S_IXUGO }}};
134
writeMode = {{{ cDefs.S_IWUGO }}};
135
mounted = null;
136
constructor(parent, name, mode, rdev) {
137
if (!parent) {
138
parent = this; // root node sets parent to itself
139
}
140
this.parent = parent;
141
this.mount = parent.mount;
142
this.id = FS.nextInode++;
143
this.name = name;
144
this.mode = mode;
145
this.rdev = rdev;
146
this.atime = this.mtime = this.ctime = Date.now();
147
}
148
get read() {
149
return (this.mode & this.readMode) === this.readMode;
150
}
151
set read(val) {
152
val ? this.mode |= this.readMode : this.mode &= ~this.readMode;
153
}
154
get write() {
155
return (this.mode & this.writeMode) === this.writeMode;
156
}
157
set write(val) {
158
val ? this.mode |= this.writeMode : this.mode &= ~this.writeMode;
159
}
160
get isFolder() {
161
return FS.isDir(this.mode);
162
}
163
get isDevice() {
164
return FS.isChrdev(this.mode);
165
}
166
},
167
168
//
169
// paths
170
//
171
lookupPath(path, opts = {}) {
172
if (!path) {
173
throw new FS.ErrnoError({{{ cDefs.ENOENT }}});
174
}
175
opts.follow_mount ??= true
176
177
if (!PATH.isAbs(path)) {
178
path = FS.cwd() + '/' + path;
179
}
180
181
// limit max consecutive symlinks to 40 (SYMLOOP_MAX).
182
linkloop: for (var nlinks = 0; nlinks < 40; nlinks++) {
183
// split the absolute path
184
var parts = path.split('/').filter((p) => !!p);
185
186
// start at the root
187
var current = FS.root;
188
var current_path = '/';
189
190
for (var i = 0; i < parts.length; i++) {
191
var islast = (i === parts.length-1);
192
if (islast && opts.parent) {
193
// stop resolving
194
break;
195
}
196
197
if (parts[i] === '.') {
198
continue;
199
}
200
201
if (parts[i] === '..') {
202
current_path = PATH.dirname(current_path);
203
if (FS.isRoot(current)) {
204
path = current_path + '/' + parts.slice(i + 1).join('/');
205
// We're making progress here, don't let many consecutive ..'s
206
// lead to ELOOP
207
nlinks--;
208
continue linkloop;
209
} else {
210
current = current.parent;
211
}
212
continue;
213
}
214
215
current_path = PATH.join2(current_path, parts[i]);
216
try {
217
current = FS.lookupNode(current, parts[i]);
218
} catch (e) {
219
// if noent_okay is true, suppress a ENOENT in the last component
220
// and return an object with an undefined node. This is needed for
221
// resolving symlinks in the path when creating a file.
222
if ((e?.errno === {{{ cDefs.ENOENT }}}) && islast && opts.noent_okay) {
223
return { path: current_path };
224
}
225
throw e;
226
}
227
228
// jump to the mount's root node if this is a mountpoint
229
if (FS.isMountpoint(current) && (!islast || opts.follow_mount)) {
230
current = current.mounted.root;
231
}
232
233
// by default, lookupPath will not follow a symlink if it is the final path component.
234
// setting opts.follow = true will override this behavior.
235
if (FS.isLink(current.mode) && (!islast || opts.follow)) {
236
if (!current.node_ops.readlink) {
237
throw new FS.ErrnoError({{{ cDefs.ENOSYS }}});
238
}
239
var link = current.node_ops.readlink(current);
240
if (!PATH.isAbs(link)) {
241
link = PATH.dirname(current_path) + '/' + link;
242
}
243
path = link + '/' + parts.slice(i + 1).join('/');
244
continue linkloop;
245
}
246
}
247
return { path: current_path, node: current };
248
}
249
throw new FS.ErrnoError({{{ cDefs.ELOOP }}});
250
},
251
getPath(node) {
252
var path;
253
while (true) {
254
if (FS.isRoot(node)) {
255
var mount = node.mount.mountpoint;
256
if (!path) return mount;
257
return mount[mount.length-1] !== '/' ? `${mount}/${path}` : mount + path;
258
}
259
path = path ? `${node.name}/${path}` : node.name;
260
node = node.parent;
261
}
262
},
263
264
//
265
// nodes
266
//
267
hashName(parentid, name) {
268
var hash = 0;
269
270
#if CASE_INSENSITIVE_FS
271
name = name.toLowerCase();
272
#endif
273
274
for (var i = 0; i < name.length; i++) {
275
hash = ((hash << 5) - hash + name.charCodeAt(i)) | 0;
276
}
277
return ((parentid + hash) >>> 0) % FS.nameTable.length;
278
},
279
hashAddNode(node) {
280
var hash = FS.hashName(node.parent.id, node.name);
281
node.name_next = FS.nameTable[hash];
282
FS.nameTable[hash] = node;
283
},
284
hashRemoveNode(node) {
285
var hash = FS.hashName(node.parent.id, node.name);
286
if (FS.nameTable[hash] === node) {
287
FS.nameTable[hash] = node.name_next;
288
} else {
289
var current = FS.nameTable[hash];
290
while (current) {
291
if (current.name_next === node) {
292
current.name_next = node.name_next;
293
break;
294
}
295
current = current.name_next;
296
}
297
}
298
},
299
lookupNode(parent, name) {
300
var errCode = FS.mayLookup(parent);
301
if (errCode) {
302
throw new FS.ErrnoError(errCode);
303
}
304
var hash = FS.hashName(parent.id, name);
305
#if CASE_INSENSITIVE_FS
306
name = name.toLowerCase();
307
#endif
308
for (var node = FS.nameTable[hash]; node; node = node.name_next) {
309
var nodeName = node.name;
310
#if CASE_INSENSITIVE_FS
311
nodeName = nodeName.toLowerCase();
312
#endif
313
if (node.parent.id === parent.id && nodeName === name) {
314
return node;
315
}
316
}
317
// if we failed to find it in the cache, call into the VFS
318
return FS.lookup(parent, name);
319
},
320
createNode(parent, name, mode, rdev) {
321
#if ASSERTIONS
322
assert(typeof parent == 'object')
323
#endif
324
var node = new FS.FSNode(parent, name, mode, rdev);
325
326
FS.hashAddNode(node);
327
328
return node;
329
},
330
destroyNode(node) {
331
FS.hashRemoveNode(node);
332
},
333
isRoot(node) {
334
return node === node.parent;
335
},
336
isMountpoint(node) {
337
return !!node.mounted;
338
},
339
isFile(mode) {
340
return (mode & {{{ cDefs.S_IFMT }}}) === {{{ cDefs.S_IFREG }}};
341
},
342
isDir(mode) {
343
return (mode & {{{ cDefs.S_IFMT }}}) === {{{ cDefs.S_IFDIR }}};
344
},
345
isLink(mode) {
346
return (mode & {{{ cDefs.S_IFMT }}}) === {{{ cDefs.S_IFLNK }}};
347
},
348
isChrdev(mode) {
349
return (mode & {{{ cDefs.S_IFMT }}}) === {{{ cDefs.S_IFCHR }}};
350
},
351
isBlkdev(mode) {
352
return (mode & {{{ cDefs.S_IFMT }}}) === {{{ cDefs.S_IFBLK }}};
353
},
354
isFIFO(mode) {
355
return (mode & {{{ cDefs.S_IFMT }}}) === {{{ cDefs.S_IFIFO }}};
356
},
357
isSocket(mode) {
358
return (mode & {{{ cDefs.S_IFSOCK }}}) === {{{ cDefs.S_IFSOCK }}};
359
},
360
361
//
362
// permissions
363
//
364
// convert O_* bitmask to a string for nodePermissions
365
flagsToPermissionString(flag) {
366
var perms = ['r', 'w', 'rw'][flag & 3];
367
if ((flag & {{{ cDefs.O_TRUNC }}})) {
368
perms += 'w';
369
}
370
return perms;
371
},
372
nodePermissions(node, perms) {
373
if (FS.ignorePermissions) {
374
return 0;
375
}
376
// return 0 if any user, group or owner bits are set.
377
if (perms.includes('r') && !(node.mode & {{{ cDefs.S_IRUGO }}})) {
378
return {{{ cDefs.EACCES }}};
379
}
380
if (perms.includes('w') && !(node.mode & {{{ cDefs.S_IWUGO }}})) {
381
return {{{ cDefs.EACCES }}};
382
}
383
if (perms.includes('x') && !(node.mode & {{{ cDefs.S_IXUGO }}})) {
384
return {{{ cDefs.EACCES }}};
385
}
386
return 0;
387
},
388
mayLookup(dir) {
389
if (!FS.isDir(dir.mode)) return {{{ cDefs.ENOTDIR }}};
390
var errCode = FS.nodePermissions(dir, 'x');
391
if (errCode) return errCode;
392
if (!dir.node_ops.lookup) return {{{ cDefs.EACCES }}};
393
return 0;
394
},
395
mayCreate(dir, name) {
396
if (!FS.isDir(dir.mode)) {
397
return {{{ cDefs.ENOTDIR }}};
398
}
399
try {
400
var node = FS.lookupNode(dir, name);
401
return {{{ cDefs.EEXIST }}};
402
} catch (e) {
403
}
404
return FS.nodePermissions(dir, 'wx');
405
},
406
mayDelete(dir, name, isdir) {
407
var node;
408
try {
409
node = FS.lookupNode(dir, name);
410
} catch (e) {
411
return e.errno;
412
}
413
var errCode = FS.nodePermissions(dir, 'wx');
414
if (errCode) {
415
return errCode;
416
}
417
if (isdir) {
418
if (!FS.isDir(node.mode)) {
419
return {{{ cDefs.ENOTDIR }}};
420
}
421
if (FS.isRoot(node) || FS.getPath(node) === FS.cwd()) {
422
return {{{ cDefs.EBUSY }}};
423
}
424
} else if (FS.isDir(node.mode)) {
425
return {{{ cDefs.EISDIR }}};
426
}
427
return 0;
428
},
429
mayOpen(node, flags) {
430
if (!node) {
431
return {{{ cDefs.ENOENT }}};
432
}
433
if (FS.isLink(node.mode)) {
434
return {{{ cDefs.ELOOP }}};
435
}
436
var mode = FS.flagsToPermissionString(flags);
437
if (FS.isDir(node.mode)) {
438
// opening for write
439
// TODO: check for O_SEARCH? (== search for dir only)
440
if (mode !== 'r' || (flags & ({{{ cDefs.O_TRUNC }}} | {{{ cDefs.O_CREAT }}}))) {
441
return {{{ cDefs.EISDIR }}};
442
}
443
}
444
return FS.nodePermissions(node, mode);
445
},
446
checkOpExists(op, err) {
447
if (!op) {
448
throw new FS.ErrnoError(err);
449
}
450
return op;
451
},
452
453
//
454
// streams
455
//
456
MAX_OPEN_FDS: 4096,
457
nextfd() {
458
for (var fd = 0; fd <= FS.MAX_OPEN_FDS; fd++) {
459
if (!FS.streams[fd]) {
460
return fd;
461
}
462
}
463
throw new FS.ErrnoError({{{ cDefs.EMFILE }}});
464
},
465
getStreamChecked(fd) {
466
var stream = FS.getStream(fd);
467
if (!stream) {
468
throw new FS.ErrnoError({{{ cDefs.EBADF }}});
469
}
470
return stream;
471
},
472
getStream: (fd) => FS.streams[fd],
473
// TODO parameterize this function such that a stream
474
// object isn't directly passed in. not possible until
475
// SOCKFS is completed.
476
createStream(stream, fd = -1) {
477
#if ASSERTIONS
478
assert(fd >= -1);
479
#endif
480
481
// clone it, so we can return an instance of FSStream
482
stream = Object.assign(new FS.FSStream(), stream);
483
if (fd == -1) {
484
fd = FS.nextfd();
485
}
486
stream.fd = fd;
487
FS.streams[fd] = stream;
488
return stream;
489
},
490
closeStream(fd) {
491
FS.streams[fd] = null;
492
},
493
dupStream(origStream, fd = -1) {
494
var stream = FS.createStream(origStream, fd);
495
stream.stream_ops?.dup?.(stream);
496
return stream;
497
},
498
doSetAttr(stream, node, attr) {
499
var setattr = stream?.stream_ops.setattr;
500
var arg = setattr ? stream : node;
501
setattr ??= node.node_ops.setattr;
502
FS.checkOpExists(setattr, {{{ cDefs.EPERM }}})
503
setattr(arg, attr);
504
},
505
506
//
507
// devices
508
//
509
// each character device consists of a device id + stream operations.
510
// when a character device node is created (e.g. /dev/stdin) it is
511
// assigned a device id that lets us map back to the actual device.
512
// by default, each character device stream (e.g. _stdin) uses chrdev_stream_ops.
513
// however, once opened, the stream's operations are overridden with
514
// the operations of the device its underlying node maps back to.
515
chrdev_stream_ops: {
516
open(stream) {
517
var device = FS.getDevice(stream.node.rdev);
518
// override node's stream ops with the device's
519
stream.stream_ops = device.stream_ops;
520
// forward the open call
521
stream.stream_ops.open?.(stream);
522
},
523
llseek() {
524
throw new FS.ErrnoError({{{ cDefs.ESPIPE }}});
525
}
526
},
527
major: (dev) => ((dev) >> 8),
528
minor: (dev) => ((dev) & 0xff),
529
makedev: (ma, mi) => ((ma) << 8 | (mi)),
530
registerDevice(dev, ops) {
531
FS.devices[dev] = { stream_ops: ops };
532
},
533
getDevice: (dev) => FS.devices[dev],
534
535
//
536
// core
537
//
538
getMounts(mount) {
539
var mounts = [];
540
var check = [mount];
541
542
while (check.length) {
543
var m = check.pop();
544
545
mounts.push(m);
546
547
check.push(...m.mounts);
548
}
549
550
return mounts;
551
},
552
syncfs(populate, callback) {
553
if (typeof populate == 'function') {
554
callback = populate;
555
populate = false;
556
}
557
558
FS.syncFSRequests++;
559
560
if (FS.syncFSRequests > 1) {
561
err(`warning: ${FS.syncFSRequests} FS.syncfs operations in flight at once, probably just doing extra work`);
562
}
563
564
var mounts = FS.getMounts(FS.root.mount);
565
var completed = 0;
566
567
function doCallback(errCode) {
568
#if ASSERTIONS
569
assert(FS.syncFSRequests > 0);
570
#endif
571
FS.syncFSRequests--;
572
return callback(errCode);
573
}
574
575
function done(errCode) {
576
if (errCode) {
577
if (!done.errored) {
578
done.errored = true;
579
return doCallback(errCode);
580
}
581
return;
582
}
583
if (++completed >= mounts.length) {
584
doCallback(null);
585
}
586
};
587
588
// sync all mounts
589
for (var mount of mounts) {
590
if (mount.type.syncfs) {
591
mount.type.syncfs(mount, populate, done);
592
} else {
593
done(null);
594
}
595
}
596
},
597
mount(type, opts, mountpoint) {
598
#if ASSERTIONS
599
if (typeof type == 'string') {
600
// The filesystem was not included, and instead we have an error
601
// message stored in the variable.
602
throw type;
603
}
604
#endif
605
var root = mountpoint === '/';
606
var pseudo = !mountpoint;
607
var node;
608
609
if (root && FS.root) {
610
throw new FS.ErrnoError({{{ cDefs.EBUSY }}});
611
} else if (!root && !pseudo) {
612
var lookup = FS.lookupPath(mountpoint, { follow_mount: false });
613
614
mountpoint = lookup.path; // use the absolute path
615
node = lookup.node;
616
617
if (FS.isMountpoint(node)) {
618
throw new FS.ErrnoError({{{ cDefs.EBUSY }}});
619
}
620
621
if (!FS.isDir(node.mode)) {
622
throw new FS.ErrnoError({{{ cDefs.ENOTDIR }}});
623
}
624
}
625
626
var mount = {
627
type,
628
opts,
629
mountpoint,
630
mounts: []
631
};
632
633
// create a root node for the fs
634
var mountRoot = type.mount(mount);
635
mountRoot.mount = mount;
636
mount.root = mountRoot;
637
638
if (root) {
639
FS.root = mountRoot;
640
} else if (node) {
641
// set as a mountpoint
642
node.mounted = mount;
643
644
// add the new mount to the current mount's children
645
if (node.mount) {
646
node.mount.mounts.push(mount);
647
}
648
}
649
650
return mountRoot;
651
},
652
unmount(mountpoint) {
653
var lookup = FS.lookupPath(mountpoint, { follow_mount: false });
654
655
if (!FS.isMountpoint(lookup.node)) {
656
throw new FS.ErrnoError({{{ cDefs.EINVAL }}});
657
}
658
659
// destroy the nodes for this mount, and all its child mounts
660
var node = lookup.node;
661
var mount = node.mounted;
662
var mounts = FS.getMounts(mount);
663
664
for (var [hash, current] of Object.entries(FS.nameTable)) {
665
while (current) {
666
var next = current.name_next;
667
668
if (mounts.includes(current.mount)) {
669
FS.destroyNode(current);
670
}
671
672
current = next;
673
}
674
}
675
676
// no longer a mountpoint
677
node.mounted = null;
678
679
// remove this mount from the child mounts
680
var idx = node.mount.mounts.indexOf(mount);
681
#if ASSERTIONS
682
assert(idx !== -1);
683
#endif
684
node.mount.mounts.splice(idx, 1);
685
},
686
lookup(parent, name) {
687
return parent.node_ops.lookup(parent, name);
688
},
689
// generic function for all node creation
690
mknod(path, mode, dev) {
691
var lookup = FS.lookupPath(path, { parent: true });
692
var parent = lookup.node;
693
var name = PATH.basename(path);
694
if (!name) {
695
throw new FS.ErrnoError({{{ cDefs.EINVAL }}});
696
}
697
if (name === '.' || name === '..') {
698
throw new FS.ErrnoError({{{ cDefs.EEXIST }}});
699
}
700
var errCode = FS.mayCreate(parent, name);
701
if (errCode) {
702
throw new FS.ErrnoError(errCode);
703
}
704
if (!parent.node_ops.mknod) {
705
throw new FS.ErrnoError({{{ cDefs.EPERM }}});
706
}
707
return parent.node_ops.mknod(parent, name, mode, dev);
708
},
709
statfs(path) {
710
return FS.statfsNode(FS.lookupPath(path, {follow: true}).node);
711
},
712
statfsStream(stream) {
713
// We keep a separate statfsStream function because noderawfs overrides
714
// it. In noderawfs, stream.node is sometimes null. Instead, we need to
715
// look at stream.path.
716
return FS.statfsNode(stream.node);
717
},
718
statfsNode(node) {
719
// NOTE: None of the defaults here are true. We're just returning safe and
720
// sane values. Currently nodefs and rawfs replace these defaults,
721
// other file systems leave them alone.
722
var rtn = {
723
bsize: 4096,
724
frsize: 4096,
725
blocks: 1e6,
726
bfree: 5e5,
727
bavail: 5e5,
728
files: FS.nextInode,
729
ffree: FS.nextInode - 1,
730
fsid: 42,
731
flags: 2,
732
namelen: 255,
733
};
734
735
if (node.node_ops.statfs) {
736
Object.assign(rtn, node.node_ops.statfs(node.mount.opts.root));
737
}
738
return rtn;
739
},
740
// helpers to create specific types of nodes
741
create(path, mode = 0o666) {
742
mode &= {{{ cDefs.S_IALLUGO }}};
743
mode |= {{{ cDefs.S_IFREG }}};
744
return FS.mknod(path, mode, 0);
745
},
746
mkdir(path, mode = 0o777) {
747
mode &= {{{ cDefs.S_IRWXUGO }}} | {{{ cDefs.S_ISVTX }}};
748
mode |= {{{ cDefs.S_IFDIR }}};
749
#if FS_DEBUG
750
FS.trackingDelegate['onMakeDirectory']?.(path, mode);
751
#endif
752
return FS.mknod(path, mode, 0);
753
},
754
// Creates a whole directory tree chain if it doesn't yet exist
755
mkdirTree(path, mode) {
756
var dirs = path.split('/');
757
var d = '';
758
for (var dir of dirs) {
759
if (!dir) continue;
760
if (d || PATH.isAbs(path)) d += '/';
761
d += dir;
762
try {
763
FS.mkdir(d, mode);
764
} catch(e) {
765
if (e.errno != {{{ cDefs.EEXIST }}}) throw e;
766
}
767
}
768
},
769
mkdev(path, mode, dev) {
770
if (typeof dev == 'undefined') {
771
dev = mode;
772
mode = 0o666;
773
}
774
mode |= {{{ cDefs.S_IFCHR }}};
775
return FS.mknod(path, mode, dev);
776
},
777
symlink(oldpath, newpath) {
778
if (!PATH_FS.resolve(oldpath)) {
779
throw new FS.ErrnoError({{{ cDefs.ENOENT }}});
780
}
781
var lookup = FS.lookupPath(newpath, { parent: true });
782
var parent = lookup.node;
783
if (!parent) {
784
throw new FS.ErrnoError({{{ cDefs.ENOENT }}});
785
}
786
var newname = PATH.basename(newpath);
787
var errCode = FS.mayCreate(parent, newname);
788
if (errCode) {
789
throw new FS.ErrnoError(errCode);
790
}
791
if (!parent.node_ops.symlink) {
792
throw new FS.ErrnoError({{{ cDefs.EPERM }}});
793
}
794
#if FS_DEBUG
795
FS.trackingDelegate['onMakeSymlink']?.(oldpath, newpath);
796
#endif
797
return parent.node_ops.symlink(parent, newname, oldpath);
798
},
799
rename(old_path, new_path) {
800
var old_dirname = PATH.dirname(old_path);
801
var new_dirname = PATH.dirname(new_path);
802
var old_name = PATH.basename(old_path);
803
var new_name = PATH.basename(new_path);
804
// parents must exist
805
var lookup, old_dir, new_dir;
806
807
// let the errors from non existent directories percolate up
808
lookup = FS.lookupPath(old_path, { parent: true });
809
old_dir = lookup.node;
810
lookup = FS.lookupPath(new_path, { parent: true });
811
new_dir = lookup.node;
812
813
if (!old_dir || !new_dir) throw new FS.ErrnoError({{{ cDefs.ENOENT }}});
814
// need to be part of the same mount
815
if (old_dir.mount !== new_dir.mount) {
816
throw new FS.ErrnoError({{{ cDefs.EXDEV }}});
817
}
818
// source must exist
819
var old_node = FS.lookupNode(old_dir, old_name);
820
// old path should not be an ancestor of the new path
821
var relative = PATH_FS.relative(old_path, new_dirname);
822
if (relative.charAt(0) !== '.') {
823
throw new FS.ErrnoError({{{ cDefs.EINVAL }}});
824
}
825
// new path should not be an ancestor of the old path
826
relative = PATH_FS.relative(new_path, old_dirname);
827
if (relative.charAt(0) !== '.') {
828
throw new FS.ErrnoError({{{ cDefs.ENOTEMPTY }}});
829
}
830
// see if the new path already exists
831
var new_node;
832
try {
833
new_node = FS.lookupNode(new_dir, new_name);
834
} catch (e) {
835
// not fatal
836
}
837
// early out if nothing needs to change
838
if (old_node === new_node) {
839
return;
840
}
841
// we'll need to delete the old entry
842
var isdir = FS.isDir(old_node.mode);
843
var errCode = FS.mayDelete(old_dir, old_name, isdir);
844
if (errCode) {
845
throw new FS.ErrnoError(errCode);
846
}
847
// need delete permissions if we'll be overwriting.
848
// need create permissions if new doesn't already exist.
849
errCode = new_node ?
850
FS.mayDelete(new_dir, new_name, isdir) :
851
FS.mayCreate(new_dir, new_name);
852
if (errCode) {
853
throw new FS.ErrnoError(errCode);
854
}
855
if (!old_dir.node_ops.rename) {
856
throw new FS.ErrnoError({{{ cDefs.EPERM }}});
857
}
858
if (FS.isMountpoint(old_node) || (new_node && FS.isMountpoint(new_node))) {
859
throw new FS.ErrnoError({{{ cDefs.EBUSY }}});
860
}
861
// if we are going to change the parent, check write permissions
862
if (new_dir !== old_dir) {
863
errCode = FS.nodePermissions(old_dir, 'w');
864
if (errCode) {
865
throw new FS.ErrnoError(errCode);
866
}
867
}
868
#if FS_DEBUG
869
FS.trackingDelegate['willMovePath']?.(old_path, new_path);
870
#endif
871
// remove the node from the lookup hash
872
FS.hashRemoveNode(old_node);
873
// do the underlying fs rename
874
try {
875
old_dir.node_ops.rename(old_node, new_dir, new_name);
876
// update old node (we do this here to avoid each backend
877
// needing to)
878
old_node.parent = new_dir;
879
} catch (e) {
880
throw e;
881
} finally {
882
// add the node back to the hash (in case node_ops.rename
883
// changed its name)
884
FS.hashAddNode(old_node);
885
}
886
#if FS_DEBUG
887
FS.trackingDelegate['onMovePath']?.(old_path, new_path);
888
#endif
889
},
890
rmdir(path) {
891
var lookup = FS.lookupPath(path, { parent: true });
892
var parent = lookup.node;
893
var name = PATH.basename(path);
894
var node = FS.lookupNode(parent, name);
895
var errCode = FS.mayDelete(parent, name, true);
896
if (errCode) {
897
throw new FS.ErrnoError(errCode);
898
}
899
if (!parent.node_ops.rmdir) {
900
throw new FS.ErrnoError({{{ cDefs.EPERM }}});
901
}
902
if (FS.isMountpoint(node)) {
903
throw new FS.ErrnoError({{{ cDefs.EBUSY }}});
904
}
905
#if FS_DEBUG
906
FS.trackingDelegate['willDeletePath']?.(path);
907
#endif
908
parent.node_ops.rmdir(parent, name);
909
FS.destroyNode(node);
910
#if FS_DEBUG
911
FS.trackingDelegate['onDeletePath']?.(path);
912
#endif
913
},
914
readdir(path) {
915
var lookup = FS.lookupPath(path, { follow: true });
916
var node = lookup.node;
917
var readdir = FS.checkOpExists(node.node_ops.readdir, {{{ cDefs.ENOTDIR }}});
918
return readdir(node);
919
},
920
unlink(path) {
921
var lookup = FS.lookupPath(path, { parent: true });
922
var parent = lookup.node;
923
if (!parent) {
924
throw new FS.ErrnoError({{{ cDefs.ENOENT }}});
925
}
926
var name = PATH.basename(path);
927
var node = FS.lookupNode(parent, name);
928
var errCode = FS.mayDelete(parent, name, false);
929
if (errCode) {
930
// According to POSIX, we should map EISDIR to EPERM, but
931
// we instead do what Linux does (and we must, as we use
932
// the musl linux libc).
933
throw new FS.ErrnoError(errCode);
934
}
935
if (!parent.node_ops.unlink) {
936
throw new FS.ErrnoError({{{ cDefs.EPERM }}});
937
}
938
if (FS.isMountpoint(node)) {
939
throw new FS.ErrnoError({{{ cDefs.EBUSY }}});
940
}
941
#if FS_DEBUG
942
FS.trackingDelegate['willDeletePath']?.(path);
943
#endif
944
parent.node_ops.unlink(parent, name);
945
FS.destroyNode(node);
946
#if FS_DEBUG
947
FS.trackingDelegate['onDeletePath']?.(path);
948
#endif
949
},
950
readlink(path) {
951
var lookup = FS.lookupPath(path);
952
var link = lookup.node;
953
if (!link) {
954
throw new FS.ErrnoError({{{ cDefs.ENOENT }}});
955
}
956
if (!link.node_ops.readlink) {
957
throw new FS.ErrnoError({{{ cDefs.EINVAL }}});
958
}
959
return link.node_ops.readlink(link);
960
},
961
stat(path, dontFollow) {
962
var lookup = FS.lookupPath(path, { follow: !dontFollow });
963
var node = lookup.node;
964
var getattr = FS.checkOpExists(node.node_ops.getattr, {{{ cDefs.EPERM }}});
965
return getattr(node);
966
},
967
fstat(fd) {
968
var stream = FS.getStreamChecked(fd);
969
var node = stream.node;
970
var getattr = stream.stream_ops.getattr;
971
var arg = getattr ? stream : node;
972
getattr ??= node.node_ops.getattr;
973
FS.checkOpExists(getattr, {{{ cDefs.EPERM }}})
974
return getattr(arg);
975
},
976
lstat(path) {
977
return FS.stat(path, true);
978
},
979
doChmod(stream, node, mode, dontFollow) {
980
FS.doSetAttr(stream, node, {
981
mode: (mode & {{{ cDefs.S_IALLUGO }}}) | (node.mode & ~{{{ cDefs.S_IALLUGO }}}),
982
ctime: Date.now(),
983
dontFollow
984
});
985
},
986
chmod(path, mode, dontFollow) {
987
var node;
988
if (typeof path == 'string') {
989
var lookup = FS.lookupPath(path, { follow: !dontFollow });
990
node = lookup.node;
991
} else {
992
node = path;
993
}
994
FS.doChmod(null, node, mode, dontFollow);
995
},
996
lchmod(path, mode) {
997
FS.chmod(path, mode, true);
998
},
999
fchmod(fd, mode) {
1000
var stream = FS.getStreamChecked(fd);
1001
FS.doChmod(stream, stream.node, mode, false);
1002
},
1003
doChown(stream, node, dontFollow) {
1004
FS.doSetAttr(stream, node, {
1005
timestamp: Date.now(),
1006
dontFollow
1007
// we ignore the uid / gid for now
1008
});
1009
},
1010
chown(path, uid, gid, dontFollow) {
1011
var node;
1012
if (typeof path == 'string') {
1013
var lookup = FS.lookupPath(path, { follow: !dontFollow });
1014
node = lookup.node;
1015
} else {
1016
node = path;
1017
}
1018
FS.doChown(null, node, dontFollow);
1019
},
1020
lchown(path, uid, gid) {
1021
FS.chown(path, uid, gid, true);
1022
},
1023
fchown(fd, uid, gid) {
1024
var stream = FS.getStreamChecked(fd);
1025
FS.doChown(stream, stream.node, false);
1026
},
1027
doTruncate(stream, node, len) {
1028
if (FS.isDir(node.mode)) {
1029
throw new FS.ErrnoError({{{ cDefs.EISDIR }}});
1030
}
1031
if (!FS.isFile(node.mode)) {
1032
throw new FS.ErrnoError({{{ cDefs.EINVAL }}});
1033
}
1034
var errCode = FS.nodePermissions(node, 'w');
1035
if (errCode) {
1036
throw new FS.ErrnoError(errCode);
1037
}
1038
FS.doSetAttr(stream, node, {
1039
size: len,
1040
timestamp: Date.now()
1041
});
1042
},
1043
truncate(path, len) {
1044
if (len < 0) {
1045
throw new FS.ErrnoError({{{ cDefs.EINVAL }}});
1046
}
1047
var node;
1048
if (typeof path == 'string') {
1049
var lookup = FS.lookupPath(path, { follow: true });
1050
node = lookup.node;
1051
} else {
1052
node = path;
1053
}
1054
FS.doTruncate(null, node, len);
1055
},
1056
ftruncate(fd, len) {
1057
var stream = FS.getStreamChecked(fd);
1058
if (len < 0 || (stream.flags & {{{ cDefs.O_ACCMODE }}}) === {{{ cDefs.O_RDONLY}}}) {
1059
throw new FS.ErrnoError({{{ cDefs.EINVAL }}});
1060
}
1061
FS.doTruncate(stream, stream.node, len);
1062
},
1063
utime(path, atime, mtime) {
1064
var lookup = FS.lookupPath(path, { follow: true });
1065
var node = lookup.node;
1066
var setattr = FS.checkOpExists(node.node_ops.setattr, {{{ cDefs.EPERM }}});
1067
setattr(node, {
1068
atime: atime,
1069
mtime: mtime
1070
});
1071
},
1072
open(path, flags, mode = 0o666) {
1073
if (path === "") {
1074
throw new FS.ErrnoError({{{ cDefs.ENOENT }}});
1075
}
1076
flags = typeof flags == 'string' ? FS_modeStringToFlags(flags) : flags;
1077
if ((flags & {{{ cDefs.O_CREAT }}})) {
1078
mode = (mode & {{{ cDefs.S_IALLUGO }}}) | {{{ cDefs.S_IFREG }}};
1079
} else {
1080
mode = 0;
1081
}
1082
var node;
1083
var isDirPath;
1084
if (typeof path == 'object') {
1085
node = path;
1086
} else {
1087
isDirPath = path.endsWith("/");
1088
// noent_okay makes it so that if the final component of the path
1089
// doesn't exist, lookupPath returns `node: undefined`. `path` will be
1090
// updated to point to the target of all symlinks.
1091
var lookup = FS.lookupPath(path, {
1092
follow: !(flags & {{{ cDefs.O_NOFOLLOW }}}),
1093
noent_okay: true
1094
});
1095
node = lookup.node;
1096
path = lookup.path;
1097
}
1098
// perhaps we need to create the node
1099
var created = false;
1100
if ((flags & {{{ cDefs.O_CREAT }}})) {
1101
if (node) {
1102
// if O_CREAT and O_EXCL are set, error out if the node already exists
1103
if ((flags & {{{ cDefs.O_EXCL }}})) {
1104
throw new FS.ErrnoError({{{ cDefs.EEXIST }}});
1105
}
1106
} else if (isDirPath) {
1107
throw new FS.ErrnoError({{{ cDefs.EISDIR }}});
1108
} else {
1109
// node doesn't exist, try to create it
1110
// Ignore the permission bits here to ensure we can `open` this new
1111
// file below. We use chmod below to apply the permissions once the
1112
// file is open.
1113
node = FS.mknod(path, mode | 0o777, 0);
1114
created = true;
1115
}
1116
}
1117
if (!node) {
1118
throw new FS.ErrnoError({{{ cDefs.ENOENT }}});
1119
}
1120
// can't truncate a device
1121
if (FS.isChrdev(node.mode)) {
1122
flags &= ~{{{ cDefs.O_TRUNC }}};
1123
}
1124
// if asked only for a directory, then this must be one
1125
if ((flags & {{{ cDefs.O_DIRECTORY }}}) && !FS.isDir(node.mode)) {
1126
throw new FS.ErrnoError({{{ cDefs.ENOTDIR }}});
1127
}
1128
// check permissions, if this is not a file we just created now (it is ok to
1129
// create and write to a file with read-only permissions; it is read-only
1130
// for later use)
1131
if (!created) {
1132
var errCode = FS.mayOpen(node, flags);
1133
if (errCode) {
1134
throw new FS.ErrnoError(errCode);
1135
}
1136
}
1137
// do truncation if necessary
1138
if ((flags & {{{ cDefs.O_TRUNC}}}) && !created) {
1139
FS.truncate(node, 0);
1140
}
1141
#if FS_DEBUG
1142
var origFlags = flags
1143
#endif
1144
// we've already handled these, don't pass down to the underlying vfs
1145
flags &= ~({{{ cDefs.O_EXCL }}} | {{{ cDefs.O_TRUNC }}} | {{{ cDefs.O_NOFOLLOW }}});
1146
1147
// register the stream with the filesystem
1148
var stream = FS.createStream({
1149
node,
1150
path: FS.getPath(node), // we want the absolute path to the node
1151
flags,
1152
seekable: true,
1153
position: 0,
1154
stream_ops: node.stream_ops,
1155
// used by the file family libc calls (fopen, fwrite, ferror, etc.)
1156
ungotten: [],
1157
error: false
1158
});
1159
// call the new stream's open function
1160
if (stream.stream_ops.open) {
1161
stream.stream_ops.open(stream);
1162
}
1163
if (created) {
1164
FS.chmod(node, mode & 0o777);
1165
}
1166
#if expectToReceiveOnModule('logReadFiles')
1167
if (Module['logReadFiles'] && !(flags & {{{ cDefs.O_WRONLY}}})) {
1168
if (!(path in FS.readFiles)) {
1169
FS.readFiles[path] = 1;
1170
err(`read file: ${path}`);
1171
}
1172
}
1173
#endif
1174
#if FS_DEBUG
1175
FS.trackingDelegate['onOpenFile']?.(path, origFlags);
1176
#endif
1177
return stream;
1178
},
1179
close(stream) {
1180
if (FS.isClosed(stream)) {
1181
throw new FS.ErrnoError({{{ cDefs.EBADF }}});
1182
}
1183
if (stream.getdents) stream.getdents = null; // free readdir state
1184
try {
1185
if (stream.stream_ops.close) {
1186
stream.stream_ops.close(stream);
1187
}
1188
} catch (e) {
1189
throw e;
1190
} finally {
1191
FS.closeStream(stream.fd);
1192
}
1193
stream.fd = null;
1194
#if FS_DEBUG
1195
if (stream.path) {
1196
FS.trackingDelegate['onCloseFile']?.(stream.path);
1197
}
1198
#endif
1199
},
1200
isClosed(stream) {
1201
return stream.fd === null;
1202
},
1203
llseek(stream, offset, whence) {
1204
if (FS.isClosed(stream)) {
1205
throw new FS.ErrnoError({{{ cDefs.EBADF }}});
1206
}
1207
if (!stream.seekable || !stream.stream_ops.llseek) {
1208
throw new FS.ErrnoError({{{ cDefs.ESPIPE }}});
1209
}
1210
if (whence != {{{ cDefs.SEEK_SET }}} && whence != {{{ cDefs.SEEK_CUR }}} && whence != {{{ cDefs.SEEK_END }}}) {
1211
throw new FS.ErrnoError({{{ cDefs.EINVAL }}});
1212
}
1213
stream.position = stream.stream_ops.llseek(stream, offset, whence);
1214
stream.ungotten = [];
1215
#if FS_DEBUG
1216
if (stream.path) {
1217
FS.trackingDelegate['onSeekFile']?.(stream.path, stream.position, whence);
1218
}
1219
#endif
1220
return stream.position;
1221
},
1222
read(stream, buffer, offset, length, position) {
1223
#if ASSERTIONS
1224
assert(offset >= 0);
1225
#endif
1226
if (length < 0 || position < 0) {
1227
throw new FS.ErrnoError({{{ cDefs.EINVAL }}});
1228
}
1229
if (FS.isClosed(stream)) {
1230
throw new FS.ErrnoError({{{ cDefs.EBADF }}});
1231
}
1232
if ((stream.flags & {{{ cDefs.O_ACCMODE }}}) === {{{ cDefs.O_WRONLY}}}) {
1233
throw new FS.ErrnoError({{{ cDefs.EBADF }}});
1234
}
1235
if (FS.isDir(stream.node.mode)) {
1236
throw new FS.ErrnoError({{{ cDefs.EISDIR }}});
1237
}
1238
if (!stream.stream_ops.read) {
1239
throw new FS.ErrnoError({{{ cDefs.EINVAL }}});
1240
}
1241
var seeking = typeof position != 'undefined';
1242
if (!seeking) {
1243
position = stream.position;
1244
} else if (!stream.seekable) {
1245
throw new FS.ErrnoError({{{ cDefs.ESPIPE }}});
1246
}
1247
var bytesRead = stream.stream_ops.read(stream, buffer, offset, length, position);
1248
if (!seeking) stream.position += bytesRead;
1249
#if FS_DEBUG
1250
if (stream.path) {
1251
FS.trackingDelegate['onReadFile']?.(stream.path, bytesRead);
1252
}
1253
#endif
1254
return bytesRead;
1255
},
1256
write(stream, buffer, offset, length, position, canOwn) {
1257
#if ASSERTIONS
1258
assert(offset >= 0);
1259
#endif
1260
if (length < 0 || position < 0) {
1261
throw new FS.ErrnoError({{{ cDefs.EINVAL }}});
1262
}
1263
if (FS.isClosed(stream)) {
1264
throw new FS.ErrnoError({{{ cDefs.EBADF }}});
1265
}
1266
if ((stream.flags & {{{ cDefs.O_ACCMODE }}}) === {{{ cDefs.O_RDONLY}}}) {
1267
throw new FS.ErrnoError({{{ cDefs.EBADF }}});
1268
}
1269
if (FS.isDir(stream.node.mode)) {
1270
throw new FS.ErrnoError({{{ cDefs.EISDIR }}});
1271
}
1272
if (!stream.stream_ops.write) {
1273
throw new FS.ErrnoError({{{ cDefs.EINVAL }}});
1274
}
1275
if (stream.seekable && stream.flags & {{{ cDefs.O_APPEND }}}) {
1276
// seek to the end before writing in append mode
1277
FS.llseek(stream, 0, {{{ cDefs.SEEK_END }}});
1278
}
1279
var seeking = typeof position != 'undefined';
1280
if (!seeking) {
1281
position = stream.position;
1282
} else if (!stream.seekable) {
1283
throw new FS.ErrnoError({{{ cDefs.ESPIPE }}});
1284
}
1285
var bytesWritten = stream.stream_ops.write(stream, buffer, offset, length, position, canOwn);
1286
if (!seeking) stream.position += bytesWritten;
1287
#if FS_DEBUG
1288
if (stream.path) {
1289
FS.trackingDelegate['onWriteToFile']?.(stream.path, bytesWritten);
1290
}
1291
#endif
1292
return bytesWritten;
1293
},
1294
mmap(stream, length, position, prot, flags) {
1295
// User requests writing to file (prot & PROT_WRITE != 0).
1296
// Checking if we have permissions to write to the file unless
1297
// MAP_PRIVATE flag is set. According to POSIX spec it is possible
1298
// to write to file opened in read-only mode with MAP_PRIVATE flag,
1299
// as all modifications will be visible only in the memory of
1300
// the current process.
1301
if ((prot & {{{ cDefs.PROT_WRITE }}}) !== 0
1302
&& (flags & {{{ cDefs.MAP_PRIVATE}}}) === 0
1303
&& (stream.flags & {{{ cDefs.O_ACCMODE }}}) !== {{{ cDefs.O_RDWR}}}) {
1304
throw new FS.ErrnoError({{{ cDefs.EACCES }}});
1305
}
1306
if ((stream.flags & {{{ cDefs.O_ACCMODE }}}) === {{{ cDefs.O_WRONLY}}}) {
1307
throw new FS.ErrnoError({{{ cDefs.EACCES }}});
1308
}
1309
if (!stream.stream_ops.mmap) {
1310
throw new FS.ErrnoError({{{ cDefs.ENODEV }}});
1311
}
1312
if (!length) {
1313
throw new FS.ErrnoError({{{ cDefs.EINVAL }}});
1314
}
1315
return stream.stream_ops.mmap(stream, length, position, prot, flags);
1316
},
1317
msync(stream, buffer, offset, length, mmapFlags) {
1318
#if ASSERTIONS
1319
assert(offset >= 0);
1320
#endif
1321
if (!stream.stream_ops.msync) {
1322
return 0;
1323
}
1324
return stream.stream_ops.msync(stream, buffer, offset, length, mmapFlags);
1325
},
1326
ioctl(stream, cmd, arg) {
1327
if (!stream.stream_ops.ioctl) {
1328
throw new FS.ErrnoError({{{ cDefs.ENOTTY }}});
1329
}
1330
return stream.stream_ops.ioctl(stream, cmd, arg);
1331
},
1332
readFile(path, opts = {}) {
1333
opts.flags = opts.flags || {{{ cDefs.O_RDONLY }}};
1334
opts.encoding = opts.encoding || 'binary';
1335
if (opts.encoding !== 'utf8' && opts.encoding !== 'binary') {
1336
abort(`Invalid encoding type "${opts.encoding}"`);
1337
}
1338
var stream = FS.open(path, opts.flags);
1339
var stat = FS.stat(path);
1340
var length = stat.size;
1341
var buf = new Uint8Array(length);
1342
FS.read(stream, buf, 0, length, 0);
1343
if (opts.encoding === 'utf8') {
1344
buf = UTF8ArrayToString(buf);
1345
}
1346
FS.close(stream);
1347
return buf;
1348
},
1349
writeFile(path, data, opts = {}) {
1350
opts.flags = opts.flags || {{{ cDefs.O_TRUNC | cDefs.O_CREAT | cDefs.O_WRONLY }}};
1351
var stream = FS.open(path, opts.flags, opts.mode);
1352
if (typeof data == 'string') {
1353
data = new Uint8Array(intArrayFromString(data, true));
1354
}
1355
if (ArrayBuffer.isView(data)) {
1356
FS.write(stream, data, 0, data.byteLength, undefined, opts.canOwn);
1357
} else {
1358
abort('Unsupported data type');
1359
}
1360
FS.close(stream);
1361
},
1362
1363
//
1364
// module-level FS code
1365
//
1366
cwd: () => FS.currentPath,
1367
chdir(path) {
1368
var lookup = FS.lookupPath(path, { follow: true });
1369
if (lookup.node === null) {
1370
throw new FS.ErrnoError({{{ cDefs.ENOENT }}});
1371
}
1372
if (!FS.isDir(lookup.node.mode)) {
1373
throw new FS.ErrnoError({{{ cDefs.ENOTDIR }}});
1374
}
1375
var errCode = FS.nodePermissions(lookup.node, 'x');
1376
if (errCode) {
1377
throw new FS.ErrnoError(errCode);
1378
}
1379
FS.currentPath = lookup.path;
1380
},
1381
createDefaultDirectories() {
1382
FS.mkdir('/tmp');
1383
FS.mkdir('/home');
1384
FS.mkdir('/home/web_user');
1385
},
1386
createDefaultDevices() {
1387
// create /dev
1388
FS.mkdir('/dev');
1389
// setup /dev/null
1390
FS.registerDevice(FS.makedev(1, 3), {
1391
read: () => 0,
1392
write: (stream, buffer, offset, length, pos) => length,
1393
llseek: () => 0,
1394
});
1395
FS.mkdev('/dev/null', FS.makedev(1, 3));
1396
// setup /dev/tty and /dev/tty1
1397
// stderr needs to print output using err() rather than out()
1398
// so we register a second tty just for it.
1399
TTY.register(FS.makedev(5, 0), TTY.default_tty_ops);
1400
TTY.register(FS.makedev(6, 0), TTY.default_tty1_ops);
1401
FS.mkdev('/dev/tty', FS.makedev(5, 0));
1402
FS.mkdev('/dev/tty1', FS.makedev(6, 0));
1403
// setup /dev/[u]random
1404
// use a buffer to avoid overhead of individual crypto calls per byte
1405
var randomBuffer = new Uint8Array(1024), randomLeft = 0;
1406
var randomByte = () => {
1407
if (randomLeft === 0) {
1408
randomFill(randomBuffer);
1409
randomLeft = randomBuffer.byteLength;
1410
}
1411
return randomBuffer[--randomLeft];
1412
};
1413
FS.createDevice('/dev', 'random', randomByte);
1414
FS.createDevice('/dev', 'urandom', randomByte);
1415
// we're not going to emulate the actual shm device,
1416
// just create the tmp dirs that reside in it commonly
1417
FS.mkdir('/dev/shm');
1418
FS.mkdir('/dev/shm/tmp');
1419
},
1420
createSpecialDirectories() {
1421
// create /proc/self/fd which allows /proc/self/fd/6 => readlink gives the
1422
// name of the stream for fd 6 (see test_unistd_ttyname)
1423
FS.mkdir('/proc');
1424
var proc_self = FS.mkdir('/proc/self');
1425
FS.mkdir('/proc/self/fd');
1426
FS.mount({
1427
mount() {
1428
var node = FS.createNode(proc_self, 'fd', {{{ cDefs.S_IFDIR | 0o777 }}}, {{{ cDefs.S_IXUGO }}});
1429
node.stream_ops = {
1430
llseek: MEMFS.stream_ops.llseek,
1431
};
1432
node.node_ops = {
1433
lookup(parent, name) {
1434
var fd = +name;
1435
var stream = FS.getStreamChecked(fd);
1436
var ret = {
1437
parent: null,
1438
mount: { mountpoint: 'fake' },
1439
node_ops: { readlink: () => stream.path },
1440
id: fd + 1,
1441
};
1442
ret.parent = ret; // make it look like a simple root node
1443
return ret;
1444
},
1445
readdir() {
1446
return Array.from(FS.streams.entries())
1447
.filter(([k, v]) => v)
1448
.map(([k, v]) => k.toString());
1449
}
1450
};
1451
return node;
1452
}
1453
}, {}, '/proc/self/fd');
1454
},
1455
createStandardStreams(input, output, error) {
1456
// TODO deprecate the old functionality of a single
1457
// input / output callback and that utilizes FS.createDevice
1458
// and instead require a unique set of stream ops
1459
1460
// by default, we symlink the standard streams to the
1461
// default tty devices. however, if the standard streams
1462
// have been overwritten we create a unique device for
1463
// them instead.
1464
if (input) {
1465
FS.createDevice('/dev', 'stdin', input);
1466
} else {
1467
FS.symlink('/dev/tty', '/dev/stdin');
1468
}
1469
if (output) {
1470
FS.createDevice('/dev', 'stdout', null, output);
1471
} else {
1472
FS.symlink('/dev/tty', '/dev/stdout');
1473
}
1474
if (error) {
1475
FS.createDevice('/dev', 'stderr', null, error);
1476
} else {
1477
FS.symlink('/dev/tty1', '/dev/stderr');
1478
}
1479
1480
// open default streams for the stdin, stdout and stderr devices
1481
var stdin = FS.open('/dev/stdin', {{{ cDefs.O_RDONLY }}});
1482
var stdout = FS.open('/dev/stdout', {{{ cDefs.O_WRONLY }}});
1483
var stderr = FS.open('/dev/stderr', {{{ cDefs.O_WRONLY }}});
1484
#if ASSERTIONS
1485
assert(stdin.fd === 0, `invalid handle for stdin (${stdin.fd})`);
1486
assert(stdout.fd === 1, `invalid handle for stdout (${stdout.fd})`);
1487
assert(stderr.fd === 2, `invalid handle for stderr (${stderr.fd})`);
1488
#endif
1489
},
1490
staticInit() {
1491
FS.nameTable = new Array(4096);
1492
1493
FS.mount(MEMFS, {}, '/');
1494
1495
FS.createDefaultDirectories();
1496
FS.createDefaultDevices();
1497
FS.createSpecialDirectories();
1498
1499
FS.filesystems = {
1500
'MEMFS': MEMFS,
1501
#if LibraryManager.has('libidbfs.js')
1502
'IDBFS': IDBFS,
1503
#endif
1504
#if LibraryManager.has('libnodefs.js')
1505
'NODEFS': NODEFS,
1506
#endif
1507
#if LibraryManager.has('libworkerfs.js')
1508
'WORKERFS': WORKERFS,
1509
#endif
1510
#if LibraryManager.has('libproxyfs.js')
1511
'PROXYFS': PROXYFS,
1512
#endif
1513
};
1514
},
1515
init(input, output, error) {
1516
#if ASSERTIONS
1517
assert(!FS.initialized, 'FS.init was previously called. If you want to initialize later with custom parameters, remove any earlier calls (note that one is automatically added to the generated code)');
1518
#endif
1519
FS.initialized = true;
1520
1521
// Allow Module.stdin etc. to provide defaults, if none explicitly passed to us here
1522
#if expectToReceiveOnModule('stdin')
1523
input ??= Module['stdin'];
1524
#endif
1525
#if expectToReceiveOnModule('stdout')
1526
output ??= Module['stdout'];
1527
#endif
1528
#if expectToReceiveOnModule('stderr')
1529
error ??= Module['stderr'];
1530
#endif
1531
1532
FS.createStandardStreams(input, output, error);
1533
},
1534
quit() {
1535
FS.initialized = false;
1536
// force-flush all streams, so we get musl std streams printed out
1537
#if hasExportedSymbol('fflush')
1538
_fflush(0);
1539
#endif
1540
// close all of our streams
1541
for (var stream of FS.streams) {
1542
if (stream) {
1543
FS.close(stream);
1544
}
1545
}
1546
},
1547
1548
//
1549
// old v1 compatibility functions
1550
//
1551
findObject(path, dontResolveLastLink) {
1552
var ret = FS.analyzePath(path, dontResolveLastLink);
1553
if (!ret.exists) {
1554
return null;
1555
}
1556
return ret.object;
1557
},
1558
analyzePath(path, dontResolveLastLink) {
1559
// operate from within the context of the symlink's target
1560
try {
1561
var lookup = FS.lookupPath(path, { follow: !dontResolveLastLink });
1562
path = lookup.path;
1563
} catch (e) {
1564
}
1565
var ret = {
1566
isRoot: false, exists: false, error: 0, name: null, path: null, object: null,
1567
parentExists: false, parentPath: null, parentObject: null
1568
};
1569
try {
1570
var lookup = FS.lookupPath(path, { parent: true });
1571
ret.parentExists = true;
1572
ret.parentPath = lookup.path;
1573
ret.parentObject = lookup.node;
1574
ret.name = PATH.basename(path);
1575
lookup = FS.lookupPath(path, { follow: !dontResolveLastLink });
1576
ret.exists = true;
1577
ret.path = lookup.path;
1578
ret.object = lookup.node;
1579
ret.name = lookup.node.name;
1580
ret.isRoot = lookup.path === '/';
1581
} catch (e) {
1582
ret.error = e.errno;
1583
};
1584
return ret;
1585
},
1586
createPath(parent, path, canRead, canWrite) {
1587
parent = typeof parent == 'string' ? parent : FS.getPath(parent);
1588
var parts = path.split('/').reverse();
1589
while (parts.length) {
1590
var part = parts.pop();
1591
if (!part) continue;
1592
var current = PATH.join2(parent, part);
1593
try {
1594
FS.mkdir(current);
1595
} catch (e) {
1596
if (e.errno != {{{ cDefs.EEXIST }}}) throw e;
1597
}
1598
parent = current;
1599
}
1600
return current;
1601
},
1602
createFile(parent, name, properties, canRead, canWrite) {
1603
var path = PATH.join2(typeof parent == 'string' ? parent : FS.getPath(parent), name);
1604
var mode = FS_getMode(canRead, canWrite);
1605
return FS.create(path, mode);
1606
},
1607
createDataFile(parent, name, data, canRead, canWrite, canOwn) {
1608
var path = name;
1609
if (parent) {
1610
parent = typeof parent == 'string' ? parent : FS.getPath(parent);
1611
path = name ? PATH.join2(parent, name) : parent;
1612
}
1613
var mode = FS_getMode(canRead, canWrite);
1614
var node = FS.create(path, mode);
1615
if (data) {
1616
if (typeof data == 'string') {
1617
var arr = new Array(data.length);
1618
for (var i = 0, len = data.length; i < len; ++i) arr[i] = data.charCodeAt(i);
1619
data = arr;
1620
}
1621
// make sure we can write to the file
1622
FS.chmod(node, mode | {{{ cDefs.S_IWUGO }}});
1623
var stream = FS.open(node, {{{ cDefs.O_TRUNC | cDefs.O_CREAT | cDefs.O_WRONLY }}});
1624
FS.write(stream, data, 0, data.length, 0, canOwn);
1625
FS.close(stream);
1626
FS.chmod(node, mode);
1627
}
1628
},
1629
createDevice(parent, name, input, output) {
1630
var path = PATH.join2(typeof parent == 'string' ? parent : FS.getPath(parent), name);
1631
var mode = FS_getMode(!!input, !!output);
1632
FS.createDevice.major ??= 64;
1633
var dev = FS.makedev(FS.createDevice.major++, 0);
1634
// Create a fake device that a set of stream ops to emulate
1635
// the old behavior.
1636
FS.registerDevice(dev, {
1637
open(stream) {
1638
stream.seekable = false;
1639
},
1640
close(stream) {
1641
// flush any pending line data
1642
if (output?.buffer?.length) {
1643
output({{{ charCode('\n') }}});
1644
}
1645
},
1646
read(stream, buffer, offset, length, pos /* ignored */) {
1647
var bytesRead = 0;
1648
for (var i = 0; i < length; i++) {
1649
var result;
1650
try {
1651
result = input();
1652
} catch (e) {
1653
throw new FS.ErrnoError({{{ cDefs.EIO }}});
1654
}
1655
if (result === undefined && bytesRead === 0) {
1656
throw new FS.ErrnoError({{{ cDefs.EAGAIN }}});
1657
}
1658
if (result === null || result === undefined) break;
1659
bytesRead++;
1660
buffer[offset+i] = result;
1661
}
1662
if (bytesRead) {
1663
stream.node.atime = Date.now();
1664
}
1665
return bytesRead;
1666
},
1667
write(stream, buffer, offset, length, pos) {
1668
for (var i = 0; i < length; i++) {
1669
try {
1670
output(buffer[offset+i]);
1671
} catch (e) {
1672
throw new FS.ErrnoError({{{ cDefs.EIO }}});
1673
}
1674
}
1675
if (length) {
1676
stream.node.mtime = stream.node.ctime = Date.now();
1677
}
1678
return i;
1679
}
1680
});
1681
return FS.mkdev(path, mode, dev);
1682
},
1683
// Makes sure a file's contents are loaded. Returns whether the file has
1684
// been loaded successfully. No-op for files that have been loaded already.
1685
forceLoadFile(obj) {
1686
if (obj.isDevice || obj.isFolder || obj.link || obj.contents) return true;
1687
#if FS_DEBUG
1688
dbg(`forceLoadFile: ${obj.url}`)
1689
#endif
1690
if (globalThis.XMLHttpRequest) {
1691
abort("Lazy loading should have been performed (contents set) in createLazyFile, but it was not. Lazy loading only works in web workers. Use --embed-file or --preload-file in emcc on the main thread.");
1692
} else { // Command-line.
1693
try {
1694
obj.contents = readBinary(obj.url);
1695
} catch (e) {
1696
#if FS_DEBUG
1697
dbg(`forceLoadFile exception: ${e}`);
1698
#endif
1699
throw new FS.ErrnoError({{{ cDefs.EIO }}});
1700
}
1701
}
1702
},
1703
// Creates a file record for lazy-loading from a URL. XXX This requires a synchronous
1704
// XHR, which is not possible in browsers except in a web worker! Use preloading,
1705
// either --preload-file in emcc or FS.createPreloadedFile
1706
createLazyFile(parent, name, url, canRead, canWrite) {
1707
// Lazy chunked Uint8Array (implements get and length from Uint8Array).
1708
// Actual getting is abstracted away for eventual reuse.
1709
class LazyUint8Array {
1710
lengthKnown = false;
1711
chunks = []; // Loaded chunks. Index is the chunk number
1712
#if USE_CLOSURE_COMPILER
1713
// Closure compiler requires us to declare all properties ahead of time.
1714
getter = undefined;
1715
_length = 0;
1716
_chunkSize = 0;
1717
#endif
1718
get(idx) {
1719
if (idx > this.length-1 || idx < 0) {
1720
return undefined;
1721
}
1722
var chunkOffset = idx % this.chunkSize;
1723
var chunkNum = (idx / this.chunkSize)|0;
1724
return this.getter(chunkNum)[chunkOffset];
1725
}
1726
setDataGetter(getter) {
1727
this.getter = getter;
1728
}
1729
cacheLength() {
1730
// Find length
1731
var xhr = new XMLHttpRequest();
1732
xhr.open('HEAD', url, false);
1733
xhr.send(null);
1734
if (!(xhr.status >= 200 && xhr.status < 300 || xhr.status === 304)) abort("Couldn't load " + url + ". Status: " + xhr.status);
1735
var datalength = Number(xhr.getResponseHeader("Content-length"));
1736
var header;
1737
var hasByteServing = (header = xhr.getResponseHeader("Accept-Ranges")) && header === "bytes";
1738
var usesGzip = (header = xhr.getResponseHeader("Content-Encoding")) && header === "gzip";
1739
1740
#if SMALL_XHR_CHUNKS
1741
var chunkSize = 1024; // Chunk size in bytes
1742
#else
1743
var chunkSize = 1024*1024; // Chunk size in bytes
1744
#endif
1745
1746
if (!hasByteServing) chunkSize = datalength;
1747
1748
// Function to get a range from the remote URL.
1749
var doXHR = (from, to) => {
1750
if (from > to) abort("invalid range (" + from + ", " + to + ") or no bytes requested!");
1751
if (to > datalength-1) abort("only " + datalength + " bytes available! programmer error!");
1752
1753
// TODO: Use mozResponseArrayBuffer, responseStream, etc. if available.
1754
var xhr = new XMLHttpRequest();
1755
xhr.open('GET', url, false);
1756
if (datalength !== chunkSize) xhr.setRequestHeader("Range", "bytes=" + from + "-" + to);
1757
1758
// Some hints to the browser that we want binary data.
1759
xhr.responseType = 'arraybuffer';
1760
if (xhr.overrideMimeType) {
1761
xhr.overrideMimeType('text/plain; charset=x-user-defined');
1762
}
1763
1764
xhr.send(null);
1765
if (!(xhr.status >= 200 && xhr.status < 300 || xhr.status === 304)) abort("Couldn't load " + url + ". Status: " + xhr.status);
1766
if (xhr.response !== undefined) {
1767
return new Uint8Array(/** @type{Array<number>} */(xhr.response || []));
1768
}
1769
return intArrayFromString(xhr.responseText || '', true);
1770
};
1771
var lazyArray = this;
1772
lazyArray.setDataGetter((chunkNum) => {
1773
var start = chunkNum * chunkSize;
1774
var end = (chunkNum+1) * chunkSize - 1; // including this byte
1775
end = Math.min(end, datalength-1); // if datalength-1 is selected, this is the last block
1776
if (typeof lazyArray.chunks[chunkNum] == 'undefined') {
1777
lazyArray.chunks[chunkNum] = doXHR(start, end);
1778
}
1779
if (typeof lazyArray.chunks[chunkNum] == 'undefined') abort('doXHR failed!');
1780
return lazyArray.chunks[chunkNum];
1781
});
1782
1783
if (usesGzip || !datalength) {
1784
// if the server uses gzip or doesn't supply the length, we have to download the whole file to get the (uncompressed) length
1785
chunkSize = datalength = 1; // this will force getter(0)/doXHR do download the whole file
1786
datalength = this.getter(0).length;
1787
chunkSize = datalength;
1788
out("LazyFiles on gzip forces download of the whole file when length is accessed");
1789
}
1790
1791
this._length = datalength;
1792
this._chunkSize = chunkSize;
1793
this.lengthKnown = true;
1794
}
1795
get length() {
1796
if (!this.lengthKnown) {
1797
this.cacheLength();
1798
}
1799
return this._length;
1800
}
1801
get chunkSize() {
1802
if (!this.lengthKnown) {
1803
this.cacheLength();
1804
}
1805
return this._chunkSize;
1806
}
1807
}
1808
1809
if (globalThis.XMLHttpRequest) {
1810
if (!ENVIRONMENT_IS_WORKER) abort('Cannot do synchronous binary XHRs outside webworkers in modern browsers. Use --embed-file or --preload-file in emcc');
1811
var lazyArray = new LazyUint8Array();
1812
var properties = { isDevice: false, contents: lazyArray };
1813
} else {
1814
var properties = { isDevice: false, url: url };
1815
}
1816
1817
var node = FS.createFile(parent, name, properties, canRead, canWrite);
1818
// This is a total hack, but I want to get this lazy file code out of the
1819
// core of MEMFS. If we want to keep this lazy file concept I feel it should
1820
// be its own thin LAZYFS proxying calls to MEMFS.
1821
if (properties.contents) {
1822
node.contents = properties.contents;
1823
} else if (properties.url) {
1824
node.contents = null;
1825
node.url = properties.url;
1826
}
1827
// Add a function that defers querying the file size until it is asked the first time.
1828
Object.defineProperties(node, {
1829
usedBytes: {
1830
get: function() { return this.contents.length; }
1831
}
1832
});
1833
// override each stream op with one that tries to force load the lazy file first
1834
var stream_ops = {};
1835
for (const [key, fn] of Object.entries(node.stream_ops)) {
1836
stream_ops[key] = (...args) => {
1837
FS.forceLoadFile(node);
1838
return fn(...args);
1839
};
1840
}
1841
function writeChunks(stream, buffer, offset, length, position) {
1842
var contents = stream.node.contents;
1843
if (position >= contents.length)
1844
return 0;
1845
var size = Math.min(contents.length - position, length);
1846
#if ASSERTIONS
1847
assert(size >= 0);
1848
#endif
1849
if (contents.slice) { // normal array
1850
for (var i = 0; i < size; i++) {
1851
buffer[offset + i] = contents[position + i];
1852
}
1853
} else {
1854
for (var i = 0; i < size; i++) { // LazyUint8Array from sync binary XHR
1855
buffer[offset + i] = contents.get(position + i);
1856
}
1857
}
1858
return size;
1859
}
1860
// use a custom read function
1861
stream_ops.read = (stream, buffer, offset, length, position) => {
1862
FS.forceLoadFile(node);
1863
return writeChunks(stream, buffer, offset, length, position)
1864
};
1865
// use a custom mmap function
1866
stream_ops.mmap = (stream, length, position, prot, flags) => {
1867
FS.forceLoadFile(node);
1868
var ptr = mmapAlloc(length);
1869
if (!ptr) {
1870
throw new FS.ErrnoError({{{ cDefs.ENOMEM }}});
1871
}
1872
writeChunks(stream, HEAP8, ptr, length, position);
1873
return { ptr, allocated: true };
1874
};
1875
node.stream_ops = stream_ops;
1876
return node;
1877
},
1878
1879
// Removed v1 functions
1880
#if ASSERTIONS
1881
absolutePath() {
1882
abort('FS.absolutePath has been removed; use PATH_FS.resolve instead');
1883
},
1884
createFolder() {
1885
abort('FS.createFolder has been removed; use FS.mkdir instead');
1886
},
1887
createLink() {
1888
abort('FS.createLink has been removed; use FS.symlink instead');
1889
},
1890
joinPath() {
1891
abort('FS.joinPath has been removed; use PATH.join instead');
1892
},
1893
mmapAlloc() {
1894
abort('FS.mmapAlloc has been replaced by the top level function mmapAlloc');
1895
},
1896
standardizePath() {
1897
abort('FS.standardizePath has been removed; use PATH.normalize instead');
1898
},
1899
#endif
1900
},
1901
1902
$FS_mkdirTree__docs: `
1903
/**
1904
* @param {number=} mode Optionally, the mode to create in. Uses mkdir's
1905
* default if not set.
1906
*/`,
1907
$FS_mkdirTree__deps: ['$FS'],
1908
$FS_mkdirTree: (path, mode) => FS.mkdirTree(path, mode),
1909
};
1910
1911
// Add library aliases for all the FS.<symbol> as FS_<symbol>.
1912
for (let key in LibraryFS.$FS) {
1913
const alias = `$FS_${key}`;
1914
// Skip defining the alias if it already exists or if it's not an API function.
1915
if (LibraryFS[alias] || key[0] !== key[0].toLowerCase()) {
1916
continue;
1917
}
1918
LibraryFS[alias] = `(...args) => FS.${key}(...args)`;
1919
LibraryFS[`${alias}__deps`] = ['$FS'];
1920
}
1921
addToLibrary(LibraryFS);
1922
1923