From cace949b52222a607f077adffa827bc3b1ee249d Mon Sep 17 00:00:00 2001 From: James M Snell Date: Mon, 21 May 2018 13:05:59 -0700 Subject: [PATCH 1/3] src: introduce node_bootstrap and node_process This continues the reorganization of node.cc by splitting the implementations of most process object functions and properties out to a separate node_process.cc and creates a separate temporary bootstrapper object used during Node.js bootstrap to avoid the need for deleting/overwriting process properties set during bootstrap. --- lib/internal/bootstrap/node.js | 38 +- lib/internal/process.js | 31 +- lib/internal/process/methods.js | 69 +- lib/internal/process/next_tick.js | 8 +- lib/internal/process/promises.js | 9 +- node.gyp | 3 + src/node.cc | 1271 +---------------------------- src/node_bootstrap.cc | 128 +++ src/node_internals.h | 53 ++ src/node_process.cc | 1108 +++++++++++++++++++++++++ src/node_process.h | 82 ++ 11 files changed, 1464 insertions(+), 1336 deletions(-) create mode 100644 src/node_bootstrap.cc create mode 100644 src/node_process.cc create mode 100644 src/node_process.h diff --git a/lib/internal/bootstrap/node.js b/lib/internal/bootstrap/node.js index d6fd67fe5d2c85..c092f71b0200aa 100644 --- a/lib/internal/bootstrap/node.js +++ b/lib/internal/bootstrap/node.js @@ -10,10 +10,16 @@ // process.binding(), process._linkedBinding(), internalBinding() and // NativeModule. And then { internalBinding, NativeModule } will be passed // into this bootstrapper to bootstrap Node.js core. - +// +// The bootstrapper argument is an object containing functions and properties +// that are only relevant during the bootstrap process. A permanent reference +// to this object must not be maintained, allowing the object to gc when it is +// no longer needed. 'use strict'; -(function bootstrapNodeJSCore(process, { internalBinding, NativeModule }) { +(function bootstrapNodeJSCore(process, + bootstrapper, + { internalBinding, NativeModule }) { const exceptionHandlerState = { captureFn: null }; function startup() { @@ -36,11 +42,11 @@ const _process = NativeModule.require('internal/process'); _process.setupConfig(NativeModule._source); _process.setupSignalHandlers(); - _process.setupUncaughtExceptionCapture(exceptionHandlerState); + _process.setupUncaughtExceptionCapture(exceptionHandlerState, bootstrapper); NativeModule.require('internal/process/warning').setup(); - NativeModule.require('internal/process/next_tick').setup(); + NativeModule.require('internal/process/next_tick').setup(bootstrapper); NativeModule.require('internal/process/stdio').setup(); - NativeModule.require('internal/process/methods').setup(); + NativeModule.require('internal/process/methods').setup(bootstrapper); const perf = process.binding('performance'); const { @@ -55,9 +61,9 @@ NODE_PERFORMANCE_MILESTONE_PRELOAD_MODULE_LOAD_END } = perf.constants; - _process.setup_hrtime(); - _process.setup_cpuUsage(); - _process.setupMemoryUsage(); + _process.setup_hrtime(bootstrapper); + _process.setup_cpuUsage(bootstrapper); + _process.setupMemoryUsage(bootstrapper); _process.setupKillAndExit(); if (global.__coverage__) NativeModule.require('internal/process/write-coverage').setup(); @@ -79,7 +85,7 @@ } _process.setupChannel(); - _process.setupRawDebug(); + _process.setupRawDebug(bootstrapper); const browserGlobals = !process._noBrowserGlobals; if (browserGlobals) { @@ -116,9 +122,14 @@ 'DeprecationWarning', 'DEP0062', startup, true); } - if (process.binding('config').experimentalModules || - process.binding('config').experimentalVMModules) { - if (process.binding('config').experimentalModules) { + const { + experimentalModules, + experimentalVMModules, + pendingDeprecation + } = process.binding('config'); + + if (experimentalModules || experimentalVMModules) { + if (experimentalModules) { process.emitWarning( 'The ESM module loader is experimental.', 'ExperimentalWarning', undefined); @@ -129,7 +140,6 @@ { // Install legacy getters on the `util` binding for typechecking. // TODO(addaleax): Turn into a full runtime deprecation. - const { pendingDeprecation } = process.binding('config'); const { deprecate } = NativeModule.require('internal/util'); const utilBinding = process.binding('util'); const types = internalBinding('types'); @@ -294,7 +304,7 @@ } function setupProcessObject() { - process._setupProcessObject(pushValueToArray); + bootstrapper._setupProcessObject(pushValueToArray); function pushValueToArray() { for (var i = 0; i < arguments.length; i++) diff --git a/lib/internal/process.js b/lib/internal/process.js index 3d02e6eb87cc6a..e7887f0acc8b7f 100644 --- a/lib/internal/process.js +++ b/lib/internal/process.js @@ -25,10 +25,7 @@ process.assert = deprecate( 'DEP0100'); // Set up the process.cpuUsage() function. -function setup_cpuUsage() { - // Get the native function, which will be replaced with a JS version. - const _cpuUsage = process.cpuUsage; - +function setup_cpuUsage({ _cpuUsage }) { // Create the argument array that will be passed to the native function. const cpuValues = new Float64Array(2); @@ -92,8 +89,7 @@ function setup_cpuUsage() { // The 3 entries filled in by the original process.hrtime contains // the upper/lower 32 bits of the second part of the value, // and the remaining nanoseconds of the value. -function setup_hrtime() { - const _hrtime = process.hrtime; +function setup_hrtime({ _hrtime }) { const hrValues = new Uint32Array(3); process.hrtime = function hrtime(time) { @@ -120,12 +116,11 @@ function setup_hrtime() { }; } -function setupMemoryUsage() { - const memoryUsage_ = process.memoryUsage; +function setupMemoryUsage({ _memoryUsage }) { const memValues = new Float64Array(4); process.memoryUsage = function memoryUsage() { - memoryUsage_(memValues); + _memoryUsage(memValues); return { rss: memValues[0], heapTotal: memValues[1], @@ -245,23 +240,21 @@ function setupChannel() { } -function setupRawDebug() { - const rawDebug = process._rawDebug; +function setupRawDebug({ _rawDebug }) { process._rawDebug = function() { - rawDebug(util.format.apply(null, arguments)); + _rawDebug(util.format.apply(null, arguments)); }; } -function setupUncaughtExceptionCapture(exceptionHandlerState) { - // This is a typed array for faster communication with JS. - const shouldAbortOnUncaughtToggle = process._shouldAbortOnUncaughtToggle; - delete process._shouldAbortOnUncaughtToggle; - +function setupUncaughtExceptionCapture(exceptionHandlerState, + { _shouldAbortOnUncaughtToggle }) { + // _shouldAbortOnUncaughtToggleis a typed array for faster + // communication with JS. process.setUncaughtExceptionCaptureCallback = function(fn) { if (fn === null) { exceptionHandlerState.captureFn = fn; - shouldAbortOnUncaughtToggle[0] = 1; + _shouldAbortOnUncaughtToggle[0] = 1; return; } if (typeof fn !== 'function') { @@ -271,7 +264,7 @@ function setupUncaughtExceptionCapture(exceptionHandlerState) { throw new ERR_UNCAUGHT_EXCEPTION_CAPTURE_ALREADY_SET(); } exceptionHandlerState.captureFn = fn; - shouldAbortOnUncaughtToggle[0] = 0; + _shouldAbortOnUncaughtToggle[0] = 0; }; process.hasUncaughtExceptionCaptureCallback = function() { diff --git a/lib/internal/process/methods.js b/lib/internal/process/methods.js index 7ed993728dbd52..d2a2fadfbac873 100644 --- a/lib/internal/process/methods.js +++ b/lib/internal/process/methods.js @@ -9,55 +9,40 @@ const { validateUint32 } = require('internal/validators'); -function setupProcessMethods() { +function setupProcessMethods(bootstrapper) { // Non-POSIX platforms like Windows don't have certain methods. - if (process.setgid !== undefined) { - setupPosixMethods(); + if (bootstrapper._setgid !== undefined) { + setupPosixMethods(bootstrapper); } - const { - chdir: _chdir, - umask: _umask, - } = process; - - process.chdir = chdir; - process.umask = umask; + setupChdir(bootstrapper); + setupUmask(bootstrapper); +} - function chdir(directory) { +function setupChdir({ _chdir }) { + process.chdir = function chdir(directory) { if (typeof directory !== 'string') { throw new ERR_INVALID_ARG_TYPE('directory', 'string', directory); } return _chdir(directory); - } + }; +} - function umask(mask) { +function setupUmask({ _umask }) { + process.umask = function umask(mask) { if (mask === undefined) { // Get the mask return _umask(mask); } mask = validateAndMaskMode(mask, 'mask'); return _umask(mask); - } + }; } -function setupPosixMethods() { - const { - initgroups: _initgroups, - setegid: _setegid, - seteuid: _seteuid, - setgid: _setgid, - setuid: _setuid, - setgroups: _setgroups - } = process; - - process.initgroups = initgroups; - process.setegid = setegid; - process.seteuid = seteuid; - process.setgid = setgid; - process.setuid = setuid; - process.setgroups = setgroups; +function setupPosixMethods({ _initgroups, _setegid, _seteuid, + _setgid, _setuid, _setgroups }) { - function initgroups(user, extraGroup) { + process.initgroups = function initgroups(user, extraGroup) { validateId(user, 'user'); validateId(extraGroup, 'extraGroup'); // Result is 0 on success, 1 if user is unknown, 2 if group is unknown. @@ -67,25 +52,25 @@ function setupPosixMethods() { } else if (result === 2) { throw new ERR_UNKNOWN_CREDENTIAL('Group', extraGroup); } - } + }; - function setegid(id) { + process.setegid = function setegid(id) { return execId(id, 'Group', _setegid); - } + }; - function seteuid(id) { + process.seteuid = function seteuid(id) { return execId(id, 'User', _seteuid); - } + }; - function setgid(id) { + process.setgid = function setgid(id) { return execId(id, 'Group', _setgid); - } + }; - function setuid(id) { + process.setuid = function setuid(id) { return execId(id, 'User', _setuid); - } + }; - function setgroups(groups) { + process.setgroups = function setgroups(groups) { if (!Array.isArray(groups)) { throw new ERR_INVALID_ARG_TYPE('groups', 'Array', groups); } @@ -98,7 +83,7 @@ function setupPosixMethods() { if (result > 0) { throw new ERR_UNKNOWN_CREDENTIAL('Group', groups[result - 1]); } - } + }; function execId(id, type, method) { validateId(id, 'id'); diff --git a/lib/internal/process/next_tick.js b/lib/internal/process/next_tick.js index dbe0ce8cdbdacf..41290b4f07da65 100644 --- a/lib/internal/process/next_tick.js +++ b/lib/internal/process/next_tick.js @@ -2,7 +2,7 @@ exports.setup = setupNextTick; -function setupNextTick() { +function setupNextTick({ _setupNextTick, _setupPromises }) { const { getDefaultTriggerAsyncId, newAsyncId, @@ -14,10 +14,10 @@ function setupNextTick() { emitDestroy, symbols: { async_id_symbol, trigger_async_id_symbol } } = require('internal/async_hooks'); - const promises = require('internal/process/promises'); + const emitPromiseRejectionWarnings = + require('internal/process/promises').setup(_setupPromises); const { ERR_INVALID_CALLBACK } = require('internal/errors').codes; const FixedQueue = require('internal/fixed_queue'); - const { emitPromiseRejectionWarnings } = promises; // tickInfo is used so that the C++ code in src/node.cc can // have easy access to our nextTick state, and avoid unnecessary @@ -26,7 +26,7 @@ function setupNextTick() { const [ tickInfo, runMicrotasks - ] = process._setupNextTick(_tickCallback); + ] = _setupNextTick(_tickCallback); // *Must* match Environment::TickInfo::Fields in src/env.h. const kHasScheduled = 0; diff --git a/lib/internal/process/promises.js b/lib/internal/process/promises.js index 6cc366d8b2b09a..f54f34b9ae92f4 100644 --- a/lib/internal/process/promises.js +++ b/lib/internal/process/promises.js @@ -7,11 +7,12 @@ const pendingUnhandledRejections = []; const asyncHandledRejections = []; let lastPromiseId = 0; -module.exports = { - emitPromiseRejectionWarnings -}; +exports.setup = setupPromises; -process._setupPromises(unhandledRejection, handledRejection); +function setupPromises(_setupPromises) { + _setupPromises(unhandledRejection, handledRejection); + return emitPromiseRejectionWarnings; +} function unhandledRejection(promise, reason) { maybeUnhandledPromises.set(promise, { diff --git a/node.gyp b/node.gyp index bf768c1995cd20..9433a431bbc77d 100644 --- a/node.gyp +++ b/node.gyp @@ -322,6 +322,7 @@ 'src/node_api.cc', 'src/node_api.h', 'src/node_api_types.h', + 'src/node_bootstrap.cc', 'src/node_buffer.cc', 'src/node_config.cc', 'src/node_constants.cc', @@ -336,6 +337,7 @@ 'src/node_platform.cc', 'src/node_perf.cc', 'src/node_postmortem_metadata.cc', + 'src/node_process.cc', 'src/node_serdes.cc', 'src/node_trace_events.cc', 'src/node_types.cc', @@ -395,6 +397,7 @@ 'src/node_perf_common.h', 'src/node_persistent.h', 'src/node_platform.h', + 'src/node_process.h', 'src/node_root_certs.h', 'src/node_version.h', 'src/node_watchdog.h', diff --git a/src/node.cc b/src/node.cc index 53130635781704..6b828760e21b2b 100644 --- a/src/node.cc +++ b/src/node.cc @@ -23,6 +23,7 @@ #include "node_constants.h" #include "node_javascript.h" #include "node_platform.h" +#include "node_process.h" #include "node_version.h" #include "node_internals.h" #include "node_revert.h" @@ -42,10 +43,6 @@ #include "node_i18n.h" #endif -#if HAVE_INSPECTOR -#include "inspector_io.h" -#endif - #if defined HAVE_DTRACE || defined HAVE_ETW #include "node_dtrace.h" #endif @@ -95,23 +92,12 @@ #if defined(_MSC_VER) #include #include -#define umask _umask -typedef int mode_t; #else #include #include // getrlimit, setrlimit #include // setuid, getuid #endif -#if defined(__POSIX__) && !defined(__ANDROID__) && !defined(__CloudABI__) -#include // getpwnam() -#include // getgrnam() -#endif - -#if defined(__POSIX__) -#include -#endif - #ifdef __APPLE__ #include #define environ (*_NSGetEnviron()) @@ -133,15 +119,12 @@ namespace node { using v8::Array; using v8::ArrayBuffer; -using v8::Boolean; using v8::Context; using v8::EscapableHandleScope; using v8::Exception; -using v8::Float64Array; using v8::Function; using v8::FunctionCallbackInfo; using v8::HandleScope; -using v8::HeapStatistics; using v8::Integer; using v8::Isolate; using v8::Just; @@ -158,20 +141,16 @@ using v8::Number; using v8::Object; using v8::ObjectTemplate; using v8::Promise; -using v8::PromiseRejectMessage; -using v8::PropertyCallbackInfo; using v8::ScriptOrigin; using v8::SealHandleScope; using v8::String; using v8::TryCatch; -using v8::Uint32; -using v8::Uint32Array; using v8::Undefined; using v8::V8; using v8::Value; -static Mutex process_mutex; -static Mutex environ_mutex; +Mutex process_mutex; +Mutex environ_mutex; static bool print_eval = false; static bool force_repl = false; @@ -188,11 +167,12 @@ static int v8_thread_pool_size = v8_default_thread_pool_size; static bool prof_process = false; static bool v8_is_profiling = false; static bool node_is_initialized = false; -static node_module* modpending; -static node_module* modlist_builtin; -static node_module* modlist_internal; -static node_module* modlist_linked; -static node_module* modlist_addon; + +node_module* modpending; +node_module* modlist_builtin; +node_module* modlist_internal; +node_module* modlist_linked; +node_module* modlist_addon; static std::string trace_enabled_categories; // NOLINT(runtime/string) static std::string trace_file_pattern = // NOLINT(runtime/string) "node_trace.${rotation}.log"; @@ -278,7 +258,7 @@ bool v8_initialized = false; bool linux_at_secure = false; // process-relative uptime base, initialized at start-up -static double prog_start_time; +double prog_start_time; static Mutex node_isolate_mutex; static v8::Isolate* node_isolate; @@ -381,41 +361,6 @@ static struct { static const unsigned kMaxSignal = 32; #endif -static void PrintErrorString(const char* format, ...) { - va_list ap; - va_start(ap, format); -#ifdef _WIN32 - HANDLE stderr_handle = GetStdHandle(STD_ERROR_HANDLE); - - // Check if stderr is something other than a tty/console - if (stderr_handle == INVALID_HANDLE_VALUE || - stderr_handle == nullptr || - uv_guess_handle(_fileno(stderr)) != UV_TTY) { - vfprintf(stderr, format, ap); - va_end(ap); - return; - } - - // Fill in any placeholders - int n = _vscprintf(format, ap); - std::vector out(n + 1); - vsprintf(out.data(), format, ap); - - // Get required wide buffer size - n = MultiByteToWideChar(CP_UTF8, 0, out.data(), -1, nullptr, 0); - - std::vector wbuf(n); - MultiByteToWideChar(CP_UTF8, 0, out.data(), -1, wbuf.data(), n); - - // Don't include the null character in the output - CHECK_GT(n, 0); - WriteConsoleW(stderr_handle, wbuf.data(), n - 1, nullptr, nullptr); -#else - vfprintf(stderr, format, ap); -#endif - va_end(ap); -} - const char *signo_string(int signo) { #define SIGNO_CASE(e) case e: return #e; switch (signo) { @@ -604,109 +549,6 @@ void* ArrayBufferAllocator::Allocate(size_t size) { return UncheckedMalloc(size); } -namespace { - -bool ShouldAbortOnUncaughtException(Isolate* isolate) { - HandleScope scope(isolate); - Environment* env = Environment::GetCurrent(isolate); - return env->should_abort_on_uncaught_toggle()[0] && - !env->inside_should_not_abort_on_uncaught_scope(); -} - - -void RunMicrotasks(const FunctionCallbackInfo& args) { - args.GetIsolate()->RunMicrotasks(); -} - - -void SetupProcessObject(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - - CHECK(args[0]->IsFunction()); - - env->set_push_values_to_array_function(args[0].As()); - env->process_object()->Delete( - env->context(), - FIXED_ONE_BYTE_STRING(env->isolate(), "_setupProcessObject")).FromJust(); -} - - -void SetupNextTick(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - - CHECK(args[0]->IsFunction()); - - env->set_tick_callback_function(args[0].As()); - - env->process_object()->Delete( - env->context(), - FIXED_ONE_BYTE_STRING(env->isolate(), "_setupNextTick")).FromJust(); - - v8::Local run_microtasks_fn = - env->NewFunctionTemplate(RunMicrotasks)->GetFunction(env->context()) - .ToLocalChecked(); - run_microtasks_fn->SetName( - FIXED_ONE_BYTE_STRING(env->isolate(), "runMicrotasks")); - - Local ret = Array::New(env->isolate(), 2); - ret->Set(env->context(), 0, - env->tick_info()->fields().GetJSArray()).FromJust(); - ret->Set(env->context(), 1, run_microtasks_fn).FromJust(); - - args.GetReturnValue().Set(ret); -} - -void PromiseRejectCallback(PromiseRejectMessage message) { - Local promise = message.GetPromise(); - Isolate* isolate = promise->GetIsolate(); - v8::PromiseRejectEvent event = message.GetEvent(); - - Environment* env = Environment::GetCurrent(isolate); - Local callback; - Local value; - - if (event == v8::kPromiseRejectWithNoHandler) { - callback = env->promise_reject_unhandled_function(); - value = message.GetValue(); - - if (value.IsEmpty()) - value = Undefined(isolate); - } else if (event == v8::kPromiseHandlerAddedAfterReject) { - callback = env->promise_reject_handled_function(); - value = Undefined(isolate); - } else { - UNREACHABLE(); - } - - Local args[] = { promise, value }; - MaybeLocal ret = callback->Call(env->context(), - Undefined(isolate), - arraysize(args), - args); - - if (!ret.IsEmpty() && ret.ToLocalChecked()->IsTrue()) - env->tick_info()->promise_rejections_toggle_on(); -} - -void SetupPromises(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - Isolate* isolate = env->isolate(); - - CHECK(args[0]->IsFunction()); - CHECK(args[1]->IsFunction()); - - isolate->SetPromiseRejectCallback(PromiseRejectCallback); - env->set_promise_reject_unhandled_function(args[0].As()); - env->set_promise_reject_handled_function(args[1].As()); - - env->process_object()->Delete( - env->context(), - FIXED_ONE_BYTE_STRING(isolate, "_setupPromises")).FromJust(); -} - -} // anonymous namespace - - void AddPromiseHook(v8::Isolate* isolate, promise_hook_func fn, void* arg) { Environment* env = Environment::GetCurrent(isolate); env->AddPromiseHook(fn, arg); @@ -1220,68 +1062,6 @@ static Local ExecuteString(Environment* env, return scope.Escape(result.ToLocalChecked()); } - -static void GetActiveRequests(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - - Local ary = Array::New(args.GetIsolate()); - Local ctx = env->context(); - Local fn = env->push_values_to_array_function(); - Local argv[NODE_PUSH_VAL_TO_ARRAY_MAX]; - size_t idx = 0; - - for (auto w : *env->req_wrap_queue()) { - if (w->persistent().IsEmpty()) - continue; - argv[idx] = w->object(); - if (++idx >= arraysize(argv)) { - fn->Call(ctx, ary, idx, argv).ToLocalChecked(); - idx = 0; - } - } - - if (idx > 0) { - fn->Call(ctx, ary, idx, argv).ToLocalChecked(); - } - - args.GetReturnValue().Set(ary); -} - - -// Non-static, friend of HandleWrap. Could have been a HandleWrap method but -// implemented here for consistency with GetActiveRequests(). -void GetActiveHandles(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - - Local ary = Array::New(env->isolate()); - Local ctx = env->context(); - Local fn = env->push_values_to_array_function(); - Local argv[NODE_PUSH_VAL_TO_ARRAY_MAX]; - size_t idx = 0; - - Local owner_sym = env->owner_string(); - - for (auto w : *env->handle_wrap_queue()) { - if (w->persistent().IsEmpty() || !HandleWrap::HasRef(w)) - continue; - Local object = w->object(); - Local owner = object->Get(owner_sym); - if (owner->IsUndefined()) - owner = object; - argv[idx] = owner; - if (++idx >= arraysize(argv)) { - fn->Call(ctx, ary, idx, argv).ToLocalChecked(); - idx = 0; - } - } - if (idx > 0) { - fn->Call(ctx, ary, idx, argv).ToLocalChecked(); - } - - args.GetReturnValue().Set(ary); -} - - NO_RETURN void Abort() { DumpBacktrace(stderr); fflush(stderr); @@ -1305,391 +1085,6 @@ NO_RETURN void Assert(const char* const (*args)[4]) { Abort(); } - -static void Abort(const FunctionCallbackInfo& args) { - Abort(); -} - - -static void Chdir(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - - CHECK_EQ(args.Length(), 1); - CHECK(args[0]->IsString()); - node::Utf8Value path(args.GetIsolate(), args[0]); - int err = uv_chdir(*path); - if (err) { - return env->ThrowUVException(err, "chdir", nullptr, *path, nullptr); - } -} - - -static void Cwd(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); -#ifdef _WIN32 - /* MAX_PATH is in characters, not bytes. Make sure we have enough headroom. */ - char buf[MAX_PATH * 4]; -#else - char buf[PATH_MAX]; -#endif - - size_t cwd_len = sizeof(buf); - int err = uv_cwd(buf, &cwd_len); - if (err) { - return env->ThrowUVException(err, "uv_cwd"); - } - - Local cwd = String::NewFromUtf8(env->isolate(), - buf, - String::kNormalString, - cwd_len); - args.GetReturnValue().Set(cwd); -} - - -static void Umask(const FunctionCallbackInfo& args) { - uint32_t old; - - CHECK_EQ(args.Length(), 1); - CHECK(args[0]->IsUndefined() || args[0]->IsUint32()); - - if (args[0]->IsUndefined()) { - old = umask(0); - umask(static_cast(old)); - } else { - int oct = args[0].As()->Value(); - old = umask(static_cast(oct)); - } - - args.GetReturnValue().Set(old); -} - - -#if defined(__POSIX__) && !defined(__ANDROID__) && !defined(__CloudABI__) - -static const uid_t uid_not_found = static_cast(-1); -static const gid_t gid_not_found = static_cast(-1); - - -static uid_t uid_by_name(const char* name) { - struct passwd pwd; - struct passwd* pp; - char buf[8192]; - - errno = 0; - pp = nullptr; - - if (getpwnam_r(name, &pwd, buf, sizeof(buf), &pp) == 0 && pp != nullptr) { - return pp->pw_uid; - } - - return uid_not_found; -} - - -static char* name_by_uid(uid_t uid) { - struct passwd pwd; - struct passwd* pp; - char buf[8192]; - int rc; - - errno = 0; - pp = nullptr; - - if ((rc = getpwuid_r(uid, &pwd, buf, sizeof(buf), &pp)) == 0 && - pp != nullptr) { - return strdup(pp->pw_name); - } - - if (rc == 0) { - errno = ENOENT; - } - - return nullptr; -} - - -static gid_t gid_by_name(const char* name) { - struct group pwd; - struct group* pp; - char buf[8192]; - - errno = 0; - pp = nullptr; - - if (getgrnam_r(name, &pwd, buf, sizeof(buf), &pp) == 0 && pp != nullptr) { - return pp->gr_gid; - } - - return gid_not_found; -} - - -#if 0 // For future use. -static const char* name_by_gid(gid_t gid) { - struct group pwd; - struct group* pp; - char buf[8192]; - int rc; - - errno = 0; - pp = nullptr; - - if ((rc = getgrgid_r(gid, &pwd, buf, sizeof(buf), &pp)) == 0 && - pp != nullptr) { - return strdup(pp->gr_name); - } - - if (rc == 0) { - errno = ENOENT; - } - - return nullptr; -} -#endif - - -static uid_t uid_by_name(Isolate* isolate, Local value) { - if (value->IsUint32()) { - return static_cast(value->Uint32Value()); - } else { - node::Utf8Value name(isolate, value); - return uid_by_name(*name); - } -} - - -static gid_t gid_by_name(Isolate* isolate, Local value) { - if (value->IsUint32()) { - return static_cast(value->Uint32Value()); - } else { - node::Utf8Value name(isolate, value); - return gid_by_name(*name); - } -} - -static void GetUid(const FunctionCallbackInfo& args) { - // uid_t is an uint32_t on all supported platforms. - args.GetReturnValue().Set(static_cast(getuid())); -} - - -static void GetGid(const FunctionCallbackInfo& args) { - // gid_t is an uint32_t on all supported platforms. - args.GetReturnValue().Set(static_cast(getgid())); -} - - -static void GetEUid(const FunctionCallbackInfo& args) { - // uid_t is an uint32_t on all supported platforms. - args.GetReturnValue().Set(static_cast(geteuid())); -} - - -static void GetEGid(const FunctionCallbackInfo& args) { - // gid_t is an uint32_t on all supported platforms. - args.GetReturnValue().Set(static_cast(getegid())); -} - - -static void SetGid(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - - CHECK_EQ(args.Length(), 1); - CHECK(args[0]->IsUint32() || args[0]->IsString()); - - gid_t gid = gid_by_name(env->isolate(), args[0]); - - if (gid == gid_not_found) { - // Tells JS to throw ERR_INVALID_CREDENTIAL - args.GetReturnValue().Set(1); - } else if (setgid(gid)) { - env->ThrowErrnoException(errno, "setgid"); - } else { - args.GetReturnValue().Set(0); - } -} - - -static void SetEGid(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - - CHECK_EQ(args.Length(), 1); - CHECK(args[0]->IsUint32() || args[0]->IsString()); - - gid_t gid = gid_by_name(env->isolate(), args[0]); - - if (gid == gid_not_found) { - // Tells JS to throw ERR_INVALID_CREDENTIAL - args.GetReturnValue().Set(1); - } else if (setegid(gid)) { - env->ThrowErrnoException(errno, "setegid"); - } else { - args.GetReturnValue().Set(0); - } -} - - -static void SetUid(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - - CHECK_EQ(args.Length(), 1); - CHECK(args[0]->IsUint32() || args[0]->IsString()); - - uid_t uid = uid_by_name(env->isolate(), args[0]); - - if (uid == uid_not_found) { - // Tells JS to throw ERR_INVALID_CREDENTIAL - args.GetReturnValue().Set(1); - } else if (setuid(uid)) { - env->ThrowErrnoException(errno, "setuid"); - } else { - args.GetReturnValue().Set(0); - } -} - - -static void SetEUid(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - - CHECK_EQ(args.Length(), 1); - CHECK(args[0]->IsUint32() || args[0]->IsString()); - - uid_t uid = uid_by_name(env->isolate(), args[0]); - - if (uid == uid_not_found) { - // Tells JS to throw ERR_INVALID_CREDENTIAL - args.GetReturnValue().Set(1); - } else if (seteuid(uid)) { - env->ThrowErrnoException(errno, "seteuid"); - } else { - args.GetReturnValue().Set(0); - } -} - - -static void GetGroups(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - - int ngroups = getgroups(0, nullptr); - - if (ngroups == -1) { - return env->ThrowErrnoException(errno, "getgroups"); - } - - gid_t* groups = new gid_t[ngroups]; - - ngroups = getgroups(ngroups, groups); - - if (ngroups == -1) { - delete[] groups; - return env->ThrowErrnoException(errno, "getgroups"); - } - - Local groups_list = Array::New(env->isolate(), ngroups); - bool seen_egid = false; - gid_t egid = getegid(); - - for (int i = 0; i < ngroups; i++) { - groups_list->Set(i, Integer::New(env->isolate(), groups[i])); - if (groups[i] == egid) - seen_egid = true; - } - - delete[] groups; - - if (seen_egid == false) { - groups_list->Set(ngroups, Integer::New(env->isolate(), egid)); - } - - args.GetReturnValue().Set(groups_list); -} - - -static void SetGroups(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - - CHECK_EQ(args.Length(), 1); - CHECK(args[0]->IsArray()); - - Local groups_list = args[0].As(); - size_t size = groups_list->Length(); - gid_t* groups = new gid_t[size]; - - for (size_t i = 0; i < size; i++) { - gid_t gid = gid_by_name(env->isolate(), groups_list->Get(i)); - - if (gid == gid_not_found) { - delete[] groups; - // Tells JS to throw ERR_INVALID_CREDENTIAL - args.GetReturnValue().Set(static_cast(i + 1)); - return; - } - - groups[i] = gid; - } - - int rc = setgroups(size, groups); - delete[] groups; - - if (rc == -1) { - return env->ThrowErrnoException(errno, "setgroups"); - } - - args.GetReturnValue().Set(0); -} - - -static void InitGroups(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - - CHECK_EQ(args.Length(), 2); - CHECK(args[0]->IsUint32() || args[0]->IsString()); - CHECK(args[1]->IsUint32() || args[1]->IsString()); - - node::Utf8Value arg0(env->isolate(), args[0]); - gid_t extra_group; - bool must_free; - char* user; - - if (args[0]->IsUint32()) { - user = name_by_uid(args[0]->Uint32Value()); - must_free = true; - } else { - user = *arg0; - must_free = false; - } - - if (user == nullptr) { - // Tells JS to throw ERR_INVALID_CREDENTIAL - return args.GetReturnValue().Set(1); - } - - extra_group = gid_by_name(env->isolate(), args[1]); - - if (extra_group == gid_not_found) { - if (must_free) - free(user); - // Tells JS to throw ERR_INVALID_CREDENTIAL - return args.GetReturnValue().Set(2); - } - - int rc = initgroups(user, extra_group); - - if (must_free) { - free(user); - } - - if (rc) { - return env->ThrowErrnoException(errno, "initgroups"); - } - - args.GetReturnValue().Set(0); -} - -#endif // __POSIX__ && !defined(__ANDROID__) && !defined(__CloudABI__) - - static void WaitForInspectorDisconnect(Environment* env) { #if HAVE_INSPECTOR if (env->inspector_agent()->HasConnectedSessions()) { @@ -1718,112 +1113,6 @@ static void Exit(const FunctionCallbackInfo& args) { } -static void Uptime(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - double uptime; - - uv_update_time(env->event_loop()); - uptime = uv_now(env->event_loop()) - prog_start_time; - - args.GetReturnValue().Set(Number::New(env->isolate(), uptime / 1000)); -} - - -static void MemoryUsage(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - - size_t rss; - int err = uv_resident_set_memory(&rss); - if (err) { - return env->ThrowUVException(err, "uv_resident_set_memory"); - } - - Isolate* isolate = env->isolate(); - // V8 memory usage - HeapStatistics v8_heap_stats; - isolate->GetHeapStatistics(&v8_heap_stats); - - // Get the double array pointer from the Float64Array argument. - CHECK(args[0]->IsFloat64Array()); - Local array = args[0].As(); - CHECK_EQ(array->Length(), 4); - Local ab = array->Buffer(); - double* fields = static_cast(ab->GetContents().Data()); - - fields[0] = rss; - fields[1] = v8_heap_stats.total_heap_size(); - fields[2] = v8_heap_stats.used_heap_size(); - fields[3] = isolate->AdjustAmountOfExternalAllocatedMemory(0); -} - - -static void Kill(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - - if (args.Length() != 2) { - return env->ThrowError("Bad argument."); - } - - int pid = args[0]->Int32Value(); - int sig = args[1]->Int32Value(); - int err = uv_kill(pid, sig); - args.GetReturnValue().Set(err); -} - -// used in Hrtime() below -#define NANOS_PER_SEC 1000000000 - -// Hrtime exposes libuv's uv_hrtime() high-resolution timer. -// The value returned by uv_hrtime() is a 64-bit int representing nanoseconds, -// so this function instead fills in an Uint32Array with 3 entries, -// to avoid any integer overflow possibility. -// The first two entries contain the second part of the value -// broken into the upper/lower 32 bits to be converted back in JS, -// because there is no Uint64Array in JS. -// The third entry contains the remaining nanosecond part of the value. -static void Hrtime(const FunctionCallbackInfo& args) { - uint64_t t = uv_hrtime(); - - Local ab = args[0].As()->Buffer(); - uint32_t* fields = static_cast(ab->GetContents().Data()); - - fields[0] = (t / NANOS_PER_SEC) >> 32; - fields[1] = (t / NANOS_PER_SEC) & 0xffffffff; - fields[2] = t % NANOS_PER_SEC; -} - -// Microseconds in a second, as a float, used in CPUUsage() below -#define MICROS_PER_SEC 1e6 - -// CPUUsage use libuv's uv_getrusage() this-process resource usage accessor, -// to access ru_utime (user CPU time used) and ru_stime (system CPU time used), -// which are uv_timeval_t structs (long tv_sec, long tv_usec). -// Returns those values as Float64 microseconds in the elements of the array -// passed to the function. -static void CPUUsage(const FunctionCallbackInfo& args) { - uv_rusage_t rusage; - - // Call libuv to get the values we'll return. - int err = uv_getrusage(&rusage); - if (err) { - // On error, return the strerror version of the error code. - Local errmsg = OneByteString(args.GetIsolate(), uv_strerror(err)); - args.GetReturnValue().Set(errmsg); - return; - } - - // Get the double array pointer from the Float64Array argument. - CHECK(args[0]->IsFloat64Array()); - Local array = args[0].As(); - CHECK_EQ(array->Length(), 2); - Local ab = array->Buffer(); - double* fields = static_cast(ab->GetContents().Data()); - - // Set the Float64Array elements to be user / system values in microseconds. - fields[0] = MICROS_PER_SEC * rusage.ru_utime.tv_sec + rusage.ru_utime.tv_usec; - fields[1] = MICROS_PER_SEC * rusage.ru_stime.tv_sec + rusage.ru_stime.tv_usec; -} - extern "C" void node_module_register(void* m) { struct node_module* mp = reinterpret_cast(m); @@ -1868,207 +1157,6 @@ node_module* get_linked_module(const char* name) { return FindModule(modlist_linked, name, NM_F_LINKED); } -class DLib { - public: -#ifdef __POSIX__ - static const int kDefaultFlags = RTLD_LAZY; -#else - static const int kDefaultFlags = 0; -#endif - - inline DLib(const char* filename, int flags) - : filename_(filename), flags_(flags), handle_(nullptr) {} - - inline bool Open(); - inline void Close(); - inline void* GetSymbolAddress(const char* name); - - const std::string filename_; - const int flags_; - std::string errmsg_; - void* handle_; -#ifndef __POSIX__ - uv_lib_t lib_; -#endif - private: - DISALLOW_COPY_AND_ASSIGN(DLib); -}; - - -#ifdef __POSIX__ -bool DLib::Open() { - handle_ = dlopen(filename_.c_str(), flags_); - if (handle_ != nullptr) - return true; - errmsg_ = dlerror(); - return false; -} - -void DLib::Close() { - if (handle_ == nullptr) return; - dlclose(handle_); - handle_ = nullptr; -} - -void* DLib::GetSymbolAddress(const char* name) { - return dlsym(handle_, name); -} -#else // !__POSIX__ -bool DLib::Open() { - int ret = uv_dlopen(filename_.c_str(), &lib_); - if (ret == 0) { - handle_ = static_cast(lib_.handle); - return true; - } - errmsg_ = uv_dlerror(&lib_); - uv_dlclose(&lib_); - return false; -} - -void DLib::Close() { - if (handle_ == nullptr) return; - uv_dlclose(&lib_); - handle_ = nullptr; -} - -void* DLib::GetSymbolAddress(const char* name) { - void* address; - if (0 == uv_dlsym(&lib_, name, &address)) return address; - return nullptr; -} -#endif // !__POSIX__ - -using InitializerCallback = void (*)(Local exports, - Local module, - Local context); - -inline InitializerCallback GetInitializerCallback(DLib* dlib) { - const char* name = "node_register_module_v" STRINGIFY(NODE_MODULE_VERSION); - return reinterpret_cast(dlib->GetSymbolAddress(name)); -} - -inline napi_addon_register_func GetNapiInitializerCallback(DLib* dlib) { - const char* name = - STRINGIFY(NAPI_MODULE_INITIALIZER_BASE) STRINGIFY(NAPI_MODULE_VERSION); - return - reinterpret_cast(dlib->GetSymbolAddress(name)); -} - -// DLOpen is process.dlopen(module, filename, flags). -// Used to load 'module.node' dynamically shared objects. -// -// FIXME(bnoordhuis) Not multi-context ready. TBD how to resolve the conflict -// when two contexts try to load the same shared object. Maybe have a shadow -// cache that's a plain C list or hash table that's shared across contexts? -static void DLOpen(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - auto context = env->context(); - - CHECK_EQ(modpending, nullptr); - - if (args.Length() < 2) { - env->ThrowError("process.dlopen needs at least 2 arguments."); - return; - } - - int32_t flags = DLib::kDefaultFlags; - if (args.Length() > 2 && !args[2]->Int32Value(context).To(&flags)) { - return env->ThrowTypeError("flag argument must be an integer."); - } - - Local module; - Local exports; - Local exports_v; - if (!args[0]->ToObject(context).ToLocal(&module) || - !module->Get(context, env->exports_string()).ToLocal(&exports_v) || - !exports_v->ToObject(context).ToLocal(&exports)) { - return; // Exception pending. - } - - node::Utf8Value filename(env->isolate(), args[1]); // Cast - DLib dlib(*filename, flags); - bool is_opened = dlib.Open(); - - // Objects containing v14 or later modules will have registered themselves - // on the pending list. Activate all of them now. At present, only one - // module per object is supported. - node_module* const mp = modpending; - modpending = nullptr; - - if (!is_opened) { - Local errmsg = OneByteString(env->isolate(), dlib.errmsg_.c_str()); - dlib.Close(); -#ifdef _WIN32 - // Windows needs to add the filename into the error message - errmsg = String::Concat(errmsg, - args[1]->ToString(context).ToLocalChecked()); -#endif // _WIN32 - env->isolate()->ThrowException(Exception::Error(errmsg)); - return; - } - - if (mp == nullptr) { - if (auto callback = GetInitializerCallback(&dlib)) { - callback(exports, module, context); - } else if (auto napi_callback = GetNapiInitializerCallback(&dlib)) { - napi_module_register_by_symbol(exports, module, context, napi_callback); - } else { - dlib.Close(); - env->ThrowError("Module did not self-register."); - } - return; - } - - // -1 is used for N-API modules - if ((mp->nm_version != -1) && (mp->nm_version != NODE_MODULE_VERSION)) { - // Even if the module did self-register, it may have done so with the wrong - // version. We must only give up after having checked to see if it has an - // appropriate initializer callback. - if (auto callback = GetInitializerCallback(&dlib)) { - callback(exports, module, context); - return; - } - char errmsg[1024]; - snprintf(errmsg, - sizeof(errmsg), - "The module '%s'" - "\nwas compiled against a different Node.js version using" - "\nNODE_MODULE_VERSION %d. This version of Node.js requires" - "\nNODE_MODULE_VERSION %d. Please try re-compiling or " - "re-installing\nthe module (for instance, using `npm rebuild` " - "or `npm install`).", - *filename, mp->nm_version, NODE_MODULE_VERSION); - - // NOTE: `mp` is allocated inside of the shared library's memory, calling - // `dlclose` will deallocate it - dlib.Close(); - env->ThrowError(errmsg); - return; - } - if (mp->nm_flags & NM_F_BUILTIN) { - dlib.Close(); - env->ThrowError("Built-in module self-registered."); - return; - } - - mp->nm_dso_handle = dlib.handle_; - mp->nm_link = modlist_addon; - modlist_addon = mp; - - if (mp->nm_context_register_func != nullptr) { - mp->nm_context_register_func(exports, module, context, mp->nm_priv); - } else if (mp->nm_register_func != nullptr) { - mp->nm_register_func(exports, module, mp->nm_priv); - } else { - dlib.Close(); - env->ThrowError("Module has no declared entry point."); - return; - } - - // Tell coverity that 'handle' should not be freed when we return. - // coverity[leaked_storage] -} - static void OnFatalError(const char* location, const char* message) { if (location) { @@ -2332,301 +1420,18 @@ static void GetLinkedBinding(const FunctionCallbackInfo& args) { args.GetReturnValue().Set(effective_exports); } -static void ProcessTitleGetter(Local property, - const PropertyCallbackInfo& info) { - char buffer[512]; - uv_get_process_title(buffer, sizeof(buffer)); - info.GetReturnValue().Set(String::NewFromUtf8(info.GetIsolate(), buffer)); -} - - -static void ProcessTitleSetter(Local property, - Local value, - const PropertyCallbackInfo& info) { - node::Utf8Value title(info.GetIsolate(), value); - uv_set_process_title(*title); -} - - -static void EnvGetter(Local property, - const PropertyCallbackInfo& info) { - Isolate* isolate = info.GetIsolate(); - if (property->IsSymbol()) { - return info.GetReturnValue().SetUndefined(); - } - Mutex::ScopedLock lock(environ_mutex); -#ifdef __POSIX__ - node::Utf8Value key(isolate, property); - const char* val = getenv(*key); - if (val) { - return info.GetReturnValue().Set(String::NewFromUtf8(isolate, val)); - } -#else // _WIN32 - node::TwoByteValue key(isolate, property); - WCHAR buffer[32767]; // The maximum size allowed for environment variables. - SetLastError(ERROR_SUCCESS); - DWORD result = GetEnvironmentVariableW(reinterpret_cast(*key), - buffer, - arraysize(buffer)); - // If result >= sizeof buffer the buffer was too small. That should never - // happen. If result == 0 and result != ERROR_SUCCESS the variable was not - // not found. - if ((result > 0 || GetLastError() == ERROR_SUCCESS) && - result < arraysize(buffer)) { - const uint16_t* two_byte_buffer = reinterpret_cast(buffer); - Local rc = String::NewFromTwoByte(isolate, two_byte_buffer); - return info.GetReturnValue().Set(rc); - } -#endif -} - - -static void EnvSetter(Local property, - Local value, - const PropertyCallbackInfo& info) { - Environment* env = Environment::GetCurrent(info); - if (config_pending_deprecation && env->EmitProcessEnvWarning() && - !value->IsString() && !value->IsNumber() && !value->IsBoolean()) { - if (ProcessEmitDeprecationWarning( - env, - "Assigning any value other than a string, number, or boolean to a " - "process.env property is deprecated. Please make sure to convert the " - "value to a string before setting process.env with it.", - "DEP0104").IsNothing()) - return; - } - - Mutex::ScopedLock lock(environ_mutex); -#ifdef __POSIX__ - node::Utf8Value key(info.GetIsolate(), property); - node::Utf8Value val(info.GetIsolate(), value); - setenv(*key, *val, 1); -#else // _WIN32 - node::TwoByteValue key(info.GetIsolate(), property); - node::TwoByteValue val(info.GetIsolate(), value); - WCHAR* key_ptr = reinterpret_cast(*key); - // Environment variables that start with '=' are read-only. - if (key_ptr[0] != L'=') { - SetEnvironmentVariableW(key_ptr, reinterpret_cast(*val)); - } -#endif - // Whether it worked or not, always return value. - info.GetReturnValue().Set(value); -} - - -static void EnvQuery(Local property, - const PropertyCallbackInfo& info) { - Mutex::ScopedLock lock(environ_mutex); - int32_t rc = -1; // Not found unless proven otherwise. - if (property->IsString()) { -#ifdef __POSIX__ - node::Utf8Value key(info.GetIsolate(), property); - if (getenv(*key)) - rc = 0; -#else // _WIN32 - node::TwoByteValue key(info.GetIsolate(), property); - WCHAR* key_ptr = reinterpret_cast(*key); - SetLastError(ERROR_SUCCESS); - if (GetEnvironmentVariableW(key_ptr, nullptr, 0) > 0 || - GetLastError() == ERROR_SUCCESS) { - rc = 0; - if (key_ptr[0] == L'=') { - // Environment variables that start with '=' are hidden and read-only. - rc = static_cast(v8::ReadOnly) | - static_cast(v8::DontDelete) | - static_cast(v8::DontEnum); - } - } -#endif - } - if (rc != -1) - info.GetReturnValue().Set(rc); -} - - -static void EnvDeleter(Local property, - const PropertyCallbackInfo& info) { - Mutex::ScopedLock lock(environ_mutex); - if (property->IsString()) { -#ifdef __POSIX__ - node::Utf8Value key(info.GetIsolate(), property); - unsetenv(*key); -#else - node::TwoByteValue key(info.GetIsolate(), property); - WCHAR* key_ptr = reinterpret_cast(*key); - SetEnvironmentVariableW(key_ptr, nullptr); -#endif - } - - // process.env never has non-configurable properties, so always - // return true like the tc39 delete operator. - info.GetReturnValue().Set(true); -} - - -static void EnvEnumerator(const PropertyCallbackInfo& info) { - Environment* env = Environment::GetCurrent(info); - Isolate* isolate = env->isolate(); - Local ctx = env->context(); - Local fn = env->push_values_to_array_function(); - Local argv[NODE_PUSH_VAL_TO_ARRAY_MAX]; - size_t idx = 0; - - Mutex::ScopedLock lock(environ_mutex); -#ifdef __POSIX__ - int size = 0; - while (environ[size]) - size++; - - Local envarr = Array::New(isolate); - - for (int i = 0; i < size; ++i) { - const char* var = environ[i]; - const char* s = strchr(var, '='); - const int length = s ? s - var : strlen(var); - argv[idx] = String::NewFromUtf8(isolate, - var, - String::kNormalString, - length); - if (++idx >= arraysize(argv)) { - fn->Call(ctx, envarr, idx, argv).ToLocalChecked(); - idx = 0; - } - } - if (idx > 0) { - fn->Call(ctx, envarr, idx, argv).ToLocalChecked(); - } -#else // _WIN32 - WCHAR* environment = GetEnvironmentStringsW(); - if (environment == nullptr) - return; // This should not happen. - Local envarr = Array::New(isolate); - WCHAR* p = environment; - while (*p) { - WCHAR *s; - if (*p == L'=') { - // If the key starts with '=' it is a hidden environment variable. - p += wcslen(p) + 1; - continue; - } else { - s = wcschr(p, L'='); - } - if (!s) { - s = p + wcslen(p); - } - const uint16_t* two_byte_buffer = reinterpret_cast(p); - const size_t two_byte_buffer_len = s - p; - argv[idx] = String::NewFromTwoByte(isolate, - two_byte_buffer, - String::kNormalString, - two_byte_buffer_len); - if (++idx >= arraysize(argv)) { - fn->Call(ctx, envarr, idx, argv).ToLocalChecked(); - idx = 0; - } - p = s + wcslen(s) + 1; - } - if (idx > 0) { - fn->Call(ctx, envarr, idx, argv).ToLocalChecked(); - } - FreeEnvironmentStringsW(environment); -#endif - - info.GetReturnValue().Set(envarr); -} - - -static void GetParentProcessId(Local property, - const PropertyCallbackInfo& info) { - info.GetReturnValue().Set(Integer::New(info.GetIsolate(), uv_os_getppid())); -} - - -static Local GetFeatures(Environment* env) { - EscapableHandleScope scope(env->isolate()); - - Local obj = Object::New(env->isolate()); -#if defined(DEBUG) && DEBUG - Local debug = True(env->isolate()); -#else - Local debug = False(env->isolate()); -#endif // defined(DEBUG) && DEBUG - - obj->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "debug"), debug); - obj->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "uv"), True(env->isolate())); - // TODO(bnoordhuis) ping libuv - obj->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "ipv6"), True(env->isolate())); - -#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation - Local tls_alpn = True(env->isolate()); -#else - Local tls_alpn = False(env->isolate()); -#endif - obj->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "tls_alpn"), tls_alpn); - -#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB - Local tls_sni = True(env->isolate()); -#else - Local tls_sni = False(env->isolate()); -#endif - obj->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "tls_sni"), tls_sni); - -#if !defined(OPENSSL_NO_TLSEXT) && defined(SSL_CTX_set_tlsext_status_cb) - Local tls_ocsp = True(env->isolate()); -#else - Local tls_ocsp = False(env->isolate()); -#endif // !defined(OPENSSL_NO_TLSEXT) && defined(SSL_CTX_set_tlsext_status_cb) - obj->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "tls_ocsp"), tls_ocsp); - - obj->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "tls"), - Boolean::New(env->isolate(), - get_builtin_module("crypto") != nullptr)); - - return scope.Escape(obj); -} - - -static void DebugPortGetter(Local property, - const PropertyCallbackInfo& info) { - Mutex::ScopedLock lock(process_mutex); - int port = debug_options.port(); -#if HAVE_INSPECTOR - if (port == 0) { - Environment* env = Environment::GetCurrent(info); - if (auto io = env->inspector_agent()->io()) - port = io->port(); - } -#endif // HAVE_INSPECTOR - info.GetReturnValue().Set(port); -} - - -static void DebugPortSetter(Local property, - Local value, - const PropertyCallbackInfo& info) { - Mutex::ScopedLock lock(process_mutex); - debug_options.set_port(value->Int32Value()); -} - - static void DebugProcess(const FunctionCallbackInfo& args); static void DebugEnd(const FunctionCallbackInfo& args); namespace { -void StartProfilerIdleNotifier(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - env->StartProfilerIdleNotifier(); -} - - -void StopProfilerIdleNotifier(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - env->StopProfilerIdleNotifier(); +bool ShouldAbortOnUncaughtException(Isolate* isolate) { + HandleScope scope(isolate); + Environment* env = Environment::GetCurrent(isolate); + return env->should_abort_on_uncaught_toggle()[0] && + !env->inside_should_not_abort_on_uncaught_scope(); } - #define READONLY_PROPERTY(obj, str, var) \ do { \ obj->DefineOwnProperty(env->context(), \ @@ -2825,13 +1630,6 @@ void SetupProcessObject(Environment* env, FIXED_ONE_BYTE_STRING(env->isolate(), "ppid"), GetParentProcessId).FromJust()); - auto should_abort_on_uncaught_toggle = - FIXED_ONE_BYTE_STRING(env->isolate(), "_shouldAbortOnUncaughtToggle"); - CHECK(process->Set(env->context(), - should_abort_on_uncaught_toggle, - env->should_abort_on_uncaught_toggle().GetJSArray()) - .FromJust()); - // -e, --eval if (eval_string) { READONLY_PROPERTY(process, @@ -2966,25 +1764,14 @@ void SetupProcessObject(Environment* env, env->SetMethod(process, "_getActiveHandles", GetActiveHandles); env->SetMethod(process, "reallyExit", Exit); env->SetMethod(process, "abort", Abort); - env->SetMethod(process, "chdir", Chdir); env->SetMethod(process, "cwd", Cwd); - env->SetMethod(process, "umask", Umask); - #if defined(__POSIX__) && !defined(__ANDROID__) && !defined(__CloudABI__) env->SetMethod(process, "getuid", GetUid); env->SetMethod(process, "geteuid", GetEUid); - env->SetMethod(process, "setuid", SetUid); - env->SetMethod(process, "seteuid", SetEUid); - - env->SetMethod(process, "setgid", SetGid); - env->SetMethod(process, "setegid", SetEGid); env->SetMethod(process, "getgid", GetGid); env->SetMethod(process, "getegid", GetEGid); - env->SetMethod(process, "getgroups", GetGroups); - env->SetMethod(process, "setgroups", SetGroups); - env->SetMethod(process, "initgroups", InitGroups); #endif // __POSIX__ && !defined(__ANDROID__) && !defined(__CloudABI__) env->SetMethod(process, "_kill", Kill); @@ -2992,18 +1779,8 @@ void SetupProcessObject(Environment* env, env->SetMethod(process, "_debugProcess", DebugProcess); env->SetMethod(process, "_debugEnd", DebugEnd); - env->SetMethod(process, "hrtime", Hrtime); - - env->SetMethod(process, "cpuUsage", CPUUsage); - env->SetMethod(process, "dlopen", DLOpen); - env->SetMethod(process, "uptime", Uptime); - env->SetMethod(process, "memoryUsage", MemoryUsage); - - env->SetMethod(process, "_setupProcessObject", SetupProcessObject); - env->SetMethod(process, "_setupNextTick", SetupNextTick); - env->SetMethod(process, "_setupPromises", SetupPromises); } @@ -3024,19 +1801,6 @@ void SignalExit(int signo) { } -// Most of the time, it's best to use `console.error` to write -// to the process.stderr stream. However, in some cases, such as -// when debugging the stream.Writable class or the process.nextTick -// function, it is useful to bypass JavaScript entirely. -static void RawDebug(const FunctionCallbackInfo& args) { - CHECK(args.Length() == 1 && args[0]->IsString() && - "must be called with a single string"); - node::Utf8Value message(args.GetIsolate(), args[0]); - PrintErrorString("%s\n", *message); - fflush(stderr); -} - - static Local GetBootstrapper(Environment* env, Local source, Local script_name) { EscapableHandleScope scope(env->isolate()); @@ -3123,8 +1887,6 @@ void LoadEnvironment(Environment* env) { // thrown during process startup. try_catch.SetVerbose(true); - env->SetMethod(env->process_object(), "_rawDebug", RawDebug); - // Expose the global object as a property on itself // (Allows you to set stuff on `global` from anywhere in JavaScript.) global->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "global"), global); @@ -3159,9 +1921,12 @@ void LoadEnvironment(Environment* env) { } // Bootstrap Node.js + Local bootstrapper = Object::New(env->isolate()); + SetupBootstrapObject(env, bootstrapper); Local bootstrapped_node; Local node_bootstrapper_args[] = { env->process_object(), + bootstrapper, bootstrapped_loaders }; if (!ExecuteBootstrapper(env, node_bootstrapper, diff --git a/src/node_bootstrap.cc b/src/node_bootstrap.cc new file mode 100644 index 00000000000000..7718c8af734731 --- /dev/null +++ b/src/node_bootstrap.cc @@ -0,0 +1,128 @@ +#include "node.h" +#include "node_internals.h" +#include "node_process.h" +#include "env.h" +#include "env-inl.h" +#include "v8.h" + +namespace node { + +using v8::Array; +using v8::Function; +using v8::FunctionCallbackInfo; +using v8::Isolate; +using v8::Local; +using v8::MaybeLocal; +using v8::Object; +using v8::Promise; +using v8::PromiseRejectMessage; +using v8::Undefined; +using v8::Value; + +void SetupProcessObject(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + CHECK(args[0]->IsFunction()); + env->set_push_values_to_array_function(args[0].As()); +} + +void RunMicrotasks(const FunctionCallbackInfo& args) { + args.GetIsolate()->RunMicrotasks(); +} + +void SetupNextTick(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + Isolate* isolate = env->isolate(); + CHECK(args[0]->IsFunction()); + env->set_tick_callback_function(args[0].As()); + + Local run_microtasks_fn = + env->NewFunctionTemplate(RunMicrotasks)->GetFunction(env->context()) + .ToLocalChecked(); + run_microtasks_fn->SetName(FIXED_ONE_BYTE_STRING(isolate, "runMicrotasks")); + Local ret = Array::New(isolate, 2); + ret->Set(env->context(), 0, + env->tick_info()->fields().GetJSArray()).FromJust(); + ret->Set(env->context(), 1, run_microtasks_fn).FromJust(); + args.GetReturnValue().Set(ret); +} + +void PromiseRejectCallback(PromiseRejectMessage message) { + Local promise = message.GetPromise(); + Isolate* isolate = promise->GetIsolate(); + v8::PromiseRejectEvent event = message.GetEvent(); + + Environment* env = Environment::GetCurrent(isolate); + Local callback; + Local value; + + if (event == v8::kPromiseRejectWithNoHandler) { + callback = env->promise_reject_unhandled_function(); + value = message.GetValue(); + + if (value.IsEmpty()) + value = Undefined(isolate); + } else if (event == v8::kPromiseHandlerAddedAfterReject) { + callback = env->promise_reject_handled_function(); + value = Undefined(isolate); + } else { + UNREACHABLE(); + } + + Local args[] = { promise, value }; + MaybeLocal ret = callback->Call(env->context(), + Undefined(isolate), + arraysize(args), + args); + + if (!ret.IsEmpty() && ret.ToLocalChecked()->IsTrue()) + env->tick_info()->promise_rejections_toggle_on(); +} + +void SetupPromises(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + Isolate* isolate = env->isolate(); + + CHECK(args[0]->IsFunction()); + CHECK(args[1]->IsFunction()); + + isolate->SetPromiseRejectCallback(PromiseRejectCallback); + env->set_promise_reject_unhandled_function(args[0].As()); + env->set_promise_reject_handled_function(args[1].As()); +} + +// The bootstrap object is used only while bootstrapping the Node.js +// process then it is allowed to be gc'd. No permanent references to +// the bootstrap object should be held after bootstrap. +void SetupBootstrapObject(Environment* env, Local bootstrap) { +#define BOOTSTRAP_METHOD(name, fn) env->SetMethod(bootstrap, #name, fn) + + BOOTSTRAP_METHOD(_setupProcessObject, SetupProcessObject); + BOOTSTRAP_METHOD(_setupNextTick, SetupNextTick); + BOOTSTRAP_METHOD(_setupPromises, SetupPromises); + BOOTSTRAP_METHOD(_chdir, Chdir); + BOOTSTRAP_METHOD(_cpuUsage, CPUUsage); + BOOTSTRAP_METHOD(_hrtime, Hrtime); + BOOTSTRAP_METHOD(_memoryUsage, MemoryUsage); + BOOTSTRAP_METHOD(_rawDebug, RawDebug); + BOOTSTRAP_METHOD(_umask, Umask); + +#if defined(__POSIX__) && !defined(__ANDROID__) && !defined(__CloudABI__) + BOOTSTRAP_METHOD(_initgroups, InitGroups); + BOOTSTRAP_METHOD(_setegid, SetEGid); + BOOTSTRAP_METHOD(_seteuid, SetEUid); + BOOTSTRAP_METHOD(_setgid, SetGid); + BOOTSTRAP_METHOD(_setuid, SetUid); + BOOTSTRAP_METHOD(_setgroups, SetGroups); +#endif // __POSIX__ && !defined(__ANDROID__) && !defined(__CloudABI__) + + auto should_abort_on_uncaught_toggle = + FIXED_ONE_BYTE_STRING(env->isolate(), "_shouldAbortOnUncaughtToggle"); + CHECK(bootstrap->Set(env->context(), + should_abort_on_uncaught_toggle, + env->should_abort_on_uncaught_toggle().GetJSArray()) + .FromJust()); + +#undef BOOTSTRAP_METHOD +} + +} // namespace node diff --git a/src/node_internals.h b/src/node_internals.h index 1ba56458b194c2..b21cc2c79457f8 100644 --- a/src/node_internals.h +++ b/src/node_internals.h @@ -36,6 +36,8 @@ #include "node_debug_options.h" #include "node_api.h" +#include +#include #include #include @@ -164,6 +166,16 @@ struct sockaddr; namespace node { +extern Mutex process_mutex; +extern Mutex environ_mutex; + +extern double prog_start_time; +extern node_module* modpending; +extern node_module* modlist_builtin; +extern node_module* modlist_internal; +extern node_module* modlist_linked; +extern node_module* modlist_addon; + // Set in node.cc by ParseArgs with the value of --openssl-config. // Used in node_crypto.cc when initializing OpenSSL. extern std::string openssl_config; @@ -357,6 +369,8 @@ inline v8::Local FillGlobalStatsArray(Environment* env, return node::FillStatsArray(env->fs_stats_field_array(), s, offset); } +void SetupBootstrapObject(Environment* env, + v8::Local bootstrap); void SetupProcessObject(Environment* env, int argc, const char* const* argv, @@ -861,6 +875,45 @@ static inline const char *errno_string(int errorno) { } } +static inline void PrintErrorString(const char* format, ...) { + va_list ap; + va_start(ap, format); +#ifdef _WIN32 + HANDLE stderr_handle = GetStdHandle(STD_ERROR_HANDLE); + + // Check if stderr is something other than a tty/console + if (stderr_handle == INVALID_HANDLE_VALUE || + stderr_handle == nullptr || + uv_guess_handle(_fileno(stderr)) != UV_TTY) { + vfprintf(stderr, format, ap); + va_end(ap); + return; + } + + // Fill in any placeholders + int n = _vscprintf(format, ap); + std::vector out(n + 1); + vsprintf(out.data(), format, ap); + + // Get required wide buffer size + n = MultiByteToWideChar(CP_UTF8, 0, out.data(), -1, nullptr, 0); + + std::vector wbuf(n); + MultiByteToWideChar(CP_UTF8, 0, out.data(), -1, wbuf.data(), n); + + // Don't include the null character in the output + CHECK_GT(n, 0); + WriteConsoleW(stderr_handle, wbuf.data(), n - 1, nullptr, nullptr); +#else + vfprintf(stderr, format, ap); +#endif + va_end(ap); +} + +node_module* get_builtin_module(const char* name); +node_module* get_internal_module(const char* name); +node_module* get_linked_module(const char* name); + #define NODE_MODULE_CONTEXT_AWARE_INTERNAL(modname, regfunc) \ NODE_MODULE_CONTEXT_AWARE_CPP(modname, regfunc, nullptr, NM_F_INTERNAL) diff --git a/src/node_process.cc b/src/node_process.cc new file mode 100644 index 00000000000000..6660d3de16a01c --- /dev/null +++ b/src/node_process.cc @@ -0,0 +1,1108 @@ +#include "node.h" +#include "node_internals.h" +#include "base_object.h" +#include "base_object-inl.h" +#include "env.h" +#include "env-inl.h" +#include "util.h" +#include "util-inl.h" +#include "v8.h" +#include "uv.h" + +#if HAVE_OPENSSL +#include "node_crypto.h" +#endif + +#if HAVE_INSPECTOR +#include "inspector_io.h" +#endif + +#include + +#if defined(_MSC_VER) +#include +#include +#define umask _umask +typedef int mode_t; +#else +#include +#include // getrlimit, setrlimit +#include // setuid, getuid +#endif + +#if defined(__POSIX__) && !defined(__ANDROID__) && !defined(__CloudABI__) +#include // getpwnam() +#include // getgrnam() +#endif + +#if defined(__POSIX__) +#include +#endif + + +namespace node { + +// used in Hrtime() +#define NANOS_PER_SEC 1000000000 +// Microseconds in a second, as a float, used in CPUUsage() +#define MICROS_PER_SEC 1e6 + + +using v8::Array; +using v8::ArrayBuffer; +using v8::Boolean; +using v8::Context; +using v8::EscapableHandleScope; +using v8::Exception; +using v8::Float64Array; +using v8::Function; +using v8::FunctionCallbackInfo; +using v8::HeapStatistics; +using v8::Integer; +using v8::Isolate; +using v8::Local; +using v8::Name; +using v8::Number; +using v8::Object; +using v8::PropertyCallbackInfo; +using v8::String; +using v8::Uint32; +using v8::Uint32Array; +using v8::Value; + +void Chdir(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + CHECK_EQ(args.Length(), 1); + CHECK(args[0]->IsString()); + node::Utf8Value path(args.GetIsolate(), args[0]); + int err = uv_chdir(*path); + if (err) { + return env->ThrowUVException(err, "chdir", nullptr, *path, nullptr); + } +} + + +void Cwd(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); +#ifdef _WIN32 + /* MAX_PATH is in characters, not bytes. Make sure we have enough headroom. */ + char buf[MAX_PATH * 4]; +#else + char buf[PATH_MAX]; +#endif + + size_t cwd_len = sizeof(buf); + int err = uv_cwd(buf, &cwd_len); + if (err) { + return env->ThrowUVException(err, "uv_cwd"); + } + + Local cwd = String::NewFromUtf8(env->isolate(), + buf, + String::kNormalString, + cwd_len); + args.GetReturnValue().Set(cwd); +} + +void Umask(const FunctionCallbackInfo& args) { + uint32_t old; + + CHECK_EQ(args.Length(), 1); + CHECK(args[0]->IsUndefined() || args[0]->IsUint32()); + + if (args[0]->IsUndefined()) { + old = umask(0); + umask(static_cast(old)); + } else { + int oct = args[0].As()->Value(); + old = umask(static_cast(oct)); + } + + args.GetReturnValue().Set(old); +} + +#if defined(__POSIX__) && !defined(__ANDROID__) && !defined(__CloudABI__) + +static const uid_t uid_not_found = static_cast(-1); +static const gid_t gid_not_found = static_cast(-1); + + +static uid_t uid_by_name(const char* name) { + struct passwd pwd; + struct passwd* pp; + char buf[8192]; + + errno = 0; + pp = nullptr; + + if (getpwnam_r(name, &pwd, buf, sizeof(buf), &pp) == 0 && pp != nullptr) { + return pp->pw_uid; + } + + return uid_not_found; +} + + +static char* name_by_uid(uid_t uid) { + struct passwd pwd; + struct passwd* pp; + char buf[8192]; + int rc; + + errno = 0; + pp = nullptr; + + if ((rc = getpwuid_r(uid, &pwd, buf, sizeof(buf), &pp)) == 0 && + pp != nullptr) { + return strdup(pp->pw_name); + } + + if (rc == 0) { + errno = ENOENT; + } + + return nullptr; +} + + +static gid_t gid_by_name(const char* name) { + struct group pwd; + struct group* pp; + char buf[8192]; + + errno = 0; + pp = nullptr; + + if (getgrnam_r(name, &pwd, buf, sizeof(buf), &pp) == 0 && pp != nullptr) { + return pp->gr_gid; + } + + return gid_not_found; +} + + +#if 0 // For future use. +static const char* name_by_gid(gid_t gid) { + struct group pwd; + struct group* pp; + char buf[8192]; + int rc; + + errno = 0; + pp = nullptr; + + if ((rc = getgrgid_r(gid, &pwd, buf, sizeof(buf), &pp)) == 0 && + pp != nullptr) { + return strdup(pp->gr_name); + } + + if (rc == 0) { + errno = ENOENT; + } + + return nullptr; +} +#endif + + +static uid_t uid_by_name(Isolate* isolate, Local value) { + if (value->IsUint32()) { + return static_cast(value->Uint32Value()); + } else { + node::Utf8Value name(isolate, value); + return uid_by_name(*name); + } +} + + +static gid_t gid_by_name(Isolate* isolate, Local value) { + if (value->IsUint32()) { + return static_cast(value->Uint32Value()); + } else { + node::Utf8Value name(isolate, value); + return gid_by_name(*name); + } +} + +void GetUid(const FunctionCallbackInfo& args) { + // uid_t is an uint32_t on all supported platforms. + args.GetReturnValue().Set(static_cast(getuid())); +} + + +void GetGid(const FunctionCallbackInfo& args) { + // gid_t is an uint32_t on all supported platforms. + args.GetReturnValue().Set(static_cast(getgid())); +} + + +void GetEUid(const FunctionCallbackInfo& args) { + // uid_t is an uint32_t on all supported platforms. + args.GetReturnValue().Set(static_cast(geteuid())); +} + + +void GetEGid(const FunctionCallbackInfo& args) { + // gid_t is an uint32_t on all supported platforms. + args.GetReturnValue().Set(static_cast(getegid())); +} + + +void SetGid(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + CHECK_EQ(args.Length(), 1); + CHECK(args[0]->IsUint32() || args[0]->IsString()); + + gid_t gid = gid_by_name(env->isolate(), args[0]); + + if (gid == gid_not_found) { + // Tells JS to throw ERR_INVALID_CREDENTIAL + args.GetReturnValue().Set(1); + } else if (setgid(gid)) { + env->ThrowErrnoException(errno, "setgid"); + } else { + args.GetReturnValue().Set(0); + } +} + + +void SetEGid(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + CHECK_EQ(args.Length(), 1); + CHECK(args[0]->IsUint32() || args[0]->IsString()); + + gid_t gid = gid_by_name(env->isolate(), args[0]); + + if (gid == gid_not_found) { + // Tells JS to throw ERR_INVALID_CREDENTIAL + args.GetReturnValue().Set(1); + } else if (setegid(gid)) { + env->ThrowErrnoException(errno, "setegid"); + } else { + args.GetReturnValue().Set(0); + } +} + + +void SetUid(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + CHECK_EQ(args.Length(), 1); + CHECK(args[0]->IsUint32() || args[0]->IsString()); + + uid_t uid = uid_by_name(env->isolate(), args[0]); + + if (uid == uid_not_found) { + // Tells JS to throw ERR_INVALID_CREDENTIAL + args.GetReturnValue().Set(1); + } else if (setuid(uid)) { + env->ThrowErrnoException(errno, "setuid"); + } else { + args.GetReturnValue().Set(0); + } +} + + +void SetEUid(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + CHECK_EQ(args.Length(), 1); + CHECK(args[0]->IsUint32() || args[0]->IsString()); + + uid_t uid = uid_by_name(env->isolate(), args[0]); + + if (uid == uid_not_found) { + // Tells JS to throw ERR_INVALID_CREDENTIAL + args.GetReturnValue().Set(1); + } else if (seteuid(uid)) { + env->ThrowErrnoException(errno, "seteuid"); + } else { + args.GetReturnValue().Set(0); + } +} + + +void GetGroups(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + int ngroups = getgroups(0, nullptr); + + if (ngroups == -1) { + return env->ThrowErrnoException(errno, "getgroups"); + } + + gid_t* groups = new gid_t[ngroups]; + + ngroups = getgroups(ngroups, groups); + + if (ngroups == -1) { + delete[] groups; + return env->ThrowErrnoException(errno, "getgroups"); + } + + Local groups_list = Array::New(env->isolate(), ngroups); + bool seen_egid = false; + gid_t egid = getegid(); + + for (int i = 0; i < ngroups; i++) { + groups_list->Set(i, Integer::New(env->isolate(), groups[i])); + if (groups[i] == egid) + seen_egid = true; + } + + delete[] groups; + + if (seen_egid == false) { + groups_list->Set(ngroups, Integer::New(env->isolate(), egid)); + } + + args.GetReturnValue().Set(groups_list); +} + + +void SetGroups(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + CHECK_EQ(args.Length(), 1); + CHECK(args[0]->IsArray()); + + Local groups_list = args[0].As(); + size_t size = groups_list->Length(); + gid_t* groups = new gid_t[size]; + + for (size_t i = 0; i < size; i++) { + gid_t gid = gid_by_name(env->isolate(), groups_list->Get(i)); + + if (gid == gid_not_found) { + delete[] groups; + // Tells JS to throw ERR_INVALID_CREDENTIAL + args.GetReturnValue().Set(static_cast(i + 1)); + return; + } + + groups[i] = gid; + } + + int rc = setgroups(size, groups); + delete[] groups; + + if (rc == -1) { + return env->ThrowErrnoException(errno, "setgroups"); + } + + args.GetReturnValue().Set(0); +} + + +void InitGroups(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + CHECK_EQ(args.Length(), 2); + CHECK(args[0]->IsUint32() || args[0]->IsString()); + CHECK(args[1]->IsUint32() || args[1]->IsString()); + + node::Utf8Value arg0(env->isolate(), args[0]); + gid_t extra_group; + bool must_free; + char* user; + + if (args[0]->IsUint32()) { + user = name_by_uid(args[0]->Uint32Value()); + must_free = true; + } else { + user = *arg0; + must_free = false; + } + + if (user == nullptr) { + // Tells JS to throw ERR_INVALID_CREDENTIAL + return args.GetReturnValue().Set(1); + } + + extra_group = gid_by_name(env->isolate(), args[1]); + + if (extra_group == gid_not_found) { + if (must_free) + free(user); + // Tells JS to throw ERR_INVALID_CREDENTIAL + return args.GetReturnValue().Set(2); + } + + int rc = initgroups(user, extra_group); + + if (must_free) { + free(user); + } + + if (rc) { + return env->ThrowErrnoException(errno, "initgroups"); + } + + args.GetReturnValue().Set(0); +} + +#endif // __POSIX__ && !defined(__ANDROID__) && !defined(__CloudABI__) + +// Hrtime exposes libuv's uv_hrtime() high-resolution timer. +// The value returned by uv_hrtime() is a 64-bit int representing nanoseconds, +// so this function instead fills in an Uint32Array with 3 entries, +// to avoid any integer overflow possibility. +// The first two entries contain the second part of the value +// broken into the upper/lower 32 bits to be converted back in JS, +// because there is no Uint64Array in JS. +// The third entry contains the remaining nanosecond part of the value. +void Hrtime(const FunctionCallbackInfo& args) { + uint64_t t = uv_hrtime(); + + Local ab = args[0].As()->Buffer(); + uint32_t* fields = static_cast(ab->GetContents().Data()); + + fields[0] = (t / NANOS_PER_SEC) >> 32; + fields[1] = (t / NANOS_PER_SEC) & 0xffffffff; + fields[2] = t % NANOS_PER_SEC; +} + + +// CPUUsage use libuv's uv_getrusage() this-process resource usage accessor, +// to access ru_utime (user CPU time used) and ru_stime (system CPU time used), +// which are uv_timeval_t structs (long tv_sec, long tv_usec). +// Returns those values as Float64 microseconds in the elements of the array +// passed to the function. +void CPUUsage(const FunctionCallbackInfo& args) { + uv_rusage_t rusage; + + // Call libuv to get the values we'll return. + int err = uv_getrusage(&rusage); + if (err) { + // On error, return the strerror version of the error code. + Local errmsg = OneByteString(args.GetIsolate(), uv_strerror(err)); + args.GetReturnValue().Set(errmsg); + return; + } + + // Get the double array pointer from the Float64Array argument. + CHECK(args[0]->IsFloat64Array()); + Local array = args[0].As(); + CHECK_EQ(array->Length(), 2); + Local ab = array->Buffer(); + double* fields = static_cast(ab->GetContents().Data()); + + // Set the Float64Array elements to be user / system values in microseconds. + fields[0] = MICROS_PER_SEC * rusage.ru_utime.tv_sec + rusage.ru_utime.tv_usec; + fields[1] = MICROS_PER_SEC * rusage.ru_stime.tv_sec + rusage.ru_stime.tv_usec; +} + +void MemoryUsage(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + size_t rss; + int err = uv_resident_set_memory(&rss); + if (err) { + return env->ThrowUVException(err, "uv_resident_set_memory"); + } + + Isolate* isolate = env->isolate(); + // V8 memory usage + HeapStatistics v8_heap_stats; + isolate->GetHeapStatistics(&v8_heap_stats); + + // Get the double array pointer from the Float64Array argument. + CHECK(args[0]->IsFloat64Array()); + Local array = args[0].As(); + CHECK_EQ(array->Length(), 4); + Local ab = array->Buffer(); + double* fields = static_cast(ab->GetContents().Data()); + + fields[0] = rss; + fields[1] = v8_heap_stats.total_heap_size(); + fields[2] = v8_heap_stats.used_heap_size(); + fields[3] = isolate->AdjustAmountOfExternalAllocatedMemory(0); +} + +// Most of the time, it's best to use `console.error` to write +// to the process.stderr stream. However, in some cases, such as +// when debugging the stream.Writable class or the process.nextTick +// function, it is useful to bypass JavaScript entirely. +void RawDebug(const FunctionCallbackInfo& args) { + CHECK(args.Length() == 1 && args[0]->IsString() && + "must be called with a single string"); + node::Utf8Value message(args.GetIsolate(), args[0]); + PrintErrorString("%s\n", *message); + fflush(stderr); +} + +void Uptime(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + double uptime; + + uv_update_time(env->event_loop()); + uptime = uv_now(env->event_loop()) - prog_start_time; + + args.GetReturnValue().Set(Number::New(env->isolate(), uptime / 1000)); +} + +void StartProfilerIdleNotifier(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + env->StartProfilerIdleNotifier(); +} + +void StopProfilerIdleNotifier(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + env->StopProfilerIdleNotifier(); +} + +void GetActiveRequests(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + Local ary = Array::New(args.GetIsolate()); + Local ctx = env->context(); + Local fn = env->push_values_to_array_function(); + Local argv[NODE_PUSH_VAL_TO_ARRAY_MAX]; + size_t idx = 0; + + for (auto w : *env->req_wrap_queue()) { + if (w->persistent().IsEmpty()) + continue; + argv[idx] = w->object(); + if (++idx >= arraysize(argv)) { + fn->Call(ctx, ary, idx, argv).ToLocalChecked(); + idx = 0; + } + } + + if (idx > 0) { + fn->Call(ctx, ary, idx, argv).ToLocalChecked(); + } + + args.GetReturnValue().Set(ary); +} + + +// Non-static, friend of HandleWrap. Could have been a HandleWrap method but +// implemented here for consistency with GetActiveRequests(). +void GetActiveHandles(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + Local ary = Array::New(env->isolate()); + Local ctx = env->context(); + Local fn = env->push_values_to_array_function(); + Local argv[NODE_PUSH_VAL_TO_ARRAY_MAX]; + size_t idx = 0; + + Local owner_sym = env->owner_string(); + + for (auto w : *env->handle_wrap_queue()) { + if (w->persistent().IsEmpty() || !HandleWrap::HasRef(w)) + continue; + Local object = w->object(); + Local owner = object->Get(owner_sym); + if (owner->IsUndefined()) + owner = object; + argv[idx] = owner; + if (++idx >= arraysize(argv)) { + fn->Call(ctx, ary, idx, argv).ToLocalChecked(); + idx = 0; + } + } + if (idx > 0) { + fn->Call(ctx, ary, idx, argv).ToLocalChecked(); + } + + args.GetReturnValue().Set(ary); +} + +void Kill(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + if (args.Length() != 2) { + return env->ThrowError("Bad argument."); + } + + int pid = args[0]->Int32Value(); + int sig = args[1]->Int32Value(); + int err = uv_kill(pid, sig); + args.GetReturnValue().Set(err); +} + +void Abort(const FunctionCallbackInfo& args) { + Abort(); +} + + +class DLib { + public: +#ifdef __POSIX__ + static const int kDefaultFlags = RTLD_LAZY; +#else + static const int kDefaultFlags = 0; +#endif + + inline DLib(const char* filename, int flags) + : filename_(filename), flags_(flags), handle_(nullptr) {} + + inline bool Open(); + inline void Close(); + inline void* GetSymbolAddress(const char* name); + + const std::string filename_; + const int flags_; + std::string errmsg_; + void* handle_; +#ifndef __POSIX__ + uv_lib_t lib_; +#endif + private: + DISALLOW_COPY_AND_ASSIGN(DLib); +}; + + +#ifdef __POSIX__ +bool DLib::Open() { + handle_ = dlopen(filename_.c_str(), flags_); + if (handle_ != nullptr) + return true; + errmsg_ = dlerror(); + return false; +} + +void DLib::Close() { + if (handle_ == nullptr) return; + dlclose(handle_); + handle_ = nullptr; +} + +void* DLib::GetSymbolAddress(const char* name) { + return dlsym(handle_, name); +} +#else // !__POSIX__ +bool DLib::Open() { + int ret = uv_dlopen(filename_.c_str(), &lib_); + if (ret == 0) { + handle_ = static_cast(lib_.handle); + return true; + } + errmsg_ = uv_dlerror(&lib_); + uv_dlclose(&lib_); + return false; +} + +void DLib::Close() { + if (handle_ == nullptr) return; + uv_dlclose(&lib_); + handle_ = nullptr; +} + +void* DLib::GetSymbolAddress(const char* name) { + void* address; + if (0 == uv_dlsym(&lib_, name, &address)) return address; + return nullptr; +} +#endif // !__POSIX__ + +using InitializerCallback = void (*)(Local exports, + Local module, + Local context); + +inline InitializerCallback GetInitializerCallback(DLib* dlib) { + const char* name = "node_register_module_v" STRINGIFY(NODE_MODULE_VERSION); + return reinterpret_cast(dlib->GetSymbolAddress(name)); +} + +inline napi_addon_register_func GetNapiInitializerCallback(DLib* dlib) { + const char* name = + STRINGIFY(NAPI_MODULE_INITIALIZER_BASE) STRINGIFY(NAPI_MODULE_VERSION); + return + reinterpret_cast(dlib->GetSymbolAddress(name)); +} + +// DLOpen is process.dlopen(module, filename, flags). +// Used to load 'module.node' dynamically shared objects. +// +// FIXME(bnoordhuis) Not multi-context ready. TBD how to resolve the conflict +// when two contexts try to load the same shared object. Maybe have a shadow +// cache that's a plain C list or hash table that's shared across contexts? +void DLOpen(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + auto context = env->context(); + + CHECK_EQ(modpending, nullptr); + + if (args.Length() < 2) { + env->ThrowError("process.dlopen needs at least 2 arguments."); + return; + } + + int32_t flags = DLib::kDefaultFlags; + if (args.Length() > 2 && !args[2]->Int32Value(context).To(&flags)) { + return env->ThrowTypeError("flag argument must be an integer."); + } + + Local module; + Local exports; + Local exports_v; + if (!args[0]->ToObject(context).ToLocal(&module) || + !module->Get(context, env->exports_string()).ToLocal(&exports_v) || + !exports_v->ToObject(context).ToLocal(&exports)) { + return; // Exception pending. + } + + node::Utf8Value filename(env->isolate(), args[1]); // Cast + DLib dlib(*filename, flags); + bool is_opened = dlib.Open(); + + // Objects containing v14 or later modules will have registered themselves + // on the pending list. Activate all of them now. At present, only one + // module per object is supported. + node_module* const mp = modpending; + modpending = nullptr; + + if (!is_opened) { + Local errmsg = OneByteString(env->isolate(), dlib.errmsg_.c_str()); + dlib.Close(); +#ifdef _WIN32 + // Windows needs to add the filename into the error message + errmsg = String::Concat(errmsg, + args[1]->ToString(context).ToLocalChecked()); +#endif // _WIN32 + env->isolate()->ThrowException(Exception::Error(errmsg)); + return; + } + + if (mp == nullptr) { + if (auto callback = GetInitializerCallback(&dlib)) { + callback(exports, module, context); + } else if (auto napi_callback = GetNapiInitializerCallback(&dlib)) { + napi_module_register_by_symbol(exports, module, context, napi_callback); + } else { + dlib.Close(); + env->ThrowError("Module did not self-register."); + } + return; + } + + // -1 is used for N-API modules + if ((mp->nm_version != -1) && (mp->nm_version != NODE_MODULE_VERSION)) { + // Even if the module did self-register, it may have done so with the wrong + // version. We must only give up after having checked to see if it has an + // appropriate initializer callback. + if (auto callback = GetInitializerCallback(&dlib)) { + callback(exports, module, context); + return; + } + char errmsg[1024]; + snprintf(errmsg, + sizeof(errmsg), + "The module '%s'" + "\nwas compiled against a different Node.js version using" + "\nNODE_MODULE_VERSION %d. This version of Node.js requires" + "\nNODE_MODULE_VERSION %d. Please try re-compiling or " + "re-installing\nthe module (for instance, using `npm rebuild` " + "or `npm install`).", + *filename, mp->nm_version, NODE_MODULE_VERSION); + + // NOTE: `mp` is allocated inside of the shared library's memory, calling + // `dlclose` will deallocate it + dlib.Close(); + env->ThrowError(errmsg); + return; + } + if (mp->nm_flags & NM_F_BUILTIN) { + dlib.Close(); + env->ThrowError("Built-in module self-registered."); + return; + } + + mp->nm_dso_handle = dlib.handle_; + mp->nm_link = modlist_addon; + modlist_addon = mp; + + if (mp->nm_context_register_func != nullptr) { + mp->nm_context_register_func(exports, module, context, mp->nm_priv); + } else if (mp->nm_register_func != nullptr) { + mp->nm_register_func(exports, module, mp->nm_priv); + } else { + dlib.Close(); + env->ThrowError("Module has no declared entry point."); + return; + } + + // Tell coverity that 'handle' should not be freed when we return. + // coverity[leaked_storage] +} + +void ProcessTitleGetter(Local property, + const PropertyCallbackInfo& info) { + char buffer[512]; + uv_get_process_title(buffer, sizeof(buffer)); + info.GetReturnValue().Set(String::NewFromUtf8(info.GetIsolate(), buffer)); +} + + +void ProcessTitleSetter(Local property, + Local value, + const PropertyCallbackInfo& info) { + node::Utf8Value title(info.GetIsolate(), value); + uv_set_process_title(*title); +} + +void EnvGetter(Local property, const PropertyCallbackInfo& info) { + Isolate* isolate = info.GetIsolate(); + if (property->IsSymbol()) { + return info.GetReturnValue().SetUndefined(); + } + Mutex::ScopedLock lock(environ_mutex); +#ifdef __POSIX__ + node::Utf8Value key(isolate, property); + const char* val = getenv(*key); + if (val) { + return info.GetReturnValue().Set(String::NewFromUtf8(isolate, val)); + } +#else // _WIN32 + node::TwoByteValue key(isolate, property); + WCHAR buffer[32767]; // The maximum size allowed for environment variables. + SetLastError(ERROR_SUCCESS); + DWORD result = GetEnvironmentVariableW(reinterpret_cast(*key), + buffer, + arraysize(buffer)); + // If result >= sizeof buffer the buffer was too small. That should never + // happen. If result == 0 and result != ERROR_SUCCESS the variable was not + // not found. + if ((result > 0 || GetLastError() == ERROR_SUCCESS) && + result < arraysize(buffer)) { + const uint16_t* two_byte_buffer = reinterpret_cast(buffer); + Local rc = String::NewFromTwoByte(isolate, two_byte_buffer); + return info.GetReturnValue().Set(rc); + } +#endif +} + + +void EnvSetter(Local property, + Local value, + const PropertyCallbackInfo& info) { + Environment* env = Environment::GetCurrent(info); + if (config_pending_deprecation && env->EmitProcessEnvWarning() && + !value->IsString() && !value->IsNumber() && !value->IsBoolean()) { + if (ProcessEmitDeprecationWarning( + env, + "Assigning any value other than a string, number, or boolean to a " + "process.env property is deprecated. Please make sure to convert the " + "value to a string before setting process.env with it.", + "DEP0104").IsNothing()) + return; + } + + Mutex::ScopedLock lock(environ_mutex); +#ifdef __POSIX__ + node::Utf8Value key(info.GetIsolate(), property); + node::Utf8Value val(info.GetIsolate(), value); + setenv(*key, *val, 1); +#else // _WIN32 + node::TwoByteValue key(info.GetIsolate(), property); + node::TwoByteValue val(info.GetIsolate(), value); + WCHAR* key_ptr = reinterpret_cast(*key); + // Environment variables that start with '=' are read-only. + if (key_ptr[0] != L'=') { + SetEnvironmentVariableW(key_ptr, reinterpret_cast(*val)); + } +#endif + // Whether it worked or not, always return value. + info.GetReturnValue().Set(value); +} + + +void EnvQuery(Local property, const PropertyCallbackInfo& info) { + Mutex::ScopedLock lock(environ_mutex); + int32_t rc = -1; // Not found unless proven otherwise. + if (property->IsString()) { +#ifdef __POSIX__ + node::Utf8Value key(info.GetIsolate(), property); + if (getenv(*key)) + rc = 0; +#else // _WIN32 + node::TwoByteValue key(info.GetIsolate(), property); + WCHAR* key_ptr = reinterpret_cast(*key); + SetLastError(ERROR_SUCCESS); + if (GetEnvironmentVariableW(key_ptr, nullptr, 0) > 0 || + GetLastError() == ERROR_SUCCESS) { + rc = 0; + if (key_ptr[0] == L'=') { + // Environment variables that start with '=' are hidden and read-only. + rc = static_cast(v8::ReadOnly) | + static_cast(v8::DontDelete) | + static_cast(v8::DontEnum); + } + } +#endif + } + if (rc != -1) + info.GetReturnValue().Set(rc); +} + + +void EnvDeleter(Local property, + const PropertyCallbackInfo& info) { + Mutex::ScopedLock lock(environ_mutex); + if (property->IsString()) { +#ifdef __POSIX__ + node::Utf8Value key(info.GetIsolate(), property); + unsetenv(*key); +#else + node::TwoByteValue key(info.GetIsolate(), property); + WCHAR* key_ptr = reinterpret_cast(*key); + SetEnvironmentVariableW(key_ptr, nullptr); +#endif + } + + // process.env never has non-configurable properties, so always + // return true like the tc39 delete operator. + info.GetReturnValue().Set(true); +} + + +void EnvEnumerator(const PropertyCallbackInfo& info) { + Environment* env = Environment::GetCurrent(info); + Isolate* isolate = env->isolate(); + Local ctx = env->context(); + Local fn = env->push_values_to_array_function(); + Local argv[NODE_PUSH_VAL_TO_ARRAY_MAX]; + size_t idx = 0; + + Mutex::ScopedLock lock(environ_mutex); +#ifdef __POSIX__ + int size = 0; + while (environ[size]) + size++; + + Local envarr = Array::New(isolate); + + for (int i = 0; i < size; ++i) { + const char* var = environ[i]; + const char* s = strchr(var, '='); + const int length = s ? s - var : strlen(var); + argv[idx] = String::NewFromUtf8(isolate, + var, + String::kNormalString, + length); + if (++idx >= arraysize(argv)) { + fn->Call(ctx, envarr, idx, argv).ToLocalChecked(); + idx = 0; + } + } + if (idx > 0) { + fn->Call(ctx, envarr, idx, argv).ToLocalChecked(); + } +#else // _WIN32 + WCHAR* environment = GetEnvironmentStringsW(); + if (environment == nullptr) + return; // This should not happen. + Local envarr = Array::New(isolate); + WCHAR* p = environment; + while (*p) { + WCHAR *s; + if (*p == L'=') { + // If the key starts with '=' it is a hidden environment variable. + p += wcslen(p) + 1; + continue; + } else { + s = wcschr(p, L'='); + } + if (!s) { + s = p + wcslen(p); + } + const uint16_t* two_byte_buffer = reinterpret_cast(p); + const size_t two_byte_buffer_len = s - p; + argv[idx] = String::NewFromTwoByte(isolate, + two_byte_buffer, + String::kNormalString, + two_byte_buffer_len); + if (++idx >= arraysize(argv)) { + fn->Call(ctx, envarr, idx, argv).ToLocalChecked(); + idx = 0; + } + p = s + wcslen(s) + 1; + } + if (idx > 0) { + fn->Call(ctx, envarr, idx, argv).ToLocalChecked(); + } + FreeEnvironmentStringsW(environment); +#endif + + info.GetReturnValue().Set(envarr); +} + +Local GetFeatures(Environment* env) { + EscapableHandleScope scope(env->isolate()); + + Local obj = Object::New(env->isolate()); +#if defined(DEBUG) && DEBUG + Local debug = True(env->isolate()); +#else + Local debug = False(env->isolate()); +#endif // defined(DEBUG) && DEBUG + + obj->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "debug"), debug); + obj->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "uv"), True(env->isolate())); + // TODO(bnoordhuis) ping libuv + obj->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "ipv6"), True(env->isolate())); + +#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation + Local tls_alpn = True(env->isolate()); +#else + Local tls_alpn = False(env->isolate()); +#endif + obj->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "tls_alpn"), tls_alpn); + +#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB + Local tls_sni = True(env->isolate()); +#else + Local tls_sni = False(env->isolate()); +#endif + obj->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "tls_sni"), tls_sni); + +#if !defined(OPENSSL_NO_TLSEXT) && defined(SSL_CTX_set_tlsext_status_cb) + Local tls_ocsp = True(env->isolate()); +#else + Local tls_ocsp = False(env->isolate()); +#endif // !defined(OPENSSL_NO_TLSEXT) && defined(SSL_CTX_set_tlsext_status_cb) + obj->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "tls_ocsp"), tls_ocsp); + + obj->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "tls"), + Boolean::New(env->isolate(), + get_builtin_module("crypto") != nullptr)); + + return scope.Escape(obj); +} + +void DebugPortGetter(Local property, + const PropertyCallbackInfo& info) { + Mutex::ScopedLock lock(process_mutex); + int port = debug_options.port(); +#if HAVE_INSPECTOR + if (port == 0) { + Environment* env = Environment::GetCurrent(info); + if (auto io = env->inspector_agent()->io()) + port = io->port(); + } +#endif // HAVE_INSPECTOR + info.GetReturnValue().Set(port); +} + + +void DebugPortSetter(Local property, + Local value, + const PropertyCallbackInfo& info) { + Mutex::ScopedLock lock(process_mutex); + debug_options.set_port(value->Int32Value()); +} + +void GetParentProcessId(Local property, + const PropertyCallbackInfo& info) { + info.GetReturnValue().Set(Integer::New(info.GetIsolate(), uv_os_getppid())); +} + + +} // namespace node diff --git a/src/node_process.h b/src/node_process.h new file mode 100644 index 00000000000000..726b989e4e9c5c --- /dev/null +++ b/src/node_process.h @@ -0,0 +1,82 @@ +#ifndef SRC_NODE_PROCESS_H_ +#define SRC_NODE_PROCESS_H_ + +#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#include "node.h" + +namespace node { + +using v8::Array; +using v8::Boolean; +using v8::FunctionCallbackInfo; +using v8::Integer; +using v8::Local; +using v8::Name; +using v8::Object; +using v8::PropertyCallbackInfo; +using v8::Value; + +void Abort(const FunctionCallbackInfo& args); +void Chdir(const FunctionCallbackInfo& args); +void CPUUsage(const FunctionCallbackInfo& args); +void Cwd(const FunctionCallbackInfo& args); +void DLOpen(const FunctionCallbackInfo& args); +void GetActiveRequests(const FunctionCallbackInfo& args); +void GetActiveHandles(const FunctionCallbackInfo& args); +void Hrtime(const FunctionCallbackInfo& args); +void Kill(const FunctionCallbackInfo& args); +void MemoryUsage(const FunctionCallbackInfo& args); +void RawDebug(const FunctionCallbackInfo& args); +void StartProfilerIdleNotifier(const FunctionCallbackInfo& args); +void StopProfilerIdleNotifier(const FunctionCallbackInfo& args); +void Umask(const FunctionCallbackInfo& args); +void Uptime(const FunctionCallbackInfo& args); + +#if defined(__POSIX__) && !defined(__ANDROID__) && !defined(__CloudABI__) +void GetUid(const FunctionCallbackInfo& args); +void GetGid(const FunctionCallbackInfo& args); +void GetEUid(const FunctionCallbackInfo& args); +void GetEGid(const FunctionCallbackInfo& args); +void SetGid(const FunctionCallbackInfo& args); +void SetEGid(const FunctionCallbackInfo& args); +void SetUid(const FunctionCallbackInfo& args); +void SetEUid(const FunctionCallbackInfo& args); +void GetGroups(const FunctionCallbackInfo& args); +void SetGroups(const FunctionCallbackInfo& args); +void InitGroups(const FunctionCallbackInfo& args); +#endif // __POSIX__ && !defined(__ANDROID__) && !defined(__CloudABI__) + +void DebugPortGetter(Local property, + const PropertyCallbackInfo& info); + +void DebugPortSetter(Local property, + Local value, + const PropertyCallbackInfo& info); + +void GetParentProcessId(Local property, + const PropertyCallbackInfo& info); + +void EnvGetter(Local property, + const PropertyCallbackInfo& info); +void EnvSetter(Local property, + Local value, + const PropertyCallbackInfo& info); +void EnvQuery(Local property, const PropertyCallbackInfo& info); +void EnvDeleter(Local property, + const PropertyCallbackInfo& info); +void EnvEnumerator(const PropertyCallbackInfo& info); + +void ProcessTitleGetter(Local property, + const PropertyCallbackInfo& info); +void ProcessTitleSetter(Local property, + Local value, + const PropertyCallbackInfo& info); + +Local GetFeatures(Environment* env); + +} // namespace node + +#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#endif // SRC_NODE_PROCESS_H_ From b91694ff9751e5700596fcbfdab654c2781a7712 Mon Sep 17 00:00:00 2001 From: James M Snell Date: Mon, 21 May 2018 17:30:54 -0700 Subject: [PATCH 2/3] src: cleanup SetupProcessObject code Various cleanups within SetupProcessObject to improve readability --- src/node.cc | 319 +++++++++++++++++++++------------------------------- 1 file changed, 130 insertions(+), 189 deletions(-) diff --git a/src/node.cc b/src/node.cc index 6b828760e21b2b..e9eccfe7dd67f5 100644 --- a/src/node.cc +++ b/src/node.cc @@ -1435,7 +1435,7 @@ bool ShouldAbortOnUncaughtException(Isolate* isolate) { #define READONLY_PROPERTY(obj, str, var) \ do { \ obj->DefineOwnProperty(env->context(), \ - OneByteString(env->isolate(), str), \ + OneByteString(env->isolate(), #str), \ var, \ v8::ReadOnly).FromJust(); \ } while (0) @@ -1443,13 +1443,26 @@ bool ShouldAbortOnUncaughtException(Isolate* isolate) { #define READONLY_DONT_ENUM_PROPERTY(obj, str, var) \ do { \ obj->DefineOwnProperty(env->context(), \ - OneByteString(env->isolate(), str), \ + OneByteString(env->isolate(), #str), \ var, \ static_cast(v8::ReadOnly | \ v8::DontEnum)) \ .FromJust(); \ } while (0) +#define READONLY_FLAG(obj, str) \ + READONLY_PROPERTY(obj, str, True(env->isolate())) + +#define READONLY_DONT_ENUM_FLAG(obj, str) \ + READONLY_DONT_ENUM_PROPERTY(obj, str, True(env->isolate())) + +#define SET_READONLY_FLAG(condition, name) \ + if (condition) READONLY_FLAG(process, name) +#define SET_READONLY_NOENUM_FLAG(condition, name) \ + if (condition) READONLY_DONT_ENUM_FLAG(process, name) + +#define PROCESS_METHOD(name, fn) env->SetMethod(process, #name, fn) + } // anonymous namespace void SetupProcessObject(Environment* env, @@ -1457,67 +1470,49 @@ void SetupProcessObject(Environment* env, const char* const* argv, int exec_argc, const char* const* exec_argv) { - HandleScope scope(env->isolate()); + Isolate* isolate = env->isolate(); + HandleScope scope(isolate); Local process = env->process_object(); - auto title_string = FIXED_ONE_BYTE_STRING(env->isolate(), "title"); CHECK(process->SetAccessor(env->context(), - title_string, + FIXED_ONE_BYTE_STRING(isolate, "title"), ProcessTitleGetter, ProcessTitleSetter, env->as_external()).FromJust()); // process.version - READONLY_PROPERTY(process, - "version", - FIXED_ONE_BYTE_STRING(env->isolate(), NODE_VERSION)); + READONLY_PROPERTY(process, version, + FIXED_ONE_BYTE_STRING(isolate, NODE_VERSION)); // process.versions - Local versions = Object::New(env->isolate()); - READONLY_PROPERTY(process, "versions", versions); + Local versions = Object::New(isolate); + READONLY_PROPERTY(process, versions, versions); + const char node_napi_version[] = NODE_STRINGIFY(NAPI_VERSION); + const char node_modules_version[] = NODE_STRINGIFY(NODE_MODULE_VERSION); const char http_parser_version[] = NODE_STRINGIFY(HTTP_PARSER_VERSION_MAJOR) "." NODE_STRINGIFY(HTTP_PARSER_VERSION_MINOR) "." NODE_STRINGIFY(HTTP_PARSER_VERSION_PATCH); - READONLY_PROPERTY(versions, - "http_parser", - FIXED_ONE_BYTE_STRING(env->isolate(), http_parser_version)); - - // +1 to get rid of the leading 'v' - READONLY_PROPERTY(versions, - "node", - OneByteString(env->isolate(), NODE_VERSION + 1)); - READONLY_PROPERTY(versions, - "v8", - OneByteString(env->isolate(), V8::GetVersion())); - READONLY_PROPERTY(versions, - "uv", - OneByteString(env->isolate(), uv_version_string())); - READONLY_PROPERTY(versions, - "zlib", - FIXED_ONE_BYTE_STRING(env->isolate(), ZLIB_VERSION)); - READONLY_PROPERTY(versions, - "ares", - FIXED_ONE_BYTE_STRING(env->isolate(), ARES_VERSION_STR)); - - const char node_modules_version[] = NODE_STRINGIFY(NODE_MODULE_VERSION); - READONLY_PROPERTY( - versions, - "modules", - FIXED_ONE_BYTE_STRING(env->isolate(), node_modules_version)); - - READONLY_PROPERTY(versions, - "nghttp2", - FIXED_ONE_BYTE_STRING(env->isolate(), NGHTTP2_VERSION)); - - const char node_napi_version[] = NODE_STRINGIFY(NAPI_VERSION); - READONLY_PROPERTY( - versions, - "napi", - FIXED_ONE_BYTE_STRING(env->isolate(), node_napi_version)); + READONLY_PROPERTY(versions, http_parser, + FIXED_ONE_BYTE_STRING(isolate, http_parser_version)); + + READONLY_PROPERTY(versions, node, + FIXED_ONE_BYTE_STRING(isolate, NODE_VERSION_STRING)); + READONLY_PROPERTY(versions, v8, OneByteString(isolate, V8::GetVersion())); + READONLY_PROPERTY(versions, uv, OneByteString(isolate, uv_version_string())); + READONLY_PROPERTY(versions, zlib, + FIXED_ONE_BYTE_STRING(isolate, ZLIB_VERSION)); + READONLY_PROPERTY(versions, ares, + FIXED_ONE_BYTE_STRING(isolate, ARES_VERSION_STR)); + READONLY_PROPERTY(versions, modules, + FIXED_ONE_BYTE_STRING(isolate, node_modules_version)); + READONLY_PROPERTY(versions, nghttp2, + FIXED_ONE_BYTE_STRING(isolate, NGHTTP2_VERSION)); + READONLY_PROPERTY(versions, napi, + FIXED_ONE_BYTE_STRING(isolate, node_napi_version)); #if HAVE_OPENSSL // Stupid code to slice out the version string. @@ -1535,30 +1530,27 @@ void SetupProcessObject(Environment* env, break; } } - READONLY_PROPERTY( - versions, - "openssl", - OneByteString(env->isolate(), &OPENSSL_VERSION_TEXT[i], j - i)); + READONLY_PROPERTY(versions, openssl, + OneByteString(isolate, &OPENSSL_VERSION_TEXT[i], j - i)); } #endif // process.arch - READONLY_PROPERTY(process, "arch", OneByteString(env->isolate(), NODE_ARCH)); + READONLY_PROPERTY(process, arch, FIXED_ONE_BYTE_STRING(isolate, NODE_ARCH)); // process.platform - READONLY_PROPERTY(process, - "platform", - OneByteString(env->isolate(), NODE_PLATFORM)); + READONLY_PROPERTY(process, platform, + FIXED_ONE_BYTE_STRING(isolate, NODE_PLATFORM)); // process.release - Local release = Object::New(env->isolate()); - READONLY_PROPERTY(process, "release", release); - READONLY_PROPERTY(release, "name", - OneByteString(env->isolate(), NODE_RELEASE)); + Local release = Object::New(isolate); + READONLY_PROPERTY(process, release, release); + READONLY_PROPERTY(release, name, + FIXED_ONE_BYTE_STRING(isolate, NODE_RELEASE)); #if NODE_VERSION_IS_LTS - READONLY_PROPERTY(release, "lts", - OneByteString(env->isolate(), NODE_VERSION_LTS_CODENAME)); + READONLY_PROPERTY(release, lts, + FIXED_ONE_BYTE_STRING(isolate, NODE_VERSION_LTS_CODENAME)); #endif // if this is a release build and no explicit base has been set @@ -1573,18 +1565,16 @@ void SetupProcessObject(Environment* env, # define NODE_RELEASE_URLPFX NODE_RELEASE_URLBASE "v" NODE_VERSION_STRING "/" # define NODE_RELEASE_URLFPFX NODE_RELEASE_URLPFX "node-v" NODE_VERSION_STRING - READONLY_PROPERTY(release, - "sourceUrl", - OneByteString(env->isolate(), - NODE_RELEASE_URLFPFX ".tar.gz")); - READONLY_PROPERTY(release, - "headersUrl", - OneByteString(env->isolate(), - NODE_RELEASE_URLFPFX "-headers.tar.gz")); + READONLY_PROPERTY(release, sourceUrl, + FIXED_ONE_BYTE_STRING(isolate, + NODE_RELEASE_URLFPFX ".tar.gz")); + READONLY_PROPERTY(release, headersUrl, + FIXED_ONE_BYTE_STRING(isolate, + NODE_RELEASE_URLFPFX + "-headers.tar.gz")); # ifdef _WIN32 - READONLY_PROPERTY(release, - "libUrl", - OneByteString(env->isolate(), + READONLY_PROPERTY(release, libUrl, + FIXED_ONE_BYTE_STRING(isolate, strcmp(NODE_ARCH, "ia32") ? NODE_RELEASE_URLPFX "win-" NODE_ARCH "/node.lib" : NODE_RELEASE_URLPFX @@ -1593,23 +1583,22 @@ void SetupProcessObject(Environment* env, #endif // process.argv - Local arguments = Array::New(env->isolate(), argc); + Local arguments = Array::New(isolate, argc); for (int i = 0; i < argc; ++i) { - arguments->Set(i, String::NewFromUtf8(env->isolate(), argv[i])); + arguments->Set(i, String::NewFromUtf8(isolate, argv[i])); } - process->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "argv"), arguments); + process->Set(FIXED_ONE_BYTE_STRING(isolate, "argv"), arguments); // process.execArgv - Local exec_arguments = Array::New(env->isolate(), exec_argc); + Local exec_arguments = Array::New(isolate, exec_argc); for (int i = 0; i < exec_argc; ++i) { - exec_arguments->Set(i, String::NewFromUtf8(env->isolate(), exec_argv[i])); + exec_arguments->Set(i, String::NewFromUtf8(isolate, exec_argv[i])); } - process->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "execArgv"), - exec_arguments); + process->Set(FIXED_ONE_BYTE_STRING(isolate, "execArgv"), exec_arguments); // create process.env Local process_env_template = - ObjectTemplate::New(env->isolate()); + ObjectTemplate::New(isolate); process_env_template->SetHandler(NamedPropertyHandlerConfiguration( EnvGetter, EnvSetter, @@ -1620,114 +1609,69 @@ void SetupProcessObject(Environment* env, Local process_env = process_env_template->NewInstance(env->context()).ToLocalChecked(); - process->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "env"), process_env); + process->Set(FIXED_ONE_BYTE_STRING(isolate, "env"), process_env); - READONLY_PROPERTY(process, "pid", - Integer::New(env->isolate(), uv_os_getpid())); - READONLY_PROPERTY(process, "features", GetFeatures(env)); + READONLY_PROPERTY(process, pid, Integer::New(isolate, uv_os_getpid())); + READONLY_PROPERTY(process, features, GetFeatures(env)); CHECK(process->SetAccessor(env->context(), - FIXED_ONE_BYTE_STRING(env->isolate(), "ppid"), + FIXED_ONE_BYTE_STRING(isolate, "ppid"), GetParentProcessId).FromJust()); // -e, --eval if (eval_string) { - READONLY_PROPERTY(process, - "_eval", - String::NewFromUtf8(env->isolate(), eval_string)); - } - - // -p, --print - if (print_eval) { - READONLY_PROPERTY(process, "_print_eval", True(env->isolate())); - } - - // -c, --check - if (syntax_check_only) { - READONLY_PROPERTY(process, "_syntax_check_only", True(env->isolate())); - } - - // -i, --interactive - if (force_repl) { - READONLY_PROPERTY(process, "_forceRepl", True(env->isolate())); + READONLY_PROPERTY(process, _eval, + String::NewFromUtf8(isolate, eval_string)); } // -r, --require if (!preload_modules.empty()) { - Local array = Array::New(env->isolate()); - for (unsigned int i = 0; i < preload_modules.size(); ++i) { - Local module = String::NewFromUtf8(env->isolate(), - preload_modules[i].c_str()); - array->Set(i, module); - } - READONLY_PROPERTY(process, - "_preload_modules", - array); - + unsigned int i = 0; + Local array = Array::New(isolate, preload_modules.size()); + for (auto module : preload_modules) + array->Set(i++, String::NewFromUtf8(isolate, module.c_str())); + READONLY_PROPERTY(process, _preload_modules, array); preload_modules.clear(); } + // -p, --print + SET_READONLY_FLAG(print_eval, _print_eval); + // -c, --check + SET_READONLY_FLAG(syntax_check_only, _syntax_check_only); + // -i, --interactive + SET_READONLY_FLAG(force_repl, _forceRepl); // --no-deprecation - if (no_deprecation) { - READONLY_PROPERTY(process, "noDeprecation", True(env->isolate())); - } - + SET_READONLY_FLAG(no_deprecation, noDeprecation); // --no-warnings - if (no_process_warnings) { - READONLY_PROPERTY(process, "noProcessWarnings", True(env->isolate())); - } - + SET_READONLY_FLAG(no_process_warnings, noProcessWarnings); // --trace-warnings - if (trace_warnings) { - READONLY_PROPERTY(process, "traceProcessWarnings", True(env->isolate())); - } - + SET_READONLY_FLAG(trace_warnings, traceProcessWarnings); // --throw-deprecation - if (throw_deprecation) { - READONLY_PROPERTY(process, "throwDeprecation", True(env->isolate())); - } + SET_READONLY_FLAG(throw_deprecation, throwDeprecation); + // --prof-process + SET_READONLY_FLAG(prof_process, profProcess); + // --trace-deprecation + SET_READONLY_FLAG(trace_deprecation, traceDeprecation); #ifdef NODE_NO_BROWSER_GLOBALS // configure --no-browser-globals - READONLY_PROPERTY(process, "_noBrowserGlobals", True(env->isolate())); + SET_READONLY_FLAG(true, _noBrowserGlobals); #endif // NODE_NO_BROWSER_GLOBALS - // --prof-process - if (prof_process) { - READONLY_PROPERTY(process, "profProcess", True(env->isolate())); - } - - // --trace-deprecation - if (trace_deprecation) { - READONLY_PROPERTY(process, "traceDeprecation", True(env->isolate())); - } - // TODO(refack): move the following 3 to `node_config` // --inspect-brk - if (debug_options.wait_for_connect()) { - READONLY_DONT_ENUM_PROPERTY(process, - "_breakFirstLine", True(env->isolate())); - } - + SET_READONLY_NOENUM_FLAG(debug_options.wait_for_connect(), + _breakFirstLine); // --inspect --debug-brk - if (debug_options.deprecated_invocation()) { - READONLY_DONT_ENUM_PROPERTY(process, - "_deprecatedDebugBrk", True(env->isolate())); - } - + SET_READONLY_NOENUM_FLAG(debug_options.deprecated_invocation(), + _deprecatedDebugBrk); // --debug or, --debug-brk without --inspect - if (debug_options.invalid_invocation()) { - READONLY_DONT_ENUM_PROPERTY(process, - "_invalidDebug", True(env->isolate())); - } + SET_READONLY_NOENUM_FLAG(debug_options.invalid_invocation(), + _invalidDebug); // --security-revert flags #define V(code, _, __) \ - do { \ - if (IsReverted(SECURITY_REVERT_ ## code)) { \ - READONLY_PROPERTY(process, "REVERT_" #code, True(env->isolate())); \ - } \ - } while (0); + SET_READONLY_FLAG(IsReverted(SECURITY_REVERT_##code), REVERT_##code); SECURITY_REVERSIONS(V) #undef V @@ -1735,56 +1679,53 @@ void SetupProcessObject(Environment* env, char* exec_path = new char[exec_path_len]; Local exec_path_value; if (uv_exepath(exec_path, &exec_path_len) == 0) { - exec_path_value = String::NewFromUtf8(env->isolate(), + exec_path_value = String::NewFromUtf8(isolate, exec_path, String::kNormalString, exec_path_len); } else { - exec_path_value = String::NewFromUtf8(env->isolate(), argv[0]); + exec_path_value = String::NewFromUtf8(isolate, argv[0]); } - process->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "execPath"), - exec_path_value); + process->Set(FIXED_ONE_BYTE_STRING(isolate, "execPath"), exec_path_value); delete[] exec_path; - auto debug_port_string = FIXED_ONE_BYTE_STRING(env->isolate(), "debugPort"); CHECK(process->SetAccessor(env->context(), - debug_port_string, + FIXED_ONE_BYTE_STRING(isolate, "debugPort"), DebugPortGetter, DebugPortSetter, env->as_external()).FromJust()); // define various internal methods - env->SetMethod(process, - "_startProfilerIdleNotifier", - StartProfilerIdleNotifier); - env->SetMethod(process, - "_stopProfilerIdleNotifier", - StopProfilerIdleNotifier); - env->SetMethod(process, "_getActiveRequests", GetActiveRequests); - env->SetMethod(process, "_getActiveHandles", GetActiveHandles); - env->SetMethod(process, "reallyExit", Exit); - env->SetMethod(process, "abort", Abort); - env->SetMethod(process, "cwd", Cwd); + PROCESS_METHOD(_debugProcess, DebugProcess); + PROCESS_METHOD(_debugEnd, DebugEnd); + PROCESS_METHOD(_getActiveRequests, GetActiveRequests); + PROCESS_METHOD(_getActiveHandles, GetActiveHandles); + PROCESS_METHOD(_kill, Kill); + PROCESS_METHOD(_startProfilerIdleNotifier, StartProfilerIdleNotifier); + PROCESS_METHOD(_stopProfilerIdleNotifier, StopProfilerIdleNotifier); + + PROCESS_METHOD(abort, Abort); + PROCESS_METHOD(cwd, Cwd); + PROCESS_METHOD(dlopen, DLOpen); + PROCESS_METHOD(reallyExit, Exit); + PROCESS_METHOD(uptime, Uptime); #if defined(__POSIX__) && !defined(__ANDROID__) && !defined(__CloudABI__) - env->SetMethod(process, "getuid", GetUid); - env->SetMethod(process, "geteuid", GetEUid); - env->SetMethod(process, "getgid", GetGid); - env->SetMethod(process, "getegid", GetEGid); - env->SetMethod(process, "getgroups", GetGroups); + PROCESS_METHOD(geteuid, GetEUid); + PROCESS_METHOD(getegid, GetEGid); + PROCESS_METHOD(getgid, GetGid); + PROCESS_METHOD(getgroups, GetGroups); + PROCESS_METHOD(getuid, GetUid); #endif // __POSIX__ && !defined(__ANDROID__) && !defined(__CloudABI__) - - env->SetMethod(process, "_kill", Kill); - - env->SetMethod(process, "_debugProcess", DebugProcess); - env->SetMethod(process, "_debugEnd", DebugEnd); - - env->SetMethod(process, "dlopen", DLOpen); - env->SetMethod(process, "uptime", Uptime); } - +#undef PROCESS_METHOD #undef READONLY_PROPERTY +#undef READONLY_DONT_ENUM_PROPERTY +#undef READONLY_FLAG +#undef READONLY_DONT_ENUM_FLAG +#undef SET_READONLY_FLAG +#undef SET_READONLY_NOENUM_FLAG void SignalExit(int signo) { @@ -1802,7 +1743,7 @@ void SignalExit(int signo) { static Local GetBootstrapper(Environment* env, Local source, - Local script_name) { + Local script_name) { EscapableHandleScope scope(env->isolate()); TryCatch try_catch(env->isolate()); From 5abe91f8d892d4d5ebfa48c998d0e69156be1366 Mon Sep 17 00:00:00 2001 From: James M Snell Date: Mon, 21 May 2018 18:08:20 -0700 Subject: [PATCH 3/3] src: move Exit into node_process --- src/node.cc | 8 +++----- src/node.h | 8 ++++++++ src/node_process.cc | 4 ++++ src/node_process.h | 1 + 4 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/node.cc b/src/node.cc index e9eccfe7dd67f5..970eea3e5e6ff3 100644 --- a/src/node.cc +++ b/src/node.cc @@ -1105,14 +1105,12 @@ static void WaitForInspectorDisconnect(Environment* env) { #endif } - -static void Exit(const FunctionCallbackInfo& args) { - WaitForInspectorDisconnect(Environment::GetCurrent(args)); +NO_RETURN void Exit(Environment* env, int32_t code) { + WaitForInspectorDisconnect(env); v8_platform.StopTracingAgent(); - exit(args[0]->Int32Value()); + exit(code); } - extern "C" void node_module_register(void* m) { struct node_module* mp = reinterpret_cast(m); diff --git a/src/node.h b/src/node.h index d4b934645163f1..d4bb3b5be97926 100644 --- a/src/node.h +++ b/src/node.h @@ -479,6 +479,14 @@ extern "C" NODE_EXTERN void node_module_register(void* mod); */ #define NODE_MODULE_DECL /* nothing */ +#ifdef __GNUC__ +#define NO_RETURN __attribute__((noreturn)) +#else +#define NO_RETURN +#endif + +NO_RETURN void Exit(Environment* env, int32_t code); + /* Called after the event loop exits but before the VM is disposed. * Callbacks are run in reverse order of registration, i.e. newest first. */ diff --git a/src/node_process.cc b/src/node_process.cc index 6660d3de16a01c..2fb074586c8dec 100644 --- a/src/node_process.cc +++ b/src/node_process.cc @@ -70,6 +70,10 @@ using v8::Uint32; using v8::Uint32Array; using v8::Value; +void Exit(const FunctionCallbackInfo& args) { + Exit(Environment::GetCurrent(args), args[0]->Int32Value()); +} + void Chdir(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); diff --git a/src/node_process.h b/src/node_process.h index 726b989e4e9c5c..bac93b75e1874f 100644 --- a/src/node_process.h +++ b/src/node_process.h @@ -22,6 +22,7 @@ void Chdir(const FunctionCallbackInfo& args); void CPUUsage(const FunctionCallbackInfo& args); void Cwd(const FunctionCallbackInfo& args); void DLOpen(const FunctionCallbackInfo& args); +void Exit(const FunctionCallbackInfo& args); void GetActiveRequests(const FunctionCallbackInfo& args); void GetActiveHandles(const FunctionCallbackInfo& args); void Hrtime(const FunctionCallbackInfo& args);