| // 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" |
| |
| #import <AppKit/AppKit.h> |
| |
| #include <AvailabilityMacros.h> |
| #include <crt_externs.h> |
| #include <dlfcn.h> |
| #include <stddef.h> |
| #include <string.h> |
| #include <sys/event.h> |
| #include <sys/time.h> |
| #include <sys/types.h> |
| #include <unistd.h> |
| |
| #include <string> |
| #include <vector> |
| |
| #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/scoped_nsautorelease_pool.h" |
| #include "base/mac/scoped_nsobject.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<std::string>& 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 (!base::PathService::Get(content::CHILD_PROCESS_EXE, &child_path)) { |
| LOG(ERROR) << "No CHILD_PROCESS_EXE"; |
| return false; |
| } |
| |
| std::vector<std::string> relauncher_args; |
| return RelaunchAppWithHelper(child_path.value(), relauncher_args, args); |
| } |
| |
| bool RelaunchAppWithHelper(const std::string& helper, |
| const std::vector<std::string>& relauncher_args, |
| const std::vector<std::string>& args) { |
| std::vector<std::string> 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 (![NSApp isActive]) { |
| 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 fds_to_remap, and the write |
| // side will be remapped to kRelauncherSyncFD by fds_to_remap. |
| 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::LaunchOptions options; |
| options.fds_to_remap.push_back( |
| std::make_pair(pipe_write_fd.get(), kRelauncherSyncFD)); |
| 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<uintptr_t>(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) { |
| base::mac::ScopedNSAutoreleasePool pool; |
| |
| // 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::scoped_nsobject<NSMutableArray> relaunch_args( |
| [[NSMutableArray alloc] initWithCapacity:argc - 4]); |
| |
| // 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 { |
| |
| NSString* arg_string = base::SysUTF8ToNSString(arg); |
| if (!arg_string) { |
| LOG(ERROR) << "base::SysUTF8ToNSString failed for " << arg; |
| return 1; |
| } |
| [relaunch_args addObject:arg_string]; |
| } |
| } |
| } |
| |
| if (!seen_relaunch_executable) { |
| LOG(ERROR) << "nothing to relaunch"; |
| return 1; |
| } |
| |
| NSString* path = base::SysUTF8ToNSString(relaunch_executable); |
| base::scoped_nsobject<NSURL> url([[NSURL alloc] initFileURLWithPath:path]); |
| NSDictionary* configuration = |
| @{NSWorkspaceLaunchConfigurationArguments : (relaunch_args.get())}; |
| |
| NSRunningApplication *application = [[NSWorkspace sharedWorkspace] |
| launchApplicationAtURL:url |
| options:NSWorkspaceLaunchDefault | |
| NSWorkspaceLaunchWithErrorPresentation | |
| (background ? NSWorkspaceLaunchWithoutActivation |
| : 0) | |
| NSWorkspaceLaunchNewInstance |
| configuration:configuration |
| error:nil]; |
| if (!application) { |
| LOG(ERROR) << "Failed to relaunch " << relaunch_executable; |
| 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 |