provides a minimalistic binary to show the journal content
shows a cursor after and before the output

Signed-off-by: Dominik Csapak <d.csa...@proxmox.com>
---
 Makefile             |  23 ++++
 debian/changelog     |   5 +
 debian/compat        |   1 +
 debian/control       |  13 ++
 debian/copyright     |  22 ++++
 debian/rules         |   9 ++
 debian/source/format |   1 +
 src/Makefile         |  23 ++++
 src/journalreader.c  | 360 +++++++++++++++++++++++++++++++++++++++++++++++++++
 9 files changed, 457 insertions(+)
 create mode 100644 Makefile
 create mode 100644 debian/changelog
 create mode 100644 debian/compat
 create mode 100644 debian/control
 create mode 100644 debian/copyright
 create mode 100755 debian/rules
 create mode 100644 debian/source/format
 create mode 100644 src/Makefile
 create mode 100644 src/journalreader.c

diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..8274419
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,23 @@
+PACKAGE := pve-journalreader
+
+ARCH != dpkg-architecture -qDEB_BUILD_ARCH
+PKGVER != dpkg-parsechangelog -S version
+GITVERSION:=$(shell git rev-parse HEAD)
+
+all: $(DEB)
+
+DEB=${PACKAGE}_${PKGVER}_${ARCH}.deb
+
+.PHONY: deb
+deb: $(DEB)
+$(DEB):
+       rm -rf build
+       rsync -a ./src/* build/
+       rsync -a ./debian build/
+       echo "git clone git://git.proxmox.com/git/pve-journalreader.git\\ngit 
checkout $(GITVERSION)" > build/debian/SOURCE
+       cd build; dpkg-buildpackage -b -us -uc
+       lintian $(DEB)
+
+.PHONY: clean
+clean:
+       rm -rf build/ *.deb *.buildinfo *.changes
diff --git a/debian/changelog b/debian/changelog
new file mode 100644
index 0000000..7fed8d8
--- /dev/null
+++ b/debian/changelog
@@ -0,0 +1,5 @@
+pve-journalreader (1.0-1) unstable; urgency=medium
+
+  *  initial package
+
+ -- Proxmox Support Team <supp...@proxmox.com>  Thu, 09 May 2019 10:47:13 +0200
diff --git a/debian/compat b/debian/compat
new file mode 100644
index 0000000..f599e28
--- /dev/null
+++ b/debian/compat
@@ -0,0 +1 @@
+10
diff --git a/debian/control b/debian/control
new file mode 100644
index 0000000..4dff950
--- /dev/null
+++ b/debian/control
@@ -0,0 +1,13 @@
+Source: pve-journalreader
+Section: admin
+Priority: extra
+Maintainer: Proxmox Support Team <supp...@proxmox.com>
+Build-Depends: debhelper (>= 10~), libsystemd-dev
+Standards-Version: 3.9.8
+
+Package: pve-journalreader
+Architecture: any
+Depends: ${misc:Depends}, ${shlibs:Depends}
+Description: Minimal Journal Reader
+ A minimal application to read the last X lines of the journal or the last X
+ lines before a cursor.
diff --git a/debian/copyright b/debian/copyright
new file mode 100644
index 0000000..88adfd7
--- /dev/null
+++ b/debian/copyright
@@ -0,0 +1,22 @@
+Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+
+Files: *
+Copyright: 2019 Proxmox Server Solutions GmbH
+License: GPL-2+
+ 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.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+ On Debian systems, the full text of the GNU General Public
+ License version 2 can be found in the file
+ `/usr/share/common-licenses/GPL-2'.
diff --git a/debian/rules b/debian/rules
new file mode 100755
index 0000000..2a6e77d
--- /dev/null
+++ b/debian/rules
@@ -0,0 +1,9 @@
+#!/usr/bin/make -f
+# See debhelper(7) (uncomment to enable)
+# output every command that modifies files on the build system.
+#DH_VERBOSE = 1
+
+
+%:
+       dh $@
+
diff --git a/debian/source/format b/debian/source/format
new file mode 100644
index 0000000..163aaf8
--- /dev/null
+++ b/debian/source/format
@@ -0,0 +1 @@
+3.0 (quilt)
diff --git a/src/Makefile b/src/Makefile
new file mode 100644
index 0000000..7bef470
--- /dev/null
+++ b/src/Makefile
@@ -0,0 +1,23 @@
+PROGRAM=journalreader
+SOURCES=journalreader.c
+
+LIBS := libsystemd
+CFLAGS += $(shell pkg-config --cflags ${LIBS})
+LFLAGS += $(shell pkg-config --libs ${LIBS})
+
+all: ${PROGRAM}
+
+${PROGRAM}: ${SOURCES}
+       gcc -Werror -Wall -Wl,-z,relro -Wtype-limits $< -o $@ ${CLFAGS} 
${LFLAGS} -g
+
+.PHONY: install
+install: ${PROGRAM}
+       mkdir -p ${DESTDIR}/usr/bin
+       install -m 0755 ${PROGRAM} ${DESTDIR}/usr/bin
+
+.PHONY: distclean
+distclean: clean
+
+.PHONY: clean
+clean:
+       rm -rf ${PROGRAM}
diff --git a/src/journalreader.c b/src/journalreader.c
new file mode 100644
index 0000000..56d0018
--- /dev/null
+++ b/src/journalreader.c
@@ -0,0 +1,360 @@
+/*
+
+    Copyright (C) 2019 Proxmox Server Solutions GmbH
+
+    Copyright: journalreader is under GNU GPL, the GNU General Public License.
+
+    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; version 2 dated June, 1991.
+
+    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
+    02111-1307, USA.
+
+    Author: Dominik Csapak <d.csa...@proxmox.com>
+
+*/
+
+#include <errno.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <systemd/sd-journal.h>
+#include <time.h>
+#include <unistd.h>
+
+#define BUFSIZE 4096
+
+static char buf[BUFSIZE+1];
+static size_t offset = 0;
+
+uint64_t convert_argument(const char *argument) {
+    errno = 0;
+    char * end;
+    uint64_t value = strtoull(argument, &end, 10);
+    if (errno != 0 || *end != '\0') {
+       fprintf(stderr, "%s is not a valid number\n", argument);
+       exit(1);
+    }
+
+    return value;
+}
+
+uint64_t get_timestamp(sd_journal *j) {
+    uint64_t timestamp;
+    int r = sd_journal_get_realtime_usec(j, &timestamp);
+    if (r < 0) {
+       fprintf(stderr, "Failed  %s\n", strerror(-r));
+       return 0xFFFFFFFFFFFFFFFF;
+    }
+    return timestamp;
+}
+
+void print_to_buf(const char * string, uint32_t length) {
+    if (!length) {
+       return;
+    }
+    size_t string_offset = 0;
+    size_t remaining = length;
+    while (offset + remaining > BUFSIZE) {
+       strncpy(buf+offset, string+string_offset, BUFSIZE-offset);
+       string_offset += BUFSIZE-offset;
+       remaining = length - string_offset;
+       write (1, buf, BUFSIZE);
+       offset = 0;
+    }
+    strncpy(buf+offset, string+string_offset, remaining);
+    offset += remaining;
+}
+
+bool printed_first_cursor = false;
+
+void print_cursor(sd_journal *j) {
+    int r;
+    char *cursor = NULL;
+    r = sd_journal_get_cursor(j, &cursor);
+    if (r < 0) {
+       fprintf(stderr, "Failed to get cursor: %s\n", strerror(-r));
+       exit(1);
+    }
+    print_to_buf(cursor, strlen(cursor));
+    print_to_buf("\n", 1);
+    free(cursor);
+}
+
+void print_first_cursor(sd_journal *j) {
+    if (!printed_first_cursor) {
+       print_cursor(j);
+       printed_first_cursor = true;
+    }
+}
+
+static uint64_t last_timestamp;
+static char timestring[16];
+static char bootid[32];
+
+void print_reboot(sd_journal *j) {
+    const char *d;
+    size_t l;
+    int r = sd_journal_get_data(j, "_BOOT_ID", (const void **)&d, &l);
+    if (r < 0) {
+       fprintf(stderr, "Failed  %s\n", strerror(-r));
+       return;
+    }
+
+    // remove '_BOOT_ID='
+    d += 9;
+    l -= 9;
+
+    if (bootid[0] != '\0') { // we have some bootid
+       if (strncmp(bootid, d, l)) { // a new bootid found
+           strncpy(bootid, d, l);
+           print_to_buf("-- Reboot --\n", 13);
+       }
+    } else {
+           strncpy(bootid, d, l);
+    }
+}
+
+void print_timestamp(sd_journal *j) {
+    uint64_t timestamp;
+    int r = sd_journal_get_realtime_usec(j, &timestamp);
+    if (r < 0) {
+       fprintf(stderr, "Failed  %s\n", strerror(-r));
+       return;
+    }
+
+    if (timestamp >= (last_timestamp+(1000*1000))) {
+       timestamp = timestamp / (1000*1000); // usec to sec
+       struct tm time;
+       localtime_r((time_t *)&timestamp, &time);
+       strftime(timestring, 16, "%b %d %T", &time);
+       last_timestamp = timestamp;
+    }
+
+    print_to_buf(timestring, 15);
+}
+
+void print_pid(sd_journal *j) {
+    const char *d;
+    size_t l;
+    int r = sd_journal_get_data(j, "_PID", (const void **)&d, &l);
+    if (r < 0) {
+       // we sometimes have no pid
+       return;
+    }
+
+    // remove '_PID='
+    d += 5;
+    l -= 5;
+
+    print_to_buf("[", 1);
+    print_to_buf(d, l);
+    print_to_buf("]", 1);
+}
+
+bool print_field(sd_journal *j, const char *field) {
+    const char *d;
+    size_t l;
+    int r = sd_journal_get_data(j, field, (const void **)&d, &l);
+    if (r < 0) {
+       // some fields do not exists
+       return false;
+    }
+
+    int fieldlen = strlen(field)+1;
+    d += fieldlen;
+    l -= fieldlen;
+    print_to_buf(d, l);
+    return true;
+}
+
+
+void print_line(sd_journal *j) {
+    print_reboot(j);
+    print_timestamp(j);
+    print_to_buf(" ", 1);
+    print_field(j, "_HOSTNAME");
+    print_to_buf(" ", 1);
+    if (!print_field(j, "SYSLOG_IDENTIFIER") &&
+       !print_field(j, "_COMM")) {
+       print_to_buf("unknown", strlen("unknown") - 1);
+    }
+    print_pid(j);
+    print_to_buf(": ", 2);
+    print_field(j, "MESSAGE");
+    print_to_buf("\n", 1);
+}
+
+void usage(char *progname) {
+    fprintf(stderr, "usage: %s [OPTIONS]\n", progname);
+    fprintf(stderr, "  -b begin\tbegin at this epoch\n");
+    fprintf(stderr, "  -e end\tend at this epoch\n");
+    fprintf(stderr, "  -d directory\tpath to journal directory\n");
+    fprintf(stderr, "  -n number\tprint the last number entries\n");
+    fprintf(stderr, "  -f from\tprint from this cursor\n");
+    fprintf(stderr, "  -t to\tprint to this cursor\n");
+    fprintf(stderr, "  -h \t\tthis help\n");
+    fprintf(stderr, "\n");
+    fprintf(stderr, "giving a range conflicts with -n\n");
+    fprintf(stderr, "-b and -f conflict\n");
+    fprintf(stderr, "-e and -t conflict\n");
+}
+
+int main(int argc, char *argv[]) {
+    uint64_t number = 0;
+    const char *directory = NULL;
+    const char *startcursor = NULL;
+    const char *endcursor = NULL;
+    uint64_t begin = 0;
+    uint64_t end = 0;
+    char c;
+
+    while ((c = getopt (argc, argv, "b:e:d:n:f:t:h")) != -1) {
+       switch (c) {
+           case 'b':
+               begin = convert_argument(optarg);
+               begin = begin*1000*1000;
+               break;
+           case 'e':
+               end = convert_argument(optarg);
+               end = end*1000*1000;
+               break;
+           case 'd':
+               directory = optarg;
+               break;
+           case 'n':
+               number = convert_argument(optarg);
+               break;
+           case 'f':
+               startcursor = optarg;
+               break;
+           case 't':
+               endcursor = optarg;
+               break;
+           case 'h':
+               usage(argv[0]);
+               exit(0);
+               break;
+           case '?':
+           default:
+               usage(argv[0]);
+               exit(1);
+       }
+    }
+
+    if (number && (begin || startcursor)) {
+       usage(argv[0]);
+       exit(1);
+    }
+
+    if (begin && startcursor) {
+       usage(argv[0]);
+       exit(1);
+    }
+
+    if (end && endcursor) {
+       usage(argv[0]);
+       exit(1);
+    }
+
+    if (argc > optind) {
+       usage(argv[0]);
+       exit(1);
+    }
+
+    // to prevent calling it everytime we generate a timestamp
+    tzset();
+
+    int r;
+    sd_journal *j;
+    if (directory == NULL) {
+       r = sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY);
+    } else {
+       r = sd_journal_open_directory(&j, directory, 0);
+    }
+
+    if (r < 0) {
+       fprintf(stderr, "Failed to open journal: %s\n", strerror(-r));
+       return 1;
+    }
+
+    // if we want to print the last x entries, seek to cursor or end,
+    // then x entries back, print the cursor and finally print the
+    // entries until end or cursor
+    if (number) {
+       if (end) {
+           r = sd_journal_seek_realtime_usec(j, end);
+       } else if (endcursor != NULL) {
+           r = sd_journal_seek_cursor(j, endcursor);
+           number++;
+       } else {
+           r = sd_journal_seek_tail(j);
+       }
+
+       if (r < 0) {
+           fprintf(stderr, "Failed to seek to end/cursor: %s\n", strerror(-r));
+           exit(1);
+       }
+
+       // seek back number entries and print cursor
+       r = sd_journal_previous_skip(j, number + 1);
+       if (r < 0) {
+           fprintf(stderr, "Failed to seek back: %s\n", strerror(-r));
+           exit(1);
+       }
+    } else {
+       if (begin) {
+           r = sd_journal_seek_realtime_usec(j, begin);
+       } else if (startcursor) {
+           r = sd_journal_seek_cursor(j, startcursor);
+       } else {
+           r = sd_journal_seek_head(j);
+       }
+
+       if (r < 0) {
+           fprintf(stderr, "Failed to seek to begin/cursor: %s\n", 
strerror(-r));
+           exit(1);
+       }
+
+       // if we have a start cursor, we want to skip the first entry
+       if (startcursor) {
+           r = sd_journal_next(j);
+           if (r < 0) {
+               fprintf(stderr, "Failed to seek to begin/cursor: %s\n", 
strerror(-r));
+               exit(1);
+           }
+           print_first_cursor(j);
+       }
+    }
+
+
+    while ((r = sd_journal_next(j)) > 0 && (end == 0 || get_timestamp(j) < 
end)) {
+       print_first_cursor(j);
+       if (endcursor != NULL && sd_journal_test_cursor(j, endcursor)) {
+           break;
+       }
+       print_line(j);
+    }
+
+    // print optional reboot
+    print_reboot(j);
+
+    // print final cursor
+    print_cursor(j);
+
+    // print remaining buffer
+    write(1, buf, offset);
+    sd_journal_close(j);
+
+    return 0;
+}
+
-- 
2.11.0


_______________________________________________
pve-devel mailing list
pve-devel@pve.proxmox.com
https://pve.proxmox.com/cgi-bin/mailman/listinfo/pve-devel

Reply via email to