implement Rotate; include "sys.m"; sys: Sys; sprint: import sys; include "draw.m"; include "arg.m"; arg: Arg; include "bufio.m"; bufio: Bufio; include "string.m"; str: String; include "daytime.m"; daytime: Daytime; Rotate: module { init: fn(nil: ref Draw->Context, nil: list of string); }; Imonth, Iday, Ihour: con iota; itab := array[] of { ("month", Imonth), ("day", Iday), ("hour", Ihour), }; progname: string; devtime: ref Sys->FD; init(nil: ref Draw->Context, args: list of string) { sys = load Sys Sys->PATH; arg = load Arg Arg->PATH; bufio = load Bufio Bufio->PATH; str = load String String->PATH; daytime = load Daytime Daytime->PATH; arg->init(args); progname = arg->progname(); arg->setusage(sprint("%s interval logdir [suffix]", progname)); if(arg->opt() != '\0') arg->usage(); args = arg->argv(); suffix := ""; case len args { 2 => ; 3 => suffix = hd tl tl args; * => arg->usage(); } if((interval := findinterval(hd args)) < 0) fail("unknown interval: " + hd args); logdir := hd tl args; progname = arg->progname() + " " + logdir; arg = nil; if((devtime = sys->open("/dev/time", Sys->OREAD)) == nil) fail(sprint("open /dev/time: %r")); if((err := direxists(logdir)) != nil) fail(sprint("%s: %s", logdir, err)); rotate(sys->fildes(0), logdir, suffix, interval); } findinterval(s: string): int { for(i := 0; i < len itab; i++) { (k, v) := itab[i]; if(k == s) return v; } return -1; } direxists(path: string): string { (err, dir) := sys->stat(path); if(err != 0) return sys->sprint("%r"); if((dir.mode & Sys->DMDIR) == 0) return "not a directory"; return nil; } fail(msg: string) { (fmsg, nil) := str->splitl(msg, ":"); warn(msg); raise "fail:" + fmsg; } warn(msg: string) { sys->fprint(sys->fildes(2), "%s: %s\n", progname, msg); } getseconds(): int { buf := array[32] of byte; n := sys->pread(devtime, buf, len buf, big 0); if(n < 0) { warn(sprint("read /dev/time: %r")); return 0; } return int (big string buf[:n] / big 1e6); } tm2filename(tm: ref Daytime->Tm): string { return sprint("%04d-%02d-%02d_%02d.%02d.%02d", tm.year + 1900, tm.mon + 1, tm.mday, tm.hour, tm.min, tm.sec ); } nextsplit(now: int, interval: int): int { tm := daytime->local(now); case interval { Imonth => tm.mon++; tm.mday = 0; tm.hour = 0; tm.min = 0; tm.sec = 0; Iday => tm.mday++; tm.hour = 0; tm.min = 0; tm.sec = 0; Ihour => tm.hour++; tm.min = 0; tm.sec = 0; * => raise "assertion:bad programming"; } # tm2epoch will account for an overflow of mday past its # dmsize, but not a change in timezone return daytime->tm2epoch(tm); } rotate(fd: ref Sys->FD, dir, suffix: string, interval: int) { splitat := getseconds(); name: string; log: ref Sys->FD; buf := array[Sys->ATOMICIO] of byte; while((n := sys->read(fd, buf, len buf)) > 0) { now := getseconds(); if(now >= splitat) { name = dir + "/" + tm2filename(daytime->local(splitat)) + suffix; if((f := sys->create(name, Sys->OWRITE, 8r666)) == nil) { msg := sprint("create %s: %r", name); if(log == nil) fail(msg); else warn(msg); } log = f; splitat = nextsplit(now, interval); } if(sys->write(log, buf, n) != n) fail(sprint("write: %r")); } if(n < 0) fail(sprint("read: %r")); }