@secure-exec/core 0.2.1 → 0.3.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 (248) hide show
  1. package/README.md +5 -5
  2. package/dist/binary.d.ts +4 -0
  3. package/dist/binary.js +25 -0
  4. package/dist/bytes.d.ts +2 -0
  5. package/dist/bytes.js +6 -0
  6. package/dist/callbacks.d.ts +41 -0
  7. package/dist/callbacks.js +94 -0
  8. package/dist/cargo.d.ts +2 -0
  9. package/dist/cargo.js +142 -0
  10. package/dist/correlation.d.ts +10 -0
  11. package/dist/correlation.js +49 -0
  12. package/dist/descriptors.d.ts +34 -0
  13. package/dist/descriptors.js +37 -0
  14. package/dist/event-buffer.d.ts +90 -0
  15. package/dist/event-buffer.js +313 -0
  16. package/dist/ext.d.ts +7 -0
  17. package/dist/ext.js +13 -0
  18. package/dist/filesystem.d.ts +41 -0
  19. package/dist/filesystem.js +70 -0
  20. package/dist/frame-payload-codec.d.ts +8 -0
  21. package/dist/frame-payload-codec.js +14 -0
  22. package/dist/frame-rpc.d.ts +38 -0
  23. package/dist/frame-rpc.js +73 -0
  24. package/dist/frame-stream.d.ts +27 -0
  25. package/dist/frame-stream.js +99 -0
  26. package/dist/framing.d.ts +7 -0
  27. package/dist/framing.js +22 -0
  28. package/dist/generated/AcpLimitsConfig.d.ts +4 -0
  29. package/dist/generated/AcpLimitsConfig.js +2 -0
  30. package/dist/generated/CreateVmConfig.d.ts +19 -0
  31. package/dist/generated/FsPermissionRule.d.ts +6 -0
  32. package/dist/generated/FsPermissionRuleSet.d.ts +6 -0
  33. package/dist/generated/FsPermissionRuleSet.js +1 -0
  34. package/dist/generated/FsPermissionScope.d.ts +3 -0
  35. package/dist/generated/FsPermissionScope.js +1 -0
  36. package/dist/generated/HttpLimitsConfig.d.ts +3 -0
  37. package/dist/generated/HttpLimitsConfig.js +2 -0
  38. package/dist/generated/JsModuleResolution.d.ts +1 -0
  39. package/dist/generated/JsModuleResolution.js +2 -0
  40. package/dist/generated/JsRuntimeConfig.d.ts +26 -0
  41. package/dist/generated/JsRuntimeConfig.js +1 -0
  42. package/dist/generated/JsRuntimeLimitsConfig.d.ts +7 -0
  43. package/dist/generated/JsRuntimeLimitsConfig.js +2 -0
  44. package/dist/generated/JsRuntimePlatform.d.ts +1 -0
  45. package/dist/generated/JsRuntimePlatform.js +2 -0
  46. package/dist/generated/MountPluginDescriptor.d.ts +4 -0
  47. package/dist/generated/MountPluginDescriptor.js +2 -0
  48. package/dist/generated/NativeRootFilesystemConfig.d.ts +5 -0
  49. package/dist/generated/NativeRootFilesystemConfig.js +1 -0
  50. package/dist/generated/PatternPermissionRule.d.ts +6 -0
  51. package/dist/generated/PatternPermissionRule.js +1 -0
  52. package/dist/generated/PatternPermissionRuleSet.d.ts +6 -0
  53. package/dist/generated/PatternPermissionRuleSet.js +1 -0
  54. package/dist/generated/PatternPermissionScope.d.ts +3 -0
  55. package/dist/generated/PatternPermissionScope.js +1 -0
  56. package/dist/generated/PermissionMode.d.ts +1 -0
  57. package/dist/generated/PermissionMode.js +2 -0
  58. package/dist/generated/PermissionsPolicy.d.ts +10 -0
  59. package/dist/generated/PermissionsPolicy.js +1 -0
  60. package/dist/generated/PluginLimitsConfig.d.ts +4 -0
  61. package/dist/generated/PluginLimitsConfig.js +2 -0
  62. package/dist/generated/PythonLimitsConfig.d.ts +5 -0
  63. package/dist/generated/PythonLimitsConfig.js +2 -0
  64. package/dist/generated/ResourceLimitsConfig.d.ts +22 -0
  65. package/dist/generated/ResourceLimitsConfig.js +2 -0
  66. package/dist/generated/RootFilesystemConfig.d.ts +9 -0
  67. package/dist/generated/RootFilesystemConfig.js +1 -0
  68. package/dist/generated/RootFilesystemEntry.d.ts +13 -0
  69. package/dist/generated/RootFilesystemEntry.js +1 -0
  70. package/dist/generated/RootFilesystemEntryEncoding.d.ts +1 -0
  71. package/dist/generated/RootFilesystemEntryEncoding.js +2 -0
  72. package/dist/generated/RootFilesystemEntryKind.d.ts +1 -0
  73. package/dist/generated/RootFilesystemEntryKind.js +2 -0
  74. package/dist/generated/RootFilesystemLowerDescriptor.d.ts +7 -0
  75. package/dist/generated/RootFilesystemLowerDescriptor.js +1 -0
  76. package/dist/generated/RootFilesystemMode.d.ts +1 -0
  77. package/dist/generated/RootFilesystemMode.js +2 -0
  78. package/dist/generated/ToolLimitsConfig.d.ts +10 -0
  79. package/dist/generated/ToolLimitsConfig.js +2 -0
  80. package/dist/generated/VmDnsConfig.d.ts +6 -0
  81. package/dist/generated/VmDnsConfig.js +2 -0
  82. package/dist/generated/VmLimitsConfig.d.ts +18 -0
  83. package/dist/generated/VmLimitsConfig.js +1 -0
  84. package/dist/generated/VmListenPolicyConfig.d.ts +5 -0
  85. package/dist/generated/VmListenPolicyConfig.js +2 -0
  86. package/dist/generated/WasmLimitsConfig.d.ts +5 -0
  87. package/dist/generated/WasmLimitsConfig.js +2 -0
  88. package/dist/generated-protocol.d.ts +1037 -0
  89. package/dist/generated-protocol.js +2887 -0
  90. package/dist/index.d.ts +24 -62
  91. package/dist/index.js +24 -53
  92. package/dist/json.d.ts +2 -0
  93. package/dist/json.js +20 -0
  94. package/dist/kernel-proxy.d.ts +149 -0
  95. package/dist/kernel-proxy.js +1733 -0
  96. package/dist/native-client.d.ts +41 -0
  97. package/dist/native-client.js +124 -0
  98. package/dist/node-runtime.d.ts +443 -0
  99. package/dist/node-runtime.js +569 -0
  100. package/dist/numbers.d.ts +1 -0
  101. package/dist/numbers.js +8 -0
  102. package/dist/ownership.d.ts +18 -0
  103. package/dist/ownership.js +77 -0
  104. package/dist/permissions.d.ts +29 -0
  105. package/dist/permissions.js +68 -0
  106. package/dist/process.d.ts +35 -0
  107. package/dist/process.js +125 -0
  108. package/dist/protocol-client.d.ts +46 -0
  109. package/dist/protocol-client.js +180 -0
  110. package/dist/protocol-frames.d.ts +68 -0
  111. package/dist/protocol-frames.js +139 -0
  112. package/dist/protocol-maps.d.ts +28 -0
  113. package/dist/protocol-maps.js +217 -0
  114. package/dist/protocol-schema.d.ts +10 -0
  115. package/dist/protocol-schema.js +11 -0
  116. package/dist/request-payloads.d.ts +137 -0
  117. package/dist/request-payloads.js +210 -0
  118. package/dist/response-payloads.d.ts +107 -0
  119. package/dist/response-payloads.js +161 -0
  120. package/dist/sidecar-client.d.ts +242 -0
  121. package/dist/sidecar-client.js +797 -0
  122. package/dist/state.d.ts +40 -0
  123. package/dist/state.js +44 -0
  124. package/dist/test-runtime.d.ts +526 -0
  125. package/dist/test-runtime.js +2119 -0
  126. package/dist/vm-config.d.ts +31 -0
  127. package/dist/vm-config.js +1 -0
  128. package/fixtures/alpine-defaults.json +520 -0
  129. package/fixtures/base-filesystem.json +528 -0
  130. package/package.json +193 -115
  131. package/LICENSE +0 -191
  132. package/dist/bridge-setup.d.ts +0 -6
  133. package/dist/bridge-setup.js +0 -9
  134. package/dist/esm-compiler.d.ts +0 -18
  135. package/dist/esm-compiler.js +0 -72
  136. package/dist/fs-helpers.d.ts +0 -23
  137. package/dist/fs-helpers.js +0 -41
  138. package/dist/generated/isolate-runtime.d.ts +0 -19
  139. package/dist/generated/isolate-runtime.js +0 -21
  140. package/dist/generated/polyfills.d.ts +0 -82
  141. package/dist/generated/polyfills.js +0 -82
  142. package/dist/isolate-runtime/apply-custom-global-policy.js +0 -53
  143. package/dist/isolate-runtime/apply-timing-mitigation-freeze.js +0 -130
  144. package/dist/isolate-runtime/apply-timing-mitigation-off.js +0 -14
  145. package/dist/isolate-runtime/bridge-attach.js +0 -29
  146. package/dist/isolate-runtime/bridge-initial-globals.js +0 -385
  147. package/dist/isolate-runtime/eval-script-result.js +0 -8
  148. package/dist/isolate-runtime/global-exposure-helpers.js +0 -36
  149. package/dist/isolate-runtime/init-commonjs-module-globals.js +0 -28
  150. package/dist/isolate-runtime/override-process-cwd.js +0 -8
  151. package/dist/isolate-runtime/override-process-env.js +0 -8
  152. package/dist/isolate-runtime/require-setup.js +0 -4153
  153. package/dist/isolate-runtime/set-commonjs-file-globals.js +0 -36
  154. package/dist/isolate-runtime/set-stdin-data.js +0 -10
  155. package/dist/isolate-runtime/setup-dynamic-import.js +0 -123
  156. package/dist/isolate-runtime/setup-fs-facade.js +0 -87
  157. package/dist/kernel/command-registry.d.ts +0 -44
  158. package/dist/kernel/command-registry.js +0 -114
  159. package/dist/kernel/device-backend.d.ts +0 -14
  160. package/dist/kernel/device-backend.js +0 -251
  161. package/dist/kernel/device-layer.d.ts +0 -12
  162. package/dist/kernel/device-layer.js +0 -271
  163. package/dist/kernel/dns-cache.d.ts +0 -29
  164. package/dist/kernel/dns-cache.js +0 -52
  165. package/dist/kernel/fd-table.d.ts +0 -84
  166. package/dist/kernel/fd-table.js +0 -278
  167. package/dist/kernel/file-lock.d.ts +0 -34
  168. package/dist/kernel/file-lock.js +0 -122
  169. package/dist/kernel/host-adapter.d.ts +0 -50
  170. package/dist/kernel/host-adapter.js +0 -8
  171. package/dist/kernel/index.d.ts +0 -36
  172. package/dist/kernel/index.js +0 -34
  173. package/dist/kernel/kernel.d.ts +0 -9
  174. package/dist/kernel/kernel.js +0 -1415
  175. package/dist/kernel/mount-table.d.ts +0 -75
  176. package/dist/kernel/mount-table.js +0 -353
  177. package/dist/kernel/permissions.d.ts +0 -36
  178. package/dist/kernel/permissions.js +0 -150
  179. package/dist/kernel/pipe-manager.d.ts +0 -64
  180. package/dist/kernel/pipe-manager.js +0 -267
  181. package/dist/kernel/proc-backend.d.ts +0 -30
  182. package/dist/kernel/proc-backend.js +0 -428
  183. package/dist/kernel/proc-layer.d.ts +0 -11
  184. package/dist/kernel/proc-layer.js +0 -507
  185. package/dist/kernel/process-table.d.ts +0 -126
  186. package/dist/kernel/process-table.js +0 -651
  187. package/dist/kernel/pty.d.ts +0 -109
  188. package/dist/kernel/pty.js +0 -552
  189. package/dist/kernel/socket-table.d.ts +0 -312
  190. package/dist/kernel/socket-table.js +0 -1188
  191. package/dist/kernel/timer-table.d.ts +0 -54
  192. package/dist/kernel/timer-table.js +0 -108
  193. package/dist/kernel/types.d.ts +0 -541
  194. package/dist/kernel/types.js +0 -98
  195. package/dist/kernel/user.d.ts +0 -29
  196. package/dist/kernel/user.js +0 -35
  197. package/dist/kernel/vfs.d.ts +0 -82
  198. package/dist/kernel/vfs.js +0 -25
  199. package/dist/kernel/wait.d.ts +0 -45
  200. package/dist/kernel/wait.js +0 -112
  201. package/dist/kernel/wstatus.d.ts +0 -21
  202. package/dist/kernel/wstatus.js +0 -33
  203. package/dist/module-resolver.d.ts +0 -29
  204. package/dist/module-resolver.js +0 -314
  205. package/dist/package-bundler.d.ts +0 -41
  206. package/dist/package-bundler.js +0 -497
  207. package/dist/runtime-driver.d.ts +0 -66
  208. package/dist/shared/api-types.d.ts +0 -83
  209. package/dist/shared/bridge-contract.d.ts +0 -772
  210. package/dist/shared/bridge-contract.js +0 -169
  211. package/dist/shared/console-formatter.d.ts +0 -22
  212. package/dist/shared/console-formatter.js +0 -161
  213. package/dist/shared/constants.d.ts +0 -3
  214. package/dist/shared/constants.js +0 -3
  215. package/dist/shared/errors.d.ts +0 -16
  216. package/dist/shared/errors.js +0 -21
  217. package/dist/shared/esm-utils.d.ts +0 -28
  218. package/dist/shared/esm-utils.js +0 -97
  219. package/dist/shared/global-exposure.d.ts +0 -38
  220. package/dist/shared/global-exposure.js +0 -876
  221. package/dist/shared/in-memory-fs.d.ts +0 -16
  222. package/dist/shared/in-memory-fs.js +0 -115
  223. package/dist/shared/permissions.d.ts +0 -36
  224. package/dist/shared/permissions.js +0 -314
  225. package/dist/shared/require-setup.d.ts +0 -6
  226. package/dist/shared/require-setup.js +0 -9
  227. package/dist/test/block-store-conformance.d.ts +0 -34
  228. package/dist/test/block-store-conformance.js +0 -251
  229. package/dist/test/metadata-store-conformance.d.ts +0 -37
  230. package/dist/test/metadata-store-conformance.js +0 -646
  231. package/dist/test/vfs-conformance.d.ts +0 -65
  232. package/dist/test/vfs-conformance.js +0 -842
  233. package/dist/types.d.ts +0 -98
  234. package/dist/types.js +0 -6
  235. package/dist/vfs/chunked-vfs.d.ts +0 -66
  236. package/dist/vfs/chunked-vfs.js +0 -1290
  237. package/dist/vfs/host-block-store.d.ts +0 -19
  238. package/dist/vfs/host-block-store.js +0 -97
  239. package/dist/vfs/memory-block-store.d.ts +0 -16
  240. package/dist/vfs/memory-block-store.js +0 -45
  241. package/dist/vfs/memory-metadata.d.ts +0 -75
  242. package/dist/vfs/memory-metadata.js +0 -528
  243. package/dist/vfs/sqlite-metadata.d.ts +0 -91
  244. package/dist/vfs/sqlite-metadata.js +0 -582
  245. package/dist/vfs/types.d.ts +0 -210
  246. package/dist/vfs/types.js +0 -8
  247. /package/dist/{runtime-driver.js → generated/CreateVmConfig.js} +0 -0
  248. /package/dist/{shared/api-types.js → generated/FsPermissionRule.js} +0 -0
