diff --git a/src/library_fs.js b/src/library_fs.js index 7ba5ebec5aa9f..0df60d410a511 100644 --- a/src/library_fs.js +++ b/src/library_fs.js @@ -479,6 +479,24 @@ FS.staticInit(); stream.stream_ops?.dup?.(stream); return stream; }, + streamGetAttr(stream) { + var node = stream.node; + var res = stream.stream_ops?.getattr?.(stream) ?? node.node_ops?.getattr?.(node); + if (res) { + return res; + } + throw new FS.ErrnoError({{{ cDefs.EPERM }}}); + }, + streamSetAttr(stream, attr) { + var node = stream.node; + var set; + if (set = stream.stream_ops.setattr) { + return set(stream, attr); + } else if (set = node.node_ops.setattr) { + return set(node, attr); + } + throw new FS.ErrnoError({{{ cDefs.EPERM }}}); + }, // // devices @@ -956,6 +974,10 @@ FS.staticInit(); } return node.node_ops.getattr(node); }, + fstat(fd) { + var stream = FS.getStreamChecked(fd); + return FS.streamGetAttr(stream); + }, lstat(path) { return FS.stat(path, true); }, @@ -981,7 +1003,10 @@ FS.staticInit(); }, fchmod(fd, mode) { var stream = FS.getStreamChecked(fd); - FS.chmod(stream.node, mode); + FS.streamSetAttr(stream, { + mode: (mode & {{{ cDefs.S_IALLUGO }}}) | (stream.node.mode & ~{{{ cDefs.S_IALLUGO }}}), + ctime: Date.now() + }); }, chown(path, uid, gid, dontFollow) { var node; @@ -1005,7 +1030,22 @@ FS.staticInit(); }, fchown(fd, uid, gid) { var stream = FS.getStreamChecked(fd); - FS.chown(stream.node, uid, gid); + FS.streamSetAttr(stream, { + timestamp: Date.now() + // we ignore the uid / gid for now + }); + }, + truncateChecks(node) { + if (FS.isDir(node.mode)) { + throw new FS.ErrnoError({{{ cDefs.EISDIR }}}); + } + if (!FS.isFile(node.mode)) { + throw new FS.ErrnoError({{{ cDefs.EINVAL }}}); + } + var errCode = FS.nodePermissions(node, 'w'); + if (errCode) { + throw new FS.ErrnoError(errCode); + } }, truncate(path, len) { if (len < 0) { @@ -1018,19 +1058,6 @@ FS.staticInit(); } else { node = path; } - if (!node.node_ops.setattr) { - throw new FS.ErrnoError({{{ cDefs.EPERM }}}); - } - if (FS.isDir(node.mode)) { - throw new FS.ErrnoError({{{ cDefs.EISDIR }}}); - } - if (!FS.isFile(node.mode)) { - throw new FS.ErrnoError({{{ cDefs.EINVAL }}}); - } - var errCode = FS.nodePermissions(node, 'w'); - if (errCode) { - throw new FS.ErrnoError(errCode); - } node.node_ops.setattr(node, { size: len, timestamp: Date.now() @@ -1038,10 +1065,14 @@ FS.staticInit(); }, ftruncate(fd, len) { var stream = FS.getStreamChecked(fd); - if ((stream.flags & {{{ cDefs.O_ACCMODE }}}) === {{{ cDefs.O_RDONLY}}}) { + if (len < 0 || (stream.flags & {{{ cDefs.O_ACCMODE }}}) === {{{ cDefs.O_RDONLY}}}) { throw new FS.ErrnoError({{{ cDefs.EINVAL }}}); } - FS.truncate(stream.node, len); + FS.truncateChecks(stream.node); + FS.streamSetAttr(stream, { + size: len, + timestamp: Date.now() + }); }, utime(path, atime, mtime) { var lookup = FS.lookupPath(path, { follow: true }); diff --git a/src/library_nodefs.js b/src/library_nodefs.js index b5d893f5a6355..dfd7a23ef70f7 100644 --- a/src/library_nodefs.js +++ b/src/library_nodefs.js @@ -116,74 +116,84 @@ addToLibrary({ } 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 getatrr: + // - arg is a native path + // - chmod, utimes, truncate are fs.chmodSync, fs.utimesSync, fs.truncateSync + // For stream getatrr: + // - 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); - var stat; - NODEFS.tryFSOperation(() => stat = fs.lstatSync(path)); - 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 - }; + return NODEFS.getattr(() => fs.lstatSync(path), node); }, setattr(node, attr) { var path = NODEFS.realPath(node); - NODEFS.tryFSOperation(() => { - if (attr.mode !== undefined) { - if (attr.dontFollow) { - throw new FS.ErrnoError({{{ cDefs.ENOSYS }}}); - } - 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 }}}; - } - fs.chmodSync(path, 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 stat = () => fs.lstatSync(NODEFS.realPath(node)); - var atime = new Date(attr.atime ?? stat().atime); - var mtime = new Date(attr.mtime ?? stat().mtime); - fs.utimesSync(path, atime, mtime); - } - if (attr.size !== undefined) { - fs.truncateSync(path, attr.size); - } - }); + 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); @@ -241,18 +251,22 @@ addToLibrary({ } }, 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(() => { - if (FS.isFile(stream.node.mode)) { - stream.shared.refcount = 1; - stream.nfd = fs.openSync(path, NODEFS.flagsForNode(stream.flags)); - } + stream.shared.refcount = 1; + stream.nfd = fs.openSync(path, NODEFS.flagsForNode(stream.flags)); }); }, close(stream) { NODEFS.tryFSOperation(() => { - if (FS.isFile(stream.node.mode) && stream.nfd && --stream.shared.refcount === 0) { + if (stream.nfd && --stream.shared.refcount === 0) { fs.closeSync(stream.nfd); } }); diff --git a/src/library_noderawfs.js b/src/library_noderawfs.js index 81ab16962a89c..a6d38afdce744 100644 --- a/src/library_noderawfs.js +++ b/src/library_noderawfs.js @@ -79,6 +79,10 @@ addToLibrary({ } return stat; }, + fstat(fd) { + var stream = FS.getStreamChecked(fd); + return fs.fstatSync(stream.nfd); + }, chmod(path, mode, dontFollow) { mode &= {{{ cDefs.S_IALLUGO }}}; if (NODEFS.isWindows) { diff --git a/src/library_syscall.js b/src/library_syscall.js index 6acb166b457b8..94ccc495d4fcb 100644 --- a/src/library_syscall.js +++ b/src/library_syscall.js @@ -678,8 +678,7 @@ var SyscallsLibrary = { return SYSCALLS.writeStat(buf, FS.lstat(path)); }, __syscall_fstat64: (fd, buf) => { - var stream = SYSCALLS.getStreamFromFD(fd); - return SYSCALLS.writeStat(buf, FS.stat(stream.path)); + return SYSCALLS.writeStat(buf, FS.fstat(fd)); }, __syscall_fchown32: (fd, owner, group) => { FS.fchown(fd, owner, group); diff --git a/test/fs/test_stat_unnamed_file_descriptor.c b/test/fs/test_stat_unnamed_file_descriptor.c new file mode 100644 index 0000000000000..a1d92f5cbaba7 --- /dev/null +++ b/test/fs/test_stat_unnamed_file_descriptor.c @@ -0,0 +1,20 @@ +#include +#include +#include +#include +#include "stdio.h" + +int main() { + int fd = open("file.txt", O_RDWR | O_CREAT, 0666); + unlink("file.txt"); + int res; + struct stat buf; + res = fstat(fd, &buf); + assert(res == 0); + assert(buf.st_atime > 1000000000); + res = fchmod(fd, 0777); + assert(res == 0); + res = ftruncate(fd, 10); + assert(res == 0); + printf("success\n"); +} diff --git a/test/test_core.py b/test/test_core.py index f22252f32ce66..7df262d41e123 100644 --- a/test/test_core.py +++ b/test/test_core.py @@ -5847,6 +5847,16 @@ def test_fs_64bit(self): self.set_setting('FORCE_FILESYSTEM') self.do_runf('fs/test_64bit.c', 'success') + @crossplatform + @also_with_nodefs_both + def test_fs_stat_unnamed_file_descriptor(self): + nodefs = '-DNODEFS' in self.emcc_args or '-DNODERAWFS' in self.emcc_args + if self.get_setting('WASMFS'): + if nodefs: + self.skipTest('NODEFS in WasmFS') + self.set_setting('FORCE_FILESYSTEM') + self.do_runf('fs/test_stat_unnamed_file_descriptor.c', 'success') + @requires_node @crossplatform @with_all_fs