Integrate zlogutil into libdlogutil 10/280910/3
authorMichal Bloch <m.bloch@samsung.com>
Fri, 26 Aug 2022 15:54:18 +0000 (17:54 +0200)
committerMichal Bloch <m.bloch@samsung.com>
Mon, 12 Sep 2022 17:10:01 +0000 (19:10 +0200)
Change-Id: I910ba8eda65721afc1a687b4d76b927587cde09b
Signed-off-by: Michal Bloch <m.bloch@samsung.com>
20 files changed:
Makefile.am
include/logcommon.h
include/zero_copy_backend.h
packaging/dlog.spec
src/libdlog/log_zero_copy.c
src/libdlogutil/fd_info.h
src/libdlogutil/fdi_logger.c
src/libdlogutil/fdi_pipe.c
src/libdlogutil/fdi_zero_copy.c [new file with mode: 0644]
src/libdlogutil/fdi_zero_copy.h [new file with mode: 0644]
src/libdlogutil/lib.c
src/libdlogutil/logretrieve.c
src/libdlogutil/logretrieve.h
src/zlogutil/zlog_file_writer.cpp [deleted file]
src/zlogutil/zlog_file_writer.h [deleted file]
src/zlogutil/zlog_filter.cpp [deleted file]
src/zlogutil/zlog_filter.h [deleted file]
src/zlogutil/zlog_list.cpp [deleted file]
src/zlogutil/zlog_list.h [deleted file]
src/zlogutil/zlogutil.cpp [deleted file]

index 445ccd3..663726f 100644 (file)
@@ -69,6 +69,7 @@ libdlogutil_la_SOURCES = \
        src/libdlogutil/fd_info.c \
        src/libdlogutil/fdi_pipe.c \
        src/libdlogutil/fdi_logger.c \
+       src/libdlogutil/fdi_zero_copy.c \
        src/shared/backend_androidlogger.c \
        src/shared/logcommon.c \
        src/shared/logprint.c \
@@ -104,35 +105,6 @@ dlogutil_SOURCES = \
        src/shared/ptrs_list.c \
        src/shared/util_parser.c
 
-bin_PROGRAMS += zlogutil
-
-zlogutil_CFLAGS =  \
-       $(AM_CFLAGS) \
-       -fPIE
-
-zlogutil_CXXFLAGS =  \
-       $(AM_CFLAGS) \
-       -fPIE
-
-zlogutil_LDFLAGS = \
-       $(AM_LDFLAGS) \
-       -pie
-
-zlogutil_SOURCES = \
-       src/zlogutil/zlogutil.cpp \
-       src/zlogutil/zlog_filter.cpp \
-       src/zlogutil/zlog_list.cpp \
-       src/zlogutil/zlog_file_writer.cpp \
-       src/shared/ptrs_list.c \
-       src/shared/logcommon.c \
-       src/shared/logconfig.c \
-       src/shared/parsers.c \
-       src/shared/translate_syslog.c \
-       src/shared/queued_entry.c \
-       src/shared/queued_entry_timestamp.c \
-       src/shared/logprint.c
-
-
 libexec_PROGRAMS = dlog-log-critical
 
 dlog_log_critical_CXXFLAGS = \
@@ -703,6 +675,7 @@ src_tests_logutil_pos_SOURCES = src/tests/logutil_pos.c \
        src/libdlogutil/fd_info.c \
        src/libdlogutil/fdi_pipe.c \
        src/libdlogutil/fdi_logger.c \
+       src/libdlogutil/fdi_zero_copy.c \
        src/shared/backend_androidlogger.c \
        src/shared/logcommon.c \
        src/shared/logprint.c \
@@ -721,6 +694,7 @@ src_tests_logutil_neg_SOURCES = src/tests/logutil_neg.c \
        src/libdlogutil/fd_info.c \
        src/libdlogutil/fdi_pipe.c \
        src/libdlogutil/fdi_logger.c \
+       src/libdlogutil/fdi_zero_copy.c \
        src/shared/backend_androidlogger.c \
        src/shared/logcommon.c \
        src/shared/logprint.c \
@@ -823,6 +797,7 @@ src_tests_libdlogutil_neg_SOURCES = src/tests/libdlogutil_neg.c  \
        src/shared/parsers.c \
        src/libdlogutil/fdi_pipe.c \
        src/libdlogutil/fdi_logger.c \
+       src/libdlogutil/fdi_zero_copy.c \
        src/shared/backend_androidlogger.c \
        src/shared/queued_entry.c \
        src/shared/translate_syslog.c
@@ -844,6 +819,7 @@ src_tests_libdlogutil_pos_SOURCES = src/tests/libdlogutil_pos.c  \
        src/shared/parsers.c \
        src/libdlogutil/fdi_pipe.c \
        src/libdlogutil/fdi_logger.c \
+       src/libdlogutil/fdi_zero_copy.c \
        src/shared/backend_androidlogger.c \
        src/shared/queued_entry.c \
        src/shared/translate_syslog.c
index 3c77d61..47897a0 100644 (file)
 #define DEFAULT_WAIT_PIPE_MS 500
 #define MIN_WAIT_PIPE_MS 7
 
