From 7b3ba739bf4b9452051369b431980f016f5967ec Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Wed, 1 Jun 2016 16:26:08 +0900 Subject: [PATCH 01/16] Import chrome/browser/mac/relauncher.{cc,h} --- chromium_src/chrome/browser/mac/relauncher.cc | 381 ++++++++++++++++++ chromium_src/chrome/browser/mac/relauncher.h | 77 ++++ filenames.gypi | 2 + 3 files changed, 460 insertions(+) create mode 100644 chromium_src/chrome/browser/mac/relauncher.cc create mode 100644 chromium_src/chrome/browser/mac/relauncher.h diff --git a/chromium_src/chrome/browser/mac/relauncher.cc b/chromium_src/chrome/browser/mac/relauncher.cc new file mode 100644 index 0000000000..40ea5371bf --- /dev/null +++ b/chromium_src/chrome/browser/mac/relauncher.cc @@ -0,0 +1,381 @@ +// Copyright 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/mac/relauncher.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "base/files/file_util.h" +#include "base/files/scoped_file.h" +#include "base/logging.h" +#include "base/mac/mac_logging.h" +#include "base/mac/mac_util.h" +#include "base/mac/scoped_cftyperef.h" +#include "base/path_service.h" +#include "base/posix/eintr_wrapper.h" +#include "base/process/launch.h" +#include "base/strings/stringprintf.h" +#include "base/strings/sys_string_conversions.h" +#include "chrome/browser/mac/install_from_dmg.h" +#include "chrome/common/chrome_switches.h" +#include "content/public/common/content_paths.h" +#include "content/public/common/content_switches.h" +#include "content/public/common/main_function_params.h" + +namespace mac_relauncher { + +namespace { + +// The "magic" file descriptor that the relauncher process' write side of the +// pipe shows up on. Chosen to avoid conflicting with stdin, stdout, and +// stderr. +const int kRelauncherSyncFD = STDERR_FILENO + 1; + +// The argument separating arguments intended for the relauncher process from +// those intended for the relaunched process. "---" is chosen instead of "--" +// because CommandLine interprets "--" as meaning "end of switches", but +// for many purposes, the relauncher process' CommandLine ought to interpret +// arguments intended for the relaunched process, to get the correct settings +// for such things as logging and the user-data-dir in case it affects crash +// reporting. +const char kRelauncherArgSeparator[] = "---"; + +// When this argument is supplied to the relauncher process, it will launch +// the relaunched process without bringing it to the foreground. +const char kRelauncherBackgroundArg[] = "--background"; + +// The beginning of the "process serial number" argument that Launch Services +// sometimes inserts into command lines. A process serial number is only valid +// for a single process, so any PSN arguments will be stripped from command +// lines during relaunch to avoid confusion. +const char kPSNArg[] = "-psn_"; + +// Returns the "type" argument identifying a relauncher process +// ("--type=relauncher"). +std::string RelauncherTypeArg() { + return base::StringPrintf("--%s=%s", + switches::kProcessType, + switches::kRelauncherProcess); +} + +} // namespace + +bool RelaunchApp(const std::vector& args) { + // Use the currently-running application's helper process. The automatic + // update feature is careful to leave the currently-running version alone, + // so this is safe even if the relaunch is the result of an update having + // been applied. In fact, it's safer than using the updated version of the + // helper process, because there's no guarantee that the updated version's + // relauncher implementation will be compatible with the running version's. + base::FilePath child_path; + if (!PathService::Get(content::CHILD_PROCESS_EXE, &child_path)) { + LOG(ERROR) << "No CHILD_PROCESS_EXE"; + return false; + } + + std::vector relauncher_args; + return RelaunchAppWithHelper(child_path.value(), relauncher_args, args); +} + +bool RelaunchAppWithHelper(const std::string& helper, + const std::vector& relauncher_args, + const std::vector& args) { + std::vector relaunch_args; + relaunch_args.push_back(helper); + relaunch_args.push_back(RelauncherTypeArg()); + + // If this application isn't in the foreground, the relaunched one shouldn't + // be either. + if (!base::mac::AmIForeground()) { + relaunch_args.push_back(kRelauncherBackgroundArg); + } + + relaunch_args.insert(relaunch_args.end(), + relauncher_args.begin(), relauncher_args.end()); + + relaunch_args.push_back(kRelauncherArgSeparator); + + // When using the CommandLine interface, -psn_ may have been rewritten as + // --psn_. Look for both. + const char alt_psn_arg[] = "--psn_"; + for (size_t index = 0; index < args.size(); ++index) { + // Strip any -psn_ arguments, as they apply to a specific process. + if (args[index].compare(0, strlen(kPSNArg), kPSNArg) != 0 && + args[index].compare(0, strlen(alt_psn_arg), alt_psn_arg) != 0) { + relaunch_args.push_back(args[index]); + } + } + + int pipe_fds[2]; + if (HANDLE_EINTR(pipe(pipe_fds)) != 0) { + PLOG(ERROR) << "pipe"; + return false; + } + + // The parent process will only use pipe_read_fd as the read side of the + // pipe. It can close the write side as soon as the relauncher process has + // forked off. The relauncher process will only use pipe_write_fd as the + // write side of the pipe. In that process, the read side will be closed by + // base::LaunchApp because it won't be present in fd_map, and the write side + // will be remapped to kRelauncherSyncFD by fd_map. + base::ScopedFD pipe_read_fd(pipe_fds[0]); + base::ScopedFD pipe_write_fd(pipe_fds[1]); + + // Make sure kRelauncherSyncFD is a safe value. base::LaunchProcess will + // preserve these three FDs in forked processes, so kRelauncherSyncFD should + // not conflict with them. + static_assert(kRelauncherSyncFD != STDIN_FILENO && + kRelauncherSyncFD != STDOUT_FILENO && + kRelauncherSyncFD != STDERR_FILENO, + "kRelauncherSyncFD must not conflict with stdio fds"); + + base::FileHandleMappingVector fd_map; + fd_map.push_back(std::make_pair(pipe_write_fd.get(), kRelauncherSyncFD)); + + base::LaunchOptions options; + options.fds_to_remap = &fd_map; + if (!base::LaunchProcess(relaunch_args, options).IsValid()) { + LOG(ERROR) << "base::LaunchProcess failed"; + return false; + } + + // The relauncher process is now starting up, or has started up. The + // original parent process continues. + + pipe_write_fd.reset(); // close(pipe_fds[1]); + + // Synchronize with the relauncher process. + char read_char; + int read_result = HANDLE_EINTR(read(pipe_read_fd.get(), &read_char, 1)); + if (read_result != 1) { + if (read_result < 0) { + PLOG(ERROR) << "read"; + } else { + LOG(ERROR) << "read: unexpected result " << read_result; + } + return false; + } + + // Since a byte has been successfully read from the relauncher process, it's + // guaranteed to have set up its kqueue monitoring this process for exit. + // It's safe to exit now. + return true; +} + +namespace { + +// In the relauncher process, performs the necessary synchronization steps +// with the parent by setting up a kqueue to watch for it to exit, writing a +// byte to the pipe, and then waiting for the exit notification on the kqueue. +// If anything fails, this logs a message and returns immediately. In those +// situations, it can be assumed that something went wrong with the parent +// process and the best recovery approach is to attempt relaunch anyway. +void RelauncherSynchronizeWithParent() { + base::ScopedFD relauncher_sync_fd(kRelauncherSyncFD); + + int parent_pid = getppid(); + + // PID 1 identifies init. launchd, that is. launchd never starts the + // relauncher process directly, having this parent_pid means that the parent + // already exited and launchd "inherited" the relauncher as its child. + // There's no reason to synchronize with launchd. + if (parent_pid == 1) { + LOG(ERROR) << "unexpected parent_pid"; + return; + } + + // Set up a kqueue to monitor the parent process for exit. + base::ScopedFD kq(kqueue()); + if (!kq.is_valid()) { + PLOG(ERROR) << "kqueue"; + return; + } + + struct kevent change = { 0 }; + EV_SET(&change, parent_pid, EVFILT_PROC, EV_ADD, NOTE_EXIT, 0, NULL); + if (kevent(kq.get(), &change, 1, NULL, 0, NULL) == -1) { + PLOG(ERROR) << "kevent (add)"; + return; + } + + // Write a '\0' character to the pipe. + if (HANDLE_EINTR(write(relauncher_sync_fd.get(), "", 1)) != 1) { + PLOG(ERROR) << "write"; + return; + } + + // Up until now, the parent process was blocked in a read waiting for the + // write above to complete. The parent process is now free to exit. Wait for + // that to happen. + struct kevent event; + int events = kevent(kq.get(), NULL, 0, &event, 1, NULL); + if (events != 1) { + if (events < 0) { + PLOG(ERROR) << "kevent (monitor)"; + } else { + LOG(ERROR) << "kevent (monitor): unexpected result " << events; + } + return; + } + + if (event.filter != EVFILT_PROC || + event.fflags != NOTE_EXIT || + event.ident != static_cast(parent_pid)) { + LOG(ERROR) << "kevent (monitor): unexpected event, filter " << event.filter + << ", fflags " << event.fflags << ", ident " << event.ident; + return; + } +} + +} // namespace + +namespace internal { + +int RelauncherMain(const content::MainFunctionParams& main_parameters) { + // CommandLine rearranges the order of the arguments returned by + // main_parameters.argv(), rendering it impossible to determine which + // arguments originally came before kRelauncherArgSeparator and which came + // after. It's crucial to distinguish between these because only those + // after the separator should be given to the relaunched process; it's also + // important to not treat the path to the relaunched process as a "loose" + // argument. NXArgc and NXArgv are pointers to the original argc and argv as + // passed to main(), so use those. Access them through _NSGetArgc and + // _NSGetArgv because NXArgc and NXArgv are normally only available to a + // main executable via crt1.o and this code will run from a dylib, and + // because of http://crbug.com/139902. + const int* argcp = _NSGetArgc(); + if (!argcp) { + NOTREACHED(); + return 1; + } + int argc = *argcp; + + const char* const* const* argvp = _NSGetArgv(); + if (!argvp) { + NOTREACHED(); + return 1; + } + const char* const* argv = *argvp; + + if (argc < 4 || RelauncherTypeArg() != argv[1]) { + LOG(ERROR) << "relauncher process invoked with unexpected arguments"; + return 1; + } + + RelauncherSynchronizeWithParent(); + + // The capacity for relaunch_args is 4 less than argc, because it + // won't contain the argv[0] of the relauncher process, the + // RelauncherTypeArg() at argv[1], kRelauncherArgSeparator, or the + // executable path of the process to be launched. + base::ScopedCFTypeRef relaunch_args( + CFArrayCreateMutable(NULL, argc - 4, &kCFTypeArrayCallBacks)); + if (!relaunch_args) { + LOG(ERROR) << "CFArrayCreateMutable"; + return 1; + } + + // Figure out what to execute, what arguments to pass it, and whether to + // start it in the background. + bool background = false; + bool in_relaunch_args = false; + std::string dmg_bsd_device_name; + bool seen_relaunch_executable = false; + std::string relaunch_executable; + const std::string relauncher_arg_separator(kRelauncherArgSeparator); + const std::string relauncher_dmg_device_arg = + base::StringPrintf("--%s=", switches::kRelauncherProcessDMGDevice); + for (int argv_index = 2; argv_index < argc; ++argv_index) { + const std::string arg(argv[argv_index]); + + // Strip any -psn_ arguments, as they apply to a specific process. + if (arg.compare(0, strlen(kPSNArg), kPSNArg) == 0) { + continue; + } + + if (!in_relaunch_args) { + if (arg == relauncher_arg_separator) { + in_relaunch_args = true; + } else if (arg == kRelauncherBackgroundArg) { + background = true; + } else if (arg.compare(0, + relauncher_dmg_device_arg.size(), + relauncher_dmg_device_arg) == 0) { + dmg_bsd_device_name.assign( + arg.substr(relauncher_dmg_device_arg.size())); + } + } else { + if (!seen_relaunch_executable) { + // The first argument after kRelauncherBackgroundArg is the path to + // the executable file or .app bundle directory. The Launch Services + // interface wants this separate from the rest of the arguments. In + // the relaunched process, this path will still be visible at argv[0]. + relaunch_executable.assign(arg); + seen_relaunch_executable = true; + } else { + base::ScopedCFTypeRef arg_cf( + base::SysUTF8ToCFStringRef(arg)); + if (!arg_cf) { + LOG(ERROR) << "base::SysUTF8ToCFStringRef failed for " << arg; + return 1; + } + CFArrayAppendValue(relaunch_args, arg_cf); + } + } + } + + if (!seen_relaunch_executable) { + LOG(ERROR) << "nothing to relaunch"; + return 1; + } + + FSRef app_fsref; + if (!base::mac::FSRefFromPath(relaunch_executable, &app_fsref)) { + LOG(ERROR) << "base::mac::FSRefFromPath failed for " << relaunch_executable; + return 1; + } + + LSApplicationParameters ls_parameters = { + 0, // version + kLSLaunchDefaults | kLSLaunchAndDisplayErrors | kLSLaunchNewInstance | + (background ? kLSLaunchDontSwitch : 0), + &app_fsref, + NULL, // asyncLaunchRefCon + NULL, // environment + relaunch_args, + NULL // initialEvent + }; + + OSStatus status = LSOpenApplication(&ls_parameters, NULL); + if (status != noErr) { + OSSTATUS_LOG(ERROR, status) << "LSOpenApplication"; + return 1; + } + + // The application should have relaunched (or is in the process of + // relaunching). From this point on, only clean-up tasks should occur, and + // failures are tolerable. + + if (!dmg_bsd_device_name.empty()) { + EjectAndTrashDiskImage(dmg_bsd_device_name); + } + + return 0; +} + +} // namespace internal + +} // namespace mac_relauncher diff --git a/chromium_src/chrome/browser/mac/relauncher.h b/chromium_src/chrome/browser/mac/relauncher.h new file mode 100644 index 0000000000..66391b423f --- /dev/null +++ b/chromium_src/chrome/browser/mac/relauncher.h @@ -0,0 +1,77 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_MAC_RELAUNCHER_H_ +#define CHROME_BROWSER_MAC_RELAUNCHER_H_ + +// mac_relauncher implements main browser application relaunches on the Mac. +// When a browser wants to relaunch itself, it can't simply fork off a new +// process and exec a new browser from within. That leaves open a window +// during which two browser applications might be running concurrently. If +// that happens, each will wind up with a distinct Dock icon, which is +// especially bad if the user expected the Dock icon to be persistent by +// choosing Keep in Dock from the icon's contextual menu. +// +// mac_relauncher approaches this problem by introducing an intermediate +// process (the "relauncher") in between the original browser ("parent") and +// replacement browser ("relaunched"). The helper executable is used for the +// relauncher process; because it's an LSUIElement, it doesn't get a Dock +// icon and isn't visible as a running application at all. The parent will +// start a relauncher process, giving it the "writer" side of a pipe that it +// retains the "reader" end of. When the relauncher starts up, it will +// establish a kqueue to wait for the parent to exit, and will then write to +// the pipe. The parent, upon reading from the pipe, is free to exit. When the +// relauncher is notified via its kqueue that the parent has exited, it +// proceeds, launching the relaunched process. The handshake to synchronize +// the parent with the relauncher is necessary to avoid races: the relauncher +// needs to be sure that it's monitoring the parent and not some other process +// in light of PID reuse, so the parent must remain alive long enough for the +// relauncher to set up its kqueue. + +#include +#include + +namespace content { +struct MainFunctionParams; +} + +namespace mac_relauncher { + +// Relaunches the application using the helper application associated with the +// currently running instance of Chrome in the parent browser process as the +// executable for the relauncher process. |args| is an argv-style vector of +// command line arguments of the form normally passed to execv. args[0] is +// also the path to the relaunched process. Because the relauncher process +// will ultimately launch the relaunched process via Launch Services, args[0] +// may be either a pathname to an executable file or a pathname to an .app +// bundle directory. The caller should exit soon after RelaunchApp returns +// successfully. Returns true on success, although some failures can occur +// after this function returns true if, for example, they occur within the +// relauncher process. Returns false when the relaunch definitely failed. +bool RelaunchApp(const std::vector& args); + +// Identical to RelaunchApp, but uses |helper| as the path to the relauncher +// process, and allows additional arguments to be supplied to the relauncher +// process in relauncher_args. Unlike args[0], |helper| must be a pathname to +// an executable file. The helper path given must be from the same version of +// Chrome as the running parent browser process, as there are no guarantees +// that the parent and relauncher processes from different versions will be +// able to communicate with one another. This variant can be useful to +// relaunch the same version of Chrome from another location, using that +// location's helper. +bool RelaunchAppWithHelper(const std::string& helper, + const std::vector& relauncher_args, + const std::vector& args); + +namespace internal { + +// The entry point from ChromeMain into the relauncher process. This is not a +// user API. Don't call it if your name isn't ChromeMain. +int RelauncherMain(const content::MainFunctionParams& main_parameters); + +} // namespace internal + +} // namespace mac_relauncher + +#endif // CHROME_BROWSER_MAC_RELAUNCHER_H_ diff --git a/filenames.gypi b/filenames.gypi index f66b98134c..0d1d97711b 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -420,6 +420,8 @@ 'chromium_src/chrome/browser/extensions/global_shortcut_listener_x11.h', 'chromium_src/chrome/browser/extensions/global_shortcut_listener_win.cc', 'chromium_src/chrome/browser/extensions/global_shortcut_listener_win.h', + 'chromium_src/chrome/browser/mac/relauncher.cc', + 'chromium_src/chrome/browser/mac/relauncher.h', 'chromium_src/chrome/browser/media/desktop_media_list.h', 'chromium_src/chrome/browser/media/desktop_media_list_observer.h', 'chromium_src/chrome/browser/media/native_desktop_media_list.cc', From abdcb9d4817ae025272aec2e3cfd567da6162670 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Wed, 1 Jun 2016 18:22:14 +0900 Subject: [PATCH 02/16] Implement app.relaunch on OS X --- atom/app/atom_main_delegate.cc | 28 +++++++++++++++++++ atom/app/atom_main_delegate.h | 9 ++++++ atom/browser/api/atom_api_app.cc | 1 + atom/browser/browser.h | 4 +++ atom/browser/browser_mac.mm | 11 ++++++++ chromium_src/chrome/browser/mac/relauncher.cc | 18 +----------- 6 files changed, 54 insertions(+), 17 deletions(-) diff --git a/atom/app/atom_main_delegate.cc b/atom/app/atom_main_delegate.cc index 9ce8dc504a..170bee2be8 100644 --- a/atom/app/atom_main_delegate.cc +++ b/atom/app/atom_main_delegate.cc @@ -21,10 +21,18 @@ #include "ui/base/l10n/l10n_util.h" #include "ui/base/resource/resource_bundle.h" +#if defined(OS_MACOSX) +#include "chrome/browser/mac/relauncher.h" +#endif + namespace atom { namespace { +#if defined(OS_MACOSX) +const char* kRelauncherProcess = "relauncher"; +#endif + bool IsBrowserProcess(base::CommandLine* cmd) { std::string process_type = cmd->GetSwitchValueASCII(switches::kProcessType); return process_type.empty(); @@ -146,6 +154,26 @@ content::ContentUtilityClient* AtomMainDelegate::CreateContentUtilityClient() { return utility_client_.get(); } +#if defined(OS_MACOSX) +int AtomMainDelegate::RunProcess( + const std::string& process_type, + const content::MainFunctionParams& main_function_params) { + if (process_type == kRelauncherProcess) + return mac_relauncher::internal::RelauncherMain(main_function_params); + else + return -1; +} + +bool AtomMainDelegate::ShouldSendMachPort(const std::string& process_type) { + return process_type != kRelauncherProcess; +} + +bool AtomMainDelegate::DelaySandboxInitialization( + const std::string& process_type) { + return process_type == kRelauncherProcess; +} +#endif + std::unique_ptr AtomMainDelegate::CreateContentClient() { return std::unique_ptr(new AtomContentClient); diff --git a/atom/app/atom_main_delegate.h b/atom/app/atom_main_delegate.h index 2f9474cff5..b12a6dcdc4 100644 --- a/atom/app/atom_main_delegate.h +++ b/atom/app/atom_main_delegate.h @@ -5,6 +5,8 @@ #ifndef ATOM_APP_ATOM_MAIN_DELEGATE_H_ #define ATOM_APP_ATOM_MAIN_DELEGATE_H_ +#include + #include "brightray/common/main_delegate.h" #include "brightray/common/content_client.h" @@ -22,6 +24,13 @@ class AtomMainDelegate : public brightray::MainDelegate { content::ContentBrowserClient* CreateContentBrowserClient() override; content::ContentRendererClient* CreateContentRendererClient() override; content::ContentUtilityClient* CreateContentUtilityClient() override; +#if defined(OS_MACOSX) + int RunProcess( + const std::string& process_type, + const content::MainFunctionParams& main_function_params) override; + bool ShouldSendMachPort(const std::string& process_type) override; + bool DelaySandboxInitialization(const std::string& process_type) override; +#endif // brightray::MainDelegate: std::unique_ptr CreateContentClient() override; diff --git a/atom/browser/api/atom_api_app.cc b/atom/browser/api/atom_api_app.cc index 3599e3195f..c5bce573dd 100644 --- a/atom/browser/api/atom_api_app.cc +++ b/atom/browser/api/atom_api_app.cc @@ -450,6 +450,7 @@ void App::BuildPrototype( mate::ObjectTemplateBuilder(isolate, prototype) .SetMethod("quit", base::Bind(&Browser::Quit, browser)) .SetMethod("exit", base::Bind(&Browser::Exit, browser)) + .SetMethod("relaunch", base::Bind(&Browser::Relaunch, browser)) .SetMethod("focus", base::Bind(&Browser::Focus, browser)) .SetMethod("getVersion", base::Bind(&Browser::GetVersion, browser)) .SetMethod("setVersion", base::Bind(&Browser::SetVersion, browser)) diff --git a/atom/browser/browser.h b/atom/browser/browser.h index 18d0c97c93..2b446d621f 100644 --- a/atom/browser/browser.h +++ b/atom/browser/browser.h @@ -53,6 +53,10 @@ class Browser : public WindowListObserver { // Cleanup everything and shutdown the application gracefully. void Shutdown(); + // Restart the app. + void Relaunch(const std::vector& args, + const std::string& app); + // Focus the application. void Focus(); diff --git a/atom/browser/browser_mac.mm b/atom/browser/browser_mac.mm index 4561eab8c9..9149a21269 100644 --- a/atom/browser/browser_mac.mm +++ b/atom/browser/browser_mac.mm @@ -13,11 +13,22 @@ #include "base/mac/foundation_util.h" #include "base/strings/sys_string_conversions.h" #include "brightray/common/application_info.h" +#include "brightray/common/mac/main_application_bundle.h" +#include "chrome/browser/mac/relauncher.h" #include "net/base/mac/url_conversions.h" #include "url/gurl.h" namespace atom { +void Browser::Relaunch(const std::vector& args, + const std::string& app) { + std::vector args_with_app(args); + args_with_app.insert( + args_with_app.begin(), + app.empty() ? brightray::MainApplicationBundlePath().value() : app); + mac_relauncher::RelaunchApp(args_with_app); +} + void Browser::Focus() { [[AtomApplication sharedApplication] activateIgnoringOtherApps:YES]; } diff --git a/chromium_src/chrome/browser/mac/relauncher.cc b/chromium_src/chrome/browser/mac/relauncher.cc index 40ea5371bf..f76e44c637 100644 --- a/chromium_src/chrome/browser/mac/relauncher.cc +++ b/chromium_src/chrome/browser/mac/relauncher.cc @@ -29,8 +29,6 @@ #include "base/process/launch.h" #include "base/strings/stringprintf.h" #include "base/strings/sys_string_conversions.h" -#include "chrome/browser/mac/install_from_dmg.h" -#include "chrome/common/chrome_switches.h" #include "content/public/common/content_paths.h" #include "content/public/common/content_switches.h" #include "content/public/common/main_function_params.h" @@ -66,9 +64,7 @@ const char kPSNArg[] = "-psn_"; // Returns the "type" argument identifying a relauncher process // ("--type=relauncher"). std::string RelauncherTypeArg() { - return base::StringPrintf("--%s=%s", - switches::kProcessType, - switches::kRelauncherProcess); + return base::StringPrintf("--%s=%s", switches::kProcessType, "relauncher"); } } // namespace @@ -292,12 +288,9 @@ int RelauncherMain(const content::MainFunctionParams& main_parameters) { // start it in the background. bool background = false; bool in_relaunch_args = false; - std::string dmg_bsd_device_name; bool seen_relaunch_executable = false; std::string relaunch_executable; const std::string relauncher_arg_separator(kRelauncherArgSeparator); - const std::string relauncher_dmg_device_arg = - base::StringPrintf("--%s=", switches::kRelauncherProcessDMGDevice); for (int argv_index = 2; argv_index < argc; ++argv_index) { const std::string arg(argv[argv_index]); @@ -311,11 +304,6 @@ int RelauncherMain(const content::MainFunctionParams& main_parameters) { in_relaunch_args = true; } else if (arg == kRelauncherBackgroundArg) { background = true; - } else if (arg.compare(0, - relauncher_dmg_device_arg.size(), - relauncher_dmg_device_arg) == 0) { - dmg_bsd_device_name.assign( - arg.substr(relauncher_dmg_device_arg.size())); } } else { if (!seen_relaunch_executable) { @@ -369,10 +357,6 @@ int RelauncherMain(const content::MainFunctionParams& main_parameters) { // relaunching). From this point on, only clean-up tasks should occur, and // failures are tolerable. - if (!dmg_bsd_device_name.empty()) { - EjectAndTrashDiskImage(dmg_bsd_device_name); - } - return 0; } From 6df18956cdfe3329c9a046e3e3b96e4942d6a43c Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Wed, 1 Jun 2016 22:04:20 +0900 Subject: [PATCH 03/16] Get ready to make relauncher cross-platform --- atom/app/atom_main_delegate.cc | 11 ++----- atom/app/atom_main_delegate.h | 2 -- atom/browser/browser_mac.mm | 4 +-- .../mac => atom/browser}/relauncher.cc | 20 ++---------- .../browser/mac => atom/browser}/relauncher.h | 31 ++++++++++--------- filenames.gypi | 4 +-- 6 files changed, 26 insertions(+), 46 deletions(-) rename {chromium_src/chrome/browser/mac => atom/browser}/relauncher.cc (95%) rename {chromium_src/chrome/browser/mac => atom/browser}/relauncher.h (77%) diff --git a/atom/app/atom_main_delegate.cc b/atom/app/atom_main_delegate.cc index 170bee2be8..dbd2712393 100644 --- a/atom/app/atom_main_delegate.cc +++ b/atom/app/atom_main_delegate.cc @@ -9,6 +9,7 @@ #include "atom/app/atom_content_client.h" #include "atom/browser/atom_browser_client.h" +#include "atom/browser/relauncher.h" #include "atom/common/google_api_key.h" #include "atom/renderer/atom_renderer_client.h" #include "atom/utility/atom_content_utility_client.h" @@ -21,17 +22,11 @@ #include "ui/base/l10n/l10n_util.h" #include "ui/base/resource/resource_bundle.h" -#if defined(OS_MACOSX) -#include "chrome/browser/mac/relauncher.h" -#endif - namespace atom { namespace { -#if defined(OS_MACOSX) const char* kRelauncherProcess = "relauncher"; -#endif bool IsBrowserProcess(base::CommandLine* cmd) { std::string process_type = cmd->GetSwitchValueASCII(switches::kProcessType); @@ -154,12 +149,11 @@ content::ContentUtilityClient* AtomMainDelegate::CreateContentUtilityClient() { return utility_client_.get(); } -#if defined(OS_MACOSX) int AtomMainDelegate::RunProcess( const std::string& process_type, const content::MainFunctionParams& main_function_params) { if (process_type == kRelauncherProcess) - return mac_relauncher::internal::RelauncherMain(main_function_params); + return relauncher::RelauncherMain(main_function_params); else return -1; } @@ -172,7 +166,6 @@ bool AtomMainDelegate::DelaySandboxInitialization( const std::string& process_type) { return process_type == kRelauncherProcess; } -#endif std::unique_ptr AtomMainDelegate::CreateContentClient() { diff --git a/atom/app/atom_main_delegate.h b/atom/app/atom_main_delegate.h index b12a6dcdc4..377665f629 100644 --- a/atom/app/atom_main_delegate.h +++ b/atom/app/atom_main_delegate.h @@ -24,13 +24,11 @@ class AtomMainDelegate : public brightray::MainDelegate { content::ContentBrowserClient* CreateContentBrowserClient() override; content::ContentRendererClient* CreateContentRendererClient() override; content::ContentUtilityClient* CreateContentUtilityClient() override; -#if defined(OS_MACOSX) int RunProcess( const std::string& process_type, const content::MainFunctionParams& main_function_params) override; bool ShouldSendMachPort(const std::string& process_type) override; bool DelaySandboxInitialization(const std::string& process_type) override; -#endif // brightray::MainDelegate: std::unique_ptr CreateContentClient() override; diff --git a/atom/browser/browser_mac.mm b/atom/browser/browser_mac.mm index 9149a21269..271b88de4c 100644 --- a/atom/browser/browser_mac.mm +++ b/atom/browser/browser_mac.mm @@ -8,13 +8,13 @@ #include "atom/browser/mac/atom_application_delegate.h" #include "atom/browser/mac/dict_util.h" #include "atom/browser/native_window.h" +#include "atom/browser/relauncher.h" #include "atom/browser/window_list.h" #include "base/mac/bundle_locations.h" #include "base/mac/foundation_util.h" #include "base/strings/sys_string_conversions.h" #include "brightray/common/application_info.h" #include "brightray/common/mac/main_application_bundle.h" -#include "chrome/browser/mac/relauncher.h" #include "net/base/mac/url_conversions.h" #include "url/gurl.h" @@ -26,7 +26,7 @@ void Browser::Relaunch(const std::vector& args, args_with_app.insert( args_with_app.begin(), app.empty() ? brightray::MainApplicationBundlePath().value() : app); - mac_relauncher::RelaunchApp(args_with_app); + relauncher::RelaunchApp(args_with_app); } void Browser::Focus() { diff --git a/chromium_src/chrome/browser/mac/relauncher.cc b/atom/browser/relauncher.cc similarity index 95% rename from chromium_src/chrome/browser/mac/relauncher.cc rename to atom/browser/relauncher.cc index f76e44c637..284093152e 100644 --- a/chromium_src/chrome/browser/mac/relauncher.cc +++ b/atom/browser/relauncher.cc @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "chrome/browser/mac/relauncher.h" +#include "atom/browser/relauncher.h" #include #include @@ -33,7 +33,7 @@ #include "content/public/common/content_switches.h" #include "content/public/common/main_function_params.h" -namespace mac_relauncher { +namespace relauncher { namespace { @@ -171,14 +171,6 @@ bool RelaunchAppWithHelper(const std::string& helper, return true; } -namespace { - -// In the relauncher process, performs the necessary synchronization steps -// with the parent by setting up a kqueue to watch for it to exit, writing a -// byte to the pipe, and then waiting for the exit notification on the kqueue. -// If anything fails, this logs a message and returns immediately. In those -// situations, it can be assumed that something went wrong with the parent -// process and the best recovery approach is to attempt relaunch anyway. void RelauncherSynchronizeWithParent() { base::ScopedFD relauncher_sync_fd(kRelauncherSyncFD); @@ -236,10 +228,6 @@ void RelauncherSynchronizeWithParent() { } } -} // namespace - -namespace internal { - int RelauncherMain(const content::MainFunctionParams& main_parameters) { // CommandLine rearranges the order of the arguments returned by // main_parameters.argv(), rendering it impossible to determine which @@ -360,6 +348,4 @@ int RelauncherMain(const content::MainFunctionParams& main_parameters) { return 0; } -} // namespace internal - -} // namespace mac_relauncher +} // namespace relauncher diff --git a/chromium_src/chrome/browser/mac/relauncher.h b/atom/browser/relauncher.h similarity index 77% rename from chromium_src/chrome/browser/mac/relauncher.h rename to atom/browser/relauncher.h index 66391b423f..f072a72533 100644 --- a/chromium_src/chrome/browser/mac/relauncher.h +++ b/atom/browser/relauncher.h @@ -1,11 +1,11 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be +// Copyright (c) 2016 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be // found in the LICENSE file. -#ifndef CHROME_BROWSER_MAC_RELAUNCHER_H_ -#define CHROME_BROWSER_MAC_RELAUNCHER_H_ +#ifndef ATOM_BROWSER_RELAUNCHER_H_ +#define ATOM_BROWSER_RELAUNCHER_H_ -// mac_relauncher implements main browser application relaunches on the Mac. +// relauncher implements main browser application relaunches across platforms. // When a browser wants to relaunch itself, it can't simply fork off a new // process and exec a new browser from within. That leaves open a window // during which two browser applications might be running concurrently. If @@ -13,7 +13,7 @@ // especially bad if the user expected the Dock icon to be persistent by // choosing Keep in Dock from the icon's contextual menu. // -// mac_relauncher approaches this problem by introducing an intermediate +// relauncher approaches this problem by introducing an intermediate // process (the "relauncher") in between the original browser ("parent") and // replacement browser ("relaunched"). The helper executable is used for the // relauncher process; because it's an LSUIElement, it doesn't get a Dock @@ -36,7 +36,7 @@ namespace content { struct MainFunctionParams; } -namespace mac_relauncher { +namespace relauncher { // Relaunches the application using the helper application associated with the // currently running instance of Chrome in the parent browser process as the @@ -64,14 +64,17 @@ bool RelaunchAppWithHelper(const std::string& helper, const std::vector& relauncher_args, const std::vector& args); -namespace internal { +// In the relauncher process, performs the necessary synchronization steps +// with the parent by setting up a kqueue to watch for it to exit, writing a +// byte to the pipe, and then waiting for the exit notification on the kqueue. +// If anything fails, this logs a message and returns immediately. In those +// situations, it can be assumed that something went wrong with the parent +// process and the best recovery approach is to attempt relaunch anyway. +void RelauncherSynchronizeWithParent(); -// The entry point from ChromeMain into the relauncher process. This is not a -// user API. Don't call it if your name isn't ChromeMain. +// The entry point from ChromeMain into the relauncher process. int RelauncherMain(const content::MainFunctionParams& main_parameters); -} // namespace internal +} // namespace relauncher -} // namespace mac_relauncher - -#endif // CHROME_BROWSER_MAC_RELAUNCHER_H_ +#endif // ATOM_BROWSER_RELAUNCHER_H_ diff --git a/filenames.gypi b/filenames.gypi index 0d1d97711b..3ce737f22a 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -225,6 +225,8 @@ 'atom/browser/net/url_request_fetch_job.h', 'atom/browser/node_debugger.cc', 'atom/browser/node_debugger.h', + 'atom/browser/relauncher.cc', + 'atom/browser/relauncher.h', 'atom/browser/render_process_preferences.cc', 'atom/browser/render_process_preferences.h', 'atom/browser/ui/accelerator_util.cc', @@ -420,8 +422,6 @@ 'chromium_src/chrome/browser/extensions/global_shortcut_listener_x11.h', 'chromium_src/chrome/browser/extensions/global_shortcut_listener_win.cc', 'chromium_src/chrome/browser/extensions/global_shortcut_listener_win.h', - 'chromium_src/chrome/browser/mac/relauncher.cc', - 'chromium_src/chrome/browser/mac/relauncher.h', 'chromium_src/chrome/browser/media/desktop_media_list.h', 'chromium_src/chrome/browser/media/desktop_media_list_observer.h', 'chromium_src/chrome/browser/media/native_desktop_media_list.cc', From fc30a2a084129f1f30678da7d922f33aaedb6f83 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Wed, 1 Jun 2016 22:15:50 +0900 Subject: [PATCH 04/16] Use AtomCommandLine to process command line parameters --- atom/browser/relauncher.cc | 41 +++++++------------------------------- 1 file changed, 7 insertions(+), 34 deletions(-) diff --git a/atom/browser/relauncher.cc b/atom/browser/relauncher.cc index 284093152e..d48f13b931 100644 --- a/atom/browser/relauncher.cc +++ b/atom/browser/relauncher.cc @@ -5,11 +5,7 @@ #include "atom/browser/relauncher.h" #include -#include -#include -#include -#include -#include + #include #include #include @@ -18,8 +14,8 @@ #include #include +#include "atom/common/atom_command_line.h" #include "base/files/file_util.h" -#include "base/files/scoped_file.h" #include "base/logging.h" #include "base/mac/mac_logging.h" #include "base/mac/mac_util.h" @@ -229,32 +225,9 @@ void RelauncherSynchronizeWithParent() { } int RelauncherMain(const content::MainFunctionParams& main_parameters) { - // CommandLine rearranges the order of the arguments returned by - // main_parameters.argv(), rendering it impossible to determine which - // arguments originally came before kRelauncherArgSeparator and which came - // after. It's crucial to distinguish between these because only those - // after the separator should be given to the relaunched process; it's also - // important to not treat the path to the relaunched process as a "loose" - // argument. NXArgc and NXArgv are pointers to the original argc and argv as - // passed to main(), so use those. Access them through _NSGetArgc and - // _NSGetArgv because NXArgc and NXArgv are normally only available to a - // main executable via crt1.o and this code will run from a dylib, and - // because of http://crbug.com/139902. - const int* argcp = _NSGetArgc(); - if (!argcp) { - NOTREACHED(); - return 1; - } - int argc = *argcp; + const std::vector& argv = atom::AtomCommandLine::argv(); - const char* const* const* argvp = _NSGetArgv(); - if (!argvp) { - NOTREACHED(); - return 1; - } - const char* const* argv = *argvp; - - if (argc < 4 || RelauncherTypeArg() != argv[1]) { + if (argv.size() < 4 || RelauncherTypeArg() != argv[1]) { LOG(ERROR) << "relauncher process invoked with unexpected arguments"; return 1; } @@ -266,7 +239,7 @@ int RelauncherMain(const content::MainFunctionParams& main_parameters) { // RelauncherTypeArg() at argv[1], kRelauncherArgSeparator, or the // executable path of the process to be launched. base::ScopedCFTypeRef relaunch_args( - CFArrayCreateMutable(NULL, argc - 4, &kCFTypeArrayCallBacks)); + CFArrayCreateMutable(NULL, argv.size() - 4, &kCFTypeArrayCallBacks)); if (!relaunch_args) { LOG(ERROR) << "CFArrayCreateMutable"; return 1; @@ -279,8 +252,8 @@ int RelauncherMain(const content::MainFunctionParams& main_parameters) { bool seen_relaunch_executable = false; std::string relaunch_executable; const std::string relauncher_arg_separator(kRelauncherArgSeparator); - for (int argv_index = 2; argv_index < argc; ++argv_index) { - const std::string arg(argv[argv_index]); + for (size_t argv_index = 2; argv_index < argv.size(); ++argv_index) { + const std::string& arg(argv[argv_index]); // Strip any -psn_ arguments, as they apply to a specific process. if (arg.compare(0, strlen(kPSNArg), kPSNArg) == 0) { From 060829da64cb3c5b6dff95bc02b525d81cbe6b39 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 2 Jun 2016 09:09:32 +0900 Subject: [PATCH 05/16] Separate implementations of RelauncherMain and RelauncherSynchronizeWithParent --- atom/app/atom_main_delegate.cc | 2 +- atom/browser/relauncher.cc | 234 ++------------------------------- atom/browser/relauncher.h | 21 +++ atom/browser/relauncher_mac.cc | 187 ++++++++++++++++++++++++++ filenames.gypi | 1 + 5 files changed, 224 insertions(+), 221 deletions(-) create mode 100644 atom/browser/relauncher_mac.cc diff --git a/atom/app/atom_main_delegate.cc b/atom/app/atom_main_delegate.cc index dbd2712393..c601fd681e 100644 --- a/atom/app/atom_main_delegate.cc +++ b/atom/app/atom_main_delegate.cc @@ -153,7 +153,7 @@ int AtomMainDelegate::RunProcess( const std::string& process_type, const content::MainFunctionParams& main_function_params) { if (process_type == kRelauncherProcess) - return relauncher::RelauncherMain(main_function_params); + return relauncher::internal::RelauncherMain(main_function_params); else return -1; } diff --git a/atom/browser/relauncher.cc b/atom/browser/relauncher.cc index d48f13b931..41b67f38b5 100644 --- a/atom/browser/relauncher.cc +++ b/atom/browser/relauncher.cc @@ -1,69 +1,31 @@ -// Copyright 2012 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be +// Copyright (c) 2016 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be // found in the LICENSE file. #include "atom/browser/relauncher.h" -#include - -#include -#include -#include -#include - #include #include -#include "atom/common/atom_command_line.h" #include "base/files/file_util.h" #include "base/logging.h" -#include "base/mac/mac_logging.h" -#include "base/mac/mac_util.h" -#include "base/mac/scoped_cftyperef.h" #include "base/path_service.h" #include "base/posix/eintr_wrapper.h" #include "base/process/launch.h" #include "base/strings/stringprintf.h" -#include "base/strings/sys_string_conversions.h" #include "content/public/common/content_paths.h" #include "content/public/common/content_switches.h" #include "content/public/common/main_function_params.h" namespace relauncher { -namespace { +namespace internal { -// The "magic" file descriptor that the relauncher process' write side of the -// pipe shows up on. Chosen to avoid conflicting with stdin, stdout, and -// stderr. const int kRelauncherSyncFD = STDERR_FILENO + 1; +const char* kRelauncherTypeArg = "--type=relauncher"; +const char* kRelauncherArgSeparator = "---"; -// The argument separating arguments intended for the relauncher process from -// those intended for the relaunched process. "---" is chosen instead of "--" -// because CommandLine interprets "--" as meaning "end of switches", but -// for many purposes, the relauncher process' CommandLine ought to interpret -// arguments intended for the relaunched process, to get the correct settings -// for such things as logging and the user-data-dir in case it affects crash -// reporting. -const char kRelauncherArgSeparator[] = "---"; - -// When this argument is supplied to the relauncher process, it will launch -// the relaunched process without bringing it to the foreground. -const char kRelauncherBackgroundArg[] = "--background"; - -// The beginning of the "process serial number" argument that Launch Services -// sometimes inserts into command lines. A process serial number is only valid -// for a single process, so any PSN arguments will be stripped from command -// lines during relaunch to avoid confusion. -const char kPSNArg[] = "-psn_"; - -// Returns the "type" argument identifying a relauncher process -// ("--type=relauncher"). -std::string RelauncherTypeArg() { - return base::StringPrintf("--%s=%s", switches::kProcessType, "relauncher"); -} - -} // namespace +} // namespace internal bool RelaunchApp(const std::vector& args) { // Use the currently-running application's helper process. The automatic @@ -87,29 +49,14 @@ bool RelaunchAppWithHelper(const std::string& helper, const std::vector& args) { std::vector relaunch_args; relaunch_args.push_back(helper); - relaunch_args.push_back(RelauncherTypeArg()); - - // If this application isn't in the foreground, the relaunched one shouldn't - // be either. - if (!base::mac::AmIForeground()) { - relaunch_args.push_back(kRelauncherBackgroundArg); - } + relaunch_args.push_back(internal::kRelauncherTypeArg); relaunch_args.insert(relaunch_args.end(), relauncher_args.begin(), relauncher_args.end()); - relaunch_args.push_back(kRelauncherArgSeparator); + relaunch_args.push_back(internal::kRelauncherArgSeparator); - // When using the CommandLine interface, -psn_ may have been rewritten as - // --psn_. Look for both. - const char alt_psn_arg[] = "--psn_"; - for (size_t index = 0; index < args.size(); ++index) { - // Strip any -psn_ arguments, as they apply to a specific process. - if (args[index].compare(0, strlen(kPSNArg), kPSNArg) != 0 && - args[index].compare(0, strlen(alt_psn_arg), alt_psn_arg) != 0) { - relaunch_args.push_back(args[index]); - } - } + relaunch_args.insert(relaunch_args.end(), args.begin(), args.end()); int pipe_fds[2]; if (HANDLE_EINTR(pipe(pipe_fds)) != 0) { @@ -129,13 +76,14 @@ bool RelaunchAppWithHelper(const std::string& helper, // Make sure kRelauncherSyncFD is a safe value. base::LaunchProcess will // preserve these three FDs in forked processes, so kRelauncherSyncFD should // not conflict with them. - static_assert(kRelauncherSyncFD != STDIN_FILENO && - kRelauncherSyncFD != STDOUT_FILENO && - kRelauncherSyncFD != STDERR_FILENO, + static_assert(internal::kRelauncherSyncFD != STDIN_FILENO && + internal::kRelauncherSyncFD != STDOUT_FILENO && + internal::kRelauncherSyncFD != STDERR_FILENO, "kRelauncherSyncFD must not conflict with stdio fds"); base::FileHandleMappingVector fd_map; - fd_map.push_back(std::make_pair(pipe_write_fd.get(), kRelauncherSyncFD)); + fd_map.push_back( + std::make_pair(pipe_write_fd.get(), internal::kRelauncherSyncFD)); base::LaunchOptions options; options.fds_to_remap = &fd_map; @@ -167,158 +115,4 @@ bool RelaunchAppWithHelper(const std::string& helper, return true; } -void RelauncherSynchronizeWithParent() { - base::ScopedFD relauncher_sync_fd(kRelauncherSyncFD); - - int parent_pid = getppid(); - - // PID 1 identifies init. launchd, that is. launchd never starts the - // relauncher process directly, having this parent_pid means that the parent - // already exited and launchd "inherited" the relauncher as its child. - // There's no reason to synchronize with launchd. - if (parent_pid == 1) { - LOG(ERROR) << "unexpected parent_pid"; - return; - } - - // Set up a kqueue to monitor the parent process for exit. - base::ScopedFD kq(kqueue()); - if (!kq.is_valid()) { - PLOG(ERROR) << "kqueue"; - return; - } - - struct kevent change = { 0 }; - EV_SET(&change, parent_pid, EVFILT_PROC, EV_ADD, NOTE_EXIT, 0, NULL); - if (kevent(kq.get(), &change, 1, NULL, 0, NULL) == -1) { - PLOG(ERROR) << "kevent (add)"; - return; - } - - // Write a '\0' character to the pipe. - if (HANDLE_EINTR(write(relauncher_sync_fd.get(), "", 1)) != 1) { - PLOG(ERROR) << "write"; - return; - } - - // Up until now, the parent process was blocked in a read waiting for the - // write above to complete. The parent process is now free to exit. Wait for - // that to happen. - struct kevent event; - int events = kevent(kq.get(), NULL, 0, &event, 1, NULL); - if (events != 1) { - if (events < 0) { - PLOG(ERROR) << "kevent (monitor)"; - } else { - LOG(ERROR) << "kevent (monitor): unexpected result " << events; - } - return; - } - - if (event.filter != EVFILT_PROC || - event.fflags != NOTE_EXIT || - event.ident != static_cast(parent_pid)) { - LOG(ERROR) << "kevent (monitor): unexpected event, filter " << event.filter - << ", fflags " << event.fflags << ", ident " << event.ident; - return; - } -} - -int RelauncherMain(const content::MainFunctionParams& main_parameters) { - const std::vector& argv = atom::AtomCommandLine::argv(); - - if (argv.size() < 4 || RelauncherTypeArg() != argv[1]) { - LOG(ERROR) << "relauncher process invoked with unexpected arguments"; - return 1; - } - - RelauncherSynchronizeWithParent(); - - // The capacity for relaunch_args is 4 less than argc, because it - // won't contain the argv[0] of the relauncher process, the - // RelauncherTypeArg() at argv[1], kRelauncherArgSeparator, or the - // executable path of the process to be launched. - base::ScopedCFTypeRef relaunch_args( - CFArrayCreateMutable(NULL, argv.size() - 4, &kCFTypeArrayCallBacks)); - if (!relaunch_args) { - LOG(ERROR) << "CFArrayCreateMutable"; - return 1; - } - - // Figure out what to execute, what arguments to pass it, and whether to - // start it in the background. - bool background = false; - bool in_relaunch_args = false; - bool seen_relaunch_executable = false; - std::string relaunch_executable; - const std::string relauncher_arg_separator(kRelauncherArgSeparator); - for (size_t argv_index = 2; argv_index < argv.size(); ++argv_index) { - const std::string& arg(argv[argv_index]); - - // Strip any -psn_ arguments, as they apply to a specific process. - if (arg.compare(0, strlen(kPSNArg), kPSNArg) == 0) { - continue; - } - - if (!in_relaunch_args) { - if (arg == relauncher_arg_separator) { - in_relaunch_args = true; - } else if (arg == kRelauncherBackgroundArg) { - background = true; - } - } else { - if (!seen_relaunch_executable) { - // The first argument after kRelauncherBackgroundArg is the path to - // the executable file or .app bundle directory. The Launch Services - // interface wants this separate from the rest of the arguments. In - // the relaunched process, this path will still be visible at argv[0]. - relaunch_executable.assign(arg); - seen_relaunch_executable = true; - } else { - base::ScopedCFTypeRef arg_cf( - base::SysUTF8ToCFStringRef(arg)); - if (!arg_cf) { - LOG(ERROR) << "base::SysUTF8ToCFStringRef failed for " << arg; - return 1; - } - CFArrayAppendValue(relaunch_args, arg_cf); - } - } - } - - if (!seen_relaunch_executable) { - LOG(ERROR) << "nothing to relaunch"; - return 1; - } - - FSRef app_fsref; - if (!base::mac::FSRefFromPath(relaunch_executable, &app_fsref)) { - LOG(ERROR) << "base::mac::FSRefFromPath failed for " << relaunch_executable; - return 1; - } - - LSApplicationParameters ls_parameters = { - 0, // version - kLSLaunchDefaults | kLSLaunchAndDisplayErrors | kLSLaunchNewInstance | - (background ? kLSLaunchDontSwitch : 0), - &app_fsref, - NULL, // asyncLaunchRefCon - NULL, // environment - relaunch_args, - NULL // initialEvent - }; - - OSStatus status = LSOpenApplication(&ls_parameters, NULL); - if (status != noErr) { - OSSTATUS_LOG(ERROR, status) << "LSOpenApplication"; - return 1; - } - - // The application should have relaunched (or is in the process of - // relaunching). From this point on, only clean-up tasks should occur, and - // failures are tolerable. - - return 0; -} - } // namespace relauncher diff --git a/atom/browser/relauncher.h b/atom/browser/relauncher.h index f072a72533..4134ef5422 100644 --- a/atom/browser/relauncher.h +++ b/atom/browser/relauncher.h @@ -64,6 +64,25 @@ bool RelaunchAppWithHelper(const std::string& helper, const std::vector& relauncher_args, const std::vector& args); +namespace internal { + +// The "magic" file descriptor that the relauncher process' write side of the +// pipe shows up on. Chosen to avoid conflicting with stdin, stdout, and +// stderr. +extern const int kRelauncherSyncFD; + +// The "type" argument identifying a relauncher process ("--type=relauncher"). +extern const char* kRelauncherTypeArg; + +// The argument separating arguments intended for the relauncher process from +// those intended for the relaunched process. "---" is chosen instead of "--" +// because CommandLine interprets "--" as meaning "end of switches", but +// for many purposes, the relauncher process' CommandLine ought to interpret +// arguments intended for the relaunched process, to get the correct settings +// for such things as logging and the user-data-dir in case it affects crash +// reporting. +extern const char* kRelauncherArgSeparator; + // In the relauncher process, performs the necessary synchronization steps // with the parent by setting up a kqueue to watch for it to exit, writing a // byte to the pipe, and then waiting for the exit notification on the kqueue. @@ -75,6 +94,8 @@ void RelauncherSynchronizeWithParent(); // The entry point from ChromeMain into the relauncher process. int RelauncherMain(const content::MainFunctionParams& main_parameters); +} // namespace internal + } // namespace relauncher #endif // ATOM_BROWSER_RELAUNCHER_H_ diff --git a/atom/browser/relauncher_mac.cc b/atom/browser/relauncher_mac.cc new file mode 100644 index 0000000000..39c478f161 --- /dev/null +++ b/atom/browser/relauncher_mac.cc @@ -0,0 +1,187 @@ +// Copyright (c) 2016 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "atom/browser/relauncher.h" + +#include + +#include +#include +#include +#include + +#include "atom/common/atom_command_line.h" +#include "base/files/file_util.h" +#include "base/logging.h" +#include "base/mac/mac_logging.h" +#include "base/mac/mac_util.h" +#include "base/mac/scoped_cftyperef.h" +#include "base/posix/eintr_wrapper.h" +#include "base/strings/sys_string_conversions.h" + +namespace relauncher { + +namespace internal { + +namespace { + +// The beginning of the "process serial number" argument that Launch Services +// sometimes inserts into command lines. A process serial number is only valid +// for a single process, so any PSN arguments will be stripped from command +// lines during relaunch to avoid confusion. +const char kPSNArg[] = "-psn_"; + +} // namespace + +void RelauncherSynchronizeWithParent() { + base::ScopedFD relauncher_sync_fd(kRelauncherSyncFD); + + int parent_pid = getppid(); + + // PID 1 identifies init. launchd, that is. launchd never starts the + // relauncher process directly, having this parent_pid means that the parent + // already exited and launchd "inherited" the relauncher as its child. + // There's no reason to synchronize with launchd. + if (parent_pid == 1) { + LOG(ERROR) << "unexpected parent_pid"; + return; + } + + // Set up a kqueue to monitor the parent process for exit. + base::ScopedFD kq(kqueue()); + if (!kq.is_valid()) { + PLOG(ERROR) << "kqueue"; + return; + } + + struct kevent change = { 0 }; + EV_SET(&change, parent_pid, EVFILT_PROC, EV_ADD, NOTE_EXIT, 0, NULL); + if (kevent(kq.get(), &change, 1, NULL, 0, NULL) == -1) { + PLOG(ERROR) << "kevent (add)"; + return; + } + + // Write a '\0' character to the pipe. + if (HANDLE_EINTR(write(relauncher_sync_fd.get(), "", 1)) != 1) { + PLOG(ERROR) << "write"; + return; + } + + // Up until now, the parent process was blocked in a read waiting for the + // write above to complete. The parent process is now free to exit. Wait for + // that to happen. + struct kevent event; + int events = kevent(kq.get(), NULL, 0, &event, 1, NULL); + if (events != 1) { + if (events < 0) { + PLOG(ERROR) << "kevent (monitor)"; + } else { + LOG(ERROR) << "kevent (monitor): unexpected result " << events; + } + return; + } + + if (event.filter != EVFILT_PROC || + event.fflags != NOTE_EXIT || + event.ident != static_cast(parent_pid)) { + LOG(ERROR) << "kevent (monitor): unexpected event, filter " << event.filter + << ", fflags " << event.fflags << ", ident " << event.ident; + return; + } +} + +int RelauncherMain(const content::MainFunctionParams& main_parameters) { + const std::vector& argv = atom::AtomCommandLine::argv(); + + if (argv.size() < 4 || kRelauncherTypeArg != argv[1]) { + LOG(ERROR) << "relauncher process invoked with unexpected arguments"; + return 1; + } + + internal::RelauncherSynchronizeWithParent(); + + // The capacity for relaunch_args is 4 less than argc, because it + // won't contain the argv[0] of the relauncher process, the + // RelauncherTypeArg() at argv[1], kRelauncherArgSeparator, or the + // executable path of the process to be launched. + base::ScopedCFTypeRef relaunch_args( + CFArrayCreateMutable(NULL, argv.size() - 4, &kCFTypeArrayCallBacks)); + if (!relaunch_args) { + LOG(ERROR) << "CFArrayCreateMutable"; + return 1; + } + + // Figure out what to execute, what arguments to pass it, and whether to + // start it in the background. + bool in_relaunch_args = false; + bool seen_relaunch_executable = false; + std::string relaunch_executable; + const std::string relauncher_arg_separator(kRelauncherArgSeparator); + for (size_t argv_index = 2; argv_index < argv.size(); ++argv_index) { + const std::string& arg(argv[argv_index]); + + // Strip any -psn_ arguments, as they apply to a specific process. + if (arg.compare(0, strlen(kPSNArg), kPSNArg) == 0) { + continue; + } + + if (!in_relaunch_args && arg == relauncher_arg_separator) { + in_relaunch_args = true; + } else { + if (!seen_relaunch_executable) { + // The first argument after kRelauncherBackgroundArg is the path to + // the executable file or .app bundle directory. The Launch Services + // interface wants this separate from the rest of the arguments. In + // the relaunched process, this path will still be visible at argv[0]. + relaunch_executable.assign(arg); + seen_relaunch_executable = true; + } else { + base::ScopedCFTypeRef arg_cf( + base::SysUTF8ToCFStringRef(arg)); + if (!arg_cf) { + LOG(ERROR) << "base::SysUTF8ToCFStringRef failed for " << arg; + return 1; + } + CFArrayAppendValue(relaunch_args, arg_cf); + } + } + } + + if (!seen_relaunch_executable) { + LOG(ERROR) << "nothing to relaunch"; + return 1; + } + + FSRef app_fsref; + if (!base::mac::FSRefFromPath(relaunch_executable, &app_fsref)) { + LOG(ERROR) << "base::mac::FSRefFromPath failed for " << relaunch_executable; + return 1; + } + + LSApplicationParameters ls_parameters = { + 0, // version + kLSLaunchDefaults | kLSLaunchAndDisplayErrors | kLSLaunchNewInstance, + &app_fsref, + NULL, // asyncLaunchRefCon + NULL, // environment + relaunch_args, + NULL // initialEvent + }; + + OSStatus status = LSOpenApplication(&ls_parameters, NULL); + if (status != noErr) { + OSSTATUS_LOG(ERROR, status) << "LSOpenApplication"; + return 1; + } + + // The application should have relaunched (or is in the process of + // relaunching). From this point on, only clean-up tasks should occur, and + // failures are tolerable. + + return 0; +} + +} // namespace internal + +} // namespace relauncher diff --git a/filenames.gypi b/filenames.gypi index 3ce737f22a..3cee4ca50c 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -225,6 +225,7 @@ 'atom/browser/net/url_request_fetch_job.h', 'atom/browser/node_debugger.cc', 'atom/browser/node_debugger.h', + 'atom/browser/relauncher_mac.cc', 'atom/browser/relauncher.cc', 'atom/browser/relauncher.h', 'atom/browser/render_process_preferences.cc', From c3fe2dae9d334ce4a16322d28a6bdaf5dd182961 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 2 Jun 2016 16:23:46 +0900 Subject: [PATCH 06/16] Separate LaunchProgram from mac implementation --- atom/app/atom_main_delegate.cc | 2 +- atom/browser/relauncher.cc | 58 ++++++++++++++++++++++++++++++ atom/browser/relauncher.h | 8 +++-- atom/browser/relauncher_mac.cc | 64 +++++++--------------------------- 4 files changed, 77 insertions(+), 55 deletions(-) diff --git a/atom/app/atom_main_delegate.cc b/atom/app/atom_main_delegate.cc index c601fd681e..dbd2712393 100644 --- a/atom/app/atom_main_delegate.cc +++ b/atom/app/atom_main_delegate.cc @@ -153,7 +153,7 @@ int AtomMainDelegate::RunProcess( const std::string& process_type, const content::MainFunctionParams& main_function_params) { if (process_type == kRelauncherProcess) - return relauncher::internal::RelauncherMain(main_function_params); + return relauncher::RelauncherMain(main_function_params); else return -1; } diff --git a/atom/browser/relauncher.cc b/atom/browser/relauncher.cc index 41b67f38b5..47c77a119a 100644 --- a/atom/browser/relauncher.cc +++ b/atom/browser/relauncher.cc @@ -7,6 +7,7 @@ #include #include +#include "atom/common/atom_command_line.h" #include "base/files/file_util.h" #include "base/logging.h" #include "base/path_service.h" @@ -115,4 +116,61 @@ bool RelaunchAppWithHelper(const std::string& helper, return true; } +int RelauncherMain(const content::MainFunctionParams& main_parameters) { + const std::vector& argv = atom::AtomCommandLine::argv(); + + if (argv.size() < 4 || argv[1] != internal::kRelauncherTypeArg) { + LOG(ERROR) << "relauncher process invoked with unexpected arguments"; + return 1; + } + + internal::RelauncherSynchronizeWithParent(); + + // Figure out what to execute, what arguments to pass it, and whether to + // start it in the background. + bool in_relaunch_args = false; + bool seen_relaunch_executable = false; + std::string relaunch_executable; + std::vector relauncher_args; + std::vector launch_args; + for (size_t argv_index = 2; argv_index < argv.size(); ++argv_index) { + const std::string& arg(argv[argv_index]); + if (!in_relaunch_args) { + if (arg == internal::kRelauncherArgSeparator) { + in_relaunch_args = true; + } else { + relauncher_args.push_back(arg); + } + } else { + if (!seen_relaunch_executable) { + // The first argument after kRelauncherBackgroundArg is the path to + // the executable file or .app bundle directory. The Launch Services + // interface wants this separate from the rest of the arguments. In + // the relaunched process, this path will still be visible at argv[0]. + relaunch_executable.assign(arg); + seen_relaunch_executable = true; + } else { + launch_args.push_back(arg); + } + } + } + + if (!seen_relaunch_executable) { + LOG(ERROR) << "nothing to relaunch"; + return 1; + } + + if (internal::LaunchProgram(relauncher_args, relaunch_executable, + launch_args) != 0) { + LOG(ERROR) << "failed to launch program"; + return 1; + } + + // The application should have relaunched (or is in the process of + // relaunching). From this point on, only clean-up tasks should occur, and + // failures are tolerable. + + return 0; +} + } // namespace relauncher diff --git a/atom/browser/relauncher.h b/atom/browser/relauncher.h index 4134ef5422..624a68eaf2 100644 --- a/atom/browser/relauncher.h +++ b/atom/browser/relauncher.h @@ -64,6 +64,9 @@ bool RelaunchAppWithHelper(const std::string& helper, const std::vector& relauncher_args, const std::vector& args); +// The entry point from ChromeMain into the relauncher process. +int RelauncherMain(const content::MainFunctionParams& main_parameters); + namespace internal { // The "magic" file descriptor that the relauncher process' write side of the @@ -91,8 +94,9 @@ extern const char* kRelauncherArgSeparator; // process and the best recovery approach is to attempt relaunch anyway. void RelauncherSynchronizeWithParent(); -// The entry point from ChromeMain into the relauncher process. -int RelauncherMain(const content::MainFunctionParams& main_parameters); +int LaunchProgram(const std::vector& relauncher_args, + const std::string& program, + const std::vector& argv); } // namespace internal diff --git a/atom/browser/relauncher_mac.cc b/atom/browser/relauncher_mac.cc index 39c478f161..a537e311ce 100644 --- a/atom/browser/relauncher_mac.cc +++ b/atom/browser/relauncher_mac.cc @@ -11,7 +11,6 @@ #include #include -#include "atom/common/atom_command_line.h" #include "base/files/file_util.h" #include "base/logging.h" #include "base/mac/mac_logging.h" @@ -91,16 +90,9 @@ void RelauncherSynchronizeWithParent() { } } -int RelauncherMain(const content::MainFunctionParams& main_parameters) { - const std::vector& argv = atom::AtomCommandLine::argv(); - - if (argv.size() < 4 || kRelauncherTypeArg != argv[1]) { - LOG(ERROR) << "relauncher process invoked with unexpected arguments"; - return 1; - } - - internal::RelauncherSynchronizeWithParent(); - +int LaunchProgram(const std::vector& relauncher_args, + const std::string& program, + const std::vector& argv) { // The capacity for relaunch_args is 4 less than argc, because it // won't contain the argv[0] of the relauncher process, the // RelauncherTypeArg() at argv[1], kRelauncherArgSeparator, or the @@ -112,50 +104,22 @@ int RelauncherMain(const content::MainFunctionParams& main_parameters) { return 1; } - // Figure out what to execute, what arguments to pass it, and whether to - // start it in the background. - bool in_relaunch_args = false; - bool seen_relaunch_executable = false; - std::string relaunch_executable; - const std::string relauncher_arg_separator(kRelauncherArgSeparator); - for (size_t argv_index = 2; argv_index < argv.size(); ++argv_index) { - const std::string& arg(argv[argv_index]); - + for (const std::string& arg : argv) { // Strip any -psn_ arguments, as they apply to a specific process. - if (arg.compare(0, strlen(kPSNArg), kPSNArg) == 0) { + if (arg.compare(0, strlen(kPSNArg), kPSNArg) == 0) continue; - } - if (!in_relaunch_args && arg == relauncher_arg_separator) { - in_relaunch_args = true; - } else { - if (!seen_relaunch_executable) { - // The first argument after kRelauncherBackgroundArg is the path to - // the executable file or .app bundle directory. The Launch Services - // interface wants this separate from the rest of the arguments. In - // the relaunched process, this path will still be visible at argv[0]. - relaunch_executable.assign(arg); - seen_relaunch_executable = true; - } else { - base::ScopedCFTypeRef arg_cf( - base::SysUTF8ToCFStringRef(arg)); - if (!arg_cf) { - LOG(ERROR) << "base::SysUTF8ToCFStringRef failed for " << arg; - return 1; - } - CFArrayAppendValue(relaunch_args, arg_cf); - } + base::ScopedCFTypeRef arg_cf(base::SysUTF8ToCFStringRef(arg)); + if (!arg_cf) { + LOG(ERROR) << "base::SysUTF8ToCFStringRef failed for " << arg; + return 1; } - } - - if (!seen_relaunch_executable) { - LOG(ERROR) << "nothing to relaunch"; - return 1; + CFArrayAppendValue(relaunch_args, arg_cf); } FSRef app_fsref; - if (!base::mac::FSRefFromPath(relaunch_executable, &app_fsref)) { - LOG(ERROR) << "base::mac::FSRefFromPath failed for " << relaunch_executable; + if (!base::mac::FSRefFromPath(program, &app_fsref)) { + LOG(ERROR) << "base::mac::FSRefFromPath failed for " << program; return 1; } @@ -175,10 +139,6 @@ int RelauncherMain(const content::MainFunctionParams& main_parameters) { return 1; } - // The application should have relaunched (or is in the process of - // relaunching). From this point on, only clean-up tasks should occur, and - // failures are tolerable. - return 0; } From a3f39e9d0b1ffee1a299e2799948ca8dc445f052 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 2 Jun 2016 16:54:21 +0900 Subject: [PATCH 07/16] Implement Relaunch on Linux --- atom/app/atom_main_delegate.cc | 2 + atom/app/atom_main_delegate.h | 2 + atom/browser/browser.cc | 12 ++++++ atom/browser/browser_mac.mm | 11 ----- atom/browser/relauncher.cc | 14 +++++- atom/browser/relauncher_linux.cc | 74 ++++++++++++++++++++++++++++++++ filenames.gypi | 1 + 7 files changed, 104 insertions(+), 12 deletions(-) create mode 100644 atom/browser/relauncher_linux.cc diff --git a/atom/app/atom_main_delegate.cc b/atom/app/atom_main_delegate.cc index dbd2712393..f4e0b60444 100644 --- a/atom/app/atom_main_delegate.cc +++ b/atom/app/atom_main_delegate.cc @@ -158,6 +158,7 @@ int AtomMainDelegate::RunProcess( return -1; } +#if defined(OS_MACOSX) bool AtomMainDelegate::ShouldSendMachPort(const std::string& process_type) { return process_type != kRelauncherProcess; } @@ -166,6 +167,7 @@ bool AtomMainDelegate::DelaySandboxInitialization( const std::string& process_type) { return process_type == kRelauncherProcess; } +#endif std::unique_ptr AtomMainDelegate::CreateContentClient() { diff --git a/atom/app/atom_main_delegate.h b/atom/app/atom_main_delegate.h index 377665f629..58a380bd7d 100644 --- a/atom/app/atom_main_delegate.h +++ b/atom/app/atom_main_delegate.h @@ -27,8 +27,10 @@ class AtomMainDelegate : public brightray::MainDelegate { int RunProcess( const std::string& process_type, const content::MainFunctionParams& main_function_params) override; +#if defined(OS_MACOSX) bool ShouldSendMachPort(const std::string& process_type) override; bool DelaySandboxInitialization(const std::string& process_type) override; +#endif // brightray::MainDelegate: std::unique_ptr CreateContentClient() override; diff --git a/atom/browser/browser.cc b/atom/browser/browser.cc index 093209ef7c..92da10bc5a 100644 --- a/atom/browser/browser.cc +++ b/atom/browser/browser.cc @@ -8,6 +8,7 @@ #include "atom/browser/atom_browser_main_parts.h" #include "atom/browser/native_window.h" +#include "atom/browser/relauncher.h" #include "atom/browser/window_list.h" #include "base/files/file_util.h" #include "base/message_loop/message_loop.h" @@ -33,6 +34,17 @@ Browser* Browser::Get() { return AtomBrowserMainParts::Get()->browser(); } +void Browser::Relaunch(const std::vector& args, + const std::string& app) { + base::FilePath exe_path; + PathService::Get(base::FILE_EXE, &exe_path); + + std::vector args_with_app(args); + args_with_app.insert(args_with_app.begin(), + app.empty() ? exe_path.value() : app); + relauncher::RelaunchApp(args_with_app); +} + void Browser::Quit() { if (is_quiting_) return; diff --git a/atom/browser/browser_mac.mm b/atom/browser/browser_mac.mm index 271b88de4c..4561eab8c9 100644 --- a/atom/browser/browser_mac.mm +++ b/atom/browser/browser_mac.mm @@ -8,27 +8,16 @@ #include "atom/browser/mac/atom_application_delegate.h" #include "atom/browser/mac/dict_util.h" #include "atom/browser/native_window.h" -#include "atom/browser/relauncher.h" #include "atom/browser/window_list.h" #include "base/mac/bundle_locations.h" #include "base/mac/foundation_util.h" #include "base/strings/sys_string_conversions.h" #include "brightray/common/application_info.h" -#include "brightray/common/mac/main_application_bundle.h" #include "net/base/mac/url_conversions.h" #include "url/gurl.h" namespace atom { -void Browser::Relaunch(const std::vector& args, - const std::string& app) { - std::vector args_with_app(args); - args_with_app.insert( - args_with_app.begin(), - app.empty() ? brightray::MainApplicationBundlePath().value() : app); - relauncher::RelaunchApp(args_with_app); -} - void Browser::Focus() { [[AtomApplication sharedApplication] activateIgnoringOtherApps:YES]; } diff --git a/atom/browser/relauncher.cc b/atom/browser/relauncher.cc index 47c77a119a..58c4d57df8 100644 --- a/atom/browser/relauncher.cc +++ b/atom/browser/relauncher.cc @@ -11,18 +11,24 @@ #include "base/files/file_util.h" #include "base/logging.h" #include "base/path_service.h" -#include "base/posix/eintr_wrapper.h" #include "base/process/launch.h" #include "base/strings/stringprintf.h" #include "content/public/common/content_paths.h" #include "content/public/common/content_switches.h" #include "content/public/common/main_function_params.h" +#if defined(OS_POSIX) +#include "base/posix/eintr_wrapper.h" +#endif + namespace relauncher { namespace internal { +#if defined(OS_POSIX) const int kRelauncherSyncFD = STDERR_FILENO + 1; +#endif + const char* kRelauncherTypeArg = "--type=relauncher"; const char* kRelauncherArgSeparator = "---"; @@ -59,6 +65,7 @@ bool RelaunchAppWithHelper(const std::string& helper, relaunch_args.insert(relaunch_args.end(), args.begin(), args.end()); +#if defined(OS_POSIX) int pipe_fds[2]; if (HANDLE_EINTR(pipe(pipe_fds)) != 0) { PLOG(ERROR) << "pipe"; @@ -85,9 +92,12 @@ bool RelaunchAppWithHelper(const std::string& helper, base::FileHandleMappingVector fd_map; fd_map.push_back( std::make_pair(pipe_write_fd.get(), internal::kRelauncherSyncFD)); +#endif base::LaunchOptions options; +#if defined(OS_POSIX) options.fds_to_remap = &fd_map; +#endif if (!base::LaunchProcess(relaunch_args, options).IsValid()) { LOG(ERROR) << "base::LaunchProcess failed"; return false; @@ -96,6 +106,7 @@ bool RelaunchAppWithHelper(const std::string& helper, // The relauncher process is now starting up, or has started up. The // original parent process continues. +#if defined(OS_POSIX) pipe_write_fd.reset(); // close(pipe_fds[1]); // Synchronize with the relauncher process. @@ -113,6 +124,7 @@ bool RelaunchAppWithHelper(const std::string& helper, // Since a byte has been successfully read from the relauncher process, it's // guaranteed to have set up its kqueue monitoring this process for exit. // It's safe to exit now. +#endif return true; } diff --git a/atom/browser/relauncher_linux.cc b/atom/browser/relauncher_linux.cc new file mode 100644 index 0000000000..7ad0c03cbd --- /dev/null +++ b/atom/browser/relauncher_linux.cc @@ -0,0 +1,74 @@ +// Copyright (c) 2016 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "atom/browser/relauncher.h" + +#include +#include +#include + +#include "atom/common/atom_command_line.h" +#include "base/files/file_util.h" +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "base/process/launch.h" + +namespace relauncher { + +namespace internal { + +void RelauncherSynchronizeWithParent() { + base::ScopedFD relauncher_sync_fd(kRelauncherSyncFD); + + // Don't execute signal handlers of SIGUSR2. + sigset_t mask; + sigemptyset(&mask); + sigaddset(&mask, SIGUSR2); + if (sigprocmask(SIG_BLOCK, &mask, NULL) < 0) { + PLOG(ERROR) << "sigprocmask"; + return; + } + + // Create a signalfd that watches for SIGUSR2. + int usr2_fd = signalfd(-1, &mask, 0); + if (usr2_fd < 0) { + PLOG(ERROR) << "signalfd"; + return; + } + + // Send SIGUSR2 to current process when parent process ends. + if (HANDLE_EINTR(prctl(PR_SET_PDEATHSIG, SIGUSR2)) != 0) { + PLOG(ERROR) << "prctl"; + return; + } + + // Write a '\0' character to the pipe. + if (HANDLE_EINTR(write(relauncher_sync_fd.get(), "", 1)) != 1) { + PLOG(ERROR) << "write"; + return; + } + + // Wait the SIGUSR2 signal to happen. + struct signalfd_siginfo si; + HANDLE_EINTR(read(usr2_fd, &si, sizeof(si))); +} + +int LaunchProgram(const std::vector& relauncher_args, + const std::string& program, + const std::vector& args) { + std::vector argv; + argv.reserve(1 + args.size()); + argv.push_back(program); + argv.insert(argv.end(), args.begin(), args.end()); + + base::LaunchOptions options; + options.allow_new_privs = true; + options.new_process_group = true; // detach + base::Process process = base::LaunchProcess(argv, options); + return process.IsValid() ? 0 : 1; +} + +} // namespace internal + +} // namespace relauncher diff --git a/filenames.gypi b/filenames.gypi index 3cee4ca50c..0317aef6b1 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -225,6 +225,7 @@ 'atom/browser/net/url_request_fetch_job.h', 'atom/browser/node_debugger.cc', 'atom/browser/node_debugger.h', + 'atom/browser/relauncher_linux.cc', 'atom/browser/relauncher_mac.cc', 'atom/browser/relauncher.cc', 'atom/browser/relauncher.h', From 0646f6ea9e95295614f166ab6bfe55f4b00e5230 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 2 Jun 2016 19:49:36 +0900 Subject: [PATCH 08/16] Implement Relaunch on Windows --- atom/app/atom_main.cc | 1 + atom/browser/browser.cc | 19 ++++---- atom/browser/browser.h | 5 +- atom/browser/relauncher.cc | 55 ++++++++++++++-------- atom/browser/relauncher.h | 36 +++++++++++---- atom/browser/relauncher_linux.cc | 1 - atom/browser/relauncher_win.cc | 78 ++++++++++++++++++++++++++++++++ atom/common/atom_command_line.cc | 14 ++++++ atom/common/atom_command_line.h | 9 ++++ filenames.gypi | 1 + 10 files changed, 179 insertions(+), 40 deletions(-) create mode 100644 atom/browser/relauncher_win.cc diff --git a/atom/app/atom_main.cc b/atom/app/atom_main.cc index 1e69ba4ba8..a3e7027002 100644 --- a/atom/app/atom_main.cc +++ b/atom/app/atom_main.cc @@ -111,6 +111,7 @@ int APIENTRY wWinMain(HINSTANCE instance, HINSTANCE, wchar_t* cmd, int) { params.instance = instance; params.sandbox_info = &sandbox_info; atom::AtomCommandLine::Init(argc, argv); + atom::AtomCommandLine::InitW(argc, wargv); return content::ContentMain(params); } diff --git a/atom/browser/browser.cc b/atom/browser/browser.cc index 92da10bc5a..88eeaab986 100644 --- a/atom/browser/browser.cc +++ b/atom/browser/browser.cc @@ -8,7 +8,6 @@ #include "atom/browser/atom_browser_main_parts.h" #include "atom/browser/native_window.h" -#include "atom/browser/relauncher.h" #include "atom/browser/window_list.h" #include "base/files/file_util.h" #include "base/message_loop/message_loop.h" @@ -34,15 +33,15 @@ Browser* Browser::Get() { return AtomBrowserMainParts::Get()->browser(); } -void Browser::Relaunch(const std::vector& args, - const std::string& app) { - base::FilePath exe_path; - PathService::Get(base::FILE_EXE, &exe_path); - - std::vector args_with_app(args); - args_with_app.insert(args_with_app.begin(), - app.empty() ? exe_path.value() : app); - relauncher::RelaunchApp(args_with_app); +bool Browser::Relaunch(const base::FilePath& app, + const relauncher::StringVector& args) { + if (app.empty()) { + base::FilePath exe_path; + PathService::Get(base::FILE_EXE, &exe_path); + return relauncher::RelaunchApp(exe_path, args); + } else { + return relauncher::RelaunchApp(app, args); + } } void Browser::Quit() { diff --git a/atom/browser/browser.h b/atom/browser/browser.h index 2b446d621f..018709ff40 100644 --- a/atom/browser/browser.h +++ b/atom/browser/browser.h @@ -13,6 +13,7 @@ #include "base/observer_list.h" #include "base/strings/string16.h" #include "atom/browser/browser_observer.h" +#include "atom/browser/relauncher.h" #include "atom/browser/window_list_observer.h" #include "native_mate/arguments.h" @@ -54,8 +55,8 @@ class Browser : public WindowListObserver { void Shutdown(); // Restart the app. - void Relaunch(const std::vector& args, - const std::string& app); + bool Relaunch(const base::FilePath& app, + const relauncher::StringVector& args); // Focus the application. void Focus(); diff --git a/atom/browser/relauncher.cc b/atom/browser/relauncher.cc index 58c4d57df8..557725e6af 100644 --- a/atom/browser/relauncher.cc +++ b/atom/browser/relauncher.cc @@ -12,7 +12,7 @@ #include "base/logging.h" #include "base/path_service.h" #include "base/process/launch.h" -#include "base/strings/stringprintf.h" +#include "base/strings/string_util.h" #include "content/public/common/content_paths.h" #include "content/public/common/content_switches.h" #include "content/public/common/main_function_params.h" @@ -29,12 +29,13 @@ namespace internal { const int kRelauncherSyncFD = STDERR_FILENO + 1; #endif -const char* kRelauncherTypeArg = "--type=relauncher"; -const char* kRelauncherArgSeparator = "---"; +const CharType* kRelauncherTypeArg = FILE_PATH_LITERAL("--type=relauncher"); +const CharType* kRelauncherArgSeparator = FILE_PATH_LITERAL("---"); } // namespace internal -bool RelaunchApp(const std::vector& args) { +bool RelaunchApp(const base::FilePath& app, + const StringVector& args) { // Use the currently-running application's helper process. The automatic // update feature is careful to leave the currently-running version alone, // so this is safe even if the relaunch is the result of an update having @@ -47,15 +48,16 @@ bool RelaunchApp(const std::vector& args) { return false; } - std::vector relauncher_args; - return RelaunchAppWithHelper(child_path.value(), relauncher_args, args); + StringVector relauncher_args; + return RelaunchAppWithHelper(app, child_path, relauncher_args, args); } -bool RelaunchAppWithHelper(const std::string& helper, - const std::vector& relauncher_args, - const std::vector& args) { - std::vector relaunch_args; - relaunch_args.push_back(helper); +bool RelaunchAppWithHelper(const base::FilePath& app, + const base::FilePath& helper, + const StringVector& relauncher_args, + const StringVector& args) { + StringVector relaunch_args; + relaunch_args.push_back(helper.value()); relaunch_args.push_back(internal::kRelauncherTypeArg); relaunch_args.insert(relaunch_args.end(), @@ -63,6 +65,7 @@ bool RelaunchAppWithHelper(const std::string& helper, relaunch_args.push_back(internal::kRelauncherArgSeparator); + relaunch_args.push_back(app.value()); relaunch_args.insert(relaunch_args.end(), args.begin(), args.end()); #if defined(OS_POSIX) @@ -97,8 +100,12 @@ bool RelaunchAppWithHelper(const std::string& helper, base::LaunchOptions options; #if defined(OS_POSIX) options.fds_to_remap = &fd_map; + base::Process process = base::LaunchProcess(relaunch_args, options); +#elif defined(OS_WIN) + base::Process process = base::LaunchProcess( + base::JoinString(relaunch_args, L" "), options); #endif - if (!base::LaunchProcess(relaunch_args, options).IsValid()) { + if (!process.IsValid()) { LOG(ERROR) << "base::LaunchProcess failed"; return false; } @@ -106,7 +113,15 @@ bool RelaunchAppWithHelper(const std::string& helper, // The relauncher process is now starting up, or has started up. The // original parent process continues. -#if defined(OS_POSIX) +#if defined(OS_WIN) + // Synchronize with the relauncher process. + StringType name = internal::GetWaitEventName(process.Pid()); + HANDLE wait_event = ::CreateEventW(NULL, TRUE, FALSE, name.c_str()); + if (wait_event != NULL) { + WaitForSingleObject(wait_event, 1000); + CloseHandle(wait_event); + } +#elif defined(OS_POSIX) pipe_write_fd.reset(); // close(pipe_fds[1]); // Synchronize with the relauncher process. @@ -129,7 +144,11 @@ bool RelaunchAppWithHelper(const std::string& helper, } int RelauncherMain(const content::MainFunctionParams& main_parameters) { - const std::vector& argv = atom::AtomCommandLine::argv(); +#if defined(OS_WIN) + const StringVector& argv = atom::AtomCommandLine::wargv(); +#else + const StringVector& argv = atom::AtomCommandLine::argv(); +#endif if (argv.size() < 4 || argv[1] != internal::kRelauncherTypeArg) { LOG(ERROR) << "relauncher process invoked with unexpected arguments"; @@ -142,11 +161,11 @@ int RelauncherMain(const content::MainFunctionParams& main_parameters) { // start it in the background. bool in_relaunch_args = false; bool seen_relaunch_executable = false; - std::string relaunch_executable; - std::vector relauncher_args; - std::vector launch_args; + StringType relaunch_executable; + StringVector relauncher_args; + StringVector launch_args; for (size_t argv_index = 2; argv_index < argv.size(); ++argv_index) { - const std::string& arg(argv[argv_index]); + const StringType& arg(argv[argv_index]); if (!in_relaunch_args) { if (arg == internal::kRelauncherArgSeparator) { in_relaunch_args = true; diff --git a/atom/browser/relauncher.h b/atom/browser/relauncher.h index 624a68eaf2..6b725263f8 100644 --- a/atom/browser/relauncher.h +++ b/atom/browser/relauncher.h @@ -32,12 +32,22 @@ #include #include +#include "base/command_line.h" + +#if defined(OS_WIN) +#include "base/process/process_handle.h" +#endif + namespace content { struct MainFunctionParams; } namespace relauncher { +using CharType = base::CommandLine::CharType; +using StringType = base::CommandLine::StringType; +using StringVector = base::CommandLine::StringVector; + // Relaunches the application using the helper application associated with the // currently running instance of Chrome in the parent browser process as the // executable for the relauncher process. |args| is an argv-style vector of @@ -49,7 +59,8 @@ namespace relauncher { // successfully. Returns true on success, although some failures can occur // after this function returns true if, for example, they occur within the // relauncher process. Returns false when the relaunch definitely failed. -bool RelaunchApp(const std::vector& args); +bool RelaunchApp(const base::FilePath& app, + const StringVector& args); // Identical to RelaunchApp, but uses |helper| as the path to the relauncher // process, and allows additional arguments to be supplied to the relauncher @@ -60,22 +71,25 @@ bool RelaunchApp(const std::vector& args); // able to communicate with one another. This variant can be useful to // relaunch the same version of Chrome from another location, using that // location's helper. -bool RelaunchAppWithHelper(const std::string& helper, - const std::vector& relauncher_args, - const std::vector& args); +bool RelaunchAppWithHelper(const base::FilePath& app, + const base::FilePath& helper, + const StringVector& relauncher_args, + const StringVector& args); // The entry point from ChromeMain into the relauncher process. int RelauncherMain(const content::MainFunctionParams& main_parameters); namespace internal { +#if defined(OS_POSIX) // The "magic" file descriptor that the relauncher process' write side of the // pipe shows up on. Chosen to avoid conflicting with stdin, stdout, and // stderr. extern const int kRelauncherSyncFD; +#endif // The "type" argument identifying a relauncher process ("--type=relauncher"). -extern const char* kRelauncherTypeArg; +extern const CharType* kRelauncherTypeArg; // The argument separating arguments intended for the relauncher process from // those intended for the relaunched process. "---" is chosen instead of "--" @@ -84,7 +98,11 @@ extern const char* kRelauncherTypeArg; // arguments intended for the relaunched process, to get the correct settings // for such things as logging and the user-data-dir in case it affects crash // reporting. -extern const char* kRelauncherArgSeparator; +extern const CharType* kRelauncherArgSeparator; + +#if defined(OS_WIN) +StringType GetWaitEventName(base::ProcessId pid); +#endif // In the relauncher process, performs the necessary synchronization steps // with the parent by setting up a kqueue to watch for it to exit, writing a @@ -94,9 +112,9 @@ extern const char* kRelauncherArgSeparator; // process and the best recovery approach is to attempt relaunch anyway. void RelauncherSynchronizeWithParent(); -int LaunchProgram(const std::vector& relauncher_args, - const std::string& program, - const std::vector& argv); +int LaunchProgram(const StringVector& relauncher_args, + const StringType& program, + const StringVector& argv); } // namespace internal diff --git a/atom/browser/relauncher_linux.cc b/atom/browser/relauncher_linux.cc index 7ad0c03cbd..8a3c3050df 100644 --- a/atom/browser/relauncher_linux.cc +++ b/atom/browser/relauncher_linux.cc @@ -8,7 +8,6 @@ #include #include -#include "atom/common/atom_command_line.h" #include "base/files/file_util.h" #include "base/logging.h" #include "base/posix/eintr_wrapper.h" diff --git a/atom/browser/relauncher_win.cc b/atom/browser/relauncher_win.cc new file mode 100644 index 0000000000..4c31466eb6 --- /dev/null +++ b/atom/browser/relauncher_win.cc @@ -0,0 +1,78 @@ +// Copyright (c) 2016 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "atom/browser/relauncher.h" + +#include + +#include "base/process/launch.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "base/win/scoped_handle.h" +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/win_utils.h" +#include "ui/base/win/shell.h" + +namespace relauncher { + +namespace internal { + +namespace { + +const CharType* kWaitEventName = L"ElectronRelauncherWaitEvent"; + +HANDLE GetParentProcessHandle(base::ProcessHandle handle) { + NtQueryInformationProcessFunction NtQueryInformationProcess = nullptr; + ResolveNTFunctionPtr("NtQueryInformationProcess", &NtQueryInformationProcess); + if (!NtQueryInformationProcess) { + LOG(ERROR) << "Unable to get NtQueryInformationProcess"; + return NULL; + } + + PROCESS_BASIC_INFORMATION pbi; + LONG status = NtQueryInformationProcess( + handle, ProcessBasicInformation, + &pbi, sizeof(PROCESS_BASIC_INFORMATION), NULL); + if (!NT_SUCCESS(status)) { + LOG(ERROR) << "NtQueryInformationProcess failed"; + return NULL; + } + + return ::OpenProcess(PROCESS_ALL_ACCESS, TRUE, + pbi.InheritedFromUniqueProcessId); +} + +} // namespace + +StringType GetWaitEventName(base::ProcessId pid) { + return base::StringPrintf(L"%s-%d", kWaitEventName, static_cast(pid)); +} + +void RelauncherSynchronizeWithParent() { + base::Process process = base::Process::Current(); + base::win::ScopedHandle parent_process( + GetParentProcessHandle(process.Handle())); + + // Notify the parent process that it can quit now. + StringType name = internal::GetWaitEventName(process.Pid()); + base::win::ScopedHandle wait_event( + ::CreateEventW(NULL, TRUE, FALSE, name.c_str())); + ::SetEvent(wait_event.Get()); + + // Wait for parent process to quit. + WaitForSingleObject(parent_process.Get(), INFINITE); +} + +int LaunchProgram(const StringVector& relauncher_args, + const StringType& program, + const StringVector& args) { + base::LaunchOptions options; + base::Process process = base::LaunchProcess( + program + L" " + base::JoinString(args, L" "), options); + return process.IsValid() ? 0 : 1; +} + +} // namespace internal + +} // namespace relauncher diff --git a/atom/common/atom_command_line.cc b/atom/common/atom_command_line.cc index 2ac62385ae..08880ffe4a 100644 --- a/atom/common/atom_command_line.cc +++ b/atom/common/atom_command_line.cc @@ -12,6 +12,11 @@ namespace atom { // static std::vector AtomCommandLine::argv_; +#if defined(OS_WIN) +// static +std::vector AtomCommandLine::wargv_; +#endif + // static void AtomCommandLine::Init(int argc, const char* const* argv) { // Hack around with the argv pointer. Used for process.title = "blah" @@ -21,6 +26,15 @@ void AtomCommandLine::Init(int argc, const char* const* argv) { } } +#if defined(OS_WIN) +// static +void AtomCommandLine::InitW(int argc, const wchar_t* const* argv) { + for (int i = 0; i < argc; ++i) { + wargv_.push_back(argv[i]); + } +} +#endif + #if defined(OS_LINUX) // static void AtomCommandLine::InitializeFromCommandLine() { diff --git a/atom/common/atom_command_line.h b/atom/common/atom_command_line.h index b5915533a4..a834ce9256 100644 --- a/atom/common/atom_command_line.h +++ b/atom/common/atom_command_line.h @@ -19,6 +19,11 @@ class AtomCommandLine { static void Init(int argc, const char* const* argv); static std::vector argv() { return argv_; } +#if defined(OS_WIN) + static void InitW(int argc, const wchar_t* const* argv); + static std::vector wargv() { return wargv_; } +#endif + #if defined(OS_LINUX) // On Linux the command line has to be read from base::CommandLine since // it is using zygote. @@ -28,6 +33,10 @@ class AtomCommandLine { private: static std::vector argv_; +#if defined(OS_WIN) + static std::vector wargv_; +#endif + DISALLOW_IMPLICIT_CONSTRUCTORS(AtomCommandLine); }; diff --git a/filenames.gypi b/filenames.gypi index 0317aef6b1..5f1fdc3b14 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -227,6 +227,7 @@ 'atom/browser/node_debugger.h', 'atom/browser/relauncher_linux.cc', 'atom/browser/relauncher_mac.cc', + 'atom/browser/relauncher_win.cc', 'atom/browser/relauncher.cc', 'atom/browser/relauncher.h', 'atom/browser/render_process_preferences.cc', From 0d066de53e5a256422c575e762788667db441f86 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 2 Jun 2016 20:32:29 +0900 Subject: [PATCH 09/16] Make sure the new instance inherite cwd on mac --- atom/browser/relauncher.cc | 22 +++------- atom/browser/relauncher.h | 4 +- atom/browser/relauncher_linux.cc | 10 +---- atom/browser/relauncher_mac.cc | 71 ++++---------------------------- atom/browser/relauncher_win.cc | 7 ++-- 5 files changed, 18 insertions(+), 96 deletions(-) diff --git a/atom/browser/relauncher.cc b/atom/browser/relauncher.cc index 557725e6af..dce4f46b9a 100644 --- a/atom/browser/relauncher.cc +++ b/atom/browser/relauncher.cc @@ -34,8 +34,7 @@ const CharType* kRelauncherArgSeparator = FILE_PATH_LITERAL("---"); } // namespace internal -bool RelaunchApp(const base::FilePath& app, - const StringVector& args) { +bool RelaunchApp(const base::FilePath& app, const StringVector& args) { // Use the currently-running application's helper process. The automatic // update feature is careful to leave the currently-running version alone, // so this is safe even if the relaunch is the result of an update having @@ -160,10 +159,9 @@ int RelauncherMain(const content::MainFunctionParams& main_parameters) { // Figure out what to execute, what arguments to pass it, and whether to // start it in the background. bool in_relaunch_args = false; - bool seen_relaunch_executable = false; StringType relaunch_executable; StringVector relauncher_args; - StringVector launch_args; + StringVector launch_argv; for (size_t argv_index = 2; argv_index < argv.size(); ++argv_index) { const StringType& arg(argv[argv_index]); if (!in_relaunch_args) { @@ -173,26 +171,16 @@ int RelauncherMain(const content::MainFunctionParams& main_parameters) { relauncher_args.push_back(arg); } } else { - if (!seen_relaunch_executable) { - // The first argument after kRelauncherBackgroundArg is the path to - // the executable file or .app bundle directory. The Launch Services - // interface wants this separate from the rest of the arguments. In - // the relaunched process, this path will still be visible at argv[0]. - relaunch_executable.assign(arg); - seen_relaunch_executable = true; - } else { - launch_args.push_back(arg); - } + launch_argv.push_back(arg); } } - if (!seen_relaunch_executable) { + if (launch_argv.empty()) { LOG(ERROR) << "nothing to relaunch"; return 1; } - if (internal::LaunchProgram(relauncher_args, relaunch_executable, - launch_args) != 0) { + if (internal::LaunchProgram(relauncher_args, launch_argv) != 0) { LOG(ERROR) << "failed to launch program"; return 1; } diff --git a/atom/browser/relauncher.h b/atom/browser/relauncher.h index 6b725263f8..f6722f946d 100644 --- a/atom/browser/relauncher.h +++ b/atom/browser/relauncher.h @@ -59,8 +59,7 @@ using StringVector = base::CommandLine::StringVector; // successfully. Returns true on success, although some failures can occur // after this function returns true if, for example, they occur within the // relauncher process. Returns false when the relaunch definitely failed. -bool RelaunchApp(const base::FilePath& app, - const StringVector& args); +bool RelaunchApp(const base::FilePath& app, const StringVector& args); // Identical to RelaunchApp, but uses |helper| as the path to the relauncher // process, and allows additional arguments to be supplied to the relauncher @@ -113,7 +112,6 @@ StringType GetWaitEventName(base::ProcessId pid); void RelauncherSynchronizeWithParent(); int LaunchProgram(const StringVector& relauncher_args, - const StringType& program, const StringVector& argv); } // namespace internal diff --git a/atom/browser/relauncher_linux.cc b/atom/browser/relauncher_linux.cc index 8a3c3050df..2fbbd47faf 100644 --- a/atom/browser/relauncher_linux.cc +++ b/atom/browser/relauncher_linux.cc @@ -53,14 +53,8 @@ void RelauncherSynchronizeWithParent() { HANDLE_EINTR(read(usr2_fd, &si, sizeof(si))); } -int LaunchProgram(const std::vector& relauncher_args, - const std::string& program, - const std::vector& args) { - std::vector argv; - argv.reserve(1 + args.size()); - argv.push_back(program); - argv.insert(argv.end(), args.begin(), args.end()); - +int LaunchProgram(const StringVector& relauncher_args, + const StringVector& argv) { base::LaunchOptions options; options.allow_new_privs = true; options.new_process_group = true; // detach diff --git a/atom/browser/relauncher_mac.cc b/atom/browser/relauncher_mac.cc index a537e311ce..ef26f8441d 100644 --- a/atom/browser/relauncher_mac.cc +++ b/atom/browser/relauncher_mac.cc @@ -4,8 +4,6 @@ #include "atom/browser/relauncher.h" -#include - #include #include #include @@ -13,9 +11,8 @@ #include "base/files/file_util.h" #include "base/logging.h" +#include "base/process/launch.h" #include "base/mac/mac_logging.h" -#include "base/mac/mac_util.h" -#include "base/mac/scoped_cftyperef.h" #include "base/posix/eintr_wrapper.h" #include "base/strings/sys_string_conversions.h" @@ -23,16 +20,6 @@ namespace relauncher { namespace internal { -namespace { - -// The beginning of the "process serial number" argument that Launch Services -// sometimes inserts into command lines. A process serial number is only valid -// for a single process, so any PSN arguments will be stripped from command -// lines during relaunch to avoid confusion. -const char kPSNArg[] = "-psn_"; - -} // namespace - void RelauncherSynchronizeWithParent() { base::ScopedFD relauncher_sync_fd(kRelauncherSyncFD); @@ -90,56 +77,12 @@ void RelauncherSynchronizeWithParent() { } } -int LaunchProgram(const std::vector& relauncher_args, - const std::string& program, - const std::vector& argv) { - // The capacity for relaunch_args is 4 less than argc, because it - // won't contain the argv[0] of the relauncher process, the - // RelauncherTypeArg() at argv[1], kRelauncherArgSeparator, or the - // executable path of the process to be launched. - base::ScopedCFTypeRef relaunch_args( - CFArrayCreateMutable(NULL, argv.size() - 4, &kCFTypeArrayCallBacks)); - if (!relaunch_args) { - LOG(ERROR) << "CFArrayCreateMutable"; - return 1; - } - - for (const std::string& arg : argv) { - // Strip any -psn_ arguments, as they apply to a specific process. - if (arg.compare(0, strlen(kPSNArg), kPSNArg) == 0) - continue; - - base::ScopedCFTypeRef arg_cf(base::SysUTF8ToCFStringRef(arg)); - if (!arg_cf) { - LOG(ERROR) << "base::SysUTF8ToCFStringRef failed for " << arg; - return 1; - } - CFArrayAppendValue(relaunch_args, arg_cf); - } - - FSRef app_fsref; - if (!base::mac::FSRefFromPath(program, &app_fsref)) { - LOG(ERROR) << "base::mac::FSRefFromPath failed for " << program; - return 1; - } - - LSApplicationParameters ls_parameters = { - 0, // version - kLSLaunchDefaults | kLSLaunchAndDisplayErrors | kLSLaunchNewInstance, - &app_fsref, - NULL, // asyncLaunchRefCon - NULL, // environment - relaunch_args, - NULL // initialEvent - }; - - OSStatus status = LSOpenApplication(&ls_parameters, NULL); - if (status != noErr) { - OSSTATUS_LOG(ERROR, status) << "LSOpenApplication"; - return 1; - } - - return 0; +int LaunchProgram(const StringVector& relauncher_args, + const StringVector& argv) { + base::LaunchOptions options; + options.new_process_group = true; // detach + base::Process process = base::LaunchProcess(argv, options); + return process.IsValid() ? 0 : 1; } } // namespace internal diff --git a/atom/browser/relauncher_win.cc b/atom/browser/relauncher_win.cc index 4c31466eb6..bb7617f29c 100644 --- a/atom/browser/relauncher_win.cc +++ b/atom/browser/relauncher_win.cc @@ -65,11 +65,10 @@ void RelauncherSynchronizeWithParent() { } int LaunchProgram(const StringVector& relauncher_args, - const StringType& program, - const StringVector& args) { + const StringVector& argv) { base::LaunchOptions options; - base::Process process = base::LaunchProcess( - program + L" " + base::JoinString(args, L" "), options); + base::Process process = + base::LaunchProcess(base::JoinString(args, L" "), options); return process.IsValid() ? 0 : 1; } From 9a08cbce274a0b08062a8cc1a81b4e07f4bffe81 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 2 Jun 2016 20:41:59 +0900 Subject: [PATCH 10/16] Uniform when to use args or argv --- atom/browser/browser.cc | 8 ++++++-- atom/browser/relauncher.cc | 32 +++++++++++++++----------------- atom/browser/relauncher.h | 5 ++--- atom/browser/relauncher_win.cc | 2 +- 4 files changed, 24 insertions(+), 23 deletions(-) diff --git a/atom/browser/browser.cc b/atom/browser/browser.cc index 88eeaab986..41d4ae5754 100644 --- a/atom/browser/browser.cc +++ b/atom/browser/browser.cc @@ -9,6 +9,7 @@ #include "atom/browser/atom_browser_main_parts.h" #include "atom/browser/native_window.h" #include "atom/browser/window_list.h" +#include "atom/common/atom_command_line.h" #include "base/files/file_util.h" #include "base/message_loop/message_loop.h" #include "base/path_service.h" @@ -35,13 +36,16 @@ Browser* Browser::Get() { bool Browser::Relaunch(const base::FilePath& app, const relauncher::StringVector& args) { + relauncher::StringVector argv; if (app.empty()) { base::FilePath exe_path; PathService::Get(base::FILE_EXE, &exe_path); - return relauncher::RelaunchApp(exe_path, args); + argv.push_back(exe_path.value()); } else { - return relauncher::RelaunchApp(app, args); + argv.push_back(app.value()); } + + return relauncher::RelaunchApp(argv); } void Browser::Quit() { diff --git a/atom/browser/relauncher.cc b/atom/browser/relauncher.cc index dce4f46b9a..5362a7b78a 100644 --- a/atom/browser/relauncher.cc +++ b/atom/browser/relauncher.cc @@ -34,7 +34,7 @@ const CharType* kRelauncherArgSeparator = FILE_PATH_LITERAL("---"); } // namespace internal -bool RelaunchApp(const base::FilePath& app, const StringVector& args) { +bool RelaunchApp(const StringVector& argv) { // Use the currently-running application's helper process. The automatic // update feature is careful to leave the currently-running version alone, // so this is safe even if the relaunch is the result of an update having @@ -48,24 +48,22 @@ bool RelaunchApp(const base::FilePath& app, const StringVector& args) { } StringVector relauncher_args; - return RelaunchAppWithHelper(app, child_path, relauncher_args, args); + return RelaunchAppWithHelper(child_path, relauncher_args, argv); } -bool RelaunchAppWithHelper(const base::FilePath& app, - const base::FilePath& helper, +bool RelaunchAppWithHelper(const base::FilePath& helper, const StringVector& relauncher_args, - const StringVector& args) { - StringVector relaunch_args; - relaunch_args.push_back(helper.value()); - relaunch_args.push_back(internal::kRelauncherTypeArg); + const StringVector& argv) { + StringVector relaunch_argv; + relaunch_argv.push_back(helper.value()); + relaunch_argv.push_back(internal::kRelauncherTypeArg); - relaunch_args.insert(relaunch_args.end(), + relaunch_argv.insert(relaunch_argv.end(), relauncher_args.begin(), relauncher_args.end()); - relaunch_args.push_back(internal::kRelauncherArgSeparator); + relaunch_argv.push_back(internal::kRelauncherArgSeparator); - relaunch_args.push_back(app.value()); - relaunch_args.insert(relaunch_args.end(), args.begin(), args.end()); + relaunch_argv.insert(relaunch_argv.end(), argv.begin(), argv.end()); #if defined(OS_POSIX) int pipe_fds[2]; @@ -99,10 +97,10 @@ bool RelaunchAppWithHelper(const base::FilePath& app, base::LaunchOptions options; #if defined(OS_POSIX) options.fds_to_remap = &fd_map; - base::Process process = base::LaunchProcess(relaunch_args, options); + base::Process process = base::LaunchProcess(relaunch_argv, options); #elif defined(OS_WIN) base::Process process = base::LaunchProcess( - base::JoinString(relaunch_args, L" "), options); + base::JoinString(relaunch_argv, L" "), options); #endif if (!process.IsValid()) { LOG(ERROR) << "base::LaunchProcess failed"; @@ -158,15 +156,15 @@ int RelauncherMain(const content::MainFunctionParams& main_parameters) { // Figure out what to execute, what arguments to pass it, and whether to // start it in the background. - bool in_relaunch_args = false; + bool in_relauncher_args = false; StringType relaunch_executable; StringVector relauncher_args; StringVector launch_argv; for (size_t argv_index = 2; argv_index < argv.size(); ++argv_index) { const StringType& arg(argv[argv_index]); - if (!in_relaunch_args) { + if (!in_relauncher_args) { if (arg == internal::kRelauncherArgSeparator) { - in_relaunch_args = true; + in_relauncher_args = true; } else { relauncher_args.push_back(arg); } diff --git a/atom/browser/relauncher.h b/atom/browser/relauncher.h index f6722f946d..c19557b330 100644 --- a/atom/browser/relauncher.h +++ b/atom/browser/relauncher.h @@ -59,7 +59,7 @@ using StringVector = base::CommandLine::StringVector; // successfully. Returns true on success, although some failures can occur // after this function returns true if, for example, they occur within the // relauncher process. Returns false when the relaunch definitely failed. -bool RelaunchApp(const base::FilePath& app, const StringVector& args); +bool RelaunchApp(const StringVector& argv); // Identical to RelaunchApp, but uses |helper| as the path to the relauncher // process, and allows additional arguments to be supplied to the relauncher @@ -70,8 +70,7 @@ bool RelaunchApp(const base::FilePath& app, const StringVector& args); // able to communicate with one another. This variant can be useful to // relaunch the same version of Chrome from another location, using that // location's helper. -bool RelaunchAppWithHelper(const base::FilePath& app, - const base::FilePath& helper, +bool RelaunchAppWithHelper(const base::FilePath& helper, const StringVector& relauncher_args, const StringVector& args); diff --git a/atom/browser/relauncher_win.cc b/atom/browser/relauncher_win.cc index bb7617f29c..a759e7c2ff 100644 --- a/atom/browser/relauncher_win.cc +++ b/atom/browser/relauncher_win.cc @@ -68,7 +68,7 @@ int LaunchProgram(const StringVector& relauncher_args, const StringVector& argv) { base::LaunchOptions options; base::Process process = - base::LaunchProcess(base::JoinString(args, L" "), options); + base::LaunchProcess(base::JoinString(argv, L" "), options); return process.IsValid() ? 0 : 1; } From 8435f1c900bb482b2daa9e995d7f91db16b69053 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 2 Jun 2016 20:47:47 +0900 Subject: [PATCH 11/16] Allow using current argv for relaunch --- atom/browser/browser.cc | 16 +++++++++++++++- atom/browser/browser.h | 3 ++- atom/browser/relauncher_win.cc | 2 +- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/atom/browser/browser.cc b/atom/browser/browser.cc index 41d4ae5754..14aebf3db5 100644 --- a/atom/browser/browser.cc +++ b/atom/browser/browser.cc @@ -34,9 +34,21 @@ Browser* Browser::Get() { return AtomBrowserMainParts::Get()->browser(); } -bool Browser::Relaunch(const base::FilePath& app, +bool Browser::Relaunch(bool override_argv, + const base::FilePath& app, const relauncher::StringVector& args) { + if (!override_argv) { +#if defined(OS_WIN) + const relauncher::StringVector& argv = atom::AtomCommandLine::wargv(); +#else + const relauncher::StringVector& argv = atom::AtomCommandLine::argv(); +#endif + return relauncher::RelaunchApp(argv); + } + relauncher::StringVector argv; + argv.reserve(1 + args.size()); + if (app.empty()) { base::FilePath exe_path; PathService::Get(base::FILE_EXE, &exe_path); @@ -45,6 +57,8 @@ bool Browser::Relaunch(const base::FilePath& app, argv.push_back(app.value()); } + argv.insert(argv.end(), args.begin(), args.end()); + return relauncher::RelaunchApp(argv); } diff --git a/atom/browser/browser.h b/atom/browser/browser.h index 018709ff40..05248a1e94 100644 --- a/atom/browser/browser.h +++ b/atom/browser/browser.h @@ -55,7 +55,8 @@ class Browser : public WindowListObserver { void Shutdown(); // Restart the app. - bool Relaunch(const base::FilePath& app, + bool Relaunch(bool override_argv, + const base::FilePath& app, const relauncher::StringVector& args); // Focus the application. diff --git a/atom/browser/relauncher_win.cc b/atom/browser/relauncher_win.cc index a759e7c2ff..1a122782a2 100644 --- a/atom/browser/relauncher_win.cc +++ b/atom/browser/relauncher_win.cc @@ -31,7 +31,7 @@ HANDLE GetParentProcessHandle(base::ProcessHandle handle) { } PROCESS_BASIC_INFORMATION pbi; - LONG status = NtQueryInformationProcess( + LONG status = NtQueryInformationProcess( handle, ProcessBasicInformation, &pbi, sizeof(PROCESS_BASIC_INFORMATION), NULL); if (!NT_SUCCESS(status)) { From 3de41fb22d7c93063162fb1595716274ce4eb25e Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 2 Jun 2016 21:10:39 +0900 Subject: [PATCH 12/16] Correctly quotes the argv on Windows --- atom/browser/relauncher.cc | 3 +- atom/browser/relauncher.h | 2 ++ atom/browser/relauncher_win.cc | 55 ++++++++++++++++++++++++++++++++-- 3 files changed, 56 insertions(+), 4 deletions(-) diff --git a/atom/browser/relauncher.cc b/atom/browser/relauncher.cc index 5362a7b78a..9610d80693 100644 --- a/atom/browser/relauncher.cc +++ b/atom/browser/relauncher.cc @@ -12,7 +12,6 @@ #include "base/logging.h" #include "base/path_service.h" #include "base/process/launch.h" -#include "base/strings/string_util.h" #include "content/public/common/content_paths.h" #include "content/public/common/content_switches.h" #include "content/public/common/main_function_params.h" @@ -100,7 +99,7 @@ bool RelaunchAppWithHelper(const base::FilePath& helper, base::Process process = base::LaunchProcess(relaunch_argv, options); #elif defined(OS_WIN) base::Process process = base::LaunchProcess( - base::JoinString(relaunch_argv, L" "), options); + internal::ArgvToCommandLineString(relaunch_argv), options); #endif if (!process.IsValid()) { LOG(ERROR) << "base::LaunchProcess failed"; diff --git a/atom/browser/relauncher.h b/atom/browser/relauncher.h index c19557b330..10f2e51287 100644 --- a/atom/browser/relauncher.h +++ b/atom/browser/relauncher.h @@ -100,6 +100,8 @@ extern const CharType* kRelauncherArgSeparator; #if defined(OS_WIN) StringType GetWaitEventName(base::ProcessId pid); + +StringType ArgvToCommandLineString(const StringVector& argv); #endif // In the relauncher process, performs the necessary synchronization steps diff --git a/atom/browser/relauncher_win.cc b/atom/browser/relauncher_win.cc index 1a122782a2..1aebb515ec 100644 --- a/atom/browser/relauncher_win.cc +++ b/atom/browser/relauncher_win.cc @@ -7,7 +7,6 @@ #include #include "base/process/launch.h" -#include "base/strings/string_util.h" #include "base/strings/stringprintf.h" #include "base/win/scoped_handle.h" #include "sandbox/win/src/nt_internals.h" @@ -43,12 +42,64 @@ HANDLE GetParentProcessHandle(base::ProcessHandle handle) { pbi.InheritedFromUniqueProcessId); } +StringType AddQuoteForArg(const StringType& arg) { + // We follow the quoting rules of CommandLineToArgvW. + // http://msdn.microsoft.com/en-us/library/17w5ykft.aspx + std::wstring quotable_chars(L" \\\""); + if (arg.find_first_of(quotable_chars) == std::wstring::npos) { + // No quoting necessary. + return arg; + } + + std::wstring out; + out.push_back(L'"'); + for (size_t i = 0; i < arg.size(); ++i) { + if (arg[i] == '\\') { + // Find the extent of this run of backslashes. + size_t start = i, end = start + 1; + for (; end < arg.size() && arg[end] == '\\'; ++end) {} + size_t backslash_count = end - start; + + // Backslashes are escapes only if the run is followed by a double quote. + // Since we also will end the string with a double quote, we escape for + // either a double quote or the end of the string. + if (end == arg.size() || arg[end] == '"') { + // To quote, we need to output 2x as many backslashes. + backslash_count *= 2; + } + for (size_t j = 0; j < backslash_count; ++j) + out.push_back('\\'); + + // Advance i to one before the end to balance i++ in loop. + i = end - 1; + } else if (arg[i] == '"') { + out.push_back('\\'); + out.push_back('"'); + } else { + out.push_back(arg[i]); + } + } + out.push_back('"'); + + return out; +} + } // namespace StringType GetWaitEventName(base::ProcessId pid) { return base::StringPrintf(L"%s-%d", kWaitEventName, static_cast(pid)); } +StringType ArgvToCommandLineString(const StringVector& argv) { + StringType command_line; + for (const StringType& arg : argv) { + if (!command_line.empty()) + command_line += L' '; + command_line += AddQuoteForArg(arg); + } + return command_line; +} + void RelauncherSynchronizeWithParent() { base::Process process = base::Process::Current(); base::win::ScopedHandle parent_process( @@ -68,7 +119,7 @@ int LaunchProgram(const StringVector& relauncher_args, const StringVector& argv) { base::LaunchOptions options; base::Process process = - base::LaunchProcess(base::JoinString(argv, L" "), options); + base::LaunchProcess(ArgvToCommandLineString(argv), options); return process.IsValid() ? 0 : 1; } From 91a9e67dcaf740db59f7f763c80c0170058b14fc Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 2 Jun 2016 21:49:29 +0900 Subject: [PATCH 13/16] Provide a flexible API for app.relaunch --- atom/browser/api/atom_api_app.cc | 43 ++++++++++++++++++++++++++++++-- atom/browser/api/atom_api_app.h | 3 ++- atom/browser/browser.cc | 29 --------------------- atom/browser/browser.h | 6 ----- 4 files changed, 43 insertions(+), 38 deletions(-) diff --git a/atom/browser/api/atom_api_app.cc b/atom/browser/api/atom_api_app.cc index c5bce573dd..76fbdb1859 100644 --- a/atom/browser/api/atom_api_app.cc +++ b/atom/browser/api/atom_api_app.cc @@ -14,6 +14,8 @@ #include "atom/browser/atom_browser_main_parts.h" #include "atom/browser/browser.h" #include "atom/browser/login_handler.h" +#include "atom/browser/relauncher.h" +#include "atom/common/atom_command_line.h" #include "atom/common/native_mate_converters/callback.h" #include "atom/common/native_mate_converters/file_path_converter.h" #include "atom/common/native_mate_converters/gurl_converter.h" @@ -408,6 +410,43 @@ void App::ReleaseSingleInstance() { } } +bool App::Relaunch(mate::Arguments* js_args) { + // Parse parameters. + bool override_argv = false; + base::FilePath exec_path; + relauncher::StringVector args; + + mate::Dictionary options; + if (js_args->GetNext(&options)) { + if (options.Get("execPath", &exec_path) || options.Get("args", &args)) + override_argv = true; + } + + if (!override_argv) { +#if defined(OS_WIN) + const relauncher::StringVector& argv = atom::AtomCommandLine::wargv(); +#else + const relauncher::StringVector& argv = atom::AtomCommandLine::argv(); +#endif + return relauncher::RelaunchApp(argv); + } + + relauncher::StringVector argv; + argv.reserve(1 + args.size()); + + if (exec_path.empty()) { + base::FilePath current_exe_path; + PathService::Get(base::FILE_EXE, ¤t_exe_path); + argv.push_back(current_exe_path.value()); + } else { + argv.push_back(exec_path.value()); + } + + argv.insert(argv.end(), args.begin(), args.end()); + + return relauncher::RelaunchApp(argv); +} + #if defined(USE_NSS_CERTS) void App::ImportCertificate( const base::DictionaryValue& options, @@ -450,7 +489,6 @@ void App::BuildPrototype( mate::ObjectTemplateBuilder(isolate, prototype) .SetMethod("quit", base::Bind(&Browser::Quit, browser)) .SetMethod("exit", base::Bind(&Browser::Exit, browser)) - .SetMethod("relaunch", base::Bind(&Browser::Relaunch, browser)) .SetMethod("focus", base::Bind(&Browser::Focus, browser)) .SetMethod("getVersion", base::Bind(&Browser::GetVersion, browser)) .SetMethod("setVersion", base::Bind(&Browser::SetVersion, browser)) @@ -489,7 +527,8 @@ void App::BuildPrototype( .SetMethod("importCertificate", &App::ImportCertificate) #endif .SetMethod("makeSingleInstance", &App::MakeSingleInstance) - .SetMethod("releaseSingleInstance", &App::ReleaseSingleInstance); + .SetMethod("releaseSingleInstance", &App::ReleaseSingleInstance) + .SetMethod("relaunch", &App::Relaunch); } } // namespace api diff --git a/atom/browser/api/atom_api_app.h b/atom/browser/api/atom_api_app.h index 979f61f0d2..e2bcb4583d 100644 --- a/atom/browser/api/atom_api_app.h +++ b/atom/browser/api/atom_api_app.h @@ -106,10 +106,11 @@ class App : public AtomBrowserClient::Delegate, const base::FilePath& path); void SetDesktopName(const std::string& desktop_name); + std::string GetLocale(); bool MakeSingleInstance( const ProcessSingleton::NotificationCallback& callback); void ReleaseSingleInstance(); - std::string GetLocale(); + bool Relaunch(mate::Arguments* args); #if defined(USE_NSS_CERTS) void ImportCertificate(const base::DictionaryValue& options, diff --git a/atom/browser/browser.cc b/atom/browser/browser.cc index 14aebf3db5..093209ef7c 100644 --- a/atom/browser/browser.cc +++ b/atom/browser/browser.cc @@ -9,7 +9,6 @@ #include "atom/browser/atom_browser_main_parts.h" #include "atom/browser/native_window.h" #include "atom/browser/window_list.h" -#include "atom/common/atom_command_line.h" #include "base/files/file_util.h" #include "base/message_loop/message_loop.h" #include "base/path_service.h" @@ -34,34 +33,6 @@ Browser* Browser::Get() { return AtomBrowserMainParts::Get()->browser(); } -bool Browser::Relaunch(bool override_argv, - const base::FilePath& app, - const relauncher::StringVector& args) { - if (!override_argv) { -#if defined(OS_WIN) - const relauncher::StringVector& argv = atom::AtomCommandLine::wargv(); -#else - const relauncher::StringVector& argv = atom::AtomCommandLine::argv(); -#endif - return relauncher::RelaunchApp(argv); - } - - relauncher::StringVector argv; - argv.reserve(1 + args.size()); - - if (app.empty()) { - base::FilePath exe_path; - PathService::Get(base::FILE_EXE, &exe_path); - argv.push_back(exe_path.value()); - } else { - argv.push_back(app.value()); - } - - argv.insert(argv.end(), args.begin(), args.end()); - - return relauncher::RelaunchApp(argv); -} - void Browser::Quit() { if (is_quiting_) return; diff --git a/atom/browser/browser.h b/atom/browser/browser.h index 05248a1e94..18d0c97c93 100644 --- a/atom/browser/browser.h +++ b/atom/browser/browser.h @@ -13,7 +13,6 @@ #include "base/observer_list.h" #include "base/strings/string16.h" #include "atom/browser/browser_observer.h" -#include "atom/browser/relauncher.h" #include "atom/browser/window_list_observer.h" #include "native_mate/arguments.h" @@ -54,11 +53,6 @@ class Browser : public WindowListObserver { // Cleanup everything and shutdown the application gracefully. void Shutdown(); - // Restart the app. - bool Relaunch(bool override_argv, - const base::FilePath& app, - const relauncher::StringVector& args); - // Focus the application. void Focus(); From d3fe80991c153856a8103389f7b3e69fa4a4f290 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 2 Jun 2016 22:06:27 +0900 Subject: [PATCH 14/16] docs: app.relaunch --- docs/api/app.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/docs/api/app.md b/docs/api/app.md index 265f328eb9..becd6039c7 100644 --- a/docs/api/app.md +++ b/docs/api/app.md @@ -276,6 +276,33 @@ Exits immediately with `exitCode`. All windows will be closed immediately without asking user and the `before-quit` and `will-quit` events will not be emitted. +### `app.relaunch([options])` + +* `options` Object (optional) + * `args` Array (optional) + * `execPath` String (optional) + +Relaunches the app when current instance exits. + +By default the new instance will use the same working directory and command line +arguments with current instance. When `args` is specified, the `args` will be +passed as command line arguments instead. When `execPath` is specified, the +`execPath` will be executed for relaunch instead of current app. + +Note that this method does not quit the app when executed, you have to call +`app.quit` or `app.exit` after calling `app.relaunch` to make the app restart. + +When `app.relaunch` is called for multiple times, multiple instances will be +started after current instance exited. + +An example of restarting current instance immediately and adding a new command +line argument to the new instance: + +```javascript +app.relaunch({args: process.argv.slice(1) + ['--relaunch']}) +app.exit(0) +``` + ### `app.focus()` On Linux, focuses on the first visible window. On OS X, makes the application From be6ed84ff2ac3aa4d5580a383531b4780b134de6 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Fri, 3 Jun 2016 12:08:45 +0900 Subject: [PATCH 15/16] args should always be checked --- atom/browser/api/atom_api_app.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/atom/browser/api/atom_api_app.cc b/atom/browser/api/atom_api_app.cc index 76fbdb1859..5d4aafca69 100644 --- a/atom/browser/api/atom_api_app.cc +++ b/atom/browser/api/atom_api_app.cc @@ -418,7 +418,7 @@ bool App::Relaunch(mate::Arguments* js_args) { mate::Dictionary options; if (js_args->GetNext(&options)) { - if (options.Get("execPath", &exec_path) || options.Get("args", &args)) + if (options.Get("execPath", &exec_path) | options.Get("args", &args)) override_argv = true; } From 707d68f719a209f7ced9b08108a4892372ebb79d Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Fri, 3 Jun 2016 12:12:20 +0900 Subject: [PATCH 16/16] spec: Add test case for app.relaunch --- spec/api-app-spec.js | 50 +++++++++++++++++++++++++ spec/fixtures/api/quit-app/package.json | 2 +- spec/fixtures/api/relaunch/main.js | 25 +++++++++++++ spec/fixtures/api/relaunch/package.json | 5 +++ spec/package.json | 2 +- 5 files changed, 82 insertions(+), 2 deletions(-) create mode 100644 spec/fixtures/api/relaunch/main.js create mode 100644 spec/fixtures/api/relaunch/package.json diff --git a/spec/api-app-spec.js b/spec/api-app-spec.js index df356ecb90..260bdd3154 100644 --- a/spec/api-app-spec.js +++ b/spec/api-app-spec.js @@ -1,6 +1,7 @@ const assert = require('assert') const ChildProcess = require('child_process') const https = require('https') +const net = require('net') const fs = require('fs') const path = require('path') const {remote} = require('electron') @@ -108,6 +109,55 @@ describe('app module', function () { }) }) + describe('app.relaunch', function () { + let server = null + const socketPath = process.platform === 'win32' ? + '\\\\.\\pipe\\electron-app-relaunch' : + '/tmp/electron-app-relaunch' + + beforeEach(function (done) { + fs.unlink(socketPath, (error) => { + server = net.createServer() + server.listen(socketPath) + done() + }) + }) + + afterEach(function (done) { + server.close(() => { + if (process.platform === 'win32') { + done() + } else { + fs.unlink(socketPath, (error) => { + done() + }) + } + }) + }) + + it('relaunches the app', function (done) { + this.timeout(100000) + let state = 'none' + server.once('error', (error) => { + done(error) + }) + server.on('connection', (client) => { + client.once('data', function (data) { + if (String(data) === 'false' && state === 'none') { + state = 'first-launch' + } else if (String(data) === 'true' && state === 'first-launch') { + done() + } else { + done(`Unexpected state: ${state}`) + } + }) + }) + + const appPath = path.join(__dirname, 'fixtures', 'api', 'relaunch') + ChildProcess.spawn(remote.process.execPath, [appPath]) + }) + }) + describe('app.setUserActivity(type, userInfo)', function () { if (process.platform !== 'darwin') { return diff --git a/spec/fixtures/api/quit-app/package.json b/spec/fixtures/api/quit-app/package.json index ea5bb1643b..fbdee7f90f 100644 --- a/spec/fixtures/api/quit-app/package.json +++ b/spec/fixtures/api/quit-app/package.json @@ -1,4 +1,4 @@ { - "name": "quit-app", + "name": "electron-quit-app", "main": "main.js" } diff --git a/spec/fixtures/api/relaunch/main.js b/spec/fixtures/api/relaunch/main.js new file mode 100644 index 0000000000..74cafc6f0d --- /dev/null +++ b/spec/fixtures/api/relaunch/main.js @@ -0,0 +1,25 @@ +const {app, dialog} = require('electron') +const net = require('net') + +const socketPath = process.platform === 'win32' ? + '\\\\.\\pipe\\electron-app-relaunch' : + '/tmp/electron-app-relaunch' + +process.on('uncaughtException', () => { + app.exit(1) +}) + +app.once('ready', () => { + let lastArg = process.argv[process.argv.length - 1] + const client = net.connect(socketPath) + client.once('connect', () => { + client.end(String(lastArg === '--second')) + }) + client.once('end', () => { + app.exit(0) + }) + + if (lastArg !== '--second') { + app.relaunch({args: process.argv.slice(1).concat('--second')}) + } +}) diff --git a/spec/fixtures/api/relaunch/package.json b/spec/fixtures/api/relaunch/package.json new file mode 100644 index 0000000000..dbaabc8489 --- /dev/null +++ b/spec/fixtures/api/relaunch/package.json @@ -0,0 +1,5 @@ +{ + "name": "electron-app-relaunch", + "main": "main.js" +} + diff --git a/spec/package.json b/spec/package.json index 0439f4b0ec..5abf89f57e 100644 --- a/spec/package.json +++ b/spec/package.json @@ -13,7 +13,7 @@ "temp": "0.8.1", "walkdir": "0.0.7", "ws": "0.7.2", - "yargs": "^3.31.0" + "yargs": "^4.7.1" }, "optionalDependencies": { "ffi": "2.0.0",