From f708f887cf30e94288670b2bfe6d7dc561fa4eea Mon Sep 17 00:00:00 2001 From: John Crispin Date: Fri, 8 Mar 2013 23:46:56 +0100 Subject: [PATCH] add syslog and klog support --- log.c | 139 +++++++++++++++++++++++++++++++ syslog.c | 283 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ syslog.h | 41 +++++++++ 3 files changed, 463 insertions(+) create mode 100644 log.c create mode 100644 syslog.c create mode 100644 syslog.h diff --git a/log.c b/log.c new file mode 100644 index 0000000..d2a7158 --- /dev/null +++ b/log.c @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2013 Felix Fietkau + * Copyright (C) 2013 John Crispin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1 + * as published by the Free Software Foundation + * + * 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. + */ + +#include +#include + +#include "procd.h" +#include "syslog.h" + +static struct ubus_subscriber log_event; + +static int notify; +struct ubus_context *_ctx; +static struct blob_buf b; + +static const struct blobmsg_policy read_policy = + { .name = "lines", .type = BLOBMSG_TYPE_INT32 }; + +static const struct blobmsg_policy write_policy = + { .name = "event", .type = BLOBMSG_TYPE_STRING }; + +static int read_log(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + struct blob_attr *tb; + struct log_head *l; + void *lines, *entry; + int count = 0; + + if (msg) { + blobmsg_parse(&read_policy, 1, &tb, blob_data(msg), blob_len(msg)); + if (tb) + count = blobmsg_get_u32(tb); + } + + blob_buf_init(&b, 0); + lines = blobmsg_open_array(&b, "lines"); + + l = log_list(count, NULL); + + while (l) { + entry = blobmsg_open_table(&b, NULL); + blobmsg_add_string(&b, "msg", l->data); + blobmsg_add_u32(&b, "id", l->id); + blobmsg_add_u32(&b, "priority", l->priority); + blobmsg_add_u32(&b, "source", l->source); + blobmsg_add_u64(&b, "time", (l->ts.tv_sec * 1000) + (l->ts.tv_nsec / 1000000)); + blobmsg_close_table(&b, entry); + l = log_list(count, l); + } + blobmsg_close_table(&b, lines); + ubus_send_reply(ctx, req, b.head); + + return 0; +} + +static int write_log(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + struct blob_attr *tb; + char *event; + + if (msg) { + blobmsg_parse(&write_policy, 1, &tb, blob_data(msg), blob_len(msg)); + if (tb) { + event = blobmsg_get_string(tb); + log_add(event, strlen(event) + 1, SOURCE_SYSLOG); + } + } + + return 0; +} + +static void log_subscribe_cb(struct ubus_context *ctx, struct ubus_object *obj) +{ + notify = obj->has_subscribers; +} + +static const struct ubus_method log_methods[] = { + { .name = "read", .handler = read_log, .policy = &read_policy, .n_policy = 1 }, + { .name = "write", .handler = write_log, .policy = &write_policy, .n_policy = 1 }, +}; + +static struct ubus_object_type log_object_type = + UBUS_OBJECT_TYPE("log", log_methods); + +static struct ubus_object log_object = { + .name = "log", + .type = &log_object_type, + .methods = log_methods, + .n_methods = ARRAY_SIZE(log_methods), + .subscribe_cb = log_subscribe_cb, +}; + +void ubus_notify_log(struct log_head *l) +{ + int ret; + + if (!notify) + return; + + blob_buf_init(&b, 0); + blobmsg_add_u32(&b, "id", l->id); + blobmsg_add_u32(&b, "priority", l->priority); + blobmsg_add_u32(&b, "source", l->source); + blobmsg_add_u64(&b, "time", (l->ts.tv_sec * 1000) + (l->ts.tv_nsec / 1000000)); + + ret = ubus_notify(_ctx, &log_object, l->data, b.head, -1); + if (ret) + ERROR("Failed to notify log: %s\n", ubus_strerror(ret)); +} + +void ubus_init_log(struct ubus_context *ctx) +{ + int ret; + + _ctx = ctx; + + ret = ubus_add_object(ctx, &log_object); + if (ret) + ERROR("Failed to add object: %s\n", ubus_strerror(ret)); + + ret = ubus_register_subscriber(ctx, &log_event); + if (ret) + ERROR("Failed to add watch handler: %s\n", ubus_strerror(ret)); +} diff --git a/syslog.c b/syslog.c new file mode 100644 index 0000000..a558e50 --- /dev/null +++ b/syslog.c @@ -0,0 +1,283 @@ +/* + * Copyright (C) 2013 Felix Fietkau + * Copyright (C) 2013 John Crispin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1 + * as published by the Free Software Foundation + * + * 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. + */ + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "procd.h" +#include "syslog.h" + +#define LOG_DEFAULT_SIZE (16 * 1024) +#define LOG_DEFAULT_SOCKET "/dev/log" +#define LOG_LINE_LEN 256 +#define SYSLOG_PADDING 16 + +#define KLOG_DEFAULT_PROC "/proc/kmsg" + +#define PAD(x) (x % 4) ? (((x) - (x % 4)) + 4) : (x) + +static char *log_dev = LOG_DEFAULT_SOCKET; +static int log_size = LOG_DEFAULT_SIZE; +static struct log_head *log, *log_end, *oldest, *newest; +static int current_id = 0; +regex_t pat_prio; +regex_t pat_tstamp; + +static struct log_head *log_next(struct log_head *h, int size) +{ + struct log_head *n = (struct log_head *) &h->data[PAD(sizeof(struct log_head) + size)]; + + return (n >= log_end) ? (log) : (n); +} + +void log_add(char *buf, int size, int source) +{ + regmatch_t matches[4]; + struct log_head *next; + int priority = 0; + int ret; + + /* strip trailing newline */ + if (buf[size - 2] == '\n') { + buf[size - 2] = '\0'; + size -= 1; + } + + /* strip the priority */ + ret = regexec(&pat_prio, buf, 3, matches, 0); + if (!ret) { + priority = atoi(&buf[matches[1].rm_so]); + size -= matches[2].rm_so; + buf += matches[2].rm_so; + } + +#if 0 + /* strip kernel timestamp */ + ret = regexec(&pat_tstamp,buf, 4, matches, 0); + if ((source == SOURCE_KLOG) && !ret) { + size -= matches[3].rm_so; + buf += matches[3].rm_so; + } +#endif + + /* strip syslog timestamp */ + if ((source == SOURCE_SYSLOG) && (size > SYSLOG_PADDING) && (buf[SYSLOG_PADDING - 1] == ' ')) { + size -= SYSLOG_PADDING; + buf += SYSLOG_PADDING; + } + + DEBUG(2, "-> %d - %s\n", priority, buf); + + /* find new oldest entry */ + next = log_next(newest, size); + if (next > newest) { + while ((oldest > newest) && (oldest <= next) && (oldest != log)) + oldest = log_next(oldest, oldest->size); + } else { + DEBUG(2, "Log wrap\n"); + newest->size = 0; + next = log_next(log, size); + for (oldest = log; oldest <= next; oldest = log_next(oldest, oldest->size)) + ; + newest = log; + } + + /* add the log message */ + newest->size = size; + newest->id = current_id++; + newest->priority = priority; + newest->source = source; + clock_gettime(CLOCK_REALTIME, &newest->ts); + strcpy(newest->data, buf); + + ubus_notify_log(newest); + + newest = next; +} + +static void slog_cb(struct ustream *s, int bytes) +{ + struct ustream_buf *buf = s->r.head; + char *str; + int len; + + do { + str = ustream_get_read_buf(s, NULL); + if (!str) + break; + len = strlen(buf->data); + if (!len) { + bytes -= 1; + ustream_consume(s, 1); + continue; + } + log_add(buf->data, len + 1, SOURCE_SYSLOG); + ustream_consume(s, len); + bytes -= len; + } while (bytes > 0); +} + +static void klog_cb(struct ustream *s, int bytes) +{ + struct ustream_buf *buf = s->r.head; + char *newline, *str; + int len; + + do { + str = ustream_get_read_buf(s, NULL); + if (!str) + break; + newline = strchr(buf->data, '\n'); + if (!newline) + break; + *newline = 0; + len = newline + 1 - str; + log_add(buf->data, len, SOURCE_KLOG); + ustream_consume(s, len); + } while (1); +} + +struct ustream_fd slog = { + .stream.string_data = true, + .stream.notify_read = slog_cb, +}; + +struct ustream_fd klog = { + .stream.string_data = true, + .stream.notify_read = klog_cb, +}; + +static int klog_open(void) +{ + int fd; + + DEBUG(1, "Opening %s\n", KLOG_DEFAULT_PROC); + fd = open(KLOG_DEFAULT_PROC, O_RDONLY | O_NONBLOCK); + if (fd < 0) { + ERROR("Failed to open %s\n", KLOG_DEFAULT_PROC); + return -1; + } + fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC); + ustream_fd_init(&klog, fd); + return 0; +} + +static int syslog_open(void) +{ + int fd; + + DEBUG(1, "Opening %s\n", log_dev); + unlink(log_dev); + fd = usock(USOCK_UNIX | USOCK_UDP | USOCK_SERVER | USOCK_NONBLOCK, log_dev, NULL); + if (fd < 0) { + ERROR("Failed to open %s\n", log_dev); + return -1; + } + chmod(log_dev, 0666); + ustream_fd_init(&slog, fd); + return 0; +} + +struct log_head* log_list(int count, struct log_head *h) +{ + unsigned int min = count; + + if (count) + min = (count < current_id) ? (current_id - count) : (0); + if (!h && oldest->id >= min) + return oldest; + if (!h) + h = oldest; + + while (h != newest) { + h = log_next(h, h->size); + if (!h->size && (h > newest)) + h = log; + if (h->id >= min && (h != newest)) + return h; + } + + return NULL; +} + +int log_buffer_init(int size) +{ + struct log_head *_log = malloc(size); + + if (!_log) { + ERROR("Failed to initialize log buffer with size %d\n", log_size); + return -1; + } + + memset(_log, 0, size); + + if (log && ((log_size + sizeof(struct log_head)) < size)) { + struct log_head *start = _log; + struct log_head *end = ((void*) _log) + size; + struct log_head *l; + + l = log_list(0, NULL); + while ((start < end) && l && l->size) { + memcpy(start, l, PAD(sizeof(struct log_head) + l->size)); + start = (struct log_head *) &l->data[PAD(l->size)]; + l = log_list(0, l); + } + free(log); + newest = start; + newest->size = 0; + oldest = log = _log; + log_end = ((void*) log) + size; + } else { + oldest = newest = log = _log; + log_end = ((void*) log) + size; + } + log_size = size; + + return 0; +} + +int log_buffer_size(void) +{ + return log_size; +} + +void log_init(void) +{ + regcomp(&pat_prio, "^<([0-9]*)>(.*)", REG_EXTENDED); + regcomp(&pat_tstamp, "^\[[ 0]*([0-9]*).([0-9]*)] (.*)", REG_EXTENDED); + + if (log_buffer_init(log_size)) { + ERROR("Failed to allocate log memory\n"); + exit(-1); + } + + syslog_open(); + klog_open(); + openlog("procd", LOG_PID, LOG_DAEMON); +} diff --git a/syslog.h b/syslog.h new file mode 100644 index 0000000..5e7ab43 --- /dev/null +++ b/syslog.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2013 Felix Fietkau + * Copyright (C) 2013 John Crispin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1 + * as published by the Free Software Foundation + * + * 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. + */ + +#ifndef __SYSLOG_H +#define __SYSLOG_H + +enum { + SOURCE_KLOG = 0, + SOURCE_SYSLOG = 1, + SOURCE_ANY = 0xff, +}; + +struct log_head { + unsigned int size; + unsigned int id; + int priority; + int source; + struct timespec ts; + char data[]; +}; + +void log_init(void); + +typedef void (*log_list_cb)(struct log_head *h); +struct log_head* log_list(int count, struct log_head *h); +int log_buffer_init(int size); +int log_buffer_size(void); +void log_add(char *buf, int size, int source); + +#endif -- 2.11.0