/*
Run scripts in a directory with specific event arguments
Copyright (C) Amitay Isaacs 2017
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, see .
*/
#include "replace.h"
#include "system/filesys.h"
#include "system/dir.h"
#include "system/locale.h"
#include "system/wait.h"
#include
#include
#include "lib/util/tevent_unix.h"
#include "lib/util/debug.h"
#include "common/logging.h"
#include "common/run_proc.h"
#include "common/run_event.h"
/*
* Utility functions
*/
static int script_filter(const struct dirent *de)
{
size_t namelen = strlen(de->d_name);
char *ptr;
/* Ignore . and .. */
if (namelen < 3) {
return 0;
}
/* Skip filenames with ~ */
ptr = strchr(de->d_name, '~');
if (ptr != NULL) {
return 0;
}
/* Filename should start with [0-9][0-9]. */
if ((! isdigit(de->d_name[0])) ||
(! isdigit(de->d_name[1])) ||
(de->d_name[2] != '.')) {
return 0;
}
/* Ignore filenames with multiple '.'s */
ptr = index(&de->d_name[3], '.');
if (ptr != NULL) {
return 0;
}
return 1;
}
static int get_script_list(TALLOC_CTX *mem_ctx,
const char *script_dir,
struct run_event_script_list **out)
{
struct dirent **namelist = NULL;
struct run_event_script_list *script_list;
int count, ret;
int i;
count = scandir(script_dir, &namelist, script_filter, alphasort);
if (count == -1) {
ret = errno;
if (ret == ENOENT) {
D_WARNING("event script dir %s removed\n", script_dir);
} else {
D_WARNING("scandir() failed on %s, ret=%d\n",
script_dir, ret);
}
*out = NULL;
ret = 0;
goto done;
}
if (count == 0) {
*out = NULL;
ret = 0;
goto done;
}
script_list = talloc_zero(mem_ctx, struct run_event_script_list);
if (script_list == NULL) {
return ENOMEM;
}
script_list->num_scripts = count;
script_list->script = talloc_zero_array(script_list,
struct run_event_script,
count);
if (script_list->script == NULL) {
ret = ENOMEM;
talloc_free(script_list);
goto done;
}
for (i=0; iscript[i];
s->name = talloc_strdup(script_list, namelist[i]->d_name);
if (s->name == NULL) {
ret = ENOMEM;
talloc_free(script_list);
goto done;
}
}
*out = script_list;
ret = 0;
done:
if (namelist != NULL && count != -1) {
for (i=0; id_name, script_name) == 0) {
/* check for valid script names */
ret = script_filter(de);
if (ret == 0) {
closedir(dirp);
return EINVAL;
}
found = true;
break;
}
}
closedir(dirp);
if (! found) {
return ENOENT;
}
filename = talloc_asprintf(mem_ctx, "%s/%s", script_dir, script_name);
if (filename == NULL) {
return ENOMEM;
}
fd = open(filename, O_RDWR);
if (fd == -1) {
ret = errno;
goto done;
}
ret = fstat(fd, &st);
if (ret != 0) {
ret = errno;
goto done;
}
if (enable) {
new_mode = st.st_mode | (S_IXUSR | S_IXGRP | S_IXOTH);
} else {
new_mode = st.st_mode & ~(S_IXUSR | S_IXGRP | S_IXOTH);
}
ret = fchmod(fd, new_mode);
if (ret != 0) {
ret = errno;
goto done;
}
done:
if (fd != -1) {
close(fd);
}
talloc_free(filename);
return ret;
}
static int script_args(TALLOC_CTX *mem_ctx, const char *event_str,
const char *arg_str, const char ***out)
{
const char **argv;
int argc;
size_t len;
/* Preallocate argv array to avoid reallocation. */
len = 8;
argv = talloc_array(mem_ctx, const char *, len);
if (argv == NULL) {
return ENOMEM;
}
argv[0] = NULL; /* script name */
argv[1] = event_str;
argc = 2;
if (arg_str != NULL) {
char *str, *t, *tok;
str = talloc_strdup(argv, arg_str);
if (str == NULL) {
return ENOMEM;
}
t = str;
while ((tok = strtok(t, " ")) != NULL) {
argv[argc] = talloc_strdup(argv, tok);
if (argv[argc] == NULL) {
talloc_free(argv);
return ENOMEM;
}
argc += 1;
if (argc >= len) {
argv = talloc_realloc(mem_ctx, argv,
const char *, len + 8);
if (argv == NULL) {
return ENOMEM;
}
len += 8;
}
t = NULL;
}
talloc_free(str);
}
argv[argc] = NULL;
argc += 1;
*out = argv;
return 0;
}
struct run_event_context {
struct run_proc_context *run_proc_ctx;
const char *script_dir;
const char *debug_prog;
bool debug_running;
struct tevent_queue *queue;
struct tevent_req *current_req;
bool monitor_running;
};
int run_event_init(TALLOC_CTX *mem_ctx, struct tevent_context *ev,
const char *script_dir, const char *debug_prog,
struct run_event_context **out)
{
struct run_event_context *run_ctx;
struct stat st;
int ret;
run_ctx = talloc_zero(mem_ctx, struct run_event_context);
if (run_ctx == NULL) {
return ENOMEM;
}
ret = run_proc_init(run_ctx, ev, &run_ctx->run_proc_ctx);
if (ret != 0) {
talloc_free(run_ctx);
return ret;
}
ret = stat(script_dir, &st);
if (ret != 0) {
ret = errno;
talloc_free(run_ctx);
return ret;
}
if (! S_ISDIR(st.st_mode)) {
talloc_free(run_ctx);
return EINVAL;
}
run_ctx->script_dir = talloc_strdup(run_ctx, script_dir);
if (run_ctx->script_dir == NULL) {
talloc_free(run_ctx);
return ENOMEM;
}
if (debug_prog != NULL) {
run_ctx->debug_prog = talloc_strdup(run_ctx, debug_prog);
if (run_ctx->debug_prog == NULL) {
talloc_free(run_ctx);
return ENOMEM;
}
}
run_ctx->debug_running = false;
run_ctx->queue = tevent_queue_create(run_ctx, "run event queue");
if (run_ctx->queue == NULL) {
talloc_free(run_ctx);
return ENOMEM;
}
run_ctx->monitor_running = false;
*out = run_ctx;
return 0;
}
static struct run_proc_context *
run_event_run_proc_context(struct run_event_context *run_ctx)
{
return run_ctx->run_proc_ctx;
}
static const char *run_event_script_dir(struct run_event_context *run_ctx)
{
return run_ctx->script_dir;
}
static const char *run_event_debug_prog(struct run_event_context *run_ctx)
{
return run_ctx->debug_prog;
}
static struct tevent_queue *run_event_queue(struct run_event_context *run_ctx)
{
return run_ctx->queue;
}
static void run_event_start_running(struct run_event_context *run_ctx,
struct tevent_req *req, bool is_monitor)
{
run_ctx->current_req = req;
run_ctx->monitor_running = is_monitor;
}
static void run_event_stop_running(struct run_event_context *run_ctx)
{
run_ctx->current_req = NULL;
run_ctx->monitor_running = false;
}
static struct tevent_req *run_event_get_running(
struct run_event_context *run_ctx,
bool *is_monitor)
{
*is_monitor = run_ctx->monitor_running;
return run_ctx->current_req;
}
static int run_event_script_status(struct run_event_script *script)
{
int ret;
if (script->result.sig > 0) {
ret = -EINTR;
} else if (script->result.err > 0) {
if (script->result.err == EACCES) {
/* Map EACCESS to ENOEXEC */
ret = -ENOEXEC;
} else {
ret = -script->result.err;
}
} else {
ret = script->result.status;
}
return ret;
}
int run_event_script_list(struct run_event_context *run_ctx,
TALLOC_CTX *mem_ctx,
struct run_event_script_list **output)
{
struct run_event_script_list *script_list;
int ret, i;
ret = get_script_list(mem_ctx, run_event_script_dir(run_ctx),
&script_list);
if (ret != 0) {
return ret;
}
if (script_list == NULL) {
*output = NULL;
return 0;
}
for (i=0; inum_scripts; i++) {
struct run_event_script *script = &script_list->script[i];
struct stat st;
char *path = NULL;
path = talloc_asprintf(mem_ctx, "%s/%s",
run_event_script_dir(run_ctx),
script->name);
if (path == NULL) {
continue;
}
ret = stat(path, &st);
if (ret != 0) {
TALLOC_FREE(path);
continue;
}
if (! (st.st_mode & S_IXUSR)) {
script->summary = -ENOEXEC;
}
TALLOC_FREE(path);
}
*output = script_list;
return 0;
}
int run_event_script_enable(struct run_event_context *run_ctx,
const char *script_name)
{
return script_chmod(run_ctx, run_event_script_dir(run_ctx),
script_name, true);
}
int run_event_script_disable(struct run_event_context *run_ctx,
const char *script_name)
{
return script_chmod(run_ctx, run_event_script_dir(run_ctx),
script_name, false);
}
/*
* Run debug program to diagnose hung scripts
*/
static int debug_args(TALLOC_CTX *mem_ctx, const char *path,
const char *event_str, pid_t pid, const char ***out)
{
const char **argv;
argv = talloc_array(mem_ctx, const char *, 4);
if (argv == NULL) {
return ENOMEM;
}
argv[0] = path;
argv[1] = talloc_asprintf(argv, "%d", pid);
argv[2] = event_str;
if (argv[1] == NULL) {
talloc_free(argv);
return ENOMEM;
}
argv[3] = NULL;
*out = argv;
return 0;
}
static void debug_log(int loglevel, const char *output, const char *log_prefix)
{
char *line, *s;
s = strdup(output);
if (s == NULL) {
DEBUG(loglevel, ("%s: %s\n", log_prefix, output));
return;
}
line = strtok(s, "\n");
while (line != NULL) {
DEBUG(loglevel, ("%s: %s\n", log_prefix, line));
line = strtok(NULL, "\n");
}
free(s);
}
struct run_debug_state {
struct run_event_context *run_ctx;
pid_t pid;
};
static void run_debug_done(struct tevent_req *subreq);
static struct tevent_req *run_debug_send(TALLOC_CTX *mem_ctx,
struct tevent_context *ev,
struct run_event_context *run_ctx,
const char *event_str, pid_t pid)
{
struct tevent_req *req, *subreq;
struct run_debug_state *state;
const char **argv;
const char *debug_prog;
int ret;
req = tevent_req_create(mem_ctx, &state, struct run_debug_state);
if (req == NULL) {
return NULL;
}
state->run_ctx = run_ctx;
state->pid = pid;
debug_prog = run_event_debug_prog(run_ctx);
if (debug_prog == NULL) {
tevent_req_done(req);
return tevent_req_post(req, ev);
}
if (run_ctx->debug_running) {
tevent_req_done(req);
return tevent_req_post(req, ev);
}
if (pid == -1) {
D_DEBUG("Event script terminated, nothing to debug\n");
tevent_req_done(req);
return tevent_req_post(req, ev);
}
ret = debug_args(state, debug_prog, event_str, pid, &argv);
if (ret != 0) {
D_ERR("debug_args() failed\n");
tevent_req_error(req, ret);
return tevent_req_post(req, ev);
}
D_DEBUG("Running debug %s with args \"%s %s\"\n",
debug_prog, argv[1], argv[2]);
subreq = run_proc_send(state, ev, run_event_run_proc_context(run_ctx),
debug_prog, argv, -1, tevent_timeval_zero());
if (tevent_req_nomem(subreq, req)) {
return tevent_req_post(req, ev);
}
tevent_req_set_callback(subreq, run_debug_done, req);
run_ctx->debug_running = true;
talloc_free(argv);
return req;
}
static void run_debug_done(struct tevent_req *subreq)
{
struct tevent_req *req = tevent_req_callback_data(
subreq, struct tevent_req);
struct run_debug_state *state = tevent_req_data(
req, struct run_debug_state);
char *output;
int ret;
bool status;
state->run_ctx->debug_running = false;
status = run_proc_recv(subreq, &ret, NULL, NULL, state, &output);
TALLOC_FREE(subreq);
if (! status) {
D_ERR("Running debug failed, ret=%d\n", ret);
}
/* Log output */
if (output != NULL) {
debug_log(DEBUG_ERR, output, "event_debug");
talloc_free(output);
}
kill(-state->pid, SIGTERM);
tevent_req_done(req);
}
static bool run_debug_recv(struct tevent_req *req, int *perr)
{
int ret;
if (tevent_req_is_unix_error(req, &ret)) {
if (perr != NULL) {
*perr = ret;
}
return false;
}
return true;
}
/*
* Run a single event
*/
struct run_event_state {
struct tevent_context *ev;
struct run_event_context *run_ctx;
const char *event_str;
const char *arg_str;
struct timeval timeout;
struct run_event_script_list *script_list;
const char **argv;
struct tevent_req *script_subreq;
int index;
bool cancelled;
};
static void run_event_cancel(struct tevent_req *req);
static void run_event_trigger(struct tevent_req *req, void *private_data);
static struct tevent_req *run_event_run_script(struct tevent_req *req);
static void run_event_next_script(struct tevent_req *subreq);
static void run_event_debug(struct tevent_req *req, pid_t pid);
static void run_event_debug_done(struct tevent_req *subreq);
struct tevent_req *run_event_send(TALLOC_CTX *mem_ctx,
struct tevent_context *ev,
struct run_event_context *run_ctx,
const char *event_str,
const char *arg_str,
struct timeval timeout)
{
struct tevent_req *req, *current_req;
struct run_event_state *state;
bool monitor_running, status;
req = tevent_req_create(mem_ctx, &state, struct run_event_state);
if (req == NULL) {
return NULL;
}
state->ev = ev;
state->run_ctx = run_ctx;
state->event_str = talloc_strdup(state, event_str);
if (tevent_req_nomem(state->event_str, req)) {
return tevent_req_post(req, ev);
}
if (arg_str != NULL) {
state->arg_str = talloc_strdup(state, arg_str);
if (tevent_req_nomem(state->arg_str, req)) {
return tevent_req_post(req, ev);
}
}
state->timeout = timeout;
state->cancelled = false;
/*
* If monitor event is running,
* cancel the running monitor event and run new event
*
* If any other event is running,
* if new event is monitor, cancel that event
* else add new event to the queue
*/
current_req = run_event_get_running(run_ctx, &monitor_running);
if (current_req != NULL) {
if (monitor_running) {
run_event_cancel(current_req);
} else if (strcmp(event_str, "monitor") == 0) {
state->script_list = talloc_zero(
state, struct run_event_script_list);
if (tevent_req_nomem(state->script_list, req)) {
return tevent_req_post(req, ev);
}
state->script_list->summary = -ECANCELED;
tevent_req_done(req);
return tevent_req_post(req, ev);
}
}
status = tevent_queue_add(run_event_queue(run_ctx), ev, req,
run_event_trigger, NULL);
if (! status) {
tevent_req_error(req, ENOMEM);
return tevent_req_post(req, ev);
}
return req;
}
static void run_event_cancel(struct tevent_req *req)
{
struct run_event_state *state = tevent_req_data(
req, struct run_event_state);
run_event_stop_running(state->run_ctx);
state->script_list->summary = -ECANCELED;
state->cancelled = true;
TALLOC_FREE(state->script_subreq);
tevent_req_done(req);
}
static void run_event_trigger(struct tevent_req *req, void *private_data)
{
struct tevent_req *subreq;
struct run_event_state *state = tevent_req_data(
req, struct run_event_state);
int ret;
bool is_monitor = false;
D_DEBUG("Running event %s with args \"%s\"\n", state->event_str,
state->arg_str == NULL ? "(null)" : state->arg_str);
ret = get_script_list(state, run_event_script_dir(state->run_ctx),
&state->script_list);
if (ret != 0) {
D_ERR("get_script_list() failed, ret=%d\n", ret);
tevent_req_error(req, ret);
return;
}
/* No scripts */
if (state->script_list == NULL ||
state->script_list->num_scripts == 0) {
tevent_req_done(req);
return;
}
ret = script_args(state, state->event_str, state->arg_str,
&state->argv);
if (ret != 0) {
D_ERR("script_args() failed, ret=%d\n", ret);
tevent_req_error(req, ret);
return;
}
state->index = 0;
subreq = run_event_run_script(req);
if (tevent_req_nomem(subreq, req)) {
return;
}
tevent_req_set_callback(subreq, run_event_next_script, req);
state->script_subreq = subreq;
if (strcmp(state->event_str, "monitor") == 0) {
is_monitor = true;
}
run_event_start_running(state->run_ctx, req, is_monitor);
}
static struct tevent_req *run_event_run_script(struct tevent_req *req)
{
struct run_event_state *state = tevent_req_data(
req, struct run_event_state);
struct run_event_script *script;
struct tevent_req *subreq;
char *path;
script = &state->script_list->script[state->index];
path = talloc_asprintf(state, "%s/%s",
run_event_script_dir(state->run_ctx),
script->name);
if (path == NULL) {
return NULL;
}
state->argv[0] = script->name;
script->begin = tevent_timeval_current();
D_DEBUG("Running %s with args \"%s %s\"\n",
path, state->argv[0], state->argv[1]);
subreq = run_proc_send(state, state->ev,
run_event_run_proc_context(state->run_ctx),
path, state->argv, -1, state->timeout);
talloc_free(path);
return subreq;
}
static void run_event_next_script(struct tevent_req *subreq)
{
struct tevent_req *req = tevent_req_callback_data(
subreq, struct tevent_req);
struct run_event_state *state = tevent_req_data(
req, struct run_event_state);
struct run_event_script *script;
pid_t pid;
int ret;
bool status;
script = &state->script_list->script[state->index];
script->end = tevent_timeval_current();
status = run_proc_recv(subreq, &ret, &script->result, &pid,
state->script_list, &script->output);
TALLOC_FREE(subreq);
state->script_subreq = NULL;
if (! status) {
D_ERR("run_proc failed for %s, ret=%d\n", script->name, ret);
tevent_req_error(req, ret);
return;
}
if (state->cancelled) {
return;
}
/* Log output */
if (script->output != NULL) {
debug_log(DEBUG_ERR, script->output, script->name);
}
D_DEBUG("Script %s finished sig=%d, err=%d, status=%d\n",
script->name, script->result.sig, script->result.err,
script->result.status);
/* If a script fails, stop running */
script->summary = run_event_script_status(script);
if (script->summary != 0 && script->summary != -ENOEXEC) {
state->script_list->num_scripts = state->index + 1;
if (script->summary == -ETIME && pid != -1) {
run_event_debug(req, pid);
}
state->script_list->summary = script->summary;
D_NOTICE("%s event %s\n", state->event_str,
(script->summary == -ETIME) ? "timed out" : "failed");
run_event_stop_running(state->run_ctx);
tevent_req_done(req);
return;
}
state->index += 1;
/* All scripts executed */
if (state->index >= state->script_list->num_scripts) {
run_event_stop_running(state->run_ctx);
tevent_req_done(req);
return;
}
subreq = run_event_run_script(req);
if (tevent_req_nomem(subreq, req)) {
return;
}
tevent_req_set_callback(subreq, run_event_next_script, req);
state->script_subreq = subreq;
}
static void run_event_debug(struct tevent_req *req, pid_t pid)
{
struct run_event_state *state = tevent_req_data(
req, struct run_event_state);
struct tevent_req *subreq;
/* Debug script is run with ectx as the memory context */
subreq = run_debug_send(state->run_ctx, state->ev, state->run_ctx,
state->event_str, pid);
if (subreq == NULL) {
/* If run debug fails, it's not an error */
D_NOTICE("Failed to run event debug\n");
return;
}
tevent_req_set_callback(subreq, run_event_debug_done, NULL);
}
static void run_event_debug_done(struct tevent_req *subreq)
{
int ret = 0;
bool status;
status = run_debug_recv(subreq, &ret);
TALLOC_FREE(subreq);
if (! status) {
D_NOTICE("run_debug() failed, ret=%d\n", ret);
}
}
bool run_event_recv(struct tevent_req *req, int *perr,
TALLOC_CTX *mem_ctx,
struct run_event_script_list **script_list)
{
struct run_event_state *state = tevent_req_data(
req, struct run_event_state);
int ret;
if (tevent_req_is_unix_error(req, &ret)) {
if (perr != NULL) {
*perr = ret;
}
return false;
}
if (script_list != NULL) {
*script_list = talloc_steal(mem_ctx, state->script_list);
}
return true;
}