From f99f5d3c0100b9c4307d8bb3a4df27c843eee531 Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Fri, 5 Aug 2022 21:46:08 +0200 Subject: [PATCH] src: add detailed embedder process initialization API So far, process initialization has been a bit all over the place in Node.js. `InitializeNodeWithArgs()` is our main public API for this, but inclusion of items in it vs. `InitializeOncePerProcess()` and `PlatformInit()` has been random at best. Likewise, some pieces of initialization have been guarded by `NODE_SHARED_MODE`, but also fairly randomly and without any meaningful connection to shared library usage. This leaves embedders in a position to cherry-pick some of the initialization code into their own code to make their application behave like typical Node.js applications to the degree to which they desire it. Electron takes an alternative route and makes direct use of `InitializeOncePerProcess()` already while it is a private API, with a `TODO` to add it to the public API in Node.js. This commit addresses that `TODO`, and `TODO`s around the `NODE_SHARED_MODE` usage. Specifically: - `InitializeOncePerProcess()` and `TearDownOncePerProcess()` are added to the public API. - The `flags` option of these functions are merged with the `flags` option for `InitializeNodeWithArgs()`, since they essentially share the same semantics. - The return value of the function is made an abstract class, rather than a struct, for easier API/ABI stability. - Initialization code from `main()` is brought into these functions (since that makes sense in general). - Add a `TODO` for turning `InitializeNodeWithArgs()` into a small wrapper around `InitializeOncePerProcess()` and eventually removing it (at least one major release cycle each, presumably). - Remove `NODE_SHARED_MODE` guards and replace them with runtime options. PR-URL: https://github.com/nodejs/node/pull/44121 Backport-PR-URL: https://github.com/nodejs/node/pull/44358 Reviewed-By: Joyee Cheung Reviewed-By: Michael Dawson --- doc/api/embedding.md | 20 +- src/node.cc | 463 +++++++++++++++++------------- src/node.h | 112 +++++++- src/node_credentials.cc | 21 +- src/node_internals.h | 39 ++- src/node_main.cc | 34 --- src/node_main_instance.cc | 15 - test/embedding/embedtest.cc | 20 +- tools/snapshot/node_mksnapshot.cc | 17 +- 9 files changed, 430 insertions(+), 311 deletions(-) diff --git a/doc/api/embedding.md b/doc/api/embedding.md index d07bec744df2e5..9f831b342c2705 100644 --- a/doc/api/embedding.md +++ b/doc/api/embedding.md @@ -37,15 +37,18 @@ the `node` and `v8` C++ namespaces, respectively. int main(int argc, char** argv) { argv = uv_setup_args(argc, argv); std::vector args(argv, argv + argc); - std::vector exec_args; - std::vector errors; // Parse Node.js CLI options, and print any errors that have occurred while // trying to parse them. - int exit_code = node::InitializeNodeWithArgs(&args, &exec_args, &errors); - for (const std::string& error : errors) + std::unique_ptr result = + node::InitializeOncePerProcess(args, { + node::ProcessInitializationFlags::kNoInitializeV8, + node::ProcessInitializationFlags::kNoInitializeNodeV8Platform + }); + + for (const std::string& error : result->errors()) fprintf(stderr, "%s: %s\n", args[0].c_str(), error.c_str()); - if (exit_code != 0) { - return exit_code; + if (result->early_return() != 0) { + return result->exit_code(); } // Create a v8::Platform instance. `MultiIsolatePlatform::Create()` is a way @@ -58,10 +61,13 @@ int main(int argc, char** argv) { V8::Initialize(); // See below for the contents of this function. - int ret = RunNodeInstance(platform.get(), args, exec_args); + int ret = RunNodeInstance( + platform.get(), result->args(), result->exec_args()); V8::Dispose(); V8::DisposePlatform(); + + node::TearDownOncePerProcess(); return ret; } ``` diff --git a/src/node.cc b/src/node.cc index c722ae312359c7..357ca1eb55652d 100644 --- a/src/node.cc +++ b/src/node.cc @@ -577,37 +577,8 @@ static struct { } stdio[1 + STDERR_FILENO]; #endif // __POSIX__ - -inline void PlatformInit() { +void ResetSignalHandlers() { #ifdef __POSIX__ -#if HAVE_INSPECTOR - sigset_t sigmask; - sigemptyset(&sigmask); - sigaddset(&sigmask, SIGUSR1); - const int err = pthread_sigmask(SIG_SETMASK, &sigmask, nullptr); -#endif // HAVE_INSPECTOR - - // Make sure file descriptors 0-2 are valid before we start logging anything. - for (auto& s : stdio) { - const int fd = &s - stdio; - if (fstat(fd, &s.stat) == 0) - continue; - // Anything but EBADF means something is seriously wrong. We don't - // have to special-case EINTR, fstat() is not interruptible. - if (errno != EBADF) - ABORT(); - if (fd != open("/dev/null", O_RDWR)) - ABORT(); - if (fstat(fd, &s.stat) != 0) - ABORT(); - } - -#if HAVE_INSPECTOR - CHECK_EQ(err, 0); -#endif // HAVE_INSPECTOR - - // TODO(addaleax): NODE_SHARED_MODE does not really make sense here. -#ifndef NODE_SHARED_MODE // Restore signal dispositions, the parent process may have changed them. struct sigaction act; memset(&act, 0, sizeof(act)); @@ -621,94 +592,150 @@ inline void PlatformInit() { act.sa_handler = (nr == SIGPIPE || nr == SIGXFSZ) ? SIG_IGN : SIG_DFL; CHECK_EQ(0, sigaction(nr, &act, nullptr)); } -#endif // !NODE_SHARED_MODE +#endif // __POSIX__ +} - // Record the state of the stdio file descriptors so we can restore it - // on exit. Needs to happen before installing signal handlers because - // they make use of that information. - for (auto& s : stdio) { - const int fd = &s - stdio; - int err; +static std::atomic init_process_flags = 0; - do - s.flags = fcntl(fd, F_GETFL); - while (s.flags == -1 && errno == EINTR); // NOLINT - CHECK_NE(s.flags, -1); +static void PlatformInit(ProcessInitializationFlags::Flags flags) { + // init_process_flags is accessed in ResetStdio(), + // which can be called from signal handlers. + CHECK(init_process_flags.is_lock_free()); + init_process_flags.store(flags); - if (uv_guess_handle(fd) != UV_TTY) continue; - s.isatty = true; + if (!(flags & ProcessInitializationFlags::kNoStdioInitialization)) { + atexit(ResetStdio); + } - do - err = tcgetattr(fd, &s.termios); - while (err == -1 && errno == EINTR); // NOLINT +#ifdef __POSIX__ + if (!(flags & ProcessInitializationFlags::kNoStdioInitialization)) { + // Disable stdio buffering, it interacts poorly with printf() + // calls elsewhere in the program (e.g., any logging from V8.) + setvbuf(stdout, nullptr, _IONBF, 0); + setvbuf(stderr, nullptr, _IONBF, 0); + + // Make sure file descriptors 0-2 are valid before we start logging + // anything. + for (auto& s : stdio) { + const int fd = &s - stdio; + if (fstat(fd, &s.stat) == 0) continue; + // Anything but EBADF means something is seriously wrong. We don't + // have to special-case EINTR, fstat() is not interruptible. + if (errno != EBADF) ABORT(); + if (fd != open("/dev/null", O_RDWR)) ABORT(); + if (fstat(fd, &s.stat) != 0) ABORT(); + } + } + + if (!(flags & ProcessInitializationFlags::kNoDefaultSignalHandling)) { +#if HAVE_INSPECTOR + sigset_t sigmask; + sigemptyset(&sigmask); + sigaddset(&sigmask, SIGUSR1); + const int err = pthread_sigmask(SIG_SETMASK, &sigmask, nullptr); CHECK_EQ(err, 0); +#endif // HAVE_INSPECTOR + + ResetSignalHandlers(); + } + + if (!(flags & ProcessInitializationFlags::kNoStdioInitialization)) { + // Record the state of the stdio file descriptors so we can restore it + // on exit. Needs to happen before installing signal handlers because + // they make use of that information. + for (auto& s : stdio) { + const int fd = &s - stdio; + int err; + + do { + s.flags = fcntl(fd, F_GETFL); + } while (s.flags == -1 && errno == EINTR); // NOLINT + CHECK_NE(s.flags, -1); + + if (uv_guess_handle(fd) != UV_TTY) continue; + s.isatty = true; + + do { + err = tcgetattr(fd, &s.termios); + } while (err == -1 && errno == EINTR); // NOLINT + CHECK_EQ(err, 0); + } } - RegisterSignalHandler(SIGINT, SignalExit, true); - RegisterSignalHandler(SIGTERM, SignalExit, true); + if (!(flags & ProcessInitializationFlags::kNoDefaultSignalHandling)) { + RegisterSignalHandler(SIGINT, SignalExit, true); + RegisterSignalHandler(SIGTERM, SignalExit, true); #if NODE_USE_V8_WASM_TRAP_HANDLER #if defined(_WIN32) - { - constexpr ULONG first = TRUE; - per_process::old_vectored_exception_handler = - AddVectoredExceptionHandler(first, TrapWebAssemblyOrContinue); - } + { + constexpr ULONG first = TRUE; + per_process::old_vectored_exception_handler = + AddVectoredExceptionHandler(first, TrapWebAssemblyOrContinue); + } #else - // Tell V8 to disable emitting WebAssembly - // memory bounds checks. This means that we have - // to catch the SIGSEGV in TrapWebAssemblyOrContinue - // and pass the signal context to V8. - { - struct sigaction sa; - memset(&sa, 0, sizeof(sa)); - sa.sa_sigaction = TrapWebAssemblyOrContinue; - sa.sa_flags = SA_SIGINFO; - CHECK_EQ(sigaction(SIGSEGV, &sa, nullptr), 0); - } + // Tell V8 to disable emitting WebAssembly + // memory bounds checks. This means that we have + // to catch the SIGSEGV in TrapWebAssemblyOrContinue + // and pass the signal context to V8. + { + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sa.sa_sigaction = TrapWebAssemblyOrContinue; + sa.sa_flags = SA_SIGINFO; + CHECK_EQ(sigaction(SIGSEGV, &sa, nullptr), 0); + } #endif // defined(_WIN32) - V8::EnableWebAssemblyTrapHandler(false); + V8::EnableWebAssemblyTrapHandler(false); #endif // NODE_USE_V8_WASM_TRAP_HANDLER + } - // Raise the open file descriptor limit. - struct rlimit lim; - if (getrlimit(RLIMIT_NOFILE, &lim) == 0 && lim.rlim_cur != lim.rlim_max) { - // Do a binary search for the limit. - rlim_t min = lim.rlim_cur; - rlim_t max = 1 << 20; - // But if there's a defined upper bound, don't search, just set it. - if (lim.rlim_max != RLIM_INFINITY) { - min = lim.rlim_max; - max = lim.rlim_max; - } - do { - lim.rlim_cur = min + (max - min) / 2; - if (setrlimit(RLIMIT_NOFILE, &lim)) { - max = lim.rlim_cur; - } else { - min = lim.rlim_cur; + if (!(flags & ProcessInitializationFlags::kNoAdjustResourceLimits)) { + // Raise the open file descriptor limit. + struct rlimit lim; + if (getrlimit(RLIMIT_NOFILE, &lim) == 0 && lim.rlim_cur != lim.rlim_max) { + // Do a binary search for the limit. + rlim_t min = lim.rlim_cur; + rlim_t max = 1 << 20; + // But if there's a defined upper bound, don't search, just set it. + if (lim.rlim_max != RLIM_INFINITY) { + min = lim.rlim_max; + max = lim.rlim_max; } - } while (min + 1 < max); + do { + lim.rlim_cur = min + (max - min) / 2; + if (setrlimit(RLIMIT_NOFILE, &lim)) { + max = lim.rlim_cur; + } else { + min = lim.rlim_cur; + } + } while (min + 1 < max); + } } #endif // __POSIX__ #ifdef _WIN32 - for (int fd = 0; fd <= 2; ++fd) { - auto handle = reinterpret_cast(_get_osfhandle(fd)); - if (handle == INVALID_HANDLE_VALUE || - GetFileType(handle) == FILE_TYPE_UNKNOWN) { - // Ignore _close result. If it fails or not depends on used Windows - // version. We will just check _open result. - _close(fd); - if (fd != _open("nul", _O_RDWR)) - ABORT(); + if (!(flags & ProcessInitializationFlags::kNoStdioInitialization)) { + for (int fd = 0; fd <= 2; ++fd) { + auto handle = reinterpret_cast(_get_osfhandle(fd)); + if (handle == INVALID_HANDLE_VALUE || + GetFileType(handle) == FILE_TYPE_UNKNOWN) { + // Ignore _close result. If it fails or not depends on used Windows + // version. We will just check _open result. + _close(fd); + if (fd != _open("nul", _O_RDWR)) ABORT(); + } } } #endif // _WIN32 } - // Safe to call more than once and from signal handlers. void ResetStdio() { + if (init_process_flags.load() & + ProcessInitializationFlags::kNoStdioInitialization) { + return; + } + uv_tty_reset_mode(); #ifdef __POSIX__ for (auto& s : stdio) { @@ -844,6 +871,8 @@ int ProcessGlobalArgs(std::vector* args, static std::atomic_bool init_called{false}; +// TODO(addaleax): Turn this into a wrapper around InitializeOncePerProcess() +// (with the corresponding additional flags set), then eventually remove this. int InitializeNodeWithArgs(std::vector* argv, std::vector* exec_argv, std::vector* errors) { @@ -854,7 +883,7 @@ int InitializeNodeWithArgs(std::vector* argv, int InitializeNodeWithArgs(std::vector* argv, std::vector* exec_argv, std::vector* errors, - ProcessFlags::Flags flags) { + ProcessInitializationFlags::Flags flags) { // Make sure InitializeNodeWithArgs() is called only once. CHECK(!init_called.exchange(true)); @@ -865,8 +894,10 @@ int InitializeNodeWithArgs(std::vector* argv, binding::RegisterBuiltinModules(); // Make inherited handles noninheritable. - if (!(flags & ProcessFlags::kEnableStdioInheritance)) + if (!(flags & ProcessInitializationFlags::kEnableStdioInheritance) && + !(flags & ProcessInitializationFlags::kNoStdioInitialization)) { uv_disable_stdio_inheritance(); + } // Cache the original command line to be // used in diagnostic reports. @@ -882,7 +913,7 @@ int InitializeNodeWithArgs(std::vector* argv, HandleEnvOptions(per_process::cli_options->per_isolate->per_env); #if !defined(NODE_WITHOUT_NODE_OPTIONS) - if (!(flags & ProcessFlags::kDisableNodeOptionsEnv)) { + if (!(flags & ProcessInitializationFlags::kDisableNodeOptionsEnv)) { std::string node_options; if (credentials::SafeGetenv("NODE_OPTIONS", &node_options)) { @@ -903,7 +934,7 @@ int InitializeNodeWithArgs(std::vector* argv, } #endif - if (!(flags & ProcessFlags::kDisableCLIOptions)) { + if (!(flags & ProcessInitializationFlags::kDisableCLIOptions)) { const int exit_code = ProcessGlobalArgs(argv, exec_argv, errors, @@ -916,7 +947,7 @@ int InitializeNodeWithArgs(std::vector* argv, uv_set_process_title(per_process::cli_options->title.c_str()); #if defined(NODE_HAVE_I18N_SUPPORT) - if (!(flags & ProcessFlags::kNoICU)) { + if (!(flags & ProcessInitializationFlags::kNoICU)) { // If the parameter isn't given, use the env variable. if (per_process::cli_options->icu_data_dir.empty()) credentials::SafeGetenv("NODE_ICU_DATA", @@ -966,82 +997,78 @@ int InitializeNodeWithArgs(std::vector* argv, return 0; } -InitializationResult InitializeOncePerProcess(int argc, char** argv) { - return InitializeOncePerProcess(argc, argv, kDefaultInitialization); -} +std::unique_ptr InitializeOncePerProcess( + const std::vector& args, + ProcessInitializationFlags::Flags flags) { + auto result = std::make_unique(); + result->args_ = args; -InitializationResult InitializeOncePerProcess( - int argc, - char** argv, - InitializationSettingsFlags flags, - ProcessFlags::Flags process_flags) { - uint64_t init_flags = flags; - if (init_flags & kDefaultInitialization) { - init_flags = init_flags | kInitializeV8 | kInitOpenSSL | kRunPlatformInit; + if (!(flags & ProcessInitializationFlags::kNoParseGlobalDebugVariables)) { + // Initialized the enabled list for Debug() calls with system + // environment variables. + per_process::enabled_debug_list.Parse(); } - // Initialized the enabled list for Debug() calls with system - // environment variables. - per_process::enabled_debug_list.Parse(); - - atexit(ResetStdio); - - if (init_flags & kRunPlatformInit) - PlatformInit(); - - CHECK_GT(argc, 0); - - // Hack around with the argv pointer. Used for process.title = "blah". - argv = uv_setup_args(argc, argv); - - InitializationResult result; - result.args = std::vector(argv, argv + argc); - std::vector errors; + PlatformInit(flags); // This needs to run *before* V8::Initialize(). { - result.exit_code = InitializeNodeWithArgs( - &(result.args), &(result.exec_args), &errors, process_flags); - for (const std::string& error : errors) - fprintf(stderr, "%s: %s\n", result.args.at(0).c_str(), error.c_str()); - if (result.exit_code != 0) { - result.early_return = true; + result->exit_code_ = InitializeNodeWithArgs( + &result->args_, &result->exec_args_, &result->errors_, flags); + if (result->exit_code() != 0) { + result->early_return_ = true; return result; } } - if (per_process::cli_options->use_largepages == "on" || - per_process::cli_options->use_largepages == "silent") { - int result = node::MapStaticCodeToLargePages(); - if (per_process::cli_options->use_largepages == "on" && result != 0) { - fprintf(stderr, "%s\n", node::LargePagesError(result)); + if (!(flags & ProcessInitializationFlags::kNoUseLargePages) && + (per_process::cli_options->use_largepages == "on" || + per_process::cli_options->use_largepages == "silent")) { + int lp_result = node::MapStaticCodeToLargePages(); + if (per_process::cli_options->use_largepages == "on" && lp_result != 0) { + result->errors_.emplace_back(node::LargePagesError(lp_result)); } } - if (per_process::cli_options->print_version) { - printf("%s\n", NODE_VERSION); - result.exit_code = 0; - result.early_return = true; - return result; - } + if (!(flags & ProcessInitializationFlags::kNoPrintHelpOrVersionOutput)) { + if (per_process::cli_options->print_version) { + printf("%s\n", NODE_VERSION); + result->exit_code_ = 0; + result->early_return_ = true; + return result; + } - if (per_process::cli_options->print_bash_completion) { - std::string completion = options_parser::GetBashCompletion(); - printf("%s\n", completion.c_str()); - result.exit_code = 0; - result.early_return = true; - return result; - } + if (per_process::cli_options->print_bash_completion) { + std::string completion = options_parser::GetBashCompletion(); + printf("%s\n", completion.c_str()); + result->exit_code_ = 0; + result->early_return_ = true; + return result; + } - if (per_process::cli_options->print_v8_help) { - V8::SetFlagsFromString("--help", static_cast(6)); - result.exit_code = 0; - result.early_return = true; - return result; + if (per_process::cli_options->print_v8_help) { + V8::SetFlagsFromString("--help", static_cast(6)); + result->exit_code_ = 0; + result->early_return_ = true; + return result; + } } - if (init_flags & kInitOpenSSL) { + if (!(flags & ProcessInitializationFlags::kNoInitOpenSSL)) { #if HAVE_OPENSSL && !defined(OPENSSL_IS_BORINGSSL) + auto GetOpenSSLErrorString = []() -> std::string { + std::string ret; + ERR_print_errors_cb( + [](const char* str, size_t len, void* opaque) { + std::string* ret = static_cast(opaque); + ret->append(str, len); + ret->append("\n"); + return 0; + }, + static_cast(&ret)); + return ret; + }; + { std::string extra_ca_certs; if (credentials::SafeGetenv("NODE_EXTRA_CA_CERTS", &extra_ca_certs)) @@ -1096,10 +1123,12 @@ InitializationResult InitializeOncePerProcess( OPENSSL_INIT_free(settings); if (ERR_peek_error() != 0) { - result.exit_code = ERR_GET_REASON(ERR_peek_error()); - result.early_return = true; - fprintf(stderr, "OpenSSL configuration error:\n"); - ERR_print_errors_fp(stderr); + // XXX: ERR_GET_REASON does not return something that is + // useful as an exit code at all. + result->exit_code_ = ERR_GET_REASON(ERR_peek_error()); + result->early_return_ = true; + result->errors_.emplace_back("OpenSSL configuration error:\n" + + GetOpenSSLErrorString()); return result; } #else // OPENSSL_VERSION_MAJOR < 3 @@ -1108,10 +1137,13 @@ InitializationResult InitializeOncePerProcess( } #endif if (!crypto::ProcessFipsOptions()) { - result.exit_code = ERR_GET_REASON(ERR_peek_error()); - result.early_return = true; - fprintf(stderr, "OpenSSL error when trying to enable FIPS:\n"); - ERR_print_errors_fp(stderr); + // XXX: ERR_GET_REASON does not return something that is + // useful as an exit code at all. + result->exit_code_ = ERR_GET_REASON(ERR_peek_error()); + result->early_return_ = true; + result->errors_.emplace_back( + "OpenSSL error when trying to enable FIPS:\n" + + GetOpenSSLErrorString()); return result; } @@ -1129,9 +1161,13 @@ InitializationResult InitializeOncePerProcess( #endif // HAVE_OPENSSL && !defined(OPENSSL_IS_BORINGSSL) } - per_process::v8_platform.Initialize( - static_cast(per_process::cli_options->v8_thread_pool_size)); - if (init_flags & kInitializeV8) { + if (!(flags & ProcessInitializationFlags::kNoInitializeNodeV8Platform)) { + per_process::v8_platform.Initialize( + static_cast(per_process::cli_options->v8_thread_pool_size)); + result->platform_ = per_process::v8_platform.Platform(); + } + + if (!(flags & ProcessInitializationFlags::kNoInitializeV8)) { V8::Initialize(); } @@ -1142,30 +1178,47 @@ InitializationResult InitializeOncePerProcess( } void TearDownOncePerProcess() { + const uint64_t flags = init_process_flags.load(); + ResetStdio(); + if (!(flags & ProcessInitializationFlags::kNoDefaultSignalHandling)) { + ResetSignalHandlers(); + } + per_process::v8_initialized = false; - V8::Dispose(); + if (!(flags & ProcessInitializationFlags::kNoInitializeV8)) { + V8::Dispose(); + } #if NODE_USE_V8_WASM_TRAP_HANDLER && defined(_WIN32) - RemoveVectoredExceptionHandler(per_process::old_vectored_exception_handler); + if (!(flags & ProcessInitializationFlags::kNoDefaultSignalHandling)) { + RemoveVectoredExceptionHandler(per_process::old_vectored_exception_handler); + } #endif - // uv_run cannot be called from the time before the beforeExit callback - // runs until the program exits unless the event loop has any referenced - // handles after beforeExit terminates. This prevents unrefed timers - // that happen to terminate during shutdown from being run unsafely. - // Since uv_run cannot be called, uv_async handles held by the platform - // will never be fully cleaned up. - per_process::v8_platform.Dispose(); + if (!(flags & ProcessInitializationFlags::kNoInitializeNodeV8Platform)) { + V8::DisposePlatform(); + // uv_run cannot be called from the time before the beforeExit callback + // runs until the program exits unless the event loop has any referenced + // handles after beforeExit terminates. This prevents unrefed timers + // that happen to terminate during shutdown from being run unsafely. + // Since uv_run cannot be called, uv_async handles held by the platform + // will never be fully cleaned up. + per_process::v8_platform.Dispose(); + } } +InitializationResult::~InitializationResult() {} +InitializationResultImpl::~InitializationResultImpl() {} + int GenerateAndWriteSnapshotData(const SnapshotData** snapshot_data_ptr, - InitializationResult* result) { + const InitializationResult* result) { + int exit_code = result->exit_code(); // nullptr indicates there's no snapshot data. DCHECK_NULL(*snapshot_data_ptr); // node:embedded_snapshot_main indicates that we are using the // embedded snapshot and we are not supposed to clean it up. - if (result->args[1] == "node:embedded_snapshot_main") { + if (result->args()[1] == "node:embedded_snapshot_main") { *snapshot_data_ptr = SnapshotBuilder::GetEmbeddedSnapshotData(); if (*snapshot_data_ptr == nullptr) { // The Node.js binary is built without embedded snapshot @@ -1173,19 +1226,19 @@ int GenerateAndWriteSnapshotData(const SnapshotData** snapshot_data_ptr, "node:embedded_snapshot_main was specified as snapshot " "entry point but Node.js was built without embedded " "snapshot.\n"); - result->exit_code = 1; - return result->exit_code; + exit_code = 1; + return exit_code; } } else { // Otherwise, load and run the specified main script. std::unique_ptr generated_data = std::make_unique(); - result->exit_code = node::SnapshotBuilder::Generate( - generated_data.get(), result->args, result->exec_args); - if (result->exit_code == 0) { + exit_code = node::SnapshotBuilder::Generate( + generated_data.get(), result->args(), result->exec_args()); + if (exit_code == 0) { *snapshot_data_ptr = generated_data.release(); } else { - return result->exit_code; + return exit_code; } } @@ -1206,13 +1259,14 @@ int GenerateAndWriteSnapshotData(const SnapshotData** snapshot_data_ptr, fprintf(stderr, "Cannot open %s for writing a snapshot.\n", snapshot_blob_path.c_str()); - result->exit_code = 1; + exit_code = 1; } - return result->exit_code; + return exit_code; } int LoadSnapshotDataAndRun(const SnapshotData** snapshot_data_ptr, - InitializationResult* result) { + const InitializationResult* result) { + int exit_code = result->exit_code(); // nullptr indicates there's no snapshot data. DCHECK_NULL(*snapshot_data_ptr); // --snapshot-blob indicates that we are reading a customized snapshot. @@ -1221,8 +1275,8 @@ int LoadSnapshotDataAndRun(const SnapshotData** snapshot_data_ptr, FILE* fp = fopen(filename.c_str(), "rb"); if (fp == nullptr) { fprintf(stderr, "Cannot open %s", filename.c_str()); - result->exit_code = 1; - return result->exit_code; + exit_code = 1; + return exit_code; } std::unique_ptr read_data = std::make_unique(); SnapshotData::FromBlob(read_data.get(), fp); @@ -1240,19 +1294,28 @@ int LoadSnapshotDataAndRun(const SnapshotData** snapshot_data_ptr, NodeMainInstance main_instance(*snapshot_data_ptr, uv_default_loop(), per_process::v8_platform.Platform(), - result->args, - result->exec_args); - result->exit_code = main_instance.Run(); - return result->exit_code; + result->args(), + result->exec_args()); + exit_code = main_instance.Run(); + return exit_code; } int Start(int argc, char** argv) { - InitializationResult result = InitializeOncePerProcess(argc, argv); - if (result.early_return) { - return result.exit_code; + CHECK_GT(argc, 0); + + // Hack around with the argv pointer. Used for process.title = "blah". + argv = uv_setup_args(argc, argv); + + std::unique_ptr result = + InitializeOncePerProcess(std::vector(argv, argv + argc)); + for (const std::string& error : result->errors()) { + FPrintF(stderr, "%s: %s\n", result->args().at(0), error); + } + if (result->early_return()) { + return result->exit_code(); } - DCHECK_EQ(result.exit_code, 0); + DCHECK_EQ(result->exit_code(), 0); const SnapshotData* snapshot_data = nullptr; auto cleanup_process = OnScopeLeave([&]() { @@ -1268,17 +1331,17 @@ int Start(int argc, char** argv) { // --build-snapshot indicates that we are in snapshot building mode. if (per_process::cli_options->build_snapshot) { - if (result.args.size() < 2) { + if (result->args().size() < 2) { fprintf(stderr, "--build-snapshot must be used with an entry point script.\n" "Usage: node --build-snapshot /path/to/entry.js\n"); return 9; } - return GenerateAndWriteSnapshotData(&snapshot_data, &result); + return GenerateAndWriteSnapshotData(&snapshot_data, result.get()); } // Without --build-snapshot, we are in snapshot loading mode. - return LoadSnapshotDataAndRun(&snapshot_data, &result); + return LoadSnapshotDataAndRun(&snapshot_data, result.get()); } int Stop(Environment* env) { diff --git a/src/node.h b/src/node.h index e8c72092c72b54..845dbb9ef6ed37 100644 --- a/src/node.h +++ b/src/node.h @@ -224,11 +224,14 @@ namespace node { class IsolateData; class Environment; +class MultiIsolatePlatform; +class InitializationResultImpl; namespace ProcessFlags { enum Flags : uint64_t { kNoFlags = 0, // Enable stdio inheritance, which is disabled by default. + // This flag is also implied by kNoStdioInitialization. kEnableStdioInheritance = 1 << 0, // Disable reading the NODE_OPTIONS environment variable. kDisableNodeOptionsEnv = 1 << 1, @@ -236,8 +239,67 @@ enum Flags : uint64_t { kDisableCLIOptions = 1 << 2, // Do not initialize ICU. kNoICU = 1 << 3, + // Do not modify stdio file descriptor or TTY state. + kNoStdioInitialization = 1 << 4, + // Do not register Node.js-specific signal handlers + // and reset other signal handlers to default state. + kNoDefaultSignalHandling = 1 << 5, + // Do not perform V8 initialization. + kNoInitializeV8 = 1 << 6, + // Do not initialize a default Node.js-provided V8 platform instance. + kNoInitializeNodeV8Platform = 1 << 7, + // Do not initialize OpenSSL config. + kNoInitOpenSSL = 1 << 8, + // Do not initialize Node.js debugging based on environment variables. + kNoParseGlobalDebugVariables = 1 << 9, + // Do not adjust OS resource limits for this process. + kNoAdjustResourceLimits = 1 << 10, + // Do not map code segments into large pages for this process. + kNoUseLargePages = 1 << 11, + // Skip printing output for --help, --version, --v8-options. + kNoPrintHelpOrVersionOutput = 1 << 12, + + // Emulate the behavior of InitializeNodeWithArgs() when passing + // a flags argument to the InitializeOncePerProcess() replacement + // function. + kLegacyInitializeNodeWithArgsBehavior = + kNoStdioInitialization | kNoDefaultSignalHandling | kNoInitializeV8 | + kNoInitializeNodeV8Platform | kNoInitOpenSSL | + kNoParseGlobalDebugVariables | kNoAdjustResourceLimits | + kNoUseLargePages | kNoPrintHelpOrVersionOutput, }; } // namespace ProcessFlags +// TODO(addaleax): Make this the canonical name, as it is more descriptive. +namespace ProcessInitializationFlags = ProcessFlags; + +class NODE_EXTERN InitializationResult { + public: + virtual ~InitializationResult(); + + // Returns a suggested process exit code. + virtual int exit_code() const = 0; + + // Returns 'true' if initialization was aborted early due to errors. + virtual bool early_return() const = 0; + + // Returns the parsed list of non-Node.js arguments. + virtual const std::vector& args() const = 0; + + // Returns the parsed list of Node.js arguments. + virtual const std::vector& exec_args() const = 0; + + // Returns an array of errors. Note that these may be warnings + // whose existence does not imply a non-zero exit code. + virtual const std::vector& errors() const = 0; + + // If kNoInitializeNodeV8Platform was not specified, the global Node.js + // platform instance. + virtual MultiIsolatePlatform* platform() const = 0; + + private: + InitializationResult() = default; + friend class InitializationResultImpl; +}; // TODO(addaleax): Officially deprecate this and replace it with something // better suited for a public embedder API. @@ -247,20 +309,44 @@ NODE_EXTERN int Start(int argc, char* argv[]); // in the loop and / or actively executing JavaScript code). NODE_EXTERN int Stop(Environment* env); +// This runs a subset of the initialization performed by +// InitializeOncePerProcess(), which supersedes this function. +// The subset is roughly equivalent to the one given by +// `ProcessInitializationFlags::kLegacyInitializeNodeWithArgsBehavior`. +NODE_DEPRECATED("Use InitializeOncePerProcess() instead", + NODE_EXTERN int InitializeNodeWithArgs( + std::vector* argv, + std::vector* exec_argv, + std::vector* errors, + ProcessInitializationFlags::Flags flags)); +NODE_DEPRECATED("Use InitializeOncePerProcess() instead", + NODE_EXTERN int InitializeNodeWithArgs( + std::vector* argv, + std::vector* exec_argv, + std::vector* errors)); + // Set up per-process state needed to run Node.js. This will consume arguments -// from argv, fill exec_argv, and possibly add errors resulting from parsing -// the arguments to `errors`. The return value is a suggested exit code for the -// program; If it is 0, then initializing Node.js succeeded. -NODE_EXTERN int InitializeNodeWithArgs( - std::vector* argv, - std::vector* exec_argv, - std::vector* errors); -// TODO(zcbenz): Turn above overloaded version into below's default argument. -NODE_EXTERN int InitializeNodeWithArgs( - std::vector* argv, - std::vector* exec_argv, - std::vector* errors, - ProcessFlags::Flags flags); +// from args, and return information about the initialization success, +// including the arguments split into argv/exec_argv, a list of potential +// errors encountered during initialization, and a potential suggested +// exit code. +NODE_EXTERN std::unique_ptr InitializeOncePerProcess( + const std::vector& args, + ProcessInitializationFlags::Flags flags = + ProcessInitializationFlags::kNoFlags); +// Undoes the initialization performed by InitializeOncePerProcess(), +// where cleanup is necessary. +NODE_EXTERN void TearDownOncePerProcess(); +// Convenience overload for specifying multiple flags without having +// to worry about casts. +inline std::unique_ptr InitializeOncePerProcess( + const std::vector& args, + std::initializer_list list) { + uint64_t flags_accum = ProcessInitializationFlags::kNoFlags; + for (const auto flag : list) flags_accum |= static_cast(flag); + return InitializeOncePerProcess( + args, static_cast(flags_accum)); +} enum OptionEnvvarSettings { kAllowedInEnvironment, diff --git a/src/node_credentials.cc b/src/node_credentials.cc index 458fa13217e329..ccc77495449b0f 100644 --- a/src/node_credentials.cc +++ b/src/node_credentials.cc @@ -13,6 +13,7 @@ #endif #ifdef __linux__ #include +#include #include #endif // __linux__ @@ -31,9 +32,18 @@ using v8::TryCatch; using v8::Uint32; using v8::Value; -namespace per_process { -bool linux_at_secure = false; -} // namespace per_process +bool linux_at_secure() { + // This could reasonably be a static variable, but this way + // we can guarantee that this function is always usable + // and returns the correct value, e.g. even in static + // initialization code in other files. +#ifdef __linux__ + static const bool value = getauxval(AT_SECURE); + return value; +#else + return false; +#endif +} namespace credentials { @@ -70,11 +80,10 @@ bool SafeGetenv(const char* key, v8::Isolate* isolate) { #if !defined(__CloudABI__) && !defined(_WIN32) #if defined(__linux__) - if ((!HasOnly(CAP_NET_BIND_SERVICE) && per_process::linux_at_secure) || + if ((!HasOnly(CAP_NET_BIND_SERVICE) && linux_at_secure()) || getuid() != geteuid() || getgid() != getegid()) #else - if (per_process::linux_at_secure || getuid() != geteuid() || - getgid() != getegid()) + if (linux_at_secure() || getuid() != geteuid() || getgid() != getegid()) #endif goto fail; #endif diff --git a/src/node_internals.h b/src/node_internals.h index 1449d2acd327b9..f27e03aed66fed 100644 --- a/src/node_internals.h +++ b/src/node_internals.h @@ -313,29 +313,24 @@ v8::MaybeLocal ExecuteBootstrapper( std::vector>* arguments); void MarkBootstrapComplete(const v8::FunctionCallbackInfo& args); -struct InitializationResult { - int exit_code = 0; - std::vector args; - std::vector exec_args; - bool early_return = false; -}; - -enum InitializationSettingsFlags : uint64_t { - kDefaultInitialization = 1 << 0, - kInitializeV8 = 1 << 1, - kRunPlatformInit = 1 << 2, - kInitOpenSSL = 1 << 3 +class InitializationResultImpl final : public InitializationResult { + public: + ~InitializationResultImpl(); + int exit_code() const { return exit_code_; } + bool early_return() const { return early_return_; } + const std::vector& args() const { return args_; } + const std::vector& exec_args() const { return exec_args_; } + const std::vector& errors() const { return errors_; } + MultiIsolatePlatform* platform() const { return platform_; } + + int exit_code_ = 0; + std::vector args_; + std::vector exec_args_; + std::vector errors_; + bool early_return_ = false; + MultiIsolatePlatform* platform_ = nullptr; }; -// TODO(codebytere): eventually document and expose to embedders. -InitializationResult NODE_EXTERN_PRIVATE InitializeOncePerProcess(int argc, - char** argv); -InitializationResult NODE_EXTERN_PRIVATE InitializeOncePerProcess( - int argc, - char** argv, - InitializationSettingsFlags flags, - ProcessFlags::Flags process_flags = ProcessFlags::kNoFlags); -void NODE_EXTERN_PRIVATE TearDownOncePerProcess(); void SetIsolateErrorHandlers(v8::Isolate* isolate, const IsolateSettings& s); void SetIsolateMiscHandlers(v8::Isolate* isolate, const IsolateSettings& s); void SetIsolateCreateParamsForNode(v8::Isolate::CreateParams* params); @@ -426,6 +421,8 @@ namespace performance { std::ostream& operator<<(std::ostream& output, const PerformanceState::SerializeInfo& d); } + +bool linux_at_secure(); } // namespace node #endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS diff --git a/src/node_main.cc b/src/node_main.cc index 6bac1075932587..d486bbc31c1f6b 100644 --- a/src/node_main.cc +++ b/src/node_main.cc @@ -88,42 +88,8 @@ int wmain(int argc, wchar_t* wargv[]) { } #else // UNIX -#ifdef __linux__ -#include -#endif // __linux__ -#if defined(__POSIX__) && defined(NODE_SHARED_MODE) -#include -#include -#endif - -namespace node { -namespace per_process { -extern bool linux_at_secure; -} // namespace per_process -} // namespace node int main(int argc, char* argv[]) { -#if defined(__POSIX__) && defined(NODE_SHARED_MODE) - // In node::PlatformInit(), we squash all signal handlers for non-shared lib - // build. In order to run test cases against shared lib build, we also need - // to do the same thing for shared lib build here, but only for SIGPIPE for - // now. If node::PlatformInit() is moved to here, then this section could be - // removed. - { - struct sigaction act; - memset(&act, 0, sizeof(act)); - act.sa_handler = SIG_IGN; - sigaction(SIGPIPE, &act, nullptr); - } -#endif - -#if defined(__linux__) - node::per_process::linux_at_secure = getauxval(AT_SECURE); -#endif - // Disable stdio buffering, it interacts poorly with printf() - // calls elsewhere in the program (e.g., any logging from V8.) - setvbuf(stdout, nullptr, _IONBF, 0); - setvbuf(stderr, nullptr, _IONBF, 0); return node::Start(argc, argv); } #endif diff --git a/src/node_main_instance.cc b/src/node_main_instance.cc index ab9d322a84c2be..12d51743dddaca 100644 --- a/src/node_main_instance.cc +++ b/src/node_main_instance.cc @@ -139,21 +139,6 @@ void NodeMainInstance::Run(int* exit_code, Environment* env) { *exit_code = SpinEventLoop(env).FromMaybe(1); } - ResetStdio(); - - // TODO(addaleax): Neither NODE_SHARED_MODE nor HAVE_INSPECTOR really - // make sense here. -#if HAVE_INSPECTOR && defined(__POSIX__) && !defined(NODE_SHARED_MODE) - struct sigaction act; - memset(&act, 0, sizeof(act)); - for (unsigned nr = 1; nr < kMaxSignal; nr += 1) { - if (nr == SIGKILL || nr == SIGSTOP || nr == SIGPROF) - continue; - act.sa_handler = (nr == SIGPIPE) ? SIG_IGN : SIG_DFL; - CHECK_EQ(0, sigaction(nr, &act, nullptr)); - } -#endif - #if defined(LEAK_SANITIZER) __lsan_do_leak_check(); #endif diff --git a/test/embedding/embedtest.cc b/test/embedding/embedtest.cc index ee1e2afb935438..2ad8afd42845dd 100644 --- a/test/embedding/embedtest.cc +++ b/test/embedding/embedtest.cc @@ -23,13 +23,16 @@ static int RunNodeInstance(MultiIsolatePlatform* platform, int main(int argc, char** argv) { argv = uv_setup_args(argc, argv); std::vector args(argv, argv + argc); - std::vector exec_args; - std::vector errors; - int exit_code = node::InitializeNodeWithArgs(&args, &exec_args, &errors); - for (const std::string& error : errors) + std::unique_ptr result = + node::InitializeOncePerProcess( + args, + {node::ProcessInitializationFlags::kNoInitializeV8, + node::ProcessInitializationFlags::kNoInitializeNodeV8Platform}); + + for (const std::string& error : result->errors()) fprintf(stderr, "%s: %s\n", args[0].c_str(), error.c_str()); - if (exit_code != 0) { - return exit_code; + if (result->early_return() != 0) { + return result->exit_code(); } std::unique_ptr platform = @@ -37,10 +40,13 @@ int main(int argc, char** argv) { V8::InitializePlatform(platform.get()); V8::Initialize(); - int ret = RunNodeInstance(platform.get(), args, exec_args); + int ret = + RunNodeInstance(platform.get(), result->args(), result->exec_args()); V8::Dispose(); V8::DisposePlatform(); + + node::TearDownOncePerProcess(); return ret; } diff --git a/tools/snapshot/node_mksnapshot.cc b/tools/snapshot/node_mksnapshot.cc index 15f96070a7e3a9..0b8b7a6d5e77cf 100644 --- a/tools/snapshot/node_mksnapshot.cc +++ b/tools/snapshot/node_mksnapshot.cc @@ -64,17 +64,18 @@ int BuildSnapshot(int argc, char* argv[]) { return 1; } - node::InitializationResult result = - node::InitializeOncePerProcess(argc, argv); + std::unique_ptr result = + node::InitializeOncePerProcess( + std::vector(argv, argv + argc)); - CHECK(!result.early_return); - CHECK_EQ(result.exit_code, 0); + CHECK(!result->early_return()); + CHECK_EQ(result->exit_code(), 0); std::string out_path; if (node::per_process::cli_options->build_snapshot) { - out_path = result.args[2]; + out_path = result->args()[2]; } else { - out_path = result.args[1]; + out_path = result->args()[1]; } std::ofstream out(out_path, std::ios::out | std::ios::binary); @@ -85,8 +86,8 @@ int BuildSnapshot(int argc, char* argv[]) { int exit_code = 0; { - exit_code = - node::SnapshotBuilder::Generate(out, result.args, result.exec_args); + exit_code = node::SnapshotBuilder::Generate( + out, result->args(), result->exec_args()); if (exit_code == 0) { if (!out) { std::cerr << "Failed to write " << out_path << "\n";