| /* |
| * cancd - CA NetConsole Daemon |
| * |
| * Simply collect netconsole messages. |
| * |
| * Author: Joel Becker <joel.becker@oracle.com> |
| * Copyright (C) 2005 Oracle. 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 021110-1307, USA. |
| */ |
| |
| #include <arpa/inet.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <getopt.h> |
| #include <libgen.h> |
| #include <limits.h> |
| #include <netinet/in.h> |
| #include <signal.h> |
| #include <stdarg.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <syslog.h> |
| #include <sys/socket.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <time.h> |
| #include <unistd.h> |
| |
| #include "kernel-list.h" |
| |
| #ifndef VERSION |
| #define VERSION "0.1.0" |
| #endif |
| |
| #define PROGNAME "cancd" |
| |
| /* |
| * Log prefix. This is the path all logs are placed under. Modified |
| * with the '-l' option. |
| */ |
| static char *log_prefix = "/var/crash"; |
| |
| /* |
| * Output file format. The format is a strftime(3) string with %Q (the |
| * only unused letter in strftime(3)) representing the IP address of |
| * the machine sending the netconsole message. |
| * The default of "%Q/%Y-%m-%d-%H:%M/log" Means that a machine |
| * 10.0.0.1 sending a message at 3:30pm on 2005/10/1 would output |
| * to 10.0.0.1/2005-10-01-15:30/log. The files are underneath |
| * the log_prefix. Modified with the '-o' option. |
| */ |
| static char *log_format = "%Q/%Y-%m-%d-%H:%M/log"; |
| |
| /* |
| * Port number to listen on. Modified with the '-p' option. |
| */ |
| static uint16_t log_port = 6667; |
| |
| /* Socket we are using */ |
| static int sock_fd; |
| |
| /* Are we a daemon? */ |
| static int daemonize = 1; |
| |
| /* What signal did we catch? */ |
| sig_atomic_t caught_sig = 0; |
| |
| void handler(int signum) |
| { |
| caught_sig = signum; |
| } |
| |
| static int setup_signals() |
| { |
| int rc = 0; |
| struct sigaction act; |
| |
| act.sa_sigaction = NULL; |
| act.sa_restorer = NULL; |
| sigemptyset(&act.sa_mask); |
| act.sa_handler = handler; |
| #ifdef SA_INTERRUPT |
| act.sa_flags = SA_INTERRUPT; |
| #endif |
| |
| rc += sigaction(SIGTERM, &act, NULL); |
| rc += sigaction(SIGINT, &act, NULL); |
| act.sa_handler = SIG_IGN; |
| rc += sigaction(SIGPIPE, &act, NULL); /* Get EPIPE instead */ |
| |
| return rc; |
| } |
| |
| static char *get_path(int af, const void *src) |
| { |
| char ntop[INET6_ADDRSTRLEN + 1]; |
| char format[PATH_MAX]; |
| size_t ntop_len; |
| char *newstr, *ptr, *fptr; |
| int havep; |
| time_t now; |
| |
| if (!inet_ntop(af, src, ntop, sizeof(ntop))) { |
| syslog(LOG_ERR, "Unable to resolve address: %s", strerror(errno)); |
| return NULL; |
| } |
| |
| ntop_len = strlen(ntop); |
| |
| for (havep = 0, ptr = log_format, fptr = format; *ptr; ptr++) { |
| switch (*ptr) { |
| case '%': |
| if (havep) { |
| /* '%%' should be passed on to strftime(3) */ |
| *fptr = '%'; |
| fptr++; |
| *fptr = '%'; |
| fptr++; |
| |
| havep = 0; |
| } else |
| havep = 1; |
| break; |
| case 'Q': |
| if (havep) { |
| /* Copy our address in */ |
| memmove(fptr, ntop, ntop_len); |
| fptr += ntop_len; |
| havep = 0; |
| break; |
| } |
| /* Fall Through */ |
| default: |
| if (havep) { |
| *fptr = '%'; |
| fptr++; |
| havep = 0; |
| } |
| *fptr = *ptr; |
| fptr++; |
| break; |
| } |
| } |
| |
| newstr = malloc(sizeof(char) * PATH_MAX); |
| if (!newstr) { |
| syslog(LOG_ERR, "Unable to allocate memory while formating log filename"); |
| return NULL; |
| } |
| |
| now = time(NULL); |
| if (!strftime(newstr, PATH_MAX, format, localtime(&now))) { |
| syslog(LOG_ERR, "Unable to format filename: %s", strerror(errno)); |
| free(newstr); |
| newstr = NULL; |
| } |
| |
| return newstr; |
| } |
| |
| struct dir_to_make { |
| struct list_head list; |
| char *path; |
| }; |
| |
| static int make_tree(const char *path, int mode) |
| { |
| struct stat stat_buf; |
| char *ptr, *tmp; |
| int rc; |
| LIST_HEAD(to_make); |
| struct list_head *p, *n; |
| struct dir_to_make *m; |
| |
| if (!path) { |
| syslog(LOG_ERR, "Can\'t make empty path!"); |
| return -EINVAL; |
| } |
| |
| ptr = strdup(path); |
| if (!ptr) { |
| syslog(LOG_ERR, "Unable to allocate memory for path \"%s\"", path); |
| return -ENOMEM; |
| } |
| |
| while (*ptr && strcmp(ptr, ".") && strcmp(ptr, "/")) { |
| rc = stat(ptr, &stat_buf); |
| if (rc) { |
| if (errno != ENOENT) { |
| rc = -errno; |
| syslog(LOG_ERR, "Unable to stat \"%s\" while creating \"%s\": %s", ptr, path, strerror(-rc)); |
| goto out_error; |
| } |
| } else { |
| if (!S_ISDIR(stat_buf.st_mode)) { |
| rc = -ENOTDIR; |
| syslog(LOG_ERR, "Path \"%s\" is not a directory while creating \"%s\"\n", ptr, path); |
| goto out_error; |
| } |
| |
| /* Found an existing parent, we're done */ |
| break; |
| } |
| |
| /* If we got here, there's a path component we need to make */ |
| rc = -ENOMEM; |
| m = malloc(sizeof(struct dir_to_make)); |
| if (!m) { |
| syslog(LOG_ERR, "Unable to allocate memory while creating path \"%s\"", path); |
| goto out_error; |
| } |
| |
| list_add(&m->list, &to_make); |
| m->path = ptr; |
| |
| /* Create a temporary copy for dirname(3) */ |
| tmp = strdup(ptr); |
| if (!tmp) { |
| syslog(LOG_ERR, "Unable to allocate memory while creating path \"%s\"", path); |
| goto out_error; |
| } |
| |
| ptr = dirname(tmp); |
| ptr = strdup(ptr); /* Because dirname could return static */ |
| free(tmp); |
| if (!ptr) { |
| syslog(LOG_ERR, "Unable to allocate memory while creating path \"%s\"", path); |
| goto out_error; |
| } |
| } |
| |
| list_for_each(p, &to_make) { |
| m = list_entry(p, struct dir_to_make, list); |
| rc = mkdir(m->path, mode); |
| if (rc) { |
| rc = -errno; |
| syslog(LOG_ERR, "Unable to create directory \"%s\" while creating path \"%s\": %s", m->path, path, strerror(-rc)); |
| goto out_error; |
| } |
| } |
| |
| rc = 0; |
| |
| out_error: |
| free(ptr); |
| |
| list_for_each_safe(p, n, &to_make) { |
| m = list_entry(p, struct dir_to_make, list); |
| list_del(&m->list); |
| if (m->path) |
| free(m->path); |
| free(m); |
| } |
| |
| return rc; |
| } |
| |
| static int open_socket() |
| { |
| int rc; |
| struct sockaddr_in servaddr = { 0, }; |
| |
| sock_fd = socket(PF_INET, SOCK_DGRAM, 0); |
| if (sock_fd < 0) { |
| rc = -errno; |
| syslog(LOG_ERR, "Unable to open socket: %s", strerror(-rc)); |
| return rc; |
| } |
| |
| servaddr.sin_family = PF_INET; |
| servaddr.sin_addr.s_addr = htonl(INADDR_ANY); |
| servaddr.sin_port = htons(log_port); |
| |
| rc = bind(sock_fd, (struct sockaddr *)&servaddr, sizeof(struct sockaddr_in)); |
| if (rc) { |
| rc = -errno; |
| syslog(LOG_ERR, "Unable to bind socket: %s", strerror(-rc)); |
| close(sock_fd); |
| sock_fd = -1; |
| return rc; |
| } |
| |
| return 0; |
| } |
| |
| /* Only return nonzero if fatal */ |
| static int do_output(const char *buf, int len, struct sockaddr_in *addr, socklen_t socklen) |
| { |
| int fd, rc, tot; |
| char *name, *tmp, *dir; |
| |
| name = get_path(PF_INET, &addr->sin_addr); |
| if (!name) |
| return 0; |
| |
| tmp = strdup(name); |
| if (!tmp) { |
| syslog(LOG_ERR, "Unable to allocate memory while logging to \"%s\"", name); |
| return 0; |
| } |
| |
| dir = dirname(tmp); |
| rc = make_tree(dir, 0700); |
| free(tmp); |
| |
| if (rc) |
| return 0; |
| |
| fd = open(name, O_WRONLY | O_APPEND | O_CREAT, 0600); |
| if (fd < 0) |
| syslog(LOG_ERR, "Unable to open \"%s\": %s", name, strerror(errno)); |
| else { |
| tot = 0; |
| while (tot < len) { |
| rc = write(fd, buf + tot, len - tot); |
| if (rc < 0) { |
| if (errno == EINTR) |
| continue; |
| syslog(LOG_ERR, "Error writing to \"%s\": %s", name, strerror(errno)); |
| break; |
| } |
| tot += rc; |
| } |
| close(fd); |
| } |
| free(name); |
| |
| return 0; |
| } |
| |
| static int set_blocking(int blocking) |
| { |
| int flags, rc; |
| |
| flags = fcntl(sock_fd, F_GETFL, 0); |
| if (flags < 0) { |
| rc = -errno; |
| syslog(LOG_ERR, "Cannot get blocking state: %s", strerror(-rc)); |
| return rc; |
| } |
| |
| if (blocking) |
| flags &= ~O_NONBLOCK; |
| else |
| flags |= O_NONBLOCK; |
| |
| rc = fcntl(sock_fd, F_SETFL, flags); |
| if (rc) { |
| rc = -errno; |
| syslog(LOG_ERR, "Cannot set blocking state: %s", strerror(-rc)); |
| } |
| |
| return rc; |
| } |
| |
| /* |
| * There is some magic state here. To allow us to batch things |
| * if we want, we go O_NONBLOCK once data is available. Once we |
| * see that data isn't available anymore, we go back to blocking. |
| */ |
| static int do_recvfrom(int fd, void *buf, size_t bufsize, int flags, struct sockaddr_in *from, socklen_t * fromlen) |
| { |
| int rc; |
| static int block = 1; |
| |
| rc = recvfrom(sock_fd, buf, bufsize, 0, (struct sockaddr *)from, fromlen); |
| if (rc < 0) { |
| rc = -errno; |
| if (rc == -EAGAIN) { |
| if (!block) { |
| rc = set_blocking(1); |
| if (!rc) { |
| block = 1; |
| rc = -EAGAIN; |
| } else |
| syslog(LOG_ERR, "Never blocking means eating CPU, goodbye"); |
| } |
| } else if (rc != -EINTR) |
| syslog(LOG_ERR, "Error reading from socket: %s", strerror(-rc)); |
| } else { |
| if (block) { |
| if (!set_blocking(0)) |
| block = 0; |
| else |
| syslog(LOG_ERR, "Unable to set nonblocking"); |
| } |
| } |
| |
| return rc; |
| } |
| |
| static int run() |
| { |
| int rc; |
| char *buf; |
| size_t bufsize = 8 * getpagesize(); |
| struct sockaddr_in from; |
| socklen_t fromlen; |
| |
| buf = malloc(sizeof(char) * bufsize); |
| if (!buf) { |
| syslog(LOG_ERR, "Unable to allocate memory for receive buffer: %s", strerror(errno)); |
| return -ENOMEM; |
| } |
| |
| syslog(LOG_INFO, "Logging to %s on port %d", log_prefix, log_port); |
| while (1) { |
| rc = do_recvfrom(sock_fd, buf, bufsize, 0, &from, &fromlen); |
| if (rc < 0) { |
| if (rc == -EAGAIN) |
| continue; |
| if (rc == -EINTR) |
| syslog(LOG_INFO, "Caught signal %d", caught_sig); |
| else |
| syslog(LOG_ERR, "Error reading from socket: %s", strerror(-rc)); |
| break; |
| } |
| /* For now, we process one at a time */ |
| rc = do_output(buf, rc, &from, fromlen); |
| if (rc) /* do_output() better not return error if nonfatal */ |
| break; |
| } |
| syslog(LOG_INFO, "Shutting down"); |
| |
| return 0; |
| } |
| |
| static int init_dir() |
| { |
| int rc; |
| rc = make_tree(log_prefix, 0755); |
| if (!rc) { |
| rc = chdir(log_prefix); |
| if (rc) { |
| rc = -errno; |
| syslog(LOG_ERR, "Unable to change to \"%s\"; %s", log_prefix, strerror(-rc)); |
| } |
| } |
| |
| return rc; |
| } |
| |
| static int init_self() |
| { |
| int rc; |
| |
| openlog(PROGNAME, LOG_PERROR | LOG_PID, LOG_DAEMON); |
| |
| rc = init_dir(); |
| if (rc) |
| return rc; |
| |
| if (daemonize) { |
| rc = daemon(1, 0); |
| if (rc) { |
| rc = -errno; |
| syslog(LOG_ERR, "daemon() failed: %s", strerror(-rc)); |
| } |
| } |
| |
| if (!rc) { |
| rc = setup_signals(); |
| if (rc) |
| syslog(LOG_ERR, "Unable to set up signal handling"); |
| } |
| |
| return rc; |
| } |
| |
| static int valid_format() |
| { |
| struct in_addr addr = { 0, }; |
| char *name; |
| int rc; |
| |
| name = get_path(PF_INET, &addr); |
| rc = ! !name; |
| if (name) |
| free(name); |
| |
| return rc; |
| } |
| |
| static void print_version(void) |
| { |
| fprintf(stdout, PROGNAME " version %s\n", VERSION); |
| exit(0); |
| } |
| |
| static void print_usage(int rc) |
| { |
| FILE *output = rc ? stderr : stdout; |
| |
| fprintf(output, "Usage: " PROGNAME " [-l <log_prefix>] [-o <log_name_format>] [-p <port>]\n" " " PROGNAME " -h\n" " " PROGNAME " -V\n"); |
| |
| exit(rc); |
| } |
| |
| extern char *optarg; |
| extern int optopt; |
| extern int opterr; |
| static int parse_options(int argc, char *argv[]) |
| { |
| int c; |
| |
| opterr = 0; |
| while ((c = getopt(argc, argv, ":hVDl:o:p:-:")) != EOF) { |
| switch (c) { |
| case 'h': |
| print_usage(0); |
| break; |
| case 'V': |
| print_version(); |
| break; |
| case '-': |
| if (!strcmp(optarg, "version")) |
| print_version(); |
| else if (!strcmp(optarg, "help")) |
| print_usage(0); |
| else { |
| fprintf(stderr, PROGNAME ": Invalid option: \'--%s\'\n", optarg); |
| print_usage(-EINVAL); |
| } |
| break; |
| case 'l': |
| if (!optarg || !*optarg) { |
| fprintf(stderr, PROGNAME ": Invalid log prefix \"%s\"\n", optarg); |
| print_usage(-EINVAL); |
| } |
| log_prefix = optarg; |
| break; |
| case 'o': |
| log_format = optarg; |
| if (!log_format || !*log_format || !valid_format()) { |
| fprintf(stderr, PROGNAME ": Invalid log filename format \"%s\"\n", optarg); |
| print_usage(-EINVAL); |
| } |
| break; |
| case 'p': |
| log_port = (uint16_t) (atoi(optarg) & (uint16_t) - 1); |
| if (!log_port) { |
| fprintf(stderr, PROGNAME ": Invalid port: \"%s\"\n", optarg); |
| print_usage(-EINVAL); |
| } |
| break; |
| case 'D': |
| daemonize = 0; |
| break; |
| case '?': |
| fprintf(stderr, PROGNAME ": Invalid option: \'-%c\'\n", optopt); |
| print_usage(-EINVAL); |
| break; |
| case ':': |
| fprintf(stderr, PROGNAME ": Option \'-%c\' requires an argument\n", optopt); |
| print_usage(-EINVAL); |
| break; |
| default: |
| fprintf(stderr, PROGNAME ": Shouldn\'t get here\n"); |
| exit(1); |
| break; |
| } |
| } |
| |
| return 0; |
| } |
| |
| int main(int argc, char *argv[]) |
| { |
| int rc; |
| |
| rc = parse_options(argc, argv); |
| if (rc) |
| return rc; |
| |
| rc = init_self(); |
| if (rc) |
| goto out; |
| |
| rc = open_socket(); |
| if (rc) |
| goto out; |
| |
| rc = run(); |
| |
| close(sock_fd); |
| |
| out: |
| closelog(); |
| |
| return rc; |
| } |