|  | /* | 
|  | *  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 | 
|  | */ | 
|  | #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 "vzconfig.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(SCRIPTDIR)*2]; | 
|  | int pid; | 
|  |  | 
|  | snprintf(script, sizeof(script), "%s/vzevent-%s", | 
|  | SCRIPTDIR, 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, "dvh")) != -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); | 
|  | } |