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