diff --git a/sys/src/cmd/watch/mkfile b/sys/src/cmd/watch/mkfile new file mode 100644 index 000000000..bb5185280 --- /dev/null +++ b/sys/src/cmd/watch/mkfile @@ -0,0 +1,14 @@ + +#include +#include + +typedef struct List List; +struct List { + List *next; + union { + Dir; + Reprog *re; + }; +}; + +char pwd[1024]; +int period = 1000; +int noregroup = 0; +int once = 0; +List *expl; +List *filel; + +void +usage(void) +{ + fprint(2, "usage: %s [-G] [[-e expr] ...] [-t sec] [cmd]\n", argv0); + exits("usage"); +} + +void* +emalloc(ulong sz) +{ + void *v = malloc(sz); + if(v == nil) + sysfatal("malloc: %r"); + setmalloctag(v, getcallerpc(&sz)); + memset(v, 0, sz); + return v; +} +char* +estrdup(char *s) +{ + if((s = strdup(s)) == nil) + sysfatal("strdup: %r"); + setmalloctag(s, getcallerpc(&s)); + return s; +} + +char* +join(char **arg) +{ + int i; + char *s, *p; + + s = estrdup(""); + for(i = 0; arg[i]; i++){ + p = s; + if((s = smprint("%s %s", s, arg[i])) == nil) + sysfatal("smprint: %r"); + free(p); + } + return s; +} + +void +eadd(char *exp) +{ + List *r; + + r = emalloc(sizeof(*r)); + r->re = regcomp(exp); + r->next = expl; + expl = r; +} + +void +fadd(Dir *d) +{ + List *f; + + f = emalloc(sizeof(*f)); + f->Dir = *d; + f->next = filel; + filel = f; +} + +int +tracked(char *name) +{ + List *r; + + for(r = expl; r; r = r->next) + if(regexec(r->re, name, nil, 0)) + return 1; + return 0; +} + +int +changed(Dir *d) +{ + List *f; + + for(f = filel; f; f = f->next) + if(f->type == d->type) + if(f->dev == d->dev) + if(f->qid.path == d->qid.path) + if(f->qid.type == d->qid.type) + if(f->qid.vers == d->qid.vers) + return 0; + else{ + f->Dir = *d; + return 1; + } + fadd(d); + return 1; +} + +void +watch(void) +{ + static int first = 1; + long fd, n; + Dir *d, *p; + + for(;;){ + sleep(period); + if((fd = open(pwd, OREAD)) < 0) + sysfatal("open: %r"); + if((n = dirreadall(fd, &d)) < 0) + sysfatal("dirreadall: %r"); + close(fd); + for(p = d; n--; p++){ + if(tracked(p->name)){ + if(first){ + fadd(p); + continue; + } + if(changed(p)){ + free(d); + return; + } + } + } + first = 0; + free(d); + } +} + +void +rc(char *cmd) +{ + Waitmsg *m; + + switch(fork()){ + case -1: sysfatal("fork: %r"); + case 0: + execl("/bin/rc", "rc", "-c", cmd, nil); + sysfatal("execl: %r"); + } + if((m = wait()) && m->msg[0]) + fprint(2, "watch: %s\n", m->msg); + free(m); +} + +void +regroup(void) +{ + char *cmd; + + cmd = smprint("cat /proc/%d/noteid >/proc/%d/noteid", + getppid(), getpid()); + rc(cmd); + free(cmd); +} + +void +main(int argc, char *argv[]) +{ + int t; + char *unit; + char *cmd; + + cmd = "mk"; + ARGBEGIN{ + case 'e': + eadd(EARGF(usage())); break; + case 't': + t = strtol(EARGF(usage()), &unit, 10); + if(t < 0) + t = -t; + if(unit != nil && strncmp(unit, "ms", 2) == 0) + period = t; + else + period = t*1000; + break; + case 'G': + noregroup = 1; break; + case '1': + once = 1; break; + default: usage(); + }ARGEND; + if(expl == nil) + eadd("\.[chsy]$"); + if(argc > 0) + cmd = join(argv); + if(getwd(pwd, sizeof pwd) == nil) + sysfatal("getwd: %r"); + + if(noregroup == 0) regroup(); + for(;;){ + watch(); + rc(cmd); + if(once) exits(nil); + } +} diff --git a/sys/src/cmd/watch/watch.man b/sys/src/cmd/watch/watch.man new file mode 100644 index 000000000..45f9d7a25 --- /dev/null +++ b/sys/src/cmd/watch/watch.man @@ -0,0 +1,68 @@ +.TH WATCH 1 +.SH NAME +watch \- run a command on file change +.SH SYNOPSIS +.B watch +[ +.B -G +] [ +.B -t +sec +] [[ +.B -e +pattern +] ... ] [ +.B command +] +.SH DESCRIPTION +.PP +Run +.IR command +.RB ( mk +by default) when a change to files +in the current directory is detected. +.PP +The options are as follows: +.TF "-e pattern" +.TP +.BI -e pattern +Watch files matching a +.IR pattern ; +it may be given multiple times and +defaults to +.BR \e.[chsy]$ +.TP +.BI -t period +Sets the polling +.IR period +in seconds (one second by default). +\'ms' can be appended to the value +to specify time in miliseconds. +.TP +.B -G +Prevents regrouping to the parent +process' note group. The default +to regroup was chosen to make it +easy to kill +.I watch +in common use by interrupting the +parent shell. +.SH EXAMPLES +.EX +watch -e '\e.man$' 'mk install; window man -P prog' & +.EE +.SH SEE ALSO +.SH SOURCE +.B git://code.a-b.xyz/watch +.SH BUGS +.I Watch +will not react on file removal. +.PP +.I Qid.vers +is watched for changes, which is +not maintained by every file server. +.PP +The polling period is actually +.I sec +plus the time it takes to run +.IR command .