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

Side by Side Diff: chrome/common/service_process_util_mac.mm

Issue 6660001: Getting service process on Mac to handle having things moved/changed underneath it. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: polishing the chrome Created 9 years, 9 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/common/service_process_util.cc ('k') | chrome/common/service_process_util_posix.h » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. 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 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "chrome/common/service_process_util_posix.h" 5 #include "chrome/common/service_process_util_posix.h"
6 6
7 #import <Foundation/Foundation.h> 7 #import <Foundation/Foundation.h>
8 #include <launch.h> 8 #include <launch.h>
9 9
10 #include <vector>
11
10 #include "base/command_line.h" 12 #include "base/command_line.h"
11 #include "base/file_path.h" 13 #include "base/file_path.h"
14 #include "base/file_util.h"
12 #include "base/mac/foundation_util.h" 15 #include "base/mac/foundation_util.h"
13 #include "base/mac/mac_util.h" 16 #include "base/mac/mac_util.h"
14 #include "base/mac/scoped_nsautorelease_pool.h" 17 #include "base/mac/scoped_nsautorelease_pool.h"
15 #include "base/path_service.h" 18 #include "base/path_service.h"
19 #include "base/process_util.h"
16 #include "base/scoped_nsobject.h" 20 #include "base/scoped_nsobject.h"
21 #include "base/stringprintf.h"
17 #include "base/string_util.h" 22 #include "base/string_util.h"
18 #include "base/sys_string_conversions.h" 23 #include "base/sys_string_conversions.h"
19 #include "base/threading/thread_restrictions.h" 24 #include "base/threading/thread_restrictions.h"
20 #include "base/version.h" 25 #include "base/version.h"
21 #include "chrome/common/chrome_paths.h" 26 #include "chrome/common/chrome_paths.h"
22 #include "chrome/common/chrome_switches.h" 27 #include "chrome/common/chrome_switches.h"
23 #include "chrome/common/chrome_version_info.h" 28 #include "chrome/common/chrome_version_info.h"
29 #include "chrome/common/launchd_mac.h"
24 #include "content/common/child_process_host.h" 30 #include "content/common/child_process_host.h"
25 #include "third_party/GTM/Foundation/GTMServiceManagement.h"
26 31
27 namespace { 32 namespace {
28 33
29 NSString* GetServiceProcessLaunchDFileName() { 34 #define kServiceProcessSessionType "Background"
30 NSString *bundle_id = [base::mac::MainAppBundle() bundleIdentifier]; 35
31 NSString *label = [bundle_id stringByAppendingPathExtension:@"plist"]; 36 CFStringRef CopyServiceProcessLaunchDName() {
32 return label; 37 base::mac::ScopedNSAutoreleasePool pool;
38 NSBundle* bundle = base::mac::MainAppBundle();
39 return CFStringCreateCopy(kCFAllocatorDefault,
40 base::mac::NSToCFCast([bundle bundleIdentifier]));
33 } 41 }
34 42
35 NSString* GetServiceProcessLaunchDLabel() { 43 NSString* GetServiceProcessLaunchDLabel() {
36 NSString *bundle_id = [base::mac::MainAppBundle() bundleIdentifier]; 44 scoped_nsobject<NSString> name(
37 NSString *label = [bundle_id stringByAppendingString:@".service_process"]; 45 base::mac::CFToNSCast(CopyServiceProcessLaunchDName()));
46 NSString *label = [name stringByAppendingString:@".service_process"];
38 FilePath user_data_dir; 47 FilePath user_data_dir;
39 PathService::Get(chrome::DIR_USER_DATA, &user_data_dir); 48 PathService::Get(chrome::DIR_USER_DATA, &user_data_dir);
40 std::string user_data_dir_path = user_data_dir.value(); 49 std::string user_data_dir_path = user_data_dir.value();
41 NSString *ns_path = base::SysUTF8ToNSString(user_data_dir_path); 50 NSString *ns_path = base::SysUTF8ToNSString(user_data_dir_path);
42 ns_path = [ns_path stringByReplacingOccurrencesOfString:@" " 51 ns_path = [ns_path stringByReplacingOccurrencesOfString:@" "
43 withString:@"_"]; 52 withString:@"_"];
44 label = [label stringByAppendingString:ns_path]; 53 label = [label stringByAppendingString:ns_path];
45 return label; 54 return label;
46 } 55 }
47 56
48 NSString* GetServiceProcessLaunchDSocketKey() { 57 NSString* GetServiceProcessLaunchDSocketKey() {
49 return @"ServiceProcessSocket"; 58 return @"ServiceProcessSocket";
50 } 59 }
51 60
52 NSString* GetServiceProcessLaunchDSocketEnvVar() { 61 NSString* GetServiceProcessLaunchDSocketEnvVar() {
53 NSString *label = GetServiceProcessLaunchDLabel(); 62 NSString *label = GetServiceProcessLaunchDLabel();
54 NSString *env_var = [label stringByReplacingOccurrencesOfString:@"." 63 NSString *env_var = [label stringByReplacingOccurrencesOfString:@"."
55 withString:@"_"]; 64 withString:@"_"];
56 env_var = [env_var stringByAppendingString:@"_SOCKET"]; 65 env_var = [env_var stringByAppendingString:@"_SOCKET"];
57 env_var = [env_var uppercaseString]; 66 env_var = [env_var uppercaseString];
58 return env_var; 67 return env_var;
59 } 68 }
60 69
61 // Creates the path that it returns. Must be called on the FILE thread. 70 bool GetParentFSRef(const FSRef& child, FSRef* parent) {
62 NSURL* GetUserAgentPath() { 71 return FSGetCatalogInfo(&child, 0, NULL, NULL, NULL, parent) == noErr;
63 NSArray* library_paths = NSSearchPathForDirectoriesInDomains(
64 NSLibraryDirectory, NSUserDomainMask, true);
65 DCHECK_EQ([library_paths count], 1U);
66 NSString* library_path = [library_paths objectAtIndex:0];
67 NSString* launch_agents_path =
68 [library_path stringByAppendingPathComponent:@"LaunchAgents"];
69
70 NSError* err;
71 if (![[NSFileManager defaultManager] createDirectoryAtPath:launch_agents_path
72 withIntermediateDirectories:YES
73 attributes:nil
74 error:&err]) {
75 LOG(ERROR) << "GetUserAgentPath: " << err;
76 }
77
78 NSString* plist_file_path =
79 [launch_agents_path
80 stringByAppendingPathComponent:GetServiceProcessLaunchDFileName()];
81 return [NSURL fileURLWithPath:plist_file_path isDirectory:NO];
82 } 72 }
83 73
84 } 74 class ExecFilePathWatcherDelegate : public FilePathWatcher::Delegate {
75 public:
76 ExecFilePathWatcherDelegate() : process_state_(NULL) { }
77 bool Init(const FilePath& path, ServiceProcessState *process_state);
78 virtual ~ExecFilePathWatcherDelegate() { }
79 virtual void OnFilePathChanged(const FilePath& path) OVERRIDE;
80
81 private:
82 FSRef executable_fsref_;
83 ServiceProcessState* process_state_;
84 };
85
86 } // namespace
85 87
86 // Gets the name of the service process IPC channel. 88 // Gets the name of the service process IPC channel.
87 IPC::ChannelHandle GetServiceProcessChannel() { 89 IPC::ChannelHandle GetServiceProcessChannel() {
90 base::mac::ScopedNSAutoreleasePool pool;
88 std::string socket_path; 91 std::string socket_path;
89 scoped_nsobject<NSDictionary> dictionary( 92 scoped_nsobject<NSDictionary> dictionary(
90 base::mac::CFToNSCast(GTMCopyLaunchdExports())); 93 base::mac::CFToNSCast(Launchd::GetInstance()->CopyExports()));
91 NSString *ns_socket_path = 94 NSString *ns_socket_path =
92 [dictionary objectForKey:GetServiceProcessLaunchDSocketEnvVar()]; 95 [dictionary objectForKey:GetServiceProcessLaunchDSocketEnvVar()];
93 if (ns_socket_path) { 96 if (ns_socket_path) {
94 socket_path = base::SysNSStringToUTF8(ns_socket_path); 97 socket_path = base::SysNSStringToUTF8(ns_socket_path);
95 } 98 }
96 return IPC::ChannelHandle(socket_path); 99 return IPC::ChannelHandle(socket_path);
97 } 100 }
98 101
99 bool ForceServiceProcessShutdown(const std::string& /* version */, 102 bool ForceServiceProcessShutdown(const std::string& /* version */,
100 base::ProcessId /* process_id */) { 103 base::ProcessId /* process_id */) {
101 NSString* label = GetServiceProcessLaunchDLabel(); 104 base::mac::ScopedNSAutoreleasePool pool;
105 CFStringRef label = base::mac::NSToCFCast(GetServiceProcessLaunchDLabel());
102 CFErrorRef err = NULL; 106 CFErrorRef err = NULL;
103 bool ret = GTMSMJobRemove(reinterpret_cast<CFStringRef>(label), &err); 107 bool ret = Launchd::GetInstance()->RemoveJob(label, &err);
104 if (!ret) { 108 if (!ret) {
105 LOG(ERROR) << "ForceServiceProcessShutdown: " << err; 109 LOG(ERROR) << "ForceServiceProcessShutdown: " << err;
106 CFRelease(err); 110 CFRelease(err);
107 } 111 }
108 return ret; 112 return ret;
109 } 113 }
110 114
111 bool GetServiceProcessData(std::string* version, base::ProcessId* pid) { 115 bool GetServiceProcessData(std::string* version, base::ProcessId* pid) {
112 CFStringRef label = 116 base::mac::ScopedNSAutoreleasePool pool;
113 reinterpret_cast<CFStringRef>(GetServiceProcessLaunchDLabel()); 117 CFStringRef label = base::mac::NSToCFCast(GetServiceProcessLaunchDLabel());
114 scoped_nsobject<NSDictionary> launchd_conf( 118 scoped_nsobject<NSDictionary> launchd_conf(base::mac::CFToNSCast(
115 base::mac::CFToNSCast(GTMSMJobCopyDictionary(label))); 119 Launchd::GetInstance()->CopyJobDictionary(label)));
116 if (!launchd_conf.get()) { 120 if (!launchd_conf.get()) {
117 return false; 121 return false;
118 } 122 }
119 // Anything past here will return true in that there does appear 123 // Anything past here will return true in that there does appear
120 // to be a service process of some sort registered with launchd. 124 // to be a service process of some sort registered with launchd.
121 if (version) { 125 if (version) {
122 *version = "0"; 126 *version = "0";
123 NSString *exe_path = [launchd_conf objectForKey:@ LAUNCH_JOBKEY_PROGRAM]; 127 NSString *exe_path = [launchd_conf objectForKey:@ LAUNCH_JOBKEY_PROGRAM];
124 if (exe_path) { 128 if (exe_path) {
125 NSString *bundle_path = [[[exe_path stringByDeletingLastPathComponent] 129 NSString *bundle_path = [[[exe_path stringByDeletingLastPathComponent]
(...skipping 27 matching lines...) Expand all
153 } 157 }
154 } 158 }
155 return true; 159 return true;
156 } 160 }
157 161
158 bool ServiceProcessState::Initialize() { 162 bool ServiceProcessState::Initialize() {
159 if (!CreateState()) { 163 if (!CreateState()) {
160 return false; 164 return false;
161 } 165 }
162 CFErrorRef err = NULL; 166 CFErrorRef err = NULL;
163 state_->launchd_conf_.reset(GTMSMJobCheckIn(&err)); 167 CFDictionaryRef dict =
164 if (!state_->launchd_conf_.get()) { 168 Launchd::GetInstance()->CopyDictionaryByCheckingIn(&err);
165 LOG(ERROR) << "InitializePlatformState: " << err; 169
170 if (!dict) {
171 LOG(ERROR) << "CopyLaunchdDictionaryByCheckingIn: " << err;
166 CFRelease(err); 172 CFRelease(err);
167 return false; 173 return false;
168 } 174 }
175 state_->launchd_conf_.reset(dict);
176 state_->ui_message_loop_= base::MessageLoopProxy::CreateForCurrentThread();
169 return true; 177 return true;
170 } 178 }
171 179
172 IPC::ChannelHandle ServiceProcessState::GetServiceProcessChannel() { 180 IPC::ChannelHandle ServiceProcessState::GetServiceProcessChannel() {
173 CHECK(state_); 181 CHECK(state_);
174 NSDictionary *ns_launchd_conf = base::mac::CFToNSCast(state_->launchd_conf_); 182 NSDictionary *ns_launchd_conf = base::mac::CFToNSCast(state_->launchd_conf_);
175 NSDictionary* socket_dict = 183 NSDictionary* socket_dict =
176 [ns_launchd_conf objectForKey:@ LAUNCH_JOBKEY_SOCKETS]; 184 [ns_launchd_conf objectForKey:@ LAUNCH_JOBKEY_SOCKETS];
177 NSArray* sockets = 185 NSArray* sockets =
178 [socket_dict objectForKey:GetServiceProcessLaunchDSocketKey()]; 186 [socket_dict objectForKey:GetServiceProcessLaunchDSocketKey()];
(...skipping 80 matching lines...) Expand 10 before | Expand all | Expand 10 after
259 // relaunch the service automatically in any other case than exiting 267 // relaunch the service automatically in any other case than exiting
260 // cleanly with a 0 return code. 268 // cleanly with a 0 return code.
261 NSDictionary *keep_alive = 269 NSDictionary *keep_alive =
262 [NSDictionary 270 [NSDictionary
263 dictionaryWithObject:[NSNumber numberWithBool:NO] 271 dictionaryWithObject:[NSNumber numberWithBool:NO]
264 forKey:@ LAUNCH_JOBKEY_KEEPALIVE_SUCCESSFULEXIT]; 272 forKey:@ LAUNCH_JOBKEY_KEEPALIVE_SUCCESSFULEXIT];
265 NSDictionary *auto_launchd_plist = 273 NSDictionary *auto_launchd_plist =
266 [[NSDictionary alloc] initWithObjectsAndKeys: 274 [[NSDictionary alloc] initWithObjectsAndKeys:
267 [NSNumber numberWithBool:YES], @ LAUNCH_JOBKEY_RUNATLOAD, 275 [NSNumber numberWithBool:YES], @ LAUNCH_JOBKEY_RUNATLOAD,
268 keep_alive, @ LAUNCH_JOBKEY_KEEPALIVE, 276 keep_alive, @ LAUNCH_JOBKEY_KEEPALIVE,
269 @"Background", @ LAUNCH_JOBKEY_LIMITLOADTOSESSIONTYPE, 277 @ kServiceProcessSessionType, @ LAUNCH_JOBKEY_LIMITLOADTOSESSIONTYPE,
270 nil]; 278 nil];
271 [launchd_plist addEntriesFromDictionary:auto_launchd_plist]; 279 [launchd_plist addEntriesFromDictionary:auto_launchd_plist];
272 } 280 }
273 return reinterpret_cast<CFDictionaryRef>(launchd_plist); 281 return reinterpret_cast<CFDictionaryRef>(launchd_plist);
274 } 282 }
275 283
276 // Writes the launchd property list into the user's LaunchAgents directory, 284 // Writes the launchd property list into the user's LaunchAgents directory,
277 // creating that directory if needed. This will cause the service process to be 285 // creating that directory if needed. This will cause the service process to be
278 // auto launched on the next user login. 286 // auto launched on the next user login.
279 bool ServiceProcessState::AddToAutoRun() { 287 bool ServiceProcessState::AddToAutoRun() {
280 // We're creating directories and writing a file. 288 // We're creating directories and writing a file.
281 base::ThreadRestrictions::AssertIOAllowed(); 289 base::ThreadRestrictions::AssertIOAllowed();
282 DCHECK(autorun_command_line_.get()); 290 DCHECK(autorun_command_line_.get());
283 291 base::mac::ScopedCFTypeRef<CFStringRef> name(CopyServiceProcessLaunchDName());
284 base::mac::ScopedNSAutoreleasePool pool; 292 base::mac::ScopedCFTypeRef<CFDictionaryRef> plist(
285 scoped_nsobject<NSDictionary> plist( 293 CreateServiceProcessLaunchdPlist(autorun_command_line_.get(), true));
286 base::mac::CFToNSCast(CreateServiceProcessLaunchdPlist( 294 return Launchd::GetInstance()->WritePlistToFile(Launchd::User,
287 autorun_command_line_.get(), true))); 295 Launchd::Agent,
288 NSURL* plist_url = GetUserAgentPath(); 296 name,
289 return [plist writeToURL:plist_url atomically:YES]; 297 plist);
290 } 298 }
291 299
292 bool ServiceProcessState::RemoveFromAutoRun() { 300 bool ServiceProcessState::RemoveFromAutoRun() {
293 // We're killing a file. 301 // We're killing a file.
294 base::ThreadRestrictions::AssertIOAllowed(); 302 base::ThreadRestrictions::AssertIOAllowed();
303 base::mac::ScopedCFTypeRef<CFStringRef> name(CopyServiceProcessLaunchDName());
304 return Launchd::GetInstance()->DeletePlist(Launchd::User,
305 Launchd::Agent,
306 name);
307 }
295 308
309 void ServiceProcessState::StateData::WatchExecutable() {
296 base::mac::ScopedNSAutoreleasePool pool; 310 base::mac::ScopedNSAutoreleasePool pool;
297 NSURL* plist_url = GetUserAgentPath(); 311 NSDictionary* ns_launchd_conf = base::mac::CFToNSCast(launchd_conf_);
298 SInt32 error = 0; 312 NSString* exe_path = [ns_launchd_conf objectForKey:@ LAUNCH_JOBKEY_PROGRAM];
299 if (!CFURLDestroyResource(reinterpret_cast<CFURLRef>(plist_url), &error)) { 313 if (!exe_path) {
300 LOG(ERROR) << "RemoveFromAutoRun: " << error; 314 LOG(ERROR) << "No " LAUNCH_JOBKEY_PROGRAM;
315 return;
316 }
317
318 FilePath executable_path = FilePath([exe_path fileSystemRepresentation]);
319 scoped_ptr<ExecFilePathWatcherDelegate> delegate(
320 new ExecFilePathWatcherDelegate);
321 if (!delegate->Init(executable_path, state_)) {
322 LOG(ERROR) << "executable_watcher_.Init " << executable_path.value();
323 return;
324 }
325 if (!executable_watcher_.Watch(executable_path,
326 delegate.release(),
327 ui_message_loop_)) {
328 LOG(ERROR) << "executable_watcher_.watch " << executable_path.value();
329 return;
330 }
331 }
332
333 bool ExecFilePathWatcherDelegate::Init(const FilePath& path,
334 ServiceProcessState *process_state) {
335 if (!process_state ||
336 !base::mac::FSRefFromPath(path.value(), &executable_fsref_)) {
301 return false; 337 return false;
302 } 338 }
339 process_state_ = process_state;
303 return true; 340 return true;
304 } 341 }
342
343 void ExecFilePathWatcherDelegate::OnFilePathChanged(const FilePath& path) {
344 base::mac::ScopedNSAutoreleasePool pool;
345 bool needs_shutdown = false;
346 bool needs_restart = false;
347 bool good_bundle = false;
348
349 FSRef macos_fsref;
350 if (GetParentFSRef(executable_fsref_, &macos_fsref)) {
351 FSRef contents_fsref;
352 if (GetParentFSRef(macos_fsref, &contents_fsref)) {
353 FSRef bundle_fsref;
354 if (GetParentFSRef(contents_fsref, &bundle_fsref)) {
355 base::mac::ScopedCFTypeRef<CFURLRef> bundle_url(
356 CFURLCreateFromFSRef(kCFAllocatorDefault, &bundle_fsref));
357 if (bundle_url.get()) {
358 base::mac::ScopedCFTypeRef<CFBundleRef> bundle(
359 CFBundleCreate(kCFAllocatorDefault, bundle_url));
360 // Check to see if the bundle still has a minimal structure.
361 good_bundle = CFBundleGetIdentifier(bundle) != NULL;
362 }
363 }
364 }
365 }
366 if (!good_bundle) {
367 needs_shutdown = true;
368 } else {
369 Boolean in_trash;
370 OSErr err = FSDetermineIfRefIsEnclosedByFolder(kOnAppropriateDisk,
371 kTrashFolderType,
372 &executable_fsref_,
373 &in_trash);
374 if (err == noErr && in_trash) {
375 needs_shutdown = true;
376 } else {
377 bool was_moved = true;
378 FSRef path_ref;
379 if (base::mac::FSRefFromPath(path.value(), &path_ref)) {
380 if (FSCompareFSRefs(&path_ref, &executable_fsref_) == noErr) {
381 was_moved = false;
382 }
383 }
384 if (was_moved) {
385 needs_restart = true;
386 }
387 }
388 }
389 if (needs_shutdown || needs_restart) {
390 // First deal with the plist.
391 base::mac::ScopedCFTypeRef<CFStringRef> name(
392 CopyServiceProcessLaunchDName());
393 if (needs_restart) {
394 base::mac::ScopedCFTypeRef<CFMutableDictionaryRef> plist(
395 Launchd::GetInstance()->CreatePlistFromFile(Launchd::User,
396 Launchd::Agent,
397 name));
398 if (plist.get()) {
399 NSMutableDictionary* ns_plist = base::mac::CFToNSCast(plist);
400 std::string new_path = base::mac::PathFromFSRef(executable_fsref_);
401 NSString* ns_new_path = base::SysUTF8ToNSString(new_path);
402 [ns_plist setObject:ns_new_path forKey:@ LAUNCH_JOBKEY_PROGRAM];
403 scoped_nsobject<NSMutableArray> args(
404 [[ns_plist objectForKey:@ LAUNCH_JOBKEY_PROGRAMARGUMENTS]
405 mutableCopy]);
406 [args replaceObjectAtIndex:0 withObject:ns_new_path];
407 [ns_plist setObject:args forKey:@ LAUNCH_JOBKEY_PROGRAMARGUMENTS];
408 if (!Launchd::GetInstance()->WritePlistToFile(Launchd::User,
409 Launchd::Agent,
410 name,
411 plist)) {
412 LOG(ERROR) << "Unable to rewrite plist.";
413 needs_shutdown = true;
414 }
415 } else {
416 LOG(ERROR) << "Unable to read plist.";
417 needs_shutdown = true;
418 }
419 }
420 if (needs_shutdown) {
421 if (!process_state_->RemoveFromAutoRun()) {
422 LOG(ERROR) << "Unable to RemoveFromAutoRun.";
423 }
424 }
425
426 // Then deal with the process.
427 CFStringRef session_type = CFSTR(kServiceProcessSessionType);
428 if (needs_restart) {
429 if (!Launchd::GetInstance()->RestartJob(Launchd::User,
430 Launchd::Agent,
431 name,
432 session_type)) {
433 LOG(ERROR) << "RestartLaunchdJob";
434 needs_shutdown = true;
435 }
436 }
437 if (needs_shutdown) {
438 CFStringRef label =
439 base::mac::NSToCFCast(GetServiceProcessLaunchDLabel());
440 CFErrorRef err = NULL;
441 if (!Launchd::GetInstance()->RemoveJob(label, &err)) {
442 base::mac::ScopedCFTypeRef<CFErrorRef> scoped_err(err);
443 LOG(ERROR) << "RemoveJob " << err;
444 // Exiting with zero, so launchd doesn't restart the process.
445 exit(0);
446 }
447 }
448 }
449 }
OLDNEW
« no previous file with comments | « chrome/common/service_process_util.cc ('k') | chrome/common/service_process_util_posix.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698