@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.
- package/dist/esm-compiler.d.ts +5 -1
- package/dist/esm-compiler.js +5 -1
- package/dist/fs-helpers.d.ts +1 -1
- package/dist/generated/isolate-runtime.d.ts +15 -15
- package/dist/generated/isolate-runtime.js +15 -15
- package/dist/index.d.ts +25 -6
- package/dist/index.js +23 -3
- package/dist/isolate-runtime/apply-custom-global-policy.js +3 -3
- package/dist/isolate-runtime/apply-timing-mitigation-freeze.js +10 -8
- package/dist/isolate-runtime/apply-timing-mitigation-off.js +2 -2
- package/dist/isolate-runtime/bridge-attach.js +2 -2
- package/dist/isolate-runtime/bridge-initial-globals.js +3 -3
- package/dist/isolate-runtime/eval-script-result.js +1 -1
- package/dist/isolate-runtime/global-exposure-helpers.js +2 -2
- package/dist/isolate-runtime/init-commonjs-module-globals.js +2 -2
- package/dist/isolate-runtime/override-process-cwd.js +1 -1
- package/dist/isolate-runtime/override-process-env.js +1 -1
- package/dist/isolate-runtime/require-setup.js +2236 -19
- package/dist/isolate-runtime/set-commonjs-file-globals.js +2 -2
- package/dist/isolate-runtime/set-stdin-data.js +1 -1
- package/dist/isolate-runtime/setup-dynamic-import.js +47 -15
- package/dist/isolate-runtime/setup-fs-facade.js +2 -2
- package/dist/kernel/command-registry.d.ts +44 -0
- package/dist/kernel/command-registry.js +114 -0
- package/dist/kernel/device-layer.d.ts +12 -0
- package/dist/kernel/device-layer.js +262 -0
- package/dist/kernel/dns-cache.d.ts +29 -0
- package/dist/kernel/dns-cache.js +52 -0
- package/dist/kernel/fd-table.d.ts +84 -0
- package/dist/kernel/fd-table.js +278 -0
- package/dist/kernel/file-lock.d.ts +34 -0
- package/dist/kernel/file-lock.js +123 -0
- package/dist/kernel/host-adapter.d.ts +50 -0
- package/dist/kernel/host-adapter.js +8 -0
- package/dist/kernel/index.d.ts +36 -0
- package/dist/kernel/index.js +34 -0
- package/dist/kernel/inode-table.d.ts +43 -0
- package/dist/kernel/inode-table.js +85 -0
- package/dist/kernel/kernel.d.ts +9 -0
- package/dist/kernel/kernel.js +1396 -0
- package/dist/kernel/permissions.d.ts +27 -0
- package/dist/kernel/permissions.js +118 -0
- package/dist/kernel/pipe-manager.d.ts +64 -0
- package/dist/kernel/pipe-manager.js +267 -0
- package/dist/kernel/proc-layer.d.ts +11 -0
- package/dist/kernel/proc-layer.js +501 -0
- package/dist/kernel/process-table.d.ts +124 -0
- package/dist/kernel/process-table.js +631 -0
- package/dist/kernel/pty.d.ts +108 -0
- package/dist/kernel/pty.js +541 -0
- package/dist/kernel/socket-table.d.ts +305 -0
- package/dist/kernel/socket-table.js +1124 -0
- package/dist/kernel/timer-table.d.ts +54 -0
- package/dist/kernel/timer-table.js +108 -0
- package/dist/kernel/types.d.ts +500 -0
- package/dist/kernel/types.js +89 -0
- package/dist/kernel/user.d.ts +29 -0
- package/dist/kernel/user.js +35 -0
- package/dist/kernel/vfs.d.ts +54 -0
- package/dist/kernel/vfs.js +8 -0
- package/dist/kernel/wait.d.ts +45 -0
- package/dist/kernel/wait.js +112 -0
- package/dist/kernel/wstatus.d.ts +21 -0
- package/dist/kernel/wstatus.js +33 -0
- package/dist/module-resolver.d.ts +4 -0
- package/dist/module-resolver.js +4 -0
- package/dist/package-bundler.d.ts +6 -1
- package/dist/runtime-driver.d.ts +3 -1
- package/dist/shared/bridge-contract.d.ts +529 -94
- package/dist/shared/bridge-contract.js +86 -3
- package/dist/shared/console-formatter.js +4 -0
- package/dist/shared/global-exposure.js +345 -0
- package/dist/shared/in-memory-fs.d.ts +30 -11
- package/dist/shared/in-memory-fs.js +383 -109
- package/dist/shared/permissions.d.ts +4 -6
- package/dist/shared/permissions.js +24 -28
- package/dist/types.d.ts +20 -130
- package/dist/types.js +5 -0
- package/package.json +12 -22
- package/dist/bridge/active-handles.d.ts +0 -22
- package/dist/bridge/active-handles.js +0 -55
- package/dist/bridge/child-process.d.ts +0 -99
- package/dist/bridge/child-process.js +0 -656
- package/dist/bridge/fs.d.ts +0 -281
- package/dist/bridge/fs.js +0 -2231
- package/dist/bridge/index.d.ts +0 -10
- package/dist/bridge/index.js +0 -41
- package/dist/bridge/module.d.ts +0 -75
- package/dist/bridge/module.js +0 -299
- package/dist/bridge/network.d.ts +0 -250
- package/dist/bridge/network.js +0 -1433
- package/dist/bridge/os.d.ts +0 -13
- package/dist/bridge/os.js +0 -256
- package/dist/bridge/polyfills.d.ts +0 -2
- package/dist/bridge/polyfills.js +0 -11
- package/dist/bridge/process.d.ts +0 -89
- package/dist/bridge/process.js +0 -994
- package/dist/bridge.js +0 -11766
- package/dist/python-runtime.d.ts +0 -16
- package/dist/python-runtime.js +0 -45
- package/dist/runtime.d.ts +0 -31
- package/dist/runtime.js +0 -69
|
@@ -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 {};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @secure-exec/kernel
|
|
3
|
+
*
|
|
4
|
+
* OS kernel providing VFS, FD table, process table, device layer,
|
|
5
|
+
* pipes, command registry, and permissions. All runtimes share the
|
|
6
|
+
* same kernel instance.
|
|
7
|
+
*/
|
|
8
|
+
export { createKernel } from "./kernel.js";
|
|
9
|
+
export type { Kernel, KernelOptions, KernelInterface, ExecOptions, ExecResult, SpawnOptions, ManagedProcess, RuntimeDriver, ProcessContext, DriverProcess, ProcessEntry, ProcessInfo, FDStat, FileDescription, FDEntry, Pipe, Permissions, PermissionDecision, PermissionCheck, FsAccessRequest, NetworkAccessRequest, ChildProcessAccessRequest, EnvAccessRequest, KernelErrorCode, SignalDisposition, SignalHandler, ProcessSignalState, Termios, TermiosCC, OpenShellOptions, ShellHandle, ConnectTerminalOptions, } from "./types.js";
|
|
10
|
+
export { KernelError, defaultTermios } from "./types.js";
|
|
11
|
+
export type { VirtualFileSystem, VirtualDirEntry, VirtualStat, } from "./vfs.js";
|
|
12
|
+
export { FDTableManager, ProcessFDTable } from "./fd-table.js";
|
|
13
|
+
export { ProcessTable } from "./process-table.js";
|
|
14
|
+
export { createDeviceLayer } from "./device-layer.js";
|
|
15
|
+
export { createProcLayer, createProcessScopedFileSystem, resolveProcSelfPath, } from "./proc-layer.js";
|
|
16
|
+
export { PipeManager } from "./pipe-manager.js";
|
|
17
|
+
export { PtyManager } from "./pty.js";
|
|
18
|
+
export type { LineDisciplineConfig } from "./pty.js";
|
|
19
|
+
export { CommandRegistry } from "./command-registry.js";
|
|
20
|
+
export { FileLockManager, LOCK_SH, LOCK_EX, LOCK_UN, LOCK_NB } from "./file-lock.js";
|
|
21
|
+
export { WaitHandle, WaitQueue } from "./wait.js";
|
|
22
|
+
export { InodeTable } from "./inode-table.js";
|
|
23
|
+
export type { Inode } from "./inode-table.js";
|
|
24
|
+
export { TimerTable } from "./timer-table.js";
|
|
25
|
+
export type { KernelTimer, TimerTableOptions } from "./timer-table.js";
|
|
26
|
+
export { DnsCache } from "./dns-cache.js";
|
|
27
|
+
export type { DnsCacheOptions } from "./dns-cache.js";
|
|
28
|
+
export { UserManager } from "./user.js";
|
|
29
|
+
export type { UserConfig } from "./user.js";
|
|
30
|
+
export { SocketTable } from "./socket-table.js";
|
|
31
|
+
export type { KernelSocket, SocketState, SockAddr, InetAddr, UnixAddr, UdpDatagram, } from "./socket-table.js";
|
|
32
|
+
export { AF_INET, AF_INET6, AF_UNIX, SOCK_STREAM, SOCK_DGRAM, SOL_SOCKET, IPPROTO_TCP, SO_REUSEADDR, SO_KEEPALIVE, SO_RCVBUF, SO_SNDBUF, TCP_NODELAY, MSG_PEEK, MSG_DONTWAIT, MSG_NOSIGNAL, MAX_DATAGRAM_SIZE, MAX_UDP_QUEUE_DEPTH, S_IFSOCK, isInetAddr, isUnixAddr, addrKey, optKey, } from "./socket-table.js";
|
|
33
|
+
export type { HostNetworkAdapter, HostSocket, HostListener, HostUdpSocket, DnsResult, } from "./host-adapter.js";
|
|
34
|
+
export { wrapFileSystem, filterEnv, checkChildProcess, allowAll, allowAllFs, allowAllNetwork, allowAllChildProcess, allowAllEnv, } from "./permissions.js";
|
|
35
|
+
export { O_RDONLY, O_WRONLY, O_RDWR, O_CREAT, O_EXCL, O_TRUNC, O_APPEND, O_CLOEXEC, F_DUPFD, F_GETFD, F_SETFD, F_GETFL, F_DUPFD_CLOEXEC, FD_CLOEXEC, SEEK_SET, SEEK_CUR, SEEK_END, FILETYPE_UNKNOWN, FILETYPE_CHARACTER_DEVICE, FILETYPE_DIRECTORY, FILETYPE_REGULAR_FILE, FILETYPE_SYMBOLIC_LINK, FILETYPE_PIPE, SIGHUP, SIGINT, SIGQUIT, SIGKILL, SIGPIPE, SIGALRM, SIGTERM, SIGCHLD, SIGCONT, SIGSTOP, SIGTSTP, SIGWINCH, SA_RESTART, SA_RESETHAND, SA_NOCLDSTOP, SIG_BLOCK, SIG_UNBLOCK, SIG_SETMASK, WNOHANG, } from "./types.js";
|
|
36
|
+
export { encodeExitStatus, encodeSignalStatus, WIFEXITED, WEXITSTATUS, WIFSIGNALED, WTERMSIG, } from "./wstatus.js";
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @secure-exec/kernel
|
|
3
|
+
*
|
|
4
|
+
* OS kernel providing VFS, FD table, process table, device layer,
|
|
5
|
+
* pipes, command registry, and permissions. All runtimes share the
|
|
6
|
+
* same kernel instance.
|
|
7
|
+
*/
|
|
8
|
+
// Kernel factory
|
|
9
|
+
export { createKernel } from "./kernel.js";
|
|
10
|
+
// Structured kernel error and termios defaults
|
|
11
|
+
export { KernelError, defaultTermios } from "./types.js";
|
|
12
|
+
// Kernel components (for direct use / testing)
|
|
13
|
+
export { FDTableManager, ProcessFDTable } from "./fd-table.js";
|
|
14
|
+
export { ProcessTable } from "./process-table.js";
|
|
15
|
+
export { createDeviceLayer } from "./device-layer.js";
|
|
16
|
+
export { createProcLayer, createProcessScopedFileSystem, resolveProcSelfPath, } from "./proc-layer.js";
|
|
17
|
+
export { PipeManager } from "./pipe-manager.js";
|
|
18
|
+
export { PtyManager } from "./pty.js";
|
|
19
|
+
export { CommandRegistry } from "./command-registry.js";
|
|
20
|
+
export { FileLockManager, LOCK_SH, LOCK_EX, LOCK_UN, LOCK_NB } from "./file-lock.js";
|
|
21
|
+
export { WaitHandle, WaitQueue } from "./wait.js";
|
|
22
|
+
export { InodeTable } from "./inode-table.js";
|
|
23
|
+
export { TimerTable } from "./timer-table.js";
|
|
24
|
+
export { DnsCache } from "./dns-cache.js";
|
|
25
|
+
export { UserManager } from "./user.js";
|
|
26
|
+
// Socket table
|
|
27
|
+
export { SocketTable } from "./socket-table.js";
|
|
28
|
+
export { AF_INET, AF_INET6, AF_UNIX, SOCK_STREAM, SOCK_DGRAM, SOL_SOCKET, IPPROTO_TCP, SO_REUSEADDR, SO_KEEPALIVE, SO_RCVBUF, SO_SNDBUF, TCP_NODELAY, MSG_PEEK, MSG_DONTWAIT, MSG_NOSIGNAL, MAX_DATAGRAM_SIZE, MAX_UDP_QUEUE_DEPTH, S_IFSOCK, isInetAddr, isUnixAddr, addrKey, optKey, } from "./socket-table.js";
|
|
29
|
+
// Permissions
|
|
30
|
+
export { wrapFileSystem, filterEnv, checkChildProcess, allowAll, allowAllFs, allowAllNetwork, allowAllChildProcess, allowAllEnv, } from "./permissions.js";
|
|
31
|
+
// Constants
|
|
32
|
+
export { O_RDONLY, O_WRONLY, O_RDWR, O_CREAT, O_EXCL, O_TRUNC, O_APPEND, O_CLOEXEC, F_DUPFD, F_GETFD, F_SETFD, F_GETFL, F_DUPFD_CLOEXEC, FD_CLOEXEC, SEEK_SET, SEEK_CUR, SEEK_END, FILETYPE_UNKNOWN, FILETYPE_CHARACTER_DEVICE, FILETYPE_DIRECTORY, FILETYPE_REGULAR_FILE, FILETYPE_SYMBOLIC_LINK, FILETYPE_PIPE, SIGHUP, SIGINT, SIGQUIT, SIGKILL, SIGPIPE, SIGALRM, SIGTERM, SIGCHLD, SIGCONT, SIGSTOP, SIGTSTP, SIGWINCH, SA_RESTART, SA_RESETHAND, SA_NOCLDSTOP, SIG_BLOCK, SIG_UNBLOCK, SIG_SETMASK, WNOHANG, } from "./types.js";
|
|
33
|
+
// POSIX wstatus encoding/decoding
|
|
34
|
+
export { encodeExitStatus, encodeSignalStatus, WIFEXITED, WEXITSTATUS, WIFSIGNALED, WTERMSIG, } from "./wstatus.js";
|