plan9port

fork of plan9port with libvec, libstr and libsdb
Log | Files | Refs | README | LICENSE

mgr.c (18765B)


      1 /*
      2  * mirror manager.
      3  * a work in progress.
      4  * use at your own risk.
      5  */
      6 
      7 #include "stdinc.h"
      8 #include <regexp.h>
      9 #include <bio.h>
     10 #include "dat.h"
     11 #include "fns.h"
     12 
     13 #ifdef PLAN9PORT
     14 #define sp s.sp
     15 #define ep e.ep
     16 #endif
     17 
     18 void sendmail(char *content, char *subject, char *msg);
     19 #define TIME "[0-9]+/[0-9]+ [0-9]+:[0-9]+:[0-9]+"
     20 
     21 char *mirrorregexp =
     22 	"^" TIME " ("
     23 		"([^ ]+ \\([0-9,]+-[0-9,]+\\))"
     24 		"|(  copy [0-9,]+-[0-9,]+ (data|hole|directory|tail))"
     25 		"|(  sha1 [0-9,]+-[0-9,]+)"
     26 		"|([^ ]+: [0-9,]+ used mirrored)"
     27 		"|([^ \\-]+-[^ \\-]+( mirrored| sealed| empty)+)"
     28 	")$";
     29 Reprog *mirrorprog;
     30 
     31 char *verifyregexp =
     32 	"^" TIME " ("
     33 		"([^ ]+: unsealed [0-9,]+ bytes)"
     34 	")$";
     35 Reprog *verifyprog;
     36 
     37 #undef pipe
     38 enum
     39 {
     40 	LogSize = 4*1024*1024	// TODO: make smaller
     41 };
     42 
     43 VtLog *errlog;
     44 
     45 typedef struct Mirror Mirror;
     46 struct Mirror
     47 {
     48 	char *src;
     49 	char *dst;
     50 };
     51 
     52 typedef struct Conf Conf;
     53 struct Conf
     54 {
     55 	Mirror *mirror;
     56 	int nmirror;
     57 	char **verify;
     58 	int nverify;
     59 	char *httpaddr;
     60 	char *webroot;
     61 	char *smtp;
     62 	char *mailfrom;
     63 	char *mailto;
     64 	int mirrorfreq;
     65 	int verifyfreq;
     66 };
     67 
     68 typedef struct Job Job;
     69 struct Job
     70 {
     71 	char *name;
     72 	QLock lk;
     73 	char *argv[10];
     74 	int oldok;
     75 	int newok;
     76 	VtLog *oldlog;
     77 	VtLog *newlog;
     78 	int pid;
     79 	int pipe;
     80 	int nrun;
     81 	vlong freq;
     82 	vlong runstart;
     83 	vlong runend;
     84 	double offset;
     85 	int (*ok)(char*);
     86 };
     87 
     88 Job *job;
     89 int njob;
     90 char *bin;
     91 
     92 vlong time0;
     93 Conf conf;
     94 
     95 void
     96 usage(void)
     97 {
     98 	fprint(2, "usage: mgr [-s] [-b bin/venti/] venti.conf\n");
     99 	threadexitsall(0);
    100 }
    101 
    102 int
    103 rdconf(char *file, Conf *conf)
    104 {
    105 	char *s, *line, *flds[10];
    106 	int i, ok;
    107 	IFile f;
    108 
    109 	if(readifile(&f, file) < 0)
    110 		return -1;
    111 	memset(conf, 0, sizeof *conf);
    112 	ok = -1;
    113 	line = nil;
    114 	for(;;){
    115 		s = ifileline(&f);
    116 		if(s == nil){
    117 			ok = 0;
    118 			break;
    119 		}
    120 		line = estrdup(s);
    121 		i = getfields(s, flds, nelem(flds), 1, " \t\r");
    122 		if(i <= 0 || strcmp(flds[0], "mgr") != 0) {
    123 			/* do nothing */
    124 		}else if(i == 4 && strcmp(flds[1], "mirror") == 0) {
    125 			if(conf->nmirror%64 == 0)
    126 				conf->mirror = vtrealloc(conf->mirror, (conf->nmirror+64)*sizeof(conf->mirror[0]));
    127 			conf->mirror[conf->nmirror].src = vtstrdup(flds[2]);
    128 			conf->mirror[conf->nmirror].dst = vtstrdup(flds[3]);
    129 			conf->nmirror++;
    130 		}else if(i == 3 && strcmp(flds[1], "mirrorfreq") == 0) {
    131 			conf->mirrorfreq = atoi(flds[2]);
    132 		}else if(i == 3 && strcmp(flds[1], "verify") == 0) {
    133 			if(conf->nverify%64 == 0)
    134 				conf->verify = vtrealloc(conf->verify, (conf->nverify+64)*sizeof(conf->verify[0]));
    135 			conf->verify[conf->nverify++] = vtstrdup(flds[2]);
    136 		}else if(i == 3 && strcmp(flds[1], "verifyfreq") == 0) {
    137 			conf->verifyfreq = atoi(flds[2]);
    138 		}else if(i == 3 && strcmp(flds[1], "httpaddr") == 0){
    139 			if(conf->httpaddr){
    140 				seterr(EAdmin, "duplicate httpaddr lines in configuration file %s", file);
    141 				break;
    142 			}
    143 			conf->httpaddr = estrdup(flds[2]);
    144 		}else if(i == 3 && strcmp(flds[1], "webroot") == 0){
    145 			if(conf->webroot){
    146 				seterr(EAdmin, "duplicate webroot lines in configuration file %s", file);
    147 				break;
    148 			}
    149 			conf->webroot = estrdup(flds[2]);
    150 		}else if(i == 3 && strcmp(flds[1], "smtp") == 0) {
    151 			if(conf->smtp){
    152 				seterr(EAdmin, "duplicate smtp lines in configuration file %s", file);
    153 				break;
    154 			}
    155 			conf->smtp = estrdup(flds[2]);
    156 		}else if(i == 3 && strcmp(flds[1], "mailfrom") == 0) {
    157 			if(conf->mailfrom){
    158 				seterr(EAdmin, "duplicate mailfrom lines in configuration file %s", file);
    159 				break;
    160 			}
    161 			conf->mailfrom = estrdup(flds[2]);
    162 		}else if(i == 3 && strcmp(flds[1], "mailto") == 0) {
    163 			if(conf->mailto){
    164 				seterr(EAdmin, "duplicate mailto lines in configuration file %s", file);
    165 				break;
    166 			}
    167 			conf->mailto = estrdup(flds[2]);
    168 		}else{
    169 			seterr(EAdmin, "illegal line '%s' in configuration file %s", line, file);
    170 			break;
    171 		}
    172 		free(line);
    173 		line = nil;
    174 	}
    175 	free(line);
    176 	freeifile(&f);
    177 	return ok;
    178 }
    179 
    180 static QLock loglk;
    181 static char *logbuf;
    182 
    183 char*
    184 logtext(VtLog *l)
    185 {
    186 	int i;
    187 	char *p;
    188 	VtLogChunk *c;
    189 
    190 	p = logbuf;
    191 	c = l->w;
    192 	for(i=0; i<l->nchunk; i++) {
    193 		if(++c == l->chunk+l->nchunk)
    194 			c = l->chunk;
    195 		memmove(p, c->p, c->wp - c->p);
    196 		p += c->wp - c->p;
    197 	}
    198 	*p = 0;
    199 	return logbuf;
    200 }
    201 
    202 
    203 typedef struct HttpObj	HttpObj;
    204 
    205 static int fromwebdir(HConnect*);
    206 
    207 enum
    208 {
    209 	ObjNameSize	= 64,
    210 	MaxObjs		= 64
    211 };
    212 
    213 struct HttpObj
    214 {
    215 	char	name[ObjNameSize];
    216 	int	(*f)(HConnect*);
    217 };
    218 
    219 static HttpObj	objs[MaxObjs];
    220 static void httpproc(void*);
    221 
    222 static HConnect*
    223 mkconnect(void)
    224 {
    225 	HConnect *c;
    226 
    227 	c = mallocz(sizeof(HConnect), 1);
    228 	if(c == nil)
    229 		sysfatal("out of memory");
    230 	c->replog = nil;
    231 	c->hpos = c->header;
    232 	c->hstop = c->header;
    233 	return c;
    234 }
    235 
    236 static int
    237 preq(HConnect *c)
    238 {
    239 	if(hparseheaders(c, 0) < 0)
    240 		return -1;
    241 	if(strcmp(c->req.meth, "GET") != 0
    242 	&& strcmp(c->req.meth, "HEAD") != 0)
    243 		return hunallowed(c, "GET, HEAD");
    244 	if(c->head.expectother || c->head.expectcont)
    245 		return hfail(c, HExpectFail, nil);
    246 	return 0;
    247 }
    248 
    249 int
    250 hsettype(HConnect *c, char *type)
    251 {
    252 	Hio *hout;
    253 	int r;
    254 
    255 	r = preq(c);
    256 	if(r < 0)
    257 		return r;
    258 
    259 	hout = &c->hout;
    260 	if(c->req.vermaj){
    261 		hokheaders(c);
    262 		hprint(hout, "Content-type: %s\r\n", type);
    263 		if(http11(c))
    264 			hprint(hout, "Transfer-Encoding: chunked\r\n");
    265 		hprint(hout, "\r\n");
    266 	}
    267 
    268 	if(http11(c))
    269 		hxferenc(hout, 1);
    270 	else
    271 		c->head.closeit = 1;
    272 	return 0;
    273 }
    274 
    275 int
    276 hsethtml(HConnect *c)
    277 {
    278 	return hsettype(c, "text/html; charset=utf-8");
    279 }
    280 
    281 int
    282 hsettext(HConnect *c)
    283 {
    284 	return hsettype(c, "text/plain; charset=utf-8");
    285 }
    286 
    287 int
    288 hnotfound(HConnect *c)
    289 {
    290 	int r;
    291 
    292 	r = preq(c);
    293 	if(r < 0)
    294 		return r;
    295 	return hfail(c, HNotFound, c->req.uri);
    296 }
    297 
    298 static int
    299 xloglist(HConnect *c)
    300 {
    301 	if(hsettype(c, "text/html") < 0)
    302 		return -1;
    303 	vtloghlist(&c->hout);
    304 	hflush(&c->hout);
    305 	return 0;
    306 }
    307 
    308 static int
    309 strpcmp(const void *va, const void *vb)
    310 {
    311 	return strcmp(*(char**)va, *(char**)vb);
    312 }
    313 
    314 void
    315 vtloghlist(Hio *h)
    316 {
    317 	char **p;
    318 	int i, n;
    319 
    320 	hprint(h, "<html><head>\n");
    321 	hprint(h, "<title>Venti Server Logs</title>\n");
    322 	hprint(h, "</head><body>\n");
    323 	hprint(h, "<b>Venti Server Logs</b>\n<p>\n");
    324 
    325 	p = vtlognames(&n);
    326 	qsort(p, n, sizeof(p[0]), strpcmp);
    327 	for(i=0; i<n; i++)
    328 		hprint(h, "<a href=\"/log?log=%s\">%s</a><br>\n", p[i], p[i]);
    329 	vtfree(p);
    330 	hprint(h, "</body></html>\n");
    331 }
    332 
    333 void
    334 vtloghdump(Hio *h, VtLog *l)
    335 {
    336 	int i;
    337 	VtLogChunk *c;
    338 	char *name;
    339 
    340 	name = l ? l->name : "&lt;nil&gt;";
    341 
    342 	hprint(h, "<html><head>\n");
    343 	hprint(h, "<title>Venti Server Log: %s</title>\n", name);
    344 	hprint(h, "</head><body>\n");
    345 	hprint(h, "<b>Venti Server Log: %s</b>\n<p>\n", name);
    346 
    347 	if(l){
    348 		c = l->w;
    349 		for(i=0; i<l->nchunk; i++){
    350 			if(++c == l->chunk+l->nchunk)
    351 				c = l->chunk;
    352 			hwrite(h, c->p, c->wp-c->p);
    353 		}
    354 	}
    355 	hprint(h, "</body></html>\n");
    356 }
    357 
    358 
    359 char*
    360 hargstr(HConnect *c, char *name, char *def)
    361 {
    362 	HSPairs *p;
    363 
    364 	for(p=c->req.searchpairs; p; p=p->next)
    365 		if(strcmp(p->s, name) == 0)
    366 			return p->t;
    367 	return def;
    368 }
    369 
    370 static int
    371 xlog(HConnect *c)
    372 {
    373 	char *name;
    374 	VtLog *l;
    375 
    376 	name = hargstr(c, "log", "");
    377 	if(!name[0])
    378 		return xloglist(c);
    379 	l = vtlogopen(name, 0);
    380 	if(l == nil)
    381 		return hnotfound(c);
    382 	if(hsettype(c, "text/html") < 0){
    383 		vtlogclose(l);
    384 		return -1;
    385 	}
    386 	vtloghdump(&c->hout, l);
    387 	vtlogclose(l);
    388 	hflush(&c->hout);
    389 	return 0;
    390 }
    391 
    392 static void
    393 httpdproc(void *vaddress)
    394 {
    395 	HConnect *c;
    396 	char *address, ndir[NETPATHLEN], dir[NETPATHLEN];
    397 	int ctl, nctl, data;
    398 
    399 	address = vaddress;
    400 	ctl = announce(address, dir);
    401 	if(ctl < 0){
    402 		sysfatal("announce %s: %r", address);
    403 		return;
    404 	}
    405 
    406 	if(0) print("announce ctl %d dir %s\n", ctl, dir);
    407 	for(;;){
    408 		/*
    409 		 *  wait for a call (or an error)
    410 		 */
    411 		nctl = listen(dir, ndir);
    412 		if(0) print("httpd listen %d %s...\n", nctl, ndir);
    413 		if(nctl < 0){
    414 			fprint(2, "mgr: httpd can't listen on %s: %r\n", address);
    415 			return;
    416 		}
    417 
    418 		data = accept(ctl, ndir);
    419 		if(0) print("httpd accept %d...\n", data);
    420 		if(data < 0){
    421 			fprint(2, "mgr: httpd accept: %r\n");
    422 			close(nctl);
    423 			continue;
    424 		}
    425 		if(0) print("httpd close nctl %d\n", nctl);
    426 		close(nctl);
    427 		c = mkconnect();
    428 		hinit(&c->hin, data, Hread);
    429 		hinit(&c->hout, data, Hwrite);
    430 		vtproc(httpproc, c);
    431 	}
    432 }
    433 
    434 static void
    435 httpproc(void *v)
    436 {
    437 	HConnect *c;
    438 	int ok, i, n;
    439 
    440 	c = v;
    441 
    442 	for(;;){
    443 		/*
    444 		 * No timeout because the signal appears to hit every
    445 		 * proc, not just us.
    446 		 */
    447 		if(hparsereq(c, 0) < 0)
    448 			break;
    449 
    450 		for(i = 0; i < MaxObjs && objs[i].name[0]; i++){
    451 			n = strlen(objs[i].name);
    452 			if((objs[i].name[n-1] == '/' && strncmp(c->req.uri, objs[i].name, n) == 0)
    453 			|| (objs[i].name[n-1] != '/' && strcmp(c->req.uri, objs[i].name) == 0)){
    454 				ok = (*objs[i].f)(c);
    455 				goto found;
    456 			}
    457 		}
    458 		ok = fromwebdir(c);
    459 	found:
    460 		hflush(&c->hout);
    461 		if(c->head.closeit)
    462 			ok = -1;
    463 		hreqcleanup(c);
    464 
    465 		if(ok < 0)
    466 			break;
    467 	}
    468 	hreqcleanup(c);
    469 	close(c->hin.fd);
    470 	free(c);
    471 }
    472 
    473 static int
    474 httpdobj(char *name, int (*f)(HConnect*))
    475 {
    476 	int i;
    477 
    478 	if(name == nil || strlen(name) >= ObjNameSize)
    479 		return -1;
    480 	for(i = 0; i < MaxObjs; i++){
    481 		if(objs[i].name[0] == '\0'){
    482 			strcpy(objs[i].name, name);
    483 			objs[i].f = f;
    484 			return 0;
    485 		}
    486 		if(strcmp(objs[i].name, name) == 0)
    487 			return -1;
    488 	}
    489 	return -1;
    490 }
    491 
    492 
    493 struct {
    494 	char *ext;
    495 	char *type;
    496 } exttab[] = {
    497 	".html",	"text/html",
    498 	".txt",	"text/plain",
    499 	".xml",	"text/xml",
    500 	".png",	"image/png",
    501 	".gif",	"image/gif",
    502 	0
    503 };
    504 
    505 static int
    506 fromwebdir(HConnect *c)
    507 {
    508 	char buf[4096], *p, *ext, *type;
    509 	int i, fd, n, defaulted;
    510 	Dir *d;
    511 
    512 	if(conf.webroot == nil || strstr(c->req.uri, ".."))
    513 		return hnotfound(c);
    514 	snprint(buf, sizeof buf-20, "%s/%s", conf.webroot, c->req.uri+1);
    515 	defaulted = 0;
    516 reopen:
    517 	if((fd = open(buf, OREAD)) < 0)
    518 		return hnotfound(c);
    519 	d = dirfstat(fd);
    520 	if(d == nil){
    521 		close(fd);
    522 		return hnotfound(c);
    523 	}
    524 	if(d->mode&DMDIR){
    525 		if(!defaulted){
    526 			defaulted = 1;
    527 			strcat(buf, "/index.html");
    528 			free(d);
    529 			close(fd);
    530 			goto reopen;
    531 		}
    532 		free(d);
    533 		return hnotfound(c);
    534 	}
    535 	free(d);
    536 	p = buf+strlen(buf);
    537 	type = "application/octet-stream";
    538 	for(i=0; exttab[i].ext; i++){
    539 		ext = exttab[i].ext;
    540 		if(p-strlen(ext) >= buf && strcmp(p-strlen(ext), ext) == 0){
    541 			type = exttab[i].type;
    542 			break;
    543 		}
    544 	}
    545 	if(hsettype(c, type) < 0){
    546 		close(fd);
    547 		return 0;
    548 	}
    549 	while((n = read(fd, buf, sizeof buf)) > 0)
    550 		if(hwrite(&c->hout, buf, n) < 0)
    551 			break;
    552 	close(fd);
    553 	hflush(&c->hout);
    554 	return 0;
    555 }
    556 
    557 static int
    558 hmanager(HConnect *c)
    559 {
    560 	Hio *hout;
    561 	int r;
    562 	int i, k;
    563 	Job *j;
    564 	VtLog *l;
    565 	VtLogChunk *ch;
    566 
    567 	r = hsethtml(c);
    568 	if(r < 0)
    569 		return r;
    570 
    571 	hout = &c->hout;
    572 	hprint(hout, "<html><head><title>venti mgr status</title></head>\n");
    573 	hprint(hout, "<body><h2>venti mgr status</h2>\n");
    574 
    575 	for(i=0; i<njob; i++) {
    576 		j = &job[i];
    577 		hprint(hout, "<b>");
    578 		if(j->nrun == 0)
    579 			hprint(hout, "----/--/-- --:--:--");
    580 		else
    581 			hprint(hout, "%+T", (long)(j->runstart + time0));
    582 		hprint(hout, " %s", j->name);
    583 		if(j->nrun > 0) {
    584 			if(j->newok == -1) {
    585 				hprint(hout, " (running)");
    586 			} else if(!j->newok) {
    587 				hprint(hout, " <font color=\"#cc0000\">(FAILED)</font>");
    588 			}
    589 		}
    590 		hprint(hout, "</b>\n");
    591 		hprint(hout, "<font size=-1><pre>\n");
    592 		l = j->newlog;
    593 		ch = l->w;
    594 		for(k=0; k<l->nchunk; k++){
    595 			if(++ch == l->chunk+l->nchunk)
    596 				ch = l->chunk;
    597 			hwrite(hout, ch->p, ch->wp-ch->p);
    598 		}
    599 		hprint(hout, "</pre></font>\n");
    600 		hprint(hout, "\n");
    601 	}
    602 	hprint(hout, "</body></html>\n");
    603 	hflush(hout);
    604 	return 0;
    605 }
    606 
    607 void
    608 piper(void *v)
    609 {
    610 	Job *j;
    611 	char buf[512];
    612 	VtLog *l;
    613 	int n;
    614 	int fd;
    615 	char *p;
    616 	int ok;
    617 
    618 	j = v;
    619 	fd = j->pipe;
    620 	l = j->newlog;
    621 	while((n = read(fd, buf, 512-1)) > 0) {
    622 		buf[n] = 0;
    623 		if(l != nil)
    624 			vtlogprint(l, "%s", buf);
    625 	}
    626 	qlock(&loglk);
    627 	p = logtext(l);
    628 	ok = j->ok(p);
    629 	qunlock(&loglk);
    630 	j->newok = ok;
    631 	close(fd);
    632 }
    633 
    634 void
    635 kickjob(Job *j)
    636 {
    637 	int i;
    638 	int fd[3];
    639 	int p[2];
    640 	VtLog *l;
    641 
    642 	if((fd[0] = open("/dev/null", ORDWR)) < 0) {
    643 		vtlogprint(errlog, "%T open /dev/null: %r\n");
    644 		return;
    645 	}
    646 	if(pipe(p) < 0) {
    647 		vtlogprint(errlog, "%T pipe: %r\n");
    648 		close(fd[0]);
    649 		return;
    650 	}
    651 	qlock(&j->lk);
    652 	l = j->oldlog;
    653 	j->oldlog = j->newlog;
    654 	j->newlog = l;
    655 	qlock(&l->lk);
    656 	for(i=0; i<l->nchunk; i++)
    657 		l->chunk[i].wp = l->chunk[i].p;
    658 	qunlock(&l->lk);
    659 	j->oldok = j->newok;
    660 	j->newok = -1;
    661 	qunlock(&j->lk);
    662 
    663 	fd[1] = p[1];
    664 	fd[2] = p[1];
    665 	j->pid = threadspawn(fd, j->argv[0], j->argv);
    666 	if(j->pid < 0) {
    667 		vtlogprint(errlog, "%T exec %s: %r\n", j->argv[0]);
    668 		close(fd[0]);
    669 		close(fd[1]);
    670 		close(p[0]);
    671 	}
    672 	// fd[0], fd[1], fd[2] are closed now
    673 	j->pipe = p[0];
    674 	j->nrun++;
    675 	vtproc(piper, j);
    676 }
    677 
    678 int
    679 getline(Resub *text, Resub *line)
    680 {
    681 	char *p;
    682 
    683 	if(text->sp >= text->ep)
    684 		return -1;
    685 	line->sp = text->sp;
    686 	p = memchr(text->sp, '\n', text->ep - text->sp);
    687 	if(p == nil) {
    688 		line->ep = text->ep;
    689 		text->sp = text->ep;
    690 	} else {
    691 		line->ep = p;
    692 		text->sp = p+1;
    693 	}
    694 	return 0;
    695 }
    696 
    697 int
    698 verifyok(char *output)
    699 {
    700 	Resub text, line, m;
    701 
    702 	text.sp = output;
    703 	text.ep = output+strlen(output);
    704 	while(getline(&text, &line) >= 0) {
    705 		*line.ep = 0;
    706 		memset(&m, 0, sizeof m);
    707 		if(!regexec(verifyprog, line.sp, nil, 0))
    708 			return 0;
    709 		*line.ep = '\n';
    710 	}
    711 	return 1;
    712 }
    713 
    714 int
    715 mirrorok(char *output)
    716 {
    717 	Resub text, line, m;
    718 
    719 	text.sp = output;
    720 	text.ep = output+strlen(output);
    721 	while(getline(&text, &line) >= 0) {
    722 		*line.ep = 0;
    723 		memset(&m, 0, sizeof m);
    724 		if(!regexec(mirrorprog, line.sp, nil, 0))
    725 			return 0;
    726 		*line.ep = '\n';
    727 	}
    728 	return 1;
    729 }
    730 
    731 void
    732 mkjob(Job *j, ...)
    733 {
    734 	int i;
    735 	char *p;
    736 	va_list arg;
    737 
    738 	memset(j, 0, sizeof *j);
    739 	i = 0;
    740 	va_start(arg, j);
    741 	while((p = va_arg(arg, char*)) != nil) {
    742 		j->argv[i++] = p;
    743 		if(i >= nelem(j->argv))
    744 			sysfatal("job argv size too small");
    745 	}
    746 	j->argv[i] = nil;
    747 	j->oldlog = vtlogopen(smprint("log%ld.0", j-job), LogSize);
    748 	j->newlog = vtlogopen(smprint("log%ld.1", j-job), LogSize);
    749 	va_end(arg);
    750 }
    751 
    752 void
    753 manager(void *v)
    754 {
    755 	int i;
    756 	Job *j;
    757 	vlong now;
    758 
    759 	USED(v);
    760 	for(;; sleep(1000)) {
    761 		for(i=0; i<njob; i++) {
    762 			now = time(0) - time0;
    763 			j = &job[i];
    764 			if(j->pid > 0 || j->newok == -1) {
    765 				// still running
    766 				if(now - j->runstart > 2*j->freq) {
    767 					//TODO: log slow running j
    768 				}
    769 				continue;
    770 			}
    771 			if((j->nrun > 0 && now - j->runend > j->freq)
    772 			|| (j->nrun == 0 && now > (vlong)(j->offset*j->freq))) {
    773 				j->runstart = now;
    774 				j->runend = 0;
    775 				kickjob(j);
    776 			}
    777 		}
    778 	}
    779 }
    780 
    781 void
    782 waitproc(void *v)
    783 {
    784 	Channel *c;
    785 	Waitmsg *w;
    786 	int i;
    787 	Job *j;
    788 
    789 	c = v;
    790 	for(;;) {
    791 		w = recvp(c);
    792 		for(i=0; i<njob; i++) {
    793 			j = &job[i];
    794 			if(j->pid == w->pid) {
    795 				j->pid = 0;
    796 				j->runend = time(0) - time0;
    797 				break;
    798 			}
    799 		}
    800 		free(w);
    801 	}
    802 }
    803 
    804 void
    805 threadmain(int argc, char **argv)
    806 {
    807 	int i;
    808 	int nofork;
    809 	char *prog;
    810 	Job *j;
    811 
    812 	ventilogging = 1;
    813 	ventifmtinstall();
    814 #ifdef PLAN9PORT
    815 	bin = unsharp("#9/bin/venti");
    816 #else
    817 	bin = "/bin/venti";
    818 #endif
    819 	nofork = 0;
    820 	ARGBEGIN{
    821 	case 'b':
    822 		bin = EARGF(usage());
    823 		break;
    824 	case 's':
    825 		nofork = 1;
    826 		break;
    827 	default:
    828 		usage();
    829 	}ARGEND
    830 
    831 	if(argc != 1)
    832 		usage();
    833 	if(rdconf(argv[0], &conf) < 0)
    834 		sysfatal("reading config: %r");
    835 	if(conf.httpaddr == nil)
    836 		sysfatal("config has no httpaddr");
    837 	if(conf.smtp != nil && conf.mailfrom == nil)
    838 		sysfatal("config has smtp but no mailfrom");
    839 	if(conf.smtp != nil && conf.mailto == nil)
    840 		sysfatal("config has smtp but no mailto");
    841 	if((mirrorprog = regcomp(mirrorregexp)) == nil)
    842 		sysfatal("mirrorregexp did not complete");
    843 	if((verifyprog = regcomp(verifyregexp)) == nil)
    844 		sysfatal("verifyregexp did not complete");
    845 	if(conf.nverify > 0 && conf.verifyfreq == 0)
    846 		sysfatal("config has no verifyfreq");
    847 	if(conf.nmirror > 0 && conf.mirrorfreq == 0)
    848 		sysfatal("config has no mirrorfreq");
    849 
    850 	time0 = time(0);
    851 //	sendmail("startup", "mgr is starting\n");
    852 
    853 	logbuf = vtmalloc(LogSize+1);	// +1 for NUL
    854 
    855 	errlog = vtlogopen("errors", LogSize);
    856 	job = vtmalloc((conf.nmirror+conf.nverify)*sizeof job[0]);
    857 	prog = smprint("%s/mirrorarenas", bin);
    858 	for(i=0; i<conf.nmirror; i++) {
    859 		// job: /bin/venti/mirrorarenas -v src dst
    860 		// filter output
    861 		j = &job[njob++];
    862 		mkjob(j, prog, "-v", conf.mirror[i].src, conf.mirror[i].dst, nil);
    863 		j->name = smprint("mirror %s %s", conf.mirror[i].src, conf.mirror[i].dst);
    864 		j->ok = mirrorok;
    865 		j->freq = conf.mirrorfreq;	// 4 hours	// TODO: put in config
    866 		j->offset = (double)i/conf.nmirror;
    867 	}
    868 
    869 	prog = smprint("%s/verifyarena", bin);
    870 	for(i=0; i<conf.nverify; i++) {
    871 		// job: /bin/venti/verifyarena -b 64M -s 1000 -v arena
    872 		// filter output
    873 		j = &job[njob++];
    874 		mkjob(j, prog, "-b64M", "-s1000", conf.verify[i], nil);
    875 		j->name = smprint("verify %s", conf.verify[i]);
    876 		j->ok = verifyok;
    877 		j->freq = conf.verifyfreq;
    878 		j->offset = (double)i/conf.nverify;
    879 	}
    880 
    881 	httpdobj("/mgr", hmanager);
    882 	httpdobj("/log", xlog);
    883 	vtproc(httpdproc, conf.httpaddr);
    884 	vtproc(waitproc, threadwaitchan());
    885 	if(nofork)
    886 		manager(nil);
    887 	else
    888 		vtproc(manager, nil);
    889 }
    890 
    891 
    892 void
    893 qp(Biobuf *b, char *p)
    894 {
    895 	int n, nspace;
    896 
    897 	nspace = 0;
    898 	n = 0;
    899 	for(; *p; p++) {
    900 		if(*p == '\n') {
    901 			if(nspace > 0) {
    902 				nspace = 0;
    903 				Bprint(b, "=\n");
    904 			}
    905 			Bputc(b, '\n');
    906 			n = 0;
    907 			continue;
    908 		}
    909 		if(n > 70) {
    910 			Bprint(b, "=\n");
    911 			nspace = 0;
    912 			continue;
    913 		}
    914 		if(33 <= *p && *p <= 126 && *p != '=') {
    915 			Bputc(b, *p);
    916 			n++;
    917 			nspace = 0;
    918 			continue;
    919 		}
    920 		if(*p == ' ' || *p == '\t') {
    921 			Bputc(b, *p);
    922 			n++;
    923 			nspace++;
    924 			continue;
    925 		}
    926 		Bprint(b, "=%02X", (uchar)*p);
    927 		n += 3;
    928 		nspace = 0;
    929 	}
    930 }
    931 
    932 int
    933 smtpread(Biobuf *b, int code)
    934 {
    935 	char *p, *q;
    936 	int n;
    937 
    938 	while((p = Brdstr(b, '\n', 1)) != nil) {
    939 		n = strtol(p, &q, 10);
    940 		if(n == 0 || q != p+3) {
    941 		error:
    942 			vtlogprint(errlog, "sending mail: %s\n", p);
    943 			free(p);
    944 			return -1;
    945 		}
    946 		if(*q == ' ') {
    947 			if(n == code) {
    948 				free(p);
    949 				return 0;
    950 			}
    951 			goto error;
    952 		}
    953 		if(*q != '-') {
    954 			goto error;
    955 		}
    956 	}
    957 	return -1;
    958 }
    959 
    960 
    961 void
    962 sendmail(char *content, char *subject, char *msg)
    963 {
    964 	int fd;
    965 	Biobuf *bin, *bout;
    966 
    967 	if((fd = dial(conf.smtp, 0, 0, 0)) < 0) {
    968 		vtlogprint(errlog, "dial %s: %r\n", conf.smtp);
    969 		return;
    970 	}
    971 	bin = vtmalloc(sizeof *bin);
    972 	bout = vtmalloc(sizeof *bout);
    973 	Binit(bin, fd, OREAD);
    974 	Binit(bout, fd, OWRITE);
    975 	if(smtpread(bin, 220) < 0){
    976 	error:
    977 		close(fd);
    978 		Bterm(bin);
    979 		Bterm(bout);
    980 		return;
    981 	}
    982 
    983 	Bprint(bout, "HELO venti-mgr\n");
    984 	Bflush(bout);
    985 	if(smtpread(bin, 250) < 0)
    986 		goto error;
    987 
    988 	Bprint(bout, "MAIL FROM:<%s>\n", conf.mailfrom);
    989 	Bflush(bout);
    990 	if(smtpread(bin, 250) < 0)
    991 		goto error;
    992 
    993 	Bprint(bout, "RCPT TO:<%s>\n", conf.mailfrom);
    994 	Bflush(bout);
    995 	if(smtpread(bin, 250) < 0)
    996 		goto error;
    997 
    998 	Bprint(bout, "DATA\n");
    999 	Bflush(bout);
   1000 	if(smtpread(bin, 354) < 0)
   1001 		goto error;
   1002 
   1003 	Bprint(bout, "From: \"venti mgr\" <%s>\n", conf.mailfrom);
   1004 	Bprint(bout, "To: <%s>\n", conf.mailto);
   1005 	Bprint(bout, "Subject: %s\n", subject);
   1006 	Bprint(bout, "MIME-Version: 1.0\n");
   1007 	Bprint(bout, "Content-Type: %s; charset=\"UTF-8\"\n", content);
   1008 	Bprint(bout, "Content-Transfer-Encoding: quoted-printable\n");
   1009 	Bprint(bout, "Message-ID: %08lux%08lux@venti.swtch.com\n", fastrand(), fastrand());
   1010 	Bprint(bout, "\n");
   1011 	qp(bout, msg);
   1012 	Bprint(bout, ".\n");
   1013 	Bflush(bout);
   1014 	if(smtpread(bin, 250) < 0)
   1015 		goto error;
   1016 
   1017 	Bprint(bout, "QUIT\n");
   1018 	Bflush(bout);
   1019 	Bterm(bin);
   1020 	Bterm(bout);
   1021 	close(fd);
   1022 }