| OLD | NEW |
| (Empty) |
| 1 // Copyright 2012 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "chrome/browser/mac/relauncher.h" | |
| 6 | |
| 7 #include <ApplicationServices/ApplicationServices.h> | |
| 8 #include <AvailabilityMacros.h> | |
| 9 #include <crt_externs.h> | |
| 10 #include <dlfcn.h> | |
| 11 #include <stddef.h> | |
| 12 #include <string.h> | |
| 13 #include <sys/event.h> | |
| 14 #include <sys/time.h> | |
| 15 #include <sys/types.h> | |
| 16 #include <unistd.h> | |
| 17 | |
| 18 #include <string> | |
| 19 #include <vector> | |
| 20 | |
| 21 #include "base/files/file_util.h" | |
| 22 #include "base/files/scoped_file.h" | |
| 23 #include "base/logging.h" | |
| 24 #include "base/mac/mac_logging.h" | |
| 25 #include "base/mac/mac_util.h" | |
| 26 #include "base/mac/scoped_cftyperef.h" | |
| 27 #include "base/path_service.h" | |
| 28 #include "base/posix/eintr_wrapper.h" | |
| 29 #include "base/process/launch.h" | |
| 30 #include "base/strings/stringprintf.h" | |
| 31 #include "base/strings/sys_string_conversions.h" | |
| 32 #include "chrome/browser/mac/install_from_dmg.h" | |
| 33 #include "chrome/common/chrome_switches.h" | |
| 34 #include "content/public/common/content_paths.h" | |
| 35 #include "content/public/common/content_switches.h" | |
| 36 #include "content/public/common/main_function_params.h" | |
| 37 | |
| 38 namespace mac_relauncher { | |
| 39 | |
| 40 namespace { | |
| 41 | |
| 42 // The "magic" file descriptor that the relauncher process' write side of the | |
| 43 // pipe shows up on. Chosen to avoid conflicting with stdin, stdout, and | |
| 44 // stderr. | |
| 45 const int kRelauncherSyncFD = STDERR_FILENO + 1; | |
| 46 | |
| 47 // The argument separating arguments intended for the relauncher process from | |
| 48 // those intended for the relaunched process. "---" is chosen instead of "--" | |
| 49 // because CommandLine interprets "--" as meaning "end of switches", but | |
| 50 // for many purposes, the relauncher process' CommandLine ought to interpret | |
| 51 // arguments intended for the relaunched process, to get the correct settings | |
| 52 // for such things as logging and the user-data-dir in case it affects crash | |
| 53 // reporting. | |
| 54 const char kRelauncherArgSeparator[] = "---"; | |
| 55 | |
| 56 // When this argument is supplied to the relauncher process, it will launch | |
| 57 // the relaunched process without bringing it to the foreground. | |
| 58 const char kRelauncherBackgroundArg[] = "--background"; | |
| 59 | |
| 60 // The beginning of the "process serial number" argument that Launch Services | |
| 61 // sometimes inserts into command lines. A process serial number is only valid | |
| 62 // for a single process, so any PSN arguments will be stripped from command | |
| 63 // lines during relaunch to avoid confusion. | |
| 64 const char kPSNArg[] = "-psn_"; | |
| 65 | |
| 66 // Returns the "type" argument identifying a relauncher process | |
| 67 // ("--type=relauncher"). | |
| 68 std::string RelauncherTypeArg() { | |
| 69 return base::StringPrintf("--%s=%s", | |
| 70 switches::kProcessType, | |
| 71 switches::kRelauncherProcess); | |
| 72 } | |
| 73 | |
| 74 } // namespace | |
| 75 | |
| 76 bool RelaunchApp(const std::vector<std::string>& args) { | |
| 77 // Use the currently-running application's helper process. The automatic | |
| 78 // update feature is careful to leave the currently-running version alone, | |
| 79 // so this is safe even if the relaunch is the result of an update having | |
| 80 // been applied. In fact, it's safer than using the updated version of the | |
| 81 // helper process, because there's no guarantee that the updated version's | |
| 82 // relauncher implementation will be compatible with the running version's. | |
| 83 base::FilePath child_path; | |
| 84 if (!PathService::Get(content::CHILD_PROCESS_EXE, &child_path)) { | |
| 85 LOG(ERROR) << "No CHILD_PROCESS_EXE"; | |
| 86 return false; | |
| 87 } | |
| 88 | |
| 89 std::vector<std::string> relauncher_args; | |
| 90 return RelaunchAppWithHelper(child_path.value(), relauncher_args, args); | |
| 91 } | |
| 92 | |
| 93 bool RelaunchAppWithHelper(const std::string& helper, | |
| 94 const std::vector<std::string>& relauncher_args, | |
| 95 const std::vector<std::string>& args) { | |
| 96 std::vector<std::string> relaunch_args; | |
| 97 relaunch_args.push_back(helper); | |
| 98 relaunch_args.push_back(RelauncherTypeArg()); | |
| 99 | |
| 100 // If this application isn't in the foreground, the relaunched one shouldn't | |
| 101 // be either. | |
| 102 if (!base::mac::AmIForeground()) { | |
| 103 relaunch_args.push_back(kRelauncherBackgroundArg); | |
| 104 } | |
| 105 | |
| 106 relaunch_args.insert(relaunch_args.end(), | |
| 107 relauncher_args.begin(), relauncher_args.end()); | |
| 108 | |
| 109 relaunch_args.push_back(kRelauncherArgSeparator); | |
| 110 | |
| 111 // When using the CommandLine interface, -psn_ may have been rewritten as | |
| 112 // --psn_. Look for both. | |
| 113 const char alt_psn_arg[] = "--psn_"; | |
| 114 for (size_t index = 0; index < args.size(); ++index) { | |
| 115 // Strip any -psn_ arguments, as they apply to a specific process. | |
| 116 if (args[index].compare(0, strlen(kPSNArg), kPSNArg) != 0 && | |
| 117 args[index].compare(0, strlen(alt_psn_arg), alt_psn_arg) != 0) { | |
| 118 relaunch_args.push_back(args[index]); | |
| 119 } | |
| 120 } | |
| 121 | |
| 122 int pipe_fds[2]; | |
| 123 if (HANDLE_EINTR(pipe(pipe_fds)) != 0) { | |
| 124 PLOG(ERROR) << "pipe"; | |
| 125 return false; | |
| 126 } | |
| 127 | |
| 128 // The parent process will only use pipe_read_fd as the read side of the | |
| 129 // pipe. It can close the write side as soon as the relauncher process has | |
| 130 // forked off. The relauncher process will only use pipe_write_fd as the | |
| 131 // write side of the pipe. In that process, the read side will be closed by | |
| 132 // base::LaunchApp because it won't be present in fd_map, and the write side | |
| 133 // will be remapped to kRelauncherSyncFD by fd_map. | |
| 134 base::ScopedFD pipe_read_fd(pipe_fds[0]); | |
| 135 base::ScopedFD pipe_write_fd(pipe_fds[1]); | |
| 136 | |
| 137 // Make sure kRelauncherSyncFD is a safe value. base::LaunchProcess will | |
| 138 // preserve these three FDs in forked processes, so kRelauncherSyncFD should | |
| 139 // not conflict with them. | |
| 140 static_assert(kRelauncherSyncFD != STDIN_FILENO && | |
| 141 kRelauncherSyncFD != STDOUT_FILENO && | |
| 142 kRelauncherSyncFD != STDERR_FILENO, | |
| 143 "kRelauncherSyncFD must not conflict with stdio fds"); | |
| 144 | |
| 145 base::FileHandleMappingVector fd_map; | |
| 146 fd_map.push_back(std::make_pair(pipe_write_fd.get(), kRelauncherSyncFD)); | |
| 147 | |
| 148 base::LaunchOptions options; | |
| 149 options.fds_to_remap = &fd_map; | |
| 150 if (!base::LaunchProcess(relaunch_args, options).IsValid()) { | |
| 151 LOG(ERROR) << "base::LaunchProcess failed"; | |
| 152 return false; | |
| 153 } | |
| 154 | |
| 155 // The relauncher process is now starting up, or has started up. The | |
| 156 // original parent process continues. | |
| 157 | |
| 158 pipe_write_fd.reset(); // close(pipe_fds[1]); | |
| 159 | |
| 160 // Synchronize with the relauncher process. | |
| 161 char read_char; | |
| 162 int read_result = HANDLE_EINTR(read(pipe_read_fd.get(), &read_char, 1)); | |
| 163 if (read_result != 1) { | |
| 164 if (read_result < 0) { | |
| 165 PLOG(ERROR) << "read"; | |
| 166 } else { | |
| 167 LOG(ERROR) << "read: unexpected result " << read_result; | |
| 168 } | |
| 169 return false; | |
| 170 } | |
| 171 | |
| 172 // Since a byte has been successfully read from the relauncher process, it's | |
| 173 // guaranteed to have set up its kqueue monitoring this process for exit. | |
| 174 // It's safe to exit now. | |
| 175 return true; | |
| 176 } | |
| 177 | |
| 178 namespace { | |
| 179 | |
| 180 // In the relauncher process, performs the necessary synchronization steps | |
| 181 // with the parent by setting up a kqueue to watch for it to exit, writing a | |
| 182 // byte to the pipe, and then waiting for the exit notification on the kqueue. | |
| 183 // If anything fails, this logs a message and returns immediately. In those | |
| 184 // situations, it can be assumed that something went wrong with the parent | |
| 185 // process and the best recovery approach is to attempt relaunch anyway. | |
| 186 void RelauncherSynchronizeWithParent() { | |
| 187 base::ScopedFD relauncher_sync_fd(kRelauncherSyncFD); | |
| 188 | |
| 189 int parent_pid = getppid(); | |
| 190 | |
| 191 // PID 1 identifies init. launchd, that is. launchd never starts the | |
| 192 // relauncher process directly, having this parent_pid means that the parent | |
| 193 // already exited and launchd "inherited" the relauncher as its child. | |
| 194 // There's no reason to synchronize with launchd. | |
| 195 if (parent_pid == 1) { | |
| 196 LOG(ERROR) << "unexpected parent_pid"; | |
| 197 return; | |
| 198 } | |
| 199 | |
| 200 // Set up a kqueue to monitor the parent process for exit. | |
| 201 base::ScopedFD kq(kqueue()); | |
| 202 if (!kq.is_valid()) { | |
| 203 PLOG(ERROR) << "kqueue"; | |
| 204 return; | |
| 205 } | |
| 206 | |
| 207 struct kevent change = { 0 }; | |
| 208 EV_SET(&change, parent_pid, EVFILT_PROC, EV_ADD, NOTE_EXIT, 0, NULL); | |
| 209 if (kevent(kq.get(), &change, 1, NULL, 0, NULL) == -1) { | |
| 210 PLOG(ERROR) << "kevent (add)"; | |
| 211 return; | |
| 212 } | |
| 213 | |
| 214 // Write a '\0' character to the pipe. | |
| 215 if (HANDLE_EINTR(write(relauncher_sync_fd.get(), "", 1)) != 1) { | |
| 216 PLOG(ERROR) << "write"; | |
| 217 return; | |
| 218 } | |
| 219 | |
| 220 // Up until now, the parent process was blocked in a read waiting for the | |
| 221 // write above to complete. The parent process is now free to exit. Wait for | |
| 222 // that to happen. | |
| 223 struct kevent event; | |
| 224 int events = kevent(kq.get(), NULL, 0, &event, 1, NULL); | |
| 225 if (events != 1) { | |
| 226 if (events < 0) { | |
| 227 PLOG(ERROR) << "kevent (monitor)"; | |
| 228 } else { | |
| 229 LOG(ERROR) << "kevent (monitor): unexpected result " << events; | |
| 230 } | |
| 231 return; | |
| 232 } | |
| 233 | |
| 234 if (event.filter != EVFILT_PROC || | |
| 235 event.fflags != NOTE_EXIT || | |
| 236 event.ident != static_cast<uintptr_t>(parent_pid)) { | |
| 237 LOG(ERROR) << "kevent (monitor): unexpected event, filter " << event.filter | |
| 238 << ", fflags " << event.fflags << ", ident " << event.ident; | |
| 239 return; | |
| 240 } | |
| 241 } | |
| 242 | |
| 243 } // namespace | |
| 244 | |
| 245 namespace internal { | |
| 246 | |
| 247 int RelauncherMain(const content::MainFunctionParams& main_parameters) { | |
| 248 // CommandLine rearranges the order of the arguments returned by | |
| 249 // main_parameters.argv(), rendering it impossible to determine which | |
| 250 // arguments originally came before kRelauncherArgSeparator and which came | |
| 251 // after. It's crucial to distinguish between these because only those | |
| 252 // after the separator should be given to the relaunched process; it's also | |
| 253 // important to not treat the path to the relaunched process as a "loose" | |
| 254 // argument. NXArgc and NXArgv are pointers to the original argc and argv as | |
| 255 // passed to main(), so use those. Access them through _NSGetArgc and | |
| 256 // _NSGetArgv because NXArgc and NXArgv are normally only available to a | |
| 257 // main executable via crt1.o and this code will run from a dylib, and | |
| 258 // because of http://crbug.com/139902. | |
| 259 const int* argcp = _NSGetArgc(); | |
| 260 if (!argcp) { | |
| 261 NOTREACHED(); | |
| 262 return 1; | |
| 263 } | |
| 264 int argc = *argcp; | |
| 265 | |
| 266 const char* const* const* argvp = _NSGetArgv(); | |
| 267 if (!argvp) { | |
| 268 NOTREACHED(); | |
| 269 return 1; | |
| 270 } | |
| 271 const char* const* argv = *argvp; | |
| 272 | |
| 273 if (argc < 4 || RelauncherTypeArg() != argv[1]) { | |
| 274 LOG(ERROR) << "relauncher process invoked with unexpected arguments"; | |
| 275 return 1; | |
| 276 } | |
| 277 | |
| 278 RelauncherSynchronizeWithParent(); | |
| 279 | |
| 280 // The capacity for relaunch_args is 4 less than argc, because it | |
| 281 // won't contain the argv[0] of the relauncher process, the | |
| 282 // RelauncherTypeArg() at argv[1], kRelauncherArgSeparator, or the | |
| 283 // executable path of the process to be launched. | |
| 284 base::ScopedCFTypeRef<CFMutableArrayRef> relaunch_args( | |
| 285 CFArrayCreateMutable(NULL, argc - 4, &kCFTypeArrayCallBacks)); | |
| 286 if (!relaunch_args) { | |
| 287 LOG(ERROR) << "CFArrayCreateMutable"; | |
| 288 return 1; | |
| 289 } | |
| 290 | |
| 291 // Figure out what to execute, what arguments to pass it, and whether to | |
| 292 // start it in the background. | |
| 293 bool background = false; | |
| 294 bool in_relaunch_args = false; | |
| 295 std::string dmg_bsd_device_name; | |
| 296 bool seen_relaunch_executable = false; | |
| 297 std::string relaunch_executable; | |
| 298 const std::string relauncher_arg_separator(kRelauncherArgSeparator); | |
| 299 const std::string relauncher_dmg_device_arg = | |
| 300 base::StringPrintf("--%s=", switches::kRelauncherProcessDMGDevice); | |
| 301 for (int argv_index = 2; argv_index < argc; ++argv_index) { | |
| 302 const std::string arg(argv[argv_index]); | |
| 303 | |
| 304 // Strip any -psn_ arguments, as they apply to a specific process. | |
| 305 if (arg.compare(0, strlen(kPSNArg), kPSNArg) == 0) { | |
| 306 continue; | |
| 307 } | |
| 308 | |
| 309 if (!in_relaunch_args) { | |
| 310 if (arg == relauncher_arg_separator) { | |
| 311 in_relaunch_args = true; | |
| 312 } else if (arg == kRelauncherBackgroundArg) { | |
| 313 background = true; | |
| 314 } else if (arg.compare(0, | |
| 315 relauncher_dmg_device_arg.size(), | |
| 316 relauncher_dmg_device_arg) == 0) { | |
| 317 dmg_bsd_device_name.assign( | |
| 318 arg.substr(relauncher_dmg_device_arg.size())); | |
| 319 } | |
| 320 } else { | |
| 321 if (!seen_relaunch_executable) { | |
| 322 // The first argument after kRelauncherBackgroundArg is the path to | |
| 323 // the executable file or .app bundle directory. The Launch Services | |
| 324 // interface wants this separate from the rest of the arguments. In | |
| 325 // the relaunched process, this path will still be visible at argv[0]. | |
| 326 relaunch_executable.assign(arg); | |
| 327 seen_relaunch_executable = true; | |
| 328 } else { | |
| 329 base::ScopedCFTypeRef<CFStringRef> arg_cf( | |
| 330 base::SysUTF8ToCFStringRef(arg)); | |
| 331 if (!arg_cf) { | |
| 332 LOG(ERROR) << "base::SysUTF8ToCFStringRef failed for " << arg; | |
| 333 return 1; | |
| 334 } | |
| 335 CFArrayAppendValue(relaunch_args, arg_cf); | |
| 336 } | |
| 337 } | |
| 338 } | |
| 339 | |
| 340 if (!seen_relaunch_executable) { | |
| 341 LOG(ERROR) << "nothing to relaunch"; | |
| 342 return 1; | |
| 343 } | |
| 344 | |
| 345 FSRef app_fsref; | |
| 346 if (!base::mac::FSRefFromPath(relaunch_executable, &app_fsref)) { | |
| 347 LOG(ERROR) << "base::mac::FSRefFromPath failed for " << relaunch_executable; | |
| 348 return 1; | |
| 349 } | |
| 350 | |
| 351 LSApplicationParameters ls_parameters = { | |
| 352 0, // version | |
| 353 kLSLaunchDefaults | kLSLaunchAndDisplayErrors | kLSLaunchNewInstance | | |
| 354 (background ? kLSLaunchDontSwitch : 0), | |
| 355 &app_fsref, | |
| 356 NULL, // asyncLaunchRefCon | |
| 357 NULL, // environment | |
| 358 relaunch_args, | |
| 359 NULL // initialEvent | |
| 360 }; | |
| 361 | |
| 362 OSStatus status = LSOpenApplication(&ls_parameters, NULL); | |
| 363 if (status != noErr) { | |
| 364 OSSTATUS_LOG(ERROR, status) << "LSOpenApplication"; | |
| 365 return 1; | |
| 366 } | |
| 367 | |
| 368 // The application should have relaunched (or is in the process of | |
| 369 // relaunching). From this point on, only clean-up tasks should occur, and | |
| 370 // failures are tolerable. | |
| 371 | |
| 372 if (!dmg_bsd_device_name.empty()) { | |
| 373 EjectAndTrashDiskImage(dmg_bsd_device_name); | |
| 374 } | |
| 375 | |
| 376 return 0; | |
| 377 } | |
| 378 | |
| 379 } // namespace internal | |
| 380 | |
| 381 } // namespace mac_relauncher | |
| OLD | NEW |