forked from mirrors/gecko-dev
		
	# ignore-this-changeset Differential Revision: https://phabricator.services.mozilla.com/D36056 --HG-- extra : source : 2616392f26053ee376b9126fbca696de5d4bb15b
		
			
				
	
	
		
			791 lines
		
	
	
	
		
			18 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			791 lines
		
	
	
	
		
			18 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 | 
						|
/* vim: set sts=2 sw=2 et tw=80: */
 | 
						|
/* This Source Code Form is subject to the terms of the Mozilla Public
 | 
						|
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 | 
						|
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | 
						|
"use strict";
 | 
						|
 | 
						|
/* exported Process */
 | 
						|
 | 
						|
/* import-globals-from subprocess_shared.js */
 | 
						|
/* import-globals-from subprocess_shared_win.js */
 | 
						|
/* import-globals-from subprocess_worker_common.js */
 | 
						|
importScripts(
 | 
						|
  "resource://gre/modules/subprocess/subprocess_shared.js",
 | 
						|
  "resource://gre/modules/subprocess/subprocess_shared_win.js",
 | 
						|
  "resource://gre/modules/subprocess/subprocess_worker_common.js"
 | 
						|
);
 | 
						|
 | 
						|
const POLL_TIMEOUT = 5000;
 | 
						|
 | 
						|
// The exit code that we send when we forcibly terminate a process.
 | 
						|
const TERMINATE_EXIT_CODE = 0x7f;
 | 
						|
 | 
						|
let io;
 | 
						|
 | 
						|
let nextPipeId = 0;
 | 
						|
 | 
						|
