@secure-exec/core 0.1.1-rc.3 → 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 +24 -5
  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 +2 -2
  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 +145 -6
  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 +1600 -338
  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 -19
  22. package/dist/isolate-runtime/setup-fs-facade.js +62 -23
  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 +329 -20
  70. package/dist/shared/bridge-contract.js +60 -5
  71. package/dist/shared/console-formatter.js +8 -4
  72. package/dist/shared/global-exposure.js +269 -19
  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 +19 -39
  77. package/dist/types.d.ts +8 -159
  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 -670
  84. package/dist/bridge/fs.d.ts +0 -281
  85. package/dist/bridge/fs.js +0 -2235
  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 -308
  90. package/dist/bridge/network.d.ts +0 -350
  91. package/dist/bridge/network.js +0 -2050
  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 -1015
  98. package/dist/bridge.js +0 -12496
  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,29 @@
1
+ /**
2
+ * Kernel DNS cache shared across runtimes.
3
+ *
4
+ * Runtimes call kernel DNS cache before falling through to the host
5
+ * adapter. Entries expire after their TTL.
6
+ */
7
+ import type { DnsResult } from "./host-adapter.js";
8
+ export interface DnsCacheOptions {
9
+ /** Default TTL in milliseconds when none is specified. Default: 30000 (30s). */
10
+ defaultTtlMs?: number;
11
+ }
12
+ export declare class DnsCache {
13
+ private cache;
14
+ private defaultTtlMs;
15
+ constructor(options?: DnsCacheOptions);
16
+ /**
17
+ * Look up a cached DNS result. Returns null on miss or expired entry.
18
+ */
19
+ lookup(hostname: string, rrtype: string): DnsResult | null;
20
+ /**
21
+ * Store a DNS result with TTL.
22
+ * @param ttlMs TTL in milliseconds. Uses defaultTtlMs if not provided.
23
+ */
24
+ store(hostname: string, rrtype: string, result: DnsResult, ttlMs?: number): void;
25
+ /** Flush all cached entries. */
26
+ flush(): void;
27
+ /** Number of entries (including possibly expired). */
28
+ get size(): number;
29
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Kernel DNS cache shared across runtimes.
3
+ *
4
+ * Runtimes call kernel DNS cache before falling through to the host
5
+ * adapter. Entries expire after their TTL.
6
+ */
7
+ export class DnsCache {
8
+ cache = new Map();
9
+ defaultTtlMs;
10
+ constructor(options) {
11
+ this.defaultTtlMs = options?.defaultTtlMs ?? 30_000;
12
+ }
13
+ /**
14
+ * Look up a cached DNS result. Returns null on miss or expired entry.
15
+ */
16
+ lookup(hostname, rrtype) {
17
+ const key = cacheKey(hostname, rrtype);
18
+ const entry = this.cache.get(key);
19
+ if (!entry)
20
+ return null;
21
+ // Expired — remove and return miss
22
+ if (Date.now() >= entry.expiresAt) {
23
+ this.cache.delete(key);
24
+ return null;
25
+ }
26
+ return entry.result;
27
+ }
28
+ /**
29
+ * Store a DNS result with TTL.
30
+ * @param ttlMs TTL in milliseconds. Uses defaultTtlMs if not provided.
31
+ */
32
+ store(hostname, rrtype, result, ttlMs) {
33
+ const key = cacheKey(hostname, rrtype);
34
+ const ttl = ttlMs ?? this.defaultTtlMs;
35
+ this.cache.set(key, {
36
+ result,
37
+ expiresAt: Date.now() + ttl,
38
+ });
39
+ }
40
+ /** Flush all cached entries. */
41
+ flush() {
42
+ this.cache.clear();
43
+ }
44
+ /** Number of entries (including possibly expired). */
45
+ get size() {
46
+ return this.cache.size;
47
+ }
48
+ }
49
+ /** Canonical cache key: "hostname:rrtype" */
50
+ function cacheKey(hostname, rrtype) {
51
+ return `${hostname}:${rrtype}`;
52
+ }
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Per-PID file descriptor table.
3
+ *
4
+ * Each process gets its own FD number space. Multiple FDs can share the
5
+ * same FileDescription (via dup/dup2), which shares the cursor position.
6
+ * Standard FDs 0-2 are pre-allocated per process.
7
+ */
8
+ import type { FDEntry, FDStat, FileDescription } from "./types.js";
9
+ /** Maximum open FDs per process before allocations are rejected (EMFILE). */
10
+ export declare const MAX_FDS_PER_PROCESS = 256;
11
+ /** Allocator function that creates a FileDescription with a unique ID. */
12
+ export type DescriptionAllocator = (path: string, flags: number) => FileDescription;
13
+ /**
14
+ * FD table for a single process.
15
+ *
16
+ * Manages FD allocation, dup/dup2, and shared cursor via FileDescription.
17
+ */
18
+ export declare class ProcessFDTable {
19
+ private entries;
20
+ private nextFd;
21
+ private allocDesc;
22
+ constructor(allocDesc: DescriptionAllocator);
23
+ /** Pre-allocate stdin, stdout, stderr */
24
+ initStdio(stdinDesc: FileDescription, stdoutDesc: FileDescription, stderrDesc: FileDescription): void;
25
+ /** Pre-allocate stdin, stdout, stderr with custom filetypes (for pipe wiring). */
26
+ initStdioWithTypes(stdinDesc: FileDescription, stdinType: number, stdoutDesc: FileDescription, stdoutType: number, stderrDesc: FileDescription, stderrType: number): void;
27
+ /** Open a new FD for the given path and flags */
28
+ open(path: string, flags: number, filetype?: number): number;
29
+ /** Open a new FD pointing to an existing FileDescription (for pipes, inherited FDs) */
30
+ openWith(description: FileDescription, filetype: number, targetFd?: number): number;
31
+ get(fd: number): FDEntry | undefined;
32
+ /** Close an FD. Decrements the refcount on the shared FileDescription. */
33
+ close(fd: number): boolean;
34
+ /** Duplicate an FD — new FD shares the same FileDescription (cursor). cloexec cleared on new FD (POSIX). */
35
+ dup(fd: number): number;
36
+ /** Duplicate FD to lowest available >= minFd (F_DUPFD). cloexec cleared on new FD. */
37
+ dupMinFd(fd: number, minFd: number): number;
38
+ /** Duplicate oldFd to newFd. Closes newFd first if open. cloexec cleared on new FD (POSIX). */
39
+ dup2(oldFd: number, newFd: number): void;
40
+ stat(fd: number): FDStat;
41
+ /** Create a copy of this table for a child process (FD inheritance). Skips cloexec FDs. */
42
+ fork(): ProcessFDTable;
43
+ /** Close all FDs, decrementing all refcounts. */
44
+ closeAll(): void;
45
+ /** Iterate all FD entries (for cleanup inspection). */
46
+ [Symbol.iterator](): IterableIterator<FDEntry>;
47
+ private allocateFd;
48
+ }
49
+ /**
50
+ * Kernel-level FD table manager.
51
+ * Owns per-PID FD tables and coordinates shared FileDescriptions.
52
+ */
53
+ export declare class FDTableManager {
54
+ private tables;
55
+ private nextDescriptionId;
56
+ /** Per-instance allocator bound to this manager's ID counter. */
57
+ private allocDesc;
58
+ /** Create a new FD table for a process with standard FDs. */
59
+ create(pid: number): ProcessFDTable;
60
+ /**
61
+ * Create a new FD table with custom stdio FileDescriptions.
62
+ * Used for pipe wiring: pass a pipe read/write end as stdin/stdout/stderr.
63
+ * Null entries fall back to default device nodes.
64
+ */
65
+ createWithStdio(pid: number, stdinOverride: {
66
+ description: FileDescription;
67
+ filetype: number;
68
+ } | null, stdoutOverride: {
69
+ description: FileDescription;
70
+ filetype: number;
71
+ } | null, stderrOverride: {
72
+ description: FileDescription;
73
+ filetype: number;
74
+ } | null): ProcessFDTable;
75
+ /** Create a child FD table by forking the parent's. */
76
+ fork(parentPid: number, childPid: number): ProcessFDTable;
77
+ get(pid: number): ProcessFDTable | undefined;
78
+ /** Check whether a PID has an FD table. */
79
+ has(pid: number): boolean;
80
+ /** Number of active FD tables. */
81
+ get size(): number;
82
+ /** Remove and close all FDs for a process. */
83
+ remove(pid: number): void;
84
+ }
@@ -0,0 +1,278 @@
1
+ /**
2
+ * Per-PID file descriptor table.
3
+ *
4
+ * Each process gets its own FD number space. Multiple FDs can share the
5
+ * same FileDescription (via dup/dup2), which shares the cursor position.
6
+ * Standard FDs 0-2 are pre-allocated per process.
7
+ */
8
+ import { FILETYPE_REGULAR_FILE, FILETYPE_CHARACTER_DEVICE, O_RDONLY, O_WRONLY, O_CLOEXEC, KernelError, } from "./types.js";
9
+ /** Maximum open FDs per process before allocations are rejected (EMFILE). */
10
+ export const MAX_FDS_PER_PROCESS = 256;
11
+ /**
12
+ * FD table for a single process.
13
+ *
14
+ * Manages FD allocation, dup/dup2, and shared cursor via FileDescription.
15
+ */
16
+ export class ProcessFDTable {
17
+ entries = new Map();
18
+ nextFd = 3; // 0, 1, 2 reserved
19
+ allocDesc;
20
+ constructor(allocDesc) {
21
+ this.allocDesc = allocDesc;
22
+ }
23
+ /** Pre-allocate stdin, stdout, stderr */
24
+ initStdio(stdinDesc, stdoutDesc, stderrDesc) {
25
+ this.entries.set(0, {
26
+ fd: 0,
27
+ description: stdinDesc,
28
+ rights: 0n,
29
+ filetype: FILETYPE_CHARACTER_DEVICE,
30
+ cloexec: false,
31
+ });
32
+ this.entries.set(1, {
33
+ fd: 1,
34
+ description: stdoutDesc,
35
+ rights: 0n,
36
+ filetype: FILETYPE_CHARACTER_DEVICE,
37
+ cloexec: false,
38
+ });
39
+ this.entries.set(2, {
40
+ fd: 2,
41
+ description: stderrDesc,
42
+ rights: 0n,
43
+ filetype: FILETYPE_CHARACTER_DEVICE,
44
+ cloexec: false,
45
+ });
46
+ }
47
+ /** Pre-allocate stdin, stdout, stderr with custom filetypes (for pipe wiring). */
48
+ initStdioWithTypes(stdinDesc, stdinType, stdoutDesc, stdoutType, stderrDesc, stderrType) {
49
+ // Shared descriptions (from pipes) get refCount bumped
50
+ stdinDesc.refCount++;
51
+ stdoutDesc.refCount++;
52
+ stderrDesc.refCount++;
53
+ this.entries.set(0, { fd: 0, description: stdinDesc, rights: 0n, filetype: stdinType, cloexec: false });
54
+ this.entries.set(1, { fd: 1, description: stdoutDesc, rights: 0n, filetype: stdoutType, cloexec: false });
55
+ this.entries.set(2, { fd: 2, description: stderrDesc, rights: 0n, filetype: stderrType, cloexec: false });
56
+ }
57
+ /** Open a new FD for the given path and flags */
58
+ open(path, flags, filetype) {
59
+ const fd = this.allocateFd();
60
+ const cloexec = (flags & O_CLOEXEC) !== 0;
61
+ const storedFlags = flags & ~O_CLOEXEC;
62
+ const description = this.allocDesc(path, storedFlags);
63
+ this.entries.set(fd, {
64
+ fd,
65
+ description,
66
+ rights: 0n,
67
+ filetype: filetype ?? FILETYPE_REGULAR_FILE,
68
+ cloexec,
69
+ });
70
+ return fd;
71
+ }
72
+ /** Open a new FD pointing to an existing FileDescription (for pipes, inherited FDs) */
73
+ openWith(description, filetype, targetFd) {
74
+ const fd = targetFd ?? this.allocateFd();
75
+ description.refCount++;
76
+ this.entries.set(fd, {
77
+ fd,
78
+ description,
79
+ rights: 0n,
80
+ filetype,
81
+ cloexec: false,
82
+ });
83
+ return fd;
84
+ }
85
+ get(fd) {
86
+ return this.entries.get(fd);
87
+ }
88
+ /** Close an FD. Decrements the refcount on the shared FileDescription. */
89
+ close(fd) {
90
+ const entry = this.entries.get(fd);
91
+ if (!entry)
92
+ return false;
93
+ entry.description.refCount--;
94
+ this.entries.delete(fd);
95
+ return true;
96
+ }
97
+ /** Duplicate an FD — new FD shares the same FileDescription (cursor). cloexec cleared on new FD (POSIX). */
98
+ dup(fd) {
99
+ const entry = this.entries.get(fd);
100
+ if (!entry)
101
+ throw new KernelError("EBADF", `bad file descriptor ${fd}`);
102
+ const newFd = this.allocateFd();
103
+ entry.description.refCount++;
104
+ this.entries.set(newFd, {
105
+ fd: newFd,
106
+ description: entry.description,
107
+ rights: entry.rights,
108
+ filetype: entry.filetype,
109
+ cloexec: false,
110
+ });
111
+ return newFd;
112
+ }
113
+ /** Duplicate FD to lowest available >= minFd (F_DUPFD). cloexec cleared on new FD. */
114
+ dupMinFd(fd, minFd) {
115
+ const entry = this.entries.get(fd);
116
+ if (!entry)
117
+ throw new KernelError("EBADF", `bad file descriptor ${fd}`);
118
+ if (this.entries.size >= MAX_FDS_PER_PROCESS) {
119
+ throw new KernelError("EMFILE", "too many open files");
120
+ }
121
+ let newFd = minFd;
122
+ while (this.entries.has(newFd))
123
+ newFd++;
124
+ entry.description.refCount++;
125
+ this.entries.set(newFd, {
126
+ fd: newFd,
127
+ description: entry.description,
128
+ rights: entry.rights,
129
+ filetype: entry.filetype,
130
+ cloexec: false,
131
+ });
132
+ return newFd;
133
+ }
134
+ /** Duplicate oldFd to newFd. Closes newFd first if open. cloexec cleared on new FD (POSIX). */
135
+ dup2(oldFd, newFd) {
136
+ const entry = this.entries.get(oldFd);
137
+ if (!entry)
138
+ throw new KernelError("EBADF", `bad file descriptor ${oldFd}`);
139
+ if (oldFd === newFd)
140
+ return;
141
+ // Close newFd if already open
142
+ if (this.entries.has(newFd)) {
143
+ this.close(newFd);
144
+ }
145
+ entry.description.refCount++;
146
+ this.entries.set(newFd, {
147
+ fd: newFd,
148
+ description: entry.description,
149
+ rights: entry.rights,
150
+ filetype: entry.filetype,
151
+ cloexec: false,
152
+ });
153
+ }
154
+ stat(fd) {
155
+ const entry = this.entries.get(fd);
156
+ if (!entry)
157
+ throw new KernelError("EBADF", `bad file descriptor ${fd}`);
158
+ return {
159
+ filetype: entry.filetype,
160
+ flags: entry.description.flags,
161
+ rights: entry.rights,
162
+ };
163
+ }
164
+ /** Create a copy of this table for a child process (FD inheritance). Skips cloexec FDs. */
165
+ fork() {
166
+ const child = new ProcessFDTable(this.allocDesc);
167
+ child.nextFd = this.nextFd;
168
+ for (const [fd, entry] of this.entries) {
169
+ if (entry.cloexec)
170
+ continue;
171
+ entry.description.refCount++;
172
+ child.entries.set(fd, {
173
+ fd,
174
+ description: entry.description,
175
+ rights: entry.rights,
176
+ filetype: entry.filetype,
177
+ cloexec: false,
178
+ });
179
+ }
180
+ return child;
181
+ }
182
+ /** Close all FDs, decrementing all refcounts. */
183
+ closeAll() {
184
+ for (const [fd] of this.entries) {
185
+ this.close(fd);
186
+ }
187
+ }
188
+ /** Iterate all FD entries (for cleanup inspection). */
189
+ *[Symbol.iterator]() {
190
+ yield* this.entries.values();
191
+ }
192
+ allocateFd() {
193
+ // Enforce per-process FD limit
194
+ if (this.entries.size >= MAX_FDS_PER_PROCESS) {
195
+ throw new KernelError("EMFILE", "too many open files");
196
+ }
197
+ // Find lowest available FD >= nextFd hint
198
+ while (this.entries.has(this.nextFd)) {
199
+ this.nextFd++;
200
+ }
201
+ return this.nextFd++;
202
+ }
203
+ }
204
+ /**
205
+ * Kernel-level FD table manager.
206
+ * Owns per-PID FD tables and coordinates shared FileDescriptions.
207
+ */
208
+ export class FDTableManager {
209
+ tables = new Map();
210
+ nextDescriptionId = 1;
211
+ /** Per-instance allocator bound to this manager's ID counter. */
212
+ allocDesc = (path, flags) => ({
213
+ id: this.nextDescriptionId++,
214
+ path,
215
+ cursor: 0n,
216
+ flags,
217
+ refCount: 1,
218
+ });
219
+ /** Create a new FD table for a process with standard FDs. */
220
+ create(pid) {
221
+ const table = new ProcessFDTable(this.allocDesc);
222
+ table.initStdio(this.allocDesc("/dev/stdin", O_RDONLY), this.allocDesc("/dev/stdout", O_WRONLY), this.allocDesc("/dev/stderr", O_WRONLY));
223
+ this.tables.set(pid, table);
224
+ return table;
225
+ }
226
+ /**
227
+ * Create a new FD table with custom stdio FileDescriptions.
228
+ * Used for pipe wiring: pass a pipe read/write end as stdin/stdout/stderr.
229
+ * Null entries fall back to default device nodes.
230
+ */
231
+ createWithStdio(pid, stdinOverride, stdoutOverride, stderrOverride) {
232
+ const table = new ProcessFDTable(this.allocDesc);
233
+ const stdinDesc = stdinOverride
234
+ ? stdinOverride.description
235
+ : this.allocDesc("/dev/stdin", O_RDONLY);
236
+ const stdinType = stdinOverride?.filetype ?? FILETYPE_CHARACTER_DEVICE;
237
+ const stdoutDesc = stdoutOverride
238
+ ? stdoutOverride.description
239
+ : this.allocDesc("/dev/stdout", O_WRONLY);
240
+ const stdoutType = stdoutOverride?.filetype ?? FILETYPE_CHARACTER_DEVICE;
241
+ const stderrDesc = stderrOverride
242
+ ? stderrOverride.description
243
+ : this.allocDesc("/dev/stderr", O_WRONLY);
244
+ const stderrType = stderrOverride?.filetype ?? FILETYPE_CHARACTER_DEVICE;
245
+ table.initStdioWithTypes(stdinDesc, stdinType, stdoutDesc, stdoutType, stderrDesc, stderrType);
246
+ this.tables.set(pid, table);
247
+ return table;
248
+ }
249
+ /** Create a child FD table by forking the parent's. */
250
+ fork(parentPid, childPid) {
251
+ const parentTable = this.tables.get(parentPid);
252
+ if (!parentTable) {
253
+ return this.create(childPid);
254
+ }
255
+ const childTable = parentTable.fork();
256
+ this.tables.set(childPid, childTable);
257
+ return childTable;
258
+ }
259
+ get(pid) {
260
+ return this.tables.get(pid);
261
+ }
262
+ /** Check whether a PID has an FD table. */
263
+ has(pid) {
264
+ return this.tables.has(pid);
265
+ }
266
+ /** Number of active FD tables. */
267
+ get size() {
268
+ return this.tables.size;
269
+ }
270
+ /** Remove and close all FDs for a process. */
271
+ remove(pid) {
272
+ const table = this.tables.get(pid);
273
+ if (table) {
274
+ table.closeAll();
275
+ this.tables.delete(pid);
276
+ }
277
+ }
278
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Advisory file lock manager (flock semantics).
3
+ *
4
+ * Locks are per-path (inode proxy). Multiple FDs sharing the same
5
+ * FileDescription (via dup) share the same lock. Locks are released
6
+ * when the description's refCount drops to zero (all FDs closed).
7
+ */
8
+ export declare const LOCK_SH = 1;
9
+ export declare const LOCK_EX = 2;
10
+ export declare const LOCK_UN = 8;
11
+ export declare const LOCK_NB = 4;
12
+ export declare class FileLockManager {
13
+ /** path -> lock state */
14
+ private locks;
15
+ /** descriptionId -> path (for cleanup) */
16
+ private descToPath;
17
+ /**
18
+ * Acquire, upgrade/downgrade, or release a lock.
19
+ *
20
+ * @param path Resolved file path (inode proxy)
21
+ * @param descId FileDescription id (shared across dup'd FDs)
22
+ * @param operation LOCK_SH | LOCK_EX | LOCK_UN, optionally | LOCK_NB
23
+ */
24
+ flock(path: string, descId: number, operation: number): Promise<void>;
25
+ /** Release the lock held by a specific description on a path. */
26
+ private unlock;
27
+ /** Release all locks held by a specific description (called on FD close when refCount drops to 0). */
28
+ releaseByDescription(descId: number): void;
29
+ /** Check if a description holds any lock. */
30
+ hasLock(descId: number): boolean;
31
+ private getOrCreate;
32
+ private tryAcquire;
33
+ private cleanupState;
34
+ }
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Advisory file lock manager (flock semantics).
3
+ *
4
+ * Locks are per-path (inode proxy). Multiple FDs sharing the same
5
+ * FileDescription (via dup) share the same lock. Locks are released
6
+ * when the description's refCount drops to zero (all FDs closed).
7
+ */
8
+ import { KernelError } from "./types.js";
9
+ import { WaitQueue } from "./wait.js";
10
+ // flock operation flags (POSIX)
11
+ export const LOCK_SH = 1;
12
+ export const LOCK_EX = 2;
13
+ export const LOCK_UN = 8;
14
+ export const LOCK_NB = 4;
15
+ const FLOCK_WAIT_TIMEOUT_MS = 30_000;
16
+ export class FileLockManager {
17
+ /** path -> lock state */
18
+ locks = new Map();
19
+ /** descriptionId -> path (for cleanup) */
20
+ descToPath = new Map();
21
+ /**
22
+ * Acquire, upgrade/downgrade, or release a lock.
23
+ *
24
+ * @param path Resolved file path (inode proxy)
25
+ * @param descId FileDescription id (shared across dup'd FDs)
26
+ * @param operation LOCK_SH | LOCK_EX | LOCK_UN, optionally | LOCK_NB
27
+ */
28
+ async flock(path, descId, operation) {
29
+ const op = operation & ~LOCK_NB;
30
+ const nonBlocking = (operation & LOCK_NB) !== 0;
31
+ if (op === LOCK_UN) {
32
+ this.unlock(path, descId);
33
+ return;
34
+ }
35
+ while (true) {
36
+ const state = this.getOrCreate(path);
37
+ if (this.tryAcquire(path, state, descId, op)) {
38
+ return;
39
+ }
40
+ if (nonBlocking) {
41
+ throw new KernelError("EAGAIN", "resource temporarily unavailable");
42
+ }
43
+ // Bound each wait so callers can re-check lock state without hanging forever.
44
+ const handle = state.waiters.enqueue(FLOCK_WAIT_TIMEOUT_MS);
45
+ try {
46
+ await handle.wait();
47
+ }
48
+ finally {
49
+ state.waiters.remove(handle);
50
+ this.cleanupState(path, state);
51
+ }
52
+ }
53
+ }
54
+ /** Release the lock held by a specific description on a path. */
55
+ unlock(path, descId) {
56
+ const state = this.locks.get(path);
57
+ if (!state)
58
+ return;
59
+ const idx = state.holders.findIndex(h => h.descriptionId === descId);
60
+ if (idx >= 0) {
61
+ state.holders.splice(idx, 1);
62
+ this.descToPath.delete(descId);
63
+ state.waiters.wakeOne();
64
+ }
65
+ this.cleanupState(path, state);
66
+ }
67
+ /** Release all locks held by a specific description (called on FD close when refCount drops to 0). */
68
+ releaseByDescription(descId) {
69
+ const path = this.descToPath.get(descId);
70
+ if (path === undefined)
71
+ return;
72
+ this.unlock(path, descId);
73
+ }
74
+ /** Check if a description holds any lock. */
75
+ hasLock(descId) {
76
+ return this.descToPath.has(descId);
77
+ }
78
+ getOrCreate(path) {
79
+ let state = this.locks.get(path);
80
+ if (!state) {
81
+ state = { holders: [], waiters: new WaitQueue() };
82
+ this.locks.set(path, state);
83
+ }
84
+ return state;
85
+ }
86
+ tryAcquire(path, state, descId, op) {
87
+ const existingIdx = state.holders.findIndex(h => h.descriptionId === descId);
88
+ if (op === LOCK_SH) {
89
+ const conflict = state.holders.some(h => h.type === "ex" && h.descriptionId !== descId);
90
+ if (conflict) {
91
+ return false;
92
+ }
93
+ if (existingIdx >= 0) {
94
+ state.holders[existingIdx].type = "sh";
95
+ }
96
+ else {
97
+ state.holders.push({ descriptionId: descId, type: "sh" });
98
+ this.descToPath.set(descId, path);
99
+ }
100
+ return true;
101
+ }
102
+ if (op === LOCK_EX) {
103
+ const conflict = state.holders.some(h => h.descriptionId !== descId);
104
+ if (conflict) {
105
+ return false;
106
+ }
107
+ if (existingIdx >= 0) {
108
+ state.holders[existingIdx].type = "ex";
109
+ }
110
+ else {
111
+ state.holders.push({ descriptionId: descId, type: "ex" });
112
+ this.descToPath.set(descId, path);
113
+ }
114
+ return true;
115
+ }
116
+ throw new KernelError("EINVAL", `unsupported flock operation ${op}`);
117
+ }
118
+ cleanupState(path, state) {
119
+ if (state.holders.length === 0 && state.waiters.pending === 0) {
120
+ this.locks.delete(path);
121
+ }
122
+ }
123
+ }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Host adapter interfaces for kernel network delegation.
3
+ *
4
+ * The kernel uses these interfaces to delegate external I/O to the host
5
+ * without knowing the host implementation. Node.js driver implements
6
+ * using node:net / node:dgram; browser driver may use WebSocket proxy.
7
+ */
8
+ /** A connected TCP socket on the host. */
9
+ export interface HostSocket {
10
+ write(data: Uint8Array): Promise<void>;
11
+ /** Returns data or null for EOF. */
12
+ read(): Promise<Uint8Array | null>;
13
+ close(): Promise<void>;
14
+ /** Forward kernel socket options to host socket. */
15
+ setOption(level: number, optname: number, optval: number): void;
16
+ /** TCP half-close / full shutdown. */
17
+ shutdown(how: "read" | "write" | "both"): void;
18
+ }
19
+ /** A TCP listener on the host. */
20
+ export interface HostListener {
21
+ /** Accept the next incoming connection. */
22
+ accept(): Promise<HostSocket>;
23
+ close(): Promise<void>;
24
+ /** Actual bound port (useful when binding port 0 for ephemeral ports). */
25
+ readonly port: number;
26
+ }
27
+ /** A UDP socket on the host. */
28
+ export interface HostUdpSocket {
29
+ recv(): Promise<{
30
+ data: Uint8Array;
31
+ remoteAddr: {
32
+ host: string;
33
+ port: number;
34
+ };
35
+ }>;
36
+ close(): Promise<void>;
37
+ }
38
+ /** DNS lookup result. */
39
+ export interface DnsResult {
40
+ address: string;
41
+ family: 4 | 6;
42
+ }
43
+ /** Host adapter that the kernel delegates external network I/O to. */
44
+ export interface HostNetworkAdapter {
45
+ tcpConnect(host: string, port: number): Promise<HostSocket>;
46
+ tcpListen(host: string, port: number): Promise<HostListener>;
47
+ udpBind(host: string, port: number): Promise<HostUdpSocket>;
48
+ udpSend(socket: HostUdpSocket, data: Uint8Array, host: string, port: number): Promise<void>;
49
+ dnsLookup(hostname: string, rrtype: string): Promise<DnsResult>;
50
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Host adapter interfaces for kernel network delegation.
3
+ *
4
+ * The kernel uses these interfaces to delegate external I/O to the host
5
+ * without knowing the host implementation. Node.js driver implements
6
+ * using node:net / node:dgram; browser driver may use WebSocket proxy.
7
+ */
8
+ export {};