forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			203 lines
		
	
	
	
		
			4.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			203 lines
		
	
	
	
		
			4.9 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/. */
 | 
						|
 | 
						|
import {
 | 
						|
  BaseProcess,
 | 
						|
  PromiseWorker,
 | 
						|
} from "resource://gre/modules/subprocess/subprocess_common.sys.mjs";
 | 
						|
 | 
						|
import { ctypes } from "resource://gre/modules/ctypes.sys.mjs";
 | 
						|
 | 
						|
var obj = { ctypes };
 | 
						|
Services.scriptloader.loadSubScript(
 | 
						|
  "resource://gre/modules/subprocess/subprocess_shared.js",
 | 
						|
  obj
 | 
						|
);
 | 
						|
Services.scriptloader.loadSubScript(
 | 
						|
  "resource://gre/modules/subprocess/subprocess_shared_unix.js",
 | 
						|
  obj
 | 
						|
);
 | 
						|
 | 
						|
const { SubprocessConstants, LIBC } = obj;
 | 
						|
 | 
						|
// libc is exported for tests.
 | 
						|
export var libc = obj.libc;
 | 
						|
 | 
						|
class UnixPromiseWorker extends PromiseWorker {
 | 
						|
  constructor(...args) {
 | 
						|
    super(...args);
 | 
						|
 | 
						|
    let fds = ctypes.int.array(2)();
 | 
						|
    let res = libc.pipe(fds);
 | 
						|
    if (res == -1) {
 | 
						|
      throw new Error("Unable to create pipe");
 | 
						|
    }
 | 
						|
 | 
						|
    this.signalFd = fds[1];
 | 
						|
 | 
						|
    libc.fcntl(fds[0], LIBC.F_SETFL, LIBC.O_NONBLOCK);
 | 
						|
    libc.fcntl(fds[0], LIBC.F_SETFD, LIBC.FD_CLOEXEC);
 | 
						|
    libc.fcntl(fds[1], LIBC.F_SETFD, LIBC.FD_CLOEXEC);
 | 
						|
 | 
						|
    this.call("init", [{ signalFd: fds[0] }]);
 | 
						|
  }
 | 
						|
 | 
						|
  closePipe() {
 | 
						|
    if (this.signalFd) {
 | 
						|
      libc.close(this.signalFd);
 | 
						|
      this.signalFd = null;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  onClose() {
 | 
						|
    this.closePipe();
 | 
						|
    super.onClose();
 | 
						|
  }
 | 
						|
 | 
						|
  signalWorker() {
 | 
						|
    libc.write(this.signalFd, new ArrayBuffer(1), 1);
 | 
						|
  }
 | 
						|
 | 
						|
  postMessage(...args) {
 | 
						|
    this.signalWorker();
 | 
						|
    return super.postMessage(...args);
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
class Process extends BaseProcess {
 | 
						|
  static get WORKER_URL() {
 | 
						|
    return "resource://gre/modules/subprocess/subprocess_unix.worker.js";
 | 
						|
  }
 | 
						|
 | 
						|
  static get WorkerClass() {
 | 
						|
    return UnixPromiseWorker;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
// Convert a null-terminated char pointer into a sized char array, and then
 | 
						|
// convert that into a JS typed array.
 | 
						|
// The resulting array will not be null-terminated.
 | 
						|
function ptrToUint8Array(input) {
 | 
						|
  let { cast, uint8_t } = ctypes;
 | 
						|
 | 
						|
  let len = 0;
 | 
						|
  for (
 | 
						|
    let ptr = cast(input, uint8_t.ptr);
 | 
						|
    ptr.contents;
 | 
						|
    ptr = ptr.increment()
 | 
						|
  ) {
 | 
						|
    len++;
 | 
						|
  }
 | 
						|
 | 
						|
  let aryPtr = cast(input, uint8_t.array(len).ptr);
 | 
						|
  return new Uint8Array(aryPtr.contents);
 | 
						|
}
 | 
						|
 | 
						|
var SubprocessUnix = {
 | 
						|
  Process,
 | 
						|
 | 
						|
  call(options) {
 | 
						|
    return Process.create(options);
 | 
						|
  },
 | 
						|
 | 
						|
  *getEnvironment() {
 | 
						|
    let environ;
 | 
						|
    if (Services.appinfo.OS === "Darwin") {
 | 
						|
      environ = libc._NSGetEnviron().contents;
 | 
						|
    } else {
 | 
						|
      environ = libc.environ;
 | 
						|
    }
 | 
						|
 | 
						|
    const EQUAL = "=".charCodeAt(0);
 | 
						|
    let decoder = new TextDecoder("utf-8", { fatal: true });
 | 
						|
 | 
						|
    function decode(array) {
 | 
						|
      try {
 | 
						|
        return decoder.decode(array);
 | 
						|
      } catch (e) {
 | 
						|
        return array;
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    for (
 | 
						|
      let envp = environ;
 | 
						|
      !envp.isNull() && !envp.contents.isNull();
 | 
						|
      envp = envp.increment()
 | 
						|
    ) {
 | 
						|
      let buf = ptrToUint8Array(envp.contents);
 | 
						|
 | 
						|
      for (let i = 0; i < buf.length; i++) {
 | 
						|
        if (buf[i] == EQUAL) {
 | 
						|
          yield [decode(buf.subarray(0, i)), decode(buf.subarray(i + 1))];
 | 
						|
          break;
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  isExecutableFile: async function isExecutable(path) {
 | 
						|
    if (!PathUtils.isAbsolute(path)) {
 | 
						|
      return false;
 | 
						|
    }
 | 
						|
 | 
						|
    try {
 | 
						|
      let info = await IOUtils.stat(path);
 | 
						|
 | 
						|
      // FIXME: We really want access(path, X_OK) here, but IOUtils does not
 | 
						|
      // support it.
 | 
						|
      return info.type !== "directory" && info.permissions & 0o111;
 | 
						|
    } catch (e) {
 | 
						|
      return false;
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Searches for the given executable file in the system executable
 | 
						|
   * file paths as specified by the PATH environment variable.
 | 
						|
   *
 | 
						|
   * On Windows, if the unadorned filename cannot be found, the
 | 
						|
   * extensions in the semicolon-separated list in the PATHEXT
 | 
						|
   * environment variable are successively appended to the original
 | 
						|
   * name and searched for in turn.
 | 
						|
   *
 | 
						|
   * @param {string} bin
 | 
						|
   *        The name of the executable to find.
 | 
						|
   * @param {object} environment
 | 
						|
   *        An object containing a key for each environment variable to be used
 | 
						|
   *        in the search.
 | 
						|
   * @returns {Promise<string>}
 | 
						|
   */
 | 
						|
  async pathSearch(bin, environment) {
 | 
						|
    if (PathUtils.isAbsolute(bin)) {
 | 
						|
      if (await this.isExecutableFile(bin)) {
 | 
						|
        return bin;
 | 
						|
      }
 | 
						|
      let error = new Error(
 | 
						|
        `File at path "${bin}" does not exist, or is not executable`
 | 
						|
      );
 | 
						|
      error.errorCode = SubprocessConstants.ERROR_BAD_EXECUTABLE;
 | 
						|
      throw error;
 | 
						|
    }
 | 
						|
 | 
						|
    let dirs = [];
 | 
						|
    if (typeof environment.PATH === "string") {
 | 
						|
      dirs = environment.PATH.split(":");
 | 
						|
    }
 | 
						|
 | 
						|
    for (let dir of dirs) {
 | 
						|
      let path = PathUtils.join(dir, bin);
 | 
						|
 | 
						|
      if (await this.isExecutableFile(path)) {
 | 
						|
        return path;
 | 
						|
      }
 | 
						|
    }
 | 
						|
    let error = new Error(`Executable not found: ${bin}`);
 | 
						|
    error.errorCode = SubprocessConstants.ERROR_BAD_EXECUTABLE;
 | 
						|
    throw error;
 | 
						|
  },
 | 
						|
};
 | 
						|
 | 
						|
export var SubprocessImpl = SubprocessUnix;
 |