blob: 4041950eca51f17a6ae87df02312fda12ba3f488 [file] [log] [blame]
/* Copyright (C) 2009 Intel Corporation
Author: Andi Kleen
Manage trigger commands running as separate processes.
mcelog 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; version
2.
mcelog 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 find a copy of v2 of the GNU General Public License somewhere
on your Linux system; if not, write to the Free Software Foundation,
Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
#define _GNU_SOURCE 1
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>
#include <sys/wait.h>
#include "trigger.h"
#include "eventloop.h"
#include "list.h"
#include "mcelog.h"
#include "memutil.h"
#include "config.h"
struct child {
struct list_head nd;
pid_t child;
const char *name;
};
static LIST_HEAD(childlist);
static int num_children;
static int children_max = 4;
static char *trigger_dir;
pid_t mcelog_fork(const char *name)
{
pid_t child;
struct child *c;
child = fork();
if (child <= 0)
return child;
num_children++;
c = xalloc(sizeof(struct child));
c->name = name;
c->child = child;
list_add_tail(&c->nd, &childlist);
return child;
}
// note: trigger must be allocated, e.g. from config
void run_trigger(char *trigger, char *argv[], char **env)
{
pid_t child;
char *fallback_argv[] = {
trigger,
NULL,
};
if (!argv)
argv = fallback_argv;
Lprintf("Running trigger `%s'\n", trigger);
if (children_max > 0 && num_children >= children_max) {
Eprintf("Too many trigger children running already\n");
return;
}
child = mcelog_fork(trigger);
if (child < 0) {
SYSERRprintf("Cannot create process for trigger");
return;
}
if (child == 0) {
if (trigger_dir && chdir(trigger_dir) == -1)
SYSERRprintf("Cannot chdir(%s) for trigger", trigger_dir);
else
execve(trigger, argv, env);
_exit(127);
}
}
/* Clean up child on SIGCHLD */
static void finish_child(pid_t child, int status)
{
struct child *c, *tmpc;
list_for_each_entry_safe (c, tmpc, &childlist, nd) {
if (c->child == child) {
if (WIFEXITED(status) && WEXITSTATUS(status)) {
Eprintf("Trigger `%s' exited with status %d\n",
c->name, WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
Eprintf("Trigger `%s' died with signal %s\n",
c->name, strsignal(WTERMSIG(status)));
}
list_del(&c->nd);
free(c);
num_children--;
return;
}
}
abort();
}
/* Runs only directly after ppoll */
static void child_handler(int sig, siginfo_t *si, void *ctx)
{
int status;
pid_t pid;
if (waitpid(si->si_pid, &status, WNOHANG) < 0) {
SYSERRprintf("Cannot collect child %d", si->si_pid);
return;
}
finish_child(si->si_pid, status);
/* Check other child(ren)'s status to avoid zombie process */
while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
finish_child(pid, status);
}
}
void trigger_setup(void)
{
char *s;
struct sigaction sa = {
.sa_sigaction = child_handler,
.sa_flags = SA_SIGINFO|SA_NOCLDSTOP|SA_RESTART,
};
sigaction(SIGCHLD, &sa, NULL);
event_signal(SIGCHLD);
config_number("trigger", "children-max", "%d", &children_max);
s = config_string("trigger", "directory");
if (s) {
if (access(s, R_OK|X_OK) < 0)
SYSERRprintf("Cannot access trigger directory `%s'", s);
trigger_dir = s;
}
}
void trigger_wait(void)
{
int status;
int pid;
while ((pid = waitpid((pid_t)-1, &status, 0)) > 0)
finish_child(pid, status);
}
int trigger_check(char *s)
{
char *name;
int rc;
if (trigger_dir)
xasprintf(&name, "%s/%s", trigger_dir, s);
else
name = s;
rc = access(name, R_OK|X_OK);
if (trigger_dir)
free(name);
return rc;
}