Skip to content

Commit

Permalink
process: add execve
Browse files Browse the repository at this point in the history
  • Loading branch information
ShogunPanda committed Jan 8, 2025
1 parent 804d41f commit 037edbc
Show file tree
Hide file tree
Showing 13 changed files with 475 additions and 1 deletion.
8 changes: 8 additions & 0 deletions doc/api/errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -2445,6 +2445,13 @@ An invalid timestamp value was provided for a performance mark or measure.

Invalid options were provided for a performance measure.

<a id="ERR_PROCESS_EXECVE_FAILED"></a>

### `ERR_PROCESS_EXECVE_FAILED`

Replacing the current process with [`process.execve()`][] has failed due to some
system error.

<a id="ERR_PROTO_ACCESS"></a>

### `ERR_PROTO_ACCESS`
Expand Down Expand Up @@ -4263,6 +4270,7 @@ An error occurred trying to allocate memory. This should never happen.
[`package.json`]: packages.md#nodejs-packagejson-field-definitions
[`postMessage()`]: worker_threads.md#portpostmessagevalue-transferlist
[`postMessageToThread()`]: worker_threads.md#workerpostmessagetothreadthreadid-value-transferlist-timeout
[`process.execve()`]: process.md#processexecvefile-args-env
[`process.on('exit')`]: process.md#event-exit
[`process.send()`]: process.md#processsendmessage-sendhandle-options-callback
[`process.setUncaughtExceptionCaptureCallback()`]: process.md#processsetuncaughtexceptioncapturecallbackfn
Expand Down
24 changes: 24 additions & 0 deletions doc/api/process.md
Original file line number Diff line number Diff line change
Expand Up @@ -3303,6 +3303,30 @@ In custom builds from non-release versions of the source tree, only the
`name` property may be present. The additional properties should not be
relied upon to exist.
## \`process.execve(file\[, args\[, env]])\`
<!-- YAML
added: REPLACEME
-->
* `file` {string} The name or path of the executable file to run.
* `args` {string\[]} List of string arguments. No argument can contain a null-byte (`\u0000`).
* `env` {Object} Environment key-value pairs.
No key or value can contain a null-byte (`\u0000`).
**Default:** `process.env`.
Replaces the current process with a new process.
This is achieved by using the `execve` Unix function and therefore no memory or other
resources from the current process are preserved, except for the standard input,
standard output and standard error file descriptor.
All other resources are discarded by system when the processes are swapped.
This function will never return, unless an error occurred.
This function is only available on POSIX platforms (i.e. not Windows or Android).
## `process.report`
<!-- YAML
Expand Down
1 change: 1 addition & 0 deletions lib/internal/bootstrap/node.js
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ const rawMethods = internalBinding('process_methods');
process.availableMemory = rawMethods.availableMemory;
process.kill = wrapped.kill;
process.exit = wrapped.exit;
process.execve = wrapped.execve;
process.ref = perThreadSetup.ref;
process.unref = perThreadSetup.unref;

