blob: 5861c7c849f59c0cda8da37c3e3567485a249de4 [file] [log] [blame]
// Copyright 2018 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/service_management.h"
#import <CoreServices/CoreServices.h>
#import <Foundation/Foundation.h>
#import <ServiceManagement/ServiceManagement.h>
#include <errno.h>
#include <launch.h>
#include "base/compiler_specific.h"
#include "base/mac/foundation_util.h"
#include "base/mac/scoped_nsobject.h"
#include "base/macros.h"
#include "base/strings/sys_string_conversions.h"
// This entire file is written in terms of the launch_data_t API, which is
// deprecated with no replacement, so just ignore the warnings for now.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
namespace {
class ScopedLaunchData {
public:
explicit ScopedLaunchData(launch_data_type_t type)
: data_(launch_data_alloc(type)) {}
explicit ScopedLaunchData(launch_data_t data) : data_(data) {}
ScopedLaunchData(ScopedLaunchData&& other) : data_(other.release()) {}
~ScopedLaunchData() { reset(); }
void reset() {
if (data_)
launch_data_free(data_);
data_ = nullptr;
}
launch_data_t release() WARN_UNUSED_RESULT {
launch_data_t val = data_;
data_ = nullptr;
return val;
}
launch_data_t get() { return data_; }
operator launch_data_t() const { return data_; }
operator bool() const { return !!data_; }
private:
launch_data_t data_;
DISALLOW_COPY_AND_ASSIGN(ScopedLaunchData);
};
ScopedLaunchData SendLaunchMessage(ScopedLaunchData&& msg) {
return ScopedLaunchData(launch_msg(msg));
}
ScopedLaunchData LaunchDataFromString(const std::string& string) {
ScopedLaunchData result(LAUNCH_DATA_STRING);
// launch_data_set_string() will make a copy of the passed-in string.
launch_data_set_string(result, string.c_str());
return result;
}
int ErrnoFromLaunchData(launch_data_t data) {
if (launch_data_get_type(data) != LAUNCH_DATA_ERRNO)
return EINVAL;
return launch_data_get_errno(data);
}
bool StringFromLaunchDataDictEntry(launch_data_t dict,
const char* key,
std::string* value) {
launch_data_t entry = launch_data_dict_lookup(dict, key);
if (!entry || launch_data_get_type(entry) != LAUNCH_DATA_STRING)
return false;
*value = std::string(launch_data_get_string(entry));
return true;
}
bool IntFromLaunchDataDictEntry(launch_data_t dict,
const char* key,
int* value) {
launch_data_t entry = launch_data_dict_lookup(dict, key);
if (!entry || launch_data_get_type(entry) != LAUNCH_DATA_INTEGER)
return false;
*value = launch_data_get_integer(entry);
return true;
}
// Extracts the first integer value from |dict[key]|, which is itself an array,
// and returns it in |*value|. This means that the type of dict is:
// map<string, array<int>>
bool FirstIntFromLaunchDataDictEntry(launch_data_t dict,
const char* key,
int* value) {
launch_data_t array = launch_data_dict_lookup(dict, key);
if (!array || launch_data_get_type(array) != LAUNCH_DATA_ARRAY ||
launch_data_array_get_count(array) == 0) {
return false;
}
launch_data_t entry = launch_data_array_get_index(array, 0);
if (launch_data_get_type(entry) == LAUNCH_DATA_INTEGER)
*value = launch_data_get_integer(entry);
else if (launch_data_get_type(entry) == LAUNCH_DATA_FD)
*value = launch_data_get_fd(entry);
else
return false;
return true;
}
ScopedLaunchData DoServiceOp(const char* verb,
const std::string& label,
int* error) {
ScopedLaunchData msg(LAUNCH_DATA_DICTIONARY);
launch_data_dict_insert(msg, LaunchDataFromString(label).release(), verb);
ScopedLaunchData result(SendLaunchMessage(std::move(msg)));
if (!result)
*error = errno;
return result;
}
NSArray* NSArrayFromStringVector(const std::vector<std::string>& vec) {
NSMutableArray* args = [NSMutableArray arrayWithCapacity:vec.size()];
for (const auto& item : vec) {
[args addObject:base::SysUTF8ToNSString(item)];
}
return args;
}
base::scoped_nsobject<NSDictionary> DictionaryForJobOptions(
const mac::services::JobOptions& options) {
base::scoped_nsobject<NSMutableDictionary> opts(
[[NSMutableDictionary alloc] init]);
[opts setObject:base::SysUTF8ToNSString(options.label)
forKey:@LAUNCH_JOBKEY_LABEL];
if (!options.executable_path.empty()) {
[opts setObject:base::SysUTF8ToNSString(options.executable_path)
forKey:@LAUNCH_JOBKEY_PROGRAM];
}
if (!options.arguments.empty()) {
[opts setObject:NSArrayFromStringVector(options.arguments)
forKey:@LAUNCH_JOBKEY_PROGRAMARGUMENTS];
}
DCHECK_EQ(options.socket_name.empty(), options.socket_key.empty());
if (!options.socket_name.empty() && !options.socket_key.empty()) {
NSDictionary* inner_dict = [NSDictionary
dictionaryWithObject:base::SysUTF8ToNSString(options.socket_name)
forKey:@LAUNCH_JOBSOCKETKEY_PATHNAME];
NSDictionary* outer_dict = [NSDictionary
dictionaryWithObject:inner_dict
forKey:base::SysUTF8ToNSString(options.socket_key)];
[opts setObject:outer_dict forKey:@LAUNCH_JOBKEY_SOCKETS];
}
if (options.run_at_load || options.auto_launch) {
[opts setObject:@YES forKey:@LAUNCH_JOBKEY_RUNATLOAD];
}
if (options.auto_launch) {
[opts setObject:@{
@LAUNCH_JOBKEY_KEEPALIVE_SUCCESSFULEXIT : @NO
}
forKey:@LAUNCH_JOBKEY_KEEPALIVE];
[opts setObject:@"Aqua" forKey:@LAUNCH_JOBKEY_LIMITLOADTOSESSIONTYPE];
}
return base::scoped_nsobject<NSDictionary>(opts.release());
}
} // namespace
namespace mac {
namespace services {
JobInfo::JobInfo() = default;
JobInfo::JobInfo(const JobInfo& other) = default;
JobInfo::~JobInfo() = default;
JobCheckinInfo::JobCheckinInfo() = default;
JobCheckinInfo::JobCheckinInfo(const JobCheckinInfo& other) = default;
JobCheckinInfo::~JobCheckinInfo() = default;
JobOptions::JobOptions() = default;
JobOptions::JobOptions(const JobOptions& other) = default;
JobOptions::~JobOptions() = default;
bool GetJobInfo(const std::string& label, JobInfo* info) {
int error = 0;
ScopedLaunchData resp = DoServiceOp(LAUNCH_KEY_GETJOB, label, &error);
if (error)
return false;
std::string program;
if (!StringFromLaunchDataDictEntry(resp.get(), LAUNCH_JOBKEY_PROGRAM,
&program))
return false;
info->program = program;
int pid;
if (IntFromLaunchDataDictEntry(resp.get(), LAUNCH_JOBKEY_PID, &pid))
info->pid = pid;
return true;
}
bool CheckIn(const std::string& socket_key, JobCheckinInfo* info) {
ScopedLaunchData resp =
SendLaunchMessage(LaunchDataFromString(LAUNCH_KEY_CHECKIN));
if (launch_data_get_type(resp.get()) != LAUNCH_DATA_DICTIONARY)
return false;
std::string program;
if (!StringFromLaunchDataDictEntry(resp.get(), LAUNCH_JOBKEY_PROGRAM,
&program))
return false;
launch_data_t sockets = launch_data_dict_lookup(resp, LAUNCH_JOBKEY_SOCKETS);
if (launch_data_get_type(sockets) != LAUNCH_DATA_DICTIONARY)
return false;
int socket_fd;
if (!FirstIntFromLaunchDataDictEntry(sockets, socket_key.c_str(), &socket_fd))
return false;
info->program = program;
info->socket = socket_fd;
return true;
}
bool SubmitJob(const JobOptions& options) {
base::scoped_nsobject<NSDictionary> options_dict =
DictionaryForJobOptions(options);
return SMJobSubmit(kSMDomainUserLaunchd,
base::mac::NSToCFCast(options_dict.get()), nullptr,
nullptr);
}
bool RemoveJob(const std::string& label) {
int error = 0;
ScopedLaunchData resp = DoServiceOp(LAUNCH_KEY_REMOVEJOB, label, &error);
if (!error)
error = ErrnoFromLaunchData(resp.get());
// On macOS 10.10+, removing a running job yields EINPROGRESS but the
// operation completes eventually (but not necessarily by the time RemoveJob
// is done). See rdar://18398683 for details.
if (error == EINPROGRESS)
error = 0;
return !error;
}
} // namespace services
} // namespace mac
#pragma clang diagnostic pop