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