[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, &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





More information about the pve-devel mailing list