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

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: Move over to FilePathWatcher 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
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 base::mac::ScopedNSAutoreleasePool pool;
37 NSString *label = [bundle_id stringByAppendingString:@".service_process"]; 45
46 scoped_nsobject<NSString> name(
47 base::mac::CFToNSCast(CopyServiceProcessLaunchDName()));
48 NSString *label = [name stringByAppendingString:@".service_process"];
38 FilePath user_data_dir; 49 FilePath user_data_dir;
39 PathService::Get(chrome::DIR_USER_DATA, &user_data_dir); 50 PathService::Get(chrome::DIR_USER_DATA, &user_data_dir);
40 std::string user_data_dir_path = user_data_dir.value(); 51 std::string user_data_dir_path = user_data_dir.value();
41 NSString *ns_path = base::SysUTF8ToNSString(user_data_dir_path); 52 NSString *ns_path = base::SysUTF8ToNSString(user_data_dir_path);
42 ns_path = [ns_path stringByReplacingOccurrencesOfString:@" " 53 ns_path = [ns_path stringByReplacingOccurrencesOfString:@" "
43 withString:@"_"]; 54 withString:@"_"];
44 label = [label stringByAppendingString:ns_path]; 55 label = [label stringByAppendingString:ns_path];
45 return label; 56 return label;
46 } 57 }
47 58
48 NSString* GetServiceProcessLaunchDSocketKey() { 59 NSString* GetServiceProcessLaunchDSocketKey() {
49 return @"ServiceProcessSocket"; 60 return @"ServiceProcessSocket";
50 } 61 }
51 62
52 NSString* GetServiceProcessLaunchDSocketEnvVar() { 63 NSString* GetServiceProcessLaunchDSocketEnvVar() {
53 NSString *label = GetServiceProcessLaunchDLabel(); 64 NSString *label = GetServiceProcessLaunchDLabel();
54 NSString *env_var = [label stringByReplacingOccurrencesOfString:@"." 65 NSString *env_var = [label stringByReplacingOccurrencesOfString:@"."
55 withString:@"_"]; 66 withString:@"_"];
56 env_var = [env_var stringByAppendingString:@"_SOCKET"]; 67 env_var = [env_var stringByAppendingString:@"_SOCKET"];
57 env_var = [env_var uppercaseString]; 68 env_var = [env_var uppercaseString];
58 return env_var; 69 return env_var;
59 } 70 }
60 71
61 // Creates the path that it returns. Must be called on the FILE thread.
62 NSURL* GetUserAgentPath() {
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 72
70 NSError* err; 73 class ExecFilePathWatcherDelegate : public FilePathWatcher::Delegate {
71 if (![[NSFileManager defaultManager] createDirectoryAtPath:launch_agents_path 74 public:
72 withIntermediateDirectories:YES 75 ExecFilePathWatcherDelegate() : process_state_(NULL) { }
73 attributes:nil 76 bool Init(const FilePath& path, ServiceProcessState *process_state);
74 error:&err]) { 77 virtual ~ExecFilePathWatcherDelegate() { }
75 LOG(ERROR) << "GetUserAgentPath: " << err; 78 virtual void OnFilePathChanged(const FilePath& path) OVERRIDE;
76 }
77 79
78 NSString* plist_file_path = 80 private:
79 [launch_agents_path 81 FSRef executable_fsref_;
80 stringByAppendingPathComponent:GetServiceProcessLaunchDFileName()]; 82 ServiceProcessState* process_state_;
81 return [NSURL fileURLWithPath:plist_file_path isDirectory:NO]; 83 };
82 }
83 84
84 } 85 } // namespace
85 86
86 // Gets the name of the service process IPC channel. 87 // Gets the name of the service process IPC channel.
87 IPC::ChannelHandle GetServiceProcessChannel() { 88 IPC::ChannelHandle GetServiceProcessChannel() {
88 std::string socket_path; 89 std::string socket_path;
89 scoped_nsobject<NSDictionary> dictionary( 90 scoped_nsobject<NSDictionary> dictionary(
90 base::mac::CFToNSCast(GTMCopyLaunchdExports())); 91 base::mac::CFToNSCast(Launchd::GetInstance()->CopyLaunchdExports()));
91 NSString *ns_socket_path = 92 NSString *ns_socket_path =
92 [dictionary objectForKey:GetServiceProcessLaunchDSocketEnvVar()]; 93 [dictionary objectForKey:GetServiceProcessLaunchDSocketEnvVar()];
93 if (ns_socket_path) { 94 if (ns_socket_path) {
94 socket_path = base::SysNSStringToUTF8(ns_socket_path); 95 socket_path = base::SysNSStringToUTF8(ns_socket_path);
95 } 96 }
96 return IPC::ChannelHandle(socket_path); 97 return IPC::ChannelHandle(socket_path);
97 } 98 }
98 99
99 bool ForceServiceProcessShutdown(const std::string& /* version */, 100 bool ForceServiceProcessShutdown(const std::string& /* version */,
100 base::ProcessId /* process_id */) { 101 base::ProcessId /* process_id */) {
101 NSString* label = GetServiceProcessLaunchDLabel(); 102 CFStringRef label = base::mac::NSToCFCast(GetServiceProcessLaunchDLabel());
102 CFErrorRef err = NULL; 103 CFErrorRef err = NULL;
103 bool ret = GTMSMJobRemove(reinterpret_cast<CFStringRef>(label), &err); 104 bool ret = Launchd::GetInstance()->RemoveLaunchdJob(label, &err);
104 if (!ret) { 105 if (!ret) {
105 LOG(ERROR) << "ForceServiceProcessShutdown: " << err; 106 LOG(ERROR) << "ForceServiceProcessShutdown: " << err;
106 CFRelease(err); 107 CFRelease(err);
107 } 108 }
108 return ret; 109 return ret;
109 } 110 }
110 111
111 bool GetServiceProcessData(std::string* version, base::ProcessId* pid) { 112 bool GetServiceProcessData(std::string* version, base::ProcessId* pid) {
112 CFStringRef label = 113 CFStringRef label = base::mac::NSToCFCast(GetServiceProcessLaunchDLabel());
113 reinterpret_cast<CFStringRef>(GetServiceProcessLaunchDLabel()); 114 scoped_nsobject<NSDictionary> launchd_conf(base::mac::CFToNSCast(
114 scoped_nsobject<NSDictionary> launchd_conf( 115 Launchd::GetInstance()->CopyLaunchdJobDictionary(label)));
115 base::mac::CFToNSCast(GTMSMJobCopyDictionary(label)));
116 if (!launchd_conf.get()) { 116 if (!launchd_conf.get()) {
117 return false; 117 return false;
118 } 118 }
119 // Anything past here will return true in that there does appear 119 // Anything past here will return true in that there does appear
120 // to be a service process of some sort registered with launchd. 120 // to be a service process of some sort registered with launchd.
121 if (version) { 121 if (version) {
122 *version = "0"; 122 *version = "0";
123 NSString *exe_path = [launchd_conf objectForKey:@ LAUNCH_JOBKEY_PROGRAM]; 123 NSString *exe_path = [launchd_conf objectForKey:@ LAUNCH_JOBKEY_PROGRAM];
124 if (exe_path) { 124 if (exe_path) {
125 NSString *bundle_path = [[[exe_path stringByDeletingLastPathComponent] 125 NSString *bundle_path = [[[exe_path stringByDeletingLastPathComponent]
(...skipping 27 matching lines...) Expand all
153 } 153 }
154 } 154 }
155 return true; 155 return true;
156 } 156 }
157 157
158 bool ServiceProcessState::Initialize() { 158 bool ServiceProcessState::Initialize() {
159 if (!CreateState()) { 159 if (!CreateState()) {
160 return false; 160 return false;
161 } 161 }
162 CFErrorRef err = NULL; 162 CFErrorRef err = NULL;
163 state_->launchd_conf_.reset(GTMSMJobCheckIn(&err)); 163 CFDictionaryRef dict =
164 if (!state_->launchd_conf_.get()) { 164 Launchd::GetInstance()->CopyLaunchdDictionaryByCheckingIn(&err);
165 LOG(ERROR) << "InitializePlatformState: " << err; 165
166 if (!dict) {
167 LOG(ERROR) << "CopyLaunchdDictionaryByCheckingIn: " << err;
166 CFRelease(err); 168 CFRelease(err);
167 return false; 169 return false;
168 } 170 }
171 state_->launchd_conf_.reset(dict);
172 state_->ui_message_loop_= base::MessageLoopProxy::CreateForCurrentThread();
169 return true; 173 return true;
170 } 174 }
171 175
172 IPC::ChannelHandle ServiceProcessState::GetServiceProcessChannel() { 176 IPC::ChannelHandle ServiceProcessState::GetServiceProcessChannel() {
173 CHECK(state_); 177 CHECK(state_);
174 NSDictionary *ns_launchd_conf = base::mac::CFToNSCast(state_->launchd_conf_); 178 NSDictionary *ns_launchd_conf = base::mac::CFToNSCast(state_->launchd_conf_);
175 NSDictionary* socket_dict = 179 NSDictionary* socket_dict =
176 [ns_launchd_conf objectForKey:@ LAUNCH_JOBKEY_SOCKETS]; 180 [ns_launchd_conf objectForKey:@ LAUNCH_JOBKEY_SOCKETS];
177 NSArray* sockets = 181 NSArray* sockets =
178 [socket_dict objectForKey:GetServiceProcessLaunchDSocketKey()]; 182 [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 263 // relaunch the service automatically in any other case than exiting
260 // cleanly with a 0 return code. 264 // cleanly with a 0 return code.
261 NSDictionary *keep_alive = 265 NSDictionary *keep_alive =
262 [NSDictionary 266 [NSDictionary
263 dictionaryWithObject:[NSNumber numberWithBool:NO] 267 dictionaryWithObject:[NSNumber numberWithBool:NO]
264 forKey:@ LAUNCH_JOBKEY_KEEPALIVE_SUCCESSFULEXIT]; 268 forKey:@ LAUNCH_JOBKEY_KEEPALIVE_SUCCESSFULEXIT];
265 NSDictionary *auto_launchd_plist = 269 NSDictionary *auto_launchd_plist =
266 [[NSDictionary alloc] initWithObjectsAndKeys: 270 [[NSDictionary alloc] initWithObjectsAndKeys:
267 [NSNumber numberWithBool:YES], @ LAUNCH_JOBKEY_RUNATLOAD, 271 [NSNumber numberWithBool:YES], @ LAUNCH_JOBKEY_RUNATLOAD,
268 keep_alive, @ LAUNCH_JOBKEY_KEEPALIVE, 272 keep_alive, @ LAUNCH_JOBKEY_KEEPALIVE,
269 @"Background", @ LAUNCH_JOBKEY_LIMITLOADTOSESSIONTYPE, 273 @ kServiceProcessSessionType, @ LAUNCH_JOBKEY_LIMITLOADTOSESSIONTYPE,
270 nil]; 274 nil];
271 [launchd_plist addEntriesFromDictionary:auto_launchd_plist]; 275 [launchd_plist addEntriesFromDictionary:auto_launchd_plist];
272 } 276 }
273 return reinterpret_cast<CFDictionaryRef>(launchd_plist); 277 return reinterpret_cast<CFDictionaryRef>(launchd_plist);
274 } 278 }
275 279
276 // Writes the launchd property list into the user's LaunchAgents directory, 280 // 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 281 // creating that directory if needed. This will cause the service process to be
278 // auto launched on the next user login. 282 // auto launched on the next user login.
279 bool ServiceProcessState::AddToAutoRun() { 283 bool ServiceProcessState::AddToAutoRun() {
280 // We're creating directories and writing a file. 284 // We're creating directories and writing a file.
281 base::ThreadRestrictions::AssertIOAllowed(); 285 base::ThreadRestrictions::AssertIOAllowed();
282 DCHECK(autorun_command_line_.get()); 286 DCHECK(autorun_command_line_.get());
283 287 base::mac::ScopedCFTypeRef<CFStringRef> name(CopyServiceProcessLaunchDName());
284 base::mac::ScopedNSAutoreleasePool pool; 288 base::mac::ScopedCFTypeRef<CFDictionaryRef> plist(
285 scoped_nsobject<NSDictionary> plist( 289 CreateServiceProcessLaunchdPlist(autorun_command_line_.get(), true));
286 base::mac::CFToNSCast(CreateServiceProcessLaunchdPlist( 290 return Launchd::GetInstance()->WriteLaunchdPlist(Launchd::User,
287 autorun_command_line_.get(), true))); 291 Launchd::Agent,
288 NSURL* plist_url = GetUserAgentPath(); 292 name,
289 return [plist writeToURL:plist_url atomically:YES]; 293 plist);
290 } 294 }
291 295
292 bool ServiceProcessState::RemoveFromAutoRun() { 296 bool ServiceProcessState::RemoveFromAutoRun() {
293 // We're killing a file. 297 // We're killing a file.
294 base::ThreadRestrictions::AssertIOAllowed(); 298 base::ThreadRestrictions::AssertIOAllowed();
299 base::mac::ScopedCFTypeRef<CFStringRef> name(CopyServiceProcessLaunchDName());
300 return Launchd::GetInstance()->DeleteLaunchDPlist(Launchd::User,
301 Launchd::Agent,
302 name);
303 }
295 304
296 base::mac::ScopedNSAutoreleasePool pool; 305 void ServiceProcessState::StateData::WatchExecutable(Task* shutdown_task) {
297 NSURL* plist_url = GetUserAgentPath(); 306 CFStringRef exe_path = reinterpret_cast<CFStringRef>(CFDictionaryGetValue(
298 SInt32 error = 0; 307 launchd_conf_, CFSTR(LAUNCH_JOBKEY_PROGRAM)));
299 if (!CFURLDestroyResource(reinterpret_cast<CFURLRef>(plist_url), &error)) { 308 if (!exe_path) {
300 LOG(ERROR) << "RemoveFromAutoRun: " << error; 309 LOG(ERROR) << "No " LAUNCH_JOBKEY_PROGRAM;
310 return;
311 }
312
313 // TODO(dmaclach): Move this into a utility function.
314 // Can also be used in launchd_mac.mm.
315 // http://crbug.com/76928
316 CFIndex max = CFStringGetMaximumSizeOfFileSystemRepresentation(exe_path);
Mark Mentovai 2011/03/21 16:50:49 This is an .mm file, just use NSString as discusse
dmac 2011/03/21 22:59:19 Done.
317 std::vector<char> c_exe_path(max);
318 CHECK(CFStringGetFileSystemRepresentation(exe_path, &c_exe_path[0], max));
319
320 FilePath executable_path = FilePath(&c_exe_path[0]);
321 scoped_ptr<ExecFilePathWatcherDelegate> delegate(
322 new ExecFilePathWatcherDelegate);
323 if (!delegate->Init(executable_path, state_)) {
324 LOG(ERROR) << "executable_watcher_.Init " << executable_path.value();
325 shutdown_task->Run();
Mark Mentovai 2011/03/21 16:50:49 Are the error cases here really the right place to
dmac 2011/03/21 22:59:19 I completely removed the error case for now. I wil
326 delete shutdown_task;
327 }
328 if (!executable_watcher_.Watch(executable_path,
329 delegate.release(),
330 ui_message_loop_)) {
331 LOG(ERROR) << "executable_watcher_.watch " << executable_path.value();
332 shutdown_task->Run();
333 delete shutdown_task;
334 }
335 }
336
337 bool ExecFilePathWatcherDelegate::Init(const FilePath& path,
338 ServiceProcessState *process_state) {
339 if (!process_state ||
340 !base::mac::FSRefFromPath(path.value(), &executable_fsref_)) {
301 return false; 341 return false;
302 } 342 }
343 process_state_ = process_state;
303 return true; 344 return true;
304 } 345 }
346
347 void ExecFilePathWatcherDelegate::OnFilePathChanged(const FilePath& path) {
348 base::mac::ScopedNSAutoreleasePool pool;
349 bool needs_shutdown = false;
350 bool needs_restart = false;
351
352 bool good_bundle = false;
353 FSRef macos_fsref;
354 if (FSGetCatalogInfo(&executable_fsref_,
355 0,
Mark Mentovai 2011/03/21 16:50:49 Just put all of these on one line. No sense in mak
dmac 2011/03/21 22:59:19 Functionized
356 NULL,
357 NULL,
358 NULL,
359 &macos_fsref) == noErr) {
360 FSRef contents_fsref;
361 if (FSGetCatalogInfo(&macos_fsref,
362 0,
363 NULL,
364 NULL,
365 NULL,
366 &contents_fsref) == noErr) {
367 FSRef bundle_fsref;
368 if (FSGetCatalogInfo(&contents_fsref,
369 0,
370 NULL,
371 NULL,
372 NULL,
373 &bundle_fsref) == noErr) {
374 base::mac::ScopedCFTypeRef<CFURLRef> bundle_url(
375 CFURLCreateFromFSRef(kCFAllocatorDefault, &bundle_fsref));
376 if (bundle_url.get()) {
377 base::mac::ScopedCFTypeRef<CFBundleRef> bundle(
378 CFBundleCreate(kCFAllocatorDefault, bundle_url));
379 // Check to see if the bundle still has a minimal structure.
380 good_bundle = CFBundleGetIdentifier(bundle) != NULL;
381 }
382 }
383 }
384 }
385 if (!good_bundle) {
386 needs_shutdown = true;
387 } else {
388 Boolean in_trash;
389 OSErr err = FSDetermineIfRefIsEnclosedByFolder(kOnAppropriateDisk,
390 kTrashFolderType,
391 &executable_fsref_,
392 &in_trash);
393 if (err == noErr && in_trash) {
394 needs_shutdown = true;
395 } else {
396 bool was_moved = true;
397 FSRef path_ref;
398 if (base::mac::FSRefFromPath(path.value(), &path_ref)) {
399 if (FSCompareFSRefs(&path_ref, &executable_fsref_) == noErr) {
400 was_moved = false;
401 }
402 }
403 if (was_moved) {
404 needs_restart = true;
405 }
406 }
407 }
408 if (needs_shutdown || needs_restart) {
Mark Mentovai 2011/03/21 16:50:49 This is a long function. Even if you can’t or don
dmac 2011/03/21 22:59:19 It's been broken down a bit, and I feel it's prett
409 // First deal with the plist.
410 base::mac::ScopedCFTypeRef<CFStringRef> name(
411 CopyServiceProcessLaunchDName());
412 if (needs_restart) {
413 base::mac::ScopedCFTypeRef<CFMutableDictionaryRef> plist(
414 Launchd::GetInstance()->ReadLaunchdPlist(Launchd::User,
415 Launchd::Agent,
416 name));
417 if (plist.get()) {
418 base::mac::ScopedNSAutoreleasePool pool;
419 NSMutableDictionary* ns_plist = base::mac::CFToNSCast(plist);
420 std::string new_path = base::mac::PathFromFSRef(executable_fsref_);
421 NSString* ns_new_path = base::SysUTF8ToNSString(new_path);
422 [ns_plist setObject:ns_new_path forKey:@ LAUNCH_JOBKEY_PROGRAM];
423 scoped_nsobject<NSMutableArray> args(
424 [[ns_plist objectForKey:@ LAUNCH_JOBKEY_PROGRAMARGUMENTS]
425 mutableCopy]);
426 [args replaceObjectAtIndex:0 withObject:ns_new_path];
427 [ns_plist setObject:args forKey:@ LAUNCH_JOBKEY_PROGRAMARGUMENTS];
428 if (!Launchd::GetInstance()->WriteLaunchdPlist(Launchd::User,
429 Launchd::Agent,
430 name,
431 plist)) {
432 LOG(ERROR) << "Unable to rewrite plist.";
433 needs_shutdown = true;
434 }
435 } else {
436 LOG(ERROR) << "Unable to read plist.";
437 needs_shutdown = true;
438 }
439 }
440 if (needs_shutdown) {
441 if (!process_state_->RemoveFromAutoRun()) {
442 LOG(ERROR) << "Unable to RemoveFromAutoRun.";
443 }
444 }
445
446 // Then deal with the process.
447 CFStringRef session_type = CFSTR(kServiceProcessSessionType);
448 if (needs_restart) {
449 if (!Launchd::GetInstance()->RestartLaunchdJob(Launchd::User,
450 Launchd::Agent,
451 name,
452 session_type)) {
453 LOG(ERROR) << "RestartLaunchdJob";
454 needs_shutdown = true;
455 }
456 }
457 if (needs_shutdown) {
458 if (!Launchd::GetInstance()->ShutdownLaunchdJob(Launchd::User,
459 Launchd::Agent,
460 name,
461 session_type)) {
462 LOG(ERROR) << "ShutdownLaunchdJob";
463 }
464 }
465 }
466 }
467
Mark Mentovai 2011/03/21 16:50:49 Blank line at EOF.
dmac 2011/03/21 22:59:19 Done.
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698