@@ -1,109 +0,0 @@
1
- /**
2
- * PTY manager.
3
- *
4
- * Allocates pseudo-terminal master/slave pairs with bidirectional data flow.
5
- * Writing to master → readable from slave (input direction).
6
- * Writing to slave → readable from master (output direction).
7
- * Follows the same FileDescription/refCount pattern as PipeManager.
8
- */
9
- import type { FileDescription, Termios, KernelLogger } from "./types.js";
10
- import { FILETYPE_CHARACTER_DEVICE } from "./types.js";
11
- import type { ProcessFDTable } from "./fd-table.js";
12
- export interface LineDisciplineConfig {
13
- /** Canonical mode: buffer input until newline, handle backspace. */
14
- canonical: boolean;
15
- /** Echo input bytes back through output (master reads them). */
16
- echo: boolean;
17
- /** Enable signal generation from control chars (^C, ^Z, ^\). */
18
- isig: boolean;
19
- }
20
- export interface PtyEnd {
21
- description: FileDescription;
22
- filetype: typeof FILETYPE_CHARACTER_DEVICE;
23
- }
24
- /** Maximum buffered bytes per PTY direction before writes are rejected (EAGAIN). */
25
- export declare const MAX_PTY_BUFFER_BYTES = 65536;
26
- /** Maximum canonical-mode line buffer size (POSIX MAX_CANON). */
27
- export declare const MAX_CANON = 4096;
28
- export declare class PtyManager {
29
- private ptys;
30
- /** Map description ID → pty ID and which end */
31
- private descToPty;
32
- /**
33
- * Signal delivery callback: (pgid, signal, excludeLeaders) → number of
34
- * processes signaled. When excludeLeaders is true, session leaders are
35
- * skipped (WasmVM workers can't handle graceful signals).
36
- */
37
- private onSignal;
38
- private nextPtyId;
39
- private nextPtyDescId;
40
- private log;
41
- constructor(onSignal?: (pgid: number, signal: number, excludeLeaders: boolean) => number, logger?: KernelLogger);
42
- /**
43
- * Allocate a PTY pair. Returns two FileDescriptions:
44
- * one for the master and one for the slave.
45
- */
46
- createPty(): {
47
- master: PtyEnd;
48
- slave: PtyEnd;
49
- path: string;
50
- };
51
- /**
52
- * Write data to a PTY end.
53
- * Master write → slave can read (input direction).
54
- * Slave write → master can read (output direction).
55
- */
56
- write(descriptionId: number, data: Uint8Array): number;
57
- /**
58
- * Read data from a PTY end.
59
- * Master read → data written by slave (output direction).
60
- * Slave read → data written by master (input direction).
61
- * Returns null on hangup (other end closed).
62
- */
63
- read(descriptionId: number, length: number): Promise<Uint8Array | null>;
64
- /** Close one end of a PTY. */
65
- close(descriptionId: number): void;
66
- /** Check if a description ID belongs to a PTY. */
67
- isPty(descriptionId: number): boolean;
68
- /** Check if a description ID is a PTY slave (terminal). */
69
- isSlave(descriptionId: number): boolean;
70
- /**
71
- * Allocate PTY FDs in the given FD table.
72
- * Returns master/slave FD numbers and the /dev/pts/N path.
73
- */
74
- createPtyFDs(fdTable: ProcessFDTable): {
75
- masterFd: number;
76
- slaveFd: number;
77
- path: string;
78
- };
79
- /** Set line discipline options for the PTY containing this description. */
80
- setDiscipline(descriptionId: number, config: Partial<LineDisciplineConfig>): void;
81
- /** Set the foreground process group for signal delivery on this PTY. */
82
- setForegroundPgid(descriptionId: number, pgid: number): void;
83
- /** Set the session leader pgid for SIGINT interception on this PTY. */
84
- setSessionLeader(descriptionId: number, pgid: number): void;
85
- /** Get terminal attributes for the PTY containing this description. */
86
- getTermios(descriptionId: number): Termios;
87
- /** Set terminal attributes for the PTY containing this description. */
88
- setTermios(descriptionId: number, termios: Partial<Termios>): void;
89
- /** Get the foreground process group for the PTY containing this description. */
90
- getForegroundPgid(descriptionId: number): number;
91
- /** Get the PTY ID from a description ID. */
92
- private getPtyId;
93
- /** Convert lone \n to \r\n in output data (POSIX ONLCR). Skipped when opost/onlcr disabled. */
94
- private processOutput;
95
- /**
96
- * Process input data through line discipline.
97
- * Master writes go through here before reaching the slave's input buffer.
98
- */
99
- private processInput;
100
- private applyInputTranslations;
101
- /** Deliver input data to slave (input buffer / waiters). */
102
- private deliverInput;
103
- /** Echo data to output (master reads it back for display). Throws EAGAIN when buffer is full. */
104
- private echoOutput;
105
- /** Map control byte to signal number using termios cc, or null if not a signal char. */
106
- private signalForByte;
107
- private bufferBytes;
108
- private drainBuffer;
109
- }
@@ -1,552 +0,0 @@
1
- /**
2
- * PTY manager.
3
- *
4
- * Allocates pseudo-terminal master/slave pairs with bidirectional data flow.
5
- * Writing to master → readable from slave (input direction).
6
- * Writing to slave → readable from master (output direction).
7
- * Follows the same FileDescription/refCount pattern as PipeManager.
8
- */
9
- import { FILETYPE_CHARACTER_DEVICE, O_RDWR, KernelError, defaultTermios, noopKernelLogger, } from "./types.js";
10
- /** Maximum buffered bytes per PTY direction before writes are rejected (EAGAIN). */
11
- export const MAX_PTY_BUFFER_BYTES = 65_536; // 64 KB
12
- /** Maximum canonical-mode line buffer size (POSIX MAX_CANON). */
13
- export const MAX_CANON = 4096;
14
- export class PtyManager {
15
- ptys = new Map();
16
- /** Map description ID → pty ID and which end */
17
- descToPty = new Map();
18
- /**
19
- * Signal delivery callback: (pgid, signal, excludeLeaders) → number of
20
- * processes signaled. When excludeLeaders is true, session leaders are
21
- * skipped (WasmVM workers can't handle graceful signals).
22
- */
23
- onSignal;
24
- nextPtyId = 0;
25
- nextPtyDescId = 200_000; // High range to avoid FD/pipe ID collisions
26
- log;
27
- constructor(onSignal, logger) {
28
- this.onSignal = onSignal ?? null;
29
- this.log = logger ?? noopKernelLogger;
30
- }
31
- /**
32
- * Allocate a PTY pair. Returns two FileDescriptions:
33
- * one for the master and one for the slave.
34
- */
35
- createPty() {
36
- const id = this.nextPtyId++;
37
- const path = `/dev/pts/${id}`;
38
- const masterDesc = {
39
- id: this.nextPtyDescId++,
40
- path: `pty:${id}:master`,
41
- cursor: 0n,
42
- flags: O_RDWR,
43
- refCount: 0, // openWith() will bump
44
- };
45
- const slaveDesc = {
46
- id: this.nextPtyDescId++,
47
- path: path,
48
- cursor: 0n,
49
- flags: O_RDWR,
50
- refCount: 0, // openWith() will bump
51
- };
52
- const state = {
53
- id,
54
- path,
55
- masterDescription: masterDesc,
56
- slaveDescription: slaveDesc,
57
- inputBuffer: [],
58
- outputBuffer: [],
59
- closed: { master: false, slave: false },
60
- inputWaiters: [],
61
- outputWaiters: [],
62
- termios: defaultTermios(),
63
- lineBuffer: [],
64
- foregroundPgid: 0,
65
- sessionLeaderPgid: 0,
66
- };
67
- this.ptys.set(id, state);
68
- this.descToPty.set(masterDesc.id, { ptyId: id, end: "master" });
69
- this.descToPty.set(slaveDesc.id, { ptyId: id, end: "slave" });
70
- this.log.debug({ ptyId: id, path, masterDescId: masterDesc.id, slaveDescId: slaveDesc.id }, "PTY created");
71
- return {
72
- master: { description: masterDesc, filetype: FILETYPE_CHARACTER_DEVICE },
73
- slave: { description: slaveDesc, filetype: FILETYPE_CHARACTER_DEVICE },
74
- path,
75
- };
76
- }
77
- /**
78
- * Write data to a PTY end.
79
- * Master write → slave can read (input direction).
80
- * Slave write → master can read (output direction).
81
- */
82
- write(descriptionId, data) {
83
- const ref = this.descToPty.get(descriptionId);
84
- if (!ref)
85
- throw new KernelError("EBADF", "not a PTY end");
86
- const state = this.ptys.get(ref.ptyId);
87
- if (!state)
88
- throw new KernelError("EBADF", "PTY not found");
89
- if (ref.end === "master") {
90
- // Master write → input direction, processed through line discipline
91
- if (state.closed.master)
92
- throw new KernelError("EIO", "master closed");
93
- if (state.closed.slave)
94
- throw new KernelError("EIO", "slave closed");
95
- return this.processInput(state, data);
96
- }
97
- else {
98
- // Slave write → output buffer (master reads)
99
- // ONLCR: convert \n to \r\n (standard POSIX terminal output processing)
100
- if (state.closed.slave)
101
- throw new KernelError("EIO", "slave closed");
102
- if (state.closed.master)
103
- throw new KernelError("EIO", "master closed");
104
- const processed = this.processOutput(state, data);
105
- if (state.outputWaiters.length > 0) {
106
- const waiter = state.outputWaiters.shift();
107
- waiter(processed);
108
- }
109
- else {
110
- // Enforce buffer limit to prevent unbounded memory growth
111
- if (this.bufferBytes(state.outputBuffer) + processed.length > MAX_PTY_BUFFER_BYTES) {
112
- throw new KernelError("EAGAIN", "PTY output buffer full");
113
- }
114
- state.outputBuffer.push(new Uint8Array(processed));
115
- }
116
- }
117
- return data.length;
118
- }
119
- /**
120
- * Read data from a PTY end.
121
- * Master read → data written by slave (output direction).
122
- * Slave read → data written by master (input direction).
123
- * Returns null on hangup (other end closed).
124
- */
125
- read(descriptionId, length) {
126
- const ref = this.descToPty.get(descriptionId);
127
- if (!ref)
128
- throw new KernelError("EBADF", "not a PTY end");
129
- const state = this.ptys.get(ref.ptyId);
130
- if (!state)
131
- throw new KernelError("EBADF", "PTY not found");
132
- if (ref.end === "master") {
133
- // Master reads from output buffer (data written by slave)
134
- if (state.closed.master)
135
- throw new KernelError("EIO", "master closed");
136
- if (state.outputBuffer.length > 0) {
137
- return Promise.resolve(this.drainBuffer(state.outputBuffer, length));
138
- }
139
- // Slave closed → EIO (terminal hangup)
140
- if (state.closed.slave) {
141
- return Promise.resolve(null);
142
- }
143
- return new Promise((resolve) => {
144
- state.outputWaiters.push(resolve);
145
- });
146
- }
147
- else {
148
- // Slave reads from input buffer (data written by master)
149
- if (state.closed.slave)
150
- throw new KernelError("EIO", "slave closed");
151
- if (state.inputBuffer.length > 0) {
152
- return Promise.resolve(this.drainBuffer(state.inputBuffer, length));
153
- }
154
- // Master closed → EIO (terminal hangup)
155
- if (state.closed.master) {
156
- return Promise.resolve(null);
157
- }
158
- return new Promise((resolve) => {
159
- state.inputWaiters.push(resolve);
160
- });
161
- }
162
- }
163
- /** Close one end of a PTY. */
164
- close(descriptionId) {
165
- const ref = this.descToPty.get(descriptionId);
166
- if (!ref)
167
- return;
168
- const state = this.ptys.get(ref.ptyId);
169
- if (!state)
170
- return;
171
- if (ref.end === "master") {
172
- state.closed.master = true;
173
- this.log.debug({ ptyId: ref.ptyId, fgPgid: state.foregroundPgid }, "PTY master closed");
174
- // SIGHUP: when master closes, send SIGHUP to foreground process group
175
- if (state.foregroundPgid > 0 && this.onSignal) {
176
- this.log.debug({ ptyId: ref.ptyId, pgid: state.foregroundPgid, signal: 1 }, "PTY SIGHUP delivery");
177
- try {
178
- this.onSignal(state.foregroundPgid, 1 /* SIGHUP */, false);
179
- }
180
- catch {
181
- // Signal delivery failure must not break PTY cleanup
182
- }
183
- }
184
- // Notify blocked slave readers with null (EIO / hangup)
185
- for (const waiter of state.inputWaiters) {
186
- waiter(null);
187
- }
188
- state.inputWaiters.length = 0;
189
- // Resolve any pending master reads (same-end close → EOF)
190
- for (const waiter of state.outputWaiters) {
191
- waiter(null);
192
- }
193
- state.outputWaiters.length = 0;
194
- }
195
- else {
196
- state.closed.slave = true;
197
- // Notify blocked master readers with null (EIO / hangup)
198
- for (const waiter of state.outputWaiters) {
199
- waiter(null);
200
- }
201
- state.outputWaiters.length = 0;
202
- // Resolve any pending slave reads (same-end close → EOF)
203
- for (const waiter of state.inputWaiters) {
204
- waiter(null);
205
- }
206
- state.inputWaiters.length = 0;
207
- }
208
- this.descToPty.delete(descriptionId);
209
- // Clean up when both ends closed
210
- if (state.closed.master && state.closed.slave) {
211
- this.ptys.delete(ref.ptyId);
212
- }
213
- }
214
- /** Check if a description ID belongs to a PTY. */
215
- isPty(descriptionId) {
216
- return this.descToPty.has(descriptionId);
217
- }
218
- /** Check if a description ID is a PTY slave (terminal). */
219
- isSlave(descriptionId) {
220
- const ref = this.descToPty.get(descriptionId);
221
- return ref?.end === "slave";
222
- }
223
- /**
224
- * Allocate PTY FDs in the given FD table.
225
- * Returns master/slave FD numbers and the /dev/pts/N path.
226
- */
227
- createPtyFDs(fdTable) {
228
- const { master, slave, path } = this.createPty();
229
- const masterFd = fdTable.openWith(master.description, master.filetype);
230
- const slaveFd = fdTable.openWith(slave.description, slave.filetype);
231
- return { masterFd, slaveFd, path };
232
- }
233
- /** Set line discipline options for the PTY containing this description. */
234
- setDiscipline(descriptionId, config) {
235
- const ptyId = this.getPtyId(descriptionId);
236
- const state = this.ptys.get(ptyId);
237
- if (!state)
238
- throw new KernelError("EBADF", "PTY not found");
239
- if (config.canonical !== undefined)
240
- state.termios.icanon = config.canonical;
241
- if (config.echo !== undefined)
242
- state.termios.echo = config.echo;
243
- if (config.isig !== undefined)
244
- state.termios.isig = config.isig;
245
- }
246
- /** Set the foreground process group for signal delivery on this PTY. */
247
- setForegroundPgid(descriptionId, pgid) {
248
- const ptyId = this.getPtyId(descriptionId);
249
- const state = this.ptys.get(ptyId);
250
- if (!state)
251
- throw new KernelError("EBADF", "PTY not found");
252
- this.log.trace({ ptyId, pgid, prev: state.foregroundPgid }, "PTY set foreground pgid");
253
- state.foregroundPgid = pgid;
254
- }
255
- /** Set the session leader pgid for SIGINT interception on this PTY. */
256
- setSessionLeader(descriptionId, pgid) {
257
- const ptyId = this.getPtyId(descriptionId);
258
- const state = this.ptys.get(ptyId);
259
- if (!state)
260
- throw new KernelError("EBADF", "PTY not found");
261
- this.log.trace({ ptyId, pgid }, "PTY set session leader");
262
- state.sessionLeaderPgid = pgid;
263
- }
264
- /** Get terminal attributes for the PTY containing this description. */
265
- getTermios(descriptionId) {
266
- const ptyId = this.getPtyId(descriptionId);
267
- const state = this.ptys.get(ptyId);
268
- if (!state)
269
- throw new KernelError("EBADF", "PTY not found");
270
- return {
271
- icrnl: state.termios.icrnl,
272
- opost: state.termios.opost,
273
- onlcr: state.termios.onlcr,
274
- icanon: state.termios.icanon,
275
- echo: state.termios.echo,
276
- isig: state.termios.isig,
277
- cc: { ...state.termios.cc },
278
- };
279
- }
280
- /** Set terminal attributes for the PTY containing this description. */
281
- setTermios(descriptionId, termios) {
282
- const ptyId = this.getPtyId(descriptionId);
283
- const state = this.ptys.get(ptyId);
284
- if (!state)
285
- throw new KernelError("EBADF", "PTY not found");
286
- this.log.trace({ ptyId, termios }, "PTY setTermios");
287
- if (termios.icrnl !== undefined)
288
- state.termios.icrnl = termios.icrnl;
289
- if (termios.opost !== undefined)
290
- state.termios.opost = termios.opost;
291
- if (termios.onlcr !== undefined)
292
- state.termios.onlcr = termios.onlcr;
293
- if (termios.icanon !== undefined)
294
- state.termios.icanon = termios.icanon;
295
- if (termios.echo !== undefined)
296
- state.termios.echo = termios.echo;
297
- if (termios.isig !== undefined)
298
- state.termios.isig = termios.isig;
299
- if (termios.cc)
300
- Object.assign(state.termios.cc, termios.cc);
301
- }
302
- /** Get the foreground process group for the PTY containing this description. */
303
- getForegroundPgid(descriptionId) {
304
- const ptyId = this.getPtyId(descriptionId);
305
- const state = this.ptys.get(ptyId);
306
- if (!state)
307
- throw new KernelError("EBADF", "PTY not found");
308
- return state.foregroundPgid;
309
- }
310
- /** Get the PTY ID from a description ID. */
311
- getPtyId(descriptionId) {
312
- const ref = this.descToPty.get(descriptionId);
313
- if (!ref)
314
- throw new KernelError("EBADF", "not a PTY end");
315
- return ref.ptyId;
316
- }
317
- // -------------------------------------------------------------------
318
- // Output processing (ONLCR)
319
- // -------------------------------------------------------------------
320
- /** Convert lone \n to \r\n in output data (POSIX ONLCR). Skipped when opost/onlcr disabled. */
321
- processOutput(state, data) {
322
- // Skip output processing when opost or onlcr is off
323
- if (!state.termios.opost || !state.termios.onlcr)
324
- return data;
325
- // Fast path: no newlines → return as-is
326
- if (!data.includes(0x0a))
327
- return data;
328
- // Count lone LFs (not preceded by CR) to size the result buffer
329
- let extraCRs = 0;
330
- for (let i = 0; i < data.length; i++) {
331
- if (data[i] === 0x0a && (i === 0 || data[i - 1] !== 0x0d)) {
332
- extraCRs++;
333
- }
334
- }
335
- if (extraCRs === 0)
336
- return data;
337
- const result = new Uint8Array(data.length + extraCRs);
338
- let j = 0;
339
- for (let i = 0; i < data.length; i++) {
340
- if (data[i] === 0x0a && (i === 0 || data[i - 1] !== 0x0d)) {
341
- result[j++] = 0x0d; // CR
342
- }
343
- result[j++] = data[i];
344
- }
345
- return result;
346
- }
347
- // -------------------------------------------------------------------
348
- // Line discipline input processing
349
- // -------------------------------------------------------------------
350
- /**
351
- * Process input data through line discipline.
352
- * Master writes go through here before reaching the slave's input buffer.
353
- */
354
- processInput(state, data) {
355
- const { termios } = state;
356
- // Raw-mode input still applies ICRNL, but it must deliver atomically so
357
- // oversized writes fail without partially filling the input buffer.
358
- if (!termios.icanon && !termios.echo && !termios.isig) {
359
- this.deliverInput(state, this.applyInputTranslations(termios.icrnl, data));
360
- return data.length;
361
- }
362
- // Process byte by byte through discipline
363
- for (let byte of data) {
364
- // ICRNL: convert CR (0x0d) to NL (0x0a) before all other processing
365
- if (termios.icrnl && byte === 0x0d)
366
- byte = 0x0a;
367
- // Signal character handling (requires isig)
368
- if (termios.isig) {
369
- const signal = this.signalForByte(state, byte);
370
- if (signal !== null) {
371
- this.log.debug({ ptyId: state.id, signal, fgPgid: state.foregroundPgid, sessionLeader: state.sessionLeaderPgid }, "PTY signal char detected");
372
- if (termios.icanon)
373
- state.lineBuffer.length = 0;
374
- // Session-leader SIGINT interception: echo ^C, protect
375
- // the shell, and inject a newline to trigger a fresh prompt
376
- // when no children are running.
377
- if (signal === 2 &&
378
- state.sessionLeaderPgid > 0 &&
379
- state.foregroundPgid === state.sessionLeaderPgid) {
380
- // Echo ^C + newline so the user sees the interruption
381
- if (termios.echo) {
382
- this.echoOutput(state, new Uint8Array([0x5e, 0x43, 0x0d, 0x0a]));
383
- }
384
- // Kill children in the group (session leader is skipped).
385
- // Returns count of non-leader processes signaled.
386
- let childrenKilled = 0;
387
- if (state.foregroundPgid > 0) {
388
- try {
389
- childrenKilled = this.onSignal?.(state.foregroundPgid, signal, true) ?? 0;
390
- }
391
- catch {
392
- // Signal delivery failure must not break line discipline
393
- }
394
- }
395
- this.log.debug({ ptyId: state.id, childrenKilled, pgid: state.foregroundPgid }, "PTY session-leader SIGINT interception");
396
- // No children running → shell is at the prompt blocking on
397
- // fdRead. Inject a newline to unblock it and trigger a
398
- // fresh prompt.
399
- if (childrenKilled === 0) {
400
- this.deliverInput(state, new Uint8Array([0x0a]));
401
- }
402
- continue;
403
- }
404
- // Echo ^Z for SIGTSTP
405
- if (signal === 20 && termios.echo) {
406
- this.echoOutput(state, new Uint8Array([0x5e, 0x5a, 0x0d, 0x0a]));
407
- }
408
- // Echo ^\ for SIGQUIT
409
- if (signal === 3 && termios.echo) {
410
- this.echoOutput(state, new Uint8Array([0x5e, 0x5c, 0x0d, 0x0a]));
411
- }
412
- // Normal signal delivery (non-SIGINT or non-session-leader)
413
- if (state.foregroundPgid > 0) {
414
- this.log.debug({ ptyId: state.id, signal, pgid: state.foregroundPgid }, "PTY signal delivery to foreground group");
415
- try {
416
- this.onSignal?.(state.foregroundPgid, signal, false);
417
- }
418
- catch {
419
- // Signal delivery failure must not break line discipline
420
- }
421
- }
422
- continue;
423
- }
424
- }
425
- if (termios.icanon) {
426
- // EOF char: flush or signal EOF
427
- if (byte === termios.cc.veof) {
428
- if (state.lineBuffer.length === 0) {
429
- this.deliverInput(state, new Uint8Array(0));
430
- }
431
- else {
432
- this.deliverInput(state, new Uint8Array(state.lineBuffer));
433
- state.lineBuffer.length = 0;
434
- }
435
- continue;
436
- }
437
- // Erase char: erase last char
438
- if (byte === termios.cc.verase || byte === 0x08) {
439
- if (state.lineBuffer.length > 0) {
440
- state.lineBuffer.pop();
441
- if (termios.echo) {
442
- this.echoOutput(state, new Uint8Array([0x08, 0x20, 0x08]));
443
- }
444
- }
445
- continue;
446
- }
447
- // Newline: flush line (echo CR+LF for correct cursor positioning)
448
- if (byte === 0x0a) {
449
- state.lineBuffer.push(0x0a);
450
- if (termios.echo)
451
- this.echoOutput(state, new Uint8Array([0x0d, 0x0a]));
452
- this.deliverInput(state, new Uint8Array(state.lineBuffer));
453
- state.lineBuffer.length = 0;
454
- continue;
455
- }
456
- // Regular char: buffer (capped at MAX_CANON to prevent unbounded growth)
457
- if (state.lineBuffer.length >= MAX_CANON)
458
- continue;
459
- state.lineBuffer.push(byte);
460
- if (termios.echo)
461
- this.echoOutput(state, new Uint8Array([byte]));
462
- }
463
- else {
464
- // Raw mode: deliver immediately
465
- if (termios.echo)
466
- this.echoOutput(state, new Uint8Array([byte]));
467
- this.deliverInput(state, new Uint8Array([byte]));
468
- }
469
- }
470
- return data.length;
471
- }
472
- applyInputTranslations(icrnl, data) {
473
- if (!icrnl || !data.includes(0x0d))
474
- return data;
475
- const translated = new Uint8Array(data.length);
476
- for (let i = 0; i < data.length; i++) {
477
- translated[i] = data[i] === 0x0d ? 0x0a : data[i];
478
- }
479
- return translated;
480
- }
481
- /** Deliver input data to slave (input buffer / waiters). */
482
- deliverInput(state, data) {
483
- if (state.inputWaiters.length > 0) {
484
- const waiter = state.inputWaiters.shift();
485
- waiter(data);
486
- }
487
- else {
488
- // Enforce buffer limit to prevent unbounded memory growth
489
- if (this.bufferBytes(state.inputBuffer) + data.length > MAX_PTY_BUFFER_BYTES) {
490
- throw new KernelError("EAGAIN", "PTY input buffer full");
491
- }
492
- state.inputBuffer.push(new Uint8Array(data));
493
- }
494
- }
495
- /** Echo data to output (master reads it back for display). Throws EAGAIN when buffer is full. */
496
- echoOutput(state, data) {
497
- if (state.outputWaiters.length > 0) {
498
- const waiter = state.outputWaiters.shift();
499
- waiter(data);
500
- }
501
- else {
502
- if (this.bufferBytes(state.outputBuffer) + data.length > MAX_PTY_BUFFER_BYTES) {
503
- throw new KernelError("EAGAIN", "PTY output buffer full (echo backpressure)");
504
- }
505
- state.outputBuffer.push(new Uint8Array(data));
506
- }
507
- }
508
- /** Map control byte to signal number using termios cc, or null if not a signal char. */
509
- signalForByte(state, byte) {
510
- const { cc } = state.termios;
511
- if (byte === cc.vintr)
512
- return 2; // SIGINT
513
- if (byte === cc.vsusp)
514
- return 20; // SIGTSTP
515
- if (byte === cc.vquit)
516
- return 3; // SIGQUIT
517
- return null;
518
- }
519
- bufferBytes(buffer) {
520
- let size = 0;
521
- for (const chunk of buffer)
522
- size += chunk.length;
523
- return size;
524
- }
525
- drainBuffer(buffer, length) {
526
- const chunks = [];
527
- let remaining = length;
528
- while (remaining > 0 && buffer.length > 0) {
529
- const chunk = buffer[0];
530
- if (chunk.length <= remaining) {
531
- chunks.push(chunk);
532
- remaining -= chunk.length;
533
- buffer.shift();
534
- }
535
- else {
536
- chunks.push(chunk.subarray(0, remaining));
537
- buffer[0] = chunk.subarray(remaining);
538
- remaining = 0;
539
- }
540
- }
541
- if (chunks.length === 1)
542
- return chunks[0];
543
- const total = chunks.reduce((sum, c) => sum + c.length, 0);
544
- const result = new Uint8Array(total);
545
- let offset = 0;
546
- for (const chunk of chunks) {
547
- result.set(chunk, offset);
548
- offset += chunk.length;
549
- }
550
- return result;
551
- }
552
- }