control-stdio.ts 2.0 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647
  1. /** Windows CRT startup descriptors for a Node payload with one inherited control pipe. */
  2. import type { NativePtr, Win32ProcessBindings } from './ffi.ts'
  3. /** Native stdio handles plus the single additional descriptor selected by the launcher. */
  4. export interface InheritedControlStdio {
  5. stdin: NativePtr
  6. stdout: NativePtr
  7. stderr: NativePtr
  8. control: { fileDescriptor: 7; handle: NativePtr }
  9. }
  10. const HANDLE_BYTES = 8
  11. const INVALID_HANDLE = 0xffff_ffff_ffff_ffffn
  12. const FOPEN = 0x01
  13. const FPIPE = 0x08
  14. const FDEV = 0x40
  15. const FILE_TYPE_CHAR = 2
  16. const FILE_TYPE_PIPE = 3
  17. /**
  18. * Encode the CRT's descriptor table before the child runtime allocates descriptors.
  19. * Supported Windows targets use 64-bit handles. Empty slots remain closed, and the
  20. * backing Buffer must remain alive until CreateProcess returns.
  21. * @param api - native file-type inspection for inherited handles.
  22. * @param stdio - standard handles and the provider-owned fd-7 control pipe.
  23. * @returns descriptor count, flag bytes, and handle values for STARTUPINFO's reserved CRT fields.
  24. */
  25. export function inheritedControlStdio(api: Pick<Win32ProcessBindings, 'getFileType'>, stdio: InheritedControlStdio): Buffer {
  26. const count = stdio.control.fileDescriptor + 1
  27. const handleOffset = 4 + count
  28. const bytes = Buffer.alloc(handleOffset + count * HANDLE_BYTES)
  29. bytes.writeUInt32LE(count, 0)
  30. for (let index = 0; index < count; index++) {
  31. bytes.writeBigUInt64LE(INVALID_HANDLE, handleOffset + index * HANDLE_BYTES)
  32. }
  33. const entries = [[0, stdio.stdin], [1, stdio.stdout], [2, stdio.stderr], [stdio.control.fileDescriptor, stdio.control.handle]] as const
  34. for (const [fd, handle] of entries) {
  35. const kind = api.getFileType(handle)
  36. if (fd === stdio.control.fileDescriptor && kind !== FILE_TYPE_PIPE) {
  37. throw new Error('subprocess control descriptor is not a Windows pipe')
  38. }
  39. bytes[4 + fd] = FOPEN | (kind === FILE_TYPE_PIPE ? FPIPE : kind === FILE_TYPE_CHAR ? FDEV : 0)
  40. bytes.writeBigUInt64LE(handle, handleOffset + fd * HANDLE_BYTES)
  41. }
  42. return bytes
  43. }