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