summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--Makefile39
-rw-r--r--args.h22
-rw-r--r--logwatch.8.man41
-rw-r--r--main.c227
-rw-r--r--readme.md3
6 files changed, 334 insertions, 0 deletions
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 <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdarg.h>
+#include <limits.h>
+#include <sys/types.h>
+#include <sys/file.h>
+#include <sys/stat.h>
+#include <sys/inotify.h>
+#include <sys/statvfs.h>
+#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/.