blob: f6da959bff08c424c01c785ae5a5af7f7af4e6e2 [file] [log] [blame] [raw]
/*
* Copyright (C) 2010-2011, Parallels, Inc. All rights reserved.
*
* 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 2 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, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <sys/types.h>
#include <sys/socket.h>
#include <linux/types.h>
#include <linux/netlink.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <limits.h>
#include <wait.h>
#include "types.h"
#include "logger.h"
#include "config.h"
#include "vzerror.h"
#include "script.h"
#define NETLINK_UEVENT 31 /* from kernel/ve/vzevent.c */
static void child_handler(int signo)
{
int pid, status;
while ((pid = waitpid(-1, &status, WNOHANG)) > 0)
{
if (WIFEXITED(status))
if (WEXITSTATUS(status) != 0)
logger(-1, 0, "Child %d failed "
"with exit code %d",
pid, WEXITSTATUS(status));
else
logger(1, 0, "Child %d exited with success",
pid);
else if (WIFSIGNALED(status))
logger(-1, 0, "Child %d killed by signal %d",
pid, WTERMSIG(status));
}
}
static int run_event_script(envid_t ctid, const char *event)
{
char script[sizeof(LIB_SCRIPTS_DIR)*2];
int pid;
snprintf(script, sizeof(script), "%svzevent-%s",
LIB_SCRIPTS_DIR, event);
logger(1, 0, "Running %s event script", event);
pid = fork();
switch (pid) {
case -1:
logger(-1, errno, "Failed to fork()");
return 1;
case 0:
exit(run_pre_script(ctid, script));
default:
logger(1, 0, "Forked child %d for %s event",
pid, event);
}
return 0;
}
static int parse_event(char *buf)
{
char *ctidp, *endp, *name;
long int t;
int len;
envid_t ctid;
const int min_event_len = 7; /* ve-stop */
/* Parse CTID */
if ((ctidp = strchr(buf, '@')) == NULL) {
logger(-1, 0, "Bad message: can't find CTID");
return -1;
}
/* Bail out of definitely bad/unknown events */
len = ctidp - buf;
if (len < min_event_len)
goto ev_unknown;
if (! (buf[0] == 'v') && (buf[1] == 'e') && (buf[2] == '-') )
goto ev_unknown;
name = buf + 3; /* Omit common "ve-" prefix */
*ctidp++ = '\0';
t = strtol(ctidp, &endp, 10);
if (*endp != '\0') {
logger(-1, 0, "Garbage in CTID in message: %s (endp=%s)",
ctidp, endp);
return -1;
}
if ((t <= 0) || (t > INT_MAX)) {
logger(-1, 0, "Bad CTID in message: %s", ctidp);
return -1;
}
ctid = (envid_t)t;
set_log_ctid(ctid);
logger(2, 0, "CTID = %d, event = %s (len=%d)", ctid, buf, len);
/* Continue parsing event name */
switch (len - 3) {
case 4:
if (strncmp(name, "stop", 4) == 0)
goto ev_stop;
goto ev_unknown;
case 5:
if (strncmp(name, "start", 5) == 0)
goto ev_start;
else if (strncmp(name, "mount", 5) == 0)
goto ev_mount;
goto ev_unknown;
case 6:
if (strncmp(name, "reboot", 6) == 0)
goto ev_reboot;
else if (strncmp(name, "umount", 6) == 0)
goto ev_umount;
goto ev_unknown;
default:
goto ev_unknown;
}
ev_unknown:
logger(-1, 0, "Unknown event: %s", buf);
return -1;
ev_mount:
ev_umount:
ev_start:
logger(2, 0, "Got %s event (ignored)", name);
/* Do nothing */
return 0;
ev_reboot:
return run_event_script(ctid, "reboot");
ev_stop:
return run_event_script(ctid, "stop");
}
static int read_events(int fd, struct sockaddr_nl *sa)
{
int len;
char buf[32];
struct iovec iov = { buf, sizeof(buf) };
struct msghdr msg;
int ret;
logger(0, 0, "Started");
while (1) {
memset(&msg, 0, sizeof(msg));
msg.msg_name = (void *)&sa;
msg.msg_namelen = sizeof(sa);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
len = recvmsg(fd, &msg, 0);
if (len > 0) {
buf[len] = '\0';
parse_event(buf);
set_log_ctid(0);
} else if (len < 0) {
if (errno != EINTR) {
logger(-1, errno, "Error in recvmsg() "
" (ret=%d, errno=%d)",
len, errno);
ret = 1;
break;
}
} else /* len==0 */ {
logger(0, 0, "Connection closed");
ret = 0;
break;
}
}
close(fd);
logger(0, 0, "Exiting...");
return ret;
}
static int daemon_read_events(int fd, struct sockaddr_nl *sa)
{
if (daemon(0, 0) < 0) {
logger(-1, errno, "Error in daemon()");
return 1;
}
/* Now make logger stop printing to stdout/stderr */
set_log_quiet(1);
return read_events(fd, sa);
}
static int prepare_read_events(int daemonize)
{
int fd;
struct sockaddr_nl sa;
struct sigaction act;
fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_UEVENT);
if (fd < 0) {
logger(-1, errno, "Error in socket()");
return 1;
}
memset(&sa, 0, sizeof(sa));
sa.nl_family = AF_NETLINK;
sa.nl_groups = 1;
if (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
if (errno == ENOENT)
logger(-1, 0, "Looks like vzevent kernel module "
"is not loaded; exiting.");
else
logger(-1, errno, "Error in bind()");
close(fd);
return 1;
}
sigemptyset(&act.sa_mask);
act.sa_handler = child_handler;
act.sa_flags = SA_NOCLDSTOP;
sigaction(SIGCHLD, &act, NULL);
if (daemonize != 0)
return daemon_read_events(fd, &sa);
else
return read_events(fd, &sa);
}
static void usage()
{
printf(
"Usage: vzeventd [options]\n"
" -v increase verbosity (can be used multiple times)\n"
" -d debug (do not daemonize, run in foreground)\n"
" -h print this help message\n"
);
}
int main(int argc, char **argv)
{
struct vps_param *param = NULL;
int daemonize = 1;
int opt, verbose = 0;
while ((opt = getopt(argc, argv, "dv")) != -1) {
switch (opt) {
case 'd':
daemonize = 0;
verbose++;
break;
case 'v':
verbose++;
break;
case 'h':
usage();
return 0;
default:
usage();
return 1;
}
}
param = init_vps_param();
/* Read global config file */
if (vps_parse_config(0, GLOBAL_CFG, param, NULL)) {
fprintf(stderr, "Global configuration file %s not found\n",
GLOBAL_CFG);
return VZ_NOCONFIG;
}
init_log(param->log.log_file, 0, param->log.enable != NO,
param->log.level + verbose,
0, "vzeventd");
return prepare_read_events(daemonize);
}