Skip to content

Commit b0ef4b5

Browse files
committed
lib: fix phantom EACCES on spawn in some environments
On some Linux environments (e.g. WSL), execvp may return EACCES instead of ENOENT when a command is not found in PATH, if it encounters permission errors during the search. This change adds a check to verify if the file actually exists in PATH when EACCES is received for a command without path separators. If not found, the error is normalized to ENOENT.
1 parent 0457bfe commit b0ef4b5

File tree

1 file changed

+37
-2
lines changed

1 file changed

+37
-2
lines changed

lib/internal/child_process.js

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ const {
3838
const EventEmitter = require('events');
3939
const net = require('net');
4040
const dgram = require('dgram');
41+
const fs = require('fs');
42+
const path = require('path');
4143
const inspect = require('internal/util/inspect').inspect;
4244
const assert = require('internal/assert');
4345

@@ -73,6 +75,34 @@ const {
7375
UV_ESRCH,
7476
} = internalBinding('uv');
7577

78+
function verifyENOENT(file, envPairs) {
79+
if (file === '.' || file.includes(path.sep)) return false;
80+
let envPath;
81+
if (envPairs) {
82+
for (let i = 0; i < envPairs.length; i++) {
83+
const pair = envPairs[i];
84+
if (pair.startsWith('PATH=')) {
85+
envPath = pair.slice(5);
86+
break;
87+
}
88+
}
89+
}
90+
if (!envPath) {
91+
envPath = process.env.PATH;
92+
}
93+
if (!envPath) return false;
94+
const paths = envPath.split(path.delimiter);
95+
for (let i = 0; i < paths.length; i++) {
96+
const p = paths[i];
97+
if (!p) continue;
98+
const fullPath = path.resolve(p, file);
99+
if (fs.existsSync(fullPath)) {
100+
return false;
101+
}
102+
}
103+
return true;
104+
}
105+
76106
const { SocketListSend, SocketListReceive } = SocketList;
77107

78108
// Lazy loaded for startup performance and to allow monkey patching of
@@ -394,8 +424,9 @@ ChildProcess.prototype.spawn = function spawn(options) {
394424

395425
const err = this._handle.spawn(options);
396426

397-
// Run-time errors should emit an error, not throw an exception.
398-
if (err === UV_EACCES ||
427+
if (err === UV_EACCES && verifyENOENT(this.spawnfile, options.envPairs)) {
428+
process.nextTick(onErrorNT, this, UV_ENOENT);
429+
} else if (err === UV_EACCES ||
399430
err === UV_EAGAIN ||
400431
err === UV_EMFILE ||
401432
err === UV_ENFILE ||
@@ -1088,6 +1119,10 @@ function maybeClose(subprocess) {
10881119
function spawnSync(options) {
10891120
const result = spawn_sync.spawn(options);
10901121

1122+
if (result.error === UV_EACCES && verifyENOENT(options.file, options.envPairs)) {
1123+
result.error = UV_ENOENT;
1124+
}
1125+
10911126
if (result.output && options.encoding && options.encoding !== 'buffer') {
10921127
for (let i = 0; i < result.output.length; i++) {
10931128
if (!result.output[i])

0 commit comments

Comments
 (0)