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