#include "macros.hpp"
#include "common.hpp"
#include <launch.h>
#include <spawn.h>
#include <errno.h>
#include <sys/fcntl.h>
#define LAUNCHD_MSG_ERR(job, key) \
VERBOSE("launch_msg(%s, %s): %s\n", job, #key, strerror(errno));
#define LAUNCHD_MSG_ERRMSG(job, key, fmt, ...) \
VERBOSE("launch_msg(%s, %s): " fmt, job, #key, ##__VA_ARGS__)
bool launchd_checkin(unsigned * idle_timeout_secs)
{
launch_data_t msg;
launch_data_t resp;
launch_data_t item;
bool is_launchd = true;
msg = launch_data_new_string(LAUNCH_KEY_CHECKIN);
resp = launch_msg(msg);
if (resp == NULL) {
LAUNCHD_MSG_ERR("checkin", LAUNCH_KEY_CHECKIN);
launch_data_free(msg);
return false;
}
if (launch_data_get_type(resp) == LAUNCH_DATA_ERRNO) {
errno = launch_data_get_errno(resp);
goto done;
}
is_launchd = true;
if ((item = launch_data_dict_lookup(resp, LAUNCH_JOBKEY_TIMEOUT))) {
long long val = launch_data_get_integer(item);
*idle_timeout_secs =
(val < 0) ? 0 : ((val > UINT_MAX) ? UINT_MAX : val);
}
done:
launch_data_free(msg);
launch_data_free(resp);
return is_launchd;
}
typedef void (*dict_iterate_callback)(const launch_data_t,
const char *, void *);
static void fill_job_info(launch_data_t data, const char *key,
LaunchJobStatus * info)
{
switch (launch_data_get_type(data)) {
case LAUNCH_DATA_STRING:
if (strcmp(key, LAUNCH_JOBKEY_LABEL) == 0) {
info->m_label = std::string(launch_data_get_string(data));
} else if (strcmp(key, LAUNCH_JOBKEY_PROGRAM) == 0) {
info->m_program = std::string(launch_data_get_string(data));
}
return;
case LAUNCH_DATA_INTEGER:
if (strcmp(key, LAUNCH_JOBKEY_PID) == 0) {
info->m_pid = (pid_t)launch_data_get_integer(data);
}
return;
case LAUNCH_DATA_DICTIONARY:
launch_data_dict_iterate(data,
(dict_iterate_callback)fill_job_info, info);
return;
default:
return;
}
}
bool
launchd_job_status(const char * job, LaunchJobStatus& info)
{
launch_data_t resp;
launch_data_t msg;
msg = launch_data_alloc(LAUNCH_DATA_DICTIONARY);
if (msg == NULL) {
throw std::runtime_error("out of memory");
}
launch_data_dict_insert(msg, launch_data_new_string(job),
LAUNCH_KEY_GETJOB);
resp = launch_msg(msg);
launch_data_free(msg);
if (resp == NULL) {
LAUNCHD_MSG_ERR(job, LAUNCH_KEY_GETJOB);
return false;
}
switch (launch_data_get_type(resp)) {
case LAUNCH_DATA_DICTIONARY:
fill_job_info(resp, NULL, &info);
launch_data_free(resp);
return true;
case LAUNCH_DATA_ERRNO:
errno = launch_data_get_errno(resp);
launch_data_free(resp);
if (errno != 0) {
LAUNCHD_MSG_ERR(job, LAUNCH_KEY_GETJOB);
return false;
}
return true;
default:
LAUNCHD_MSG_ERRMSG(job, LAUNCH_KEY_GETJOB,
"unexpected respose type %d",
launch_data_get_type(resp));
launch_data_free(resp);
return false;
}
}
bool
launchd_stop_job(const char * job)
{
launch_data_t resp;
launch_data_t msg;
msg = launch_data_alloc(LAUNCH_DATA_DICTIONARY);
if (msg == NULL) {
throw std::runtime_error("out of memory");
}
launch_data_dict_insert(msg, launch_data_new_string(job),
LAUNCH_KEY_STOPJOB);
resp = launch_msg(msg);
launch_data_free(msg);
if (resp == NULL) {
LAUNCHD_MSG_ERR(job, LAUNCH_KEY_STOPJOB);
return false;
}
switch (launch_data_get_type(resp)) {
case LAUNCH_DATA_ERRNO:
errno = launch_data_get_errno(resp);
launch_data_free(resp);
if (errno != 0) {
LAUNCHD_MSG_ERR(job, LAUNCH_KEY_STOPJOB);
return false;
}
return true;
default:
LAUNCHD_MSG_ERRMSG(job, LAUNCH_KEY_STOPJOB,
"unexpected respose type %d",
launch_data_get_type(resp));
launch_data_free(resp);
return false;
}
}
static bool run_helper(const char ** argv)
{
posix_spawn_file_actions_t action;
int status;
int err;
pid_t child;
err = posix_spawn_file_actions_init(&action);
if (err != 0) {
VERBOSE("spawn init failed for %s: %s\n", *argv, strerror(err));
return false;
}
err = posix_spawn_file_actions_addopen(&action, STDIN_FILENO,
"/dev/null", O_RDONLY, 0 );
if (err != 0) {
VERBOSE("stdin redirect failed for %s: %s\n", *argv, strerror(err));
}
if (!(Options::Verbose || Options::Debug)) {
err = posix_spawn_file_actions_addopen(&action, STDOUT_FILENO,
"/dev/null", O_WRONLY, 0 );
if (err != 0) {
VERBOSE("stdout redirect failed for %s: %s\n",
*argv, strerror(err));
}
err = posix_spawn_file_actions_addopen(&action, STDERR_FILENO,
"/dev/null", O_WRONLY, 0 );
if (err != 0) {
VERBOSE("stderr redirect failed for %s: %s\n",
*argv, strerror(err));
}
}
err = posix_spawn(&child, *argv, &action, NULL ,
(char * const *)argv, NULL );
if (err != 0) {
VERBOSE("failed to spawn %s: %s\n", *argv, strerror(err));
posix_spawn_file_actions_destroy(&action);
return false;
}
posix_spawn_file_actions_destroy(&action);
while (waitpid(child, &status, 0) != child) {
}
if (WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS) {
return true;
}
return false;
}
bool launchd_unload_job(const LaunchService& svc)
{
const char * unload_cmd[] =
{
"/bin/launchctl",
"unload",
"-w",
NULL,
NULL
};
unload_cmd[3] = svc.plist().c_str();
return run_helper(unload_cmd);
}
bool launchd_load_job(const LaunchService& svc)
{
const char * unload_cmd[] =
{
"/bin/launchctl",
"load",
"-w",
NULL,
NULL
};
unload_cmd[3] = svc.plist().c_str();
return run_helper(unload_cmd);
}