@secure-exec/core 0.1.1-rc.2 → 0.2.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/dist/esm-compiler.d.ts +5 -1
  2. package/dist/esm-compiler.js +5 -1
  3. package/dist/fs-helpers.d.ts +1 -1
  4. package/dist/generated/isolate-runtime.d.ts +15 -15
  5. package/dist/generated/isolate-runtime.js +15 -15
  6. package/dist/index.d.ts +25 -6
  7. package/dist/index.js +23 -3
  8. package/dist/isolate-runtime/apply-custom-global-policy.js +3 -3
  9. package/dist/isolate-runtime/apply-timing-mitigation-freeze.js +10 -8
  10. package/dist/isolate-runtime/apply-timing-mitigation-off.js +2 -2
  11. package/dist/isolate-runtime/bridge-attach.js +2 -2
  12. package/dist/isolate-runtime/bridge-initial-globals.js +3 -3
  13. package/dist/isolate-runtime/eval-script-result.js +1 -1
  14. package/dist/isolate-runtime/global-exposure-helpers.js +2 -2
  15. package/dist/isolate-runtime/init-commonjs-module-globals.js +2 -2
  16. package/dist/isolate-runtime/override-process-cwd.js +1 -1
  17. package/dist/isolate-runtime/override-process-env.js +1 -1
  18. package/dist/isolate-runtime/require-setup.js +2236 -19
  19. package/dist/isolate-runtime/set-commonjs-file-globals.js +2 -2
  20. package/dist/isolate-runtime/set-stdin-data.js +1 -1
  21. package/dist/isolate-runtime/setup-dynamic-import.js +47 -15
  22. package/dist/isolate-runtime/setup-fs-facade.js +2 -2
  23. package/dist/kernel/command-registry.d.ts +44 -0
  24. package/dist/kernel/command-registry.js +114 -0
  25. package/dist/kernel/device-layer.d.ts +12 -0
  26. package/dist/kernel/device-layer.js +262 -0
  27. package/dist/kernel/dns-cache.d.ts +29 -0
  28. package/dist/kernel/dns-cache.js +52 -0
  29. package/dist/kernel/fd-table.d.ts +84 -0
  30. package/dist/kernel/fd-table.js +278 -0
  31. package/dist/kernel/file-lock.d.ts +34 -0
  32. package/dist/kernel/file-lock.js +123 -0
  33. package/dist/kernel/host-adapter.d.ts +50 -0
  34. package/dist/kernel/host-adapter.js +8 -0
  35. package/dist/kernel/index.d.ts +36 -0
  36. package/dist/kernel/index.js +34 -0
  37. package/dist/kernel/inode-table.d.ts +43 -0
  38. package/dist/kernel/inode-table.js +85 -0
  39. package/dist/kernel/kernel.d.ts +9 -0
  40. package/dist/kernel/kernel.js +1396 -0
  41. package/dist/kernel/permissions.d.ts +27 -0
  42. package/dist/kernel/permissions.js +118 -0
  43. package/dist/kernel/pipe-manager.d.ts +64 -0
  44. package/dist/kernel/pipe-manager.js +267 -0
  45. package/dist/kernel/proc-layer.d.ts +11 -0
  46. package/dist/kernel/proc-layer.js +501 -0
  47. package/dist/kernel/process-table.d.ts +124 -0
  48. package/dist/kernel/process-table.js +631 -0
  49. package/dist/kernel/pty.d.ts +108 -0
  50. package/dist/kernel/pty.js +541 -0
  51. package/dist/kernel/socket-table.d.ts +305 -0
  52. package/dist/kernel/socket-table.js +1124 -0
  53. package/dist/kernel/timer-table.d.ts +54 -0
  54. package/dist/kernel/timer-table.js +108 -0
  55. package/dist/kernel/types.d.ts +500 -0
  56. package/dist/kernel/types.js +89 -0
  57. package/dist/kernel/user.d.ts +29 -0
  58. package/dist/kernel/user.js +35 -0
  59. package/dist/kernel/vfs.d.ts +54 -0
  60. package/dist/kernel/vfs.js +8 -0
  61. package/dist/kernel/wait.d.ts +45 -0
  62. package/dist/kernel/wait.js +112 -0
  63. package/dist/kernel/wstatus.d.ts +21 -0
  64. package/dist/kernel/wstatus.js +33 -0
  65. package/dist/module-resolver.d.ts +4 -0
  66. package/dist/module-resolver.js +4 -0
  67. package/dist/package-bundler.d.ts +6 -1
  68. package/dist/runtime-driver.d.ts +3 -1
  69. package/dist/shared/bridge-contract.d.ts +529 -94
  70. package/dist/shared/bridge-contract.js +86 -3
  71. package/dist/shared/console-formatter.js +4 -0
  72. package/dist/shared/global-exposure.js +345 -0
  73. package/dist/shared/in-memory-fs.d.ts +30 -11
  74. package/dist/shared/in-memory-fs.js +383 -109
  75. package/dist/shared/permissions.d.ts +4 -6
  76. package/dist/shared/permissions.js +24 -28
  77. package/dist/types.d.ts +20 -130
  78. package/dist/types.js +5 -0
  79. package/package.json +12 -22
  80. package/dist/bridge/active-handles.d.ts +0 -22
  81. package/dist/bridge/active-handles.js +0 -55
  82. package/dist/bridge/child-process.d.ts +0 -99
  83. package/dist/bridge/child-process.js +0 -656
  84. package/dist/bridge/fs.d.ts +0 -281
  85. package/dist/bridge/fs.js +0 -2231
  86. package/dist/bridge/index.d.ts +0 -10
  87. package/dist/bridge/index.js +0 -41
  88. package/dist/bridge/module.d.ts +0 -75
  89. package/dist/bridge/module.js +0 -299
  90. package/dist/bridge/network.d.ts +0 -250
  91. package/dist/bridge/network.js +0 -1433
  92. package/dist/bridge/os.d.ts +0 -13
  93. package/dist/bridge/os.js +0 -256
  94. package/dist/bridge/polyfills.d.ts +0 -2
  95. package/dist/bridge/polyfills.js +0 -11
  96. package/dist/bridge/process.d.ts +0 -89
  97. package/dist/bridge/process.js +0 -994
  98. package/dist/bridge.js +0 -11766
  99. package/dist/python-runtime.d.ts +0 -16
  100. package/dist/python-runtime.js +0 -45
  101. package/dist/runtime.d.ts +0 -31
  102. package/dist/runtime.js +0 -69
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Permission enforcement layer.
3
+ *
4
+ * Deny-by-default access control. Wraps VFS and other kernel operations
5
+ * with permission checks that throw on denial.
6
+ */
7
+ import type { Permissions } from "./types.js";
8
+ import type { VirtualFileSystem } from "./vfs.js";
9
+ /**
10
+ * Wrap a VFS with permission checks on every operation.
11
+ */
12
+ export declare function wrapFileSystem(fs: VirtualFileSystem, permissions?: Permissions): VirtualFileSystem;
13
+ /**
14
+ * Filter an env record through the env permission check.
15
+ * Returns only allowed key-value pairs.
16
+ */
17
+ export declare function filterEnv(env: Record<string, string> | undefined, permissions?: Permissions): Record<string, string>;
18
+ /**
19
+ * Check childProcess permission before spawning.
20
+ * No-op when no permissions or no childProcess check is configured.
21
+ */
22
+ export declare function checkChildProcess(permissions: Permissions | undefined, command: string, args: string[], cwd?: string): void;
23
+ export declare const allowAllFs: Pick<Permissions, "fs">;
24
+ export declare const allowAllNetwork: Pick<Permissions, "network">;
25
+ export declare const allowAllChildProcess: Pick<Permissions, "childProcess">;
26
+ export declare const allowAllEnv: Pick<Permissions, "env">;
27
+ export declare const allowAll: Permissions;
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Permission enforcement layer.
3
+ *
4
+ * Deny-by-default access control. Wraps VFS and other kernel operations
5
+ * with permission checks that throw on denial.
6
+ */
7
+ import { KernelError } from "./types.js";
8
+ function checkPermission(check, request, errorFactory) {
9
+ if (!check)
10
+ throw errorFactory(request);
11
+ const decision = check(request);
12
+ if (!decision?.allow)
13
+ throw errorFactory(request, decision?.reason);
14
+ }
15
+ function fsError(op, path, reason) {
16
+ const msg = reason
17
+ ? `permission denied, ${op} '${path ?? ""}': ${reason}`
18
+ : `permission denied, ${op} '${path ?? ""}'`;
19
+ return new KernelError("EACCES", msg);
20
+ }
21
+ /**
22
+ * Wrap a VFS with permission checks on every operation.
23
+ */
24
+ export function wrapFileSystem(fs, permissions) {
25
+ const check = (op, path) => {
26
+ checkPermission(permissions?.fs, { op, path }, (req, reason) => fsError(op, req.path, reason));
27
+ };
28
+ const wrapped = {
29
+ prepareOpenSync: (path, flags) => {
30
+ if ((flags & 0o100) !== 0 || (flags & 0o1000) !== 0) {
31
+ check("write", path);
32
+ }
33
+ const syncFs = fs;
34
+ return syncFs.prepareOpenSync?.(path, flags) ?? false;
35
+ },
36
+ readFile: async (path) => { check("read", path); return fs.readFile(path); },
37
+ readTextFile: async (path) => { check("read", path); return fs.readTextFile(path); },
38
+ readDir: async (path) => { check("readdir", path); return fs.readDir(path); },
39
+ readDirWithTypes: async (path) => { check("readdir", path); return fs.readDirWithTypes(path); },
40
+ writeFile: async (path, content) => { check("write", path); return fs.writeFile(path, content); },
41
+ createDir: async (path) => { check("createDir", path); return fs.createDir(path); },
42
+ mkdir: async (path, options) => { check("mkdir", path); return fs.mkdir(path, options); },
43
+ exists: async (path) => { check("exists", path); return fs.exists(path); },
44
+ stat: async (path) => { check("stat", path); return fs.stat(path); },
45
+ removeFile: async (path) => { check("rm", path); return fs.removeFile(path); },
46
+ removeDir: async (path) => { check("rm", path); return fs.removeDir(path); },
47
+ rename: async (oldPath, newPath) => {
48
+ check("rename", oldPath);
49
+ check("rename", newPath);
50
+ return fs.rename(oldPath, newPath);
51
+ },
52
+ realpath: async (path) => { check("read", path); return fs.realpath(path); },
53
+ symlink: async (target, linkPath) => { check("symlink", linkPath); return fs.symlink(target, linkPath); },
54
+ readlink: async (path) => { check("readlink", path); return fs.readlink(path); },
55
+ lstat: async (path) => { check("stat", path); return fs.lstat(path); },
56
+ link: async (oldPath, newPath) => { check("link", newPath); return fs.link(oldPath, newPath); },
57
+ chmod: async (path, mode) => { check("chmod", path); return fs.chmod(path, mode); },
58
+ chown: async (path, uid, gid) => { check("chown", path); return fs.chown(path, uid, gid); },
59
+ utimes: async (path, atime, mtime) => { check("utimes", path); return fs.utimes(path, atime, mtime); },
60
+ truncate: async (path, length) => { check("truncate", path); return fs.truncate(path, length); },
61
+ pread: async (path, offset, length) => { check("read", path); return fs.pread(path, offset, length); },
62
+ };
63
+ return wrapped;
64
+ }
65
+ /**
66
+ * Filter an env record through the env permission check.
67
+ * Returns only allowed key-value pairs.
68
+ */
69
+ export function filterEnv(env, permissions) {
70
+ if (!env)
71
+ return {};
72
+ if (!permissions?.env)
73
+ return {};
74
+ const result = {};
75
+ for (const [key, value] of Object.entries(env)) {
76
+ const request = { op: "read", key, value };
77
+ const decision = permissions.env(request);
78
+ if (decision?.allow) {
79
+ result[key] = value;
80
+ }
81
+ }
82
+ return result;
83
+ }
84
+ /**
85
+ * Check childProcess permission before spawning.
86
+ * No-op when no permissions or no childProcess check is configured.
87
+ */
88
+ export function checkChildProcess(permissions, command, args, cwd) {
89
+ if (!permissions?.childProcess)
90
+ return;
91
+ const request = { command, args, cwd };
92
+ const decision = permissions.childProcess(request);
93
+ if (!decision?.allow) {
94
+ const msg = decision?.reason
95
+ ? `permission denied, spawn '${command}': ${decision.reason}`
96
+ : `permission denied, spawn '${command}'`;
97
+ throw new KernelError("EACCES", msg);
98
+ }
99
+ }
100
+ // Permission presets
101
+ export const allowAllFs = {
102
+ fs: () => ({ allow: true }),
103
+ };
104
+ export const allowAllNetwork = {
105
+ network: () => ({ allow: true }),
106
+ };
107
+ export const allowAllChildProcess = {
108
+ childProcess: () => ({ allow: true }),
109
+ };
110
+ export const allowAllEnv = {
111
+ env: () => ({ allow: true }),
112
+ };
113
+ export const allowAll = {
114
+ ...allowAllFs,
115
+ ...allowAllNetwork,
116
+ ...allowAllChildProcess,
117
+ ...allowAllEnv,
118
+ };
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Pipe manager.
3
+ *
4
+ * Creates and manages pipes for inter-process communication.
5
+ * Supports cross-runtime pipes: data flows through kernel-managed buffers.
6
+ * SharedArrayBuffer ring buffers are deferred — this uses buffered pipes.
7
+ */
8
+ import type { FileDescription } from "./types.js";
9
+ import { FILETYPE_PIPE } from "./types.js";
10
+ import type { ProcessFDTable } from "./fd-table.js";
11
+ export interface PipeEnd {
12
+ description: FileDescription;
13
+ filetype: typeof FILETYPE_PIPE;
14
+ }
15
+ /** Maximum buffered bytes per pipe before writers block or O_NONBLOCK returns EAGAIN. */
16
+ export declare const MAX_PIPE_BUFFER_BYTES = 65536;
17
+ export declare class PipeManager {
18
+ private pipes;
19
+ /** Map description ID → pipe ID for routing reads/writes */
20
+ private descToPipe;
21
+ private nextPipeId;
22
+ private nextDescId;
23
+ /** Called before EPIPE when a write hits a closed read end. Receives writer PID. */
24
+ onBrokenPipe: ((pid: number) => void) | null;
25
+ /**
26
+ * Create a pipe. Returns two FileDescriptions:
27
+ * one for reading and one for writing.
28
+ */
29
+ createPipe(): {
30
+ read: PipeEnd;
31
+ write: PipeEnd;
32
+ };
33
+ /** Write data to a pipe's write end. Delivers SIGPIPE via onBrokenPipe when read end is closed. */
34
+ write(descriptionId: number, data: Uint8Array, writerPid?: number): number | Promise<number>;
35
+ /** Read data from a pipe's read end. Returns null on EOF. */
36
+ read(descriptionId: number, length: number): Promise<Uint8Array | null>;
37
+ /** Close one end of a pipe. */
38
+ close(descriptionId: number): void;
39
+ /** Check if a description ID belongs to a pipe */
40
+ isPipe(descriptionId: number): boolean;
41
+ /** Query poll state for a pipe end (used by poll/select syscalls). */
42
+ pollState(descriptionId: number): {
43
+ readable: boolean;
44
+ writable: boolean;
45
+ hangup: boolean;
46
+ } | null;
47
+ /** Get the pipe ID for a description, or undefined if not a pipe */
48
+ pipeIdFor(descriptionId: number): number | undefined;
49
+ /** Wait for a pipe poll state change (data, capacity, or hangup). */
50
+ waitForPoll(descriptionId: number, timeoutMs?: number): Promise<void>;
51
+ /**
52
+ * Create pipe FDs in the given FD table.
53
+ * Returns the FD numbers for {read, write}.
54
+ */
55
+ createPipeFDs(fdTable: ProcessFDTable): {
56
+ readFd: number;
57
+ writeFd: number;
58
+ };
59
+ private bufferSize;
60
+ private drainBuffer;
61
+ private writeBlocking;
62
+ private writeAvailable;
63
+ private assertWriteOpen;
64
+ }
@@ -0,0 +1,267 @@
1
+ /**
2
+ * Pipe manager.
3
+ *
4
+ * Creates and manages pipes for inter-process communication.
5
+ * Supports cross-runtime pipes: data flows through kernel-managed buffers.
6
+ * SharedArrayBuffer ring buffers are deferred — this uses buffered pipes.
7
+ */
8
+ import { FILETYPE_PIPE, O_NONBLOCK, O_RDONLY, O_WRONLY, KernelError } from "./types.js";
9
+ import { WaitQueue } from "./wait.js";
10
+ /** Maximum buffered bytes per pipe before writers block or O_NONBLOCK returns EAGAIN. */
11
+ export const MAX_PIPE_BUFFER_BYTES = 65_536; // 64 KB — matches Linux default
12
+ export class PipeManager {
13
+ pipes = new Map();
14
+ /** Map description ID → pipe ID for routing reads/writes */
15
+ descToPipe = new Map();
16
+ nextPipeId = 1;
17
+ nextDescId = 100_000; // High range to avoid FD table collisions
18
+ /** Called before EPIPE when a write hits a closed read end. Receives writer PID. */
19
+ onBrokenPipe = null;
20
+ /**
21
+ * Create a pipe. Returns two FileDescriptions:
22
+ * one for reading and one for writing.
23
+ */
24
+ createPipe() {
25
+ const id = this.nextPipeId++;
26
+ const readDesc = {
27
+ id: this.nextDescId++,
28
+ path: `pipe:${id}:read`,
29
+ cursor: 0n,
30
+ flags: O_RDONLY,
31
+ refCount: 0, // Not in any FD table yet — openWith() will bump
32
+ };
33
+ const writeDesc = {
34
+ id: this.nextDescId++,
35
+ path: `pipe:${id}:write`,
36
+ cursor: 0n,
37
+ flags: O_WRONLY,
38
+ refCount: 0, // Not in any FD table yet — openWith() will bump
39
+ };
40
+ const state = {
41
+ id,
42
+ buffer: [],
43
+ closed: { read: false, write: false },
44
+ readDescription: readDesc,
45
+ writeDescription: writeDesc,
46
+ readWaiters: [],
47
+ writeWaiters: new WaitQueue(),
48
+ pollWaiters: new WaitQueue(),
49
+ };
50
+ this.pipes.set(id, state);
51
+ this.descToPipe.set(readDesc.id, { pipeId: id, end: "read" });
52
+ this.descToPipe.set(writeDesc.id, { pipeId: id, end: "write" });
53
+ return {
54
+ read: { description: readDesc, filetype: FILETYPE_PIPE },
55
+ write: { description: writeDesc, filetype: FILETYPE_PIPE },
56
+ };
57
+ }
58
+ /** Write data to a pipe's write end. Delivers SIGPIPE via onBrokenPipe when read end is closed. */
59
+ write(descriptionId, data, writerPid) {
60
+ const ref = this.descToPipe.get(descriptionId);
61
+ if (!ref || ref.end !== "write")
62
+ throw new KernelError("EBADF", "not a pipe write end");
63
+ const state = this.pipes.get(ref.pipeId);
64
+ if (!state)
65
+ throw new KernelError("EBADF", "pipe not found");
66
+ const nonBlocking = (state.writeDescription.flags & O_NONBLOCK) !== 0;
67
+ const written = this.writeAvailable(state, data, writerPid);
68
+ if (written === data.length) {
69
+ return data.length;
70
+ }
71
+ if (nonBlocking) {
72
+ if (written === 0) {
73
+ throw new KernelError("EAGAIN", "pipe buffer full");
74
+ }
75
+ return written;
76
+ }
77
+ return this.writeBlocking(state, data, written, writerPid);
78
+ }
79
+ /** Read data from a pipe's read end. Returns null on EOF. */
80
+ read(descriptionId, length) {
81
+ const ref = this.descToPipe.get(descriptionId);
82
+ if (!ref || ref.end !== "read")
83
+ throw new KernelError("EBADF", "not a pipe read end");
84
+ const state = this.pipes.get(ref.pipeId);
85
+ if (!state)
86
+ throw new KernelError("EBADF", "pipe not found");
87
+ // Data available in buffer
88
+ if (state.buffer.length > 0) {
89
+ const data = this.drainBuffer(state, length);
90
+ state.writeWaiters.wakeOne();
91
+ state.pollWaiters.wakeAll();
92
+ return Promise.resolve(data);
93
+ }
94
+ // Write end closed — EOF
95
+ if (state.closed.write) {
96
+ return Promise.resolve(null);
97
+ }
98
+ // Block until data or EOF
99
+ return new Promise((resolve) => {
100
+ state.readWaiters.push(resolve);
101
+ });
102
+ }
103
+ /** Close one end of a pipe. */
104
+ close(descriptionId) {
105
+ const ref = this.descToPipe.get(descriptionId);
106
+ if (!ref)
107
+ return;
108
+ const state = this.pipes.get(ref.pipeId);
109
+ if (!state)
110
+ return;
111
+ if (ref.end === "read") {
112
+ state.closed.read = true;
113
+ state.writeWaiters.wakeAll();
114
+ }
115
+ else {
116
+ state.closed.write = true;
117
+ // Notify any blocked readers with EOF
118
+ for (const waiter of state.readWaiters) {
119
+ waiter(null);
120
+ }
121
+ state.readWaiters.length = 0;
122
+ state.writeWaiters.wakeAll();
123
+ }
124
+ state.pollWaiters.wakeAll();
125
+ this.descToPipe.delete(descriptionId);
126
+ // Clean up when both ends are closed
127
+ if (state.closed.read && state.closed.write) {
128
+ this.pipes.delete(ref.pipeId);
129
+ }
130
+ }
131
+ /** Check if a description ID belongs to a pipe */
132
+ isPipe(descriptionId) {
133
+ return this.descToPipe.has(descriptionId);
134
+ }
135
+ /** Query poll state for a pipe end (used by poll/select syscalls). */
136
+ pollState(descriptionId) {
137
+ const ref = this.descToPipe.get(descriptionId);
138
+ if (!ref)
139
+ return null;
140
+ const state = this.pipes.get(ref.pipeId);
141
+ if (!state)
142
+ return null;
143
+ if (ref.end === "read") {
144
+ const hasData = this.bufferSize(state) > 0;
145
+ return {
146
+ readable: hasData || state.closed.write,
147
+ writable: false,
148
+ hangup: state.closed.write,
149
+ };
150
+ }
151
+ else {
152
+ return {
153
+ readable: false,
154
+ writable: !state.closed.read && this.bufferSize(state) < MAX_PIPE_BUFFER_BYTES,
155
+ hangup: state.closed.read,
156
+ };
157
+ }
158
+ }
159
+ /** Get the pipe ID for a description, or undefined if not a pipe */
160
+ pipeIdFor(descriptionId) {
161
+ return this.descToPipe.get(descriptionId)?.pipeId;
162
+ }
163
+ /** Wait for a pipe poll state change (data, capacity, or hangup). */
164
+ async waitForPoll(descriptionId, timeoutMs) {
165
+ const ref = this.descToPipe.get(descriptionId);
166
+ if (!ref)
167
+ throw new KernelError("EBADF", "not a pipe description");
168
+ const state = this.pipes.get(ref.pipeId);
169
+ if (!state)
170
+ throw new KernelError("EBADF", "pipe not found");
171
+ const handle = state.pollWaiters.enqueue(timeoutMs);
172
+ try {
173
+ await handle.wait();
174
+ }
175
+ finally {
176
+ state.pollWaiters.remove(handle);
177
+ }
178
+ }
179
+ /**
180
+ * Create pipe FDs in the given FD table.
181
+ * Returns the FD numbers for {read, write}.
182
+ */
183
+ createPipeFDs(fdTable) {
184
+ const { read, write } = this.createPipe();
185
+ const readFd = fdTable.openWith(read.description, read.filetype);
186
+ const writeFd = fdTable.openWith(write.description, write.filetype);
187
+ return { readFd, writeFd };
188
+ }
189
+ bufferSize(state) {
190
+ let size = 0;
191
+ for (const chunk of state.buffer)
192
+ size += chunk.length;
193
+ return size;
194
+ }
195
+ drainBuffer(state, length) {
196
+ // Concatenate buffered chunks up to `length` bytes
197
+ const chunks = [];
198
+ let remaining = length;
199
+ while (remaining > 0 && state.buffer.length > 0) {
200
+ const chunk = state.buffer[0];
201
+ if (chunk.length <= remaining) {
202
+ chunks.push(chunk);
203
+ remaining -= chunk.length;
204
+ state.buffer.shift();
205
+ }
206
+ else {
207
+ chunks.push(chunk.subarray(0, remaining));
208
+ state.buffer[0] = chunk.subarray(remaining);
209
+ remaining = 0;
210
+ }
211
+ }
212
+ if (chunks.length === 1)
213
+ return chunks[0];
214
+ const total = chunks.reduce((sum, c) => sum + c.length, 0);
215
+ const result = new Uint8Array(total);
216
+ let offset = 0;
217
+ for (const chunk of chunks) {
218
+ result.set(chunk, offset);
219
+ offset += chunk.length;
220
+ }
221
+ return result;
222
+ }
223
+ async writeBlocking(state, data, offset, writerPid) {
224
+ while (offset < data.length) {
225
+ const handle = state.writeWaiters.enqueue();
226
+ try {
227
+ await handle.wait();
228
+ }
229
+ finally {
230
+ state.writeWaiters.remove(handle);
231
+ }
232
+ offset += this.writeAvailable(state, data.subarray(offset), writerPid);
233
+ }
234
+ return data.length;
235
+ }
236
+ writeAvailable(state, data, writerPid) {
237
+ this.assertWriteOpen(state, writerPid);
238
+ if (data.length === 0)
239
+ return 0;
240
+ // If readers are waiting, deliver directly without growing the buffer.
241
+ if (state.readWaiters.length > 0 && state.buffer.length === 0) {
242
+ const waiter = state.readWaiters.shift();
243
+ waiter(new Uint8Array(data));
244
+ state.pollWaiters.wakeAll();
245
+ return data.length;
246
+ }
247
+ const capacity = MAX_PIPE_BUFFER_BYTES - this.bufferSize(state);
248
+ if (capacity <= 0) {
249
+ return 0;
250
+ }
251
+ const bytesToWrite = Math.min(capacity, data.length);
252
+ state.buffer.push(new Uint8Array(data.subarray(0, bytesToWrite)));
253
+ state.pollWaiters.wakeAll();
254
+ return bytesToWrite;
255
+ }
256
+ assertWriteOpen(state, writerPid) {
257
+ if (state.closed.write)
258
+ throw new KernelError("EPIPE", "write end closed");
259
+ if (state.closed.read) {
260
+ // Deliver SIGPIPE before EPIPE (POSIX: signal first, then errno)
261
+ if (writerPid !== undefined && this.onBrokenPipe) {
262
+ this.onBrokenPipe(writerPid);
263
+ }
264
+ throw new KernelError("EPIPE", "read end closed");
265
+ }
266
+ }
267
+ }
@@ -0,0 +1,11 @@
1
+ import type { FDTableManager } from "./fd-table.js";
2
+ import type { ProcessTable } from "./process-table.js";
3
+ import type { VirtualFileSystem } from "./vfs.js";
4
+ export interface ProcLayerOptions {
5
+ processTable: ProcessTable;
6
+ fdTableManager: FDTableManager;
7
+ hostname?: string;
8
+ }
9
+ export declare function resolveProcSelfPath(path: string, pid: number): string;
10
+ export declare function createProcessScopedFileSystem(vfs: VirtualFileSystem, pid: number): VirtualFileSystem;
11
+ export declare function createProcLayer(vfs: VirtualFileSystem, options: ProcLayerOptions): VirtualFileSystem;