blob: cffc2ee3b077832661342ca8f752eb4461bf235e [file] [log] [blame]
/*
* 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;
}