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

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
« no previous file with comments | « chrome/browser/mac/relauncher.h ('k') | chrome/chrome_browser.gypi » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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
Robert Sesek 2011/06/23 19:30:16 nit: full-stop
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 = 3;
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 impacts 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 file_util::ScopedFD pipe_read_fd(&pipe_fds[0]);
128 file_util::ScopedFD pipe_write_fd(&pipe_fds[1]);
129
130 base::file_handle_mapping_vector fd_map;
131 fd_map.push_back(std::pair<int, int>(*pipe_write_fd, kRelauncherSyncFD));
132
133 if (!base::LaunchApp(relaunch_args, fd_map, false, NULL)) {
134 LOG(ERROR) << "base::LaunchApp failed";
135 return false;
136 }
137
138 // The relauncher process is now starting up, or has started up. The
139 // original parent process continues.
140
141 pipe_write_fd.reset(); // close(pipe_fds[1]);
142
143 // Synchronize with the relauncher process.
144 char read_char;
145 int read_result = HANDLE_EINTR(read(*pipe_read_fd, &read_char, 1));
146 if (read_result != 1) {
147 if (read_result < 0) {
148 PLOG(ERROR) << "read";
149 } else {
150 LOG(ERROR) << "read: unexpected result " << read_result;
151 }
152 return false;
153 }
154
155 // Since a byte has been successfully read from the relauncher process, it's
156 // guaranteed to have set up its kqueue monitoring this process for exit.
157 // It's safe to exit now.
158 return true;
159 }
160
161 namespace {
162
163 // In the relauncher process, performs the necessary synchronization steps
164 // with the parent, by setting up a kqueue to watch for it to exit, writing
165 // a byte to the pipe, and then waiting for the exit notification on the
166 // kqueue. If anything fails, this logs a message and returns immediately.
167 // In those situations, it can be assumed that something went wrong with the
168 // parent process and the best recovery approach is to attempt relaunch
169 // anyway.
170 void RelauncherSynchronizeWithParent() {
171 // file_util::ScopedFD needs something non-const to operate on.
172 int relauncher_sync_fd = kRelauncherSyncFD;
173 file_util::ScopedFD relauncher_sync_fd_closer(&relauncher_sync_fd);
174
175 int parent_pid = getppid();
176
177 // PID 1 identifies init. launchd, that is. launchd never starts the
178 // relauncher process directly, having this parent_pid means that the parent
179 // already exited and launchd "inherited" the relauncher as its child.
180 // There's no reason to synchronize with launchd.
181 if (parent_pid == 1) {
182 LOG(ERROR) << "unexpected parent_pid";
183 return;
184 }
185
186 // Set up a kqueue to monitor the parent process for exit.
187 int kq = kqueue();
188 if (kq < 0) {
189 PLOG(ERROR) << "kqueue";
190 return;
191 }
192 file_util::ScopedFD kq_closer(&kq);
193
194 struct kevent change = { 0 };
195 EV_SET(&change, parent_pid, EVFILT_PROC, EV_ADD, NOTE_EXIT, 0, NULL);
196 if (kevent(kq, &change, 1, NULL, 0, NULL) == -1) {
197 PLOG(ERROR) << "kevent (add)";
198 return;
199 }
200
201 // Write a '\0' character to the pipe.
202 if (HANDLE_EINTR(write(relauncher_sync_fd, "", 1)) != 1) {
203 PLOG(ERROR) << "write";
204 return;
205 }
206
207 // Up until now, the parent process was blocked in a read waiting for the
208 // write above to complete. The parent process is now free to exit. Wait for
209 // that to happen.
210 struct kevent event;
211 int events = kevent(kq, NULL, 0, &event, 1, NULL);
212 if (events != 1) {
213 if (events < 0) {
214 PLOG(ERROR) << "kevent (monitor)";
215 } else {
216 LOG(ERROR) << "kevent (monitor): unexpected result " << events;
217 }
218 return;
219 }
220
221 if (event.filter != EVFILT_PROC ||
222 event.fflags != NOTE_EXIT ||
223 event.ident != static_cast<uintptr_t>(parent_pid)) {
224 LOG(ERROR) << "kevent (monitor): unexpected event, filter " << event.filter
225 << ", fflags " << event.fflags << ", ident " << event.ident;
226 return;
227 }
228 }
229
230 } // namespace
231
232 int RelauncherMain(const MainFunctionParams& main_parameters) {
233 // CommandLine rearranges the order of the arguments returned by
234 // main_parameters.argv(), rendering it impossible to determine which
235 // arguments originally came before kRelauncherArgSeparator and which came
236 // after. It's crucial to distinguish between these because only those
237 // after the separator should be given to the relaunched process; it's also
238 // important to not treat the path to the relaunched process as a "loose"
239 // argument. NXArgc and NXArgv are pointers to the original argc and argv as
240 // passed to main(), so use those. The typical mechanism to do this is to
241 // provide "extern" declarations to access these, but they're only present
242 // in the crt1.o start file. This function will be linked into the framework
243 // dylib, having no direct access to anything in crt1.o. dlsym to the
244 // rescue.
245 const int* argcp = static_cast<const int*>(dlsym(RTLD_MAIN_ONLY, "NXArgc"));
246 if (!argcp) {
247 LOG(ERROR) << "dlsym NXArgc: " << dlerror();
248 return 1;
249 }
250 const int argc = *argcp;
251
252 const char* const** argvp =
253 static_cast<const char* const**>(dlsym(RTLD_MAIN_ONLY, "NXArgv"));
254 if (!argvp) {
255 LOG(ERROR) << "dlsym NXArgv: " << dlerror();
256 return 1;
257 }
258 const char* const* argv = *argvp;
259
260 if (argc < 4 || RelauncherTypeArg() != argv[1]) {
261 LOG(ERROR) << "relauncher process invoked with unexpected arguments";
262 return 1;
263 }
264
265 RelauncherSynchronizeWithParent();
266
267 // The capacity for relaunch_args is 4 less than argc, because it
268 // won't contain the argv[0] of the relauncher process, the
269 // RelauncherTypeArg() at argv[1], kRelauncherArgSeparator, or the
270 // executable path of the process to be launched.
271 base::mac::ScopedCFTypeRef<CFMutableArrayRef> relaunch_args(
272 CFArrayCreateMutable(NULL, argc - 4, &kCFTypeArrayCallBacks));
273 if (!relaunch_args) {
274 LOG(ERROR) << "CFArrayCreateMutable";
275 return 1;
276 }
277
278 // Figure out what to execute, what arguments to pass it, and whether to
279 // start it in the background.
280 bool background = false;
281 bool in_relaunch_args = false;
282 bool seen_relaunch_executable = false;
283 std::string relaunch_executable;
284 const std::string relauncher_arg_separator(kRelauncherArgSeparator);
285 for (int argv_index = 2; argv_index < argc; ++argv_index) {
286 const std::string arg(argv[argv_index]);
287
288 // Strip any -psn_ arguments, as they apply to a specific process.
289 if (arg.compare(0, strlen(kPSNArg), kPSNArg) == 0) {
290 continue;
291 }
292
293 if (!in_relaunch_args) {
294 if (arg == relauncher_arg_separator) {
295 in_relaunch_args = true;
296 } else if (arg == kRelauncherBackgroundArg) {
297 background = true;
298 }
299 } else {
300 if (!seen_relaunch_executable) {
301 // The first argument after kRelauncherBackgroundArg is the path to
302 // the executable file or .app bundle directory. The Launch Services
303 // interface wants this separate from the rest of the arguments. In
304 // the relaunched process, this path will still be visible at argv[0].
305 relaunch_executable.assign(arg);
306 seen_relaunch_executable = true;
307 } else {
308 base::mac::ScopedCFTypeRef<CFStringRef> arg_cf(
309 base::SysUTF8ToCFStringRef(arg));
310 if (!arg_cf) {
311 LOG(ERROR) << "base::SysUTF8ToCFStringRef failed for " << arg;
312 return 1;
313 }
314 CFArrayAppendValue(relaunch_args, arg_cf);
315 }
316 }
317 }
318
319 if (!seen_relaunch_executable) {
320 LOG(ERROR) << "nothing to relaunch";
321 return 1;
322 }
323
324 FSRef app_fsref;
325 if (!base::mac::FSRefFromPath(relaunch_executable, &app_fsref)) {
326 LOG(ERROR) << "base::mac::FSRefFromPath failed for " << relaunch_executable;
327 return 1;
328 }
329
330 LSApplicationParameters ls_parameters = {
331 0, // version
332 kLSLaunchDefaults | kLSLaunchAndDisplayErrors | kLSLaunchNewInstance |
333 (background ? kLSLaunchDontSwitch : 0),
334 &app_fsref,
335 NULL, // asyncLaunchRefCon
336 NULL, // environment
337 relaunch_args,
338 NULL // initialEvent
339 };
340
341 OSStatus err = LSOpenApplication(&ls_parameters, NULL);
342 if (err != noErr) {
343 LOG(ERROR) << "LSOpenApplication: " << err;
344 return 1;
345 }
346
347 return 0;
348 }
349
350 } // namespace mac_relauncher
OLDNEW
« no previous file with comments | « chrome/browser/mac/relauncher.h ('k') | chrome/chrome_browser.gypi » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698