| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641 |
- /** Typed Win32 process operations over the shared binding table. */
- import * as abi from './abi.ts'
- import { inheritedControlStdio } from './control-stdio.ts'
- import {
- allocProcessInfo,
- allocPtrSlot,
- allocStartupInfo,
- allocUint32,
- decodeProcessInfo,
- decodePtr,
- decodeUint32,
- encodeStartupInfo,
- isNullPtr,
- throwLastError,
- throwWin32,
- } from './ffi.ts'
- import type { CurrentTokenProcessBindings, NativePtr, Win32ProcessBindings } from './ffi.ts'
- import { requireKoffi } from './koffi.ts'
- /**
- * Quote one argument according to CommandLineToArgvW parsing.
- * @param argument - one argv entry.
- * @returns bare or quoted command-line segment.
- */
- export function quoteArg(argument: string): string {
- if (argument === '') return '""'
- if (!/[\s"]/u.test(argument)) return argument
- let quoted = '"'
- for (let index = 0; index < argument.length; index++) {
- let backslashes = 0
- while (index < argument.length && argument.charAt(index) === '\\') {
- backslashes += 1
- index += 1
- }
- if (index === argument.length) {
- quoted += '\\'.repeat(backslashes * 2)
- } else if (argument.charAt(index) === '"') {
- quoted += '\\'.repeat(backslashes * 2 + 1) + '"'
- } else {
- quoted += '\\'.repeat(backslashes) + argument.charAt(index)
- }
- }
- return quoted + '"'
- }
- /**
- * Build the mutable command line accepted by CreateProcessAsUserW.
- * @param program - executable argv entry.
- * @param args - remaining argv entries.
- * @returns joined Win32 command line.
- */
- export function buildCommandLine(program: string, args: readonly string[]): string {
- return [program, ...args].map(quoteArg).join(' ')
- }
- function compareWindowsEnvironmentKeys(
- [left]: readonly [string, string],
- [right]: readonly [string, string],
- ): number {
- const foldedLeft = left.toUpperCase()
- const foldedRight = right.toUpperCase()
- return foldedLeft < foldedRight ? -1 : foldedLeft > foldedRight ? 1 : 0
- }
- function encodeWindowsEnvironment(env: Readonly<Record<string, string>>): Buffer {
- const entries = Object.entries(env).sort(compareWindowsEnvironmentKeys)
- const strings = entries.map(([key, value]) => `${key}=${value}`)
- return Buffer.from(`${strings.join('\0')}\0\0`, 'utf16le')
- }
- interface ProcessSpawnOptions {
- /** Executable argv entry passed through CreateProcess. */
- command: string
- /** Arguments excluding the executable. */
- args: readonly string[]
- /** Existing child working directory. */
- cwd: string
- }
- /** Ordinary process creation inputs used by the local Win32 runner. */
- export interface CurrentTokenProcessSpawnOptions extends ProcessSpawnOptions {
- /** Resolved executable path passed separately from the preserved argv entry. */
- applicationName: string
- /** Complete target environment passed without mutating the runner. */
- env: Readonly<Record<string, string>>
- /** Runner CRT descriptors carrying target stdin, stdout, and stderr. */
- stdio: CurrentTokenStdioFileDescriptors
- }
- /** Runner CRT descriptors whose OS handles become the target standard handles. */
- export interface CurrentTokenStdioFileDescriptors {
- stdin: number
- stdout: number
- stderr: number
- /** Optional carrier and target descriptor for the inherited control pipe. */
- control?: 7
- }
- /** Restricted-token process creation inputs owned by the Windows ACL sandbox. */
- export interface RestrictedProcessSpawnOptions extends ProcessSpawnOptions {
- /** Restricted primary token supplied by sandbox policy. */
- token: NativePtr
- /** Optional control pipe inherited at the same descriptor in the payload. */
- controlFileDescriptor?: 7
- }
- /** Piped child resources whose process and read handles remain caller-owned. */
- export interface SpawnedPipedProcess {
- /** Direct child process id. */
- pid: number
- /** Process handle closed by waitForProcessExit. */
- process: NativePtr
- /** Stdout pipe read end closed by drainPipe. */
- stdoutRead: NativePtr
- /** Stderr pipe read end closed by drainPipe. */
- stderrRead: NativePtr
- }
- /** Suspended child assigned to one caller-owned kill-on-close Job before resume. */
- export interface SpawnedJobProcess {
- /** Direct child process id. */
- pid: number
- /** Process handle closed by waitForProcessExit. */
- process: NativePtr
- /** Job handle closed by the lifecycle owner. */
- job: NativePtr
- }
- interface PipePair {
- read: NativePtr
- write: NativePtr
- }
- function freeNative(pointer: NativePtr | undefined): void {
- if (pointer !== undefined) requireKoffi().free(pointer)
- }
- function closeBestEffort(api: Win32ProcessBindings, handle: NativePtr | null | undefined): void {
- if (!isNullPtr(handle)) api.closeHandle(handle)
- }
- function createPipe(api: Win32ProcessBindings, owned: Set<NativePtr>): PipePair {
- const readSlot = allocPtrSlot()
- let writeSlot: NativePtr | undefined
- try {
- writeSlot = allocPtrSlot()
- if (api.createPipe(readSlot, writeSlot, null, 0) === 0) throwLastError(api, 'CreatePipe')
- const read = decodePtr(readSlot)
- const write = decodePtr(writeSlot)
- if (read === null || write === null) {
- closeBestEffort(api, read)
- closeBestEffort(api, write)
- throwLastError(api, 'CreatePipe', 'null pipe handle')
- }
- owned.add(read)
- owned.add(write)
- return { read, write }
- } finally {
- freeNative(writeSlot)
- requireKoffi().free(readSlot)
- }
- }
- function closeOwned(api: Win32ProcessBindings, owned: Set<NativePtr>, handle: NativePtr): void {
- /* v8 ignore next -- each successfully decoded pipe end is uniquely owned. */
- if (!owned.delete(handle)) return
- api.closeHandle(handle)
- }
- function closeAllOwned(api: Win32ProcessBindings, owned: Set<NativePtr>): void {
- for (const handle of owned) api.closeHandle(handle)
- owned.clear()
- }
- function createRestrictedProcess(
- api: Win32ProcessBindings,
- options: RestrictedProcessSpawnOptions,
- commandLine: string,
- creationFlags: number,
- startupInfo: NativePtr,
- processInfo: NativePtr,
- ): number {
- // The sandbox mutates its process environment before this call. Passing an
- // explicit block through Koffi makes CreateProcessAsUserW reject the request
- // with ERROR_INVALID_PARAMETER, so lpEnvironment remains NULL.
- return api.createProcessAsUserW(
- options.token,
- null,
- commandLine,
- null,
- null,
- 1,
- creationFlags,
- null,
- options.cwd,
- startupInfo,
- processInfo,
- )
- }
- /**
- * Spawn a process with anonymous-pipe stdout/stderr and immediate stdin EOF.
- * New console windows start hidden without changing console inheritance.
- * @param api - active binding table.
- * @param options - command, cwd, args, and restricted primary token.
- * @returns caller-owned process and pipe read handles.
- */
- export function spawnPipedProcess(
- api: Win32ProcessBindings,
- options: RestrictedProcessSpawnOptions,
- ): SpawnedPipedProcess {
- const owned = new Set<NativePtr>()
- let startupInfo: NativePtr | undefined
- let processInfo: NativePtr | undefined
- try {
- const stdIn = createPipe(api, owned)
- const stdOut = createPipe(api, owned)
- const stdErr = createPipe(api, owned)
- for (const [handle, label] of [
- [stdIn.read, 'stdin read end'],
- [stdOut.write, 'stdout write end'],
- [stdErr.write, 'stderr write end'],
- ] as const) {
- if (api.setHandleInformation(handle, abi.HANDLE_FLAG_INHERIT, abi.HANDLE_FLAG_INHERIT) === 0) {
- throwLastError(api, 'SetHandleInformation', label)
- }
- }
- startupInfo = allocStartupInfo()
- encodeStartupInfo(startupInfo, {
- cb: abi.STARTUPINFOW_SIZE,
- dwFlags: abi.STARTF_USESTDHANDLES | abi.STARTF_USESHOWWINDOW,
- wShowWindow: abi.SW_HIDE,
- hStdInput: stdIn.read,
- hStdOutput: stdOut.write,
- hStdError: stdErr.write,
- })
- processInfo = allocProcessInfo()
- const created = createRestrictedProcess(
- api,
- options,
- buildCommandLine(options.command, options.args),
- 0,
- startupInfo,
- processInfo,
- )
- if (created === 0) {
- const win32Code = api.getLastError()
- throwWin32(api, 'CreateProcessAsUserW', win32Code, `command: ${options.command}, cwd: ${options.cwd}`)
- }
- const info = decodeProcessInfo(processInfo)
- if (info.hProcess === null || info.hThread === null) {
- if (info.hProcess !== null) api.terminateProcess(info.hProcess, 1)
- closeBestEffort(api, info.hThread)
- closeBestEffort(api, info.hProcess)
- throw new Error(`CreateProcessAsUserW succeeded but returned null process/thread handles (pid ${info.dwProcessId})`)
- }
- closeOwned(api, owned, stdIn.read)
- closeOwned(api, owned, stdIn.write)
- closeOwned(api, owned, stdOut.write)
- closeOwned(api, owned, stdErr.write)
- closeBestEffort(api, info.hThread)
- owned.delete(stdOut.read)
- owned.delete(stdErr.read)
- return {
- pid: info.dwProcessId,
- process: info.hProcess,
- stdoutRead: stdOut.read,
- stderrRead: stdErr.read,
- }
- } catch (error) {
- closeAllOwned(api, owned)
- throw error
- } finally {
- freeNative(processInfo)
- freeNative(startupInfo)
- }
- }
- /**
- * Drain one anonymous pipe until the writer closes it.
- * @param api - active binding table.
- * @param handle - caller-owned pipe read end.
- * @returns complete bytes read before EOF; the handle is always closed.
- * @throws when a Win32 pipe operation fails.
- */
- export async function drainPipe(
- api: Win32ProcessBindings,
- handle: NativePtr,
- ): Promise<Buffer> {
- const chunks: Buffer[] = []
- let countSlot: NativePtr | undefined
- try {
- countSlot = allocUint32()
- for (;;) {
- const peeked = api.peekNamedPipe(handle, null, 0, null, countSlot, null)
- if (peeked === 0) {
- const win32Code = api.getLastError()
- if (win32Code === abi.ERROR_BROKEN_PIPE || win32Code === abi.ERROR_NO_DATA) break
- throwLastError(api, 'PeekNamedPipe', `drain failure after ${chunks.length} chunk(s)`)
- }
- const available = decodeUint32(countSlot)
- if (available > 0) {
- const chunk = Buffer.alloc(available)
- if (api.readFile(handle, chunk, chunk.length, countSlot, null) === 0) {
- throwLastError(api, 'ReadFile', `drain failure after ${chunks.length} chunk(s)`)
- }
- chunks.push(chunk.subarray(0, decodeUint32(countSlot)))
- }
- await new Promise<void>(resolve => setTimeout(resolve, 1))
- }
- return Buffer.concat(chunks)
- } finally {
- freeNative(countSlot)
- api.closeHandle(handle)
- }
- }
- /**
- * Wait for a process and always close its handle.
- * @param api - active binding table.
- * @param process - caller-owned process handle.
- * @returns direct process exit code.
- */
- export function waitForProcessExit(api: Win32ProcessBindings, process: NativePtr): number {
- let exitCodeSlot: NativePtr | undefined
- try {
- if (api.waitForSingleObject(process, abi.INFINITE) === 0xFFFFFFFF) {
- throwLastError(api, 'WaitForSingleObject')
- }
- exitCodeSlot = allocUint32()
- if (api.getExitCodeProcess(process, exitCodeSlot) === 0) throwLastError(api, 'GetExitCodeProcess')
- return decodeUint32(exitCodeSlot)
- } finally {
- freeNative(exitCodeSlot)
- api.closeHandle(process)
- }
- }
- function createKillOnCloseJob(api: Win32ProcessBindings): NativePtr {
- const job = api.createJobObjectW(null, null)
- if (isNullPtr(job)) throwLastError(api, 'CreateJobObjectW')
- const information = Buffer.alloc(abi.JOBOBJECT_EXTENDED_LIMIT_SIZE)
- information.writeUInt32LE(
- abi.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE,
- abi.JOBOBJECT_EXTENDED_LIMIT_FLAGS_OFFSET,
- )
- if (api.setInformationJobObject(
- job,
- abi.JobObjectExtendedLimitInformation,
- information,
- information.length,
- ) === 0) {
- const win32Code = api.getLastError()
- api.closeHandle(job)
- throwWin32(api, 'SetInformationJobObject', win32Code)
- }
- return job
- }
- interface ProcessStandardHandles {
- stdin: NativePtr
- stdout: NativePtr
- stderr: NativePtr
- control?: { fileDescriptor: 7; handle: NativePtr }
- }
- // Koffi exposes PVOID as an unsigned 64-bit bigint on supported Windows hosts.
- const UV_INVALID_OS_FILE_HANDLE = 0xffff_ffff_ffff_ffffn
- const UV_INVALID_FILE_DESCRIPTOR = 0xffff_ffff_ffff_fffen
- function inheritedStandardHandles(api: Win32ProcessBindings, controlFileDescriptor?: 7): ProcessStandardHandles {
- const get = (selector: number, label: string): NativePtr => {
- const handle = api.getStdHandle(selector)
- if (!isNullPtr(handle)) return handle
- throwLastError(api, 'GetStdHandle', `null ${label} handle`)
- }
- return {
- stdin: get(abi.STD_INPUT_HANDLE, 'stdin'),
- stdout: get(abi.STD_OUTPUT_HANDLE, 'stdout'),
- stderr: get(abi.STD_ERROR_HANDLE, 'stderr'),
- ...controlFileDescriptor === undefined ? {} : {
- control: { fileDescriptor: controlFileDescriptor, handle: descriptorHandle(api, controlFileDescriptor, 'control') },
- },
- }
- }
- function descriptorHandle(api: Win32ProcessBindings, fileDescriptor: number, label: string): NativePtr {
- const handle = api.uvGetOsfhandle(fileDescriptor)
- if (
- isNullPtr(handle)
- || handle === UV_INVALID_OS_FILE_HANDLE
- || handle === UV_INVALID_FILE_DESCRIPTOR
- ) {
- throw new Error(`uv_get_osfhandle returned an invalid handle for target ${label} fd ${String(fileDescriptor)}`)
- }
- return handle
- }
- function targetCarrierHandles(
- api: CurrentTokenProcessBindings,
- descriptors: CurrentTokenStdioFileDescriptors,
- ): ProcessStandardHandles {
- return {
- stdin: descriptorHandle(api, descriptors.stdin, 'stdin'),
- stdout: descriptorHandle(api, descriptors.stdout, 'stdout'),
- stderr: descriptorHandle(api, descriptors.stderr, 'stderr'),
- ...descriptors.control === undefined ? {} : {
- control: { fileDescriptor: descriptors.control, handle: descriptorHandle(api, descriptors.control, 'control') },
- },
- }
- }
- /** Shared suspended-create, Job-assignment, and resume lifecycle. */
- function spawnJobProcess(
- api: Win32ProcessBindings,
- options: ProcessSpawnOptions,
- resolveStdio: () => ProcessStandardHandles,
- createName: 'CreateProcessAsUserW' | 'CreateProcessW',
- create: (startupInfo: NativePtr, processInfo: NativePtr) => number,
- ): SpawnedJobProcess {
- const job = createKillOnCloseJob(api)
- const enabled: NativePtr[] = []
- let startupInfo: NativePtr | undefined
- let processInfo: NativePtr | undefined
- let controlDescriptorBlock: { pointer: NativePtr; length: number } | undefined
- let created = 0
- let createFailureCode = 0
- try {
- const stdio = resolveStdio()
- const inherited: Array<readonly [NativePtr, string]> = [
- [stdio.stdin, 'stdin'],
- [stdio.stdout, 'stdout'],
- [stdio.stderr, 'stderr'],
- ]
- if (stdio.control !== undefined) inherited.push([stdio.control.handle, 'control'])
- for (const [handle, label] of inherited) {
- if (api.setHandleInformation(handle, abi.HANDLE_FLAG_INHERIT, abi.HANDLE_FLAG_INHERIT) === 0) {
- throwLastError(api, 'SetHandleInformation', `${label} (enable inherit)`)
- }
- enabled.push(handle)
- }
- const controlBytes = stdio.control === undefined
- ? undefined
- : inheritedControlStdio(api, { ...stdio, control: stdio.control })
- if (controlBytes !== undefined) {
- const koffi = requireKoffi()
- controlDescriptorBlock = { pointer: koffi.alloc('uint8', controlBytes.length) as NativePtr, length: controlBytes.length }
- koffi.encode(controlDescriptorBlock.pointer, 'uint8', controlBytes, controlBytes.length)
- }
- startupInfo = allocStartupInfo()
- encodeStartupInfo(startupInfo, {
- cb: abi.STARTUPINFOW_SIZE,
- // Preserve console inheritance: CREATE_NO_WINDOW can fail restricted-token DLL initialization.
- dwFlags: abi.STARTF_USESTDHANDLES | abi.STARTF_USESHOWWINDOW,
- wShowWindow: abi.SW_HIDE,
- hStdInput: stdio.stdin,
- hStdOutput: stdio.stdout,
- hStdError: stdio.stderr,
- ...controlDescriptorBlock === undefined ? {} : {
- cbReserved2: controlDescriptorBlock.length,
- lpReserved2: controlDescriptorBlock.pointer,
- },
- })
- processInfo = allocProcessInfo()
- created = create(startupInfo, processInfo)
- if (created === 0) createFailureCode = api.getLastError()
- } catch (error) {
- freeNative(processInfo)
- api.closeHandle(job)
- throw error
- } finally {
- freeNative(startupInfo)
- freeNative(controlDescriptorBlock?.pointer)
- for (const handle of enabled) {
- // The runner spawns nothing else; cleanup failure must not mask the child.
- api.setHandleInformation(handle, abi.HANDLE_FLAG_INHERIT, 0)
- }
- }
- if (created === 0) {
- freeNative(processInfo)
- api.closeHandle(job)
- throwWin32(
- api,
- createName,
- createFailureCode,
- `command: ${options.command}, cwd: ${options.cwd}`,
- )
- }
- let info: ReturnType<typeof decodeProcessInfo>
- try {
- info = decodeProcessInfo(processInfo)
- } finally {
- freeNative(processInfo)
- }
- if (info.hProcess === null || info.hThread === null) {
- if (info.hProcess !== null) api.terminateProcess(info.hProcess, 1)
- api.closeHandle(job)
- closeBestEffort(api, info.hThread)
- closeBestEffort(api, info.hProcess)
- throw new Error(`${createName} succeeded but returned null process/thread handles (pid ${info.dwProcessId})`)
- }
- if (api.assignProcessToJobObject(job, info.hProcess) === 0) {
- const win32Code = api.getLastError()
- api.terminateProcess(info.hProcess, 1)
- closeBestEffort(api, info.hThread)
- closeBestEffort(api, info.hProcess)
- api.closeHandle(job)
- throwWin32(api, 'AssignProcessToJobObject', win32Code, `pid ${info.dwProcessId}`)
- }
- if (api.resumeThread(info.hThread) === 0xFFFFFFFF) {
- const win32Code = api.getLastError()
- closeBestEffort(api, info.hThread)
- closeBestEffort(api, info.hProcess)
- api.closeHandle(job)
- throwWin32(api, 'ResumeThread', win32Code, `pid ${info.dwProcessId}`)
- }
- closeBestEffort(api, info.hThread)
- return { pid: info.dwProcessId, process: info.hProcess, job }
- }
- /**
- * Spawn a restricted-token process suspended with hidden initial windows, assign its Job, then resume it.
- * @param api - active binding table.
- * @param options - command, cwd, args, and restricted primary token.
- * @returns caller-owned process and Job handles after successful resume.
- * @remarks Node clears stdio handle inheritability at startup through
- * uv_disable_stdio_inheritance. This operation temporarily restores the bits
- * required by STARTF_USESTDHANDLES. Restoring them afterward is best-effort:
- * failure must not replace the already-created child's outcome.
- */
- export function spawnInheritedJobProcess(
- api: Win32ProcessBindings,
- options: RestrictedProcessSpawnOptions,
- ): SpawnedJobProcess {
- const commandLine = buildCommandLine(options.command, options.args)
- return spawnJobProcess(api, options, () => inheritedStandardHandles(api, options.controlFileDescriptor), 'CreateProcessAsUserW', (startupInfo, processInfo) =>
- createRestrictedProcess(
- api,
- options,
- commandLine,
- abi.CREATE_SUSPENDED,
- startupInfo,
- processInfo,
- ))
- }
- /**
- * Spawn an ordinary process suspended with hidden initial windows, assign its Job, then resume it.
- * @param api - active binding table.
- * @param options - command, cwd, argv, and target carrier descriptors.
- * @returns caller-owned process and Job handles after successful resume.
- */
- export function spawnCurrentTokenJobProcess(
- api: CurrentTokenProcessBindings,
- options: CurrentTokenProcessSpawnOptions,
- ): SpawnedJobProcess {
- const commandLine = buildCommandLine(options.command, options.args)
- const environment = encodeWindowsEnvironment(options.env)
- return spawnJobProcess(api, options, () => targetCarrierHandles(api, options.stdio), 'CreateProcessW', (startupInfo, processInfo) =>
- api.createProcessW(
- options.applicationName,
- commandLine,
- null,
- null,
- 1,
- abi.CREATE_SUSPENDED | abi.CREATE_UNICODE_ENVIRONMENT,
- environment,
- options.cwd,
- startupInfo,
- processInfo,
- ))
- }
- /**
- * Verify that an unnamed kill-on-close Job can be created and released now.
- * @param api - active binding table.
- */
- export function probeCurrentTokenJobSupport(api: CurrentTokenProcessBindings): void {
- const job = createKillOnCloseJob(api)
- closeHandleChecked(api, job, 'current-token Job capability probe')
- }
- /**
- * Poll one process handle without blocking the runner event loop.
- * @param api - active binding table.
- * @param process - caller-owned process handle.
- * @returns the direct exit code when signalled, or undefined while running.
- */
- export function pollProcessExit(api: Win32ProcessBindings, process: NativePtr): number | undefined {
- const waitResult = api.waitForSingleObject(process, 0)
- if (waitResult === abi.WAIT_TIMEOUT) return undefined
- if (waitResult === 0xFFFFFFFF) throwLastError(api, 'WaitForSingleObject')
- const exitCodeSlot = allocUint32()
- try {
- if (api.getExitCodeProcess(process, exitCodeSlot) === 0) throwLastError(api, 'GetExitCodeProcess')
- return decodeUint32(exitCodeSlot)
- } finally {
- requireKoffi().free(exitCodeSlot)
- }
- }
- /**
- * Return whether a Job has no active processes.
- * @param api - active binding table.
- * @param job - caller-owned Job handle.
- * @returns true once the Job reports zero active processes.
- */
- export function isJobEmpty(api: Win32ProcessBindings, job: NativePtr): boolean {
- const information = Buffer.alloc(abi.JOBOBJECT_BASIC_ACCOUNTING_SIZE)
- if (api.queryInformationJobObject(
- job,
- abi.JobObjectBasicAccountingInformation,
- information,
- information.length,
- null,
- ) === 0) {
- throwLastError(api, 'QueryInformationJobObject', 'active process count')
- }
- return information.readUInt32LE(abi.JOBOBJECT_BASIC_ACCOUNTING_ACTIVE_PROCESSES_OFFSET) === 0
- }
- /**
- * Terminate every process in a Job.
- * @param api - active binding table.
- * @param job - caller-owned Job handle.
- * @param exitCode - direct Windows exit code assigned to members.
- */
- export function terminateJob(api: Win32ProcessBindings, job: NativePtr, exitCode: number): void {
- if (api.terminateJobObject(job, exitCode) === 0) throwLastError(api, 'TerminateJobObject')
- }
- /**
- * Close a caller-owned handle and report a labelled Win32 failure.
- * @param api - active binding table.
- * @param handle - handle to close.
- * @param detail - lifecycle label for diagnostics.
- */
- export function closeHandleChecked(api: Win32ProcessBindings, handle: NativePtr, detail: string): void {
- if (api.closeHandle(handle) === 0) throwLastError(api, 'CloseHandle', detail)
- }
|