From e2e807dd68ad1e0708e8a2ab8f81c64da7695456 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20G=2E=20L=C3=B3pez?= Date: Wed, 26 Aug 2020 11:00:11 +0200 Subject: initial release. --- .gitignore | 2 + Makefile | 39 ++++++++++ args.h | 22 ++++++ logwatch.8.man | 41 +++++++++++ main.c | 227 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ readme.md | 3 + 6 files changed, 334 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 args.h create mode 100644 logwatch.8.man create mode 100644 main.c create mode 100644 readme.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c478614 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.hg/ +.hgignore diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..35ac87d --- /dev/null +++ b/Makefile @@ -0,0 +1,39 @@ +CC=cc +CFLAGS=-Wall -Wno-missing-braces -Wno-parentheses -Wno-switch -Wno-pointer-to-int-cast -fno-diagnostics-color -ggdb -c -O2 +LDFLAGS=-static +O=o +BIN=$(HOME)/bin + +TARG=logwatch +OFILES=\ + main.$O\ + +LIBS=\ + libutf/libutf.a\ + +HFILES=\ + args.h\ + libutf/utf.h\ + +.PHONY: all clean +all: $(TARG) + +%.$O: %.c + $(CC) $(CFLAGS) $< + +$(OFILES): $(HFILES) + +$(TARG): $(OFILES) + $(CC) -o $@ $(OFILES) $(LIBS) $(LDFLAGS) + +install: $(TARG) + cp $(TARG) $(BIN)/ + +uninstall: + rm -f $(BIN)/$(TARG) + +man: + groff -man $(TARG).8.man | ps2pdf - >$(TARG).8.pdf + +clean: + rm $(TARG) *.o *.pdf diff --git a/args.h b/args.h new file mode 100644 index 0000000..f7e3d5f --- /dev/null +++ b/args.h @@ -0,0 +1,22 @@ +extern char *argv0; +#define SET(x) ((x)=0) +#define USED(x) if(x);else +#define ARGBEGIN for((argv0? 0: (argv0=*argv)),argv++,argc--;\ + argv[0] && argv[0][0]=='-' && argv[0][1];\ + argc--, argv++) {\ + char *_args, *_argt;\ + Rune _argc;\ + _args = &argv[0][1];\ + if(_args[0]=='-' && _args[1]==0){\ + argc--; argv++; break;\ + }\ + _argc = 0;\ + while(*_args && (_args += chartorune(&_argc, _args)))\ + switch(_argc) +#define ARGEND SET(_argt);USED(_argt); USED(_argc); USED(_args);}USED(argv); USED(argc); +#define ARGF() (_argt=_args, _args="",\ + (*_argt? _argt: argv[1]? (argc--, *++argv): 0)) +#define ARGC() _argc + +#define EARGF(x) (_argt=_args, _args="",\ + (*_argt? _argt: argv[1]? (argc--, *++argv): (x, (char*)0))) diff --git a/logwatch.8.man b/logwatch.8.man new file mode 100644 index 0000000..9613f5f --- /dev/null +++ b/logwatch.8.man @@ -0,0 +1,41 @@ +.TH LOGWATCH 8 +.SH NAME +logwatch \- log file stalker +.SH SYNOPSIS +.PP +.B logwatch +[ +.B -d +] [ +.B -t +.I perc +] +.I logfile +.SH DESCRIPTION +.I logwatch +uses +.I inotify (7) +watches to monitor changes in a specific +.IR logfile . +It also monitors its parent directory, so it can track local movements (e.g. log rotations). +If the +.I logfile +exceeds an established disk quota, it is truncated to half its size, keeping the latest data. +.PP +The flags are: +.TP +.B d +Enable debugging output. +.TP +.B t +Sets a percentage +.I perc +for the disk usage threshold the file cannot exceed. The available quota is 100\-perc%. +By default the threshold is set to 5%. +.SH SEE ALSO +.IR inotify (7), +.IR logrotate (8). +.SH BUGS +The heuristics used to clamp the file are faulty, if the disk is already filled to the threshold, the program assumes it's the log file's fault and truncates it. +.br +Slicing the file in half isn't necessarily the best approach. More disk usage statistics are needed to make an informed decision. diff --git a/main.c b/main.c new file mode 100644 index 0000000..adafc3b --- /dev/null +++ b/main.c @@ -0,0 +1,227 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "libutf/utf.h" +#include "args.h" + +#define nil NULL + +typedef unsigned long long uvlong; + +enum +{ + Wlogfile, + Wlogdir, + NWATCH, + + KB = 1024 +}; + +char *argv0; +int debug; + +void +sysfatal(char *s) +{ + perror(s); + exit(1); +} + +void +dprint(char *fmt, ...) +{ + va_list ap; + + if(!debug) + return; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); +} + +void* +emalloc(ulong n) +{ + void *p; + + p = malloc(n); + if(p == nil) + sysfatal("malloc"); + return p; +} + +char* +estrdup(char *s) +{ + char *d; + + d = strdup(s); + if(d == nil) + sysfatal("strdup"); + return d; +} + +void +lockfile(int fd) +{ + if(flock(fd, LOCK_EX) < 0) + sysfatal("flock locking"); +} + +void +unlockfile(int fd) +{ + if(flock(fd, LOCK_UN) < 0) + sysfatal("flock unlocking"); +} + +int +checkusage(char *f, struct statvfs *vfs, int thres, ulong *flen) +{ + struct stat st; + uvlong total, free, barrier; + + total = vfs->f_bsize*vfs->f_blocks; + free = vfs->f_bsize*vfs->f_bavail; + barrier = thres*total/100; + if(stat(f, &st) < 0) + sysfatal("stat"); + *flen = st.st_size; + dprint("total %llu\nfree %llu\nbarrier %llu (%d%%)\nlogsize %llu\n", + total, free, barrier, thres, *flen); + if(free > barrier) + return 0; + return -1; +} + +void +defcon1(char *f, ulong flen) +{ + char tmpf[] = "/tmp/logwatch.XXXXXX"; + char buf[128*KB]; + int fd, tfd, n; + + fd = open(f, O_RDWR); + if(fd < 0) + sysfatal("open"); + tfd = mkstemp(tmpf); + if(tfd < 0) + sysfatal("mkstemp"); + lockfile(fd); + if(lseek(fd, flen/2, SEEK_SET) < 0) + sysfatal("lseek [logfile]"); + while((n = read(fd, buf, sizeof buf)) > 0) + if(write(tfd, buf, n) != n) + sysfatal("write [tmpfile]"); + if(n < 0) + sysfatal("read [logfile]"); + if(ftruncate(fd, 0) < 0) + sysfatal("ftruncate"); + if(lseek(fd, 0, SEEK_SET) < 0) + sysfatal("lseek [logfile]"); + if(lseek(tfd, 0, SEEK_SET) < 0) + sysfatal("lseek [tmpfile]"); + while((n = read(tfd, buf, sizeof buf)) > 0) + if(write(fd, buf, n) != n) + sysfatal("write [logfile]"); + if(n < 0) + sysfatal("read [tmpfile]"); + unlockfile(fd); + close(tfd); + close(fd); + unlink(tmpf); +} + +void +usage(void) +{ + fprintf(stderr, "usage: %s [-d] [-t perc] logfile\n", argv0); + exit(1); +} + +int +main(int argc, char *argv[]) +{ + struct inotify_event *e; + struct statvfs vfs; + char buf[sizeof(struct inotify_event)+NAME_MAX+1], *logfile, *logdir, *p; + int fd, watches[NWATCH], n, elen; + int thres; + ulong cookie, logfilelen; + + thres = 5; + ARGBEGIN{ + case 'd': debug++; break; + case 't': + thres = strtol(EARGF(usage()), nil, 10); + break; + default: usage(); + }ARGEND; + if(argc != 1) + usage(); + if(thres < 0 || thres > 100) + thres = 5; + p = strrchr(argv[0], '/'); + if(p == nil) + logdir = getcwd(nil, 0); + else{ + logdir = emalloc(p-argv[0]+1); + strncpy(logdir, argv[0], p-argv[0]); + logdir[p-argv[0]] = 0; + } + logfile = estrdup(p? p+1: argv[0]); + fd = inotify_init(); + if(fd < 0) + sysfatal("inotify_init"); + watches[Wlogfile] = inotify_add_watch(fd, logfile, IN_MODIFY); + if(watches[Wlogfile] < 0) + sysfatal("inotify_add_watch [file]"); + watches[Wlogdir] = inotify_add_watch(fd, logdir, IN_MOVE); + if(watches[Wlogdir] < 0) + sysfatal("inotify_add_watch [dir]"); + dprint("file: %d\ndir: %d\n", watches[Wlogfile], watches[Wlogdir]); + cookie = 0; + while((n = read(fd, buf, sizeof buf)) >= 0){ +Decode: + e = (struct inotify_event*)buf; + if(e->wd == watches[Wlogfile]){ + if((e->mask & IN_MODIFY) != 0) + dprint("modified %s\n", logfile); + if((e->mask & IN_IGNORED) != 0){ + dprint("deleted %s\n", logfile); + exit(0); + } + if(statvfs(logdir, &vfs) < 0) + sysfatal("statvfs"); + if(checkusage(logfile, &vfs, thres, &logfilelen) < 0) + defcon1(logfile, logfilelen); + }else if(e->wd == watches[Wlogdir]){ + if((e->mask & IN_MOVED_FROM) != 0) + if(e->len > 0 && strcmp(e->name, logfile) == 0) + cookie = e->cookie; + if((e->mask & IN_MOVED_TO) != 0) + if(e->cookie == cookie && e->len > 0){ + dprint("rename from %s", logfile); + free(logfile); + logfile = estrdup(e->name); + dprint(" to %s\n", logfile); + } + } + elen = sizeof(struct inotify_event)+e->len; + if(n > elen){ + p = buf+elen; + memmove(buf, p, n-elen); + n -= elen; + goto Decode; + } + } + exit(0); +} diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..c41cc49 --- /dev/null +++ b/readme.md @@ -0,0 +1,3 @@ +# logwatch(8) + +It requires libutf from https://9fans.github.io/plan9port/unix/. -- cgit v1.2.3