| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269 |
- /** Closed private transports shared by the native subprocess runner. */
- import {
- chmodSync,
- existsSync,
- lstatSync,
- mkdtempSync,
- readFileSync,
- rmdirSync,
- unlinkSync,
- writeFileSync,
- } from 'node:fs'
- import { tmpdir } from 'node:os'
- import { basename, dirname, isAbsolute, join } from 'node:path'
- /** Target state restored by the Linux bootstrap after systemd establishes the scope. */
- export interface LinuxLaunchRequest {
- cwd: string
- env: Record<string, string>
- }
- /** Bounded Node-shaped error fields allowed across a private runner boundary. */
- export interface SerializedRunnerError {
- name: string
- message: string
- code?: string
- syscall?: string
- path?: string
- }
- /** A Linux pre-exec failure published beside its consumed request. */
- export type LinuxStartupError =
- { type: 'error'; error: SerializedRunnerError }
- /** The only parent-to-runner start message on Windows. */
- export interface WindowsStartRequest {
- type: 'start'
- cwd: string
- env: Record<string, string>
- }
- /** The only parent-to-runner control message on Windows. */
- export interface WindowsTerminateRequest {
- type: 'terminate'
- }
- /** Exactly one direct-result branch is sent by a connected Windows runner. */
- export type WindowsRunnerResult =
- | { type: 'target-exit'; exitCode: number }
- | { type: 'error'; error: SerializedRunnerError }
- /** Private paths owned by one Linux ordinary or PTY spawn. */
- export interface LinuxLaunchFiles {
- directory: string
- requestPath: string
- startupErrorPath: string
- }
- function isRecord(value: unknown): value is Record<string, unknown> {
- return value !== null && typeof value === 'object' && !Array.isArray(value)
- }
- function hasExactKeys(value: Record<string, unknown>, required: readonly string[], optional: readonly string[] = []): boolean {
- const allowed = new Set([...required, ...optional])
- return required.every(key => Object.hasOwn(value, key))
- && Object.keys(value).every(key => allowed.has(key))
- }
- function isStringRecord(value: unknown): value is Record<string, string> {
- return isRecord(value) && Object.values(value).every(entry => typeof entry === 'string')
- }
- function isSerializedRunnerError(value: unknown): value is SerializedRunnerError {
- if (!isRecord(value) || !hasExactKeys(
- value,
- ['name', 'message'],
- ['code', 'syscall', 'path'],
- )) return false
- return typeof value.name === 'string'
- && typeof value.message === 'string'
- && (value.code === undefined || typeof value.code === 'string')
- && (value.syscall === undefined || typeof value.syscall === 'string')
- && (value.path === undefined || typeof value.path === 'string')
- }
- function parseErrorResult(value: Record<string, unknown>): LinuxStartupError {
- if (!hasExactKeys(value, ['type', 'error']) || !isSerializedRunnerError(value.error)) {
- throw new Error('subprocess runner emitted an invalid error result')
- }
- if (value.type !== 'error') {
- throw new Error('subprocess runner emitted an unknown error result')
- }
- return { type: 'error', error: value.error }
- }
- /**
- * Create a private 0700 directory and one complete 0600 launch request.
- * @param request - target cwd and complete environment for the bootstrap.
- * @returns private paths owned by this launch.
- */
- export function createLinuxLaunchFiles(request: LinuxLaunchRequest): LinuxLaunchFiles {
- const directory = mkdtempSync(join(tmpdir(), 'dsh-subprocess-launch-'))
- const files = {
- directory,
- requestPath: join(directory, 'launch-request.json'),
- startupErrorPath: join(directory, 'startup-error.json'),
- }
- try {
- chmodSync(directory, 0o700)
- writeFileSync(files.requestPath, JSON.stringify(request), { flag: 'wx', mode: 0o600 })
- return files
- } catch (error) {
- cleanupLinuxLaunchFiles(files)
- throw error
- }
- }
- /**
- * Derive the only permitted startup-error path from an absolute request locator.
- * @param requestPath - absolute path to the private launch-request file.
- * @returns validated sibling paths for this launch.
- */
- export function linuxLaunchFilesFromLocator(requestPath: string): LinuxLaunchFiles {
- if (!isAbsolute(requestPath) || basename(requestPath) !== 'launch-request.json') {
- throw new Error('subprocess runner received an invalid Linux launch-request locator')
- }
- const directory = dirname(requestPath)
- return { directory, requestPath, startupErrorPath: join(directory, 'startup-error.json') }
- }
- /**
- * Strictly read and remove a one-shot Linux launch request.
- * @param requestPath - private launch-request path to consume.
- * @returns validated target cwd and environment.
- */
- export function consumeLinuxLaunchRequest(requestPath: string): LinuxLaunchRequest {
- const text = readFileSync(requestPath, 'utf8')
- unlinkSync(requestPath)
- const value: unknown = JSON.parse(text)
- if (!isRecord(value) || !hasExactKeys(value, ['cwd', 'env'])
- || typeof value.cwd !== 'string' || !isStringRecord(value.env)) {
- throw new Error('subprocess runner received an invalid Linux launch request')
- }
- return { cwd: value.cwd, env: value.env }
- }
- /**
- * Publish one strict 0600 Linux pre-exec error.
- * @param files - private paths for this launch.
- * @param error - bounded spawn or runner failure to publish.
- */
- export function writeLinuxStartupError(files: LinuxLaunchFiles, error: LinuxStartupError): void {
- writeFileSync(files.startupErrorPath, JSON.stringify(error), { flag: 'wx', mode: 0o600 })
- }
- /**
- * Read the Linux pre-exec error, if the bootstrap published one.
- * @param path - expected startup-error path.
- * @returns the validated failure, or undefined when none was published.
- */
- export function readLinuxStartupError(path: string): LinuxStartupError | undefined {
- if (!existsSync(path)) return undefined
- const value: unknown = JSON.parse(readFileSync(path, 'utf8'))
- if (!isRecord(value)) throw new Error('subprocess runner emitted an invalid startup error')
- return parseErrorResult(value)
- }
- /**
- * Strictly parse the single Windows start message.
- * @param value - untrusted IPC payload.
- * @returns validated target start request.
- */
- export function parseWindowsStartRequest(value: unknown): WindowsStartRequest {
- if (!isRecord(value) || !hasExactKeys(value, ['type', 'cwd', 'env'])
- || value.type !== 'start' || typeof value.cwd !== 'string' || !isStringRecord(value.env)) {
- throw new Error('subprocess runner received an invalid Windows start request')
- }
- return { type: 'start', cwd: value.cwd, env: value.env }
- }
- /**
- * Return true only for the exact, payload-free Windows terminate control.
- * @param value - untrusted IPC payload.
- * @returns whether the payload is the exact terminate request.
- */
- export function isWindowsTerminateRequest(value: unknown): value is WindowsTerminateRequest {
- return isRecord(value) && hasExactKeys(value, ['type']) && value.type === 'terminate'
- }
- /**
- * Strictly parse one of the two Windows direct-result branches.
- * @param value - untrusted IPC payload.
- * @returns validated direct-result message.
- */
- export function parseWindowsRunnerResult(value: unknown): WindowsRunnerResult {
- if (!isRecord(value) || typeof value.type !== 'string') {
- throw new Error('subprocess runner emitted an invalid Windows result')
- }
- if (value.type === 'error') return parseErrorResult(value)
- if (value.type === 'target-exit') {
- const validExitCode = typeof value.exitCode === 'number'
- && Number.isSafeInteger(value.exitCode)
- && value.exitCode >= 0
- if (!hasExactKeys(value, ['type', 'exitCode']) || !validExitCode) {
- throw new Error('subprocess runner emitted an invalid target-exit result')
- }
- return {
- type: 'target-exit',
- exitCode: value.exitCode as number,
- }
- }
- throw new Error(`subprocess runner emitted an unknown Windows result: ${value.type}`)
- }
- /**
- * Convert an unknown failure into the bounded cross-process error record.
- * @param error - failure caught at the process boundary.
- * @returns bounded serializable error fields.
- */
- export function serializeRunnerError(error: unknown): SerializedRunnerError {
- const source = error instanceof Error ? error : new Error(String(error))
- const node = source as NodeJS.ErrnoException & { path?: string }
- return {
- name: source.name,
- message: source.message,
- ...typeof node.code === 'string' ? { code: node.code } : {},
- ...typeof node.syscall === 'string' ? { syscall: node.syscall } : {},
- ...typeof node.path === 'string' ? { path: node.path } : {},
- }
- }
- /**
- * Rebuild a Node-shaped Error from a strict runner record.
- * @param serialized - validated bounded error fields.
- * @returns reconstructed Error with supported Node fields.
- */
- export function deserializeRunnerError(serialized: SerializedRunnerError): Error {
- const error = new Error(serialized.message)
- error.name = serialized.name
- return Object.assign(error, {
- ...serialized.code === undefined ? {} : { code: serialized.code },
- ...serialized.syscall === undefined ? {} : { syscall: serialized.syscall },
- ...serialized.path === undefined ? {} : { path: serialized.path },
- })
- }
- /**
- * Best-effort removal of only the private paths created for this Linux spawn.
- * @param files - exact private paths owned by this launch.
- */
- export function cleanupLinuxLaunchFiles(files: LinuxLaunchFiles): void {
- try {
- if (lstatSync(files.directory).isSymbolicLink()) {
- unlinkSync(files.directory)
- return
- }
- for (const path of [
- files.requestPath,
- files.startupErrorPath,
- ]) {
- try { unlinkSync(path) } catch (error) {
- if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error
- }
- }
- rmdirSync(files.directory)
- } catch {
- // Crash residue remains private and no later spawn reuses this directory.
- }
- }
|