Expand Down
50 changes: 50 additions & 0 deletions lib/internal/process/per_thread.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const {
FunctionPrototypeCall,
NumberMAX_SAFE_INTEGER,
ObjectDefineProperty,
ObjectEntries,
ObjectFreeze,
ReflectApply,
RegExpPrototypeExec,
Expand All @@ -24,6 +25,7 @@ const {
SetPrototypeEntries,
SetPrototypeValues,
StringPrototypeEndsWith,
StringPrototypeIncludes,
StringPrototypeReplace,
StringPrototypeSlice,
Symbol,
Expand All @@ -34,17 +36,20 @@ const {
const {
ErrnoException,
codes: {
ERR_FEATURE_UNAVAILABLE_ON_PLATFORM,
ERR_INVALID_ARG_TYPE,
ERR_INVALID_ARG_VALUE,
ERR_OUT_OF_RANGE,
ERR_UNKNOWN_SIGNAL,
ERR_WORKER_UNSUPPORTED_OPERATION,
},
} = require('internal/errors');
const format = require('internal/util/inspect').format;
const {
validateArray,
validateNumber,
validateObject,
validateString,
} = require('internal/validators');

const constants = internalBinding('constants').os.signals;
Expand Down Expand Up @@ -101,6 +106,7 @@ function wrapProcessMethods(binding) {
rss,
resourceUsage: _resourceUsage,
loadEnvFile: _loadEnvFile,
execve: _execve,
} = binding;

function _rawDebug(...args) {
Expand Down Expand Up @@ -223,6 +229,49 @@ function wrapProcessMethods(binding) {
return true;
}

function execve(execPath, args, env) {
const { isMainThread } = require('internal/worker');

if (!isMainThread) {
throw new ERR_WORKER_UNSUPPORTED_OPERATION('Calling process.execve');
} else if (process.platform === 'win32') {
throw new ERR_FEATURE_UNAVAILABLE_ON_PLATFORM('process.execve');
}

validateString(execPath, 'execPath');
validateArray(args, 'args');

for (let i = 0; i < args.length; i++) {
const arg = args[i];
if (typeof arg !== 'string' || StringPrototypeIncludes(arg, '\u0000')) {
throw new ERR_INVALID_ARG_VALUE(`args[${i}]`, arg, 'must be a string without null bytes');
}
}

if (env !== undefined) {
validateObject(env, 'env');

for (const { 0: key, 1: value } of ObjectEntries(env)) {
if (
typeof key !== 'string' ||
typeof value !== 'string' ||
StringPrototypeIncludes(key, '\u0000') ||
StringPrototypeIncludes(value, '\u0000')
) {
throw new ERR_INVALID_ARG_VALUE(
'env', env, 'must be an object with string keys and values without null bytes',
);
}
}
}

// Construct the environment array.
const envArray = ArrayPrototypeMap(ObjectEntries(env || {}), ({ 0: key, 1: value }) => `${key}=${value}`);

// Perform the system call
_execve(execPath, args, envArray);
}

const resourceValues = new Float64Array(16);
function resourceUsage() {
_resourceUsage(resourceValues);
Expand Down Expand Up @@ -267,6 +316,7 @@ function wrapProcessMethods(binding) {
memoryUsage,
kill,
exit,
execve,
loadEnvFile,
};
}
Expand Down
17 changes: 16 additions & 1 deletion src/node_errors.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@
#include <sstream>

namespace node {
// This forward declaration is required to have the method
// available in error messages.
namespace errors {
const char* errno_string(int errorno);
}

enum ErrorHandlingMode { CONTEXTIFY_ERROR, FATAL_ERROR, MODULE_ERROR };
void AppendExceptionLine(Environment* env,
Expand Down Expand Up @@ -111,7 +116,8 @@ void OOMErrorHandler(const char* location, const v8::OOMDetails& details);
V(ERR_WASI_NOT_STARTED, Error) \
V(ERR_ZLIB_INITIALIZATION_FAILED, Error) \
V(ERR_WORKER_INIT_FAILED, Error) \
V(ERR_PROTO_ACCESS, Error)
V(ERR_PROTO_ACCESS, Error) \
V(ERR_PROCESS_EXECVE_FAILED, Error)

#define V(code, type) \
template <typename... Args> \
Expand Down Expand Up @@ -253,6 +259,15 @@ inline v8::Local<v8::Object> ERR_STRING_TOO_LONG(v8::Isolate* isolate) {
return ERR_STRING_TOO_LONG(isolate, message);
}

inline void THROW_ERR_PROCESS_EXECVE_FAILED(Environment* env, int code) {
char message[128];
snprintf(message,
sizeof(message),
"process.execve failed with error code %s",
errors::errno_string(code));
THROW_ERR_PROCESS_EXECVE_FAILED(env, message);
}

#define THROW_AND_RETURN_IF_NOT_BUFFER(env, val, prefix) \
do { \
if (!Buffer::HasInstance(val)) \
Expand Down
129 changes: 129 additions & 0 deletions src/node_process_methods.cc
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,14 @@
#if defined(_MSC_VER)
#include <direct.h>
#include <io.h>
#include <process.h>
#define umask _umask
typedef int mode_t;
#else
#include <pthread.h>
#include <sys/resource.h> // getrlimit, setrlimit
#include <termios.h> // tcgetattr, tcsetattr
#include <unistd.h>
#endif

namespace node {
Expand Down Expand Up @@ -471,6 +473,125 @@ static void ReallyExit(const FunctionCallbackInfo<Value>& args) {
env->Exit(code);
}

inline char** copy_js_strings_array(Environment* env,
Local<Array> js_array,
int* target_length) {
Local<Context> context = env->context();
char** target = nullptr;
int length = js_array->Length();

CHECK_LT(length, INT_MAX);

target = new char*[length + 1];
target[length] = nullptr;

if (length > 0) {
for (int i = 0; i < length; i++) {
node::Utf8Value str(env->isolate(),
js_array->Get(context, i).ToLocalChecked());
target[i] = strdup(*str);
CHECK_NOT_NULL(target[i]);
}
}

if (target_length) {
*target_length = length;
}

return target;
}

#ifdef __POSIX__
inline int persist_standard_stream(int fd) {
int flags = fcntl(fd, F_GETFD, 0);

if (flags < 0) {
return flags;
}

flags &= ~FD_CLOEXEC;
return fcntl(fd, F_SETFD, flags);
}

static void Execve(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
Local<Context> context = env->context();

THROW_IF_INSUFFICIENT_PERMISSIONS(
env, permission::PermissionScope::kChildProcess, "");

CHECK_GT(args.Length(), 1);
CHECK(args[0]->IsString());

// Prepare arguments and environment
char** argv = nullptr;
char** envp = nullptr;
int envp_length = 0;

// Prepare arguments - Note that the pathname is always the first argument
if (args.Length() > 1) {
CHECK(args[1]->IsArray());

Local<Array> argv_array = args[1].As<Array>();

Local<Array> full_argv_array =
Array::New(env->isolate(), argv_array->Length() + 1);
full_argv_array->Set(context, 0, args[0].As<String>()).Check();
for (unsigned int i = 0; i < argv_array->Length(); i++) {
full_argv_array
->Set(context, i + 1, argv_array->Get(context, i).ToLocalChecked())
.Check();
}

argv = copy_js_strings_array(env, full_argv_array, nullptr);
} else {
Utf8Value pathname_string(env->isolate(), args[0].As<String>());

argv = new char*[2];
argv[0] = new char[pathname_string.length()];
argv[1] = nullptr;

memcpy(argv[0], *pathname_string, pathname_string.length());
}

if (args.Length() > 2) {
CHECK(args[2]->IsArray());
envp = copy_js_strings_array(env, args[2].As<Array>(), &envp_length);
}

// Set stdin, stdout and stderr to be non-close-on-exec
// so that the new process will inherit it.
for (int i = 0; i < 3; i++) {
// Operation failed. Free up memory, then throw.
if (persist_standard_stream(i) < 0) {
int error_code = errno;
delete[] argv;

if (envp_length > 0) {
delete[] envp;
}

THROW_ERR_PROCESS_EXECVE_FAILED(env, error_code);
}
}

// Perform the execve operation.
// If it returns, it means that the execve operation failed.
RunAtExit(env);
execve(argv[0], argv, envp);
int error_code = errno;

// Free up memory, then throw.
delete[] argv;

if (envp_length > 0) {
delete[] envp;
}

THROW_ERR_PROCESS_EXECVE_FAILED(env, error_code);
}
#endif

static void LoadEnvFile(const v8::FunctionCallbackInfo<v8::Value>& args) {
Environment* env = Environment::GetCurrent(args);
std::string path = ".env";
Expand Down Expand Up @@ -662,6 +783,10 @@ static void CreatePerIsolateProperties(IsolateData* isolate_data,
SetMethodNoSideEffect(isolate, target, "cwd", Cwd);
SetMethod(isolate, target, "dlopen", binding::DLOpen);
SetMethod(isolate, target, "reallyExit", ReallyExit);

#ifdef __POSIX__
SetMethod(isolate, target, "execve", Execve);
#endif
SetMethodNoSideEffect(isolate, target, "uptime", Uptime);
SetMethod(isolate, target, "patchProcessObject", PatchProcessObject);

Expand Down Expand Up @@ -704,6 +829,10 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
registry->Register(Cwd);
registry->Register(binding::DLOpen);
registry->Register(ReallyExit);

#ifdef __POSIX__
registry->Register(Execve);
#endif
registry->Register(Uptime);
registry->Register(PatchProcessObject);

Expand Down
Loading

0 comments on commit 037edbc

Please sign in to comment.