From ef54b1da6e972b4c102a488d2d5c93d5acb89512 Mon Sep 17 00:00:00 2001 From: Project516 <138796702+Project516@users.noreply.github.com> Date: Wed, 4 Nov 2026 16:45:04 -0600 Subject: [PATCH 1/2] Report a file system's block size and space from the host, so direct I/O and FileStore work as on HotSpot --- src/fs.ts | 16 ++++++++++++++++ src/natives/io.ts | 8 +++++++- src/natives/nio.ts | 14 ++++++++++++-- src/node.ts | 8 ++++++++ test/java/FileStores.java | 20 ++++++++++++++++++++ test/jtreg/passing.txt | 4 ++++ 6 files changed, 67 insertions(+), 3 deletions(-) create mode 100644 test/java/FileStores.java diff --git a/src/fs.ts b/src/fs.ts index 9f65408..e5211ea 100644 --- a/src/fs.ts +++ b/src/fs.ts @@ -1,5 +1,12 @@ // Virtual file system the Java side sees. Paths are absolute and use "/". +export interface Space { + bsize: bigint; + blocks: bigint; + bfree: bigint; + bavail: bigint; +} + export interface Stat { dir: boolean; size: number; @@ -30,6 +37,8 @@ export interface Backend { mkdir?(path: string): boolean; remove?(path: string): boolean; rename?(from: string, to: string): boolean; + // the file system holding path: block size and block counts, as statvfs gives them + space?(path: string): Space | null; // sets the modification time, in milliseconds since the epoch setMtime?(path: string, ms: number): boolean; // whether the host refuses to write the file, or to create it when it is missing @@ -184,6 +193,13 @@ export class VFS { return !!r && r[0].writable; } + // A file system without its own figures reports 4 KB blocks and no space, + // as Linux does for virtual file systems such as /proc. + space(path: string): Space { + const r = this.resolve(path); + return (r && r[0].space?.(r[1])) ?? { bsize: 4096n, blocks: 0n, bfree: 0n, bavail: 0n }; + } + setMtime(path: string, ms: number): boolean { const r = this.resolve(path); return !!r && r[0].writable && !!r[0].setMtime?.(r[1], ms); diff --git a/src/natives/io.ts b/src/natives/io.ts index 170ee2a..5f10865 100644 --- a/src/natives/io.ts +++ b/src/natives/io.ts @@ -479,6 +479,12 @@ export const natives: Natives = { renamePath(t, resolvePath(t, pathOf(t, a)), resolvePath(t, pathOf(t, b))), [`${UFS}.setLastModifiedTime0(Ljava/io/File;J)Z`]: (t, [, f, ms]) => setMtime(t, resolvePath(t, pathOf(t, f)), Number(ms)), [`${UFS}.setReadOnly0(Ljava/io/File;)Z`]: (t, [, f]) => chmod(t, f, (m) => m & ~0o222), - [`${UFS}.getSpace0(Ljava/io/File;I)J`]: () => 0n, + // SPACE_TOTAL, SPACE_FREE or SPACE_USABLE of the file system holding f; 0 if f is missing + [`${UFS}.getSpace0(Ljava/io/File;I)J`]: (t, [, f, kind]) => { + const abs = resolvePath(t, pathOf(t, f)); + if (!t.vm.fs.stat(abs)) return 0n; + const s = t.vm.fs.space(abs); + return s.bsize * (kind === 0 ? s.blocks : kind === 1 ? s.bfree : s.bavail); + }, [`${UFS}.getNameMax0(Ljava/lang/String;)J`]: () => 255n, }; diff --git a/src/natives/nio.ts b/src/natives/nio.ts index 929f1a9..aa2a350 100644 --- a/src/natives/nio.ts +++ b/src/natives/nio.ts @@ -241,7 +241,15 @@ export const natives: Natives = { [`${N}getpwnam0(J)I`]: () => 1000, [`${N}getgrnam0(J)I`]: () => 1000, [`${N}fchmodatNoFollowSupported0()Z`]: () => 0, - [`${N}statvfs0(JLsun/nio/fs/UnixFileStoreAttributes;)V`]: () => undefined, + [`${N}statvfs0(JLsun/nio/fs/UnixFileStoreAttributes;)V`]: (t, [p, attrs]) => { + const abs = path(t, p); + if (!t.vm.fs.stat(abs)) unixError(ENOENT); + const s = t.vm.fs.space(abs), A = "sun/nio/fs/UnixFileStoreAttributes"; + t.vm.setField(attrs, A, "f_frsize", s.bsize); + t.vm.setField(attrs, A, "f_blocks", s.blocks); + t.vm.setField(attrs, A, "f_bfree", s.bfree); + t.vm.setField(attrs, A, "f_bavail", s.bavail); + }, [`${N}flistxattr(IJI)I`]: () => 0, [`${N}getlinelen(J)I`]: () => -1, [`${N}rewind(J)V`]: () => undefined, @@ -435,7 +443,9 @@ Object.assign(natives, { "java/nio/MappedMemoryUtils.isLoaded0(JJJ)Z": () => 1, "java/nio/MappedMemoryUtils.load0(JJ)V": () => undefined, "java/nio/MappedMemoryUtils.unload0(JJ)V": () => undefined, - [`${UFD}setDirect0(Ljava/io/FileDescriptor;)I`]: () => -1, + // Bytes go through the same buffers either way; this only reports the block + // size direct I/O aligns to, as fstatvfs gives it. + [`${UFD}setDirect0(Ljava/io/FileDescriptor;)I`]: (t, [fd]) => Number(t.vm.fs.space(chanFile(t, fd).path).bsize), "sun/nio/ch/FileDispatcherImpl.init0()V": () => undefined, "sun/nio/ch/FileDispatcherImpl.transferTo0(Ljava/io/FileDescriptor;JJLjava/io/FileDescriptor;Z)J": () => BigInt(UNSUPPORTED_CASE), "sun/nio/ch/FileDispatcherImpl.transferFrom0(Ljava/io/FileDescriptor;Ljava/io/FileDescriptor;JJZ)J": () => BigInt(UNSUPPORTED_CASE), diff --git a/src/node.ts b/src/node.ts index c523f8e..eef27ef 100644 --- a/src/node.ts +++ b/src/node.ts @@ -147,6 +147,14 @@ export class NodeBackend implements Backend { return false; } } + space(p: string) { + try { + const s = fs.statfsSync(this.host(p), { bigint: true }); + return { bsize: s.bsize, blocks: s.blocks, bfree: s.bfree, bavail: s.bavail }; + } catch { + return null; + } + } setMtime(p: string, ms: number) { try { fs.utimesSync(this.host(p), fs.statSync(this.host(p)).atime, ms / 1000); diff --git a/test/java/FileStores.java b/test/java/FileStores.java new file mode 100644 index 0000000..e714654 --- /dev/null +++ b/test/java/FileStores.java @@ -0,0 +1,20 @@ +import java.io.File; +import java.nio.ByteBuffer; +import java.nio.file.*; + +// A file store reports its host file system's block size and totals, as +// statvfs gives them; direct I/O sizes its buffers from the block size. +public class FileStores { + public static void main(String[] args) throws Exception { + Path tmp = Path.of(System.getProperty("java.io.tmpdir")); + FileStore store = Files.getFileStore(tmp); + long block = store.getBlockSize(); + System.out.println("block size " + block); + System.out.println("total positive " + (store.getTotalSpace() > 0)); + System.out.println("File total matches " + (tmp.toFile().getTotalSpace() == store.getTotalSpace())); + System.out.println("usable within total " + (store.getUsableSpace() <= store.getTotalSpace())); + System.out.println("missing file " + new File(tmp.toFile(), "no-such-file").getTotalSpace()); + ByteBuffer buf = ByteBuffer.allocateDirect((int) block * 2).alignedSlice((int) block); + System.out.println("aligned capacity " + buf.capacity()); + } +} diff --git a/test/jtreg/passing.txt b/test/jtreg/passing.txt index d35c862..bf09c63 100644 --- a/test/jtreg/passing.txt +++ b/test/jtreg/passing.txt @@ -1979,6 +1979,10 @@ java/nio/channels/FileChannel/Truncate.java java/nio/channels/FileChannel/TruncateRAF.java java/nio/channels/FileChannel/TryLock.java java/nio/channels/FileChannel/Write.java +java/nio/channels/FileChannel/directio/PreadDirect.java +java/nio/channels/FileChannel/directio/PwriteDirect.java +java/nio/channels/FileChannel/directio/ReadDirect.java +java/nio/channels/FileChannel/directio/WriteDirect.java java/nio/channels/FileLock/FileLockConstructor.java java/nio/channels/FileLock/FileLockGC.java java/nio/channels/FileLock/Overlaps.java From 179548051c7a32a5fbb67405c527dc7ab34e4172 Mon Sep 17 00:00:00 2001 From: Project516 <138796702+Project516@users.noreply.github.com> Date: Wed, 4 Nov 2026 18:20:02 -0600 Subject: [PATCH 2/2] Take an unlinked file's block size from the path it had --- src/natives/nio.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/natives/nio.ts b/src/natives/nio.ts index 0f8c5bf..f359835 100644 --- a/src/natives/nio.ts +++ b/src/natives/nio.ts @@ -451,7 +451,11 @@ Object.assign(natives, { "java/nio/MappedMemoryUtils.unload0(JJ)V": () => undefined, // Bytes go through the same buffers either way; this only reports the block // size direct I/O aligns to, as fstatvfs gives it. - [`${UFD}setDirect0(Ljava/io/FileDescriptor;)I`]: (t, [fd]) => Number(t.vm.fs.space(chanFile(t, fd).path).bsize), + [`${UFD}setDirect0(Ljava/io/FileDescriptor;)I`]: (t, [fd]) => { + const f = chanFile(t, fd); + // an unlinked file has no path; the one it had names its file system + return Number(t.vm.fs.space(f.path || f.detached?.path || "/").bsize); + }, "sun/nio/ch/FileDispatcherImpl.init0()V": () => undefined, "sun/nio/ch/FileDispatcherImpl.transferTo0(Ljava/io/FileDescriptor;JJLjava/io/FileDescriptor;Z)J": () => BigInt(UNSUPPORTED_CASE), "sun/nio/ch/FileDispatcherImpl.transferFrom0(Ljava/io/FileDescriptor;Ljava/io/FileDescriptor;JJZ)J": () => BigInt(UNSUPPORTED_CASE),