/** * @license * Copyright 2013 The Emscripten Authors * SPDX-License-Identifier: MIT */ addToLibrary({ #if WASMFS $NODEFS__deps: ['$stringToUTF8OnStack', 'wasmfs_create_node_backend'], $NODEFS: { createBackend(opts) { return _wasmfs_create_node_backend(stringToUTF8OnStack(opts.root)); } } #else $NODEFS__deps: ['$FS', '$PATH', '$ERRNO_CODES', '$mmapAlloc'], $NODEFS__postset: 'if (ENVIRONMENT_IS_NODE) { NODEFS.staticInit(); }', $NODEFS: { isWindows: false, staticInit() { NODEFS.isWindows = !!process.platform.match(/^win/); var flags = process.binding("constants")["fs"]; NODEFS.flagsForNodeMap = { "{{{ cDefs.O_APPEND }}}": flags["O_APPEND"], "{{{ cDefs.O_CREAT }}}": flags["O_CREAT"], "{{{ cDefs.O_EXCL }}}": flags["O_EXCL"], "{{{ cDefs.O_NOCTTY }}}": flags["O_NOCTTY"], "{{{ cDefs.O_RDONLY }}}": flags["O_RDONLY"], "{{{ cDefs.O_RDWR }}}": flags["O_RDWR"], "{{{ cDefs.O_DSYNC }}}": flags["O_SYNC"], "{{{ cDefs.O_TRUNC }}}": flags["O_TRUNC"], "{{{ cDefs.O_WRONLY }}}": flags["O_WRONLY"], "{{{ cDefs.O_NOFOLLOW }}}": flags["O_NOFOLLOW"], }; #if ASSERTIONS // The 0 define must match on both sides, as otherwise we would not // know to add it. assert(NODEFS.flagsForNodeMap["0"] === 0); #endif }, convertNodeCode(e) { var code = e.code; #if ASSERTIONS assert(code in ERRNO_CODES, `unexpected node error code: ${code} (${e})`); #endif return ERRNO_CODES[code]; }, tryFSOperation(f) { try { return f(); } catch (e) { if (!e.code) throw e; // node under windows can return code 'UNKNOWN' here: // https://github.com/emscripten-core/emscripten/issues/15468 if (e.code === 'UNKNOWN') throw new FS.ErrnoError({{{ cDefs.EINVAL }}}); throw new FS.ErrnoError(NODEFS.convertNodeCode(e)); } }, mount(mount) { #if ASSERTIONS assert(ENVIRONMENT_IS_NODE); #endif return NODEFS.createNode(null, '/', NODEFS.getMode(mount.opts.root), 0); }, createNode(parent, name, mode, dev) { if (!FS.isDir(mode) && !FS.isFile(mode) && !FS.isLink(mode)) { throw new FS.ErrnoError({{{ cDefs.EINVAL }}}); } var node = FS.createNode(parent, name, mode); node.node_ops = NODEFS.node_ops; node.stream_ops = NODEFS.stream_ops; return node; }, getMode(path) { return NODEFS.tryFSOperation(() => { var mode = fs.lstatSync(path).mode; if (NODEFS.isWindows) { // Windows does not report the 'x' permission bit, so propagate read // bits to execute bits. mode |= (mode & {{{ cDefs.S_IRUGO }}}) >> 2; } return mode; }); }, realPath(node) { var parts = []; while (node.parent !== node) { parts.push(node.name); node = node.parent; } parts.push(node.mount.opts.root); parts.reverse(); return PATH.join(...parts); }, // This maps the integer permission modes from http://linux.die.net/man/3/open // to node.js-specific file open permission strings at http://nodejs.org/api/fs.html#fs_fs_open_path_flags_mode_callback flagsForNode(flags) { flags &= ~{{{ cDefs.O_PATH }}}; // Ignore this flag from musl, otherwise node.js fails to open the file. flags &= ~{{{ cDefs.O_NONBLOCK }}}; // Ignore this flag from musl, otherwise node.js fails to open the file. flags &= ~{{{ cDefs.O_LARGEFILE }}}; // Ignore this flag from musl, otherwise node.js fails to open the file. flags &= ~{{{ cDefs.O_CLOEXEC }}}; // Some applications may pass it; it makes no sense for a single process. flags &= ~{{{ cDefs.O_DIRECTORY }}}; // Node.js doesn't need this passed in, it errors. var newFlags = 0; for (var k in NODEFS.flagsForNodeMap) { if (flags & k) { newFlags |= NODEFS.flagsForNodeMap[k]; flags ^= k; } } if (flags) { throw new FS.ErrnoError({{{ cDefs.EINVAL }}}); } return newFlags; }, getattr(func, node) { var stat = NODEFS.tryFSOperation(func); if (NODEFS.isWindows) { // node.js v0.10.20 doesn't report blksize and blocks on Windows. Fake // them with default blksize of 4096. // See http://support.microsoft.com/kb/140365 if (!stat.blksize) { stat.blksize = 4096; } if (!stat.blocks) { stat.blocks = (stat.size+stat.blksize-1)/stat.blksize|0; } // Windows does not report the 'x' permission bit, so propagate read // bits to execute bits. stat.mode |= (stat.mode & {{{ cDefs.S_IRUGO }}}) >> 2; } return { dev: stat.dev, ino: node.id, mode: stat.mode, nlink: stat.nlink, uid: stat.uid, gid: stat.gid, rdev: stat.rdev, size: stat.size, atime: stat.atime, mtime: stat.mtime, ctime: stat.ctime, blksize: stat.blksize, blocks: stat.blocks }; }, // Common code for both node and stream setattr // For node getattr: // - arg is a native path // - chmod, utimes, truncate are fs.chmodSync, fs.utimesSync, fs.truncateSync // For stream getattr: // - arg is a native file descriptor // - chmod, utimes, truncate are fs.fchmodSync, fs.futimesSync, fs.ftruncateSync setattr(arg, node, attr, chmod, utimes, truncate, stat) { NODEFS.tryFSOperation(() => { if (attr.mode !== undefined) { var mode = attr.mode; if (NODEFS.isWindows) { // Windows only supports S_IREAD / S_IWRITE (S_IRUSR / S_IWUSR) // https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/chmod-wchmod mode &= {{{ cDefs.S_IRUSR | cDefs.S_IWUSR }}}; } chmod(arg, mode); // update the common node structure mode as well node.mode = attr.mode; } if (typeof (attr.atime ?? attr.mtime) === "number") { // Unfortunately, we have to stat the current value if we don't want // to change it. On top of that, since the times don't round trip // this will only keep the value nearly unchanged not exactly // unchanged. See: // https://github.com/nodejs/node/issues/56492 var atime = new Date(attr.atime ?? stat(arg).atime); var mtime = new Date(attr.mtime ?? stat(arg).mtime); utimes(arg, atime, mtime); } if (attr.size !== undefined) { truncate(arg, attr.size); } }); }, node_ops: { getattr(node) { var path = NODEFS.realPath(node); return NODEFS.getattr(() => fs.lstatSync(path), node); }, setattr(node, attr) { var path = NODEFS.realPath(node); if (attr.mode != null && attr.dontFollow) { throw new FS.ErrnoError({{{ cDefs.ENOSYS }}}); } NODEFS.setattr(path, node, attr, fs.chmodSync, fs.utimesSync, fs.truncateSync, fs.lstatSync); }, lookup(parent, name) { var path = PATH.join2(NODEFS.realPath(parent), name); var mode = NODEFS.getMode(path); return NODEFS.createNode(parent, name, mode); }, mknod(parent, name, mode, dev) { var node = NODEFS.createNode(parent, name, mode, dev); // create the backing node for this in the fs root as well var path = NODEFS.realPath(node); NODEFS.tryFSOperation(() => { if (FS.isDir(node.mode)) { fs.mkdirSync(path, node.mode); } else { fs.writeFileSync(path, '', { mode: node.mode }); } }); return node; }, rename(oldNode, newDir, newName) { var oldPath = NODEFS.realPath(oldNode); var newPath = PATH.join2(NODEFS.realPath(newDir), newName); try { FS.unlink(newPath); } catch(e) {} NODEFS.tryFSOperation(() => fs.renameSync(oldPath, newPath)); oldNode.name = newName; }, unlink(parent, name) { var path = PATH.join2(NODEFS.realPath(parent), name); NODEFS.tryFSOperation(() => fs.unlinkSync(path)); }, rmdir(parent, name) { var path = PATH.join2(NODEFS.realPath(parent), name); NODEFS.tryFSOperation(() => fs.rmdirSync(path)); }, readdir(node) { var path = NODEFS.realPath(node); return NODEFS.tryFSOperation(() => fs.readdirSync(path)); }, symlink(parent, newName, oldPath) { var newPath = PATH.join2(NODEFS.realPath(parent), newName); NODEFS.tryFSOperation(() => fs.symlinkSync(oldPath, newPath)); }, readlink(node) { var path = NODEFS.realPath(node); return NODEFS.tryFSOperation(() => fs.readlinkSync(path)); }, statfs(path) { var stats = NODEFS.tryFSOperation(() => fs.statfsSync(path)); // Node.js doesn't provide frsize (fragment size). Set it to bsize (block size) // as they're often the same in many file systems. May not be accurate for all. stats.frsize = stats.bsize; return stats; } }, stream_ops: { getattr(stream) { return NODEFS.getattr(() => fs.fstatSync(stream.nfd), stream.node); }, setattr(stream, attr) { NODEFS.setattr(stream.nfd, stream.node, attr, fs.fchmodSync, fs.futimesSync, fs.ftruncateSync, fs.fstatSync); }, open(stream) { var path = NODEFS.realPath(stream.node); NODEFS.tryFSOperation(() => { stream.shared.refcount = 1; stream.nfd = fs.openSync(path, NODEFS.flagsForNode(stream.flags)); }); }, close(stream) { NODEFS.tryFSOperation(() => { if (stream.nfd && --stream.shared.refcount === 0) { fs.closeSync(stream.nfd); } }); }, dup(stream) { stream.shared.refcount++; }, read(stream, buffer, offset, length, position) { return NODEFS.tryFSOperation(() => fs.readSync(stream.nfd, buffer, offset, length, position) ); }, write(stream, buffer, offset, length, position) { return NODEFS.tryFSOperation(() => fs.writeSync(stream.nfd, buffer, offset, length, position) ); }, llseek(stream, offset, whence) { var position = offset; if (whence === {{{ cDefs.SEEK_CUR }}}) { position += stream.position; } else if (whence === {{{ cDefs.SEEK_END }}}) { if (FS.isFile(stream.node.mode)) { NODEFS.tryFSOperation(() => { var stat = fs.fstatSync(stream.nfd); position += stat.size; }); } } if (position < 0) { throw new FS.ErrnoError({{{ cDefs.EINVAL }}}); } return position; }, mmap(stream, length, position, prot, flags) { if (!FS.isFile(stream.node.mode)) { throw new FS.ErrnoError({{{ cDefs.ENODEV }}}); } var ptr = mmapAlloc(length); NODEFS.stream_ops.read(stream, HEAP8, ptr, length, position); return { ptr, allocated: true }; }, msync(stream, buffer, offset, length, mmapFlags) { NODEFS.stream_ops.write(stream, buffer, 0, length, offset, false); // should we check if bytesWritten and length are the same? return 0; } } } #endif });