Files
smeserver-mailstats/journalwrap.c

180 lines
5.7 KiB
C
Raw Normal View History

2025-09-03 11:00:00 +01:00
#include <systemd/sd-journal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <errno.h>
#include <time.h>
#ifndef MAX_OUTPUT_BYTES
#define MAX_OUTPUT_BYTES (2 * 1000 * 1000) // 2 MB
#endif
static int append_bytes(char **buf, size_t *len, size_t *cap, const char *src, size_t n) {
if (*len + n + 1 > *cap) {
size_t newcap = (*cap == 0) ? 8192 : *cap;
while (*len + n + 1 > newcap) {
newcap *= 2;
if (newcap > (size_t)(MAX_OUTPUT_BYTES + 65536)) {
newcap = (size_t)(MAX_OUTPUT_BYTES + 65536);
break;
}
}
char *nbuf = realloc(*buf, newcap);
if (!nbuf) return -1;
*buf = nbuf; *cap = newcap;
}
memcpy(*buf + *len, src, n);
*len += n;
(*buf)[*len] = '\0';
return 0;
}
static int append_cstr(char **buf, size_t *len, size_t *cap, const char *s) {
return append_bytes(buf, len, cap, s, strlen(s));
}
static size_t min_size(size_t a, size_t b) { return a < b ? a : b; }
static void sanitize_text(char *s, size_t n) {
for (size_t i = 0; i < n; i++) if (s[i] == '\0') s[i] = ' ';
}
static void format_ts(char *out, size_t outsz, uint64_t usec) {
time_t sec = (time_t)(usec / 1000000ULL);
struct tm tm;
localtime_r(&sec, &tm);
strftime(out, outsz, "%Y-%m-%d %H:%M:%S", &tm);
}
static const char* field_value(const void *data, size_t len, const char *key, size_t *vlen) {
size_t klen = strlen(key);
if (len < klen + 1) return NULL;
const char *p = (const char *)data;
if (memcmp(p, key, klen) != 0 || p[klen] != '=') return NULL;
*vlen = len - (klen + 1);
return p + klen + 1;
}
static int append_entry_line(sd_journal *j, char **buf, size_t *len, size_t *cap) {
uint64_t usec = 0;
(void)sd_journal_get_realtime_usec(j, &usec);
char ts[32];
format_ts(ts, sizeof(ts), usec);
const void *data = NULL;
size_t dlen = 0;
const char *message = NULL;
size_t mlen = 0;
int r = sd_journal_get_data(j, "MESSAGE", &data, &dlen);
if (r >= 0) message = field_value(data, dlen, "MESSAGE", &mlen);
const char *ident = NULL;
size_t ilen = 0;
r = sd_journal_get_data(j, "SYSLOG_IDENTIFIER", &data, &dlen);
if (r >= 0) {
ident = field_value(data, dlen, "SYSLOG_IDENTIFIER", &ilen);
} else if (sd_journal_get_data(j, "_COMM", &data, &dlen) >= 0) {
ident = field_value(data, dlen, "_COMM", &ilen);
}
if (append_cstr(buf, len, cap, "[") < 0) return -1;
if (append_cstr(buf, len, cap, ts) < 0) return -1;
if (append_cstr(buf, len, cap, "] ") < 0) return -1;
if (ident && ilen > 0) {
if (append_bytes(buf, len, cap, ident, ilen) < 0) return -1;
if (append_cstr(buf, len, cap, ": ") < 0) return -1;
}
if (message && mlen > 0) {
char *tmp = malloc(mlen);
if (!tmp) return -1;
memcpy(tmp, message, mlen);
sanitize_text(tmp, mlen);
size_t to_copy = min_size(mlen, (size_t)(MAX_OUTPUT_BYTES > *len ? MAX_OUTPUT_BYTES - *len : 0));
int ok = append_bytes(buf, len, cap, tmp, to_copy);
free(tmp);
if (ok < 0) return -1;
} else {
const char *keys[] = {"PRIORITY","SYSLOG_IDENTIFIER","_COMM","_EXE","_CMDLINE","MESSAGE"};
for (size_t i = 0; i < sizeof(keys)/sizeof(keys[0]); i++) {
if (sd_journal_get_data(j, keys[i], &data, &dlen) < 0) continue;
if (append_cstr(buf, len, cap, (i == 0 ? "" : " ")) < 0) return -1;
if (append_bytes(buf, len, cap, (const char*)data, min_size(dlen, (size_t)(MAX_OUTPUT_BYTES - *len))) < 0) return -1;
}
}
if (*len < MAX_OUTPUT_BYTES) {
if (append_cstr(buf, len, cap, "\n") < 0) return -1;
}
return 0;
}
static char* journal_get_by_pid_impl(int pid) {
if (pid <= 0) { char *z = malloc(1); if (z) z[0] = '\0'; return z; }
sd_journal *j = NULL;
if (sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY) < 0) {
char *z = malloc(1); if (z) z[0] = '\0'; return z;
}
char match[64];
snprintf(match, sizeof(match), "_PID=%d", pid);
if (sd_journal_add_match(j, match, 0) < 0) {
sd_journal_close(j);
char *z = malloc(1); if (z) z[0] = '\0'; return z;
}
sd_journal_seek_head(j);
char *buf = NULL; size_t len = 0, cap = 0;
int r;
while ((r = sd_journal_next(j)) > 0) {
if (len >= MAX_OUTPUT_BYTES) break;
if (append_entry_line(j, &buf, &len, &cap) < 0) {
free(buf); sd_journal_close(j); return NULL;
}
}
if (len >= MAX_OUTPUT_BYTES) {
const char *trunc = "[output truncated]\n";
(void)append_bytes(&buf, &len, &cap, trunc, strlen(trunc));
}
if (!buf) { buf = malloc(1); if (!buf) { sd_journal_close(j); return NULL; } buf[0] = '\0'; }
sd_journal_close(j);
return buf;
}
#ifdef __GNUC__
__attribute__((visibility("default")))
#endif
char* journal_get_by_pid(int pid) { return journal_get_by_pid_impl(pid); }
#ifdef __GNUC__
__attribute__((visibility("default")))
#endif
void journal_free(char* p) { free(p); }
#ifdef BUILD_CLI
static int parse_pid(const char *s, int *out) {
if (!s || !*s) return -1;
char *end = NULL;
errno = 0;
long v = strtol(s, &end, 10);
if (errno != 0 || end == s || *end != '\0' || v <= 0 || v > 0x7fffffffL) return -1;
*out = (int)v; return 0;
}
int main(int argc, char **argv) {
if (argc != 2) { fprintf(stderr, "Usage: %s <pid>\n", argv[0]); return 2; }
int pid = 0;
if (parse_pid(argv[1], &pid) != 0) { fprintf(stderr, "Invalid pid\n"); return 2; }
char *out = journal_get_by_pid_impl(pid);
if (!out) { fprintf(stderr, "Out of memory or error\n"); return 1; }
fputs(out, stdout);
free(out);
return 0;
}
#endif