mirror of
https://github.com/denoland/deno.git
synced 2024-12-22 15:24:46 -05:00
refactor(runtime): merge "spawn" into "process" (#18022)
This commit merges "runtime/js/40_spawn.js" into "runtime/js/40_process.js", and "runtime::ops::spawn" into "runtime::ops::process". It makes little sense to have them separated given that we want to factor out these APIs into a separate extension crate.
This commit is contained in:
parent
d4807f458e
commit
de0d148d93
9 changed files with 792 additions and 827 deletions
|
@ -207,7 +207,6 @@ mod startup_snapshot {
|
||||||
"40_http.js",
|
"40_http.js",
|
||||||
"40_process.js",
|
"40_process.js",
|
||||||
"40_signals.js",
|
"40_signals.js",
|
||||||
"40_spawn.js",
|
|
||||||
"40_tty.js",
|
"40_tty.js",
|
||||||
"41_prompt.js",
|
"41_prompt.js",
|
||||||
"90_deno_ns.js",
|
"90_deno_ns.js",
|
||||||
|
|
|
@ -2,10 +2,6 @@
|
||||||
|
|
||||||
const core = globalThis.Deno.core;
|
const core = globalThis.Deno.core;
|
||||||
const ops = core.ops;
|
const ops = core.ops;
|
||||||
import { FsFile } from "internal:runtime/30_fs.js";
|
|
||||||
import { readAll } from "internal:deno_io/12_io.js";
|
|
||||||
import { pathFromURL } from "internal:runtime/06_util.js";
|
|
||||||
import { assert } from "internal:deno_web/00_infra.js";
|
|
||||||
const primordials = globalThis.__bootstrap.primordials;
|
const primordials = globalThis.__bootstrap.primordials;
|
||||||
const {
|
const {
|
||||||
ArrayPrototypeMap,
|
ArrayPrototypeMap,
|
||||||
|
@ -14,7 +10,25 @@ const {
|
||||||
ObjectEntries,
|
ObjectEntries,
|
||||||
SafeArrayIterator,
|
SafeArrayIterator,
|
||||||
String,
|
String,
|
||||||
|
ObjectPrototypeIsPrototypeOf,
|
||||||
|
PromisePrototypeThen,
|
||||||
|
SafePromiseAll,
|
||||||
|
SymbolFor,
|
||||||
|
Symbol,
|
||||||
} = primordials;
|
} = primordials;
|
||||||
|
import { FsFile } from "internal:runtime/30_fs.js";
|
||||||
|
import { readAll } from "internal:deno_io/12_io.js";
|
||||||
|
import { pathFromURL } from "internal:runtime/06_util.js";
|
||||||
|
import { assert } from "internal:deno_web/00_infra.js";
|
||||||
|
import * as abortSignal from "internal:deno_web/03_abort_signal.js";
|
||||||
|
import {
|
||||||
|
readableStreamCollectIntoUint8Array,
|
||||||
|
readableStreamForRidUnrefable,
|
||||||
|
readableStreamForRidUnrefableRef,
|
||||||
|
readableStreamForRidUnrefableUnref,
|
||||||
|
ReadableStreamPrototype,
|
||||||
|
writableStreamForRid,
|
||||||
|
} from "internal:deno_web/06_streams.js";
|
||||||
|
|
||||||
function opKill(pid, signo, apiName) {
|
function opKill(pid, signo, apiName) {
|
||||||
ops.op_kill(pid, signo, apiName);
|
ops.op_kill(pid, signo, apiName);
|
||||||
|
@ -130,4 +144,301 @@ function run({
|
||||||
return new Process(res);
|
return new Process(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { kill, Process, run };
|
const illegalConstructorKey = Symbol("illegalConstructorKey");
|
||||||
|
const promiseIdSymbol = SymbolFor("Deno.core.internalPromiseId");
|
||||||
|
|
||||||
|
function spawnChildInner(opFn, command, apiName, {
|
||||||
|
args = [],
|
||||||
|
cwd = undefined,
|
||||||
|
clearEnv = false,
|
||||||
|
env = {},
|
||||||
|
uid = undefined,
|
||||||
|
gid = undefined,
|
||||||
|
stdin = "null",
|
||||||
|
stdout = "piped",
|
||||||
|
stderr = "piped",
|
||||||
|
signal = undefined,
|
||||||
|
windowsRawArguments = false,
|
||||||
|
} = {}) {
|
||||||
|
const child = opFn({
|
||||||
|
cmd: pathFromURL(command),
|
||||||
|
args: ArrayPrototypeMap(args, String),
|
||||||
|
cwd: pathFromURL(cwd),
|
||||||
|
clearEnv,
|
||||||
|
env: ObjectEntries(env),
|
||||||
|
uid,
|
||||||
|
gid,
|
||||||
|
stdin,
|
||||||
|
stdout,
|
||||||
|
stderr,
|
||||||
|
windowsRawArguments,
|
||||||
|
}, apiName);
|
||||||
|
return new ChildProcess(illegalConstructorKey, {
|
||||||
|
...child,
|
||||||
|
signal,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function spawnChild(command, options = {}) {
|
||||||
|
return spawnChildInner(
|
||||||
|
ops.op_spawn_child,
|
||||||
|
command,
|
||||||
|
"Deno.Command().spawn()",
|
||||||
|
options,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function collectOutput(readableStream) {
|
||||||
|
if (
|
||||||
|
!(ObjectPrototypeIsPrototypeOf(ReadableStreamPrototype, readableStream))
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return readableStreamCollectIntoUint8Array(readableStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
class ChildProcess {
|
||||||
|
#rid;
|
||||||
|
#waitPromiseId;
|
||||||
|
#unrefed = false;
|
||||||
|
|
||||||
|
#pid;
|
||||||
|
get pid() {
|
||||||
|
return this.#pid;
|
||||||
|
}
|
||||||
|
|
||||||
|
#stdin = null;
|
||||||
|
get stdin() {
|
||||||
|
if (this.#stdin == null) {
|
||||||
|
throw new TypeError("stdin is not piped");
|
||||||
|
}
|
||||||
|
return this.#stdin;
|
||||||
|
}
|
||||||
|
|
||||||
|
#stdoutRid;
|
||||||
|
#stdout = null;
|
||||||
|
get stdout() {
|
||||||
|
if (this.#stdout == null) {
|
||||||
|
throw new TypeError("stdout is not piped");
|
||||||
|
}
|
||||||
|
return this.#stdout;
|
||||||
|
}
|
||||||
|
|
||||||
|
#stderrRid;
|
||||||
|
#stderr = null;
|
||||||
|
get stderr() {
|
||||||
|
if (this.#stderr == null) {
|
||||||
|
throw new TypeError("stderr is not piped");
|
||||||
|
}
|
||||||
|
return this.#stderr;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(key = null, {
|
||||||
|
signal,
|
||||||
|
rid,
|
||||||
|
pid,
|
||||||
|
stdinRid,
|
||||||
|
stdoutRid,
|
||||||
|
stderrRid,
|
||||||
|
} = null) {
|
||||||
|
if (key !== illegalConstructorKey) {
|
||||||
|
throw new TypeError("Illegal constructor.");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.#rid = rid;
|
||||||
|
this.#pid = pid;
|
||||||
|
|
||||||
|
if (stdinRid !== null) {
|
||||||
|
this.#stdin = writableStreamForRid(stdinRid);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stdoutRid !== null) {
|
||||||
|
this.#stdoutRid = stdoutRid;
|
||||||
|
this.#stdout = readableStreamForRidUnrefable(stdoutRid);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stderrRid !== null) {
|
||||||
|
this.#stderrRid = stderrRid;
|
||||||
|
this.#stderr = readableStreamForRidUnrefable(stderrRid);
|
||||||
|
}
|
||||||
|
|
||||||
|
const onAbort = () => this.kill("SIGTERM");
|
||||||
|
signal?.[abortSignal.add](onAbort);
|
||||||
|
|
||||||
|
const waitPromise = core.opAsync("op_spawn_wait", this.#rid);
|
||||||
|
this.#waitPromiseId = waitPromise[promiseIdSymbol];
|
||||||
|
this.#status = PromisePrototypeThen(waitPromise, (res) => {
|
||||||
|
this.#rid = null;
|
||||||
|
signal?.[abortSignal.remove](onAbort);
|
||||||
|
return res;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#status;
|
||||||
|
get status() {
|
||||||
|
return this.#status;
|
||||||
|
}
|
||||||
|
|
||||||
|
async output() {
|
||||||
|
if (this.#stdout?.locked) {
|
||||||
|
throw new TypeError(
|
||||||
|
"Can't collect output because stdout is locked",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (this.#stderr?.locked) {
|
||||||
|
throw new TypeError(
|
||||||
|
"Can't collect output because stderr is locked",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { 0: status, 1: stdout, 2: stderr } = await SafePromiseAll([
|
||||||
|
this.#status,
|
||||||
|
collectOutput(this.#stdout),
|
||||||
|
collectOutput(this.#stderr),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: status.success,
|
||||||
|
code: status.code,
|
||||||
|
signal: status.signal,
|
||||||
|
get stdout() {
|
||||||
|
if (stdout == null) {
|
||||||
|
throw new TypeError("stdout is not piped");
|
||||||
|
}
|
||||||
|
return stdout;
|
||||||
|
},
|
||||||
|
get stderr() {
|
||||||
|
if (stderr == null) {
|
||||||
|
throw new TypeError("stderr is not piped");
|
||||||
|
}
|
||||||
|
return stderr;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
kill(signo = "SIGTERM") {
|
||||||
|
if (this.#rid === null) {
|
||||||
|
throw new TypeError("Child process has already terminated.");
|
||||||
|
}
|
||||||
|
ops.op_kill(this.#pid, signo, "Deno.Child.kill()");
|
||||||
|
}
|
||||||
|
|
||||||
|
ref() {
|
||||||
|
this.#unrefed = false;
|
||||||
|
core.refOp(this.#waitPromiseId);
|
||||||
|
if (this.#stdout) readableStreamForRidUnrefableRef(this.#stdout);
|
||||||
|
if (this.#stderr) readableStreamForRidUnrefableRef(this.#stderr);
|
||||||
|
}
|
||||||
|
|
||||||
|
unref() {
|
||||||
|
this.#unrefed = true;
|
||||||
|
core.unrefOp(this.#waitPromiseId);
|
||||||
|
if (this.#stdout) readableStreamForRidUnrefableUnref(this.#stdout);
|
||||||
|
if (this.#stderr) readableStreamForRidUnrefableUnref(this.#stderr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function spawn(command, options) {
|
||||||
|
if (options?.stdin === "piped") {
|
||||||
|
throw new TypeError(
|
||||||
|
"Piped stdin is not supported for this function, use 'Deno.Command().spawn()' instead",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return spawnChildInner(
|
||||||
|
ops.op_spawn_child,
|
||||||
|
command,
|
||||||
|
"Deno.Command().output()",
|
||||||
|
options,
|
||||||
|
)
|
||||||
|
.output();
|
||||||
|
}
|
||||||
|
|
||||||
|
function spawnSync(command, {
|
||||||
|
args = [],
|
||||||
|
cwd = undefined,
|
||||||
|
clearEnv = false,
|
||||||
|
env = {},
|
||||||
|
uid = undefined,
|
||||||
|
gid = undefined,
|
||||||
|
stdin = "null",
|
||||||
|
stdout = "piped",
|
||||||
|
stderr = "piped",
|
||||||
|
windowsRawArguments = false,
|
||||||
|
} = {}) {
|
||||||
|
if (stdin === "piped") {
|
||||||
|
throw new TypeError(
|
||||||
|
"Piped stdin is not supported for this function, use 'Deno.Command().spawn()' instead",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const result = ops.op_spawn_sync({
|
||||||
|
cmd: pathFromURL(command),
|
||||||
|
args: ArrayPrototypeMap(args, String),
|
||||||
|
cwd: pathFromURL(cwd),
|
||||||
|
clearEnv,
|
||||||
|
env: ObjectEntries(env),
|
||||||
|
uid,
|
||||||
|
gid,
|
||||||
|
stdin,
|
||||||
|
stdout,
|
||||||
|
stderr,
|
||||||
|
windowsRawArguments,
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
success: result.status.success,
|
||||||
|
code: result.status.code,
|
||||||
|
signal: result.status.signal,
|
||||||
|
get stdout() {
|
||||||
|
if (result.stdout == null) {
|
||||||
|
throw new TypeError("stdout is not piped");
|
||||||
|
}
|
||||||
|
return result.stdout;
|
||||||
|
},
|
||||||
|
get stderr() {
|
||||||
|
if (result.stderr == null) {
|
||||||
|
throw new TypeError("stderr is not piped");
|
||||||
|
}
|
||||||
|
return result.stderr;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
class Command {
|
||||||
|
#command;
|
||||||
|
#options;
|
||||||
|
|
||||||
|
constructor(command, options) {
|
||||||
|
this.#command = command;
|
||||||
|
this.#options = options;
|
||||||
|
}
|
||||||
|
|
||||||
|
output() {
|
||||||
|
if (this.#options?.stdin === "piped") {
|
||||||
|
throw new TypeError(
|
||||||
|
"Piped stdin is not supported for this function, use 'Deno.Command.spawn()' instead",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return spawn(this.#command, this.#options);
|
||||||
|
}
|
||||||
|
|
||||||
|
outputSync() {
|
||||||
|
if (this.#options?.stdin === "piped") {
|
||||||
|
throw new TypeError(
|
||||||
|
"Piped stdin is not supported for this function, use 'Deno.Command.spawn()' instead",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return spawnSync(this.#command, this.#options);
|
||||||
|
}
|
||||||
|
|
||||||
|
spawn() {
|
||||||
|
const options = {
|
||||||
|
...(this.#options ?? {}),
|
||||||
|
stdout: this.#options?.stdout ?? "inherit",
|
||||||
|
stderr: this.#options?.stderr ?? "inherit",
|
||||||
|
stdin: this.#options?.stdin ?? "inherit",
|
||||||
|
};
|
||||||
|
return spawnChild(this.#command, options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { ChildProcess, Command, kill, Process, run };
|
||||||
|
|
|
@ -1,326 +0,0 @@
|
||||||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
|
||||||
|
|
||||||
const core = globalThis.Deno.core;
|
|
||||||
const ops = core.ops;
|
|
||||||
const primordials = globalThis.__bootstrap.primordials;
|
|
||||||
import { pathFromURL } from "internal:runtime/06_util.js";
|
|
||||||
import { add, remove } from "internal:deno_web/03_abort_signal.js";
|
|
||||||
const {
|
|
||||||
ArrayPrototypeMap,
|
|
||||||
ObjectEntries,
|
|
||||||
ObjectPrototypeIsPrototypeOf,
|
|
||||||
String,
|
|
||||||
TypeError,
|
|
||||||
PromisePrototypeThen,
|
|
||||||
SafePromiseAll,
|
|
||||||
SymbolFor,
|
|
||||||
Symbol,
|
|
||||||
} = primordials;
|
|
||||||
import {
|
|
||||||
readableStreamCollectIntoUint8Array,
|
|
||||||
readableStreamForRidUnrefable,
|
|
||||||
readableStreamForRidUnrefableRef,
|
|
||||||
readableStreamForRidUnrefableUnref,
|
|
||||||
ReadableStreamPrototype,
|
|
||||||
writableStreamForRid,
|
|
||||||
} from "internal:deno_web/06_streams.js";
|
|
||||||
|
|
||||||
const illegalConstructorKey = Symbol("illegalConstructorKey");
|
|
||||||
|
|
||||||
const promiseIdSymbol = SymbolFor("Deno.core.internalPromiseId");
|
|
||||||
|
|
||||||
function spawnChildInner(opFn, command, apiName, {
|
|
||||||
args = [],
|
|
||||||
cwd = undefined,
|
|
||||||
clearEnv = false,
|
|
||||||
env = {},
|
|
||||||
uid = undefined,
|
|
||||||
gid = undefined,
|
|
||||||
stdin = "null",
|
|
||||||
stdout = "piped",
|
|
||||||
stderr = "piped",
|
|
||||||
signal = undefined,
|
|
||||||
windowsRawArguments = false,
|
|
||||||
} = {}) {
|
|
||||||
const child = opFn({
|
|
||||||
cmd: pathFromURL(command),
|
|
||||||
args: ArrayPrototypeMap(args, String),
|
|
||||||
cwd: pathFromURL(cwd),
|
|
||||||
clearEnv,
|
|
||||||
env: ObjectEntries(env),
|
|
||||||
uid,
|
|
||||||
gid,
|
|
||||||
stdin,
|
|
||||||
stdout,
|
|
||||||
stderr,
|
|
||||||
windowsRawArguments,
|
|
||||||
}, apiName);
|
|
||||||
return new ChildProcess(illegalConstructorKey, {
|
|
||||||
...child,
|
|
||||||
signal,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function spawnChild(command, options = {}) {
|
|
||||||
return spawnChildInner(
|
|
||||||
ops.op_spawn_child,
|
|
||||||
command,
|
|
||||||
"Deno.Command().spawn()",
|
|
||||||
options,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function collectOutput(readableStream) {
|
|
||||||
if (
|
|
||||||
!(ObjectPrototypeIsPrototypeOf(ReadableStreamPrototype, readableStream))
|
|
||||||
) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return readableStreamCollectIntoUint8Array(readableStream);
|
|
||||||
}
|
|
||||||
|
|
||||||
class ChildProcess {
|
|
||||||
#rid;
|
|
||||||
#waitPromiseId;
|
|
||||||
#unrefed = false;
|
|
||||||
|
|
||||||
#pid;
|
|
||||||
get pid() {
|
|
||||||
return this.#pid;
|
|
||||||
}
|
|
||||||
|
|
||||||
#stdin = null;
|
|
||||||
get stdin() {
|
|
||||||
if (this.#stdin == null) {
|
|
||||||
throw new TypeError("stdin is not piped");
|
|
||||||
}
|
|
||||||
return this.#stdin;
|
|
||||||
}
|
|
||||||
|
|
||||||
#stdoutRid;
|
|
||||||
#stdout = null;
|
|
||||||
get stdout() {
|
|
||||||
if (this.#stdout == null) {
|
|
||||||
throw new TypeError("stdout is not piped");
|
|
||||||
}
|
|
||||||
return this.#stdout;
|
|
||||||
}
|
|
||||||
|
|
||||||
#stderrRid;
|
|
||||||
#stderr = null;
|
|
||||||
get stderr() {
|
|
||||||
if (this.#stderr == null) {
|
|
||||||
throw new TypeError("stderr is not piped");
|
|
||||||
}
|
|
||||||
return this.#stderr;
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(key = null, {
|
|
||||||
signal,
|
|
||||||
rid,
|
|
||||||
pid,
|
|
||||||
stdinRid,
|
|
||||||
stdoutRid,
|
|
||||||
stderrRid,
|
|
||||||
} = null) {
|
|
||||||
if (key !== illegalConstructorKey) {
|
|
||||||
throw new TypeError("Illegal constructor.");
|
|
||||||
}
|
|
||||||
|
|
||||||
this.#rid = rid;
|
|
||||||
this.#pid = pid;
|
|
||||||
|
|
||||||
if (stdinRid !== null) {
|
|
||||||
this.#stdin = writableStreamForRid(stdinRid);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stdoutRid !== null) {
|
|
||||||
this.#stdoutRid = stdoutRid;
|
|
||||||
this.#stdout = readableStreamForRidUnrefable(stdoutRid);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stderrRid !== null) {
|
|
||||||
this.#stderrRid = stderrRid;
|
|
||||||
this.#stderr = readableStreamForRidUnrefable(stderrRid);
|
|
||||||
}
|
|
||||||
|
|
||||||
const onAbort = () => this.kill("SIGTERM");
|
|
||||||
signal?.[add](onAbort);
|
|
||||||
|
|
||||||
const waitPromise = core.opAsync("op_spawn_wait", this.#rid);
|
|
||||||
this.#waitPromiseId = waitPromise[promiseIdSymbol];
|
|
||||||
this.#status = PromisePrototypeThen(waitPromise, (res) => {
|
|
||||||
this.#rid = null;
|
|
||||||
signal?.[remove](onAbort);
|
|
||||||
return res;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#status;
|
|
||||||
get status() {
|
|
||||||
return this.#status;
|
|
||||||
}
|
|
||||||
|
|
||||||
async output() {
|
|
||||||
if (this.#stdout?.locked) {
|
|
||||||
throw new TypeError(
|
|
||||||
"Can't collect output because stdout is locked",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (this.#stderr?.locked) {
|
|
||||||
throw new TypeError(
|
|
||||||
"Can't collect output because stderr is locked",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { 0: status, 1: stdout, 2: stderr } = await SafePromiseAll([
|
|
||||||
this.#status,
|
|
||||||
collectOutput(this.#stdout),
|
|
||||||
collectOutput(this.#stderr),
|
|
||||||
]);
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: status.success,
|
|
||||||
code: status.code,
|
|
||||||
signal: status.signal,
|
|
||||||
get stdout() {
|
|
||||||
if (stdout == null) {
|
|
||||||
throw new TypeError("stdout is not piped");
|
|
||||||
}
|
|
||||||
return stdout;
|
|
||||||
},
|
|
||||||
get stderr() {
|
|
||||||
if (stderr == null) {
|
|
||||||
throw new TypeError("stderr is not piped");
|
|
||||||
}
|
|
||||||
return stderr;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
kill(signo = "SIGTERM") {
|
|
||||||
if (this.#rid === null) {
|
|
||||||
throw new TypeError("Child process has already terminated.");
|
|
||||||
}
|
|
||||||
ops.op_kill(this.#pid, signo, "Deno.Child.kill()");
|
|
||||||
}
|
|
||||||
|
|
||||||
ref() {
|
|
||||||
this.#unrefed = false;
|
|
||||||
core.refOp(this.#waitPromiseId);
|
|
||||||
if (this.#stdout) readableStreamForRidUnrefableRef(this.#stdout);
|
|
||||||
if (this.#stderr) readableStreamForRidUnrefableRef(this.#stderr);
|
|
||||||
}
|
|
||||||
|
|
||||||
unref() {
|
|
||||||
this.#unrefed = true;
|
|
||||||
core.unrefOp(this.#waitPromiseId);
|
|
||||||
if (this.#stdout) readableStreamForRidUnrefableUnref(this.#stdout);
|
|
||||||
if (this.#stderr) readableStreamForRidUnrefableUnref(this.#stderr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function spawn(command, options) {
|
|
||||||
if (options?.stdin === "piped") {
|
|
||||||
throw new TypeError(
|
|
||||||
"Piped stdin is not supported for this function, use 'Deno.Command().spawn()' instead",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return spawnChildInner(
|
|
||||||
ops.op_spawn_child,
|
|
||||||
command,
|
|
||||||
"Deno.Command().output()",
|
|
||||||
options,
|
|
||||||
)
|
|
||||||
.output();
|
|
||||||
}
|
|
||||||
|
|
||||||
function spawnSync(command, {
|
|
||||||
args = [],
|
|
||||||
cwd = undefined,
|
|
||||||
clearEnv = false,
|
|
||||||
env = {},
|
|
||||||
uid = undefined,
|
|
||||||
gid = undefined,
|
|
||||||
stdin = "null",
|
|
||||||
stdout = "piped",
|
|
||||||
stderr = "piped",
|
|
||||||
windowsRawArguments = false,
|
|
||||||
} = {}) {
|
|
||||||
if (stdin === "piped") {
|
|
||||||
throw new TypeError(
|
|
||||||
"Piped stdin is not supported for this function, use 'Deno.Command().spawn()' instead",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const result = ops.op_spawn_sync({
|
|
||||||
cmd: pathFromURL(command),
|
|
||||||
args: ArrayPrototypeMap(args, String),
|
|
||||||
cwd: pathFromURL(cwd),
|
|
||||||
clearEnv,
|
|
||||||
env: ObjectEntries(env),
|
|
||||||
uid,
|
|
||||||
gid,
|
|
||||||
stdin,
|
|
||||||
stdout,
|
|
||||||
stderr,
|
|
||||||
windowsRawArguments,
|
|
||||||
});
|
|
||||||
return {
|
|
||||||
success: result.status.success,
|
|
||||||
code: result.status.code,
|
|
||||||
signal: result.status.signal,
|
|
||||||
get stdout() {
|
|
||||||
if (result.stdout == null) {
|
|
||||||
throw new TypeError("stdout is not piped");
|
|
||||||
}
|
|
||||||
return result.stdout;
|
|
||||||
},
|
|
||||||
get stderr() {
|
|
||||||
if (result.stderr == null) {
|
|
||||||
throw new TypeError("stderr is not piped");
|
|
||||||
}
|
|
||||||
return result.stderr;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
class Command {
|
|
||||||
#command;
|
|
||||||
#options;
|
|
||||||
|
|
||||||
constructor(command, options) {
|
|
||||||
this.#command = command;
|
|
||||||
this.#options = options;
|
|
||||||
}
|
|
||||||
|
|
||||||
output() {
|
|
||||||
if (this.#options?.stdin === "piped") {
|
|
||||||
throw new TypeError(
|
|
||||||
"Piped stdin is not supported for this function, use 'Deno.Command.spawn()' instead",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return spawn(this.#command, this.#options);
|
|
||||||
}
|
|
||||||
|
|
||||||
outputSync() {
|
|
||||||
if (this.#options?.stdin === "piped") {
|
|
||||||
throw new TypeError(
|
|
||||||
"Piped stdin is not supported for this function, use 'Deno.Command.spawn()' instead",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return spawnSync(this.#command, this.#options);
|
|
||||||
}
|
|
||||||
|
|
||||||
spawn() {
|
|
||||||
const options = {
|
|
||||||
...(this.#options ?? {}),
|
|
||||||
stdout: this.#options?.stdout ?? "inherit",
|
|
||||||
stderr: this.#options?.stderr ?? "inherit",
|
|
||||||
stdin: this.#options?.stdin ?? "inherit",
|
|
||||||
};
|
|
||||||
return spawnChild(this.#command, options);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export { ChildProcess, Command };
|
|
|
@ -22,7 +22,6 @@ import * as fsEvents from "internal:runtime/40_fs_events.js";
|
||||||
import * as process from "internal:runtime/40_process.js";
|
import * as process from "internal:runtime/40_process.js";
|
||||||
import * as signals from "internal:runtime/40_signals.js";
|
import * as signals from "internal:runtime/40_signals.js";
|
||||||
import * as tty from "internal:runtime/40_tty.js";
|
import * as tty from "internal:runtime/40_tty.js";
|
||||||
import * as spawn from "internal:runtime/40_spawn.js";
|
|
||||||
// TODO(bartlomieju): this is funky we have two `http` imports
|
// TODO(bartlomieju): this is funky we have two `http` imports
|
||||||
import * as httpRuntime from "internal:runtime/40_http.js";
|
import * as httpRuntime from "internal:runtime/40_http.js";
|
||||||
|
|
||||||
|
@ -148,9 +147,9 @@ const denoNs = {
|
||||||
consoleSize: tty.consoleSize,
|
consoleSize: tty.consoleSize,
|
||||||
gid: os.gid,
|
gid: os.gid,
|
||||||
uid: os.uid,
|
uid: os.uid,
|
||||||
Command: spawn.Command,
|
Command: process.Command,
|
||||||
// TODO(bartlomieju): why is this exported?
|
// TODO(bartlomieju): why is this exported?
|
||||||
ChildProcess: spawn.ChildProcess,
|
ChildProcess: process.ChildProcess,
|
||||||
};
|
};
|
||||||
|
|
||||||
const denoNsUnstable = {
|
const denoNsUnstable = {
|
||||||
|
|
|
@ -8,7 +8,6 @@ pub mod permissions;
|
||||||
pub mod process;
|
pub mod process;
|
||||||
pub mod runtime;
|
pub mod runtime;
|
||||||
pub mod signal;
|
pub mod signal;
|
||||||
pub mod spawn;
|
|
||||||
pub mod tty;
|
pub mod tty;
|
||||||
mod utils;
|
mod utils;
|
||||||
pub mod web_worker;
|
pub mod web_worker;
|
||||||
|
|
|
@ -1,13 +1,10 @@
|
||||||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||||
|
|
||||||
|
use super::check_unstable;
|
||||||
|
use super::signal;
|
||||||
use crate::permissions::PermissionsContainer;
|
use crate::permissions::PermissionsContainer;
|
||||||
use deno_core::error::AnyError;
|
use deno_core::error::AnyError;
|
||||||
use deno_core::op;
|
use deno_core::op;
|
||||||
use deno_io::ChildStderrResource;
|
|
||||||
use deno_io::ChildStdinResource;
|
|
||||||
use deno_io::ChildStdoutResource;
|
|
||||||
use deno_io::StdFileResource;
|
|
||||||
|
|
||||||
use deno_core::serde_json;
|
use deno_core::serde_json;
|
||||||
use deno_core::AsyncMutFuture;
|
use deno_core::AsyncMutFuture;
|
||||||
use deno_core::AsyncRefCell;
|
use deno_core::AsyncRefCell;
|
||||||
|
@ -16,21 +13,26 @@ use deno_core::OpState;
|
||||||
use deno_core::RcRef;
|
use deno_core::RcRef;
|
||||||
use deno_core::Resource;
|
use deno_core::Resource;
|
||||||
use deno_core::ResourceId;
|
use deno_core::ResourceId;
|
||||||
|
use deno_core::ZeroCopyBuf;
|
||||||
|
use deno_io::ChildStderrResource;
|
||||||
|
use deno_io::ChildStdinResource;
|
||||||
|
use deno_io::ChildStdoutResource;
|
||||||
|
use deno_io::StdFileResource;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
|
use std::process::ExitStatus;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(windows)]
|
||||||
use std::os::unix::process::ExitStatusExt;
|
use std::os::windows::process::CommandExt;
|
||||||
|
|
||||||
pub fn init() -> Extension {
|
#[cfg(unix)]
|
||||||
Extension::builder("deno_process")
|
use std::os::unix::prelude::ExitStatusExt;
|
||||||
.ops(vec![op_run::decl(), op_run_status::decl(), op_kill::decl()])
|
#[cfg(unix)]
|
||||||
.build()
|
use std::os::unix::process::CommandExt;
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Eq, PartialEq, Deserialize)]
|
#[derive(Copy, Clone, Eq, PartialEq, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
|
@ -98,9 +100,276 @@ impl StdioOrRid {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn init_ops() -> Extension {
|
||||||
|
Extension::builder("deno_process")
|
||||||
|
.ops(vec![
|
||||||
|
op_spawn_child::decl(),
|
||||||
|
op_spawn_wait::decl(),
|
||||||
|
op_spawn_sync::decl(),
|
||||||
|
deprecated::op_run::decl(),
|
||||||
|
deprecated::op_run_status::decl(),
|
||||||
|
deprecated::op_kill::decl(),
|
||||||
|
])
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ChildResource(tokio::process::Child);
|
||||||
|
|
||||||
|
impl Resource for ChildResource {
|
||||||
|
fn name(&self) -> Cow<str> {
|
||||||
|
"child".into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct RunArgs {
|
pub struct SpawnArgs {
|
||||||
|
cmd: String,
|
||||||
|
args: Vec<String>,
|
||||||
|
cwd: Option<String>,
|
||||||
|
clear_env: bool,
|
||||||
|
env: Vec<(String, String)>,
|
||||||
|
#[cfg(unix)]
|
||||||
|
gid: Option<u32>,
|
||||||
|
#[cfg(unix)]
|
||||||
|
uid: Option<u32>,
|
||||||
|
#[cfg(windows)]
|
||||||
|
windows_raw_arguments: bool,
|
||||||
|
|
||||||
|
#[serde(flatten)]
|
||||||
|
stdio: ChildStdio,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct ChildStdio {
|
||||||
|
stdin: Stdio,
|
||||||
|
stdout: Stdio,
|
||||||
|
stderr: Stdio,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct ChildStatus {
|
||||||
|
success: bool,
|
||||||
|
code: i32,
|
||||||
|
signal: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<ExitStatus> for ChildStatus {
|
||||||
|
type Error = AnyError;
|
||||||
|
|
||||||
|
fn try_from(status: ExitStatus) -> Result<Self, Self::Error> {
|
||||||
|
let code = status.code();
|
||||||
|
#[cfg(unix)]
|
||||||
|
let signal = status.signal();
|
||||||
|
#[cfg(not(unix))]
|
||||||
|
let signal: Option<i32> = None;
|
||||||
|
|
||||||
|
let status = if let Some(signal) = signal {
|
||||||
|
ChildStatus {
|
||||||
|
success: false,
|
||||||
|
code: 128 + signal,
|
||||||
|
#[cfg(unix)]
|
||||||
|
signal: Some(
|
||||||
|
crate::ops::signal::signal_int_to_str(signal)?.to_string(),
|
||||||
|
),
|
||||||
|
#[cfg(not(unix))]
|
||||||
|
signal: None,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let code = code.expect("Should have either an exit code or a signal.");
|
||||||
|
|
||||||
|
ChildStatus {
|
||||||
|
success: code == 0,
|
||||||
|
code,
|
||||||
|
signal: None,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct SpawnOutput {
|
||||||
|
status: ChildStatus,
|
||||||
|
stdout: Option<ZeroCopyBuf>,
|
||||||
|
stderr: Option<ZeroCopyBuf>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_command(
|
||||||
|
state: &mut OpState,
|
||||||
|
args: SpawnArgs,
|
||||||
|
api_name: &str,
|
||||||
|
) -> Result<std::process::Command, AnyError> {
|
||||||
|
state
|
||||||
|
.borrow_mut::<PermissionsContainer>()
|
||||||
|
.check_run(&args.cmd, api_name)?;
|
||||||
|
|
||||||
|
let mut command = std::process::Command::new(args.cmd);
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
if args.windows_raw_arguments {
|
||||||
|
for arg in args.args.iter() {
|
||||||
|
command.raw_arg(arg);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
command.args(args.args);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
command.args(args.args);
|
||||||
|
|
||||||
|
if let Some(cwd) = args.cwd {
|
||||||
|
command.current_dir(cwd);
|
||||||
|
}
|
||||||
|
|
||||||
|
if args.clear_env {
|
||||||
|
command.env_clear();
|
||||||
|
}
|
||||||
|
command.envs(args.env);
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
if let Some(gid) = args.gid {
|
||||||
|
command.gid(gid);
|
||||||
|
}
|
||||||
|
#[cfg(unix)]
|
||||||
|
if let Some(uid) = args.uid {
|
||||||
|
command.uid(uid);
|
||||||
|
}
|
||||||
|
#[cfg(unix)]
|
||||||
|
// TODO(bartlomieju):
|
||||||
|
#[allow(clippy::undocumented_unsafe_blocks)]
|
||||||
|
unsafe {
|
||||||
|
command.pre_exec(|| {
|
||||||
|
libc::setgroups(0, std::ptr::null());
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
command.stdin(args.stdio.stdin.as_stdio());
|
||||||
|
command.stdout(match args.stdio.stdout {
|
||||||
|
Stdio::Inherit => StdioOrRid::Rid(1).as_stdio(state)?,
|
||||||
|
value => value.as_stdio(),
|
||||||
|
});
|
||||||
|
command.stderr(match args.stdio.stderr {
|
||||||
|
Stdio::Inherit => StdioOrRid::Rid(2).as_stdio(state)?,
|
||||||
|
value => value.as_stdio(),
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(command)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct Child {
|
||||||
|
rid: ResourceId,
|
||||||
|
pid: u32,
|
||||||
|
stdin_rid: Option<ResourceId>,
|
||||||
|
stdout_rid: Option<ResourceId>,
|
||||||
|
stderr_rid: Option<ResourceId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn spawn_child(
|
||||||
|
state: &mut OpState,
|
||||||
|
command: std::process::Command,
|
||||||
|
) -> Result<Child, AnyError> {
|
||||||
|
let mut command = tokio::process::Command::from(command);
|
||||||
|
// TODO(@crowlkats): allow detaching processes.
|
||||||
|
// currently deno will orphan a process when exiting with an error or Deno.exit()
|
||||||
|
// We want to kill child when it's closed
|
||||||
|
command.kill_on_drop(true);
|
||||||
|
|
||||||
|
let mut child = command.spawn()?;
|
||||||
|
let pid = child.id().expect("Process ID should be set.");
|
||||||
|
|
||||||
|
let stdin_rid = child
|
||||||
|
.stdin
|
||||||
|
.take()
|
||||||
|
.map(|stdin| state.resource_table.add(ChildStdinResource::from(stdin)));
|
||||||
|
|
||||||
|
let stdout_rid = child
|
||||||
|
.stdout
|
||||||
|
.take()
|
||||||
|
.map(|stdout| state.resource_table.add(ChildStdoutResource::from(stdout)));
|
||||||
|
|
||||||
|
let stderr_rid = child
|
||||||
|
.stderr
|
||||||
|
.take()
|
||||||
|
.map(|stderr| state.resource_table.add(ChildStderrResource::from(stderr)));
|
||||||
|
|
||||||
|
let child_rid = state.resource_table.add(ChildResource(child));
|
||||||
|
|
||||||
|
Ok(Child {
|
||||||
|
rid: child_rid,
|
||||||
|
pid,
|
||||||
|
stdin_rid,
|
||||||
|
stdout_rid,
|
||||||
|
stderr_rid,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[op]
|
||||||
|
fn op_spawn_child(
|
||||||
|
state: &mut OpState,
|
||||||
|
args: SpawnArgs,
|
||||||
|
api_name: String,
|
||||||
|
) -> Result<Child, AnyError> {
|
||||||
|
let command = create_command(state, args, &api_name)?;
|
||||||
|
spawn_child(state, command)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[op]
|
||||||
|
async fn op_spawn_wait(
|
||||||
|
state: Rc<RefCell<OpState>>,
|
||||||
|
rid: ResourceId,
|
||||||
|
) -> Result<ChildStatus, AnyError> {
|
||||||
|
let resource = state
|
||||||
|
.borrow_mut()
|
||||||
|
.resource_table
|
||||||
|
.take::<ChildResource>(rid)?;
|
||||||
|
Rc::try_unwrap(resource)
|
||||||
|
.ok()
|
||||||
|
.unwrap()
|
||||||
|
.0
|
||||||
|
.wait()
|
||||||
|
.await?
|
||||||
|
.try_into()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[op]
|
||||||
|
fn op_spawn_sync(
|
||||||
|
state: &mut OpState,
|
||||||
|
args: SpawnArgs,
|
||||||
|
) -> Result<SpawnOutput, AnyError> {
|
||||||
|
let stdout = matches!(args.stdio.stdout, Stdio::Piped);
|
||||||
|
let stderr = matches!(args.stdio.stderr, Stdio::Piped);
|
||||||
|
let output =
|
||||||
|
create_command(state, args, "Deno.Command().outputSync()")?.output()?;
|
||||||
|
|
||||||
|
Ok(SpawnOutput {
|
||||||
|
status: output.status.try_into()?,
|
||||||
|
stdout: if stdout {
|
||||||
|
Some(output.stdout.into())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
|
stderr: if stderr {
|
||||||
|
Some(output.stderr.into())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
mod deprecated {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct RunArgs {
|
||||||
cmd: Vec<String>,
|
cmd: Vec<String>,
|
||||||
cwd: Option<String>,
|
cwd: Option<String>,
|
||||||
clear_env: bool,
|
clear_env: bool,
|
||||||
|
@ -112,37 +381,40 @@ pub struct RunArgs {
|
||||||
stdin: StdioOrRid,
|
stdin: StdioOrRid,
|
||||||
stdout: StdioOrRid,
|
stdout: StdioOrRid,
|
||||||
stderr: StdioOrRid,
|
stderr: StdioOrRid,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ChildResource {
|
struct ChildResource {
|
||||||
child: AsyncRefCell<tokio::process::Child>,
|
child: AsyncRefCell<tokio::process::Child>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Resource for ChildResource {
|
impl Resource for ChildResource {
|
||||||
fn name(&self) -> Cow<str> {
|
fn name(&self) -> Cow<str> {
|
||||||
"child".into()
|
"child".into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ChildResource {
|
impl ChildResource {
|
||||||
fn borrow_mut(self: Rc<Self>) -> AsyncMutFuture<tokio::process::Child> {
|
fn borrow_mut(self: Rc<Self>) -> AsyncMutFuture<tokio::process::Child> {
|
||||||
RcRef::map(self, |r| &r.child).borrow_mut()
|
RcRef::map(self, |r| &r.child).borrow_mut()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
// TODO(@AaronO): maybe find a more descriptive name or a convention for return structs
|
// TODO(@AaronO): maybe find a more descriptive name or a convention for return structs
|
||||||
struct RunInfo {
|
struct RunInfo {
|
||||||
rid: ResourceId,
|
rid: ResourceId,
|
||||||
pid: Option<u32>,
|
pid: Option<u32>,
|
||||||
stdin_rid: Option<ResourceId>,
|
stdin_rid: Option<ResourceId>,
|
||||||
stdout_rid: Option<ResourceId>,
|
stdout_rid: Option<ResourceId>,
|
||||||
stderr_rid: Option<ResourceId>,
|
stderr_rid: Option<ResourceId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[op]
|
#[op]
|
||||||
fn op_run(state: &mut OpState, run_args: RunArgs) -> Result<RunInfo, AnyError> {
|
fn op_run(
|
||||||
|
state: &mut OpState,
|
||||||
|
run_args: RunArgs,
|
||||||
|
) -> Result<RunInfo, AnyError> {
|
||||||
let args = run_args.cmd;
|
let args = run_args.cmd;
|
||||||
state
|
state
|
||||||
.borrow_mut::<PermissionsContainer>()
|
.borrow_mut::<PermissionsContainer>()
|
||||||
|
@ -251,21 +523,21 @@ fn op_run(state: &mut OpState, run_args: RunArgs) -> Result<RunInfo, AnyError> {
|
||||||
stdout_rid,
|
stdout_rid,
|
||||||
stderr_rid,
|
stderr_rid,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
struct ProcessStatus {
|
struct ProcessStatus {
|
||||||
got_signal: bool,
|
got_signal: bool,
|
||||||
exit_code: i32,
|
exit_code: i32,
|
||||||
exit_signal: i32,
|
exit_signal: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[op]
|
#[op]
|
||||||
async fn op_run_status(
|
async fn op_run_status(
|
||||||
state: Rc<RefCell<OpState>>,
|
state: Rc<RefCell<OpState>>,
|
||||||
rid: ResourceId,
|
rid: ResourceId,
|
||||||
) -> Result<ProcessStatus, AnyError> {
|
) -> Result<ProcessStatus, AnyError> {
|
||||||
let resource = state
|
let resource = state
|
||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
.resource_table
|
.resource_table
|
||||||
|
@ -289,20 +561,20 @@ async fn op_run_status(
|
||||||
exit_code: code.unwrap_or(-1),
|
exit_code: code.unwrap_or(-1),
|
||||||
exit_signal: signal.unwrap_or(-1),
|
exit_signal: signal.unwrap_or(-1),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
pub fn kill(pid: i32, signal: &str) -> Result<(), AnyError> {
|
pub fn kill(pid: i32, signal: &str) -> Result<(), AnyError> {
|
||||||
let signo = super::signal::signal_str_to_int(signal)?;
|
let signo = super::signal::signal_str_to_int(signal)?;
|
||||||
use nix::sys::signal::kill as unix_kill;
|
use nix::sys::signal::kill as unix_kill;
|
||||||
use nix::sys::signal::Signal;
|
use nix::sys::signal::Signal;
|
||||||
use nix::unistd::Pid;
|
use nix::unistd::Pid;
|
||||||
let sig = Signal::try_from(signo)?;
|
let sig = Signal::try_from(signo)?;
|
||||||
unix_kill(Pid::from_raw(pid), Option::Some(sig)).map_err(AnyError::from)
|
unix_kill(Pid::from_raw(pid), Option::Some(sig)).map_err(AnyError::from)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(unix))]
|
#[cfg(not(unix))]
|
||||||
pub fn kill(pid: i32, signal: &str) -> Result<(), AnyError> {
|
pub fn kill(pid: i32, signal: &str) -> Result<(), AnyError> {
|
||||||
use deno_core::error::type_error;
|
use deno_core::error::type_error;
|
||||||
use std::io::Error;
|
use std::io::Error;
|
||||||
use std::io::ErrorKind::NotFound;
|
use std::io::ErrorKind::NotFound;
|
||||||
|
@ -322,7 +594,8 @@ pub fn kill(pid: i32, signal: &str) -> Result<(), AnyError> {
|
||||||
Err(type_error("Invalid pid"))
|
Err(type_error("Invalid pid"))
|
||||||
} else {
|
} else {
|
||||||
// SAFETY: winapi call
|
// SAFETY: winapi call
|
||||||
let handle = unsafe { OpenProcess(PROCESS_TERMINATE, FALSE, pid as DWORD) };
|
let handle =
|
||||||
|
unsafe { OpenProcess(PROCESS_TERMINATE, FALSE, pid as DWORD) };
|
||||||
|
|
||||||
if handle.is_null() {
|
if handle.is_null() {
|
||||||
// SAFETY: winapi call
|
// SAFETY: winapi call
|
||||||
|
@ -344,18 +617,19 @@ pub fn kill(pid: i32, signal: &str) -> Result<(), AnyError> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[op]
|
#[op]
|
||||||
fn op_kill(
|
fn op_kill(
|
||||||
state: &mut OpState,
|
state: &mut OpState,
|
||||||
pid: i32,
|
pid: i32,
|
||||||
signal: String,
|
signal: String,
|
||||||
api_name: String,
|
api_name: String,
|
||||||
) -> Result<(), AnyError> {
|
) -> Result<(), AnyError> {
|
||||||
state
|
state
|
||||||
.borrow_mut::<PermissionsContainer>()
|
.borrow_mut::<PermissionsContainer>()
|
||||||
.check_run_all(&api_name)?;
|
.check_run_all(&api_name)?;
|
||||||
kill(pid, &signal)?;
|
kill(pid, &signal)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,289 +0,0 @@
|
||||||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
|
||||||
|
|
||||||
use super::process::Stdio;
|
|
||||||
use super::process::StdioOrRid;
|
|
||||||
use crate::permissions::PermissionsContainer;
|
|
||||||
use deno_core::error::AnyError;
|
|
||||||
use deno_core::op;
|
|
||||||
use deno_core::Extension;
|
|
||||||
use deno_core::OpState;
|
|
||||||
use deno_core::Resource;
|
|
||||||
use deno_core::ResourceId;
|
|
||||||
use deno_core::ZeroCopyBuf;
|
|
||||||
use deno_io::ChildStderrResource;
|
|
||||||
use deno_io::ChildStdinResource;
|
|
||||||
use deno_io::ChildStdoutResource;
|
|
||||||
use serde::Deserialize;
|
|
||||||
use serde::Serialize;
|
|
||||||
use std::borrow::Cow;
|
|
||||||
use std::cell::RefCell;
|
|
||||||
#[cfg(windows)]
|
|
||||||
use std::os::windows::process::CommandExt;
|
|
||||||
use std::process::ExitStatus;
|
|
||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
#[cfg(unix)]
|
|
||||||
use std::os::unix::prelude::ExitStatusExt;
|
|
||||||
#[cfg(unix)]
|
|
||||||
use std::os::unix::process::CommandExt;
|
|
||||||
|
|
||||||
pub fn init() -> Extension {
|
|
||||||
Extension::builder("deno_spawn")
|
|
||||||
.ops(vec![
|
|
||||||
op_spawn_child::decl(),
|
|
||||||
op_spawn_wait::decl(),
|
|
||||||
op_spawn_sync::decl(),
|
|
||||||
])
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ChildResource(tokio::process::Child);
|
|
||||||
|
|
||||||
impl Resource for ChildResource {
|
|
||||||
fn name(&self) -> Cow<str> {
|
|
||||||
"child".into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct SpawnArgs {
|
|
||||||
cmd: String,
|
|
||||||
args: Vec<String>,
|
|
||||||
cwd: Option<String>,
|
|
||||||
clear_env: bool,
|
|
||||||
env: Vec<(String, String)>,
|
|
||||||
#[cfg(unix)]
|
|
||||||
gid: Option<u32>,
|
|
||||||
#[cfg(unix)]
|
|
||||||
uid: Option<u32>,
|
|
||||||
#[cfg(windows)]
|
|
||||||
windows_raw_arguments: bool,
|
|
||||||
|
|
||||||
#[serde(flatten)]
|
|
||||||
stdio: ChildStdio,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct ChildStdio {
|
|
||||||
stdin: Stdio,
|
|
||||||
stdout: Stdio,
|
|
||||||
stderr: Stdio,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct ChildStatus {
|
|
||||||
success: bool,
|
|
||||||
code: i32,
|
|
||||||
signal: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<ExitStatus> for ChildStatus {
|
|
||||||
type Error = AnyError;
|
|
||||||
|
|
||||||
fn try_from(status: ExitStatus) -> Result<Self, Self::Error> {
|
|
||||||
let code = status.code();
|
|
||||||
#[cfg(unix)]
|
|
||||||
let signal = status.signal();
|
|
||||||
#[cfg(not(unix))]
|
|
||||||
let signal: Option<i32> = None;
|
|
||||||
|
|
||||||
let status = if let Some(signal) = signal {
|
|
||||||
ChildStatus {
|
|
||||||
success: false,
|
|
||||||
code: 128 + signal,
|
|
||||||
#[cfg(unix)]
|
|
||||||
signal: Some(
|
|
||||||
crate::ops::signal::signal_int_to_str(signal)?.to_string(),
|
|
||||||
),
|
|
||||||
#[cfg(not(unix))]
|
|
||||||
signal: None,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let code = code.expect("Should have either an exit code or a signal.");
|
|
||||||
|
|
||||||
ChildStatus {
|
|
||||||
success: code == 0,
|
|
||||||
code,
|
|
||||||
signal: None,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(status)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct SpawnOutput {
|
|
||||||
status: ChildStatus,
|
|
||||||
stdout: Option<ZeroCopyBuf>,
|
|
||||||
stderr: Option<ZeroCopyBuf>,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_command(
|
|
||||||
state: &mut OpState,
|
|
||||||
args: SpawnArgs,
|
|
||||||
api_name: &str,
|
|
||||||
) -> Result<std::process::Command, AnyError> {
|
|
||||||
state
|
|
||||||
.borrow_mut::<PermissionsContainer>()
|
|
||||||
.check_run(&args.cmd, api_name)?;
|
|
||||||
|
|
||||||
let mut command = std::process::Command::new(args.cmd);
|
|
||||||
|
|
||||||
#[cfg(windows)]
|
|
||||||
if args.windows_raw_arguments {
|
|
||||||
for arg in args.args.iter() {
|
|
||||||
command.raw_arg(arg);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
command.args(args.args);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(windows))]
|
|
||||||
command.args(args.args);
|
|
||||||
|
|
||||||
if let Some(cwd) = args.cwd {
|
|
||||||
command.current_dir(cwd);
|
|
||||||
}
|
|
||||||
|
|
||||||
if args.clear_env {
|
|
||||||
command.env_clear();
|
|
||||||
}
|
|
||||||
command.envs(args.env);
|
|
||||||
|
|
||||||
#[cfg(unix)]
|
|
||||||
if let Some(gid) = args.gid {
|
|
||||||
command.gid(gid);
|
|
||||||
}
|
|
||||||
#[cfg(unix)]
|
|
||||||
if let Some(uid) = args.uid {
|
|
||||||
command.uid(uid);
|
|
||||||
}
|
|
||||||
#[cfg(unix)]
|
|
||||||
// TODO(bartlomieju):
|
|
||||||
#[allow(clippy::undocumented_unsafe_blocks)]
|
|
||||||
unsafe {
|
|
||||||
command.pre_exec(|| {
|
|
||||||
libc::setgroups(0, std::ptr::null());
|
|
||||||
Ok(())
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
command.stdin(args.stdio.stdin.as_stdio());
|
|
||||||
command.stdout(match args.stdio.stdout {
|
|
||||||
Stdio::Inherit => StdioOrRid::Rid(1).as_stdio(state)?,
|
|
||||||
value => value.as_stdio(),
|
|
||||||
});
|
|
||||||
command.stderr(match args.stdio.stderr {
|
|
||||||
Stdio::Inherit => StdioOrRid::Rid(2).as_stdio(state)?,
|
|
||||||
value => value.as_stdio(),
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(command)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
struct Child {
|
|
||||||
rid: ResourceId,
|
|
||||||
pid: u32,
|
|
||||||
stdin_rid: Option<ResourceId>,
|
|
||||||
stdout_rid: Option<ResourceId>,
|
|
||||||
stderr_rid: Option<ResourceId>,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn spawn_child(
|
|
||||||
state: &mut OpState,
|
|
||||||
command: std::process::Command,
|
|
||||||
) -> Result<Child, AnyError> {
|
|
||||||
let mut command = tokio::process::Command::from(command);
|
|
||||||
// TODO(@crowlkats): allow detaching processes.
|
|
||||||
// currently deno will orphan a process when exiting with an error or Deno.exit()
|
|
||||||
// We want to kill child when it's closed
|
|
||||||
command.kill_on_drop(true);
|
|
||||||
|
|
||||||
let mut child = command.spawn()?;
|
|
||||||
let pid = child.id().expect("Process ID should be set.");
|
|
||||||
|
|
||||||
let stdin_rid = child
|
|
||||||
.stdin
|
|
||||||
.take()
|
|
||||||
.map(|stdin| state.resource_table.add(ChildStdinResource::from(stdin)));
|
|
||||||
|
|
||||||
let stdout_rid = child
|
|
||||||
.stdout
|
|
||||||
.take()
|
|
||||||
.map(|stdout| state.resource_table.add(ChildStdoutResource::from(stdout)));
|
|
||||||
|
|
||||||
let stderr_rid = child
|
|
||||||
.stderr
|
|
||||||
.take()
|
|
||||||
.map(|stderr| state.resource_table.add(ChildStderrResource::from(stderr)));
|
|
||||||
|
|
||||||
let child_rid = state.resource_table.add(ChildResource(child));
|
|
||||||
|
|
||||||
Ok(Child {
|
|
||||||
rid: child_rid,
|
|
||||||
pid,
|
|
||||||
stdin_rid,
|
|
||||||
stdout_rid,
|
|
||||||
stderr_rid,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[op]
|
|
||||||
fn op_spawn_child(
|
|
||||||
state: &mut OpState,
|
|
||||||
args: SpawnArgs,
|
|
||||||
api_name: String,
|
|
||||||
) -> Result<Child, AnyError> {
|
|
||||||
let command = create_command(state, args, &api_name)?;
|
|
||||||
spawn_child(state, command)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[op]
|
|
||||||
async fn op_spawn_wait(
|
|
||||||
state: Rc<RefCell<OpState>>,
|
|
||||||
rid: ResourceId,
|
|
||||||
) -> Result<ChildStatus, AnyError> {
|
|
||||||
let resource = state
|
|
||||||
.borrow_mut()
|
|
||||||
.resource_table
|
|
||||||
.take::<ChildResource>(rid)?;
|
|
||||||
Rc::try_unwrap(resource)
|
|
||||||
.ok()
|
|
||||||
.unwrap()
|
|
||||||
.0
|
|
||||||
.wait()
|
|
||||||
.await?
|
|
||||||
.try_into()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[op]
|
|
||||||
fn op_spawn_sync(
|
|
||||||
state: &mut OpState,
|
|
||||||
args: SpawnArgs,
|
|
||||||
) -> Result<SpawnOutput, AnyError> {
|
|
||||||
let stdout = matches!(args.stdio.stdout, Stdio::Piped);
|
|
||||||
let stderr = matches!(args.stdio.stderr, Stdio::Piped);
|
|
||||||
let output =
|
|
||||||
create_command(state, args, "Deno.Command().outputSync()")?.output()?;
|
|
||||||
|
|
||||||
Ok(SpawnOutput {
|
|
||||||
status: output.status.try_into()?,
|
|
||||||
stdout: if stdout {
|
|
||||||
Some(output.stdout.into())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
},
|
|
||||||
stderr: if stderr {
|
|
||||||
Some(output.stderr.into())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -438,8 +438,7 @@ impl WebWorker {
|
||||||
deno_node::init::<PermissionsContainer>(options.npm_resolver),
|
deno_node::init::<PermissionsContainer>(options.npm_resolver),
|
||||||
ops::os::init_for_worker(),
|
ops::os::init_for_worker(),
|
||||||
ops::permissions::init(),
|
ops::permissions::init(),
|
||||||
ops::process::init(),
|
ops::process::init_ops(),
|
||||||
ops::spawn::init(),
|
|
||||||
ops::signal::init(),
|
ops::signal::init(),
|
||||||
ops::tty::init(),
|
ops::tty::init(),
|
||||||
deno_http::init(),
|
deno_http::init(),
|
||||||
|
|
|
@ -254,7 +254,6 @@ impl MainWorker {
|
||||||
options.web_worker_pre_execute_module_cb.clone(),
|
options.web_worker_pre_execute_module_cb.clone(),
|
||||||
options.format_js_error_fn.clone(),
|
options.format_js_error_fn.clone(),
|
||||||
),
|
),
|
||||||
ops::spawn::init(),
|
|
||||||
ops::fs_events::init(),
|
ops::fs_events::init(),
|
||||||
ops::fs::init::<PermissionsContainer>(),
|
ops::fs::init::<PermissionsContainer>(),
|
||||||
deno_io::init(options.stdio),
|
deno_io::init(options.stdio),
|
||||||
|
@ -269,7 +268,7 @@ impl MainWorker {
|
||||||
deno_node::init::<PermissionsContainer>(options.npm_resolver),
|
deno_node::init::<PermissionsContainer>(options.npm_resolver),
|
||||||
ops::os::init(exit_code.clone()),
|
ops::os::init(exit_code.clone()),
|
||||||
ops::permissions::init(),
|
ops::permissions::init(),
|
||||||
ops::process::init(),
|
ops::process::init_ops(),
|
||||||
ops::signal::init(),
|
ops::signal::init(),
|
||||||
ops::tty::init(),
|
ops::tty::init(),
|
||||||
deno_http::init(),
|
deno_http::init(),
|
||||||
|
|
Loading…
Reference in a new issue