forked from mirrors/gecko-dev
		
	 996eb87d1d
			
		
	
	
		996eb87d1d
		
	
	
	
	
		
			
			Currently, Subprocess.jsm on Unix uses js-ctypes to call `posix_spawn`. This has some issues, primarily that file descriptors are inherited by the child process unless explicitly opted-out, which unfortunately a lot of code doesn't do. This patch changes it to use IPC process launching, where fd inheritance is opt-in, by: 1. Extending `base::LaunchApp` to handle a few features that Subprocess needs (setting the process's working directory, specifying the full environment, and the macOS `disclaim` flag) 2. Adding a WebIDL method to `IOUtils` to expose that function to JS (currently Unix-only; Windows could also be supported but it would probably want to use a different IDL type for strings, given that the OS APIs use 16-bit code units). 3. Replacing the part of Subprocess that invokes `posix_spawn` (and related functions) by calling that method; the rest of Subprocess's machinery to manage pipes and I/O is unchanged. (The Windows backend is also unchanged; I'm not aware of any functional issues there.) This results in some dead code, which is removed. Differential Revision: https://phabricator.services.mozilla.com/D152336
		
			
				
	
	
		
			211 lines
		
	
	
	
		
			4.2 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			211 lines
		
	
	
	
		
			4.2 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";
 | |
| 
 | |
| // This file is loaded into the same context as subprocess_worker_unix.js
 | |
| // and subprocess_worker_win.js
 | |
| /* import-globals-from subprocess_worker_unix.js */
 | |
| 
 | |
| /* exported BasePipe, BaseProcess, debug */
 | |
| 
 | |
| function debug(message) {
 | |
|   self.postMessage({ msg: "debug", message });
 | |
| }
 | |
| 
 | |
| class BasePipe {
 | |
|   constructor() {
 | |
|     this.closing = false;
 | |
|     this.closed = false;
 | |
| 
 | |
|     this.closedPromise = new Promise(resolve => {
 | |
|       this.resolveClosed = resolve;
 | |
|     });
 | |
| 
 | |
|     this.pending = [];
 | |
|   }
 | |
| 
 | |
|   shiftPending() {
 | |
|     let result = this.pending.shift();
 | |
| 
 | |
|     if (this.closing && !this.pending.length) {
 | |
|       this.close();
 | |
|     }
 | |
| 
 | |
|     return result;
 | |
|   }
 | |
| }
 | |
| 
 | |
| let nextProcessId = 0;
 | |
| 
 | |
