diff --git a/atom/app/atom_main_delegate.cc b/atom/app/atom_main_delegate.cc index dbd271239332..c601fd681e7e 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 d48f13b931a4..41b67f38b5e2 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 f072a725336e..4134ef54225c 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 000000000000..39c478f1614b --- /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 3ce737f22a04..3cee4ca50c5b 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',