Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow pipes to be stat'ed #23306

Open
wants to merge 30 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
f3f36d5
Make fstat work on file descriptor with no name
hoodmane Dec 3, 2024
260278f
Fix node rawfs
hoodmane Dec 3, 2024
f865bdb
Cleanup
hoodmane Dec 3, 2024
cac8b19
Fix nodefs handling of unnamed file descriptors
hoodmane Dec 3, 2024
cad0ddf
Fix indentation
hoodmane Dec 4, 2024
8b1711e
Apply review comments to test
hoodmane Dec 4, 2024
c961354
Fix flake8
hoodmane Dec 4, 2024
bebfc6b
Better argment names and comments
hoodmane Dec 4, 2024
7f4533e
Fix ftruncate
hoodmane Dec 4, 2024
2c41571
Fix directory fds for nameless file patch
hoodmane Dec 4, 2024
6a4dfc2
Implement both truncate and ftruncate via truncateCommon
hoodmane Dec 4, 2024
9d0c79a
Merge branch 'main' into anonymous-file-descriptors
hoodmane Dec 5, 2024
baadd84
Merge branch 'main' into anonymous-file-descriptors
hoodmane Dec 6, 2024
3aa358f
Fix merge
hoodmane Dec 6, 2024
04192f0
Merge branch 'main' into anonymous-file-descriptors
hoodmane Dec 9, 2024
989cf22
Refactor things a bit more
hoodmane Dec 9, 2024
ad02402
Change doStat to writeStat
hoodmane Dec 9, 2024
84bcf30
Fix tests
hoodmane Dec 9, 2024
ba35730
Fix reference error
hoodmane Dec 9, 2024
4e67e7f
Fix test
hoodmane Dec 10, 2024
9399402
Declare stream variable
hoodmane Dec 10, 2024
7593dc1
Merge branch 'main' into anonymous-file-descriptors
hoodmane Dec 12, 2024
efff804
Merge branch 'main' into anonymous-file-descriptors
hoodmane Dec 18, 2024
20de480
Merge branch 'main' into anonymous-file-descriptors
hoodmane Dec 19, 2024
e83ac75
Remove incorrect comment
hoodmane Dec 19, 2024
b509358
Fix merge
hoodmane Dec 19, 2024
8c556c8
Address review comment
hoodmane Dec 19, 2024
4ed8004
Merge branch 'main' into anonymous-file-descriptors
hoodmane Jan 6, 2025
bdfca83
Fix test
hoodmane Jan 6, 2025
f81c931
Allow pipes to be stat'ed
hoodmane Dec 11, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 56 additions & 18 deletions src/library_fs.js
Original file line number Diff line number Diff line change
Expand Up @@ -956,6 +956,16 @@ FS.staticInit();
}
return node.node_ops.getattr(node);
},
fstat(fd) {
var stream = FS.getStreamChecked(fd);
var node = stream.node;
if (stream.stream_ops.getattr) {
return stream.stream_ops.getattr(stream);
} else if (node.node_ops.getattr) {
return node.node_ops.getattr(node);
}
throw new FS.ErrnoError({{{ cDefs.EPERM }}});
},
lstat(path) {
return FS.stat(path, true);
},
Expand All @@ -980,7 +990,17 @@ FS.staticInit();
},
fchmod(fd, mode) {
var stream = FS.getStreamChecked(fd);
FS.chmod(stream.node, mode);
var node = stream.node;
var attrs = {
mode: (mode & {{{ cDefs.S_IALLUGO }}}) | (node.mode & ~{{{ cDefs.S_IALLUGO }}}),
ctime: Date.now()
};
if (stream.stream_ops.getattr) {
return stream.stream_ops.setattr(stream, attrs);
} else if (node.node_ops.getattr) {
return node.node_ops.setattr(node, attrs);
}
throw new FS.ErrnoError({{{ cDefs.EPERM }}});
},
chown(path, uid, gid, dontFollow) {
var node;
Expand All @@ -1003,20 +1023,20 @@ FS.staticInit();
},
fchown(fd, uid, gid) {
var stream = FS.getStreamChecked(fd);
FS.chown(stream.node, uid, gid);
},
truncate(path, len) {
if (len < 0) {
throw new FS.ErrnoError({{{ cDefs.EINVAL }}});
}
var node;
if (typeof path == 'string') {
var lookup = FS.lookupPath(path, { follow: true });
node = lookup.node;
} else {
node = path;
var node = stream.node;
var attrs = {
timestamp: Date.now()
// we ignore the uid / gid for now
};
if (stream.stream_ops.getattr) {
return stream.stream_ops.setattr(stream, attrs);
} else if (node.node_ops.getattr) {
return node.node_ops.setattr(node, attrs);
}
if (!node.node_ops.setattr) {
throw new FS.ErrnoError({{{ cDefs.EPERM }}});
},
truncateCommon(node, stream, len) {
if (!node.node_ops.setattr && !stream?.stream_ops.setattr) {
throw new FS.ErrnoError({{{ cDefs.EPERM }}});
}
if (FS.isDir(node.mode)) {
Expand All @@ -1029,17 +1049,35 @@ FS.staticInit();
if (errCode) {
throw new FS.ErrnoError(errCode);
}
node.node_ops.setattr(node, {
var attrs = {
size: len,
timestamp: Date.now()
});
};
if (stream?.stream_ops.setattr) {
stream.stream_ops.setattr(stream, attrs);
} else {
node.node_ops.setattr(node, attrs);
}
},
truncate(path, len) {
if (len < 0) {
throw new FS.ErrnoError({{{ cDefs.EINVAL }}});
}
var node;
if (typeof path == 'string') {
var lookup = FS.lookupPath(path, { follow: true });
node = lookup.node;
} else {
node = path;
}
FS.truncateCommon(node, undefined, len, false);
},
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.truncateCommon(stream.node, stream, len);
},
utime(path, atime, mtime) {
var lookup = FS.lookupPath(path, { follow: true });
Expand Down
131 changes: 73 additions & 58 deletions src/library_nodefs.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,65 +116,76 @@ 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) {
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 (attr.atime || attr.mtime) {
var atime = attr.atime && new Date(attr.atime);
var mtime = attr.mtime && new Date(attr.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) {
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 (attr.atime || attr.mtime) {
var atime = attr.atime && new Date(attr.atime);
var mtime = attr.mtime && new Date(attr.mtime);
fs.utimesSync(path, atime, mtime);
}
if (attr.size !== undefined) {
fs.truncateSync(path, attr.size);
}
});
NODEFS.setattr(path, node, attr, fs.chmodSync, fs.utimesSync, fs.truncateSync);
},
lookup(parent, name) {
var path = PATH.join2(NODEFS.realPath(parent), name);
Expand Down Expand Up @@ -232,18 +243,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);
},
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);
}
});
Expand Down
4 changes: 4 additions & 0 deletions src/library_noderawfs.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
20 changes: 20 additions & 0 deletions src/library_pipefs.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ addToLibrary({
// refcnt 2 because pipe has a read end and a write end. We need to be
// able to read from the read end after write end is closed.
refcnt : 2,
timestamp: new Date(),
};

pipe.buckets.push({
Expand Down Expand Up @@ -60,6 +61,25 @@ addToLibrary({
};
},
stream_ops: {
getattr(stream) {
var node = stream.node;
var timestamp = node.pipe.timestamp;
return {
dev: 14,
ino: node.id,
mode: 0o10600,
nlink: 1,
uid: 0,
gid: 0,
rdev: 0,
size: 0,
atime: timestamp,
mtime: timestamp,
ctime: timestamp,
blksize: 4096,
blocks: 0,
};
},
poll(stream) {
var pipe = stream.node.pipe;

Expand Down
3 changes: 1 addition & 2 deletions src/library_syscall.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
21 changes: 21 additions & 0 deletions test/fs/test_stat_unnamed_file_descriptor.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <assert.h>
#include "stdio.h"

int main() {
setup();
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");
}
10 changes: 10 additions & 0 deletions test/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -5842,6 +5842,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', emcc_args=args)

@requires_node
@crossplatform
@with_all_fs
Expand Down
5 changes: 5 additions & 0 deletions test/unistd/pipe.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <poll.h>
#include <errno.h>
Expand Down Expand Up @@ -65,6 +66,10 @@ int test_most() {

assert(pipe(fd) == 0);

// Test that pipe is statable
struct stat st;
assert(fstat(fd[0], &st) == 0);

// Test that pipe is not seekable

memset(buf, 0, sizeof buf);
Expand Down
Loading