[pve-devel] [RFC pve-journalreader 1/1] inital commit for journalreader
Dominik Csapak
d.csapak at proxmox.com
Mon May 13 14:49:18 CEST 2019
provides a minimalistic binary to show the journal content
shows a cursor after and before the output
Signed-off-by: Dominik Csapak <d.csapak at 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 <support at 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 <support at 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.csapak at 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, ×tamp);
+ 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, ×tamp);
+ 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 *)×tamp, &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
More information about the pve-devel
mailing list