Skip to content

Commit cd99812

Browse files
committed
feat: [US-019] - Fix bugs found in adversarial review of US-001 through US-009
1 parent 3716f9e commit cd99812

3 files changed

Lines changed: 62 additions & 13 deletions

File tree

packages/core/src/vfs/chunked-vfs.ts

Lines changed: 53 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -546,10 +546,12 @@ export function createChunkedVfs(options: ChunkedVfsOptions): VirtualFileSystem
546546
if (existingIno !== null) {
547547
const release = await mutex.acquire(existingIno);
548548
try {
549-
const meta = await requireInode(existingIno);
550-
writeBuffers.delete(existingIno);
551-
await writeInodeContent(existingIno, data, meta);
552-
await metadata.updateInode(existingIno, { nlink: Math.max(meta.nlink, 1) });
549+
await metadata.transaction(async () => {
550+
const meta = await requireInode(existingIno);
551+
writeBuffers.delete(existingIno);
552+
await writeInodeContent(existingIno, data, meta);
553+
await metadata.updateInode(existingIno, { nlink: Math.max(meta.nlink, 1) });
554+
});
553555
} finally {
554556
release();
555557
}
@@ -655,9 +657,8 @@ export function createChunkedVfs(options: ChunkedVfsOptions): VirtualFileSystem
655657
},
656658

657659
async pwrite(path: string, offset: number, data: Uint8Array): Promise<void> {
658-
if (data.length === 0) return;
659-
660660
const ino = await resolveIno(path);
661+
if (data.length === 0) return;
661662
const release = await mutex.acquire(ino);
662663
try {
663664
const meta = await requireInode(ino);
@@ -971,10 +972,45 @@ export function createChunkedVfs(options: ChunkedVfsOptions): VirtualFileSystem
971972
}
972973
},
973974

974-
async mkdir(path: string, _options?: { recursive?: boolean }): Promise<void> {
975+
async mkdir(path: string, options?: { recursive?: boolean }): Promise<void> {
976+
const recursive = options?.recursive ?? false;
975977
const parts = splitPath(normalizePath(path));
976-
let currentIno = 1; // root
977978

979+
if (!recursive) {
980+
// Non-recursive: parent must exist, target must not.
981+
if (parts.length === 0) {
982+
throw new KernelError("EEXIST", `directory already exists: '${path}'`);
983+
}
984+
let parentIno = 1; // root
985+
for (let i = 0; i < parts.length - 1; i++) {
986+
const childIno = await metadata.lookup(parentIno, parts[i]!);
987+
if (childIno === null) {
988+
throw new KernelError("ENOENT", `no such file or directory: '${path}'`);
989+
}
990+
parentIno = childIno;
991+
}
992+
const targetName = parts[parts.length - 1]!;
993+
const existingIno = await metadata.lookup(parentIno, targetName);
994+
if (existingIno !== null) {
995+
throw new KernelError("EEXIST", `directory already exists: '${path}'`);
996+
}
997+
const dirIno = await metadata.createInode({
998+
type: "directory",
999+
mode: 0o755,
1000+
uid: 0,
1001+
gid: 0,
1002+
});
1003+
await metadata.createDentry(parentIno, targetName, dirIno, "directory");
1004+
await metadata.updateInode(dirIno, { nlink: 2, size: 4096 });
1005+
const parentMeta = await metadata.getInode(parentIno);
1006+
if (parentMeta) {
1007+
await metadata.updateInode(parentIno, { nlink: parentMeta.nlink + 1 });
1008+
}
1009+
return;
1010+
}
1011+
1012+
// Recursive: create all intermediate directories.
1013+
let currentIno = 1; // root
9781014
for (const part of parts) {
9791015
const childIno = await metadata.lookup(currentIno, part);
9801016
if (childIno !== null) {
@@ -1433,5 +1469,14 @@ export function createChunkedVfs(options: ChunkedVfsOptions): VirtualFileSystem
14331469
Object.assign(vfs, { versioning: versioningApi });
14341470
}
14351471

1472+
// Expose dispose() to clear the auto-flush timer.
1473+
const dispose = (): void => {
1474+
if (autoFlushTimer !== undefined) {
1475+
clearInterval(autoFlushTimer);
1476+
autoFlushTimer = undefined;
1477+
}
1478+
};
1479+
Object.assign(vfs, { dispose });
1480+
14361481
return vfs;
14371482
}

packages/core/src/vfs/memory-block-store.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export class InMemoryBlockStore implements FsBlockStore {
3434
}
3535

3636
async write(key: string, data: Uint8Array): Promise<void> {
37-
this.blocks.set(key, data);
37+
this.blocks.set(key, new Uint8Array(data));
3838
}
3939

4040
async delete(key: string): Promise<void> {

packages/core/src/vfs/sqlite-metadata.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ export class SqliteMetadataStore implements FsMetadataStore, FsMetadataStoreVers
166166
uid INTEGER NOT NULL DEFAULT 0,
167167
gid INTEGER NOT NULL DEFAULT 0,
168168
size INTEGER NOT NULL DEFAULT 0,
169-
nlink INTEGER NOT NULL DEFAULT 1,
169+
nlink INTEGER NOT NULL DEFAULT 0,
170170
atime_ms INTEGER NOT NULL,
171171
mtime_ms INTEGER NOT NULL,
172172
ctime_ms INTEGER NOT NULL,
@@ -255,14 +255,18 @@ export class SqliteMetadataStore implements FsMetadataStore, FsMetadataStoreVers
255255

256256
// -- Transactions --
257257

258+
private savepointCounter = 0;
259+
258260
async transaction<T>(fn: () => Promise<T>): Promise<T> {
259-
this.db.exec("BEGIN");
261+
const name = `sp_${this.savepointCounter++}`;
262+
this.db.exec(`SAVEPOINT ${name}`);
260263
try {
261264
const result = await fn();
262-
this.db.exec("COMMIT");
265+
this.db.exec(`RELEASE ${name}`);
263266
return result;
264267
} catch (err) {
265-
this.db.exec("ROLLBACK");
268+
this.db.exec(`ROLLBACK TO ${name}`);
269+
this.db.exec(`RELEASE ${name}`);
266270
throw err;
267271
}
268272
}

0 commit comments

Comments
 (0)