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
Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com>
Reviewed-By: Michael Dawson <midawson@redhat.com>
This commit is contained in:
Anna Henningsen 2022-08-05 21:46:08 +02:00 committed by GitHub
parent 4c5b96b376
commit b697160256
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 441 additions and 316 deletions

View File

@ -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<std::string> args(argv, argv + argc);
std::vector<std::string> exec_args;
std::vector<std::string> 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<node::InitializationResult> 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;
}
```

View File

@ -567,37 +567,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));
@ -611,94 +582,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<uint64_t> 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;
do
err = tcgetattr(fd, &s.termios);
while (err == -1 && errno == EINTR); // NOLINT
CHECK_EQ(err, 0);
if (!(flags & ProcessInitializationFlags::kNoStdioInitialization)) {
atexit(ResetStdio);
}
RegisterSignalHandler(SIGINT, SignalExit, true);
RegisterSignalHandler(SIGTERM, SignalExit, true);
#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);
}
}
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);
}
#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);
}
#endif // defined(_WIN32)
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;
{
constexpr ULONG first = TRUE;
per_process::old_vectored_exception_handler =
AddVectoredExceptionHandler(first, TrapWebAssemblyOrContinue);
}
do {
lim.rlim_cur = min + (max - min) / 2;
if (setrlimit(RLIMIT_NOFILE, &lim)) {
max = lim.rlim_cur;
} else {
min = lim.rlim_cur;
#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);
}
#endif // defined(_WIN32)
V8::EnableWebAssemblyTrapHandler(false);
#endif // NODE_USE_V8_WASM_TRAP_HANDLER
}
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<HANDLE>(_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<HANDLE>(_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) {
@ -834,10 +861,12 @@ int ProcessGlobalArgs(std::vector<std::string>* 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<std::string>* argv,
std::vector<std::string>* exec_argv,
std::vector<std::string>* errors,
ProcessFlags::Flags flags) {
ProcessInitializationFlags::Flags flags) {
// Make sure InitializeNodeWithArgs() is called only once.
CHECK(!init_called.exchange(true));
@ -848,8 +877,10 @@ int InitializeNodeWithArgs(std::vector<std::string>* 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.
@ -865,7 +896,7 @@ int InitializeNodeWithArgs(std::vector<std::string>* 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)) {
@ -886,7 +917,7 @@ int InitializeNodeWithArgs(std::vector<std::string>* argv,
}
#endif
if (!(flags & ProcessFlags::kDisableCLIOptions)) {
if (!(flags & ProcessInitializationFlags::kDisableCLIOptions)) {
const int exit_code = ProcessGlobalArgs(argv,
exec_argv,
errors,
@ -899,7 +930,7 @@ int InitializeNodeWithArgs(std::vector<std::string>* 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",
@ -949,82 +980,78 @@ int InitializeNodeWithArgs(std::vector<std::string>* argv,
return 0;
}
InitializationResult InitializeOncePerProcess(int argc, char** argv) {
return InitializeOncePerProcess(argc, argv, kDefaultInitialization);
}
std::unique_ptr<InitializationResult> InitializeOncePerProcess(
const std::vector<std::string>& args,
ProcessInitializationFlags::Flags flags) {
auto result = std::make_unique<InitializationResultImpl>();
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<std::string>(argv, argv + argc);
std::vector<std::string> 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_v8_help) {
V8::SetFlagsFromString("--help", static_cast<size_t>(6));
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<size_t>(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<std::string*>(opaque);
ret->append(str, len);
ret->append("\n");
return 0;
},
static_cast<void*>(&ret));
return ret;
};
{
std::string extra_ca_certs;
if (credentials::SafeGetenv("NODE_EXTRA_CA_CERTS", &extra_ca_certs))
@ -1080,10 +1107,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
@ -1091,22 +1120,30 @@ InitializationResult InitializeOncePerProcess(
OPENSSL_init();
}
#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);
if (!crypto::ProcessFipsOptions()) {
// 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;
}
// V8 on Windows doesn't have a good source of entropy. Seed it from
// OpenSSL's pool.
V8::SetEntropySource(crypto::EntropySource);
#endif // HAVE_OPENSSL && !defined(OPENSSL_IS_BORINGSSL)
}
// V8 on Windows doesn't have a good source of entropy. Seed it from
// OpenSSL's pool.
V8::SetEntropySource(crypto::EntropySource);
#endif // HAVE_OPENSSL && !defined(OPENSSL_IS_BORINGSSL)
}
per_process::v8_platform.Initialize(
static_cast<int>(per_process::cli_options->v8_thread_pool_size));
if (init_flags & kInitializeV8) {
if (!(flags & ProcessInitializationFlags::kNoInitializeNodeV8Platform)) {
per_process::v8_platform.Initialize(
static_cast<int>(per_process::cli_options->v8_thread_pool_size));
result->platform_ = per_process::v8_platform.Platform();
}
if (!(flags & ProcessInitializationFlags::kNoInitializeV8)) {
V8::Initialize();
}
@ -1117,30 +1154,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
@ -1148,19 +1202,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<SnapshotData> generated_data =
std::make_unique<SnapshotData>();
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;
}
}
@ -1181,13 +1235,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.
@ -1196,8 +1251,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<SnapshotData> read_data = std::make_unique<SnapshotData>();
SnapshotData::FromBlob(read_data.get(), fp);
@ -1215,19 +1270,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<InitializationResult> result =
InitializeOncePerProcess(std::vector<std::string>(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([&]() {
@ -1243,17 +1307,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) {

View File

@ -223,11 +223,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,
@ -235,8 +238,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<std::string>& args() const = 0;
// Returns the parsed list of Node.js arguments.
virtual const std::vector<std::string>& 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<std::string>& 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.
@ -250,11 +312,40 @@ NODE_EXTERN int Stop(Environment* env);
// 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<std::string>* argv,
std::vector<std::string>* exec_argv,
std::vector<std::string>* errors,
ProcessFlags::Flags flags = ProcessFlags::kNoFlags);
// 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<std::string>* argv,
std::vector<std::string>* exec_argv,
std::vector<std::string>* errors,
ProcessInitializationFlags::Flags flags =
ProcessInitializationFlags::kNoFlags));
// Set up per-process state needed to run Node.js. This will consume arguments
// 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<InitializationResult> InitializeOncePerProcess(
const std::vector<std::string>& 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<InitializationResult> InitializeOncePerProcess(
const std::vector<std::string>& args,
std::initializer_list<ProcessInitializationFlags::Flags> list) {
uint64_t flags_accum = ProcessInitializationFlags::kNoFlags;
for (const auto flag : list) flags_accum |= static_cast<uint64_t>(flag);
return InitializeOncePerProcess(
args, static_cast<ProcessInitializationFlags::Flags>(flags_accum));
}
enum OptionEnvvarSettings {
kAllowedInEnvironment,

View File

@ -13,6 +13,7 @@
#endif
#ifdef __linux__
#include <linux/capability.h>
#include <sys/auxv.h>
#include <sys/syscall.h>
#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

View File

@ -311,29 +311,24 @@ v8::MaybeLocal<v8::Value> ExecuteBootstrapper(
std::vector<v8::Local<v8::Value>>* arguments);
void MarkBootstrapComplete(const v8::FunctionCallbackInfo<v8::Value>& args);
struct InitializationResult {
int exit_code = 0;
std::vector<std::string> args;
std::vector<std::string> exec_args;
bool early_return = false;
class InitializationResultImpl final : public InitializationResult {
public:
~InitializationResultImpl();
int exit_code() const { return exit_code_; }
bool early_return() const { return early_return_; }
const std::vector<std::string>& args() const { return args_; }
const std::vector<std::string>& exec_args() const { return exec_args_; }
const std::vector<std::string>& errors() const { return errors_; }
MultiIsolatePlatform* platform() const { return platform_; }
int exit_code_ = 0;
std::vector<std::string> args_;
std::vector<std::string> exec_args_;
std::vector<std::string> errors_;
bool early_return_ = false;
MultiIsolatePlatform* platform_ = nullptr;
};
enum InitializationSettingsFlags : uint64_t {
kDefaultInitialization = 1 << 0,
kInitializeV8 = 1 << 1,
kRunPlatformInit = 1 << 2,
kInitOpenSSL = 1 << 3
};
// 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);
@ -424,6 +419,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

View File

@ -88,42 +88,8 @@ int wmain(int argc, wchar_t* wargv[]) {
}
#else
// UNIX
#ifdef __linux__
#include <sys/auxv.h>
#endif // __linux__
#if defined(__POSIX__) && defined(NODE_SHARED_MODE)
#include <string.h>
#include <signal.h>
#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

View File

@ -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

View File

@ -23,13 +23,16 @@ static int RunNodeInstance(MultiIsolatePlatform* platform,
int main(int argc, char** argv) {
argv = uv_setup_args(argc, argv);
std::vector<std::string> args(argv, argv + argc);
std::vector<std::string> exec_args;
std::vector<std::string> errors;
int exit_code = node::InitializeNodeWithArgs(&args, &exec_args, &errors);
for (const std::string& error : errors)
std::unique_ptr<node::InitializationResult> 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<MultiIsolatePlatform> 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;
}

View File

@ -64,17 +64,18 @@ int BuildSnapshot(int argc, char* argv[]) {
return 1;
}
node::InitializationResult result =
node::InitializeOncePerProcess(argc, argv);
std::unique_ptr<node::InitializationResult> result =
node::InitializeOncePerProcess(
std::vector<std::string>(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";