diff --git a/host/src/vfs/memory-fs.ts b/host/src/vfs/memory-fs.ts index 3bcef2610..c61ed0613 100644 --- a/host/src/vfs/memory-fs.ts +++ b/host/src/vfs/memory-fs.ts @@ -1531,12 +1531,7 @@ export class MemoryFileSystem implements FileSystemBackend { length: number, ): number { if (offset !== null) { - // pread semantics: read at offset without changing file position - const savedPos = this.fs.lseek(handle, 0, 1); // SEEK_CUR - this.fs.lseek(handle, offset, 0); // SEEK_SET - const n = this.fs.read(handle, buffer.subarray(0, length)); - this.fs.lseek(handle, savedPos, 0); // restore position - return n; + return this.fs.readAt(handle, buffer.subarray(0, length), offset); } return this.fs.read(handle, buffer.subarray(0, length)); } @@ -1548,11 +1543,7 @@ export class MemoryFileSystem implements FileSystemBackend { length: number, ): number { if (offset !== null) { - // pwrite semantics: write at offset without changing file position - const savedPos = this.fs.lseek(handle, 0, 1); // SEEK_CUR - this.fs.lseek(handle, offset, 0); // SEEK_SET - const n = this.fs.write(handle, buffer.subarray(0, length)); - this.fs.lseek(handle, savedPos, 0); // restore position + const n = this.fs.writeAt(handle, buffer.subarray(0, length), offset); if (n > 0) this.invalidateLazyData(this.fs.fstat(handle)); return n; } diff --git a/host/src/vfs/sharedfs-vendor.ts b/host/src/vfs/sharedfs-vendor.ts index 8ad1d9d66..97e3bbd37 100644 --- a/host/src/vfs/sharedfs-vendor.ts +++ b/host/src/vfs/sharedfs-vendor.ts @@ -2408,6 +2408,22 @@ export class SharedFS { } } + readAt(fd: number, buffer: Uint8Array, offset: number): number { + const entry = this.fdGet(fd); + if (!entry) throw new SFSError(EBADF); + const inoOff = this.inodeOffset(entry.ino); + const mode = this.r32(inoOff + INO_MODE); + if ((mode & S_IFMT) === S_IFDIR) throw new SFSError(EISDIR); + this.validateSeekPosition(offset); + + this.inodeReadLock(entry.ino); + try { + return this.inodeReadData(entry.ino, offset, buffer, buffer.length); + } finally { + this.inodeReadUnlock(entry.ino); + } + } + write(fd: number, data: Uint8Array): number { const entry = this.fdGet(fd); if (!entry) throw new SFSError(EBADF); @@ -2445,6 +2461,26 @@ export class SharedFS { } } + writeAt(fd: number, data: Uint8Array, offset: number): number { + const entry = this.fdGet(fd); + if (!entry) throw new SFSError(EBADF); + + const accMode = entry.flags & O_ACCMODE; + if (accMode === O_RDONLY) throw new SFSError(EBADF); + this.validateSeekPosition(offset); + + this.inodeWriteLock(entry.ino); + try { + // Positioned writes use their explicit offset even on an O_APPEND fd. + if (offset > MAX_FILE_SIZE || data.length > MAX_FILE_SIZE - offset) { + throw new SFSError(EFBIG); + } + return this.inodeWriteData(entry.ino, offset, data, data.length); + } finally { + this.inodeWriteUnlock(entry.ino); + } + } + lseek(fd: number, offset: number, whence: number): number { const entry = this.fdGet(fd); if (!entry) throw new SFSError(EBADF); diff --git a/host/test/vfs/sharedfs-positioned-io.test.ts b/host/test/vfs/sharedfs-positioned-io.test.ts new file mode 100644 index 000000000..6579fc96f --- /dev/null +++ b/host/test/vfs/sharedfs-positioned-io.test.ts @@ -0,0 +1,75 @@ +import { describe, expect, it } from "vitest"; +import { MemoryFileSystem } from "../../src/vfs/memory-fs"; +import { + O_APPEND, + O_CREAT, + O_RDWR, + O_TRUNC, + SEEK_SET, + SharedFS, +} from "../../src/vfs/sharedfs-vendor"; + +const encoder = new TextEncoder(); +const decoder = new TextDecoder(); + +function text(bytes: Uint8Array): string { + return decoder.decode(bytes); +} + +describe("SharedFS positioned I/O", () => { + it("readAt and writeAt do not mutate the shared fd offset", () => { + const sab = new SharedArrayBuffer(4 * 1024 * 1024); + const fs = SharedFS.mkfs(sab); + const fd = fs.open( + "/sorter.tmp", + O_RDWR | O_CREAT | O_TRUNC | O_APPEND, + 0o600, + ); + + expect(fs.write(fd, encoder.encode("0123456789abcdef"))).toBe(16); + expect(fs.lseek(fd, 10, SEEK_SET)).toBe(10); + + const positionedRead = new Uint8Array(4); + expect(fs.readAt(fd, positionedRead, 2)).toBe(4); + expect(text(positionedRead)).toBe("2345"); + + expect(fs.writeAt(fd, encoder.encode("XY"), 4)).toBe(2); + + const sequentialRead = new Uint8Array(3); + expect(fs.read(fd, sequentialRead)).toBe(3); + expect(text(sequentialRead)).toBe("abc"); + + expect(fs.lseek(fd, 0, SEEK_SET)).toBe(0); + const full = new Uint8Array(16); + expect(fs.read(fd, full)).toBe(16); + expect(text(full)).toBe("0123XY6789abcdef"); + }); + + it("MemoryFileSystem pread and pwrite keep the shared offset stable", () => { + const sab = new SharedArrayBuffer(4 * 1024 * 1024); + const fs = MemoryFileSystem.create(sab); + const fd = fs.open( + "/sorter.tmp", + O_RDWR | O_CREAT | O_TRUNC | O_APPEND, + 0o600, + ); + + expect(fs.write(fd, encoder.encode("0123456789abcdef"), null, 16)).toBe(16); + expect(fs.seek(fd, 10, SEEK_SET)).toBe(10); + + const positionedRead = new Uint8Array(4); + expect(fs.read(fd, positionedRead, 2, 4)).toBe(4); + expect(text(positionedRead)).toBe("2345"); + + expect(fs.write(fd, encoder.encode("XY"), 4, 2)).toBe(2); + + const sequentialRead = new Uint8Array(3); + expect(fs.read(fd, sequentialRead, null, 3)).toBe(3); + expect(text(sequentialRead)).toBe("abc"); + + expect(fs.seek(fd, 0, SEEK_SET)).toBe(0); + const full = new Uint8Array(16); + expect(fs.read(fd, full, null, 16)).toBe(16); + expect(text(full)).toBe("0123XY6789abcdef"); + }); +});