plan9port

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

mkfs.c (14332B)


      1 #include <u.h>
      2 #include <libc.h>
      3 #include <auth.h>
      4 #include <bio.h>
      5 
      6 #define mkdir plan9mkdir
      7 #define getmode plan9_getmode
      8 #define setuid plan9_setuid
      9 
     10 enum{
     11 	LEN	= 8*1024,
     12 	HUNKS	= 128,
     13 
     14 	/*
     15 	 * types of destination file sytems
     16 	 */
     17 	Kfs = 0,
     18 	Fs,
     19 	Archive,
     20 };
     21 
     22 typedef struct File	File;
     23 
     24 struct File{
     25 	char	*new;
     26 	char	*elem;
     27 	char	*old;
     28 	char	*uid;
     29 	char	*gid;
     30 	ulong	mode;
     31 };
     32 
     33 void	arch(Dir*);
     34 void	copy(Dir*);
     35 int	copyfile(File*, Dir*, int);
     36 void*	emalloc(ulong);
     37 void	error(char *, ...);
     38 void	freefile(File*);
     39 File*	getfile(File*);
     40 char*	getmode(char*, ulong*);
     41 char*	getname(char*, char**);
     42 char*	getpath(char*);
     43 void	kfscmd(char *);
     44 void	mkdir(Dir*);
     45 int	mkfile(File*);
     46 void	mkfs(File*, int);
     47 char*	mkpath(char*, char*);
     48 void	mktree(File*, int);
     49 void	mountkfs(char*);
     50 void	printfile(File*);
     51 void	setnames(File*);
     52 void	setusers(void);
     53 void	skipdir(void);
     54 char*	strdup(char*);
     55 int	uptodate(Dir*, char*);
     56 void	usage(void);
     57 void	warn(char *, ...);
     58 
     59 Biobuf	*b;
     60 Biobuf	bout;			/* stdout when writing archive */
     61 uchar	boutbuf[2*LEN];
     62 char	newfile[LEN];
     63 char	oldfile[LEN];
     64 char	*proto;
     65 char	*cputype;
     66 char	*users;
     67 char	*oldroot;
     68 char	*newroot;
     69 char	*prog = "mkfs";
     70 int	lineno;
     71 char	*buf;
     72 char	*zbuf;
     73 int	buflen = 1024-8;
     74 int	indent;
     75 int	verb;
     76 int	modes;
     77 int	ream;
     78 int	debug;
     79 int	xflag;
     80 int	sfd;
     81 int	fskind;			/* Kfs, Fs, Archive */
     82 int	setuid;			/* on Fs: set uid and gid? */
     83 char	*user;
     84 
     85 void
     86 main(int argc, char **argv)
     87 {
     88 	File file;
     89 	char *name;
     90 	int i, errs;
     91 
     92 	quotefmtinstall();
     93 	user = getuser();
     94 	name = "";
     95 	memset(&file, 0, sizeof file);
     96 	file.new = "";
     97 	file.old = 0;
     98 	oldroot = "";
     99 	newroot = "/n/kfs";
    100 	users = 0;
    101 	fskind = Kfs;
    102 	ARGBEGIN{
    103 	case 'a':
    104 		if(fskind != Kfs) {
    105 			fprint(2, "cannot use -a with -d\n");
    106 			usage();
    107 		}
    108 		fskind = Archive;
    109 		newroot = "";
    110 		Binits(&bout, 1, OWRITE, boutbuf, sizeof boutbuf);
    111 		break;
    112 	case 'd':
    113 		if(fskind != Kfs) {
    114 			fprint(2, "cannot use -d with -a\n");
    115 			usage();
    116 		}
    117 		fskind = Fs;
    118 		newroot = ARGF();
    119 		break;
    120 	case 'D':
    121 		debug = 1;
    122 		break;
    123 	case 'n':
    124 		name = EARGF(usage());
    125 		break;
    126 	case 'p':
    127 		modes = 1;
    128 		break;
    129 	case 'r':
    130 		ream = 1;
    131 		break;
    132 	case 's':
    133 		oldroot = ARGF();
    134 		break;
    135 	case 'u':
    136 		users = ARGF();
    137 		break;
    138 	case 'U':
    139 		setuid = 1;
    140 		break;
    141 	case 'v':
    142 		verb = 1;
    143 		break;
    144 	case 'x':
    145 		xflag = 1;
    146 		break;
    147 	case 'z':
    148 		buflen = atoi(ARGF())-8;
    149 		break;
    150 	default:
    151 		usage();
    152 	}ARGEND
    153 
    154 	if(!argc)
    155 		usage();
    156 
    157 	buf = emalloc(buflen);
    158 	zbuf = emalloc(buflen);
    159 	memset(zbuf, 0, buflen);
    160 
    161 	mountkfs(name);
    162 	kfscmd("allow");
    163 	proto = "users";
    164 	setusers();
    165 	cputype = getenv("cputype");
    166 	if(cputype == 0)
    167 		cputype = "68020";
    168 
    169 	errs = 0;
    170 	for(i = 0; i < argc; i++){
    171 		proto = argv[i];
    172 		fprint(2, "processing %q\n", proto);
    173 
    174 		b = Bopen(proto, OREAD);
    175 		if(!b){
    176 			fprint(2, "%q: can't open %q: skipping\n", prog, proto);
    177 			errs++;
    178 			continue;
    179 		}
    180 
    181 		lineno = 0;
    182 		indent = 0;
    183 		mkfs(&file, -1);
    184 		Bterm(b);
    185 	}
    186 	fprint(2, "file system made\n");
    187 	kfscmd("disallow");
    188 	kfscmd("sync");
    189 	if(errs)
    190 		exits("skipped protos");
    191 	if(fskind == Archive){
    192 		Bprint(&bout, "end of archive\n");
    193 		Bterm(&bout);
    194 	}
    195 	exits(0);
    196 }
    197 
    198 void
    199 mkfs(File *me, int level)
    200 {
    201 	File *child;
    202 	int rec;
    203 
    204 	child = getfile(me);
    205 	if(!child)
    206 		return;
    207 	if((child->elem[0] == '+' || child->elem[0] == '*') && child->elem[1] == '\0'){
    208 		rec = child->elem[0] == '+';
    209 		free(child->new);
    210 		child->new = strdup(me->new);
    211 		setnames(child);
    212 		mktree(child, rec);
    213 		freefile(child);
    214 		child = getfile(me);
    215 	}
    216 	while(child && indent > level){
    217 		if(mkfile(child))
    218 			mkfs(child, indent);
    219 		freefile(child);
    220 		child = getfile(me);
    221 	}
    222 	if(child){
    223 		freefile(child);
    224 		Bseek(b, -Blinelen(b), 1);
    225 		lineno--;
    226 	}
    227 }
    228 
    229 void
    230 mktree(File *me, int rec)
    231 {
    232 	File child;
    233 	Dir *d;
    234 	int i, n, fd;
    235 
    236 	fd = open(oldfile, OREAD);
    237 	if(fd < 0){
    238 		warn("can't open %q: %r", oldfile);
    239 		return;
    240 	}
    241 
    242 	child = *me;
    243 	while((n = dirread(fd, &d)) > 0){
    244 		for(i = 0; i < n; i++){
    245 			child.new = mkpath(me->new, d[i].name);
    246 			if(me->old)
    247 				child.old = mkpath(me->old, d[i].name);
    248 			child.elem = d[i].name;
    249 			setnames(&child);
    250 			if(copyfile(&child, &d[i], 1) && rec)
    251 				mktree(&child, rec);
    252 			free(child.new);
    253 			if(child.old)
    254 				free(child.old);
    255 		}
    256 	}
    257 	close(fd);
    258 }
    259 
    260 int
    261 mkfile(File *f)
    262 {
    263 	Dir *dir;
    264 
    265 	if((dir = dirstat(oldfile)) == nil){
    266 		warn("can't stat file %q: %r", oldfile);
    267 		skipdir();
    268 		return 0;
    269 	}
    270 	return copyfile(f, dir, 0);
    271 }
    272 
    273 int
    274 copyfile(File *f, Dir *d, int permonly)
    275 {
    276 	ulong mode;
    277 	Dir nd;
    278 
    279 	if(xflag){
    280 		Bprint(&bout, "%q\t%ld\t%lld\n", f->new, d->mtime, d->length);
    281 		return (d->mode & DMDIR) != 0;
    282 	}
    283 	if(verb && (fskind == Archive || ream))
    284 		fprint(2, "%q\n", f->new);
    285 	d->name = f->elem;
    286 	if(d->type != 'M' && d->type != 'Z'){
    287 		d->uid = "sys";
    288 		d->gid = "sys";
    289 		mode = (d->mode >> 6) & 7;
    290 		d->mode |= mode | (mode << 3);
    291 	}
    292 	if(strcmp(f->uid, "-") != 0)
    293 		d->uid = f->uid;
    294 	if(strcmp(f->gid, "-") != 0)
    295 		d->gid = f->gid;
    296 	if(fskind == Fs && !setuid){
    297 		d->uid = "";
    298 		d->gid = "";
    299 	}
    300 	if(f->mode != ~0){
    301 		if(permonly)
    302 			d->mode = (d->mode & ~0666) | (f->mode & 0666);
    303 		else if((d->mode&DMDIR) != (f->mode&DMDIR))
    304 			warn("inconsistent mode for %q", f->new);
    305 		else
    306 			d->mode = f->mode;
    307 	}
    308 	if(!uptodate(d, newfile)){
    309 		if(verb && (fskind != Archive && ream == 0))
    310 			fprint(2, "%q\n", f->new);
    311 		if(d->mode & DMDIR)
    312 			mkdir(d);
    313 		else
    314 			copy(d);
    315 	}else if(modes){
    316 		nulldir(&nd);
    317 		nd.mode = d->mode;
    318 		nd.gid = d->gid;
    319 		nd.mtime = d->mtime;
    320 		if(verb && (fskind != Archive && ream == 0))
    321 			fprint(2, "%q\n", f->new);
    322 		if(dirwstat(newfile, &nd) < 0)
    323 			warn("can't set modes for %q: %r", f->new);
    324 		nulldir(&nd);
    325 		nd.uid = d->uid;
    326 		dirwstat(newfile, &nd);
    327 	}
    328 	return (d->mode & DMDIR) != 0;
    329 }
    330 
    331 /*
    332  * check if file to is up to date with
    333  * respect to the file represented by df
    334  */
    335 int
    336 uptodate(Dir *df, char *to)
    337 {
    338 	int ret;
    339 	Dir *dt;
    340 
    341 	if(fskind == Archive || ream || (dt = dirstat(to)) == nil)
    342 		return 0;
    343 	ret = dt->mtime >= df->mtime;
    344 	free(dt);
    345 	return ret;
    346 }
    347 
    348 void
    349 copy(Dir *d)
    350 {
    351 	char cptmp[LEN], *p;
    352 	int f, t, n, needwrite, nowarnyet = 1;
    353 	vlong tot, len;
    354 	Dir nd;
    355 
    356 	f = open(oldfile, OREAD);
    357 	if(f < 0){
    358 		warn("can't open %q: %r", oldfile);
    359 		return;
    360 	}
    361 	t = -1;
    362 	if(fskind == Archive)
    363 		arch(d);
    364 	else{
    365 		strcpy(cptmp, newfile);
    366 		p = utfrrune(cptmp, L'/');
    367 		if(!p)
    368 			error("internal temporary file error");
    369 		strcpy(p+1, "__mkfstmp");
    370 		t = create(cptmp, OWRITE, 0666);
    371 		if(t < 0){
    372 			warn("can't create %q: %r", newfile);
    373 			close(f);
    374 			return;
    375 		}
    376 	}
    377 
    378 	needwrite = 0;
    379 	for(tot = 0; tot < d->length; tot += n){
    380 		len = d->length - tot;
    381 		/* don't read beyond d->length */
    382 		if (len > buflen)
    383 			len = buflen;
    384 		n = read(f, buf, len);
    385 		if(n <= 0) {
    386 			if(n < 0 && nowarnyet) {
    387 				warn("can't read %q: %r", oldfile);
    388 				nowarnyet = 0;
    389 			}
    390 			/*
    391 			 * don't quit: pad to d->length (in pieces) to agree
    392 			 * with the length in the header, already emitted.
    393 			 */
    394 			memset(buf, 0, len);
    395 			n = len;
    396 		}
    397 		if(fskind == Archive){
    398 			if(Bwrite(&bout, buf, n) != n)
    399 				error("write error: %r");
    400 		}else if(memcmp(buf, zbuf, n) == 0){
    401 			if(seek(t, n, 1) < 0)
    402 				error("can't write zeros to %q: %r", newfile);
    403 			needwrite = 1;
    404 		}else{
    405 			if(write(t, buf, n) < n)
    406 				error("can't write %q: %r", newfile);
    407 			needwrite = 0;
    408 		}
    409 	}
    410 	close(f);
    411 	if(needwrite){
    412 		if(seek(t, -1, 1) < 0 || write(t, zbuf, 1) != 1)
    413 			error("can't write zero at end of %q: %r", newfile);
    414 	}
    415 	if(tot != d->length){
    416 		/* this should no longer happen */
    417 		warn("wrong number of bytes written to %q (was %lld should be %lld)\n",
    418 			newfile, tot, d->length);
    419 		if(fskind == Archive){
    420 			warn("seeking to proper position\n");
    421 			/* does no good if stdout is a pipe */
    422 			Bseek(&bout, d->length - tot, 1);
    423 		}
    424 	}
    425 	if(fskind == Archive)
    426 		return;
    427 	remove(newfile);
    428 	nulldir(&nd);
    429 	nd.mode = d->mode;
    430 	nd.gid = d->gid;
    431 	nd.mtime = d->mtime;
    432 	nd.name = d->name;
    433 	if(dirfwstat(t, &nd) < 0)
    434 		error("can't move tmp file to %q: %r", newfile);
    435 	nulldir(&nd);
    436 	nd.uid = d->uid;
    437 	dirfwstat(t, &nd);
    438 	close(t);
    439 }
    440 
    441 void
    442 mkdir(Dir *d)
    443 {
    444 	Dir *d1;
    445 	Dir nd;
    446 	int fd;
    447 
    448 	if(fskind == Archive){
    449 		arch(d);
    450 		return;
    451 	}
    452 	fd = create(newfile, OREAD, d->mode);
    453 	nulldir(&nd);
    454 	nd.mode = d->mode;
    455 	nd.gid = d->gid;
    456 	nd.mtime = d->mtime;
    457 	if(fd < 0){
    458 		if((d1 = dirstat(newfile)) == nil || !(d1->mode & DMDIR)){
    459 			free(d1);
    460 			error("can't create %q", newfile);
    461 		}
    462 		free(d1);
    463 		if(dirwstat(newfile, &nd) < 0)
    464 			warn("can't set modes for %q: %r", newfile);
    465 		nulldir(&nd);
    466 		nd.uid = d->uid;
    467 		dirwstat(newfile, &nd);
    468 		return;
    469 	}
    470 	if(dirfwstat(fd, &nd) < 0)
    471 		warn("can't set modes for %q: %r", newfile);
    472 	nulldir(&nd);
    473 	nd.uid = d->uid;
    474 	dirfwstat(fd, &nd);
    475 	close(fd);
    476 }
    477 
    478 void
    479 arch(Dir *d)
    480 {
    481 	Bprint(&bout, "%q %luo %q %q %lud %lld\n",
    482 		newfile, d->mode, d->uid, d->gid, d->mtime, d->length);
    483 }
    484 
    485 char *
    486 mkpath(char *prefix, char *elem)
    487 {
    488 	char *p;
    489 	int n;
    490 
    491 	n = strlen(prefix) + strlen(elem) + 2;
    492 	p = emalloc(n);
    493 	sprint(p, "%s/%s", prefix, elem);
    494 	return p;
    495 }
    496 
    497 char *
    498 strdup(char *s)
    499 {
    500 	char *t;
    501 
    502 	t = emalloc(strlen(s) + 1);
    503 	return strcpy(t, s);
    504 }
    505 
    506 void
    507 setnames(File *f)
    508 {
    509 	sprint(newfile, "%s%s", newroot, f->new);
    510 	if(f->old){
    511 		if(f->old[0] == '/')
    512 			sprint(oldfile, "%s%s", oldroot, f->old);
    513 		else
    514 			strcpy(oldfile, f->old);
    515 	}else
    516 		sprint(oldfile, "%s%s", oldroot, f->new);
    517 	if(strlen(newfile) >= sizeof newfile
    518 	|| strlen(oldfile) >= sizeof oldfile)
    519 		error("name overfile");
    520 }
    521 
    522 void
    523 freefile(File *f)
    524 {
    525 	if(f->old)
    526 		free(f->old);
    527 	if(f->new)
    528 		free(f->new);
    529 	free(f);
    530 }
    531 
    532 /*
    533  * skip all files in the proto that
    534  * could be in the current dir
    535  */
    536 void
    537 skipdir(void)
    538 {
    539 	char *p, c;
    540 	int level;
    541 
    542 	if(indent < 0 || b == nil)	/* b is nil when copying adm/users */
    543 		return;
    544 	level = indent;
    545 	for(;;){
    546 		indent = 0;
    547 		p = Brdline(b, '\n');
    548 		lineno++;
    549 		if(!p){
    550 			indent = -1;
    551 			return;
    552 		}
    553 		while((c = *p++) != '\n')
    554 			if(c == ' ')
    555 				indent++;
    556 			else if(c == '\t')
    557 				indent += 8;
    558 			else
    559 				break;
    560 		if(indent <= level){
    561 			Bseek(b, -Blinelen(b), 1);
    562 			lineno--;
    563 			return;
    564 		}
    565 	}
    566 }
    567 
    568 File*
    569 getfile(File *old)
    570 {
    571 	File *f;
    572 	char *elem;
    573 	char *p;
    574 	int c;
    575 
    576 	if(indent < 0)
    577 		return 0;
    578 loop:
    579 	indent = 0;
    580 	p = Brdline(b, '\n');
    581 	lineno++;
    582 	if(!p){
    583 		indent = -1;
    584 		return 0;
    585 	}
    586 	while((c = *p++) != '\n')
    587 		if(c == ' ')
    588 			indent++;
    589 		else if(c == '\t')
    590 			indent += 8;
    591 		else
    592 			break;
    593 	if(c == '\n' || c == '#')
    594 		goto loop;
    595 	p--;
    596 	f = emalloc(sizeof *f);
    597 	p = getname(p, &elem);
    598 	if(debug)
    599 		fprint(2, "getfile: %q root %q\n", elem, old->new);
    600 	f->new = mkpath(old->new, elem);
    601 	f->elem = utfrrune(f->new, L'/') + 1;
    602 	p = getmode(p, &f->mode);
    603 	p = getname(p, &f->uid);
    604 	if(!*f->uid)
    605 		f->uid = "-";
    606 	p = getname(p, &f->gid);
    607 	if(!*f->gid)
    608 		f->gid = "-";
    609 	f->old = getpath(p);
    610 	if(f->old && strcmp(f->old, "-") == 0){
    611 		free(f->old);
    612 		f->old = 0;
    613 	}
    614 	setnames(f);
    615 
    616 	if(debug)
    617 		printfile(f);
    618 
    619 	return f;
    620 }
    621 
    622 char*
    623 getpath(char *p)
    624 {
    625 	char *q, *new;
    626 	int c, n;
    627 
    628 	while((c = *p) == ' ' || c == '\t')
    629 		p++;
    630 	q = p;
    631 	while((c = *q) != '\n' && c != ' ' && c != '\t')
    632 		q++;
    633 	if(q == p)
    634 		return 0;
    635 	n = q - p;
    636 	new = emalloc(n + 1);
    637 	memcpy(new, p, n);
    638 	new[n] = 0;
    639 	return new;
    640 }
    641 
    642 char*
    643 getname(char *p, char **buf)
    644 {
    645 	char *s, *start;
    646 	int c;
    647 
    648 	while((c = *p) == ' ' || c == '\t')
    649 		p++;
    650 
    651 	start = p;
    652 	while((c = *p) != '\n' && c != ' ' && c != '\t' && c != '\0')
    653 		p++;
    654 
    655 	*buf = malloc(p+1-start);
    656 	if(*buf == nil)
    657 		return nil;
    658 	memmove(*buf, start, p-start);
    659 	(*buf)[p-start] = '\0';
    660 
    661 	if(**buf == '$'){
    662 		s = getenv(*buf+1);
    663 		if(s == 0){
    664 			warn("can't read environment variable %q", *buf+1);
    665 			skipdir();
    666 			free(*buf);
    667 			return nil;
    668 		}
    669 		free(*buf);
    670 		*buf = s;
    671 	}
    672 	return p;
    673 }
    674 
    675 char*
    676 getmode(char *p, ulong *xmode)
    677 {
    678 	char *buf, *s;
    679 	ulong m;
    680 
    681 	*xmode = ~0;
    682 	p = getname(p, &buf);
    683 	if(p == nil)
    684 		return nil;
    685 
    686 	s = buf;
    687 	if(!*s || strcmp(s, "-") == 0)
    688 		return p;
    689 	m = 0;
    690 	if(*s == 'd'){
    691 		m |= DMDIR;
    692 		s++;
    693 	}
    694 	if(*s == 'a'){
    695 		m |= DMAPPEND;
    696 		s++;
    697 	}
    698 	if(*s == 'l'){
    699 		m |= DMEXCL;
    700 		s++;
    701 	}
    702 	if(s[0] < '0' || s[0] > '7'
    703 	|| s[1] < '0' || s[1] > '7'
    704 	|| s[2] < '0' || s[2] > '7'
    705 	|| s[3]){
    706 		warn("bad mode specification %q", buf);
    707 		free(buf);
    708 		return p;
    709 	}
    710 	*xmode = m | strtoul(s, 0, 8);
    711 	free(buf);
    712 	return p;
    713 }
    714 
    715 void
    716 setusers(void)
    717 {
    718 	File file;
    719 	int m;
    720 
    721 	if(fskind != Kfs)
    722 		return;
    723 	m = modes;
    724 	modes = 1;
    725 	file.uid = "adm";
    726 	file.gid = "adm";
    727 	file.mode = DMDIR|0775;
    728 	file.new = "/adm";
    729 	file.elem = "adm";
    730 	file.old = 0;
    731 	setnames(&file);
    732 	strcpy(oldfile, file.new);	/* Don't use root for /adm */
    733 	mkfile(&file);
    734 	file.new = "/adm/users";
    735 	file.old = users;
    736 	file.elem = "users";
    737 	file.mode = 0664;
    738 	setnames(&file);
    739 	if (file.old)
    740 		strcpy(oldfile, file.old);	/* Don't use root for /adm/users */
    741 	mkfile(&file);
    742 	kfscmd("user");
    743 	mkfile(&file);
    744 	file.mode = DMDIR|0775;
    745 	file.new = "/adm";
    746 	file.old = "/adm";
    747 	file.elem = "adm";
    748 	setnames(&file);
    749 	strcpy(oldfile, file.old);	/* Don't use root for /adm */
    750 	mkfile(&file);
    751 	modes = m;
    752 }
    753 
    754 void
    755 mountkfs(char *name)
    756 {
    757 	if(fskind != Kfs)
    758 		return;
    759 	sysfatal("no kfs: use -a or -d");
    760 }
    761 
    762 void
    763 kfscmd(char *cmd)
    764 {
    765 	char buf[4*1024];
    766 	int n;
    767 
    768 	if(fskind != Kfs)
    769 		return;
    770 	if(write(sfd, cmd, strlen(cmd)) != strlen(cmd)){
    771 		fprint(2, "%q: error writing %q: %r", prog, cmd);
    772 		return;
    773 	}
    774 	for(;;){
    775 		n = read(sfd, buf, sizeof buf - 1);
    776 		if(n <= 0)
    777 			return;
    778 		buf[n] = '\0';
    779 		if(strcmp(buf, "done") == 0 || strcmp(buf, "success") == 0)
    780 			return;
    781 		if(strcmp(buf, "unknown command") == 0){
    782 			fprint(2, "%q: command %q not recognized\n", prog, cmd);
    783 			return;
    784 		}
    785 	}
    786 }
    787 
    788 void *
    789 emalloc(ulong n)
    790 {
    791 	void *p;
    792 
    793 	if((p = malloc(n)) == 0)
    794 		error("out of memory");
    795 	return p;
    796 }
    797 
    798 void
    799 error(char *fmt, ...)
    800 {
    801 	char buf[1024];
    802 	va_list arg;
    803 
    804 	sprint(buf, "%q: %q:%d: ", prog, proto, lineno);
    805 	va_start(arg, fmt);
    806 	vseprint(buf+strlen(buf), buf+sizeof(buf), fmt, arg);
    807 	va_end(arg);
    808 	fprint(2, "%s\n", buf);
    809 	kfscmd("disallow");
    810 	kfscmd("sync");
    811 	exits(0);
    812 }
    813 
    814 void
    815 warn(char *fmt, ...)
    816 {
    817 	char buf[1024];
    818 	va_list arg;
    819 
    820 	sprint(buf, "%q: %q:%d: ", prog, proto, lineno);
    821 	va_start(arg, fmt);
    822 	vseprint(buf+strlen(buf), buf+sizeof(buf), fmt, arg);
    823 	va_end(arg);
    824 	fprint(2, "%s\n", buf);
    825 }
    826 
    827 void
    828 printfile(File *f)
    829 {
    830 	if(f->old)
    831 		fprint(2, "%q from %q %q %q %lo\n", f->new, f->old, f->uid, f->gid, f->mode);
    832 	else
    833 		fprint(2, "%q %q %q %lo\n", f->new, f->uid, f->gid, f->mode);
    834 }
    835 
    836 void
    837 usage(void)
    838 {
    839 	fprint(2, "usage: %q [-aprvx] [-d root] [-n name] [-s source] [-u users] [-z n] proto ...\n", prog);
    840 	exits("usage");
    841 }