Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(349)

Side by Side Diff: chrome/browser/mac/relauncher.cc

Issue 7215040: Fix relaunches on the Mac (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src/
Patch Set: '' Created 9 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
Property Changes:
Added: svn:eol-style
+ LF
OLDNEW
(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
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698