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";