class Pipe extends BasePipe {
 | 
						|
  constructor(process, origHandle) {
 | 
						|
    super();
 | 
						|
 | 
						|
    let handle = win32.HANDLE();
 | 
						|
 | 
						|
    let curProc = libc.GetCurrentProcess();
 | 
						|
    libc.DuplicateHandle(
 | 
						|
      curProc,
 | 
						|
      origHandle,
 | 
						|
      curProc,
 | 
						|
      handle.address(),
 | 
						|
      0,
 | 
						|
      false /* inheritable */,
 | 
						|
      win32.DUPLICATE_SAME_ACCESS
 | 
						|
    );
 | 
						|
 | 
						|
    origHandle.dispose();
 | 
						|
 | 
						|
    this.id = nextPipeId++;
 | 
						|
    this.process = process;
 | 
						|
 | 
						|
    this.handle = win32.Handle(handle);
 | 
						|
 | 
						|
    let event = libc.CreateEventW(null, false, false, null);
 | 
						|
 | 
						|
    this.overlapped = win32.OVERLAPPED();
 | 
						|
    this.overlapped.hEvent = event;
 | 
						|
 | 
						|
    this._event = win32.Handle(event);
 | 
						|
 | 
						|
    this.buffer = null;
 | 
						|
  }
 | 
						|
 | 
						|
  get event() {
 | 
						|
    if (this.pending.length) {
 | 
						|
      return this._event;
 | 
						|
    }
 | 
						|
    return null;
 | 
						|
  }
 | 
						|
 | 
						|
  maybeClose() {}
 | 
						|
 | 
						|
  /**
 | 
						|
   * Closes the file handle.
 | 
						|
   *
 | 
						|
   * @param {boolean} [force=false]
 | 
						|
   *        If true, the file handle is closed immediately. If false, the
 | 
						|
   *        file handle is closed after all current pending IO operations
 | 
						|
   *        have completed.
 | 
						|
   *
 | 
						|
   * @returns {Promise<void>}
 | 
						|
   *          Resolves when the file handle has been closed.
 | 
						|
   */
 | 
						|
  close(force = false) {
 | 
						|
    if (!force && this.pending.length) {
 | 
						|
      this.closing = true;
 | 
						|
      return this.closedPromise;
 | 
						|
    }
 | 
						|
 | 
						|
    for (let { reject } of this.pending) {
 | 
						|
      let error = new Error("File closed");
 | 
						|
      error.errorCode = SubprocessConstants.ERROR_END_OF_FILE;
 | 
						|
      reject(error);
 | 
						|
    }
 | 
						|
    this.pending.length = 0;
 | 
						|
 | 
						|
    this.buffer = null;
 | 
						|
 | 
						|
    if (!this.closed) {
 | 
						|
      this.handle.dispose();
 | 
						|
      this._event.dispose();
 | 
						|
 | 
						|
      io.pipes.delete(this.id);
 | 
						|
 | 
						|
      this.handle = null;
 | 
						|
      this.closed = true;
 | 
						|
      this.resolveClosed();
 | 
						|
 | 
						|
      io.updatePollEvents();
 | 
						|
    }
 | 
						|
    return this.closedPromise;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Called when an error occurred while attempting an IO operation on our file
 | 
						|
   * handle.
 | 
						|
   */
 | 
						|
  onError() {
 | 
						|
    this.close(true);
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
class InputPipe extends Pipe {
 | 
						|
  /**
 | 
						|
   * Queues the next chunk of data to be read from the pipe if, and only if,
 | 
						|
   * there is no IO operation currently pending.
 | 
						|
   */
 | 
						|
  readNext() {
 | 
						|
    if (this.buffer === null) {
 | 
						|
      this.readBuffer(this.pending[0].length);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Closes the pipe if there is a pending read operation with no more
 | 
						|
   * buffered data to be read.
 | 
						|
   */
 | 
						|
  maybeClose() {
 | 
						|
    if (this.buffer) {
 | 
						|
      let read = win32.DWORD();
 | 
						|
 | 
						|
      let ok = libc.GetOverlappedResult(
 | 
						|
        this.handle,
 | 
						|
        this.overlapped.address(),
 | 
						|
        read.address(),
 | 
						|
        false
 | 
						|
      );
 | 
						|
 | 
						|
      if (!ok) {
 | 
						|
        this.onError();
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Asynchronously reads at most `length` bytes of binary data from the file
 | 
						|
   * descriptor into an ArrayBuffer of the same size. Returns a promise which
 | 
						|
   * resolves when the operation is complete.
 | 
						|
   *
 | 
						|
   * @param {integer} length
 | 
						|
   *        The number of bytes to read.
 | 
						|
   *
 | 
						|
   * @returns {Promise<ArrayBuffer>}
 | 
						|
   */
 | 
						|
  read(length) {
 | 
						|
    if (this.closing || this.closed) {
 | 
						|
      throw new Error("Attempt to read from closed pipe");
 | 
						|
    }
 | 
						|
 | 
						|
    return new Promise((resolve, reject) => {
 | 
						|
      this.pending.push({ resolve, reject, length });
 | 
						|
      this.readNext();
 | 
						|
    });
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Initializes an overlapped IO read operation to read exactly `count` bytes
 | 
						|
   * into a new ArrayBuffer, which is stored in the `buffer` property until the
 | 
						|
   * operation completes.
 | 
						|
   *
 | 
						|
   * @param {integer} count
 | 
						|
   *        The number of bytes to read.
 | 
						|
   */
 | 
						|
  readBuffer(count) {
 | 
						|
    this.buffer = new ArrayBuffer(count);
 | 
						|
 | 
						|
    let ok = libc.ReadFile(
 | 
						|
      this.handle,
 | 
						|
      this.buffer,
 | 
						|
      count,
 | 
						|
      null,
 | 
						|
      this.overlapped.address()
 | 
						|
    );
 | 
						|
 | 
						|
    if (!ok && (!this.process.handle || libc.winLastError)) {
 | 
						|
      this.onError();
 | 
						|
    } else {
 | 
						|
      io.updatePollEvents();
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Called when our pending overlapped IO operation has completed, whether
 | 
						|
   * successfully or in failure.
 | 
						|
   */
 | 
						|
  onReady() {
 | 
						|
    let read = win32.DWORD();
 | 
						|
 | 
						|
    let ok = libc.GetOverlappedResult(
 | 
						|
      this.handle,
 | 
						|
      this.overlapped.address(),
 | 
						|
      read.address(),
 | 
						|
      false
 | 
						|
    );
 | 
						|
 | 
						|
    read = read.value;
 | 
						|
 | 
						|
    if (!ok) {
 | 
						|
      this.onError();
 | 
						|
    } else if (read > 0) {
 | 
						|
      let buffer = this.buffer;
 | 
						|
      this.buffer = null;
 | 
						|
 | 
						|
      let { resolve } = this.shiftPending();
 | 
						|
 | 
						|
      if (read == buffer.byteLength) {
 | 
						|
        resolve(buffer);
 | 
						|
      } else {
 | 
						|
        resolve(ArrayBuffer.transfer(buffer, read));
 | 
						|
      }
 | 
						|
 | 
						|
      if (this.pending.length) {
 | 
						|
        this.readNext();
 | 
						|
      } else {
 | 
						|
        io.updatePollEvents();
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
class OutputPipe extends Pipe {
 | 
						|
  /**
 | 
						|
   * Queues the next chunk of data to be written to the pipe if, and only if,
 | 
						|
   * there is no IO operation currently pending.
 | 
						|
   */
 | 
						|
  writeNext() {
 | 
						|
    if (this.buffer === null) {
 | 
						|
      this.writeBuffer(this.pending[0].buffer);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Asynchronously writes the given buffer to our file descriptor, and returns
 | 
						|
   * a promise which resolves when the operation is complete.
 | 
						|
   *
 | 
						|
   * @param {ArrayBuffer} buffer
 | 
						|
   *        The buffer to write.
 | 
						|
   *
 | 
						|
   * @returns {Promise<integer>}
 | 
						|
   *          Resolves to the number of bytes written when the operation is
 | 
						|
   *          complete.
 | 
						|
   */
 | 
						|
  write(buffer) {
 | 
						|
    if (this.closing || this.closed) {
 | 
						|
      throw new Error("Attempt to write to closed pipe");
 | 
						|
    }
 | 
						|
 | 
						|
    return new Promise((resolve, reject) => {
 | 
						|
      this.pending.push({ resolve, reject, buffer });
 | 
						|
      this.writeNext();
 | 
						|
    });
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Initializes an overapped IO read operation to write the data in `buffer` to
 | 
						|
   * our file descriptor.
 | 
						|
   *
 | 
						|
   * @param {ArrayBuffer} buffer
 | 
						|
   *        The buffer to write.
 | 
						|
   */
 | 
						|
  writeBuffer(buffer) {
 | 
						|
    this.buffer = buffer;
 | 
						|
 | 
						|
    let ok = libc.WriteFile(
 | 
						|
      this.handle,
 | 
						|
      buffer,
 | 
						|
      buffer.byteLength,
 | 
						|
      null,
 | 
						|
      this.overlapped.address()
 | 
						|
    );
 | 
						|
 | 
						|
    if (!ok && libc.winLastError) {
 | 
						|
      this.onError();
 | 
						|
    } else {
 | 
						|
      io.updatePollEvents();
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Called when our pending overlapped IO operation has completed, whether
 | 
						|
   * successfully or in failure.
 | 
						|
   */
 | 
						|
  onReady() {
 | 
						|
    let written = win32.DWORD();
 | 
						|
 | 
						|
    let ok = libc.GetOverlappedResult(
 | 
						|
      this.handle,
 | 
						|
      this.overlapped.address(),
 | 
						|
      written.address(),
 | 
						|
      false
 | 
						|
    );
 | 
						|
 | 
						|
    written = written.value;
 | 
						|
 | 
						|
    if (!ok || written != this.buffer.byteLength) {
 | 
						|
      this.onError();
 | 
						|
    } else if (written > 0) {
 | 
						|
      let { resolve } = this.shiftPending();
 | 
						|
 | 
						|
      this.buffer = null;
 | 
						|
      resolve(written);
 | 
						|
 | 
						|
      if (this.pending.length) {
 | 
						|
        this.writeNext();
 | 
						|
      } else {
 | 
						|
        io.updatePollEvents();
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
class Signal {
 | 
						|
  constructor(event) {
 | 
						|
    this.event = event;
 | 
						|
  }
 | 
						|
 | 
						|
  cleanup() {
 | 
						|
    libc.CloseHandle(this.event);
 | 
						|
    this.event = null;
 | 
						|
  }
 | 
						|
 | 
						|
  onError() {
 | 
						|
    io.shutdown();
 | 
						|
  }
 | 
						|
 | 
						|
  onReady() {
 | 
						|
    io.messageCount += 1;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
class Process extends BaseProcess {
 | 
						|
  constructor(...args) {
 | 
						|
    super(...args);
 | 
						|
 | 
						|
    this.killed = false;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Returns our process handle for use as an event in a WaitForMultipleObjects
 | 
						|
   * call.
 | 
						|
   */
 | 
						|
  get event() {
 | 
						|
    return this.handle;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Forcibly terminates the process.
 | 
						|
   */
 | 
						|
  kill() {
 | 
						|
    this.killed = true;
 | 
						|
    libc.TerminateJobObject(this.jobHandle, TERMINATE_EXIT_CODE);
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Initializes the IO pipes for use as standard input, output, and error
 | 
						|
   * descriptors in the spawned process.
 | 
						|
   *
 | 
						|
   * @returns {win32.Handle[]}
 | 
						|
   *          The array of file handles belonging to the spawned process.
 | 
						|
   */
 | 
						|
  initPipes({ stderr }) {
 | 
						|
    let our_pipes = [];
 | 
						|
    let their_pipes = [];
 | 
						|
 | 
						|
    let secAttr = new win32.SECURITY_ATTRIBUTES();
 | 
						|
    secAttr.nLength = win32.SECURITY_ATTRIBUTES.size;
 | 
						|
    secAttr.bInheritHandle = true;
 | 
						|
 | 
						|
    let pipe = input => {
 | 
						|
      if (input) {
 | 
						|
        let handles = win32.createPipe(secAttr, win32.FILE_FLAG_OVERLAPPED);
 | 
						|
        our_pipes.push(new InputPipe(this, handles[0]));
 | 
						|
        return handles[1];
 | 
						|
      }
 | 
						|
      let handles = win32.createPipe(secAttr, 0, win32.FILE_FLAG_OVERLAPPED);
 | 
						|
      our_pipes.push(new OutputPipe(this, handles[1]));
 | 
						|
      return handles[0];
 | 
						|
    };
 | 
						|
 | 
						|
    their_pipes[0] = pipe(false);
 | 
						|
    their_pipes[1] = pipe(true);
 | 
						|
 | 
						|
    if (stderr == "pipe") {
 | 
						|
      their_pipes[2] = pipe(true);
 | 
						|
    } else {
 | 
						|
      let srcHandle;
 | 
						|
      if (stderr == "stdout") {
 | 
						|
        srcHandle = their_pipes[1];
 | 
						|
      } else {
 | 
						|
        srcHandle = libc.GetStdHandle(win32.STD_ERROR_HANDLE);
 | 
						|
      }
 | 
						|
 | 
						|
      // If we don't have a valid stderr handle, just pass it along without duplicating.
 | 
						|
      if (
 | 
						|
        String(srcHandle) == win32.INVALID_HANDLE_VALUE ||
 | 
						|
        String(srcHandle) == win32.NULL_HANDLE_VALUE
 | 
						|
      ) {
 | 
						|
        their_pipes[2] = srcHandle;
 | 
						|
      } else {
 | 
						|
        let handle = win32.HANDLE();
 | 
						|
 | 
						|
        let curProc = libc.GetCurrentProcess();
 | 
						|
        let ok = libc.DuplicateHandle(
 | 
						|
          curProc,
 | 
						|
          srcHandle,
 | 
						|
          curProc,
 | 
						|
          handle.address(),
 | 
						|
          0,
 | 
						|
          true /* inheritable */,
 | 
						|
          win32.DUPLICATE_SAME_ACCESS
 | 
						|
        );
 | 
						|
 | 
						|
        their_pipes[2] = ok && win32.Handle(handle);
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    if (!their_pipes.every(handle => handle)) {
 | 
						|
      throw new Error("Failed to create pipe");
 | 
						|
    }
 | 
						|
 | 
						|
    this.pipes = our_pipes;
 | 
						|
 | 
						|
    return their_pipes;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Creates a null-separated, null-terminated string list.
 | 
						|
   *
 | 
						|
   * @param {Array<string>} strings
 | 
						|
   * @returns {win32.WCHAR.array}
 | 
						|
   */
 | 
						|
  stringList(strings) {
 | 
						|
    // Remove empty strings, which would terminate the list early.
 | 
						|
    strings = strings.filter(string => string);
 | 
						|
 | 
						|
    let string = strings.join("\0") + "\0\0";
 | 
						|
 | 
						|
    return win32.WCHAR.array()(string);
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Quotes a string for use as a single command argument, using Windows quoting
 | 
						|
   * conventions.
 | 
						|
   *
 | 
						|
   * @see https://msdn.microsoft.com/en-us/library/17w5ykft(v=vs.85).aspx
 | 
						|
   *
 | 
						|
   * @param {string} str
 | 
						|
   *        The argument string to quote.
 | 
						|
   * @returns {string}
 | 
						|
   */
 | 
						|
  quoteString(str) {
 | 
						|
    if (!/[\s"]/.test(str)) {
 | 
						|
      return str;
 | 
						|
    }
 | 
						|
 | 
						|
    let escaped = str.replace(/(\\*)("|$)/g, (m0, m1, m2) => {
 | 
						|
      if (m2) {
 | 
						|
        m2 = `\\${m2}`;
 | 
						|
      }
 | 
						|
      return `${m1}${m1}${m2}`;
 | 
						|
    });
 | 
						|
 | 
						|
    return `"${escaped}"`;
 | 
						|
  }
 | 
						|
 | 
						|
  spawn(options) {
 | 
						|
    let { command, arguments: args } = options;
 | 
						|
 | 
						|
    if (
 | 
						|
      /\\cmd\.exe$/i.test(command) &&
 | 
						|
      args.length == 3 &&
 | 
						|
      /^(\/S)?\/C$/i.test(args[1])
 | 
						|
    ) {
 | 
						|
      // cmd.exe is insane and requires special treatment.
 | 
						|
      args = [this.quoteString(args[0]), "/S/C", `"${args[2]}"`];
 | 
						|
    } else {
 | 
						|
      args = args.map(arg => this.quoteString(arg));
 | 
						|
    }
 | 
						|
 | 
						|
    if (/\.(bat|cmd)$/i.test(command)) {
 | 
						|
      command = io.comspec;
 | 
						|
      args = ["cmd.exe", "/s/c", `"${args.join(" ")}"`];
 | 
						|
    }
 | 
						|
 | 
						|
    let envp = this.stringList(options.environment);
 | 
						|
 | 
						|
    let handles = this.initPipes(options);
 | 
						|
 | 
						|
    let processFlags =
 | 
						|
      win32.CREATE_NO_WINDOW |
 | 
						|
      win32.CREATE_SUSPENDED |
 | 
						|
      win32.CREATE_UNICODE_ENVIRONMENT;
 | 
						|
 | 
						|
    if (io.breakAwayFromJob) {
 | 
						|
      processFlags |= win32.CREATE_BREAKAWAY_FROM_JOB;
 | 
						|
    }
 | 
						|
 | 
						|
    let startupInfoEx = new win32.STARTUPINFOEXW();
 | 
						|
    let startupInfo = startupInfoEx.StartupInfo;
 | 
						|
 | 
						|
    startupInfo.cb = win32.STARTUPINFOW.size;
 | 
						|
    startupInfo.dwFlags = win32.STARTF_USESTDHANDLES;
 | 
						|
 | 
						|
    startupInfo.hStdInput = handles[0];
 | 
						|
    startupInfo.hStdOutput = handles[1];
 | 
						|
    startupInfo.hStdError = handles[2];
 | 
						|
 | 
						|
    // Note: This needs to be kept alive until we destroy the attribute list.
 | 
						|
    let handleArray = win32.HANDLE.array()(handles);
 | 
						|
 | 
						|
    let threadAttrs = win32.createThreadAttributeList(handleArray);
 | 
						|
    if (threadAttrs) {
 | 
						|
      // If have thread attributes to pass, pass the size of the full extended
 | 
						|
      // startup info struct.
 | 
						|
      processFlags |= win32.EXTENDED_STARTUPINFO_PRESENT;
 | 
						|
      startupInfo.cb = win32.STARTUPINFOEXW.size;
 | 
						|
 | 
						|
      startupInfoEx.lpAttributeList = threadAttrs;
 | 
						|
    }
 | 
						|
 | 
						|
    let procInfo = new win32.PROCESS_INFORMATION();
 | 
						|
 | 
						|
    let errorMessage = "Failed to create process";
 | 
						|
    let ok = libc.CreateProcessW(
 | 
						|
      command,
 | 
						|
      args.join(" "),
 | 
						|
      null /* Security attributes */,
 | 
						|
      null /* Thread security attributes */,
 | 
						|
      true /* Inherits handles */,
 | 
						|
      processFlags,
 | 
						|
      envp,
 | 
						|
      options.workdir,
 | 
						|
      startupInfo.address(),
 | 
						|
      procInfo.address()
 | 
						|
    );
 | 
						|
 | 
						|
    for (let handle of new Set(handles)) {
 | 
						|
      // If any of our handles are invalid, they don't have finalizers.
 | 
						|
      if (handle && handle.dispose) {
 | 
						|
        handle.dispose();
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    if (threadAttrs) {
 | 
						|
      libc.DeleteProcThreadAttributeList(threadAttrs);
 | 
						|
    }
 | 
						|
 | 
						|
    if (ok) {
 | 
						|
      this.jobHandle = win32.Handle(libc.CreateJobObjectW(null, null));
 | 
						|
 | 
						|
      let info = win32.JOBOBJECT_EXTENDED_LIMIT_INFORMATION();
 | 
						|
      info.BasicLimitInformation.LimitFlags =
 | 
						|
        win32.JOB_OBJECT_LIMIT_BREAKAWAY_OK;
 | 
						|
 | 
						|
      ok = libc.SetInformationJobObject(
 | 
						|
        this.jobHandle,
 | 
						|
        win32.JobObjectExtendedLimitInformation,
 | 
						|
        ctypes.cast(info.address(), ctypes.voidptr_t),
 | 
						|
        info.constructor.size
 | 
						|
      );
 | 
						|
      errorMessage = `Failed to set job limits: 0x${(
 | 
						|
        ctypes.winLastError || 0
 | 
						|
      ).toString(16)}`;
 | 
						|
    }
 | 
						|
 | 
						|
    if (ok) {
 | 
						|
      ok = libc.AssignProcessToJobObject(this.jobHandle, procInfo.hProcess);
 | 
						|
      if (!ok) {
 | 
						|
        errorMessage = `Failed to attach process to job object: 0x${(
 | 
						|
          ctypes.winLastError || 0
 | 
						|
        ).toString(16)}`;
 | 
						|
        libc.TerminateProcess(procInfo.hProcess, TERMINATE_EXIT_CODE);
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    if (!ok) {
 | 
						|
      for (let pipe of this.pipes) {
 | 
						|
        pipe.close();
 | 
						|
      }
 | 
						|
      throw new Error(errorMessage);
 | 
						|
    }
 | 
						|
 | 
						|
    this.handle = win32.Handle(procInfo.hProcess);
 | 
						|
    this.pid = procInfo.dwProcessId;
 | 
						|
 | 
						|
    libc.ResumeThread(procInfo.hThread);
 | 
						|
    libc.CloseHandle(procInfo.hThread);
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Called when our process handle is signaled as active, meaning the process
 | 
						|
   * has exited.
 | 
						|
   */
 | 
						|
  onReady() {
 | 
						|
    this.wait();
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Attempts to wait for the process's exit status, without blocking. If
 | 
						|
   * successful, resolves the `exitPromise` to the process's exit value.
 | 
						|
   *
 | 
						|
   * @returns {integer|null}
 | 
						|
   *          The process's exit status, if it has already exited.
 | 
						|
   */
 | 
						|
  wait() {
 | 
						|
    if (this.exitCode !== null) {
 | 
						|
      return this.exitCode;
 | 
						|
    }
 | 
						|
 | 
						|
    let status = win32.DWORD();
 | 
						|
 | 
						|
    let ok = libc.GetExitCodeProcess(this.handle, status.address());
 | 
						|
    if (ok && status.value != win32.STILL_ACTIVE) {
 | 
						|
      let exitCode = status.value;
 | 
						|
      if (this.killed && exitCode == TERMINATE_EXIT_CODE) {
 | 
						|
        // If we forcibly terminated the process, return the force kill exit
 | 
						|
        // code that we return on other platforms.
 | 
						|
        exitCode = -9;
 | 
						|
      }
 | 
						|
 | 
						|
      this.resolveExit(exitCode);
 | 
						|
      this.exitCode = exitCode;
 | 
						|
 | 
						|
      this.handle.dispose();
 | 
						|
      this.handle = null;
 | 
						|
 | 
						|
      libc.TerminateJobObject(this.jobHandle, TERMINATE_EXIT_CODE);
 | 
						|
      this.jobHandle.dispose();
 | 
						|
      this.jobHandle = null;
 | 
						|
 | 
						|
      for (let pipe of this.pipes) {
 | 
						|
        pipe.maybeClose();
 | 
						|
      }
 | 
						|
 | 
						|
      io.updatePollEvents();
 | 
						|
 | 
						|
      return exitCode;
 | 
						|
    }
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
io = {
 | 
						|
  events: null,
 | 
						|
  eventHandlers: null,
 | 
						|
 | 
						|
  pipes: new Map(),
 | 
						|
 | 
						|
  processes: new Map(),
 | 
						|
 | 
						|
  messageCount: 0,
 | 
						|
 | 
						|
  running: true,
 | 
						|
 | 
						|
  polling: false,
 | 
						|
 | 
						|
  init(details) {
 | 
						|
    this.comspec = details.comspec;
 | 
						|
 | 
						|
    let signalEvent = ctypes.cast(
 | 
						|
      ctypes.uintptr_t(details.signalEvent),
 | 
						|
      win32.HANDLE
 | 
						|
    );
 | 
						|
    this.signal = new Signal(signalEvent);
 | 
						|
    this.updatePollEvents();
 | 
						|
 | 
						|
    this.breakAwayFromJob = details.breakAwayFromJob;
 | 
						|
 | 
						|
    setTimeout(this.loop.bind(this), 0);
 | 
						|
  },
 | 
						|
 | 
						|
  shutdown() {
 | 
						|
    if (this.running) {
 | 
						|
      this.running = false;
 | 
						|
 | 
						|
      this.signal.cleanup();
 | 
						|
      this.signal = null;
 | 
						|
 | 
						|
      self.postMessage({ msg: "close" });
 | 
						|
      self.close();
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  getPipe(pipeId) {
 | 
						|
    let pipe = this.pipes.get(pipeId);
 | 
						|
 | 
						|
    if (!pipe) {
 | 
						|
      let error = new Error("File closed");
 | 
						|
      error.errorCode = SubprocessConstants.ERROR_END_OF_FILE;
 | 
						|
      throw error;
 | 
						|
    }
 | 
						|
    return pipe;
 | 
						|
  },
 | 
						|
 | 
						|
  getProcess(processId) {
 | 
						|
    let process = this.processes.get(processId);
 | 
						|
 | 
						|
    if (!process) {
 | 
						|
      throw new Error(`Invalid process ID: ${processId}`);
 | 
						|
    }
 | 
						|
    return process;
 | 
						|
  },
 | 
						|
 | 
						|
  updatePollEvents() {
 | 
						|
    let handlers = [
 | 
						|
      this.signal,
 | 
						|
      ...this.pipes.values(),
 | 
						|
      ...this.processes.values(),
 | 
						|
    ];
 | 
						|
 | 
						|
    handlers = handlers.filter(handler => handler.event);
 | 
						|
 | 
						|
    // Our poll loop is only useful if we've got at least 1 thing to poll other than our own
 | 
						|
    // signal.
 | 
						|
    if (handlers.length == 1) {
 | 
						|
      this.polling = false;
 | 
						|
    } else if (!this.polling && this.running) {
 | 
						|
      // Restart the poll loop if necessary:
 | 
						|
      setTimeout(this.loop.bind(this), 0);
 | 
						|
      this.polling = true;
 | 
						|
    }
 | 
						|
 | 
						|
    this.eventHandlers = handlers;
 | 
						|
 | 
						|
    let handles = handlers.map(handler => handler.event);
 | 
						|
    this.events = win32.HANDLE.array()(handles);
 | 
						|
  },
 | 
						|
 | 
						|
  loop() {
 | 
						|
    this.poll();
 | 
						|
    if (this.running && this.polling) {
 | 
						|
      setTimeout(this.loop.bind(this), 0);
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  poll() {
 | 
						|
    let timeout = this.messageCount > 0 ? 0 : POLL_TIMEOUT;
 | 
						|
    for (; ; timeout = 0) {
 | 
						|
      let events = this.events;
 | 
						|
      let handlers = this.eventHandlers;
 | 
						|
 | 
						|
      let result = libc.WaitForMultipleObjects(
 | 
						|
        events.length,
 | 
						|
        events,
 | 
						|
        false,
 | 
						|
        timeout
 | 
						|
      );
 | 
						|
 | 
						|
      if (result < handlers.length) {
 | 
						|
        try {
 | 
						|
          handlers[result].onReady();
 | 
						|
        } catch (e) {
 | 
						|
          console.error(e);
 | 
						|
          debug(`Worker error: ${e} :: ${e.stack}`);
 | 
						|
          handlers[result].onError();
 | 
						|
        }
 | 
						|
      } else {
 | 
						|
        break;
 | 
						|
      }
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  addProcess(process) {
 | 
						|
    this.processes.set(process.id, process);
 | 
						|
 | 
						|
    for (let pipe of process.pipes) {
 | 
						|
      this.pipes.set(pipe.id, pipe);
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  cleanupProcess(process) {
 | 
						|
    this.processes.delete(process.id);
 | 
						|
  },
 | 
						|
};
 |