+#define NSEC_PER_SEC 1000000000
+#define NSEC_PER_MSEC 1000000
+#define USEC_PER_MSEC 1000
+
 #ifdef __cplusplus
 extern "C" {
 #endif
index dd9035e..07a7a9e 100644 (file)
@@ -3,6 +3,19 @@
 
 #pragma once
 
+#include <logcommon.h>
+
 #include <linux/zlogger.h>
 
+#include <stdint.h>
+#include <time.h>
+
 #define ZERO_COPY_DEVICE_NAME "/dev/zlogger"
+
+static inline uint64_t get_zlog_clock(void)
+{
+       struct timespec begin;
+       clock_gettime(CLOCK_MONOTONIC, &begin);
+       return (uint64_t)begin.tv_nsec + (uint64_t)begin.tv_sec * NSEC_PER_SEC;
+}
+
index dc83a9e..b14030e 100644 (file)
@@ -202,8 +202,6 @@ install -m 0644 gcov-obj/* %{buildroot}%{_datadir}/gcov/obj/%{name}
 %manifest dlog.manifest
 %license LICENSE.APACHE2.0 LICENSE.MIT
 %attr(750,log,log) %{_bindir}/dlogutil
-# Zlogutil is a temporary solution, to be integrated into Dlogutil
-%attr(750,log,log) %{_bindir}/zlogutil
 %attr(755,log,log) %{_bindir}/dlogsend
 %attr(750,log,log) %{_bindir}/dlog_cleanup
 %attr(750,log,log) %{_bindir}/dlogmetrics
index 1436db5..e3ab035 100644 (file)
@@ -68,13 +68,6 @@ static inline int alloc_block(int tid)
        return r;
 }
 
-static inline uint64_t get_local_clock(void)
-{
-       struct timespec begin;
-       clock_gettime(CLOCK_MONOTONIC, &begin);
-       return (uint64_t)begin.tv_nsec + ((uint64_t)begin.tv_sec) * 1000 * 1000 * 1000;
-}
-
 static inline struct zlogger_block *get_valid_block(int tid, size_t len)
 {
        if (blk != 0) {
@@ -95,7 +88,11 @@ static int zero_copy_write(log_id_t log_id, log_priority prio, const char *tag,
 {
        // TODO: log_id and tp_mono ignored for now
 
-       uint64_t ts = get_local_clock();
+       /* TODO: drop zlogger_entry and just write
+        * dlogutil_entry directly. Needs kernel support
+        * for stdout logs though. */
+
+       uint64_t ts = get_zlog_clock();
        const int tid = gettid();
        size_t hd_size = sizeof(struct zlogger_entry);
        size_t prio_size = 1;
index 7753f4a..279ffff 100644 (file)
@@ -74,6 +74,10 @@ struct fd_ops {
        /// Check whether there will be no more logs on this FD
        bool (*eof)(const struct fd_info *fdi);
 
+       /// Check how long until the FDI is ready to read (in ms).
+       /// This op can be NULL, in which case its main FD will be handled via epoll.
+       int (*poll_timeout)(const struct fd_info *fdi);
+
        // Whether to filter logs on the client
        bool should_filter;
 };
index 092b719..64b11c9 100644 (file)
@@ -339,5 +339,6 @@ const struct fd_ops ops_logger = {
        .get_capacity = logger_get_capacity,
        .get_usage = logger_get_usage,
        .eof = logger_eof,
+       .poll_timeout = NULL,
        .should_filter = true,
 };
index b002e96..b753c19 100644 (file)
@@ -407,5 +407,6 @@ const struct fd_ops ops_pipe = {
        .get_capacity = pipe_get_capacity,
        .get_usage = pipe_get_usage,
        .eof = pipe_eof,
+       .poll_timeout = NULL,
        .should_filter = false,
 };
diff --git a/src/libdlogutil/fdi_zero_copy.c b/src/libdlogutil/fdi_zero_copy.c
new file mode 100644 (file)
index 0000000..48a9939
--- /dev/null
@@ -0,0 +1,394 @@
+/*  MIT License
+ *
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is furnished
+ * to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE. */
+
+// C
+#include <errno.h>
+#include <string.h>
+
+// POSIX
+#include <sys/mman.h>
+
+// DLog
+#include <logconfig.h>
+#include <queued_entry_timestamp.h>
+#include <zero_copy_backend.h>
+
+// dlogutil
+#include "fd_info.h"
+#include "fdi_zero_copy.h"
+
+static const uint64_t ZLOGGER_MONITOR_INTERVAL = 1 * NSEC_PER_SEC;
+
+static int zero_copy_get_capacity(struct fd_info *fdi, unsigned int *capacity)
+{
+       /* The kernel doesn't seem to have an interface to read this,
+        * but the constant has to be correct for everything else to
+        * work correctly, anyway. */
+
+       *capacity = (unsigned int) ZLOGGER_BUFFER_SIZE;
+       return 0;
+}
+
+static int zero_copy_get_usage(struct fd_info *fdi, unsigned int *usage)
+{
+       /* Semantics not obvious. There are two relevant values really,
+        * logs inside a block and blocks themselves (in other backends
+        * each log is its own block and can be purged and allocated
+        * to programs separately). Since this backend is intended not
+        * to be read until something happens, this function is not too
+        * useful (since there's hopefully 100% usage by the time). */
+
+       return -ENOTSUP;
+}
+
+static int zero_copy_clear(struct fd_info *fdi)
+{
+       /* No kernel interface.
+        * Production-only backend. */
+
+       return -ENOTSUP;
+}
+
+static int zero_copy_create(struct fd_info *fdi, const struct log_config *conf, log_id_t id, list_head *used_paths, log_id_t *aliased)
+{
+       // We can't clear, so the flag is R not RW
+       fdi->fd = open(ZERO_COPY_DEVICE_NAME, O_RDONLY | O_CLOEXEC);
+       if (fdi->fd < 0)
+               return -errno;
+
+       struct zero_copy_priv_data *const zpd = malloc(sizeof *zpd);
+       if (!zpd) {
+               close(fdi->fd);
+               fdi->fd = -1;
+               return -ENOMEM;
+       }
+
+       zpd->this_read_ts = 0;
+       zpd->prev_read_ts = 0;
+       zpd->items = NULL;
+       zpd->dump = false;
+       zpd->monitor = false;
+       zpd->eof = false;
+
+       fdi->priv_data = zpd;
+
+       return 0;
+}
+
+static void free_entry_list(struct zlog_entry_list **items)
+{
+       struct zlog_entry_list *item = *items;
+       while (item) {
+               /* I guess they can't, I guess they won't, I guess they front!
+                * That's how I know my life is out of luck, fool. */
+               struct zlog_entry_list *const front = item;
+               item = front->next;
+               free(front->entry);
+               free(front);
+       }
+
+       *items = NULL;
+}
+
+static void zero_copy_destroy(struct fd_info *fdi)
+{
+       struct zero_copy_priv_data *const zpd = (struct zero_copy_priv_data *)fdi->priv_data;
+       if (!zpd)
+               return;
+
+       close(fdi->fd);
+       free_entry_list(&zpd->items);
+       free(zpd);
+}
+
+static int zero_copy_prepare_print(struct fd_info *fdi, int dump, bool monitor, struct log_filter *filter_object, char *compress)
+{
+       /* The backend doesn't allow us to do anything other than read,
+        * so at least reading requires no preparatory steps. */
+
+       struct zero_copy_priv_data *const zpd = (struct zero_copy_priv_data *)fdi->priv_data;
+       zpd->dump = monitor ? 0 : dump;
+       zpd->monitor = monitor;
+       zpd->prev_read_ts = monitor ? get_zlog_clock() : 0;
+       zpd->this_read_ts = zpd->prev_read_ts;
+
+       return 0;
+}
+
+static bool zero_copy_eof(const struct fd_info *fdi)
+{
+       const struct zero_copy_priv_data *const zpd = (const struct zero_copy_priv_data *)fdi->priv_data;
+       return zpd->eof;
+}
+
+static int zero_copy_poll_timeout(const struct fd_info *fdi)
+{
+       const struct zero_copy_priv_data *const zpd = (const struct zero_copy_priv_data *)fdi->priv_data;
+       if (zpd->eof)
+               return -1;
+
+       const uint64_t now = get_zlog_clock();
+       const uint64_t time_since_last_read = zpd->prev_read_ts - now;
+       if (time_since_last_read > ZLOGGER_MONITOR_INTERVAL)
+               return 0;
+
+       const uint64_t millisec_remaining = (ZLOGGER_MONITOR_INTERVAL - time_since_last_read) / NSEC_PER_MSEC;
+       if (millisec_remaining > INT_MAX)
+               return -1;
+       return millisec_remaining;
+}
+
+static dlogutil_entry_s *alloc_dlogutil_entry_from_zlog_entry(struct zlogger_entry *ze)
+{
+       /* TODO: possibly this should be in shared/queued_entry.h?
+        * I don't like how that one is responsible for everything though. */
+
+       if (ze->len < 4) // prio, tag, nul, msg
+               return NULL;
+
+       int32_t tag_len = 0;
+       for (int i = 1; i < ze->len; ++i) {
+               if (ze->data[i] != '\0')
+                       continue;
+               tag_len = i - 1;
+               break;
+       }
+       if (!tag_len)
+               return NULL;
+
+       const uint16_t len = sizeof (dlogutil_entry_s) + ze->len - 1; // FIXME: potential overflow
+       dlogutil_entry_s *const de = malloc(len);
+       if (!de)
+               return NULL;
+
+       memcpy(de->msg, ze->data + 1, ze->len - 1);
+       de->len = len;
+
+       de-> sec_sent_mono = ze->time / NSEC_PER_SEC;
+       de->nsec_sent_mono = ze->time % NSEC_PER_SEC;
+
+       /* Arguably these two are the same as SENT, but maybe
+        * it's best to cull the proliferation of timestamps */
+       de->sec_recv_mono = 0;
+       de->nsec_recv_mono = -1;
+
+       de->sec_sent_real = 0;
+       de->nsec_sent_real = -1;
+       de->sec_recv_real = 0;
+       de->nsec_recv_real = -1;
+
+       de->priority = ze->data[0];
+       de->tag_len = tag_len;
+
+       return de;
+}
+
+static void append_dlogutil_entry_to_item_list(dlogutil_entry_s *de, struct zlog_entry_list **items)
+{
+       struct zlog_entry_list *const item = malloc(sizeof *item);
+       if (!item) {
+               free(de); // must consoom
+               return;
+       }
+
+       item->entry = de;
+       item->next = *items;
+       *items = item;
+}
+
+static void load_single_device(struct zlogger_block *zb, struct zlog_entry_list **items, uint64_t min_timestamp, uint64_t max_timestamp)
+{
+       /* TODO: document this mumbo jumbo.
+        * Requires expert earth magic to understand, probably. */
+
+       static size_t const BLOCK_COUNT = ZLOGGER_MAP_SIZE / sizeof *zb;
+       for (size_t i = 0; i < BLOCK_COUNT; i++, zb++) {
+               if (zb->head.offset > ZLOGGER_DATA_MAX)
+                       continue;
+
+               struct zlogger_entry *ze = (struct zlogger_entry *) zb->data;
+
+               while ((uintptr_t) ze < (uintptr_t) zb->data + zb->head.offset) {
+                       if (ze->len == 0)
+                               break;
+
+                       if ((uintptr_t) ze + sizeof *ze + ze->len > (uintptr_t) zb->data + zb->head.offset)
+                               break;
+
+                       unsigned short *const slt = (unsigned short *)&ze->time;
+                       unsigned short const CRC = slt[0] + slt[1] + slt[2] + slt[3]; // TODO: zero_copy_backend.h, get_crc(ze) { return x >> 16 + x >> 48 ... }
+                       if (CRC != ze->CRC)
+                               break;
+
+                       if (ze->time >= min_timestamp && ze->time < max_timestamp) {
+                               dlogutil_entry_s *const de = alloc_dlogutil_entry_from_zlog_entry(ze);
+                               if (de) {
+                                       de->pid = zb->head.pid;
+                                       de->tid = zb->head.tid;
+                                       append_dlogutil_entry_to_item_list(de, items);
+                               } else {
+                                       // TODO: consider -ENOMEM or something
+                               }
+                       }
+
+                       ze = (struct zlogger_entry *) ((uintptr_t) ze + sizeof *ze + ze->len);
+               }
+       }
+}
+
+static void sort_items_list(struct zlog_entry_list **items)
+{
+       /* Stable radix sort with 256 buckets,
+        * done for each byte of the timestamp. */
+
+       struct zlog_entry_list *item_group = *items;
+       for (int step = 0; step < 64; step += 8) {
+               struct zlog_entry_list *begin[256];
+               struct zlog_entry_list *  end[256];
+               memset(begin, 0, sizeof begin);
+               memset(end, 0, sizeof end);
+
+               struct zlog_entry_list *item = item_group;
+               while (item) {
+                       struct zlog_entry_list *const next = item->next;
+
+                       /* TODO: it would be good not to have to recalculate this.
+                        * Perhaps have the item struct contain entry, next, timestamp?
+                        * Maybe it's fine though, math is cheap. */
+                       const uint64_t timestamp64 = item->entry->sec_sent_mono * NSEC_PER_SEC + item->entry->nsec_sent_mono;
+
+                       int const bucket = (timestamp64 >> step) & 0xFF;
+                       if (!begin[bucket])
+                               begin[bucket] = item;
+                       else
+                               end[bucket]->next = item;
+
+                       item->next = NULL;
+                       end[bucket] = item;
+                       item = next;
+               }
+
+               struct zlog_entry_list *prev = NULL;
+               struct zlog_entry_list *next = NULL;
+               for (int bucket = 0; bucket < 256; bucket++) {
+                       if (!begin[bucket])
+                               continue;
+
+                       if (!prev) {
+                               prev = begin[bucket];
+                               next = end[bucket];
+                               continue;
+                       }
+
+                       next->next = begin[bucket];
+                       next = end[bucket];
+               }
+               item_group = prev;
+       }
+       *items = item_group;
+}
+
+static int zero_copy_read(struct fd_info *fdi)
+{
+       struct zero_copy_priv_data *const zpd = (struct zero_copy_priv_data *)fdi->priv_data;
+
+       if (zpd->items || zpd->eof)
+               return -EAGAIN;
+
+       /* In theory logs can fit in the crack between
+        * the two time windows. This is mostly fine as
+        * storage is not robust anyway and the primary
+        * use case for this backend is dump-oriented. */
+       const uint64_t now = get_zlog_clock();
+       zpd->prev_read_ts = zpd->this_read_ts;
+       zpd->this_read_ts = now;
+
+       for (size_t i = 0; i < ZLOGGER_DEVICE_COUNT; ++i) {
+               void *const addr = mmap(NULL, ZLOGGER_MAP_SIZE, PROT_READ, MAP_SHARED, fdi->fd, ZLOGGER_MAP_SIZE * i);
+               if (addr == MAP_FAILED)
+                       break;
+               load_single_device(addr, &zpd->items, zpd->prev_read_ts, now);
+               munmap(addr, ZLOGGER_MAP_SIZE);
+       }
+
+       sort_items_list(&zpd->items);
+
+       if (zpd->dump)
+               zpd->eof = true;
+
+       /* In theory this should return bytes read,
+        * but we don't really track it and it's not
+        * used anyway as long as it's > 0. */
+       return 1;
+}
+
+static dlogutil_entry_s *zero_copy_extract_entry(struct fd_info *fdi)
+{
+       struct zero_copy_priv_data *const zpd = (struct zero_copy_priv_data *) fdi->priv_data;
+       if (!zpd->items)
+               return NULL;
+
+       struct zlog_entry_list *const item = zpd->items;
+       zpd->items = item->next;
+
+       dlogutil_entry_s *const entry = item->entry;
+       free(item);
+
+       if (zpd->dump) {
+               -- zpd->dump;
+               if (!zpd->dump)
+                       free_entry_list(&zpd->items);
+       }
+
+       return entry;
+}
+
+static const dlogutil_entry_s *zero_copy_peek_entry(const struct fd_info *fdi)
+{
+       const struct zero_copy_priv_data *const zpd = (const struct zero_copy_priv_data *)fdi->priv_data;
+       if (!zpd->items)
+               return NULL;
+
+       return zpd->items->entry;
+}
+
+static bool zero_copy_has_log(struct fd_info *fdi)
+{
+       const struct zero_copy_priv_data *const zpd = (const struct zero_copy_priv_data *)fdi->priv_data;
+       return zpd->items != NULL;
+}
+
+const struct fd_ops ops_zero_copy = {
+       .create        = zero_copy_create,
+       .destroy       = zero_copy_destroy,
+       .read          = zero_copy_read,
+       .has_log       = zero_copy_has_log,
+       .extract_entry = zero_copy_extract_entry,
+       .peek_entry    = zero_copy_peek_entry,
+       .prepare_print = zero_copy_prepare_print,
+       .clear         = zero_copy_clear,
+       .get_capacity  = zero_copy_get_capacity,
+       .get_usage     = zero_copy_get_usage,
+       .eof           = zero_copy_eof,
+       .poll_timeout  = zero_copy_poll_timeout,
+       .should_filter = true,
+};
diff --git a/src/libdlogutil/fdi_zero_copy.h b/src/libdlogutil/fdi_zero_copy.h
new file mode 100644 (file)
index 0000000..fad75d6
--- /dev/null
@@ -0,0 +1,44 @@
+/*  MIT License
+ *
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is furnished
+ * to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE. */
+
+#pragma once
+
+#include "fd_info.h"
+
+extern const struct fd_ops ops_zero_copy;
+
+/* Not using dlog ptrs_list because that one is opaque
+ * while the sorting algo inspects the list contents. */
+struct zlog_entry_list {
+       dlogutil_entry_s *entry;
+       struct zlog_entry_list *next;
+};
+
+struct zero_copy_priv_data {
+       uint64_t prev_read_ts;
+       uint64_t this_read_ts;
+       struct zlog_entry_list *items;
+       int dump;
+       bool monitor;
+       bool eof;
+};
+
index 0b7bbd8..d1c60e2 100644 (file)
@@ -177,17 +177,16 @@ EXPORT_API int dlogutil_config_connect(dlogutil_config_s *config, dlogutil_state
                return TIZEN_ERROR_IO_ERROR;
 
        if (backend == BACKEND_ZERO_COPY) {
-               /* At the moment, the zero-copy backend has a separate
-                * binary, `zlogutil`. It is in the process of being
-                * integrated into regular dlogutil, but not there yet. */
-               return TIZEN_ERROR_NOT_SUPPORTED;
+               /* FIXME! There are no buffers under
+                * zero-copy backend, what now? */
+               config->buffers = 1 << LOG_ID_MAIN;
        }
 
        __attribute__ ((cleanup(fdi_array_free))) struct fd_info **fdi_ptrs = NULL;
        log_id_t aliased[LOG_ID_MAX];
        for (int i = 0; i < LOG_ID_MAX; ++i)
                aliased[i] = LOG_ID_INVALID;
-       int fdi_cnt = create_initial_fdis(&fdi_ptrs, config->buffers, backend == BACKEND_PIPE, config->mode == DLOGUTIL_MODE_COMPRESSED_MEMORY_DUMP, &conf, aliased);
+       int fdi_cnt = create_initial_fdis(&fdi_ptrs, config->buffers, backend, config->mode == DLOGUTIL_MODE_COMPRESSED_MEMORY_DUMP, &conf, aliased);
        if (fdi_cnt < 0)
                return TIZEN_ERROR_IO_ERROR;
        if (fdi_cnt == 0)
index 842488b..7ef5162 100644 (file)
 
 #include "fdi_pipe.h"
 #include "fdi_logger.h"
+#include "fdi_zero_copy.h"
 #include "logretrieve.h"
 
 // C
+#include <assert.h>
 #include <limits.h>
 
 bool all_buffers_known_disabled(const struct log_config *conf, int buffers)
@@ -38,7 +40,13 @@ bool all_buffers_known_disabled(const struct log_config *conf, int buffers)
                && !log_config_get_boolean(conf, "handle_kmsg", true);
 }
 
-int create_initial_fdis(struct fd_info ***fdis, int enabled_buffers, bool is_pipe, bool is_compressed_memory, const struct log_config *conf, log_id_t aliased[LOG_ID_MAX])
+static const struct fd_ops *const backend_ops[] = {
+       [BACKEND_PIPE]           = &ops_pipe,
+       [BACKEND_ANDROID_LOGGER] = &ops_logger,
+       [BACKEND_ZERO_COPY]      = &ops_zero_copy,
+};
+
+int create_initial_fdis(struct fd_info ***fdis, int enabled_buffers, backend_t backend, bool is_compressed_memory, const struct log_config *conf, log_id_t aliased[LOG_ID_MAX])
 {
        assert(fdis);
        assert(conf);
@@ -78,8 +86,7 @@ int create_initial_fdis(struct fd_info ***fdis, int enabled_buffers, bool is_pip
                struct fd_info *fdi;
                if (is_compressed_memory) {
                        /* Ignore `is_pipe`. This feature is done
-                        * via pipes always, even on the Android
-                        * Logger backend. */
+                        * via pipes always, even on other backends. */
                        fdi = fdi_create(&ops_pipe, i);
                } else {
                        switch (i) {
@@ -88,7 +95,7 @@ int create_initial_fdis(struct fd_info ***fdis, int enabled_buffers, bool is_pip
                                fdi = fdi_create(&ops_pipe, i);
                                break;
                        default:
-                               fdi = fdi_create(is_pipe ? &ops_pipe : &ops_logger, i);
+                               fdi = fdi_create(backend_ops[backend], i);
                                break;
                        }
                }
@@ -202,8 +209,13 @@ static int state_prepare_single_print(dlogutil_state_s *state, int nfds, char *c
                .events = EPOLLIN,
        };
        state->enabled[nfds] = true;
-       epoll_ctl(state->epollfd, EPOLL_CTL_ADD, fd, &ev);
-       ++state->epoll_cnt;
+
+       if (!state->data_fds[nfds]->ops->poll_timeout) {
+               epoll_ctl(state->epollfd, EPOLL_CTL_ADD, fd, &ev);
+               ++ state->epoll_cnt;
+       } else {
+               ++ state->non_epoll_cnt;
+       }
 
        return TIZEN_ERROR_NONE;
 }
@@ -237,6 +249,7 @@ int dlogutil_state_init(dlogutil_state_s *state, struct fd_info ***data_fds_ptr,
        // TODO: optimize mode == DLOGUTIL_MODE_NONPRINTING case
        state->epollfd = -1;
        state->epoll_cnt = 0;
+       state->non_epoll_cnt = 0;
        state->enabled = NULL;
        state->filter_object = NULL;
        state->evs = NULL;
@@ -280,9 +293,11 @@ int dlogutil_state_init(dlogutil_state_s *state, struct fd_info ***data_fds_ptr,
                        return r;
        }
 
-       state->evs = calloc(state->epoll_cnt, sizeof *state->evs);
-       if (!state->evs)
-               return TIZEN_ERROR_OUT_OF_MEMORY;
+       if (state->epoll_cnt) {
+               state->evs = calloc(state->epoll_cnt, sizeof *state->evs);
+               if (!state->evs)
+                       return TIZEN_ERROR_OUT_OF_MEMORY;
+       }
 
        return TIZEN_ERROR_NONE;
 }
@@ -333,13 +348,21 @@ static void remove_drained_buffers(dlogutil_state_s *state)
                        continue;
 
                state->enabled[i] = false;
-               epoll_ctl(state->epollfd, EPOLL_CTL_DEL, fdi->fd, NULL);
-               --state->epoll_cnt;
+
+               if (!fdi->ops->poll_timeout) {
+                       epoll_ctl(state->epollfd, EPOLL_CTL_DEL, fdi->fd, NULL);
+                       -- state->epoll_cnt;
+               } else {
+                       -- state->non_epoll_cnt;
+               }
        }
 }
 
-static int refill_fdi_buffers(dlogutil_state_s *state, int timeout)
+static int refill_fdi_buffers_regular(dlogutil_state_s *state, int timeout)
 {
+       if (!state->epoll_cnt)
+               return 0;
+
        int const nfds = epoll_wait(state->epollfd, state->evs, state->epoll_cnt, timeout);
        if (nfds < 0)
                return -errno;
@@ -354,6 +377,87 @@ static int refill_fdi_buffers(dlogutil_state_s *state, int timeout)
        return 0;
 }
 
+static int how_long_would_we_have_to_wait(dlogutil_state_s *state)
+{
+       /* Pick the smallest timeout. We're using epoll timeout
+        * convention where -1 means infinite. This is mostly
+        * for future-proofing since the only client using this
+        * is the zero-copy backend which only has 1 FDI anyway. */
+
+       int ret = -1;
+       for (int i = 0; i < state->fd_count; ++i) {
+               if (!state->enabled[i])
+                       continue;
+
+               const struct fd_info *const fdi = state->data_fds[i];
+               if (fdi->ops->eof(fdi))
+                       continue;
+
+               const int timeout = fdi->ops->poll_timeout(fdi);
+               if (timeout != -1 && (timeout < ret || ret == -1))
+                       ret = timeout;
+       }
+       return ret;
+}
+
+static int refill_fdi_buffers_special(dlogutil_state_s *state, int timeout)
+{
+       if (!state->non_epoll_cnt)
+               return 0;
+
+       /* Figure out how long we would have to wait. Sleep until then, or at least
+        * as long as we are allowed to (can't always pull off an all-nighter). */
+       int needed_timeout = how_long_would_we_have_to_wait(state);
+       if (needed_timeout > timeout && timeout != -1) {
+               usleep(timeout * USEC_PER_MSEC);
+               return 0;
+       }
+
+       // FIXME: can the sleep be interrupted via signals etc?
+       // also FIXME: can `needed_timeout` be -1 at this point?
+
+       usleep(needed_timeout * USEC_PER_MSEC);
+       for (int i = 0; i < state->fd_count; ++i) {
+               if (!state->enabled[i])
+                       continue;
+
+               struct fd_info *const fdi = state->data_fds[i];
+               if (fdi->ops->eof(fdi))
+                       continue;
+
+               int const rd = fdi->ops->read(fdi);
+               if (rd < 0 && rd != -EAGAIN)
+                       return rd;
+       }
+
+       return 0;
+}
+
+static int refill_fdi_buffers(dlogutil_state_s *state, int timeout)
+{
+       int r;
+
+       /* Both of the two modes below perform a timeout-limited
+        * sleep; they can't both do it at the same time or we
+        * would exceed the timeout. Fortunately they don't live
+        * together, so we can assume only one of the two will run. */
+       assert(state->non_epoll_cnt == 0 || state->epoll_cnt == 0);
+
+       /* Regular file descriptors are handled via a collective epoll
+        * to minimize the number of performed syscalls. */
+       r = refill_fdi_buffers_regular(state, timeout);
+       if (r)
+               return r;
+
+       /* Special needs file descriptors have their own
+        * means of figuring out if they're ready to refill. */
+       r = refill_fdi_buffers_special(state, timeout);
+       if (r)
+               return r;
+
+       return 0;
+}
+
 static void set_flush_target(dlogutil_state_s *state, bool all_buffers_empty, long last_log_age)
 {
        /* For the dumping modes, don't flush since that just removes the
@@ -426,7 +530,7 @@ int do_print_once(dlogutil_state_s *state, int timeout, dlogutil_entry_s **out)
        const bool is_limited_dumping = state->mode == DLOGUTIL_MODE_DUMP
                && state->dump_size != DLOGUTIL_MAX_DUMP_SIZE;
 
-       while (state->epoll_cnt > 0) {
+       while (state->epoll_cnt > 0 || state->non_epoll_cnt > 0) {
                /* Note: sort_vector_time_span assumes that either now.tv_nsec == -1,
                 * or now is the current CLOCK_MONOTONIC timestamp. If you change the clock,
                 * make sure to change that function! */
index b28d51c..f4725de 100644 (file)
@@ -41,11 +41,12 @@ typedef struct dlogutil_state {
        dlogutil_mode_e mode;
        int epollfd;
        int epoll_cnt;
+       int non_epoll_cnt;
        int fd_count;
        bool need_epoll;
        log_id_t aliased[LOG_ID_MAX];
 } dlogutil_state_s;
 
-int create_initial_fdis(struct fd_info ***fdis, int enabled_buffers, bool is_pipe, bool is_compressed_memory, const struct log_config *conf, log_id_t aliased[LOG_ID_MAX]);
+int create_initial_fdis(struct fd_info ***fdis, int enabled_buffers, backend_t backend, bool is_compressed_memory, const struct log_config *conf, log_id_t aliased[LOG_ID_MAX]);
 int dlogutil_state_init(dlogutil_state_s *state, struct fd_info ***data_fds_ptr, int fd_count, dlogutil_config_s *config, bool sorting_needed, dlogutil_sorting_order_e real_sort_by, const struct log_config *conf, log_id_t aliased[LOG_ID_MAX]);
 int do_print_once(dlogutil_state_s *state, int timeout, dlogutil_entry_s **out);
diff --git a/src/zlogutil/zlog_file_writer.cpp b/src/zlogutil/zlog_file_writer.cpp
deleted file mode 100644 (file)
index c630e41..0000000
+++ /dev/null
@@ -1,67 +0,0 @@
-// Copyright 2022 Samsung Electronics Co., Ltd. All rights reserved
-// SPDX-License-Identifier: MIT
-
-#include <cerrno>
-#include <cstdarg>
-#include <cstring>
-
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <unistd.h>
-
-#include "zlog_file_writer.h"
-
-/* TODO: consider putting this in a constructor instead.
- * On the other hand, we will have to use exceptions then,
- * and we still need open/close for recycling. */
-int zlog_file_writer::open(const char *filename)
-{
-       if (_fp)
-               return -EBUSY;
-
-       if (!filename || strlen(filename) == 0) {
-               _fp = stdout;
-               return 0;
-       }
-
-       struct stat st;
-       if (lstat (filename, &st) == 0 && S_ISLNK(st.st_mode))
-               return -ENOLINK; // not the most fortunate choice
-
-       /* In theory there is a TOCTTOU issue here, but
-        * in practice the real zlog device can't just
-        * disappear and if we're pwned we can't do anything. */
-       _fp = fopen(filename, "wb");
-       if (!_fp)
-               return -errno;
-
-       return 0;
-}
-
-void zlog_file_writer::close(void)
-{
-       if (_fp && _fp != stdout)
-               fclose(_fp);
-
-       _fp = nullptr;
-}
-
-zlog_file_writer::~zlog_file_writer (void)
-{
-       close();
-}
-
-/* TODO: realistically, this class is just a wrapper for `FILE *`.
- * Maybe some standard class does this better already? */
-int zlog_file_writer::printf(const char *fmt, ...) const
-{
-       if (!_fp)
-               return 0;
-
-       va_list args;
-       va_start(args, fmt);
-       int r = vfprintf(_fp, fmt, args);
-       va_end(args);
-
-       return r;
-}
diff --git a/src/zlogutil/zlog_file_writer.h b/src/zlogutil/zlog_file_writer.h
deleted file mode 100644 (file)
index 3d81987..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-// Copyright 2022 Samsung Electronics Co., Ltd. All rights reserved
-// SPDX-License-Identifier: MIT
-
-#pragma once
-
-#include <cstdio>
-
-class zlog_file_writer
-{
-public:
-       int open(const char *filename);
-       void close(void);
-       int printf(const char *fmt, ...) const;
-
-       ~ zlog_file_writer (void);
-private:
-       FILE *_fp;
-};
diff --git a/src/zlogutil/zlog_filter.cpp b/src/zlogutil/zlog_filter.cpp
deleted file mode 100644 (file)
index 713ddd8..0000000
+++ /dev/null
@@ -1,72 +0,0 @@
-// Copyright 2022 Samsung Electronics Co., Ltd. All rights reserved
-// SPDX-License-Identifier: MIT
-
-#include <cstring>
-#include <fstream>
-
-#include <logcommon.h>
-#include <zero_copy_backend.h>
-
-#include "zlog_filter.h"
-
-/* TODO: this is the client's equivalent to the dlog config file
- * that usually contains limiter rules. Probably to be handled by
- * libdlog though. */
-size_t zlog_filter::load_file(const char *filename)
-{
-       static const char *const WHITE_SPACE = " \t\n\r\f\v";
-       std::ifstream ifs(filename);
-       std::string line;
-
-       while (getline(ifs, line))
-       {
-               line = line.erase(0, line.find_first_not_of(WHITE_SPACE));
-               line = line.erase(line.find_last_not_of(WHITE_SPACE) + 1);
-               add_filter(line.c_str());
-       }
-
-       return _tag_map.size();
-}
-
-// TODO: missing features like T:=E and T*:E
-bool zlog_filter::is_allowed(const char *tag, log_priority priority) const
-{
-       auto got = _tag_map.find(tag);
-       if (got != _tag_map.end())
-               return priority >= got->second;
-
-       got = _tag_map.find("*");
-       if (got != _tag_map.end())
-               return priority >= got->second;
-
-       return false;
-}
-
-int zlog_filter::add_filter(const char *token)
-{
-       std::string tag(token);
-       log_priority prio = DLOG_DEFAULT;
-
-       auto pos = tag.find(":");
-       if (pos != std::string::npos)
-       {
-               prio = filter_char_to_pri(tag[pos+1]);
-               tag = tag.substr(0, pos);
-       }
-
-       if (tag.size() > ZLOGGER_TAG_MAX)
-               return -1;
-
-       if (prio < DLOG_DEFAULT)
-               return -1;
-
-       _tag_map[tag] = prio;
-
-       return 0;
-}
-
-size_t zlog_filter::get_count(void) const
-{
-       return _tag_map.size();
-}
-
diff --git a/src/zlogutil/zlog_filter.h b/src/zlogutil/zlog_filter.h
deleted file mode 100644 (file)
index 78db897..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright 2022 Samsung Electronics Co., Ltd. All rights reserved
-// SPDX-License-Identifier: MIT
-
-#pragma once
-
-#include <string>
-#include <unordered_map>
-
-#include <dlog.h>
-
-class zlog_filter
-{
-public:
-       size_t load_file(const char *filename);
-       bool is_allowed(const char *tag, log_priority priority) const;
-       int add_filter(const char *token);
-       size_t get_count(void) const;
-
-private:
-       std::unordered_map <std::string, log_priority> _tag_map;
-};
diff --git a/src/zlogutil/zlog_list.cpp b/src/zlogutil/zlog_list.cpp
deleted file mode 100644 (file)
index 943f100..0000000
+++ /dev/null
@@ -1,141 +0,0 @@
-// Copyright 2022 Samsung Electronics Co., Ltd. All rights reserved
-// SPDX-License-Identifier: MIT
-
-#include <algorithm>
-#include <cstring>
-#include <memory>
-
-#include <fcntl.h>
-#include <sys/mman.h>
-#include <unistd.h>
-
-#include "zlog_list.h"
-
-zlog_list::zlog_list()
-: _head(nullptr)
-{
-}
-
-zlog_list::~zlog_list()
-{
-       while (_head)
-       {
-               zlog_t *l = _head;
-               _head = l->next;
-               free(l);
-       }
-}
-
-unsigned int zlog_list::load(void)
-{
-       int fd = open(ZERO_COPY_DEVICE_NAME, O_RDONLY | O_CLOEXEC);
-       if (fd < 0)
-               return 0;
-
-       unsigned int count = 0;
-
-       for (int i = 0; i < ZLOGGER_DEVICE_COUNT; i++) {
-               void *addr = mmap(NULL, ZLOGGER_MAP_SIZE, PROT_READ, MAP_SHARED, fd, ZLOGGER_MAP_SIZE*i);
-               if (addr == MAP_FAILED)
-                       break;
-               count += _load(addr, ZLOGGER_MAP_SIZE);
-               munmap(addr, ZLOGGER_MAP_SIZE);
-       }
-
-       close(fd);
-
-       return count;
-}
-
-unsigned int zlog_list::_load(void *address, size_t length)
-{
-       /* TODO: document this mumbo jumbo.
-        * Requires expert earth magic to understand, probably. */
-
-       zlogger_block *b = reinterpret_cast<zlogger_block *>(address);
-       int block_count = (int)(length / sizeof(zlogger_block));
-       unsigned int count = 0;
-
-       for (int i = 0; i < block_count; i++, b++)
-       {
-               if (b->head.offset > ZLOGGER_DATA_MAX)
-                       continue;
-
-               zlogger_entry *e = reinterpret_cast<zlogger_entry *>(b->data);
-
-               while ((unsigned long)e < (unsigned long)b->data + b->head.offset)
-               {
-                       if (e->len == 0)
-                               break;
-
-                       if ((unsigned long)e + sizeof(zlogger_entry) + e->len > (unsigned long)b->data + b->head.offset)
-                               break;
-
-                       unsigned short *slt = (unsigned short *)&e->time;
-                       unsigned short CRC = slt[0] + slt[1] + slt[2] + slt[3];
-                       if (CRC != e->CRC)
-                               break;
-
-                       zlog_t *l = reinterpret_cast<zlog_t *>(malloc(sizeof(zlog_t) + e->len));
-                       memcpy(&l->entry, e, sizeof(zlogger_entry) + e->len);
-                       l->pid = b->head.pid;
-                       l->tid = b->head.tid;
-                       l->next = _head;
-                       _head = l;
-
-                       e = reinterpret_cast<zlogger_entry *>((unsigned long)e + sizeof(zlogger_entry) + e->len);
-                       count ++;
-               }
-       }
-
-       return count;
-}
-
-void zlog_list::sort(void)
-{
-       /* Stable radix sort with 256 buckets,
-        * done for each byte of the timestamp. */
-
-       zlog_t *log = _head;
-       for (int step = 0; step < 64; step += 8)
-       {
-               zlog_t *begin[256], *end[256];
-               memset(begin, 0, sizeof(begin));
-               memset(end, 0, sizeof(end));
-
-               zlog_t *l = log;
-               zlog_t *n = nullptr;
-               while (l != nullptr)
-               {
-                       n = l->next;
-                       int index = (l->entry.time >> step) & 0xFF;
-                       if (begin[index] == nullptr)
-                               begin[index] = l;
-                       else
-                               end[index]->next = l;
-                       l->next = nullptr;
-                       end[index] = l;
-                       l = n;
-               }
-
-               zlog_t *prev = nullptr;
-               zlog_t *next = nullptr;
-               for (int a = 0; a < 256; a++)
-               {
-                       if (begin[a] == nullptr)
-                               continue;
-
-                       if (prev == nullptr)
-                       {
-                               prev = begin[a];
-                               next = end[a];
-                               continue;
-                       }
-                       next->next = begin[a];
-                       next = end[a];
-               }
-               log = prev;
-       }
-       _head = log;
-}
-
diff --git a/src/zlogutil/zlog_list.h b/src/zlogutil/zlog_list.h
deleted file mode 100644 (file)
index b0b7fca..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright 2022 Samsung Electronics Co., Ltd. All rights reserved
-// SPDX-License-Identifier: MIT
-
-#pragma once
-
-#include <zero_copy_backend.h>
-
-struct zlog_t
-{
-       struct zlog_t *next;
-       unsigned short pid;
-       unsigned short tid;
-       struct zlogger_entry entry;
-};
-
-class zlog_list
-{
-public:
-       zlog_list();
-       ~zlog_list();
-       unsigned int load(void);
-       void sort(void);
-       struct zlog_t *get_head_entry(void)
-       {
-               return _head;
-       }
-
-private:
-       unsigned int _load(void *address, size_t length);
-
-       struct zlog_t *_head;
-};
-
diff --git a/src/zlogutil/zlogutil.cpp b/src/zlogutil/zlogutil.cpp
deleted file mode 100644 (file)
index df10b3c..0000000
+++ /dev/null
@@ -1,236 +0,0 @@
-// Copyright 2022 Samsung Electronics Co., Ltd. All rights reserved
-// SPDX-License-Identifier: MIT
-
-#include <cinttypes>
-#include <cstdint>
-#include <cstdio>
-#include <cstdlib>
-#include <cstring>
-#include <map>
-#include <memory>
-#include <string>
-
-#include <fcntl.h>
-#include <sys/mman.h>
-#include <sys/shm.h>
-#include <sys/stat.h>
-#include <sys/ioctl.h>
-#include <unistd.h>
-
-#include <logcommon.h>
-#include <zero_copy_backend.h>
-
-#include "zlog_filter.h"
-#include "zlog_list.h"
-#include "zlog_file_writer.h"
-
-#define ns_to_sec(ns) (ns * 1.0e-9)
-
-typedef enum {
-       LOG_FORMAT_NONE = 0,
-       LOG_FORMAT_KERNELTIME,
-       LOG_FORMAT_HWCLOCK,
-} log_format_e;
-
-// TODO: could be configurable; requires a new --param or making zlogutil read config
-static constexpr uint64_t NSEC_PER_SEC = 1000000000ULL;
-static constexpr uint64_t MONITORING_INTERVAL = 1 * NSEC_PER_SEC;
-
-struct log_option {
-       std::string filename;
-       uint64_t start_time;
-       uint64_t end_time;
-       uint64_t hwc_offset;
-       log_format_e format;
-       zlog_filter user_filter;
-       zlog_filter policy_filter;
-       bool dump;
-       bool monitor;
-};
-
-static bool log_should_print_entry(zlogger_entry &entry, struct log_option &option)
-{
-       if (entry.time < option.start_time)
-               return false;
-
-       if (entry.time > option.end_time)
-               return false;
-
-       const log_priority pri = (log_priority)entry.data[0];
-       const char *tag = &(entry.data[1]);
-
-       if (option.policy_filter.get_count() > 0 && !option.policy_filter.is_allowed(tag, pri))
-               return false;
-
-       return option.user_filter.is_allowed(tag, pri);
-}
-
-static int print_entry(zlog_file_writer *writer, struct log_option &option, zlog_t *log)
-{
-       log_priority pri = (log_priority)log->entry.data[0];
-       char *tag = &log->entry.data[1];
-       char *msg = strchr(tag, 0)+1;
-
-       switch (option.format)
-       {
-       case LOG_FORMAT_KERNELTIME:
-               return writer->printf("%9.3f %c/%-8s(P%5u, T%5u): %s\n", ns_to_sec(log->entry.time), filter_pri_to_char(pri), tag, log->pid, log->tid, msg);
-       case LOG_FORMAT_HWCLOCK:
-               return writer->printf("%12.6f %c/%-8s(P%5u, T%5u): %s\n", ns_to_sec(log->entry.time + option.hwc_offset), filter_pri_to_char(pri), tag, log->pid, log->tid, msg);
-       case LOG_FORMAT_NONE:
-               return writer->printf("%s\n", msg);
-       default:
-               return -1;
-       }
-
-       return 0;
-}
-
-void trim_log_newlines(zlog_t *log)
-{
-       // TODO: consider trimming whitespace in general
-       int last_idx = log->entry.len - 2;
-       while (last_idx > 0 && (log->entry.data[last_idx] == '\n' || log->entry.data[last_idx] == '\r')) {
-               log->entry.data[last_idx] = 0;
-               last_idx--;
-       }
-}
-
-void write_log(zlog_file_writer *writer, zlog_list &logs, struct log_option &option)
-{
-       for (zlog_t *log = logs.get_head_entry(); log; log = log->next) {
-               if (!log_should_print_entry(log->entry, option))
-                       continue;
-
-               trim_log_newlines(log);
-               print_entry(writer, option, log);
-       }
-}
-
-static log_format_e get_log_format(const char *str)
-{
-       std::map <std::string, log_format_e> g_formats = {
-               {"kerneltime", LOG_FORMAT_KERNELTIME},
-               {"hwclock"   , LOG_FORMAT_HWCLOCK   }, // TODO: not in base dlogutil
-       };
-       return g_formats[str];
-}
-
-static bool get_time_offset(log_option &opt)
-{
-       /* I have no idea what this device is,
-        * but I'll trust that it knows about
-        * time more than I do, because it
-        * invented it, and then it perfected
-        * it so that no living timekeeper
-        * could best it in the ring of honor */
-       FILE *fp = fopen("/sys/class/misc/zlogger/time", "r");
-       if (!fp)
-               return false;
-
-       int r = fscanf(fp, "%" SCNu64 " %" SCNu64 " %" SCNu64
-               , &opt.start_time
-               , &opt.end_time
-               , &opt.hwc_offset
-       );
-
-       fclose(fp);
-       return r == 3;
-}
-
-static inline bool is_zlog_on(void)
-{
-       return (access(ZERO_COPY_DEVICE_NAME, F_OK) == 0);
-}
-
-static void print_help(const char *cmd)
-{
-       // TODO: diverges from regular dlogutil a lot
-       fprintf(stdout, "Usage: %s [OPTION]... [FILTER]...\n", cmd);
-       fprintf(stdout, "\nOption:\n"
-                       " -f <filename>   specify target file name\n"
-                       " -v <format>     set time format as \"kerneltime\" or \"hwclock\"\n");
-       fprintf(stdout, "\nFilter:\n"
-                       " filterspecs are a series of <tag>[:priority]\n"
-                       " where <tag> is a log component tag (or * for all) and priority is:\n"
-                       "  V    Verbose\n"
-                       "  D    Debug\n"
-                       "  I    Info\n"
-                       "  W    Warn\n"
-                       "  E    Error\n"
-                       "  F    Fatal\n"
-                       "  S    Silent (suppress all output)\n\n"
-                       " <tag> by itself means <tag>:V\n"
-                       " If no filterspec is found, filter defaults to '*:V'\n\n");
-}
-
-int main(int argc, char *argv[])
-{
-       if (!is_zlog_on()) {
-               fprintf(stderr, "Zero-copy kernel module not present");
-               return EXIT_FAILURE;
-       }
-
-       struct log_option opt = {};
-       int c;
-
-       opt.format = LOG_FORMAT_KERNELTIME;
-       while ((c = getopt(argc, argv, "dlf:v:t:zhm")) != -1) {
-               switch (c) {
-               case 'd':
-                       opt.dump = true;
-                       break;
-               case 'm':
-                       opt.monitor = true;
-                       break;
-               case 'f':
-                       opt.filename = optarg;
-                       break;
-               case 'v':
-                       opt.format = get_log_format(optarg);
-                       break;
-               case 'h':
-               default:
-                       print_help(argv[0]);
-                       return 0;
-               }
-       }
-
-       for (int i = optind; i < argc; i++)
-               opt.user_filter.add_filter(argv[i]);
-
-       if (opt.user_filter.get_count() == 0)
-               opt.user_filter.add_filter("*");
-
-       if (!opt.dump && !opt.monitor) {
-               opt.dump = true;
-               opt.monitor = true;
-       }
-
-       if (!get_time_offset(opt))
-               return EXIT_FAILURE;
-
-       if (!opt.dump)
-               opt.start_time = opt.end_time - MONITORING_INTERVAL;
-
-       auto writer = std::make_unique <zlog_file_writer> ();
-       int r = writer->open(opt.filename.c_str());
-       if (r < 0)
-               return EXIT_FAILURE;
-
-       do {
-               zlog_list logs;
-               size_t sz = logs.load();
-               if (sz == 0)
-                       return EXIT_FAILURE;
-
-               logs.sort();
-
-               write_log(writer.get(), logs, opt);
-
-               opt.start_time = opt.end_time;
-               opt.end_time += MONITORING_INTERVAL;
-       } while (opt.monitor);
-
-       return EXIT_SUCCESS;
-}