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/common/mac/launchd.h" 6 7 #import <Foundation/Foundation.h> 8 #include <launch.h> 9 10 #include "base/mac/mac_util.h" 11 #include "base/mac/scoped_cftyperef.h" 12 #include "base/mac/scoped_nsautorelease_pool.h" 13 #include "base/process/launch.h" 14 #include "base/strings/stringprintf.h" 15 #include "base/strings/sys_string_conversions.h" 16 #include "third_party/google_toolbox_for_mac/src/Foundation/GTMServiceManagement.h" 17 18 namespace { 19 20 NSString* SanitizeShellArgument(NSString* arg) { 21 if (!arg) { 22 return nil; 23 } 24 NSString *sanitize = [arg stringByReplacingOccurrencesOfString:@"'" 25 withString:@"'\''"]; 26 return [NSString stringWithFormat:@"'%@'", sanitize]; 27 } 28 29 NSURL* GetPlistURL(Launchd::Domain domain, 30 Launchd::Type type, 31 CFStringRef name) { 32 NSArray* library_paths = 33 NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, domain, YES); 34 DCHECK_EQ([library_paths count], 1U); 35 NSString* library_path = [library_paths objectAtIndex:0]; 36 37 NSString *launch_dir_name = (type == Launchd::Daemon) ? @"LaunchDaemons" 38 : @"LaunchAgents"; 39 NSString* launch_dir = 40 [library_path stringByAppendingPathComponent:launch_dir_name]; 41 42 NSError* err; 43 if (![[NSFileManager defaultManager] createDirectoryAtPath:launch_dir 44 withIntermediateDirectories:YES 45 attributes:nil 46 error:&err]) { 47 DLOG(ERROR) << "GetPlistURL " << base::mac::NSToCFCast(err); 48 return nil; 49 } 50 51 NSString* plist_file_path = 52 [launch_dir stringByAppendingPathComponent:base::mac::CFToNSCast(name)]; 53 plist_file_path = [plist_file_path stringByAppendingPathExtension:@"plist"]; 54 return [NSURL fileURLWithPath:plist_file_path isDirectory:NO]; 55 } 56 57 } // namespace 58 59 COMPILE_ASSERT(static_cast<int>(Launchd::User) == 60 static_cast<int>(NSUserDomainMask), 61 NSUserDomainMask_value_changed); 62 COMPILE_ASSERT(static_cast<int>(Launchd::Local) == 63 static_cast<int>(NSLocalDomainMask), 64 NSLocalDomainMask_value_changed); 65 COMPILE_ASSERT(static_cast<int>(Launchd::Network) == 66 static_cast<int>(NSNetworkDomainMask), 67 NSNetworkDomainMask_value_changed); 68 COMPILE_ASSERT(static_cast<int>(Launchd::System) == 69 static_cast<int>(NSSystemDomainMask), 70 NSSystemDomainMask_value_changed); 71 72 Launchd* Launchd::g_instance_ = NULL; 73 74 Launchd* Launchd::GetInstance() { 75 if (!g_instance_) { 76 g_instance_ = Singleton<Launchd>::get(); 77 } 78 return g_instance_; 79 } 80 81 void Launchd::SetInstance(Launchd* instance) { 82 if (instance) { 83 CHECK(!g_instance_); 84 } 85 g_instance_ = instance; 86 } 87 88 Launchd::~Launchd() { } 89 90 CFDictionaryRef Launchd::CopyExports() { 91 return GTMCopyLaunchdExports(); 92 } 93 94 CFDictionaryRef Launchd::CopyJobDictionary(CFStringRef label) { 95 return GTMSMJobCopyDictionary(label); 96 } 97 98 CFDictionaryRef Launchd::CopyDictionaryByCheckingIn(CFErrorRef* error) { 99 return GTMSMCopyJobCheckInDictionary(error); 100 } 101 102 bool Launchd::RemoveJob(CFStringRef label, CFErrorRef* error) { 103 return GTMSMJobRemove(label, error); 104 } 105 106 bool Launchd::RestartJob(Domain domain, 107 Type type, 108 CFStringRef name, 109 CFStringRef cf_session_type) { 110 base::mac::ScopedNSAutoreleasePool pool; 111 NSURL* url = GetPlistURL(domain, type, name); 112 NSString* ns_path = [url path]; 113 ns_path = SanitizeShellArgument(ns_path); 114 const char* file_path = [ns_path fileSystemRepresentation]; 115 116 NSString* ns_session_type = 117 SanitizeShellArgument(base::mac::CFToNSCast(cf_session_type)); 118 if (!file_path || !ns_session_type) { 119 return false; 120 } 121 122 std::vector<std::string> argv; 123 argv.push_back("/bin/bash"); 124 argv.push_back("--noprofile"); 125 argv.push_back("-c"); 126 std::string command = base::StringPrintf( 127 "/bin/launchctl unload -S %s %s;" 128 "/bin/launchctl load -S %s %s;", 129 [ns_session_type UTF8String], file_path, 130 [ns_session_type UTF8String], file_path); 131 argv.push_back(command); 132 133 base::LaunchOptions options; 134 options.new_process_group = true; 135 return base::LaunchProcess(argv, options, NULL); 136 } 137 138 CFMutableDictionaryRef Launchd::CreatePlistFromFile(Domain domain, 139 Type type, 140 CFStringRef name) { 141 base::mac::ScopedNSAutoreleasePool pool; 142 NSURL* ns_url = GetPlistURL(domain, type, name); 143 NSMutableDictionary* plist = 144 [[NSMutableDictionary alloc] initWithContentsOfURL:ns_url]; 145 return base::mac::NSToCFCast(plist); 146 } 147 148 bool Launchd::WritePlistToFile(Domain domain, 149 Type type, 150 CFStringRef name, 151 CFDictionaryRef dict) { 152 base::mac::ScopedNSAutoreleasePool pool; 153 NSURL* ns_url = GetPlistURL(domain, type, name); 154 return [base::mac::CFToNSCast(dict) writeToURL:ns_url atomically:YES]; 155 } 156 157 bool Launchd::DeletePlist(Domain domain, Type type, CFStringRef name) { 158 base::mac::ScopedNSAutoreleasePool pool; 159 NSURL* ns_url = GetPlistURL(domain, type, name); 160 NSError* err = nil; 161 if (![[NSFileManager defaultManager] removeItemAtPath:[ns_url path] 162 error:&err]) { 163 if ([err code] != NSFileNoSuchFileError) { 164 DLOG(ERROR) << "DeletePlist: " << base::mac::NSToCFCast(err); 165 } 166 return false; 167 } 168 return true; 169 } 170