// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/common/mac/launchd.h"

#import <Foundation/Foundation.h>
#include <launch.h>

#include "base/mac/mac_util.h"
#include "base/mac/scoped_cftyperef.h"
#include "base/mac/scoped_nsautorelease_pool.h"
#include "base/process/launch.h"
#include "base/strings/stringprintf.h"
#include "base/strings/sys_string_conversions.h"
#include "third_party/google_toolbox_for_mac/src/Foundation/GTMServiceManagement.h"

namespace {

NSString* SanitizeShellArgument(NSString* arg) {
  if (!arg) {
    return nil;
  }
  NSString *sanitize = [arg stringByReplacingOccurrencesOfString:@"'"
                                                      withString:@"'\''"];
  return [NSString stringWithFormat:@"'%@'", sanitize];
}

NSURL* GetPlistURL(Launchd::Domain domain,
                   Launchd::Type type,
                   CFStringRef name) {
  NSArray* library_paths =
      NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, domain, YES);
  DCHECK_EQ([library_paths count], 1U);
  NSString* library_path = [library_paths objectAtIndex:0];

  NSString *launch_dir_name = (type == Launchd::Daemon) ? @"LaunchDaemons"
                                                        : @"LaunchAgents";
  NSString* launch_dir =
      [library_path stringByAppendingPathComponent:launch_dir_name];

  NSError* err;
  if (![[NSFileManager defaultManager] createDirectoryAtPath:launch_dir
                                 withIntermediateDirectories:YES
                                                  attributes:nil
                                                       error:&err]) {
    DLOG(ERROR) << "GetPlistURL " << base::mac::NSToCFCast(err);
    return nil;
  }

  NSString* plist_file_path =
      [launch_dir stringByAppendingPathComponent:base::mac::CFToNSCast(name)];
  plist_file_path = [plist_file_path stringByAppendingPathExtension:@"plist"];
  return [NSURL fileURLWithPath:plist_file_path isDirectory:NO];
}

}  // namespace

COMPILE_ASSERT(static_cast<int>(Launchd::User) ==
               static_cast<int>(NSUserDomainMask),
               NSUserDomainMask_value_changed);
COMPILE_ASSERT(static_cast<int>(Launchd::Local) ==
               static_cast<int>(NSLocalDomainMask),
               NSLocalDomainMask_value_changed);
COMPILE_ASSERT(static_cast<int>(Launchd::Network) ==
               static_cast<int>(NSNetworkDomainMask),
               NSNetworkDomainMask_value_changed);
COMPILE_ASSERT(static_cast<int>(Launchd::System) ==
               static_cast<int>(NSSystemDomainMask),
               NSSystemDomainMask_value_changed);

Launchd* Launchd::g_instance_ = NULL;

Launchd* Launchd::GetInstance() {
  if (!g_instance_) {
    g_instance_ = Singleton<Launchd>::get();
  }
  return g_instance_;
}

void Launchd::SetInstance(Launchd* instance) {
  if (instance) {
    CHECK(!g_instance_);
  }
  g_instance_ = instance;
}

Launchd::~Launchd() { }

CFDictionaryRef Launchd::CopyExports() {
  return GTMCopyLaunchdExports();
}

CFDictionaryRef Launchd::CopyJobDictionary(CFStringRef label) {
  return GTMSMJobCopyDictionary(label);
}

CFDictionaryRef Launchd::CopyDictionaryByCheckingIn(CFErrorRef* error) {
  return GTMSMCopyJobCheckInDictionary(error);
}

bool Launchd::RemoveJob(CFStringRef label, CFErrorRef* error) {
  return GTMSMJobRemove(label, error);
}

bool Launchd::RestartJob(Domain domain,
                         Type type,
                         CFStringRef name,
                         CFStringRef cf_session_type) {
  base::mac::ScopedNSAutoreleasePool pool;
  NSURL* url = GetPlistURL(domain, type, name);
  NSString* ns_path = [url path];
  ns_path = SanitizeShellArgument(ns_path);
  const char* file_path = [ns_path fileSystemRepresentation];

  NSString* ns_session_type =
      SanitizeShellArgument(base::mac::CFToNSCast(cf_session_type));
  if (!file_path || !ns_session_type) {
    return false;
  }

  std::vector<std::string> argv;
  argv.push_back("/bin/bash");
  argv.push_back("--noprofile");
  argv.push_back("-c");
  std::string command = base::StringPrintf(
      "/bin/launchctl unload -S %s %s;"
      "/bin/launchctl load -S %s %s;",
      [ns_session_type UTF8String], file_path,
      [ns_session_type UTF8String], file_path);
  argv.push_back(command);

  base::LaunchOptions options;
  options.new_process_group = true;
  return base::LaunchProcess(argv, options, NULL);
}

CFMutableDictionaryRef Launchd::CreatePlistFromFile(Domain domain,
                                                    Type type,
                                                    CFStringRef name) {
  base::mac::ScopedNSAutoreleasePool pool;
  NSURL* ns_url = GetPlistURL(domain, type, name);
  NSMutableDictionary* plist =
      [[NSMutableDictionary alloc] initWithContentsOfURL:ns_url];
  return base::mac::NSToCFCast(plist);
}

bool Launchd::WritePlistToFile(Domain domain,
                               Type type,
                               CFStringRef name,
                               CFDictionaryRef dict) {
  base::mac::ScopedNSAutoreleasePool pool;
  NSURL* ns_url = GetPlistURL(domain, type, name);
  return [base::mac::CFToNSCast(dict) writeToURL:ns_url atomically:YES];
}

bool Launchd::DeletePlist(Domain domain, Type type, CFStringRef name) {
  base::mac::ScopedNSAutoreleasePool pool;
  NSURL* ns_url = GetPlistURL(domain, type, name);
  NSError* err = nil;
  if (![[NSFileManager defaultManager] removeItemAtPath:[ns_url path]
                                                  error:&err]) {
    if ([err code] != NSFileNoSuchFileError) {
      DLOG(ERROR) << "DeletePlist: " << base::mac::NSToCFCast(err);
    }
    return false;
  }
  return true;
}