From f1e082bfaf444d39638f724c16dea726f4619561 Mon Sep 17 00:00:00 2001
From: Hood Chatham <roberthoodchatham@gmail.com>
Date: Mon, 13 Jan 2025 12:31:36 +0100
Subject: [PATCH 1/3] [FS] Make fstatfs actually work

Prior to this change, `fstatfs` calls `statfs64` with the `char*` path
pointer argument set to zero. Generally byte zero of the memory seems
to be null, so when `statfs64` converts this `char*` to a JS string it
gets the empty string. Then it calls `FS.statfs` with the empty string
as an argument. `FS.statfs` calls `lookupPath` with an empty string
argument which returns a `null` node, so `fstatfs` returns the default
values for when there is no statfs handler in the files system.

This fixes the problem and adds testing for statfs/fstatfs on nodefs and
rawfs. We have to add separate `FS.statfsNode` and `FS.statfsStream` handlers
because in some cases `stream.node` is null and in some cases `stream.path
is useless (like for stdout). Also, in noderawfs we need to give paths for
the standard streams because there is unfortunately no `fs.fstatfsSync` so
we can only access information from the host via a path. This means the
noderawfs  test won't be portable to windows.
---
 src/library_fs.js        | 15 ++++++++++-----
 src/library_noderawfs.js | 11 +++++++++--
 src/library_syscall.js   | 30 ++++++++++++++++++------------
 test/test_other.py       |  5 +++--
 test/unistd/fstatfs.c    | 13 +++++++++++--
 5 files changed, 51 insertions(+), 23 deletions(-)

diff --git a/src/library_fs.js b/src/library_fs.js
index 891c5200ccba2..59eb88a6ddf7d 100644
--- a/src/library_fs.js
+++ b/src/library_fs.js
@@ -691,9 +691,15 @@ FS.staticInit();
       return parent.node_ops.mknod(parent, name, mode, dev);
     },
     statfs(path) {
-
+      return FS.statfsNode(FS.lookupPath(path, {follow: true}).node);
+    },
+    statfsStream(stream) {
+      return FS.statfsNode(stream.node);
+    },
+    statfsNode(node) {
       // NOTE: None of the defaults here are true. We're just returning safe and
-      //       sane values.
+      //       sane values. Currently nodefs and rawfs replace these defaults,
+      //       other file systems leave them alone.
       var rtn = {
         bsize: 4096,
         frsize: 4096,
@@ -707,9 +713,8 @@ FS.staticInit();
         namelen: 255,
       };
 
-      var parent = FS.lookupPath(path, {follow: true}).node;
-      if (parent?.node_ops.statfs) {
-        Object.assign(rtn, parent.node_ops.statfs(parent.mount.opts.root));
+      if (node.node_ops.statfs) {
+        Object.assign(rtn, node.node_ops.statfs(node.mount.opts.root));
       }
       return rtn;
     },
diff --git a/src/library_noderawfs.js b/src/library_noderawfs.js
index 81ab16962a89c..89151b47c0a55 100644
--- a/src/library_noderawfs.js
+++ b/src/library_noderawfs.js
@@ -48,9 +48,10 @@ addToLibrary({
     },
     createStandardStreams() {
       // FIXME: tty is set to true to appease isatty(), the underlying ioctl syscalls still needs to be implemented, see issue #22264.
-      FS.createStream({ nfd: 0, position: 0, path: '', flags: 0, tty: true, seekable: false }, 0);
+      FS.createStream({ nfd: 0, position: 0, path: '/dev/stdin', flags: 0, tty: true, seekable: false }, 0);
+      var paths = [,'/dev/stdout', '/dev/stderr'];
       for (var i = 1; i < 3; i++) {
-        FS.createStream({ nfd: i, position: 0, path: '', flags: {{{ cDefs.O_TRUNC | cDefs.O_CREAT | cDefs.O_WRONLY }}}, tty: true, seekable: false }, i);
+        FS.createStream({ nfd: i, position: 0, path: paths[i], flags: {{{ cDefs.O_TRUNC | cDefs.O_CREAT | cDefs.O_WRONLY }}}, tty: true, seekable: false }, i);
       }
     },
     // generic function for all node creation
@@ -79,6 +80,12 @@ addToLibrary({
       }
       return stat;
     },
+    statfsStream(stream) {
+      return fs.statfsSync(stream.path);
+    },
+    statfsNode(node) {
+      return fs.statfsSync(node.path);
+    },
     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..41d2dfc4e1550 100644
--- a/src/library_syscall.js
+++ b/src/library_syscall.js
@@ -63,6 +63,18 @@ var SyscallsLibrary = {
       {{{ makeSetValue('buf', C_STRUCTS.stat.st_ino, 'stat.ino', 'i64') }}};
       return 0;
     },
+    writeStatFs(buf, stats) {
+      {{{ makeSetValue('buf', C_STRUCTS.statfs.f_bsize, 'stats.bsize', 'i32') }}};
+      {{{ makeSetValue('buf', C_STRUCTS.statfs.f_frsize, 'stats.bsize', 'i32') }}};
+      {{{ makeSetValue('buf', C_STRUCTS.statfs.f_blocks, 'stats.blocks', 'i32') }}};
+      {{{ makeSetValue('buf', C_STRUCTS.statfs.f_bfree, 'stats.bfree', 'i32') }}};
+      {{{ makeSetValue('buf', C_STRUCTS.statfs.f_bavail, 'stats.bavail', 'i32') }}};
+      {{{ makeSetValue('buf', C_STRUCTS.statfs.f_files, 'stats.files', 'i32') }}};
+      {{{ makeSetValue('buf', C_STRUCTS.statfs.f_ffree, 'stats.ffree', 'i32') }}};
+      {{{ makeSetValue('buf', C_STRUCTS.statfs.f_fsid, 'stats.fsid', 'i32') }}};
+      {{{ makeSetValue('buf', C_STRUCTS.statfs.f_flags, 'stats.flags', 'i32') }}};  // ST_NOSUID
+      {{{ makeSetValue('buf', C_STRUCTS.statfs.f_namelen, 'stats.namelen', 'i32') }}};
+    },
     doMsync(addr, stream, len, flags, offset) {
       if (!FS.isFile(stream.node.mode)) {
         throw new FS.ErrnoError({{{ cDefs.ENODEV }}});
@@ -801,23 +813,17 @@ var SyscallsLibrary = {
 #if ASSERTIONS
     assert(size === {{{ C_STRUCTS.statfs.__size__ }}});
 #endif
-    var stats = FS.statfs(SYSCALLS.getStr(path));
-    {{{ makeSetValue('buf', C_STRUCTS.statfs.f_bsize, 'stats.bsize', 'i32') }}};
-    {{{ makeSetValue('buf', C_STRUCTS.statfs.f_frsize, 'stats.bsize', 'i32') }}};
-    {{{ makeSetValue('buf', C_STRUCTS.statfs.f_blocks, 'stats.blocks', 'i32') }}};
-    {{{ makeSetValue('buf', C_STRUCTS.statfs.f_bfree, 'stats.bfree', 'i32') }}};
-    {{{ makeSetValue('buf', C_STRUCTS.statfs.f_bavail, 'stats.bavail', 'i32') }}};
-    {{{ makeSetValue('buf', C_STRUCTS.statfs.f_files, 'stats.files', 'i32') }}};
-    {{{ makeSetValue('buf', C_STRUCTS.statfs.f_ffree, 'stats.ffree', 'i32') }}};
-    {{{ makeSetValue('buf', C_STRUCTS.statfs.f_fsid, 'stats.fsid', 'i32') }}};
-    {{{ makeSetValue('buf', C_STRUCTS.statfs.f_flags, 'stats.flags', 'i32') }}};  // ST_NOSUID
-    {{{ makeSetValue('buf', C_STRUCTS.statfs.f_namelen, 'stats.namelen', 'i32') }}};
+    SYSCALLS.writeStatFs(buf, FS.statfs(SYSCALLS.getStr(path)));
     return 0;
   },
   __syscall_fstatfs64__deps: ['__syscall_statfs64'],
   __syscall_fstatfs64: (fd, size, buf) => {
+#if ASSERTIONS
+    assert(size === {{{ C_STRUCTS.statfs.__size__ }}});
+#endif
     var stream = SYSCALLS.getStreamFromFD(fd);
-    return ___syscall_statfs64(0, size, buf);
+    SYSCALLS.writeStatFs(buf, FS.statfsStream(stream));
+    return 0;
   },
   __syscall_fadvise64__nothrow: true,
   __syscall_fadvise64__proxy: 'none',
diff --git a/test/test_other.py b/test/test_other.py
index 23fe7c164cd9d..2e6d241cc1738 100644
--- a/test/test_other.py
+++ b/test/test_other.py
@@ -36,7 +36,7 @@
 from common import create_file, parameterized, NON_ZERO, node_pthreads, TEST_ROOT, test_file
 from common import compiler_for, EMBUILDER, requires_v8, requires_node, requires_wasm64, requires_node_canary
 from common import requires_wasm_eh, crossplatform, with_all_eh_sjlj, with_all_sjlj
-from common import also_with_standalone_wasm, also_with_wasm2js, also_with_noderawfs, also_with_wasmfs
+from common import also_with_standalone_wasm, also_with_wasm2js, also_with_noderawfs, also_with_wasmfs, with_all_fs
 from common import also_with_minimal_runtime, also_with_wasm_bigint, also_with_wasm64, flaky
 from common import EMTEST_BUILD_VERBOSE, PYTHON, WEBIDL_BINDER
 from common import requires_network, parameterize
@@ -13741,7 +13741,8 @@ def test_unistd_login(self):
   def test_unistd_sleep(self):
     self.do_run_in_out_file_test('unistd/sleep.c')
 
-  @also_with_wasmfs
+  @crossplatform
+  @with_all_fs
   def test_unistd_fstatfs(self):
     self.do_run_in_out_file_test('unistd/fstatfs.c')
 
diff --git a/test/unistd/fstatfs.c b/test/unistd/fstatfs.c
index 6d9cf08d2413b..be6c602d3e692 100644
--- a/test/unistd/fstatfs.c
+++ b/test/unistd/fstatfs.c
@@ -2,11 +2,20 @@
 #include <stdio.h>
 #include <sys/vfs.h>
 #include <unistd.h>
+#include <fcntl.h>
 
 int main() {
   struct statfs buf;
-  int rtn = fstatfs(STDOUT_FILENO, &buf);
-  assert(rtn == 0);
+
+  int rtn;
+  assert(fstatfs(STDOUT_FILENO, &buf) == 0);
+  printf("f_type: %ld\n", buf.f_type);
+
+  int f = open("file", O_RDWR | O_CREAT);
+  assert(fstatfs(f, &buf) == 0);
+  printf("f_type: %ld\n", buf.f_type);
+
+  assert(statfs("file", &buf) == 0);
   printf("f_type: %ld\n", buf.f_type);
   return 0;
 }

From 31bb3b1bd2a01b5efcb4ba8c69f397455406dbe8 Mon Sep 17 00:00:00 2001
From: Hood Chatham <roberthoodchatham@gmail.com>
Date: Mon, 13 Jan 2025 14:56:48 +0100
Subject: [PATCH 2/3] Skip rawfs test on windows

---
 test/test_other.py | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/test/test_other.py b/test/test_other.py
index 2e6d241cc1738..4eaa556bbeac1 100644
--- a/test/test_other.py
+++ b/test/test_other.py
@@ -13744,6 +13744,8 @@ def test_unistd_sleep(self):
   @crossplatform
   @with_all_fs
   def test_unistd_fstatfs(self):
+    if '-DNODERAWFS' in self.emcc_args and WINDOWS:
+      self.skipTest('Cannot look up /dev/stdout on windows')
     self.do_run_in_out_file_test('unistd/fstatfs.c')
 
   @no_windows("test is Linux-specific")

From 4027e6cbc64ef16032ba73fa9bdde8e029b903dc Mon Sep 17 00:00:00 2001
From: Hood Chatham <roberthoodchatham@gmail.com>
Date: Mon, 13 Jan 2025 20:05:37 +0100
Subject: [PATCH 3/3] Add comment

---
 src/library_fs.js | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/src/library_fs.js b/src/library_fs.js
index f53b24e2b6ed0..2bfeb673589cb 100644
--- a/src/library_fs.js
+++ b/src/library_fs.js
@@ -696,6 +696,9 @@ FS.staticInit();
       return FS.statfsNode(FS.lookupPath(path, {follow: true}).node);
     },
     statfsStream(stream) {
+      // We keep a separate statfsStream function because noderawfs overrides
+      // it. In noderawfs, stream.node is sometimes null. Instead, we need to
+      // look at stream.path.
       return FS.statfsNode(stream.node);
     },
     statfsNode(node) {
