add log daemon
authorJohn Crispin <blogic@openwrt.org>
Thu, 14 Nov 2013 12:47:18 +0000 (13:47 +0100)
committerJohn Crispin <blogic@openwrt.org>
Fri, 15 Nov 2013 16:10:20 +0000 (17:10 +0100)
Signed-off-by: John Crispin <blogic@openwrt.org>
CMakeLists.txt
log/logd.c [new file with mode: 0644]
log/logread.c [new file with mode: 0644]
log/syslog.c [new file with mode: 0644]
log/syslog.h [new file with mode: 0644]

index 8cfbbc8b5af5df18655c501f35e50d7e43d499ca..1aa9a869e3e3c4a6f34b60ef826218cdc7a1f467 100644 (file)
@@ -70,3 +70,15 @@ TARGET_LINK_LIBRARIES(validate_data ${LIBS} validate)
 INSTALL(TARGETS validate_data
        RUNTIME DESTINATION sbin
 )
+
+ADD_EXECUTABLE(logd log/logd.c log/syslog.c)
+TARGET_LINK_LIBRARIES(logd ${LIBS})
+INSTALL(TARGETS logd
+       RUNTIME DESTINATION sbin
+)
+
+ADD_EXECUTABLE(logread log/logread.c)
+TARGET_LINK_LIBRARIES(logread ${LIBS})
+INSTALL(TARGETS logread
+       RUNTIME DESTINATION sbin
+)
diff --git a/log/logd.c b/log/logd.c
new file mode 100644 (file)
index 0000000..978d7d1
--- /dev/null
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2013 John Crispin <blogic@openwrt.org>
+ *
+ * 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 <stdio.h>
+#include <syslog.h>
+
+#include <linux/types.h>
+
+#include <libubox/uloop.h>
+#include <libubox/blobmsg.h>
+#include <libubus.h>
+
+#include "syslog.h"
+
+int debug = 0;
+static int notify;
+static struct blob_buf b;
+static struct ubus_context *_ctx;
+static struct uloop_timeout ubus_timer;
+
+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);
+               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", (((__u64) l->ts.tv_sec) * 1000) + (l->ts.tv_nsec / 1000000));
+
+       ret = ubus_notify(_ctx, &log_object, l->data, b.head, -1);
+       if (ret)
+               fprintf(stderr, "Failed to notify log: %s\n", ubus_strerror(ret));
+}
+
+static void
+ubus_reconnect_cb(struct uloop_timeout *timeout)
+{
+       if (!ubus_reconnect(_ctx, NULL))
+               ubus_add_uloop(_ctx);
+       else
+               uloop_timeout_set(timeout, 1000);
+}
+
+static void
+ubus_disconnect_cb(struct ubus_context *ctx)
+{
+       ubus_timer.cb = ubus_reconnect_cb;
+       uloop_timeout_set(&ubus_timer, 1000);
+}
+
+static void
+ubus_connect_cb(struct uloop_timeout *timeout)
+{
+       int ret;
+
+       _ctx = ubus_connect(NULL);
+       if (!_ctx) {
+               uloop_timeout_set(timeout, 1000);
+               fprintf(stderr, "failed to connect to ubus\n");
+               return;
+       }
+       _ctx->connection_lost = ubus_disconnect_cb;
+       ret = ubus_add_object(_ctx, &log_object);
+       if (ret)
+               fprintf(stderr, "Failed to add object: %s\n", ubus_strerror(ret));
+       fprintf(stderr, "log: connected to ubus\n");
+       ubus_add_uloop(_ctx);
+}
+
+int
+main(int argc, char **argv)
+{
+       signal(SIGPIPE, SIG_IGN);
+
+       uloop_init();
+       ubus_timer.cb = ubus_connect_cb;
+       uloop_timeout_set(&ubus_timer, 1000);
+       log_init();
+       uloop_run();
+       if (_ctx)
+               ubus_free(_ctx);
+       log_shutdown();
+       uloop_done();
+
+       return 0;
+}
diff --git a/log/logread.c b/log/logread.c
new file mode 100644 (file)
index 0000000..e8749f8
--- /dev/null
@@ -0,0 +1,369 @@
+/*
+ * Copyright (C) 2013 Felix Fietkau <nbd@openwrt.org>
+ * Copyright (C) 2013 John Crispin <blogic@openwrt.org>
+ *
+ * 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 <sys/types.h>
+#include <sys/stat.h>
+
+#include <fcntl.h>
+#include <time.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#define SYSLOG_NAMES
+#include <syslog.h>
+
+#include <libubox/blobmsg_json.h>
+#include <libubox/usock.h>
+#include <libubox/uloop.h>
+#include "libubus.h"
+#include "syslog.h"
+
+enum {
+       LOG_STDOUT,
+       LOG_FILE,
+       LOG_NET,
+};
+
+enum {
+       LOG_MSG,
+       LOG_ID,
+       LOG_PRIO,
+       LOG_SOURCE,
+       LOG_TIME,
+       __LOG_MAX
+};
+
+static const struct blobmsg_policy log_policy[] = {
+       [LOG_MSG] = { .name = "msg", .type = BLOBMSG_TYPE_STRING },
+       [LOG_ID] = { .name = "id", .type = BLOBMSG_TYPE_INT32 },
+       [LOG_PRIO] = { .name = "priority", .type = BLOBMSG_TYPE_INT32 },
+       [LOG_SOURCE] = { .name = "source", .type = BLOBMSG_TYPE_INT32 },
+       [LOG_TIME] = { .name = "time", .type = BLOBMSG_TYPE_INT64 },
+};
+
+static struct ubus_subscriber log_event;
+static struct uloop_timeout retry;
+static struct uloop_fd sender;
+static const char *log_file, *log_ip, *log_port, *log_prefix, *pid_file, *hostname;
+static int log_type = LOG_STDOUT;
+static int log_size, log_udp;
+
+static const char* getcodetext(int value, CODE *codetable) {
+       CODE *i;
+
+       if (value >= 0)
+               for (i = codetable; i->c_val != -1; i++)
+                       if (i->c_val == value)
+                               return (i->c_name);
+       return "<unknown>";
+};
+
+static void log_handle_reconnect(struct uloop_timeout *timeout)
+{
+       sender.fd = usock((log_udp) ? (USOCK_UDP) : (USOCK_TCP), log_ip, log_port);
+       if (sender.fd < 0) {
+               fprintf(stderr, "failed to connect: %s\n", strerror(errno));
+               uloop_timeout_set(&retry, 1000);
+       } else {
+               uloop_fd_add(&sender, ULOOP_READ);
+               syslog(0, "Logread connected to %s:%s\n", log_ip, log_port);
+       }
+}
+
+static void log_handle_remove(struct ubus_context *ctx, struct ubus_subscriber *s,
+                       uint32_t id)
+{
+       fprintf(stderr, "Object %08x went away\n", id);
+}
+
+static void log_handle_fd(struct uloop_fd *u, unsigned int events)
+{
+       if (u->eof) {
+               uloop_fd_delete(u);
+               close(sender.fd);
+               sender.fd = -1;
+               uloop_timeout_set(&retry, 1000);
+       }
+}
+
+static int log_notify(struct ubus_context *ctx, struct ubus_object *obj,
+                       struct ubus_request_data *req, const char *method,
+                       struct blob_attr *msg)
+{
+       struct blob_attr *tb[__LOG_MAX];
+       struct stat s;
+       char buf[512];
+       uint32_t p;
+       char *str;
+       time_t t;
+       char *c;
+
+       if (sender.fd < 0)
+               return 0;
+
+       blobmsg_parse(log_policy, ARRAY_SIZE(log_policy), tb, blob_data(msg), blob_len(msg));
+       if (!tb[LOG_ID] || !tb[LOG_PRIO] || !tb[LOG_SOURCE] || !tb[LOG_TIME])
+               return 1;
+
+       if ((log_type == LOG_FILE) && log_size && (!stat(log_file, &s)) && (s.st_size > log_size)) {
+               char *old = malloc(strlen(log_file) + 5);
+
+               close(sender.fd);
+               if (old) {
+                       sprintf(old, "%s.old", log_file);
+                       rename(log_file, old);
+                       free(old);
+               }
+               sender.fd = open(log_file, O_CREAT | O_WRONLY | O_APPEND, 0600);
+               if (sender.fd < 0) {
+//                     fprintf(stderr, "failed to open %s: %s\n", log_file, strerror(errno));
+                       exit(-1);
+               }
+       }
+
+       t = blobmsg_get_u64(tb[LOG_TIME]) / 1000;
+       c = ctime(&t);
+       p = blobmsg_get_u32(tb[LOG_PRIO]);
+       c[strlen(c) - 1] = '\0';
+       str = blobmsg_format_json(msg, true);
+       if (log_type == LOG_NET) {
+               int err;
+
+               *buf = '\0';
+               if (hostname)
+                       snprintf(buf, sizeof(buf), "%s ", hostname);
+               if (log_prefix) {
+                       strncat(buf, log_prefix, sizeof(buf));
+                       strncat(buf, ": ", sizeof(buf));
+               }
+               if (blobmsg_get_u32(tb[LOG_SOURCE]) == SOURCE_KLOG)
+                       strncat(buf, "kernel: ", sizeof(buf));
+               strncat(buf, method, sizeof(buf));
+               if (log_udp)
+                       err = write(sender.fd, buf, strlen(buf));
+               else
+                       err = send(sender.fd, buf, strlen(buf), 0);
+
+               if (err < 0) {
+                       syslog(0, "failed to send log data to %s:%s via %s\n",
+                               log_ip, log_port, (log_udp) ? ("udp") : ("tcp"));
+                       uloop_fd_delete(&sender);
+                       close(sender.fd);
+                       sender.fd = -1;
+                       uloop_timeout_set(&retry, 1000);
+               }
+       } else {
+               snprintf(buf, sizeof(buf), "%s %s.%s%s %s\n",
+                       c, getcodetext(LOG_FAC(p) << 3, facilitynames), getcodetext(LOG_PRI(p), prioritynames),
+                       (blobmsg_get_u32(tb[LOG_SOURCE])) ? ("") : (" kernel:"),
+                       method);
+               write(sender.fd, buf, strlen(buf));
+       }
+
+       free(str);
+       if (log_type == LOG_FILE)
+               fsync(sender.fd);
+
+       return 0;
+}
+
+static void follow_log(struct ubus_context *ctx, int id)
+{
+       FILE *fp;
+       int ret;
+
+       signal(SIGPIPE, SIG_IGN);
+
+       if (pid_file) {
+               fp = fopen(pid_file, "w+");
+               if (fp) {
+                       fprintf(fp, "%d", getpid());
+                       fclose(fp);
+               }
+       }
+
+       uloop_init();
+       ubus_add_uloop(ctx);
+
+       log_event.remove_cb = log_handle_remove;
+       log_event.cb = log_notify;
+       ret = ubus_register_subscriber(ctx, &log_event);
+       if (ret)
+               fprintf(stderr, "Failed to add watch handler: %s\n", ubus_strerror(ret));
+
+       ret = ubus_subscribe(ctx, &log_event, id);
+       if (ret)
+               fprintf(stderr, "Failed to add watch handler: %s\n", ubus_strerror(ret));
+
+       if (log_ip && log_port) {
+               openlog("logread", LOG_PID, LOG_DAEMON);
+               log_type = LOG_NET;
+               sender.cb = log_handle_fd;
+               retry.cb = log_handle_reconnect;
+               uloop_timeout_set(&retry, 1000);
+       } else if (log_file) {
+               log_type = LOG_FILE;
+               sender.fd = open(log_file, O_CREAT | O_WRONLY| O_APPEND, 0600);
+               if (sender.fd < 0) {
+                       fprintf(stderr, "failed to open %s: %s\n", log_file, strerror(errno));
+                       exit(-1);
+               }
+       } else {
+               sender.fd = STDOUT_FILENO;
+       }
+
+       uloop_run();
+       ubus_free(ctx);
+       uloop_done();
+}
+
+enum {
+       READ_LINE,
+       __READ_MAX
+};
+
+
+
+static const struct blobmsg_policy read_policy[] = {
+       [READ_LINE] = { .name = "lines", .type = BLOBMSG_TYPE_ARRAY },
+};
+
+static void read_cb(struct ubus_request *req, int type, struct blob_attr *msg)
+{
+       struct blob_attr *cur;
+       struct blob_attr *_tb[__READ_MAX];
+       time_t t;
+       int rem;
+
+       if (!msg)
+               return;
+
+       blobmsg_parse(read_policy, ARRAY_SIZE(read_policy), _tb, blob_data(msg), blob_len(msg));
+       if (!_tb[READ_LINE])
+               return;
+       blobmsg_for_each_attr(cur, _tb[READ_LINE], rem) {
+               struct blob_attr *tb[__LOG_MAX];
+               uint32_t p;
+               char *c;
+
+               if (blobmsg_type(cur) != BLOBMSG_TYPE_TABLE)
+                       continue;
+
+               blobmsg_parse(log_policy, ARRAY_SIZE(log_policy), tb, blobmsg_data(cur), blobmsg_data_len(cur));
+               if (!tb[LOG_MSG] || !tb[LOG_ID] || !tb[LOG_PRIO] || !tb[LOG_SOURCE] || !tb[LOG_TIME])
+                       continue;
+
+               t = blobmsg_get_u64(tb[LOG_TIME]);
+               p = blobmsg_get_u32(tb[LOG_PRIO]);
+               c = ctime(&t);
+               c[strlen(c) - 1] = '\0';
+
+               printf("%s %s.%s%s %s\n",
+                       c, getcodetext(LOG_FAC(p) << 3, facilitynames), getcodetext(LOG_PRI(p), prioritynames),
+                       (blobmsg_get_u32(tb[LOG_SOURCE])) ? ("") : (" kernel:"),
+                       blobmsg_get_string(tb[LOG_MSG]));
+       }
+}
+
+static int usage(const char *prog)
+{
+       fprintf(stderr, "Usage: %s [options]\n"
+               "Options:\n"
+               "    -s <path>          Path to ubus socket\n"
+               "    -l <count>         Got only the last 'count' messages\n"
+               "    -r <server> <port> Stream message to a server\n"
+               "    -F <file>          Log file\n"
+               "    -S <bytes>         Log size\n"
+               "    -p <file>          PID file\n"
+               "    -h <hostname>      Add hostname to the message\n"
+               "    -P <prefix>        Prefix custom text to streamed messages\n"
+               "    -f                 Follow log messages\n"
+               "    -u                 Use UDP as the protocol\n"
+               "\n", prog);
+       return 1;
+}
+
+int main(int argc, char **argv)
+{
+       struct ubus_context *ctx;
+       uint32_t id;
+       const char *ubus_socket = NULL;
+       int ch, ret, subscribe = 0, lines = 0;
+       static struct blob_buf b;
+
+       while ((ch = getopt(argc, argv, "ufcs:l:r:F:p:S:P:h:")) != -1) {
+               switch (ch) {
+               case 'u':
+                       log_udp = 1;
+                       break;
+               case 's':
+                       ubus_socket = optarg;
+                       break;
+               case 'r':
+                       log_ip = optarg++;
+                       log_port = argv[optind++];
+                       break;
+               case 'F':
+                       log_file = optarg;
+                       break;
+               case 'p':
+                       pid_file = optarg;
+                       break;
+               case 'P':
+                       log_prefix = optarg;
+                       break;
+               case 'f':
+                       subscribe = 1;
+                       break;
+               case 'l':
+                       lines = atoi(optarg);
+                       break;
+               case 'S':
+                       log_size = atoi(optarg);
+                       if (log_size < 1)
+                               log_size = 1;
+                       log_size *= 1024;
+                       break;
+               case 'h':
+                       hostname = optarg;
+                       break;
+               default:
+                       return usage(*argv);
+               }
+       }
+
+       ctx = ubus_connect(ubus_socket);
+       if (!ctx) {
+               fprintf(stderr, "Failed to connect to ubus\n");
+               return -1;
+       }
+
+       ret = ubus_lookup_id(ctx, "log", &id);
+       if (ret)
+               fprintf(stderr, "Failed to find log object: %s\n", ubus_strerror(ret));
+
+       if (!subscribe || lines) {
+               blob_buf_init(&b, 0);
+               if (lines)
+                       blobmsg_add_u32(&b, "lines", lines);
+               ubus_invoke(ctx, id, "read", b.head, read_cb, 0, 3000);
+       }
+
+       if (subscribe)
+               follow_log(ctx, id);
+
+       return 0;
+}
diff --git a/log/syslog.c b/log/syslog.c
new file mode 100644 (file)
index 0000000..fcc4a74
--- /dev/null
@@ -0,0 +1,299 @@
+/*
+ * Copyright (C) 2013 John Crispin <blogic@openwrt.org>
+ *
+ * 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 <linux/un.h>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+
+#include <fcntl.h>
+#include <regex.h>
+#include <time.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <syslog.h>
+
+#include <libubox/uloop.h>
+#include <libubox/usock.h>
+#include <libubox/ustream.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;
+static regex_t pat_prio;
+static 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;
+
+       /* bounce out if we don't have init'ed yet (regmatch etc will blow) */
+       if (!log) {
+               fprintf(stderr, buf);
+               return;
+       }
+
+       /* 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;
+       }
+
+       //fprintf(stderr, "-> %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 {
+               //fprintf(stderr, "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;
+
+       fd = open(KLOG_DEFAULT_PROC, O_RDONLY | O_NONBLOCK);
+       if (fd < 0) {
+               fprintf(stderr, "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;
+
+       unlink(log_dev);
+       fd = usock(USOCK_UNIX | USOCK_UDP |  USOCK_SERVER | USOCK_NONBLOCK, log_dev, NULL);
+       if (fd < 0) {
+               fprintf(stderr,"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) {
+               fprintf(stderr, "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;
+}
+
+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)) {
+               fprintf(stderr, "Failed to allocate log memory\n");
+               exit(-1);
+       }
+
+       syslog_open();
+       klog_open();
+       openlog("sysinit", LOG_CONS, LOG_DAEMON);
+}
+
+void
+log_shutdown(void)
+{
+       ustream_free(&slog.stream);
+       ustream_free(&klog.stream);
+       close(slog.fd.fd);
+       close(klog.fd.fd);
+}
diff --git a/log/syslog.h b/log/syslog.h
new file mode 100644 (file)
index 0000000..dc712ff
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2013 John Crispin <blogic@openwrt.org>
+ *
+ * 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_INTERNAL = 2,
+       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);
+void log_shutdown(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);
+void log_add(char *buf, int size, int source);
+void ubus_notify_log(struct log_head *l);
+
+#endif