diff --git a/src/fs.ts b/src/fs.ts index ba90da9..d534dd9 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; @@ -33,6 +40,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 @@ -202,6 +211,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 }; + } + // ns, when given, is the exact time asked for. Hosts keep times as floating // point seconds or milliseconds, which lose nanoseconds, so stat reports ns // for as long as the file keeps the time this set. diff --git a/src/natives/io.ts b/src/natives/io.ts index ce6571c..c94ed93 100644 --- a/src/natives/io.ts +++ b/src/natives/io.ts @@ -549,6 +549,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), BigInt(ms) * 1_000_000n), [`${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 6e52b3b..f359835 100644 --- a/src/natives/nio.ts +++ b/src/natives/nio.ts @@ -247,7 +247,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, @@ -441,7 +449,13 @@ 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]) => { + 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), diff --git a/src/node.ts b/src/node.ts index 3c5259e..595bffb 100644 --- a/src/node.ts +++ b/src/node.ts @@ -149,6 +149,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 3b6155c..4d4e403 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 @@ -2109,7 +2113,6 @@ java/nio/file/Files/CopyToNonDefaultFS.java java/nio/file/Files/CreateDirectories.java java/nio/file/Files/CustomOptions.java java/nio/file/Files/FileAttributes.java -java/nio/file/Files/InterruptCopy.java java/nio/file/Files/Links.java java/nio/file/Files/Misc.java java/nio/file/Files/Mismatch.java diff --git a/test/jtreg/times.tsv b/test/jtreg/times.tsv index 0d435c2..ca2a829 100644 --- a/test/jtreg/times.tsv +++ b/test/jtreg/times.tsv @@ -3393,7 +3393,6 @@ 1201 java/nio/channels/DatagramChannel/NoSender.java 1187 java/nio/channels/Pipe/NonBlocking.java 1178 java/nio/channels/DatagramChannel/IsConnected.java -1157 java/nio/file/Files/InterruptCopy.java 1150 java/net/ResponseCache/B6181108.java 1143 java/net/URL/TestRFC2732.java 1143 java/lang/annotation/typeAnnotations/TestObjectMethods.java