Skip to content

Commit ac676d5

Browse files
authored
[nodefs] Return real values for statvfs via __syscall_statfs64 #22631 (#22932)
statfs syscall functions are returning hardcoded default values which can in some cases lead to unexpected results. This PR introduces a way for __syscall_statfs64 to return real values if the underlying filesystem supports this. For now, I've only implemented this for the NODEFS filesystem but we can expand this to other filesystems when it makes sense. Additionally, in the previous defaults, ffree could be larger than files which is incorrect, this has also been addressed. Fixes: #22607
1 parent 55934a5 commit ac676d5

7 files changed

+95
-19
lines changed

src/library_fs.js

+24-1
Original file line numberDiff line numberDiff line change
@@ -666,6 +666,29 @@ FS.staticInit();
666666
}
667667
return parent.node_ops.mknod(parent, name, mode, dev);
668668
},
669+
statfs(path) {
670+
671+
// NOTE: None of the defaults here are true. We're just returning safe and
672+
// sane values.
673+
var rtn = {
674+
bsize: 4096,
675+
frsize: 4096,
676+
blocks: 1e6,
677+
bfree: 5e5,
678+
bavail: 5e5,
679+
files: FS.nextInode,
680+
ffree: FS.nextInode - 1,
681+
fsid: 42,
682+
flags: 2,
683+
namelen: 255,
684+
};
685+
686+
var parent = FS.lookupPath(path, {follow: true}).node;
687+
if (parent?.node_ops.statfs) {
688+
Object.assign(rtn, parent.node_ops.statfs(parent.mount.opts.root));
689+
}
690+
return rtn;
691+
},
669692
// helpers to create specific types of nodes
670693
create(path, mode = 0o666) {
671694
mode &= {{{ cDefs.S_IALLUGO }}};
@@ -807,7 +830,7 @@ FS.staticInit();
807830
// do the underlying fs rename
808831
try {
809832
old_dir.node_ops.rename(old_node, new_dir, new_name);
810-
// update old node (we do this here to avoid each backend
833+
// update old node (we do this here to avoid each backend
811834
// needing to)
812835
old_node.parent = new_dir;
813836
} catch (e) {

src/library_nodefs.js

+7
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,13 @@ addToLibrary({
219219
var path = NODEFS.realPath(node);
220220
return NODEFS.tryFSOperation(() => fs.readlinkSync(path));
221221
},
222+
statfs(path) {
223+
var stats = NODEFS.tryFSOperation(() => fs.statfsSync(path));
224+
// Node.js doesn't provide frsize (fragment size). Set it to bsize (block size)
225+
// as they're often the same in many file systems. May not be accurate for all.
226+
stats.frsize = stats.bsize;
227+
return stats;
228+
}
222229
},
223230
stream_ops: {
224231
open(stream) {

src/library_syscall.js

+11-13
Original file line numberDiff line numberDiff line change
@@ -790,22 +790,20 @@ var SyscallsLibrary = {
790790
},
791791
792792
__syscall_statfs64: (path, size, buf) => {
793-
path = SYSCALLS.getStr(path);
794793
#if ASSERTIONS
795794
assert(size === {{{ C_STRUCTS.statfs.__size__ }}});
796795
#endif
797-
// NOTE: None of the constants here are true. We're just returning safe and
798-
// sane values.
799-
{{{ makeSetValue('buf', C_STRUCTS.statfs.f_bsize, '4096', 'i32') }}};
800-
{{{ makeSetValue('buf', C_STRUCTS.statfs.f_frsize, '4096', 'i32') }}};
801-
{{{ makeSetValue('buf', C_STRUCTS.statfs.f_blocks, '1000000', 'i32') }}};
802-
{{{ makeSetValue('buf', C_STRUCTS.statfs.f_bfree, '500000', 'i32') }}};
803-
{{{ makeSetValue('buf', C_STRUCTS.statfs.f_bavail, '500000', 'i32') }}};
804-
{{{ makeSetValue('buf', C_STRUCTS.statfs.f_files, 'FS.nextInode', 'i32') }}};
805-
{{{ makeSetValue('buf', C_STRUCTS.statfs.f_ffree, '1000000', 'i32') }}};
806-
{{{ makeSetValue('buf', C_STRUCTS.statfs.f_fsid, '42', 'i32') }}};
807-
{{{ makeSetValue('buf', C_STRUCTS.statfs.f_flags, '2', 'i32') }}}; // ST_NOSUID
808-
{{{ makeSetValue('buf', C_STRUCTS.statfs.f_namelen, '255', 'i32') }}};
796+
var stats = FS.statfs(SYSCALLS.getStr(path));
797+
{{{ makeSetValue('buf', C_STRUCTS.statfs.f_bsize, 'stats.bsize', 'i32') }}};
798+
{{{ makeSetValue('buf', C_STRUCTS.statfs.f_frsize, 'stats.bsize', 'i32') }}};
799+
{{{ makeSetValue('buf', C_STRUCTS.statfs.f_blocks, 'stats.blocks', 'i32') }}};
800+
{{{ makeSetValue('buf', C_STRUCTS.statfs.f_bfree, 'stats.bfree', 'i32') }}};
801+
{{{ makeSetValue('buf', C_STRUCTS.statfs.f_bavail, 'stats.bavail', 'i32') }}};
802+
{{{ makeSetValue('buf', C_STRUCTS.statfs.f_files, 'stats.files', 'i32') }}};
803+
{{{ makeSetValue('buf', C_STRUCTS.statfs.f_ffree, 'stats.ffree', 'i32') }}};
804+
{{{ makeSetValue('buf', C_STRUCTS.statfs.f_fsid, 'stats.fsid', 'i32') }}};
805+
{{{ makeSetValue('buf', C_STRUCTS.statfs.f_flags, 'stats.flags', 'i32') }}}; // ST_NOSUID
806+
{{{ makeSetValue('buf', C_STRUCTS.statfs.f_namelen, 'stats.namelen', 'i32') }}};
809807
return 0;
810808
},
811809
__syscall_fstatfs64__deps: ['__syscall_statfs64'],

test/core/test_statvfs.c

+5-2
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,13 @@
88
#include <stdio.h>
99
#include <errno.h>
1010
#include <sys/statvfs.h>
11+
#include <sys/types.h>
12+
#include <sys/stat.h>
1113

1214
int main() {
1315
struct statvfs s;
1416

17+
mkdir("/test", S_IRWXU | S_IRWXG | S_IRWXO);
1518
printf("result: %d\n", statvfs("/test", &s));
1619
printf("errno: %d\n", errno);
1720

@@ -21,8 +24,8 @@ int main() {
2124
printf("f_bfree: %u\n", s.f_bfree);
2225
printf("f_bavail: %u\n", s.f_bavail);
2326
printf("f_files: %d\n", s.f_files > 5);
24-
printf("f_ffree: %u\n", s.f_ffree);
25-
printf("f_favail: %u\n", s.f_favail);
27+
printf("f_ffree: %u\n", s.f_ffree <= s.f_files && s.f_ffree > 0);
28+
printf("f_favail: %u\n", s.f_favail <= s.f_files && s.f_favail > 0);
2629
printf("f_fsid: %lu\n", s.f_fsid);
2730
printf("f_flag: %lu\n", s.f_flag);
2831
printf("f_namemax: %lu\n", s.f_namemax);

test/core/test_statvfs.out

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ f_blocks: 1000000
66
f_bfree: 500000
77
f_bavail: 500000
88
f_files: 1
9-
f_ffree: 1000000
10-
f_favail: 1000000
9+
f_ffree: 1
10+
f_favail: 1
1111
f_fsid: 42
1212
f_flag: 2
1313
f_namemax: 255

test/fs/test_nodefs_statvfs.c

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
#include <assert.h>
2+
#include <stdio.h>
3+
#include <sys/statvfs.h>
4+
#include <emscripten.h>
5+
6+
void test_statvfs(const char *path) {
7+
printf("Testing statfs for path: %s\n", path);
8+
struct statvfs st;
9+
int result = statvfs(path, &st);
10+
11+
assert(result == 0 && "statvfs should succeed");
12+
13+
// Basic sanity checks
14+
assert(st.f_bsize > 0 && "Block size should be positive");
15+
assert(st.f_blocks > 0 && "Total blocks should be positive");
16+
assert(st.f_bfree <= st.f_blocks && "Free blocks should not exceed total blocks");
17+
assert(st.f_bavail <= st.f_bfree && "Available blocks should not exceed free blocks");
18+
assert(st.f_files >= 0 && "Total inodes should be 0 or positive");
19+
assert(st.f_ffree <= st.f_files && "Free inodes should not exceed total inodes");
20+
}
21+
22+
void setup() {
23+
EM_ASM(
24+
FS.mkdir('/working');
25+
FS.mount(NODEFS, { root: '.' }, '/working');
26+
);
27+
}
28+
29+
int main() {
30+
setup();
31+
// Test the root filesystem (which should be MEMFS by default)
32+
test_statvfs("/");
33+
test_statvfs("/working");
34+
puts("success");
35+
}

test/test_core.py

+11-1
Original file line numberDiff line numberDiff line change
@@ -5784,10 +5784,20 @@ def test_fs_nodefs_readdir(self):
57845784
# externally setup an existing folder structure: existing/a
57855785
if self.get_setting('WASMFS'):
57865786
self.set_setting('FORCE_FILESYSTEM')
5787-
os.makedirs(os.path.join(self.working_dir, 'existing', 'a'))
5787+
os.makedirs('existing/a')
57885788
self.emcc_args += ['-lnodefs.js']
57895789
self.do_runf('fs/test_nodefs_readdir.c', 'success')
57905790

5791+
@requires_node
5792+
@crossplatform
5793+
def test_fs_nodefs_statvfs(self):
5794+
# externally setup an existing folder structure: existing/a
5795+
if self.get_setting('WASMFS'):
5796+
self.set_setting('FORCE_FILESYSTEM')
5797+
os.makedirs('existing/a')
5798+
self.emcc_args += ['-lnodefs.js']
5799+
self.do_runf('fs/test_nodefs_statvfs.c', 'success')
5800+
57915801
@no_windows('no symlink support on windows')
57925802
@requires_node
57935803
def test_fs_noderawfs_nofollow(self):

0 commit comments

Comments
 (0)