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