mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-27 06:43:19 +08:00
Look up spawn and execFileSync commands on the replacement environment's PATH
spawn and execFileSync with an `env` option searched the parent's PATH for a command without a '/'. They now resolve it the way spawnSync does, as Node (libuv) does: on the replacement environment's PATH, or the default path when it has none, with relative entries read from the cwd option's directory.
This commit is contained in:
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/7036-child-env-path-lookup.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
@@ -3116,9 +3116,12 @@ static void scr_exec_envp_free(char **envp);
|
||||
* environment, found as libuv finds it: a name with a '/' is used as
|
||||
* given; otherwise each entry of the replacement's PATH (an empty entry
|
||||
* is the current directory), or of the default path when the replacement
|
||||
* has no PATH, is tried in order. Returns a malloc'd path, or NULL with
|
||||
* *err set: EACCES when a candidate exists but cannot run, else ENOENT. */
|
||||
static char *scr_spawn_path_lookup(const char *file, char *const *envp, int *err) {
|
||||
* has no PATH, is tried in order. Relative candidates are checked from
|
||||
* `cwd`, the directory the child starts in (NULL: this process's). Returns
|
||||
* a malloc'd path, or NULL with *err set: EACCES when a candidate exists
|
||||
* but cannot run, else ENOENT. */
|
||||
static char *scr_spawn_path_lookup(const char *file, char *const *envp, const char *cwd,
|
||||
int *err) {
|
||||
if (strchr(file, '/')) {
|
||||
char *copy = strdup(file);
|
||||
if (!copy) scr_child_oom();
|
||||
@@ -3132,6 +3135,7 @@ static char *scr_spawn_path_lookup(const char *file, char *const *envp, int *err
|
||||
}
|
||||
}
|
||||
size_t file_len = strlen(file);
|
||||
size_t cwd_len = cwd ? strlen(cwd) : 0;
|
||||
bool denied = false;
|
||||
for (const char *dir = path;;) {
|
||||
const char *end = strchr(dir, ':');
|
||||
@@ -3143,13 +3147,24 @@ static char *scr_spawn_path_lookup(const char *file, char *const *envp, int *err
|
||||
memcpy(candidate, prefix, prefix_len);
|
||||
candidate[prefix_len] = '/';
|
||||
memcpy(candidate + prefix_len + 1, file, file_len + 1);
|
||||
char *checked = candidate;
|
||||
if (cwd_len > 0 && candidate[0] != '/') {
|
||||
checked = malloc(cwd_len + 1 + strlen(candidate) + 1);
|
||||
if (!checked) scr_child_oom();
|
||||
memcpy(checked, cwd, cwd_len);
|
||||
checked[cwd_len] = '/';
|
||||
strcpy(checked + cwd_len + 1, candidate);
|
||||
}
|
||||
struct stat st;
|
||||
if (stat(candidate, &st) == 0) {
|
||||
if (S_ISREG(st.st_mode) && access(candidate, X_OK) == 0) return candidate;
|
||||
denied = true;
|
||||
bool runs = false;
|
||||
if (stat(checked, &st) == 0) {
|
||||
runs = S_ISREG(st.st_mode) && access(checked, X_OK) == 0;
|
||||
if (!runs) denied = true;
|
||||
} else if (errno == EACCES) {
|
||||
denied = true;
|
||||
}
|
||||
if (checked != candidate) free(checked);
|
||||
if (runs) return candidate;
|
||||
free(candidate);
|
||||
if (!end) break;
|
||||
dir = end + 1;
|
||||
@@ -3158,6 +3173,22 @@ static char *scr_spawn_path_lookup(const char *file, char *const *envp, int *err
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* posix_spawnp for the child_process spawns: with `envp` (a replacement
|
||||
* environment) the command resolves on that environment's PATH
|
||||
* (scr_spawn_path_lookup) as Node's does; without it, on this process's
|
||||
* PATH over `environ`. Returns posix_spawn's error number. */
|
||||
static int scr_spawn_command(pid_t *pid, const char *file, const posix_spawn_file_actions_t *fa,
|
||||
const posix_spawnattr_t *attr, char *const *argv,
|
||||
char *const *envp, const char *cwd) {
|
||||
if (envp == NULL) return posix_spawnp(pid, file, fa, attr, argv, environ);
|
||||
int err = 0;
|
||||
char *resolved = scr_spawn_path_lookup(file, envp, cwd, &err);
|
||||
if (!resolved) return err;
|
||||
err = posix_spawn(pid, resolved, fa, attr, argv, envp);
|
||||
free(resolved);
|
||||
return err;
|
||||
}
|
||||
|
||||
/* env_pairs: NULL inherits the parent's environment; else [k,v,...]
|
||||
* replaces it, as Node's `env` option does, and the command is looked up
|
||||
* on the replacement's PATH (scr_spawn_path_lookup). */
|
||||
@@ -3200,16 +3231,7 @@ static ScrSpawnRes *scr_spawn_sync_core(ScrStr *cmd, ScrArr *args, double timeou
|
||||
char **argv = scr_child_argv(cmd, args);
|
||||
char **envp = env_pairs != NULL ? scr_exec_envp(env_pairs) : NULL;
|
||||
pid_t pid = -1;
|
||||
int spawn_err;
|
||||
if (envp != NULL) {
|
||||
char *resolved = scr_spawn_path_lookup(cmd->data, envp, &spawn_err);
|
||||
if (resolved) {
|
||||
spawn_err = posix_spawn(&pid, resolved, &fa, NULL, argv, envp);
|
||||
free(resolved);
|
||||
}
|
||||
} else {
|
||||
spawn_err = posix_spawnp(&pid, cmd->data, &fa, NULL, argv, environ);
|
||||
}
|
||||
int spawn_err = scr_spawn_command(&pid, cmd->data, &fa, NULL, argv, envp, NULL);
|
||||
posix_spawn_file_actions_destroy(&fa);
|
||||
free(argv);
|
||||
scr_exec_envp_free(envp);
|
||||
@@ -3556,8 +3578,8 @@ ScrStr *scr_exec_sync_core(ScrStr *cmd, ScrArr *args, const ScrExecOpts *o) {
|
||||
char **argv = scr_child_argv(cmd, args);
|
||||
char **envp = o->env_pairs != NULL ? scr_exec_envp(o->env_pairs) : NULL;
|
||||
pid_t pid = -1;
|
||||
int spawn_err = posix_spawnp(&pid, cmd->data, &fa, NULL, argv,
|
||||
envp != NULL ? envp : environ);
|
||||
int spawn_err = scr_spawn_command(&pid, cmd->data, &fa, NULL, argv, envp,
|
||||
o->cwd != NULL ? o->cwd->data : NULL);
|
||||
posix_spawn_file_actions_destroy(&fa);
|
||||
free(argv);
|
||||
scr_exec_envp_free(envp);
|
||||
@@ -5115,8 +5137,8 @@ ScrChild *scr_spawn_opts(ScrStr *cmd, ScrArr *args, double in_mode,
|
||||
char **envp = has_env && env_pairs != NULL ? scr_exec_envp(env_pairs) : NULL;
|
||||
pid_t pid = -1;
|
||||
const char *file = shell ? "/bin/sh" : cmd->data;
|
||||
int spawn_err = posix_spawnp(&pid, file, &fa, &at, argv,
|
||||
envp != NULL ? envp : environ);
|
||||
int spawn_err = scr_spawn_command(&pid, file, &fa, &at, argv, envp,
|
||||
cwd != NULL && cwd->len > 0 ? cwd->data : NULL);
|
||||
posix_spawnattr_destroy(&at);
|
||||
posix_spawn_file_actions_destroy(&fa);
|
||||
if (!shell) free(argv);
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
// spawn and execFileSync with an env option find the command on the
|
||||
// replacement environment's PATH, or on the default path when it has
|
||||
// none, as Node does; a relative PATH entry is read from the cwd option.
|
||||
import { execFileSync, spawn } from "node:child_process";
|
||||
import { chmodSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
|
||||
const dir = mkdtempSync(join(tmpdir(), "scr-child-path-"));
|
||||
mkdirSync(join(dir, "tools"));
|
||||
writeFileSync(join(dir, "tools", "child-tool"), "#!/bin/sh\necho child-tool $1\n");
|
||||
chmodSync(join(dir, "tools", "child-tool"), 0o755);
|
||||
|
||||
const show = (e: unknown): string => {
|
||||
const err = e as NodeJS.ErrnoException;
|
||||
return `${err.code} ${err.message.replace(dir, "<dir>")}`;
|
||||
};
|
||||
|
||||
console.log(execFileSync("child-tool", ["sync"], { env: { PATH: join(dir, "tools") }, encoding: "utf8" }).trim());
|
||||
console.log(execFileSync("sh", ["-c", "echo default"], { env: { A: "1" }, encoding: "utf8" }).trim());
|
||||
try {
|
||||
execFileSync("sh", ["-c", "echo never"], { env: { PATH: "/nonexistent" }, encoding: "utf8" });
|
||||
} catch (e) {
|
||||
console.log(show(e));
|
||||
}
|
||||
console.log(
|
||||
execFileSync("child-tool", ["relative"], { env: { PATH: "tools" }, cwd: dir, encoding: "utf8" }).trim(),
|
||||
);
|
||||
|
||||
const child = spawn("child-tool", ["async"], { env: { PATH: join(dir, "tools") }, stdio: ["ignore", "pipe", "ignore"] });
|
||||
let out = "";
|
||||
child.stdout!.on("data", (chunk: Buffer) => (out += chunk.toString()));
|
||||
child.on("close", (code) => {
|
||||
console.log(out.trim(), code);
|
||||
const missing = spawn("sh", ["-c", "true"], { env: { PATH: "/nonexistent" }, stdio: "ignore" });
|
||||
missing.on("error", (e) => {
|
||||
console.log("error", show(e));
|
||||
rmSync(dir, { recursive: true });
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 2570, "san": 4110 }
|
||||
Reference in New Issue
Block a user