diff --git a/docs/api/command-line-switches.md b/docs/api/command-line-switches.md index 314b39a8e7..725144c1a4 100644 --- a/docs/api/command-line-switches.md +++ b/docs/api/command-line-switches.md @@ -193,6 +193,11 @@ Disables the Chromium [sandbox](https://www.chromium.org/developers/design-docum Forces renderer process and Chromium helper processes to run un-sandboxed. Should only be used for testing. +### --no-stdio-init + +Disable stdio initialization during node initialization. +Used to avoid node initialization crash when the nul device is disabled on Windows platform. + ### --proxy-bypass-list=`hosts` Instructs Electron to bypass the proxy server for the given semi-colon-separated diff --git a/shell/app/node_main.cc b/shell/app/node_main.cc index ddc61c0257..7cd0a71eea 100644 --- a/shell/app/node_main.cc +++ b/shell/app/node_main.cc @@ -35,6 +35,8 @@ #include "shell/common/node_bindings.h" #include "shell/common/node_includes.h" #include "shell/common/node_util.h" +#include "shell/common/options_switches.h" +#include "shell/common/platform_util.h" #if BUILDFLAG(IS_WIN) #include "chrome/child/v8_crashpad_support_win.h" @@ -153,9 +155,10 @@ int NodeMain() { v8_crashpad_support::SetUp(); #endif + auto* command_line = base::CommandLine::ForCurrentProcess(); + #if BUILDFLAG(IS_LINUX) int pid = -1; - auto* command_line = base::CommandLine::ForCurrentProcess(); std::optional fd_string = os_env->GetVar("CRASHDUMP_SIGNAL_FD"); std::optional pid_string = os_env->GetVar("CRASHPAD_HANDLER_PID"); @@ -189,14 +192,32 @@ int NodeMain() { NodeBindings::RegisterBuiltinBindings(); // Parse Node.js cli flags and strip out disallowed options. - const std::vector args = ElectronCommandLine::AsUtf8(); + std::vector args = ElectronCommandLine::AsUtf8(); ExitIfContainsDisallowedFlags(args); + uint64_t process_flags = + node::ProcessInitializationFlags::kNoInitializeV8 | + node::ProcessInitializationFlags::kNoInitializeNodeV8Platform; + + if (command_line->HasSwitch(switches::kNoStdioInit)) { + process_flags |= node::ProcessInitializationFlags::kNoStdioInitialization; + // remove the option to avoid node error "bad option: --no-stdio-init" + std::string option = std::string("--") + switches::kNoStdioInit; + std::erase(args, option); + } else { +#if BUILDFLAG(IS_WIN) + if (!platform_util::IsNulDeviceEnabled()) { + LOG(FATAL) << "Unable to open nul device needed for initialization," + "aborting startup. As a workaround, try starting with --" + << switches::kNoStdioInit; + } +#endif + } + std::shared_ptr result = node::InitializeOncePerProcess( - args, - {node::ProcessInitializationFlags::kNoInitializeV8, - node::ProcessInitializationFlags::kNoInitializeNodeV8Platform}); + args, static_cast( + process_flags)); for (const std::string& error : result->errors()) std::cerr << args[0] << ": " << error << '\n'; diff --git a/shell/browser/api/electron_api_utility_process.cc b/shell/browser/api/electron_api_utility_process.cc index 08bd1c95d7..2fa5bb1750 100644 --- a/shell/browser/api/electron_api_utility_process.cc +++ b/shell/browser/api/electron_api_utility_process.cc @@ -132,6 +132,7 @@ UtilityProcessWrapper::UtilityProcessWrapper( OPEN_EXISTING, 0, nullptr); if (handle == INVALID_HANDLE_VALUE) { PLOG(ERROR) << "Failed to create null handle"; + Emit("error", "Failed to create null handle for ignoring stdio"); return; } if (io_handle == IOHandle::STDOUT) { diff --git a/shell/browser/electron_browser_client.cc b/shell/browser/electron_browser_client.cc index ea0db0c309..1d51f7b7ab 100644 --- a/shell/browser/electron_browser_client.cc +++ b/shell/browser/electron_browser_client.cc @@ -549,7 +549,7 @@ void ElectronBrowserClient::AppendExtraCommandLineSwitches( if (process_type == ::switches::kUtilityProcess || process_type == ::switches::kRendererProcess) { // Copy following switches to child process. - static constexpr std::array kCommonSwitchNames = { + static constexpr std::array kCommonSwitchNames = { switches::kStandardSchemes.c_str(), switches::kEnableSandbox.c_str(), switches::kSecureSchemes.c_str(), @@ -558,6 +558,7 @@ void ElectronBrowserClient::AppendExtraCommandLineSwitches( switches::kFetchSchemes.c_str(), switches::kServiceWorkerSchemes.c_str(), switches::kStreamingSchemes.c_str(), + switches::kNoStdioInit.c_str(), switches::kCodeCacheSchemes.c_str()}; command_line->CopySwitchesFrom(*base::CommandLine::ForCurrentProcess(), kCommonSwitchNames); diff --git a/shell/common/node_bindings.cc b/shell/common/node_bindings.cc index bde0c03105..c0dd635839 100644 --- a/shell/common/node_bindings.cc +++ b/shell/common/node_bindings.cc @@ -40,6 +40,8 @@ #include "shell/common/mac/main_application_bundle.h" #include "shell/common/node_includes.h" #include "shell/common/node_util.h" +#include "shell/common/options_switches.h" +#include "shell/common/platform_util.h" #include "shell/common/process_util.h" #include "shell/common/world_ids.h" #include "third_party/blink/public/common/web_preferences/web_preferences.h" @@ -672,6 +674,19 @@ void NodeBindings::Initialize(v8::Isolate* const isolate, if (!fuses::IsNodeOptionsEnabled()) process_flags |= node::ProcessInitializationFlags::kDisableNodeOptionsEnv; + base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); + if (command_line->HasSwitch(switches::kNoStdioInit)) { + process_flags |= node::ProcessInitializationFlags::kNoStdioInitialization; + } else { +#if BUILDFLAG(IS_WIN) + if (!platform_util::IsNulDeviceEnabled()) { + LOG(FATAL) << "Unable to open nul device needed for initialization," + "aborting startup. As a workaround, try starting with --" + << switches::kNoStdioInit; + } +#endif + } + std::shared_ptr result = node::InitializeOncePerProcess( args, diff --git a/shell/common/options_switches.h b/shell/common/options_switches.h index 7b0cb2bb96..6cffc099c6 100644 --- a/shell/common/options_switches.h +++ b/shell/common/options_switches.h @@ -309,6 +309,10 @@ inline constexpr base::cstring_view kDisableNTLMv2 = "disable-ntlm-v2"; inline constexpr base::cstring_view kServiceWorkerPreload = "service-worker-preload"; +// If set, flag node::ProcessInitializationFlags::kNoStdioInitialization would +// be set for node initialization. +inline constexpr base::cstring_view kNoStdioInit = "no-stdio-init"; + } // namespace switches } // namespace electron diff --git a/shell/common/platform_util.h b/shell/common/platform_util.h index c20376e5c3..ccca2f778b 100644 --- a/shell/common/platform_util.h +++ b/shell/common/platform_util.h @@ -47,6 +47,9 @@ void Beep(); #if BUILDFLAG(IS_WIN) // SHGetFolderPath calls not covered by Chromium bool GetFolderPath(int key, base::FilePath* result); + +// Check if nul device can be used. +bool IsNulDeviceEnabled(); #endif #if BUILDFLAG(IS_MAC) diff --git a/shell/common/platform_util_win.cc b/shell/common/platform_util_win.cc index a4333d7f1b..b20b66db9c 100644 --- a/shell/common/platform_util_win.cc +++ b/shell/common/platform_util_win.cc @@ -12,6 +12,8 @@ #include #include #include +#include +#include #include #include #include @@ -450,4 +452,15 @@ void Beep() { MessageBeep(MB_OK); } +bool IsNulDeviceEnabled() { + bool ret = true; + int fd = _open("nul", _O_RDWR); + if (fd < 0) { + ret = false; + } else { + _close(fd); + } + return ret; +} + } // namespace platform_util