| class BaseProcess {
 | |
|   constructor(options) {
 | |
|     this.id = nextProcessId++;
 | |
| 
 | |
|     this.exitCode = null;
 | |
| 
 | |
|     this.exitPromise = new Promise(resolve => {
 | |
|       this.resolveExit = resolve;
 | |
|     });
 | |
|     this.exitPromise.then(() => {
 | |
|       // The input file descriptors will be closed after poll
 | |
|       // reports that their input buffers are empty. If we close
 | |
|       // them now, we may lose output.
 | |
|       this.pipes[0].close(true);
 | |
|     });
 | |
| 
 | |
|     this.pid = null;
 | |
|     this.pipes = [];
 | |
| 
 | |
|     this.spawn(options);
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Waits for the process to exit and all of its pending IO operations to
 | |
|    * complete.
 | |
|    *
 | |
|    * @returns {Promise<void>}
 | |
|    */
 | |
|   awaitFinished() {
 | |
|     return Promise.all([
 | |
|       this.exitPromise,
 | |
|       ...this.pipes.map(pipe => pipe.closedPromise),
 | |
|     ]);
 | |
|   }
 | |
| }
 | |
| 
 | |
| let requests = {
 | |
|   init(details) {
 | |
|     io.init(details);
 | |
| 
 | |
|     return { data: {} };
 | |
|   },
 | |
| 
 | |
|   shutdown() {
 | |
|     io.shutdown();
 | |
| 
 | |
|     return { data: {} };
 | |
|   },
 | |
| 
 | |
|   close(pipeId, force = false) {
 | |
|     let pipe = io.getPipe(pipeId);
 | |
| 
 | |
|     return pipe.close(force).then(() => ({ data: {} }));
 | |
|   },
 | |
| 
 | |
|   spawn(options) {
 | |
|     let process = new Process(options);
 | |
|     let processId = process.id;
 | |
| 
 | |
|     io.addProcess(process);
 | |
| 
 | |
|     let fds = process.pipes.map(pipe => pipe.id);
 | |
| 
 | |
|     return { data: { processId, fds, pid: process.pid } };
 | |
|   },
 | |
| 
 | |
|   kill(processId, force = false) {
 | |
|     let process = io.getProcess(processId);
 | |
| 
 | |
|     process.kill(force ? 9 : 15);
 | |
| 
 | |
|     return { data: {} };
 | |
|   },
 | |
| 
 | |
|   wait(processId) {
 | |
|     let process = io.getProcess(processId);
 | |
| 
 | |
|     process.wait();
 | |
| 
 | |
|     process.awaitFinished().then(() => {
 | |
|       io.cleanupProcess(process);
 | |
|     });
 | |
| 
 | |
|     return process.exitPromise.then(exitCode => {
 | |
|       return { data: { exitCode } };
 | |
|     });
 | |
|   },
 | |
| 
 | |
|   read(pipeId, count) {
 | |
|     let pipe = io.getPipe(pipeId);
 | |
| 
 | |
|     return pipe.read(count).then(buffer => {
 | |
|       return { data: { buffer } };
 | |
|     });
 | |
|   },
 | |
| 
 | |
|   write(pipeId, buffer) {
 | |
|     let pipe = io.getPipe(pipeId);
 | |
| 
 | |
|     return pipe.write(buffer).then(bytesWritten => {
 | |
|       return { data: { bytesWritten } };
 | |
|     });
 | |
|   },
 | |
| 
 | |
|   getOpenFiles() {
 | |
|     return { data: new Set(io.pipes.keys()) };
 | |
|   },
 | |
| 
 | |
|   getProcesses() {
 | |
|     let data = new Map(
 | |
|       Array.from(io.processes.values())
 | |
|         .filter(proc => proc.exitCode == null)
 | |
|         .map(proc => [proc.id, proc.pid])
 | |
|     );
 | |
|     return { data };
 | |
|   },
 | |
| 
 | |
|   waitForNoProcesses() {
 | |
|     return Promise.all(
 | |
|       Array.from(io.processes.values(), proc => proc.awaitFinished())
 | |
|     );
 | |
|   },
 | |
| };
 | |
| 
 | |
| onmessage = event => {
 | |
|   io.messageCount--;
 | |
| 
 | |
|   let { msg, msgId, args } = event.data;
 | |
| 
 | |
|   new Promise(resolve => {
 | |
|     resolve(requests[msg](...args));
 | |
|   })
 | |
|     .then(result => {
 | |
|       let response = {
 | |
|         msg: "success",
 | |
|         msgId,
 | |
|         data: result.data,
 | |
|       };
 | |
| 
 | |
|       self.postMessage(response, result.transfer || []);
 | |
|     })
 | |
|     .catch(error => {
 | |
|       if (error instanceof Error) {
 | |
|         error = {
 | |
|           message: error.message,
 | |
|           fileName: error.fileName,
 | |
|           lineNumber: error.lineNumber,
 | |
|           column: error.column,
 | |
|           stack: error.stack,
 | |
|           errorCode: error.errorCode,
 | |
|         };
 | |
|       }
 | |
| 
 | |
|       self.postMessage({
 | |
|         msg: "failure",
 | |
|         msgId,
 | |
|         error,
 | |
|       });
 | |
|     })
 | |
|     .catch(error => {
 | |
|       console.error(error);
 | |
| 
 | |
|       self.postMessage({
 | |
|         msg: "failure",
 | |
|         msgId,
 | |
|         error: {},
 | |
|       });
 | |
|     });
 | |
| };
 |