fix: harden native workspace root and docker gates
This commit is contained in:
+8
-2
@@ -3,6 +3,12 @@ declare const nativeWorkspaceFsAtComponentBrand: unique symbol;
|
||||
export interface NativeWorkspaceFsAtHandleV1 { readonly [nativeWorkspaceFsAtHandleBrand]: true; }
|
||||
export type NativeWorkspaceFsAtComponentV1 = string & { readonly [nativeWorkspaceFsAtComponentBrand]: true };
|
||||
export interface NativeWorkspaceFsAtStatV1 { readonly device: bigint; readonly inode: bigint; readonly mode: number; readonly uid: number; readonly gid: number; readonly nlink: bigint; }
|
||||
export interface NativeWorkspaceFsAtErrorV1 extends Error { readonly code:string; readonly errno:number; readonly syscall:"openat"|"mkdirat"|"fstat"|"fstatat"|"fsync"|"fcntl"|"close"; }
|
||||
export interface NativeWorkspaceFsAtErrorV1 extends Error { readonly code:string; readonly errno:number; readonly syscall:"openat"|"mkdirat"|"fstat"|"fstatat"|"fsync"|"borrow"|"dup2"|"close"; }
|
||||
export interface NativeWorkspaceFsAtOpenResultV1 { readonly handle: NativeWorkspaceFsAtHandleV1; readonly openedStat: NativeWorkspaceFsAtStatV1; }
|
||||
export interface WorkspaceFsAtBindingV1 { openat(input: { readonly parent: NativeWorkspaceFsAtHandleV1|null; readonly name: "/"|NativeWorkspaceFsAtComponentV1; readonly kind: "directory"|"regular_lock"; readonly createMode: 0|0o600 }): NativeWorkspaceFsAtOpenResultV1; mkdirat(parent: NativeWorkspaceFsAtHandleV1,name:NativeWorkspaceFsAtComponentV1,mode:0o700):void; fstatat(parent:NativeWorkspaceFsAtHandleV1,name:NativeWorkspaceFsAtComponentV1):NativeWorkspaceFsAtStatV1; fsyncDirectory(handle:NativeWorkspaceFsAtHandleV1):void; close(handle:NativeWorkspaceFsAtHandleV1):void; fdNumberForSynchronousBorrow(handle:NativeWorkspaceFsAtHandleV1):number; }
|
||||
export interface WorkspaceFsAtBindingV1 {
|
||||
openat(input: { readonly parent: NativeWorkspaceFsAtHandleV1|null; readonly name: "/"|NativeWorkspaceFsAtComponentV1; readonly kind: "directory"|"regular_lock"; readonly createMode: 0|0o600 }): NativeWorkspaceFsAtOpenResultV1;
|
||||
mkdirat(parent: NativeWorkspaceFsAtHandleV1,name:NativeWorkspaceFsAtComponentV1,mode:0o700):void;
|
||||
fstatat(parent:NativeWorkspaceFsAtHandleV1,name:NativeWorkspaceFsAtComponentV1):NativeWorkspaceFsAtStatV1;
|
||||
fsyncDirectory(handle:NativeWorkspaceFsAtHandleV1):void;
|
||||
close(handle:NativeWorkspaceFsAtHandleV1):void;
|
||||
}
|
||||
|
||||
@@ -108,18 +108,20 @@ export class BorrowedWorkspaceSessionReadersExclusiveLockLease {
|
||||
}
|
||||
export class WorkspaceWriterLockCapability {
|
||||
private live = true; private readerExclusive = false;
|
||||
private constructor(readonly workspaceId: CanonicalWorkspaceId, readonly rootIdentity: WorkspaceLockRootIdentityV1, private readonly root: VerifiedWorkspaceLockRootLease, private readonly fs: WorkspaceFsAtV1, private readonly writer: OwnedWorkspaceFsAtRegularFile) {}
|
||||
static make(id: CanonicalWorkspaceId, identity: WorkspaceLockRootIdentityV1, root: VerifiedWorkspaceLockRootLease, fs: WorkspaceFsAtV1, writer: OwnedWorkspaceFsAtRegularFile) { return new WorkspaceWriterLockCapability(id, identity, root, fs, writer); }
|
||||
private constructor(readonly workspaceId: CanonicalWorkspaceId, readonly rootIdentity: WorkspaceLockRootIdentityV1, private readonly root: VerifiedWorkspaceLockRootLease, private readonly writer: import("./workspace-lock-root-lease.js").WorkspaceRootLock) {}
|
||||
static create(id: CanonicalWorkspaceId, identity: WorkspaceLockRootIdentityV1, root: VerifiedWorkspaceLockRootLease, writer: import("./workspace-lock-root-lease.js").WorkspaceRootLock): WorkspaceWriterLockCapability { return new WorkspaceWriterLockCapability(id, identity, root, writer); }
|
||||
private check(): void { if (!this.live) throw fail(); }
|
||||
async runUnderSessionReadersExclusive<T>(action: (lease: BorrowedWorkspaceSessionReadersExclusiveLockLease) => Promise<T>): Promise<T> {
|
||||
this.check(); if (this.readerExclusive) throw fail(); this.readerExclusive = true;
|
||||
let lock: OwnedWorkspaceFsAtRegularFile | undefined; let borrowed: BorrowedWorkspaceSessionReadersExclusiveLockLease | undefined; let result!: T;
|
||||
try { result = await this.root._withRoot(async (dir, fs) => { lock = fs.openOrCreateLockAt(dir, "session-readers.lock", 0o600); fs.flockOwnedLock(lock, "exclusive", "nonblocking"); borrowed = BorrowedWorkspaceSessionReadersExclusiveLockLease.make(this.workspaceId, this.rootIdentity); return action(borrowed); }); return result; }
|
||||
finally { borrowed?.invalidate(); let failed = false; try { lock?.close(); } catch { failed = true; } if (failed) { this.live = false; } this.readerExclusive = false; if (failed) throw fail(); }
|
||||
let lock: import("./workspace-lock-root-lease.js").WorkspaceRootLock | undefined; let borrowed: BorrowedWorkspaceSessionReadersExclusiveLockLease | undefined;
|
||||
try { lock = await this.root.acquireSessionReadersExclusive(); borrowed = BorrowedWorkspaceSessionReadersExclusiveLockLease.make(this.workspaceId, this.rootIdentity); return await action(borrowed); }
|
||||
catch { throw fail(); }
|
||||
finally { borrowed?.invalidate(); try { lock?.close(); } catch { this.live = false; } this.readerExclusive = false; }
|
||||
}
|
||||
async spawnChild(_request: WorkspaceLockedChildRequest): Promise<WorkspaceLockedChildResult> { this.check(); throw fail("child runner is not configured"); }
|
||||
async close(): Promise<void> { if (!this.live) return; if (this.readerExclusive) throw fail(); this.live = false; let error: unknown; try { this.writer.close(); } catch (e) { error = e; } try { await this.root.close(); } catch (e) { error ??= e; } if (error) throw fail(); }
|
||||
}
|
||||
function makeWriterCapability(id: CanonicalWorkspaceId, identity: WorkspaceLockRootIdentityV1, root: VerifiedWorkspaceLockRootLease, writer: import("./workspace-lock-root-lease.js").WorkspaceRootLock): WorkspaceWriterLockCapability { return WorkspaceWriterLockCapability.create(id, identity, root, writer); }
|
||||
export interface OrderedWorkspaceCapability { readonly workspaceId: CanonicalWorkspaceId; readonly rootLease: BorrowedVerifiedWorkspaceLockRootLease; readonly writerCapability: WorkspaceWriterLockCapability; }
|
||||
export class OrderedWorkspaceWriterCapabilitySet {
|
||||
private live = true; private constructor(private readonly caps: Map<CanonicalWorkspaceId, WorkspaceWriterLockCapability>) {}
|
||||
@@ -132,9 +134,10 @@ export class OrderedWorkspaceWriterCapabilitySet {
|
||||
export async function runUnderOrderedWorkspaceWriterLocks<T>(rootLeases: readonly VerifiedWorkspaceLockRootLease[], action: (capabilities: OrderedWorkspaceWriterCapabilitySet) => Promise<T>): Promise<T> {
|
||||
const sorted = [...rootLeases].sort((a, b) => a.identity.workspaceId.localeCompare(b.identity.workspaceId)); if (new Set(sorted.map(x => x.identity.workspaceId)).size !== sorted.length) throw fail();
|
||||
const caps: WorkspaceWriterLockCapability[] = [];
|
||||
try { for (const source of sorted) { const root = source.transfer(); const fs = new WorkspaceFsAtV1(); let cap: WorkspaceWriterLockCapability | undefined; await root._withRoot(async (dir) => { const writer = fs.openOrCreateLockAt(dir, "writer.lock", 0o600); try { fs.flockOwnedLock(writer, "exclusive", "nonblocking"); cap = WorkspaceWriterLockCapability.make(root.identity.workspaceId, root.identity, root, fs, writer); } catch (e) { writer.close(); throw e; } }); if (!cap) throw fail(); caps.push(cap); }
|
||||
const transferred: VerifiedWorkspaceLockRootLease[] = [];
|
||||
try { for (const source of sorted) { const root = source.transfer(); transferred.push(root); let writer: import("./workspace-lock-root-lease.js").WorkspaceRootLock | undefined; try { writer = await root.acquireWriterLock(); caps.push(makeWriterCapability(root.identity.workspaceId, root.identity, root, writer)); } catch (error) { try { writer?.close(); } catch {} try { await root.close(); } catch {} throw error; } }
|
||||
const set = OrderedWorkspaceWriterCapabilitySet.make(new Map(caps.map(c => [c.workspaceId, c]))); try { return await action(set); } finally { set.invalidate(); for (const cap of [...caps].reverse()) await cap.close(); }
|
||||
} catch (error) { for (const cap of [...caps].reverse()) await cap.close().catch(() => undefined); throw error; }
|
||||
} catch (error) { for (const cap of [...caps].reverse()) await cap.close().catch(() => undefined); for (const root of transferred.slice(caps.length).reverse()) await root.close().catch(() => undefined); throw error; }
|
||||
}
|
||||
export function runUnderWorkspaceWriterLock<T>(rootLease: VerifiedWorkspaceLockRootLease, action: (capability: WorkspaceWriterLockCapability) => Promise<T>) { return runUnderOrderedWorkspaceWriterLocks([rootLease], set => set.forWorkspace(rootLease.identity.workspaceId, x => action(x.writerCapability))); }
|
||||
export async function probeWorkspaceWriterLock(rootLease: VerifiedWorkspaceLockRootLease): Promise<"available" | "held"> { try { await runUnderWorkspaceWriterLock(rootLease, async () => undefined); return "available"; } catch { return "held"; } }
|
||||
|
||||
@@ -1,30 +1,72 @@
|
||||
import { createRequire } from "node:module";
|
||||
import type { WorkspaceFsAtBindingV1, NativeWorkspaceFsAtHandleV1, NativeWorkspaceFsAtStatV1, NativeWorkspaceFsAtComponentV1 } from "../native/workspace-fs-at-binding.js";
|
||||
const require = createRequire(import.meta.url);
|
||||
const binding: WorkspaceFsAtBindingV1 = require("../../native/workspace-fs-at/build/Release/workspace_fs_at.node");
|
||||
const binding = require("../../native/workspace-fs-at/build/Release/workspace_fs_at.node") as WorkspaceFsAtBindingV1 & {
|
||||
withFd(handle: NativeWorkspaceFsAtHandleV1, action: (fd: number) => void): void;
|
||||
duplicateForChildStdio(writer: NativeWorkspaceFsAtHandleV1, root: NativeWorkspaceFsAtHandleV1, writerFd: number, rootFd: number): void;
|
||||
};
|
||||
export interface WorkspaceFsAtStatV1 extends NativeWorkspaceFsAtStatV1 {}
|
||||
export type LockFileName = "writer.lock" | "session-readers.lock";
|
||||
export type WorkspaceFlockKindV1 = "shared" | "exclusive";
|
||||
export type WorkspaceFlockWaitV1 = "blocking" | "nonblocking";
|
||||
function component(value:string): NativeWorkspaceFsAtComponentV1 { if (typeof value!=="string" || value.length===0 || value.length>255 || value!==value.trim() || value==='.' || value==='..' || value.includes('/') || value.includes('\0')) throw new Error("invalid path component"); if (!isComponent(value)) throw new Error("invalid path component"); return value; }
|
||||
function isComponent(value:string): value is NativeWorkspaceFsAtComponentV1 { return true; }
|
||||
function normalizeError(error: unknown): Error { if (error instanceof Error) return error; return new Error(String(error)); }
|
||||
class Owned {
|
||||
protected live=true;
|
||||
constructor(protected readonly raw: NativeWorkspaceFsAtHandleV1, readonly opened: WorkspaceFsAtStatV1) {}
|
||||
stat(): WorkspaceFsAtStatV1 { if(!this.live) throw new Error("workspace descriptor is closed"); return this.opened; }
|
||||
close(): void { if(!this.live)return; this.live=false; try { binding.close(this.raw); } catch(e){ throw normalizeError(e); } }
|
||||
_raw(): NativeWorkspaceFsAtHandleV1 { if(!this.live) throw new Error("workspace descriptor is closed"); return this.raw; }
|
||||
|
||||
function component(value: string): NativeWorkspaceFsAtComponentV1 {
|
||||
if (typeof value !== "string" || value.length === 0 || Buffer.byteLength(value, "utf8") > 255 || value !== value.trim() || value === "." || value === ".." || value.includes("/") || value.includes("\0")) throw new Error("invalid path component");
|
||||
return value as NativeWorkspaceFsAtComponentV1;
|
||||
}
|
||||
function normalizeError(error: unknown): Error {
|
||||
if (error instanceof Error) return error;
|
||||
return new Error(String(error));
|
||||
}
|
||||
const INTERNAL = Symbol("workspace-fs-at-owned");
|
||||
const rawHandles = new WeakMap<object, NativeWorkspaceFsAtHandleV1>();
|
||||
const borrowing = new WeakMap<object, number>();
|
||||
abstract class Owned {
|
||||
private live = true;
|
||||
private borrowing = 0;
|
||||
protected constructor(raw: NativeWorkspaceFsAtHandleV1, readonly opened: WorkspaceFsAtStatV1, token: symbol) { if (token !== INTERNAL) throw new TypeError("private workspace descriptor"); rawHandles.set(this, raw); borrowing.set(this, 0); }
|
||||
stat(): WorkspaceFsAtStatV1 { if (!this.live) throw new Error("workspace descriptor is closed"); return this.opened; }
|
||||
close(): void {
|
||||
if (!this.live) return;
|
||||
if ((borrowing.get(this) ?? 0) !== 0) throw Object.assign(new Error("workspace descriptor is borrowed"), { code: "ERR_WORKSPACE_FS_AT_BORROWED" });
|
||||
this.live = false;
|
||||
try { binding.close(rawHandles.get(this)!); } catch (error) { throw normalizeError(error); }
|
||||
}
|
||||
}
|
||||
export class OwnedWorkspaceFsAtDirectory extends Owned {
|
||||
constructor(raw: NativeWorkspaceFsAtHandleV1, stat: WorkspaceFsAtStatV1, token: symbol) { super(raw, stat, token); }
|
||||
}
|
||||
export class OwnedWorkspaceFsAtRegularFile extends Owned {
|
||||
constructor(raw: NativeWorkspaceFsAtHandleV1, stat: WorkspaceFsAtStatV1, token: symbol) { super(raw, stat, token); }
|
||||
|
||||
}
|
||||
function wrapDirectory(result: {handle: NativeWorkspaceFsAtHandleV1; openedStat: WorkspaceFsAtStatV1}): OwnedWorkspaceFsAtDirectory {
|
||||
try { if ((result.openedStat.mode & 0o170000) !== 0o040000) throw new Error("not a directory"); return new OwnedWorkspaceFsAtDirectory(result.handle, result.openedStat, INTERNAL); }
|
||||
catch (error) { try { binding.close(result.handle); } catch { /* preserve conversion error */ } throw error; }
|
||||
}
|
||||
function wrapLock(result: {handle: NativeWorkspaceFsAtHandleV1; openedStat: WorkspaceFsAtStatV1}): OwnedWorkspaceFsAtRegularFile {
|
||||
try {
|
||||
const st = result.openedStat;
|
||||
if ((st.mode & 0o170000) !== 0o100000 || (st.mode & 0o777) !== 0o600 || st.uid !== (process.getuid?.() ?? st.uid) || st.nlink !== 1n) throw new Error("invalid lock identity");
|
||||
return new OwnedWorkspaceFsAtRegularFile(result.handle, st, INTERNAL);
|
||||
} catch (error) { try { binding.close(result.handle); } catch { /* preserve conversion error */ } throw error; }
|
||||
}
|
||||
function rawDirectory(value: OwnedWorkspaceFsAtDirectory): NativeWorkspaceFsAtHandleV1 { if (!rawHandles.has(value)) throw new Error("workspace descriptor is closed"); return rawHandles.get(value)!; }
|
||||
function withLockFd<T>(value: OwnedWorkspaceFsAtRegularFile, action: (fd: number) => T): T {
|
||||
if (!rawHandles.has(value)) throw new Error("workspace descriptor is closed");
|
||||
const count = borrowing.get(value) ?? 0; borrowing.set(value, count + 1);
|
||||
try { return binding.withFd(rawHandles.get(value)!, action as (fd: number) => void) as T; } finally { borrowing.set(value, count); }
|
||||
}
|
||||
export class OwnedWorkspaceFsAtDirectory extends Owned { private constructor(raw:NativeWorkspaceFsAtHandleV1, stat:WorkspaceFsAtStatV1){super(raw,stat);} static from(raw:NativeWorkspaceFsAtHandleV1,stat:WorkspaceFsAtStatV1){return new OwnedWorkspaceFsAtDirectory(raw,stat);} }
|
||||
export class OwnedWorkspaceFsAtRegularFile extends Owned { private constructor(raw:NativeWorkspaceFsAtHandleV1, stat:WorkspaceFsAtStatV1){super(raw,stat);} static from(raw:NativeWorkspaceFsAtHandleV1,stat:WorkspaceFsAtStatV1){return new OwnedWorkspaceFsAtRegularFile(raw,stat);} }
|
||||
function rawDirectory(value:OwnedWorkspaceFsAtDirectory){ return value._raw(); }
|
||||
export class WorkspaceFsAtV1 {
|
||||
openRoot(): OwnedWorkspaceFsAtDirectory { const r=binding.openat({parent:null,name:"/",kind:"directory",createMode:0}); return OwnedWorkspaceFsAtDirectory.from(r.handle,r.openedStat); }
|
||||
openDirectoryAt(parent:OwnedWorkspaceFsAtDirectory, name:string):OwnedWorkspaceFsAtDirectory { const r=binding.openat({parent:rawDirectory(parent),name:component(name),kind:"directory",createMode:0}); return OwnedWorkspaceFsAtDirectory.from(r.handle,r.openedStat); }
|
||||
openOrCreateLockAt(parent:OwnedWorkspaceFsAtDirectory,name:LockFileName,mode:0o600):OwnedWorkspaceFsAtRegularFile { if(name!=="writer.lock"&&name!=="session-readers.lock"||mode!==0o600)throw new Error("invalid lock"); const r=binding.openat({parent:rawDirectory(parent),name:component(name),kind:"regular_lock",createMode:0o600}); return OwnedWorkspaceFsAtRegularFile.from(r.handle,r.openedStat); }
|
||||
mkdirAt(parent:OwnedWorkspaceFsAtDirectory,name:string,mode:0o700):void { binding.mkdirat(rawDirectory(parent),component(name),mode); }
|
||||
statAtNoFollow(parent:OwnedWorkspaceFsAtDirectory,name:string):WorkspaceFsAtStatV1 { return binding.fstatat(rawDirectory(parent),component(name)); }
|
||||
fsyncDirectory(directory:OwnedWorkspaceFsAtDirectory):void { binding.fsyncDirectory(rawDirectory(directory)); }
|
||||
flockOwnedLock(owned:OwnedWorkspaceFsAtRegularFile,kind:WorkspaceFlockKindV1,wait:WorkspaceFlockWaitV1):void { const fd=binding.fdNumberForSynchronousBorrow(owned._raw()); const fsExt: {flockSync(fd:number,operation:string):void}=require("fs-ext"); const operation=kind==="shared"?(wait==="blocking"?"sh":"shnb"):(wait==="blocking"?"ex":"exnb"); fsExt.flockSync(fd,operation); }
|
||||
openRoot(): OwnedWorkspaceFsAtDirectory { return wrapDirectory(binding.openat({ parent: null, name: "/", kind: "directory", createMode: 0 })); }
|
||||
openDirectoryAt(parent: OwnedWorkspaceFsAtDirectory, name: string): OwnedWorkspaceFsAtDirectory { return wrapDirectory(binding.openat({ parent: rawDirectory(parent), name: component(name), kind: "directory", createMode: 0 })); }
|
||||
openOrCreateLockAt(parent: OwnedWorkspaceFsAtDirectory, name: LockFileName, mode: 0o600): OwnedWorkspaceFsAtRegularFile { if ((name !== "writer.lock" && name !== "session-readers.lock") || mode !== 0o600) throw new Error("invalid lock"); return wrapLock(binding.openat({ parent: rawDirectory(parent), name: component(name), kind: "regular_lock", createMode: 0o600 })); }
|
||||
mkdirAt(parent: OwnedWorkspaceFsAtDirectory, name: string, mode: 0o700): void { binding.mkdirat(rawDirectory(parent), component(name), mode); }
|
||||
statAtNoFollow(parent: OwnedWorkspaceFsAtDirectory, name: string): WorkspaceFsAtStatV1 { return binding.fstatat(rawDirectory(parent), component(name)); }
|
||||
fsyncDirectory(directory: OwnedWorkspaceFsAtDirectory): void { binding.fsyncDirectory(rawDirectory(directory)); }
|
||||
flockOwnedLock(owned: OwnedWorkspaceFsAtRegularFile, kind: WorkspaceFlockKindV1, wait: WorkspaceFlockWaitV1): void {
|
||||
const fsExt: { flockSync(fd: number, operation: string): void } = require("fs-ext");
|
||||
const operation = kind === "shared" ? (wait === "blocking" ? "sh" : "shnb") : (wait === "blocking" ? "ex" : "exnb");
|
||||
withLockFd(owned, fd => fsExt.flockSync(fd, operation));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,41 +1,79 @@
|
||||
import { WorkspaceFsAtV1, OwnedWorkspaceFsAtDirectory, type OwnedWorkspaceFsAtRegularFile, type WorkspaceFsAtStatV1 } from "./workspace-fs-at.js";
|
||||
import { lstatSync, realpathSync } from "node:fs";
|
||||
import { resolve } from "node:path";
|
||||
import { WorkspaceFsAtV1, OwnedWorkspaceFsAtDirectory, type OwnedWorkspaceFsAtRegularFile, type WorkspaceFsAtStatV1, type WorkspaceFlockKindV1, type WorkspaceFlockWaitV1 } from "./workspace-fs-at.js";
|
||||
export type CanonicalWorkspaceId = string & { readonly __workspaceId: unique symbol };
|
||||
export interface WorkspaceLockRootIdentityV1 { readonly schemaVersion:1; readonly workspaceId:CanonicalWorkspaceId; readonly device:bigint; readonly inode:bigint; }
|
||||
export class CanonicalWorkspaceLockRootInput { private constructor(readonly workspaceId:CanonicalWorkspaceId, readonly owner:symbol){} static make(id:CanonicalWorkspaceId,owner:symbol){return new CanonicalWorkspaceLockRootInput(id,owner);} }
|
||||
export class BorrowedVerifiedWorkspaceLockRootLease { private constructor(readonly identity:WorkspaceLockRootIdentityV1, private readonly check:()=>void){} static make(i:WorkspaceLockRootIdentityV1,c:()=>void){return new BorrowedVerifiedWorkspaceLockRootLease(i,c);} assertLive(){this.check();} }
|
||||
function conflict(message="preprocessing conflict"):Error { const e=new Error(message); e.name="PreprocessingConflictError"; return e; }
|
||||
function exactStat(stat:WorkspaceFsAtStatV1,uid:number):boolean { return (stat.mode&0o170000)===0o040000 && (stat.mode&0o777)===0o700 && stat.uid===uid && stat.nlink>=2n; }
|
||||
export interface WorkspaceLockRootIdentityV1 { readonly schemaVersion: 1; readonly workspaceId: CanonicalWorkspaceId; readonly device: bigint; readonly inode: bigint; }
|
||||
export class CanonicalWorkspaceLockRootInput { private constructor(readonly workspaceId: CanonicalWorkspaceId, readonly owner: symbol) {} }
|
||||
function conflict(message = "preprocessing_conflict"): Error { const e = new Error(message); e.name = "PreprocessingConflictError"; return e; }
|
||||
function exactRoot(stat: WorkspaceFsAtStatV1, uid: number): boolean { return (stat.mode & 0o170000) === 0o040000 && (stat.mode & 0o777) === 0o700 && stat.uid === uid && stat.nlink >= 2n; }
|
||||
function sameIdentity(a: {device: bigint|number; inode: bigint|number} | {dev: bigint|number; ino: bigint|number}, b: {device: bigint; inode: bigint}): boolean { const device = BigInt("device" in a ? a.device : a.dev); const inode = BigInt("inode" in a ? a.inode : a.ino); return device === b.device && inode === b.inode; }
|
||||
|
||||
/** Internal opaque lock returned only after the root has been checked. */
|
||||
export class WorkspaceRootLock {
|
||||
private live = true;
|
||||
constructor(private readonly fs: WorkspaceFsAtV1, private readonly lock: OwnedWorkspaceFsAtRegularFile) {}
|
||||
flock(kind: WorkspaceFlockKindV1, wait: WorkspaceFlockWaitV1): void { if (!this.live) throw conflict(); this.fs.flockOwnedLock(this.lock, kind, wait); }
|
||||
close(): void { if (!this.live) return; this.live = false; this.lock.close(); }
|
||||
}
|
||||
export class BorrowedVerifiedWorkspaceLockRootLease {
|
||||
private constructor(readonly identity: WorkspaceLockRootIdentityV1, private readonly check: () => void) {}
|
||||
static make(identity: WorkspaceLockRootIdentityV1, check: () => void): BorrowedVerifiedWorkspaceLockRootLease { return new BorrowedVerifiedWorkspaceLockRootLease(identity, check); }
|
||||
assertLive(): void { this.check(); }
|
||||
}
|
||||
export class WorkspaceSessionReadersLockLease {
|
||||
private live=true; private constructor(private readonly lock:OwnedWorkspaceFsAtRegularFile, private readonly root:OwnedWorkspaceFsAtDirectory, readonly rootIdentity:WorkspaceLockRootIdentityV1){}
|
||||
static make(lock:OwnedWorkspaceFsAtRegularFile,root:OwnedWorkspaceFsAtDirectory,id:WorkspaceLockRootIdentityV1){return new WorkspaceSessionReadersLockLease(lock,root,id);}
|
||||
transfer():WorkspaceSessionReadersLockLease { if(!this.live)throw conflict(); this.live=false; return WorkspaceSessionReadersLockLease.make(this.lock,this.root,this.rootIdentity); }
|
||||
async close():Promise<void>{if(!this.live)return;this.live=false;let failure:unknown;try{this.lock.close();}catch(e){failure=e;}try{this.root.close();}catch(e){failure??=e;}if(failure)throw conflict();}
|
||||
private live = true;
|
||||
private constructor(private readonly lock: WorkspaceRootLock, private readonly root: OwnedWorkspaceFsAtDirectory, readonly rootIdentity: WorkspaceLockRootIdentityV1) {}
|
||||
transfer(): WorkspaceSessionReadersLockLease { if (!this.live) throw conflict(); this.live = false; return new WorkspaceSessionReadersLockLease(this.lock, this.root, this.rootIdentity); }
|
||||
async close(): Promise<void> { if (!this.live) return; this.live = false; let failure: unknown; try { this.lock.close(); } catch (e) { failure = e; } try { this.root.close(); } catch (e) { failure ??= e; } if (failure) throw conflict(); }
|
||||
static from(lock: WorkspaceRootLock, root: OwnedWorkspaceFsAtDirectory, identity: WorkspaceLockRootIdentityV1): WorkspaceSessionReadersLockLease { return new WorkspaceSessionReadersLockLease(lock, root, identity); }
|
||||
}
|
||||
export class VerifiedWorkspaceLockRootLease {
|
||||
private live=true; private borrowed=0; private constructor(private readonly owner:symbol,private readonly fs:WorkspaceFsAtV1,private readonly root:OwnedWorkspaceFsAtDirectory,readonly identity:WorkspaceLockRootIdentityV1){}
|
||||
static make(owner:symbol,fs:WorkspaceFsAtV1,root:OwnedWorkspaceFsAtDirectory,id:WorkspaceLockRootIdentityV1){return new VerifiedWorkspaceLockRootLease(owner,fs,root,id);}
|
||||
private check(){if(!this.live)throw conflict(); const s=this.root.stat();if(s.device!==this.identity.device||s.inode!==this.identity.inode)throw conflict("workspace root identity changed");}
|
||||
async borrow<T>(action:(b:BorrowedVerifiedWorkspaceLockRootLease)=>Promise<T>):Promise<T>{this.check();this.borrowed++;try{return await action(BorrowedVerifiedWorkspaceLockRootLease.make(this.identity,()=>this.check()));}finally{this.borrowed--;}}
|
||||
transfer():VerifiedWorkspaceLockRootLease{this.check();if(this.borrowed)throw conflict("workspace root is borrowed");this.live=false;return VerifiedWorkspaceLockRootLease.make(this.owner,this.fs,this.root,this.identity);}
|
||||
async acquireSessionReadersShared():Promise<WorkspaceSessionReadersLockLease>{this.check();let lock:OwnedWorkspaceFsAtRegularFile|undefined;try{lock=this.fs.openOrCreateLockAt(this.root,"session-readers.lock",0o600);this.fs.flockOwnedLock(lock,"shared","nonblocking");this.check();this.live=false;return WorkspaceSessionReadersLockLease.make(lock,this.root,this.identity);}catch(e){try{lock?.close();}catch{this.live=false;try{this.root.close();}catch{}}throw conflict();}}
|
||||
async close():Promise<void>{if(!this.live)return;if(this.borrowed)await new Promise<void>(resolve=>{const tick=()=>this.borrowed?setTimeout(tick,1):resolve();tick();});this.live=false;try{this.root.close();}catch{throw conflict();}}
|
||||
// package-private operation used by the writer capability; never returns the underlying handle.
|
||||
async _withRoot<T>(action:(root:OwnedWorkspaceFsAtDirectory,fs:WorkspaceFsAtV1)=>Promise<T>):Promise<T>{this.check();return action(this.root,this.fs);}
|
||||
private live = true;
|
||||
private borrowed = 0;
|
||||
private constructor(private readonly owner: symbol, private readonly fs: WorkspaceFsAtV1, private readonly root: OwnedWorkspaceFsAtDirectory, readonly identity: WorkspaceLockRootIdentityV1, private readonly rootPath: string, private readonly serviceUid: number) {}
|
||||
private assertPath(): void {
|
||||
if (!this.live) throw conflict();
|
||||
try { const st = lstatSync(this.rootPath); if (!st.isDirectory() || st.uid !== this.serviceUid || (st.mode & 0o777) !== 0o700 || !sameIdentity(st, this.identity)) throw conflict("preprocessing_conflict: workspace root identity changed"); }
|
||||
catch (error) { if ((error as Error).name === "PreprocessingConflictError") throw error; throw conflict("preprocessing_conflict: workspace root identity changed"); }
|
||||
const retained = this.root.stat(); if (!sameIdentity(retained, this.identity) || !exactRoot(retained, this.serviceUid)) throw conflict("preprocessing_conflict: workspace root identity changed");
|
||||
}
|
||||
assertLive(): void { this.assertPath(); }
|
||||
async borrow<T>(action: (lease: { readonly identity: WorkspaceLockRootIdentityV1; assertLive(): void }) => Promise<T>): Promise<T> { this.assertPath(); this.borrowed++; try { return await action({ identity: this.identity, assertLive: () => this.assertPath() }); } finally { this.borrowed--; } }
|
||||
transfer(): VerifiedWorkspaceLockRootLease { this.assertPath(); if (this.borrowed) throw conflict("workspace root is borrowed"); this.live = false; return new VerifiedWorkspaceLockRootLease(this.owner, this.fs, this.root, this.identity, this.rootPath, this.serviceUid); }
|
||||
async acquireSessionReadersShared(): Promise<WorkspaceSessionReadersLockLease> {
|
||||
this.assertPath(); let lock: WorkspaceRootLock | undefined;
|
||||
try { const owned = this.fs.openOrCreateLockAt(this.root, "session-readers.lock", 0o600); lock = new WorkspaceRootLock(this.fs, owned); lock.flock("shared", "nonblocking"); this.assertPath(); this.live = false; return WorkspaceSessionReadersLockLease.from(lock, this.root, this.identity); }
|
||||
catch (error) { try { lock?.close(); } catch { this.live = false; try { this.root.close(); } catch {} } throw conflict(); }
|
||||
}
|
||||
async acquireWriterLock(): Promise<WorkspaceRootLock> { this.assertPath(); const owned = this.fs.openOrCreateLockAt(this.root, "writer.lock", 0o600); const lock = new WorkspaceRootLock(this.fs, owned); try { lock.flock("exclusive", "nonblocking"); this.assertPath(); return lock; } catch (error) { try { lock.close(); } catch {} throw conflict(); } }
|
||||
async acquireSessionReadersExclusive(): Promise<WorkspaceRootLock> { this.assertPath(); const owned = this.fs.openOrCreateLockAt(this.root, "session-readers.lock", 0o600); const lock = new WorkspaceRootLock(this.fs, owned); try { lock.flock("exclusive", "nonblocking"); this.assertPath(); return lock; } catch { try { lock.close(); } catch {} throw conflict(); } }
|
||||
fsync(): void { this.assertPath(); this.fs.fsyncDirectory(this.root); this.assertPath(); }
|
||||
async close(): Promise<void> { if (!this.live) return; while (this.borrowed) await new Promise<void>(resolve => setTimeout(resolve, 1)); this.live = false; try { this.root.close(); } catch { throw conflict(); } }
|
||||
static from(owner: symbol, fs: WorkspaceFsAtV1, root: OwnedWorkspaceFsAtDirectory, identity: WorkspaceLockRootIdentityV1, rootPath: string, uid: number): VerifiedWorkspaceLockRootLease { return new VerifiedWorkspaceLockRootLease(owner, fs, root, identity, rootPath, uid); }
|
||||
}
|
||||
export class VerifiedWorkspaceLockRootLeaseFactory {
|
||||
private readonly owner=Symbol(); private readonly parent:OwnedWorkspaceFsAtDirectory;
|
||||
constructor(private readonly input:{readonly workspaceFsAt:WorkspaceFsAtV1;readonly installationId:string;readonly sessionsRootFromValidatedInstallationConfig:string;readonly serviceUid:number;readonly provisionedWorkspaceMode:0o700}) { if(!Number.isInteger(input.serviceUid)||input.serviceUid<0)throw new Error("invalid service uid");if(input.provisionedWorkspaceMode!==0o700)throw new Error("invalid workspace mode");if(!input.sessionsRootFromValidatedInstallationConfig.startsWith("/"))throw new Error("sessions root must be absolute");let d=input.workspaceFsAt.openRoot();try{for(const c of input.sessionsRootFromValidatedInstallationConfig.split("/").filter(Boolean)) {const n=input.workspaceFsAt.openDirectoryAt(d,c);d.close();d=n;}this.parent=d;}catch(e){try{d.close();}catch{}throw e;}}
|
||||
canonicalInput(workspaceId:string):CanonicalWorkspaceLockRootInput{if(!/^[a-z][a-z0-9-]{2,62}$/.test(workspaceId))throw conflict();return CanonicalWorkspaceLockRootInput.make(workspaceId as CanonicalWorkspaceId,this.owner);}
|
||||
private async open(input:CanonicalWorkspaceLockRootInput):Promise<VerifiedWorkspaceLockRootLease>{if(input.owner!==this.owner)throw conflict();const root=this.input.workspaceFsAt.openDirectoryAt(this.parent,input.workspaceId);if(!exactStat(root.stat(),this.input.serviceUid)){root.close();throw conflict();}return VerifiedWorkspaceLockRootLease.make(this.owner,this.input.workspaceFsAt,root,{schemaVersion:1,workspaceId:input.workspaceId,device:root.stat().device,inode:root.stat().inode});}
|
||||
acquire(input:CanonicalWorkspaceLockRootInput){return this.open(input);}
|
||||
async acquireOrProvision(input:CanonicalWorkspaceLockRootInput){
|
||||
if(input.owner!==this.owner)throw conflict();
|
||||
try{return await this.open(input);}catch(error){if((error as {code?:unknown})?.code!=="ENOENT")throw conflict();}
|
||||
try{this.input.workspaceFsAt.mkdirAt(this.parent,input.workspaceId,0o700);}catch(error){if((error as {code?:unknown})?.code!=="EEXIST")throw conflict();}
|
||||
try{this.input.workspaceFsAt.fsyncDirectory(this.parent);}catch{throw conflict();}
|
||||
const lease=await this.open(input);
|
||||
try{await lease._withRoot(async(dir,fs)=>fs.fsyncDirectory(dir));}
|
||||
catch{await lease.close().catch(()=>undefined);throw conflict();}
|
||||
return lease;
|
||||
private readonly owner = Symbol("workspace-root-factory");
|
||||
private readonly parent: OwnedWorkspaceFsAtDirectory;
|
||||
private readonly parentPath: string;
|
||||
constructor(private readonly input: { readonly workspaceFsAt: WorkspaceFsAtV1; readonly installationId: string; readonly sessionsRootFromValidatedInstallationConfig: string; readonly serviceUid: number; readonly provisionedWorkspaceMode: 0o700 }) {
|
||||
if (!Number.isInteger(input.serviceUid) || input.serviceUid < 0 || input.provisionedWorkspaceMode !== 0o700) throw new Error("invalid workspace root policy");
|
||||
try { const configured = resolve(input.sessionsRootFromValidatedInstallationConfig); this.parentPath = realpathSync(configured); if (this.parentPath !== configured) throw conflict("sessions root must be canonical"); } catch (error) { if ((error as Error).name === "PreprocessingConflictError") throw error; throw conflict("invalid sessions root"); }
|
||||
if (!this.parentPath.startsWith("/") || this.parentPath.split("/").includes("..")) throw conflict("invalid sessions root");
|
||||
let d = input.workspaceFsAt.openRoot();
|
||||
try { for (const c of this.parentPath.split("/").filter(Boolean)) { const n = input.workspaceFsAt.openDirectoryAt(d, c); d.close(); d = n; } this.parent = d; this.checkParent(); }
|
||||
catch (error) { try { d.close(); } catch {} throw error; }
|
||||
}
|
||||
private checkParent(): void { try { const s = lstatSync(this.parentPath); const p = this.parent.stat(); if (!s.isDirectory() || !sameIdentity(s, p) || s.uid !== this.input.serviceUid || (s.mode & 0o777) !== 0o700) throw conflict("installation root identity changed"); } catch (error) { if ((error as Error).name === "PreprocessingConflictError") throw error; throw conflict("installation root identity changed"); } }
|
||||
canonicalInput(workspaceId: string): CanonicalWorkspaceLockRootInput { if (!/^[a-z][a-z0-9-]{2,62}$/.test(workspaceId)) throw conflict(); return Object.assign(Object.create(CanonicalWorkspaceLockRootInput.prototype), { workspaceId: workspaceId as CanonicalWorkspaceId, owner: this.owner }) as CanonicalWorkspaceLockRootInput; }
|
||||
private async open(input: CanonicalWorkspaceLockRootInput): Promise<VerifiedWorkspaceLockRootLease> { if (input.owner !== this.owner) throw conflict(); this.checkParent(); let root: OwnedWorkspaceFsAtDirectory; try { root = this.input.workspaceFsAt.openDirectoryAt(this.parent, input.workspaceId); this.checkParent(); } catch { throw conflict(); } if (!exactRoot(root.stat(), this.input.serviceUid)) { root.close(); throw conflict(); } const identity = { schemaVersion: 1 as const, workspaceId: input.workspaceId, device: root.stat().device, inode: root.stat().inode }; const lease = VerifiedWorkspaceLockRootLease.from(this.owner, this.input.workspaceFsAt, root, identity, `${this.parentPath}/${input.workspaceId}`, this.input.serviceUid); try { lease.assertLive(); } catch { await lease.close().catch(() => undefined); throw conflict(); } return lease; }
|
||||
acquire(input: CanonicalWorkspaceLockRootInput): Promise<VerifiedWorkspaceLockRootLease> { return this.open(input); }
|
||||
async acquireOrProvision(input: CanonicalWorkspaceLockRootInput): Promise<VerifiedWorkspaceLockRootLease> {
|
||||
if (input.owner !== this.owner) throw conflict();
|
||||
try { return await this.open(input); } catch (error) { if (!String((error as Error).message).includes("identity") && (error as {code?: string}).code !== "ENOENT") { /* open errors are normalized; probe the anchored parent below */ } }
|
||||
this.checkParent();
|
||||
try { this.input.workspaceFsAt.mkdirAt(this.parent, input.workspaceId, 0o700); } catch (error) { if ((error as {code?: string}).code !== "EEXIST") throw conflict(); }
|
||||
this.checkParent();
|
||||
try { this.input.workspaceFsAt.fsyncDirectory(this.parent); } catch { throw conflict(); }
|
||||
return this.open(input);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user