| OLD | NEW |
| (Empty) | |
| 1 // Copyright (c) 2011 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 <dlfcn.h> |
| 10 #include <string.h> |
| 11 #include <sys/event.h> |
| 12 #include <sys/time.h> |
| 13 #include <sys/types.h> |
| 14 #include <unistd.h> |
| 15 |
| 16 #include <string> |
| 17 #include <vector> |
| 18 |
| 19 #include "base/basictypes.h" |
| 20 #include "base/eintr_wrapper.h" |
| 21 #include "base/file_util.h" |
| 22 #include "base/logging.h" |
| 23 #include "base/mac/mac_util.h" |
| 24 #include "base/mac/scoped_cftyperef.h" |
| 25 #include "base/path_service.h" |
| 26 #include "base/process_util.h" |
| 27 #include "base/stringprintf.h" |
| 28 #include "base/sys_string_conversions.h" |
| 29 #include "chrome/common/chrome_switches.h" |
| 30 #include "content/common/content_paths.h" |
| 31 #include "content/common/content_switches.h" |
| 32 #include "content/common/main_function_params.h" |
| 33 |
| 34 // RTLD_MAIN_ONLY is supported as of Mac OS X 10.5, but <dlfcn.h> does not |
| 35 // define it in the 10.5 SDK. It is present in the 10.6 SDK and is documented |
| 36 // as working on 10.5 and later. The source code for the version of dyld that |
| 37 // shipped in 10.5, dyld-95.3/src/dyldAPIs.cpp, confirms that this feature is |
| 38 // supported. Provide a fallback definition here. |
| 39 #if MAC_OS_X_VERSION_MAX_ALLOWED == MAC_OS_X_VERSION_10_5 // 10.5 SDK |
| 40 #define RTLD_MAIN_ONLY ((void*)-5) // Search main executable only. |
| 41 #endif |
| 42 |
| 43 namespace mac_relauncher { |
| 44 |
| 45 namespace { |
| 46 |
| 47 // The "magic" file descriptor that the relauncher process' write side of the |
| 48 // pipe shows up on. Chosen to avoid conflicting with stdin, stdout, and |
| 49 // stderr. |
| 50 const int kRelauncherSyncFD = STDERR_FILENO + 1; |
| 51 |
| 52 // The argument separating arguments intended for the relauncher process from |
| 53 // those intended for the relaunched process. "---" is chosen instead of "--" |
| 54 // because CommandLine interprets "--" as meaning "end of switches", but |
| 55 // for many purposes, the relauncher process' CommandLine ought to interpret |
| 56 // arguments intended for the relaunched process, to get the correct settings |
| 57 // for such things as logging and the user-data-dir in case it affects crash |
| 58 // reporting. |
| 59 const char kRelauncherArgSeparator[] = "---"; |
| 60 |
| 61 // When this argument is supplied to the relauncher process, it will launch |
| 62 // the relaunched process without bringing it to the foreground. |
| 63 const char kRelauncherBackgroundArg[] = "--background"; |
| 64 |
| 65 // The beginning of the "process serial number" argument that Launch Services |
| 66 // sometimes inserts into command lines. A process serial number is only valid |
| 67 // for a single process, so any PSN arguments will be stripped from command |
| 68 // lines during relaunch to avoid confusion. |
| 69 const char kPSNArg[] = "-psn_"; |
| 70 |
| 71 // Returns the "type" argument identifying a relauncher process |
| 72 // ("--type=relauncher"). |
| 73 std::string RelauncherTypeArg() { |
| 74 return base::StringPrintf("--%s=%s", |
| 75 switches::kProcessType, |
| 76 switches::kRelauncherProcess); |
| 77 } |
| 78 |
| 79 } // namespace |
| 80 |
| 81 bool RelaunchApp(const std::vector<std::string>& args) { |
| 82 // Use the currently-running application's helper process. The automatic |
| 83 // update feature is careful to leave the currently-running version alone, |
| 84 // so this is safe even if the relaunch is the result of an update having |
| 85 // been applied. In fact, it's safer than using the updated version of the |
| 86 // helper process, because there's no guarantee that the updated version's |
| 87 // relauncher implementation will be compatible with the running version's. |
| 88 FilePath child_path; |
| 89 if (!PathService::Get(content::CHILD_PROCESS_EXE, &child_path)) { |
| 90 LOG(ERROR) << "No CHILD_PROCESS_EXE"; |
| 91 return false; |
| 92 } |
| 93 |
| 94 return RelaunchAppWithHelper(child_path.value(), args); |
| 95 } |
| 96 |
| 97 bool RelaunchAppWithHelper(const std::string& helper, |
| 98 const std::vector<std::string>& args) { |
| 99 std::vector<std::string> relaunch_args; |
| 100 relaunch_args.push_back(helper); |
| 101 relaunch_args.push_back(RelauncherTypeArg()); |
| 102 |
| 103 // If this application isn't in the foreground, the relaunched one shouldn't |
| 104 // be either. |
| 105 if (!base::mac::AmIForeground()) { |
| 106 relaunch_args.push_back(kRelauncherBackgroundArg); |
| 107 } |
| 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 file_util::ScopedFD pipe_read_fd(&pipe_fds[0]); |
| 135 file_util::ScopedFD pipe_write_fd(&pipe_fds[1]); |
| 136 |
| 137 // Make sure kRelauncherSyncFD is a safe value. base::LaunchApp will |
| 138 // preserve these three FDs in forked processes, so kRelauncherSyncFD should |
| 139 // not conflict with them. |
| 140 COMPILE_ASSERT(kRelauncherSyncFD != STDIN_FILENO && |
| 141 kRelauncherSyncFD != STDOUT_FILENO && |
| 142 kRelauncherSyncFD != STDERR_FILENO, |
| 143 kRelauncherSyncFD_must_not_conflict_with_stdio_fds); |
| 144 |
| 145 base::file_handle_mapping_vector fd_map; |
| 146 fd_map.push_back(std::make_pair(*pipe_write_fd, kRelauncherSyncFD)); |
| 147 |
| 148 if (!base::LaunchApp(relaunch_args, fd_map, false, NULL)) { |
| 149 LOG(ERROR) << "base::LaunchApp failed"; |
| 150 return false; |
| 151 } |
| 152 |
| 153 // The relauncher process is now starting up, or has started up. The |
| 154 // original parent process continues. |
| 155 |
| 156 pipe_write_fd.reset(); // close(pipe_fds[1]); |
| 157 |
| 158 // Synchronize with the relauncher process. |
| 159 char read_char; |
| 160 int read_result = HANDLE_EINTR(read(*pipe_read_fd, &read_char, 1)); |
| 161 if (read_result != 1) { |
| 162 if (read_result < 0) { |
| 163 PLOG(ERROR) << "read"; |
| 164 } else { |
| 165 LOG(ERROR) << "read: unexpected result " << read_result; |
| 166 } |
| 167 return false; |
| 168 } |
| 169 |
| 170 // Since a byte has been successfully read from the relauncher process, it's |
| 171 // guaranteed to have set up its kqueue monitoring this process for exit. |
| 172 // It's safe to exit now. |
| 173 return true; |
| 174 } |
| 175 |
| 176 namespace { |
| 177 |
| 178 // In the relauncher process, performs the necessary synchronization steps |
| 179 // with the parent by setting up a kqueue to watch for it to exit, writing a |
| 180 // byte to the pipe, and then waiting for the exit notification on the kqueue. |
| 181 // If anything fails, this logs a message and returns immediately. In those |
| 182 // situations, it can be assumed that something went wrong with the parent |
| 183 // process and the best recovery approach is to attempt relaunch anyway. |
| 184 void RelauncherSynchronizeWithParent() { |
| 185 // file_util::ScopedFD needs something non-const to operate on. |
| 186 int relauncher_sync_fd = kRelauncherSyncFD; |
| 187 file_util::ScopedFD relauncher_sync_fd_closer(&relauncher_sync_fd); |
| 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 int kq = kqueue(); |
| 202 if (kq < 0) { |
| 203 PLOG(ERROR) << "kqueue"; |
| 204 return; |
| 205 } |
| 206 file_util::ScopedFD kq_closer(&kq); |
| 207 |
| 208 struct kevent change = { 0 }; |
| 209 EV_SET(&change, parent_pid, EVFILT_PROC, EV_ADD, NOTE_EXIT, 0, NULL); |
| 210 if (kevent(kq, &change, 1, NULL, 0, NULL) == -1) { |
| 211 PLOG(ERROR) << "kevent (add)"; |
| 212 return; |
| 213 } |
| 214 |
| 215 // Write a '\0' character to the pipe. |
| 216 if (HANDLE_EINTR(write(relauncher_sync_fd, "", 1)) != 1) { |
| 217 PLOG(ERROR) << "write"; |
| 218 return; |
| 219 } |
| 220 |
| 221 // Up until now, the parent process was blocked in a read waiting for the |
| 222 // write above to complete. The parent process is now free to exit. Wait for |
| 223 // that to happen. |
| 224 struct kevent event; |
| 225 int events = kevent(kq, NULL, 0, &event, 1, NULL); |
| 226 if (events != 1) { |
| 227 if (events < 0) { |
| 228 PLOG(ERROR) << "kevent (monitor)"; |
| 229 } else { |
| 230 LOG(ERROR) << "kevent (monitor): unexpected result " << events; |
| 231 } |
| 232 return; |
| 233 } |
| 234 |
| 235 if (event.filter != EVFILT_PROC || |
| 236 event.fflags != NOTE_EXIT || |
| 237 event.ident != static_cast<uintptr_t>(parent_pid)) { |
| 238 LOG(ERROR) << "kevent (monitor): unexpected event, filter " << event.filter |
| 239 << ", fflags " << event.fflags << ", ident " << event.ident; |
| 240 return; |
| 241 } |
| 242 } |
| 243 |
| 244 } // namespace |
| 245 |
| 246 namespace internal { |
| 247 |
| 248 int RelauncherMain(const MainFunctionParams& main_parameters) { |
| 249 // CommandLine rearranges the order of the arguments returned by |
| 250 // main_parameters.argv(), rendering it impossible to determine which |
| 251 // arguments originally came before kRelauncherArgSeparator and which came |
| 252 // after. It's crucial to distinguish between these because only those |
| 253 // after the separator should be given to the relaunched process; it's also |
| 254 // important to not treat the path to the relaunched process as a "loose" |
| 255 // argument. NXArgc and NXArgv are pointers to the original argc and argv as |
| 256 // passed to main(), so use those. The typical mechanism to do this is to |
| 257 // provide "extern" declarations to access these, but they're only present |
| 258 // in the crt1.o start file. This function will be linked into the framework |
| 259 // dylib, having no direct access to anything in crt1.o. dlsym to the |
| 260 // rescue. |
| 261 const int* argcp = static_cast<const int*>(dlsym(RTLD_MAIN_ONLY, "NXArgc")); |
| 262 if (!argcp) { |
| 263 LOG(ERROR) << "dlsym NXArgc: " << dlerror(); |
| 264 return 1; |
| 265 } |
| 266 int argc = *argcp; |
| 267 |
| 268 const char* const** argvp = |
| 269 static_cast<const char* const**>(dlsym(RTLD_MAIN_ONLY, "NXArgv")); |
| 270 if (!argvp) { |
| 271 LOG(ERROR) << "dlsym NXArgv: " << dlerror(); |
| 272 return 1; |
| 273 } |
| 274 const char* const* argv = *argvp; |
| 275 |
| 276 if (argc < 4 || RelauncherTypeArg() != argv[1]) { |
| 277 LOG(ERROR) << "relauncher process invoked with unexpected arguments"; |
| 278 return 1; |
| 279 } |
| 280 |
| 281 RelauncherSynchronizeWithParent(); |
| 282 |
| 283 // The capacity for relaunch_args is 4 less than argc, because it |
| 284 // won't contain the argv[0] of the relauncher process, the |
| 285 // RelauncherTypeArg() at argv[1], kRelauncherArgSeparator, or the |
| 286 // executable path of the process to be launched. |
| 287 base::mac::ScopedCFTypeRef<CFMutableArrayRef> relaunch_args( |
| 288 CFArrayCreateMutable(NULL, argc - 4, &kCFTypeArrayCallBacks)); |
| 289 if (!relaunch_args) { |
| 290 LOG(ERROR) << "CFArrayCreateMutable"; |
| 291 return 1; |
| 292 } |
| 293 |
| 294 // Figure out what to execute, what arguments to pass it, and whether to |
| 295 // start it in the background. |
| 296 bool background = false; |
| 297 bool in_relaunch_args = false; |
| 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 } |
| 315 } else { |
| 316 if (!seen_relaunch_executable) { |
| 317 // The first argument after kRelauncherBackgroundArg is the path to |
| 318 // the executable file or .app bundle directory. The Launch Services |
| 319 // interface wants this separate from the rest of the arguments. In |
| 320 // the relaunched process, this path will still be visible at argv[0]. |
| 321 relaunch_executable.assign(arg); |
| 322 seen_relaunch_executable = true; |
| 323 } else { |
| 324 base::mac::ScopedCFTypeRef<CFStringRef> arg_cf( |
| 325 base::SysUTF8ToCFStringRef(arg)); |
| 326 if (!arg_cf) { |
| 327 LOG(ERROR) << "base::SysUTF8ToCFStringRef failed for " << arg; |
| 328 return 1; |
| 329 } |
| 330 CFArrayAppendValue(relaunch_args, arg_cf); |
| 331 } |
| 332 } |
| 333 } |
| 334 |
| 335 if (!seen_relaunch_executable) { |
| 336 LOG(ERROR) << "nothing to relaunch"; |
| 337 return 1; |
| 338 } |
| 339 |
| 340 FSRef app_fsref; |
| 341 if (!base::mac::FSRefFromPath(relaunch_executable, &app_fsref)) { |
| 342 LOG(ERROR) << "base::mac::FSRefFromPath failed for " << relaunch_executable; |
| 343 return 1; |
| 344 } |
| 345 |
| 346 LSApplicationParameters ls_parameters = { |
| 347 0, // version |
| 348 kLSLaunchDefaults | kLSLaunchAndDisplayErrors | kLSLaunchNewInstance | |
| 349 (background ? kLSLaunchDontSwitch : 0), |
| 350 &app_fsref, |
| 351 NULL, // asyncLaunchRefCon |
| 352 NULL, // environment |
| 353 relaunch_args, |
| 354 NULL // initialEvent |
| 355 }; |
| 356 |
| 357 OSStatus err = LSOpenApplication(&ls_parameters, NULL); |
| 358 if (err != noErr) { |
| 359 LOG(ERROR) << "LSOpenApplication: " << err; |
| 360 return 1; |
| 361 } |
| 362 |
| 363 return 0; |
| 364 } |
| 365 |
| 366 } // namespace internal |
| 367 |
| 368 } // namespace mac_relauncher |
| OLD | NEW |