xref: /inferno-os/appl/cmd/disk/mkfs.b (revision 98a84ad18808c4c7892dac82539cd678dc45e952)
1implement Mkfs;
2
3include "sys.m";
4	sys: Sys;
5	Dir, sprint, fprint: import sys;
6
7include "draw.m";
8
9include "bufio.m";
10	bufio: Bufio;
11	Iobuf: import bufio;
12
13include "string.m";
14	str: String;
15
16include "arg.m";
17	arg: Arg;
18
19Mkfs: module
20{
21	init:	fn(nil: ref Draw->Context, nil: list of string);
22};
23
24LEN: con Sys->ATOMICIO;
25HUNKS: con 128;
26
27Kfs, Fs, Archive: con iota;	# types of destination file sytems
28
29File: adt {
30	new:	string;
31	elem:	string;
32	old:	string;
33	uid:	string;
34	gid:	string;
35	mode:	int;
36};
37
38b: ref Iobuf;
39bout: ref Iobuf;			# stdout when writing archive
40newfile: string;
41oldfile: string;
42proto: string;
43cputype: string;
44users: string;
45oldroot: string;
46newroot: string;
47prog := "mkfs";
48lineno := 0;
49buf: array of byte;
50zbuf: array of byte;
51buflen := 1024-8;
52indent: int;
53verb: int;
54modes: int;
55ream: int;
56debug: int;
57xflag: int;
58sfd: ref Sys->FD;
59fskind: int;	# Kfs, Fs, Archive
60user: string;
61stderr: ref Sys->FD;
62usrid, grpid : string;
63setuid: int;
64
65init(nil: ref Draw->Context, args: list of string)
66{
67	sys = load Sys Sys->PATH;
68	bufio = load Bufio Bufio->PATH;
69	str = load String String->PATH;
70	arg = load Arg Arg->PATH;
71
72	sys->pctl(Sys->NEWPGRP|Sys->FORKNS|Sys->FORKFD, nil);
73
74	stderr = sys->fildes(2);
75	if(arg == nil)
76		error(sys->sprint("can't load %q: %r", Arg->PATH));
77
78	user = getuser();
79	if(user == nil)
80		user = "none";
81	name := "";
82	file := ref File;
83	file.new = "";
84	file.old = nil;
85	file.mode = 0;
86	oldroot = "";
87	newroot = "/n/kfs";
88	users = nil;
89	fskind = Kfs;	# i suspect Inferno default should be different
90	arg->init(args);
91	arg->setusage("mkfs [-aprvxS] [-d root] [-n kfscmdname] [-s src-fs] [-u userfile] [-z n] [-G group] [-U user] proto ...");
92	while((c := arg->opt()) != 0)
93		case c {
94		'a' =>
95			fskind = Archive;
96			newroot = "";
97			bout = bufio->fopen(sys->fildes(1), Sys->OWRITE);
98			if(bout == nil)
99				error(sys->sprint("can't open standard output for archive: %r"));
100		'd' =>
101			fskind = Fs;
102			newroot = arg->earg();
103		'D' =>
104			debug = 1;
105		'n' =>
106			name = arg->earg();
107		'p' =>
108			modes = 1;
109		'q' =>
110			;
111		'r' =>
112			ream = 1;
113		's' =>
114			oldroot = arg->earg();
115		'u' =>
116			users = arg->earg();
117		'v' =>
118			verb = 1;
119		'x' =>
120			xflag = 1;
121		'z' =>
122			(buflen, nil) = str->toint(arg->earg(), 10);
123			buflen -= 8;	# qid.path and tag at end of each kfs block
124		'U' =>
125			usrid = arg->earg();
126		'G' =>
127			grpid = arg->earg();
128		'S' =>
129			setuid = 1;
130		* =>
131			arg->usage();
132		}
133
134	args = arg->argv();
135	if(args == nil)
136		arg->usage();
137
138	buf = array [buflen] of byte;
139	zbuf = array [buflen] of { * => byte 0 };
140
141	if(name != nil)
142		openkfscmd(name);
143	kfscmd("allow");
144	if(users != nil){
145		proto = "users";	# for diagnostics
146		setusers();
147	}
148	cputype = getenv("cputype");
149	if(cputype == nil)
150		cputype = "dis";
151
152	errs := 0;
153	for(; args != nil; args = tl args){
154		proto = hd args;
155		fprint(stderr, "processing %s\n", proto);
156
157		b = bufio->open(proto, Sys->OREAD);
158		if(b == nil){
159			fprint(stderr, "%s: can't open %q: %r: skipping\n", prog, proto);
160			errs++;
161			continue;
162		}
163
164		lineno = 0;
165		indent = 0;
166		mkfs(file, -1);
167		b.close();
168	}
169	fprint(stderr, "file system made\n");
170	kfscmd("disallow");
171	kfscmd("sync");
172	if(errs)
173		quit("skipped protos");
174	if(fskind == Archive){
175		bout.puts("end of archive\n");
176		if(bout.flush() == Bufio->ERROR)
177			error(sys->sprint("write error: %r"));
178	}
179}
180
181quit(why: string)
182{
183	if(bout != nil)
184		bout.flush();
185	if(why != nil)
186		raise "fail:"+why;
187	exit;
188}
189
190mkfs(me: ref File, level: int)
191{
192	(child, fp) := getfile(me);
193	if(child == nil)
194		return;
195	if(child.elem == "+" || child.elem == "*" || child.elem == "%"){
196		rec := child.elem[0] == '+';
197		filesonly := child.elem[0] == '%';
198		child.new = me.new;
199		setnames(child);
200		mktree(child, rec, filesonly);
201		(child, fp) = getfile(me);
202	}
203	while(child != nil && indent > level){
204		if(mkfile(child))
205			mkfs(child, indent);
206		(child, fp) = getfile(me);
207	}
208	if(child != nil){
209		b.seek(fp, 0);
210		lineno--;
211	}
212}
213
214mktree(me: ref File, rec: int, filesonly: int)
215{
216	fd := sys->open(oldfile, Sys->OREAD);
217	if(fd == nil){
218		warn(sys->sprint("can't open %q: %r", oldfile));
219		return;
220	}
221
222	child := ref *me;
223	r := ref Rec(nil, 0);
224	for(;;){
225		(n, d) := sys->dirread(fd);
226		if(n <= 0)
227			break;
228		for(i := 0; i < n; i++)
229		  	if (!recall(d[i].name, r)) {
230				if(filesonly && d[i].mode & Sys->DMDIR)
231					continue;
232				child.new = mkpath(me.new, d[i].name);
233				if(me.old != nil)
234					child.old = mkpath(me.old, d[i].name);
235				child.elem = d[i].name;
236				setnames(child);
237				if(copyfile(child, ref d[i], 1) && rec)
238					mktree(child, rec, filesonly);
239		  	}
240	}
241}
242
243# Recall namespace fix
244# -- remove duplicates (could use Readdir->init(,Readdir->COMPACT))
245# obc
246
247Rec: adt
248{
249	ad: array of string;
250	l: int;
251};
252
253AL : con HUNKS;
254recall(e : string, r : ref Rec) : int
255{
256	if (r.ad == nil) r.ad = array[AL] of string;
257	# double array
258	if (r.l >= len r.ad) {
259		nar := array[2*(len r.ad)] of string;
260		nar[0:] = r.ad;
261		r.ad = nar;
262	}
263	for(i := 0; i < r.l; i++)
264		if (r.ad[i] == e) return 1;
265	r.ad[r.l++] = e;
266	return 0;
267}
268
269mkfile(f: ref File): int
270{
271	(i, dir) := sys->stat(oldfile);
272	if(i < 0){
273		warn(sys->sprint("can't stat file %q: %r", oldfile));
274		skipdir();
275		return 0;
276	}
277	return copyfile(f, ref dir, 0);
278}
279
280copyfile(f: ref File, d: ref Dir, permonly: int): int
281{
282	mode: int;
283
284	if(xflag && bout != nil){
285		bout.puts(sys->sprint("%q\t%d\t%bd\n", f.new, d.mtime, d.length));
286		return (d.mode & Sys->DMDIR) != 0;
287	}
288	d.name = f.elem;
289	if(d.dtype != 'M' && d.dtype != 'U'){		# hmm... Indeed!
290		d.uid = "inferno";
291		d.gid = "inferno";
292		mode = (d.mode >> 6) & 7;
293		d.mode |= mode | (mode << 3);
294	}
295	if(f.uid != "-")
296		d.uid = f.uid;
297	if(f.gid != "-")
298		d.gid = f.gid;
299	if(fskind == Fs && !setuid){	# new system: set to nil
300		d.uid = user;
301		d.gid = user;
302	}
303	if (usrid != nil)
304		d.uid = usrid;
305	if (grpid != nil)
306		d.gid = grpid;
307	if(f.mode != ~0){
308		if(permonly)
309			d.mode = (d.mode & ~8r666) | (f.mode & 8r666);
310		else if((d.mode&Sys->DMDIR) != (f.mode&Sys->DMDIR))
311			warn(sys->sprint("inconsistent mode for %s", f.new));
312		else
313			d.mode = f.mode;
314	}
315	if(!uptodate(d, newfile)){
316		if(d.mode & Sys->DMDIR)
317			mkdir(d);
318		else {
319			if(verb)
320				fprint(stderr, "%q\n", f.new);
321			copy(d);
322		}
323	}else if(modes){
324		nd := sys->nulldir;
325		nd.mode = d.mode;
326		nd.mtime = d.mtime;
327		nd.gid = d.gid;
328		if(sys->wstat(newfile, nd) < 0)
329			warn(sys->sprint("can't set modes for %q: %r", f.new));
330		# do the uid separately since different file systems object
331		nd = sys->nulldir;
332		nd.uid = d.uid;
333		sys->wstat(newfile, nd);
334	}
335	return (d.mode & Sys->DMDIR) != 0;
336}
337
338
339# check if file to is up to date with
340# respect to the file represented by df
341
342uptodate(df: ref Dir, newf: string): int
343{
344	if(fskind == Archive || ream)
345		return 0;
346	(i, dt) := sys->stat(newf);
347	if(i < 0)
348		return 0;
349	return dt.mtime >= df.mtime;
350}
351
352copy(d: ref Dir)
353{
354	t: ref Sys->FD;
355	n: int;
356
357	f := sys->open(oldfile, Sys->OREAD);
358	if(f == nil){
359		warn(sys->sprint("can't open %q: %r", oldfile));
360		return;
361	}
362	t = nil;
363	if(fskind == Archive)
364		arch(d);
365	else{
366		(dname, fname) := str->splitr(newfile, "/");
367		if(fname == nil)
368			error(sys->sprint("internal temporary file error (%s)", dname));
369		cptmp := dname+"__mkfstmp";
370		t = sys->create(cptmp, Sys->OWRITE, 8r666);
371		if(t == nil){
372			warn(sys->sprint("can't create %q: %r", newfile));
373			return;
374		}
375	}
376
377	for(tot := big 0;; tot += big n){
378		n = sys->read(f, buf, buflen);
379		if(n < 0){
380			warn(sys->sprint("can't read %q: %r", oldfile));
381			break;
382		}
383		if(n == 0)
384			break;
385		if(fskind == Archive){
386			if(bout.write(buf, n) != n)
387				error(sys->sprint("write error: %r"));
388		}else if(buf[0:buflen] == zbuf[0:buflen]){
389			if(sys->seek(t, big buflen, 1) < big 0)
390				error(sys->sprint("can't write zeros to %q: %r", newfile));
391		}else if(sys->write(t, buf, n) < n)
392			error(sys->sprint("can't write %q: %r", newfile));
393	}
394	f = nil;
395	if(tot != d.length){
396		warn(sys->sprint("wrong number bytes written to %s (was %bd should be %bd)",
397			newfile, tot, d.length));
398		if(fskind == Archive){
399			warn("seeking to proper position");
400			bout.seek(d.length - tot, 1);
401		}
402	}
403	if(fskind == Archive)
404		return;
405	sys->remove(newfile);
406	nd := sys->nulldir;
407	nd.name = d.name;
408	nd.mode = d.mode;
409	nd.mtime = d.mtime;
410	if(sys->fwstat(t, nd) < 0)
411		error(sys->sprint("can't move tmp file to %q: %r", newfile));
412	nd = sys->nulldir;
413	nd.gid = d.gid;
414	if(sys->fwstat(t, nd) < 0)
415		warn(sys->sprint("can't set group id of %q to %q: %r", newfile, d.gid));
416	nd.gid = nil;
417	nd.uid = d.uid;
418	sys->fwstat(t, nd);
419}
420
421mkdir(d: ref Dir)
422{
423	if(fskind == Archive){
424		arch(d);
425		return;
426	}
427	fd := sys->create(newfile, Sys->OREAD, d.mode);
428	nd := sys->nulldir;
429	nd.mode = d.mode;
430	nd.gid = d.gid;
431	nd.mtime = d.mtime;
432	if(fd == nil){
433		(i, d1) := sys->stat(newfile);
434		if(i < 0 || !(d1.mode & Sys->DMDIR))
435			error(sys->sprint("can't create %q", newfile));
436		if(sys->wstat(newfile, nd) < 0)
437			warn(sys->sprint("can't set modes for %q: %r", newfile));
438		nd = sys->nulldir;
439		nd.uid = d.uid;
440		sys->wstat(newfile, nd);
441		return;
442	}
443	if(sys->fwstat(fd, nd) < 0)
444		warn(sys->sprint("can't set modes for %q: %r", newfile));
445	nd = sys->nulldir;
446	nd.uid = d.uid;
447	sys->fwstat(fd, nd);
448}
449
450arch(d: ref Dir)
451{
452	bout.puts(sys->sprint("%q %uo %q %q %ud %bd\n",
453		newfile, d.mode, d.uid, d.gid, d.mtime, d.length));
454}
455
456mkpath(prefix, elem: string): string
457{
458	return sys->sprint("%s/%s", prefix, elem);
459}
460
461setnames(f: ref File)
462{
463	newfile = newroot+f.new;
464	if(f.old != nil){
465		if(f.old[0] == '/')
466			oldfile = oldroot+f.old;
467		else
468			oldfile = f.old;
469	}else
470		oldfile = oldroot+f.new;
471}
472
473#
474# skip all files in the proto that
475# could be in the current dir
476#
477skipdir()
478{
479	if(indent < 0)
480		return;
481	level := indent;
482	for(;;){
483		indent = 0;
484		fp := b.offset();
485		p := b.gets('\n');
486		lineno++;
487		if(p == nil){
488			indent = -1;
489			return;
490		}
491		for(j := 0; (c := p[j++]) != '\n';)
492			if(c == ' ')
493				indent++;
494			else if(c == '\t')
495				indent += 8;
496			else
497				break;
498		if(indent <= level){
499			b.seek(fp, 0);
500			lineno--;
501			return;
502		}
503	}
504}
505
506getfile(old: ref File): (ref File, big)
507{
508	f: ref File;
509	p, elem: string;
510	c: int;
511
512	if(indent < 0)
513		return (nil, big 0);
514	fp := b.offset();
515	do {
516		indent = 0;
517		p = b.gets('\n');
518		lineno++;
519		if(p == nil){
520			indent = -1;
521			return (nil, big 0);
522		}
523		for(; (c = p[0]) != '\n'; p = p[1:])
524			if(c == ' ')
525				indent++;
526			else if(c == '\t')
527				indent += 8;
528			else
529				break;
530	} while(c == '\n' || c == '#');
531	f = ref File;
532	(elem, p) = getname(p);
533	if(debug)
534		fprint(stderr, "getfile: %q root %q\n", elem, old.new);
535	f.new = mkpath(old.new, elem);
536	(nil, f.elem) = str->splitr(f.new, "/");
537	if(f.elem == nil)
538		error(sys->sprint("can't find file name component of %q", f.new));
539	(f.mode, p) = getmode(p);
540	(f.uid, p) = getname(p);
541	if(f.uid == nil)
542		f.uid = "-";
543	(f.gid, p) = getname(p);
544	if(f.gid == nil)
545		f.gid = "-";
546	f.old = getpath(p);
547	if(f.old == "-")
548		f.old = nil;
549	setnames(f);
550
551	if(debug)
552		printfile(f);
553
554	return (f, fp);
555}
556
557getpath(p: string): string
558{
559	for(i := 0; i < len p && (p[i] == ' ' || p[i] == '\t'); i++)
560		;
561	for(n := i; n < len p && (c := p[n]) != '\n' && c != ' ' && c != '\t'; n++)
562		;
563	return p[i:n];
564}
565
566getname(p: string): (string, string)
567{
568	for(i := 0; i < len p && (p[0] == ' ' || p[0] == '\t'); i++)
569		;
570	s := "";
571	quoted := 0;
572	for(; i < len p && (c := p[i]) != '\n' && (c != ' ' && c != '\t' || quoted); i++){
573		if(c == '\''){
574			if(i+1 >= len p || p[i+1] != '\''){
575				quoted = !quoted;
576				continue;
577			}
578			i++;
579		}
580		s[len s] = c;
581	}
582	if(len s > 0 && s[0] == '$'){
583		s = getenv(s[1:]);
584		if(s == nil)
585			error(sys->sprint("can't read environment variable %q", s));
586	}
587	return (s, p[i:]);
588}
589
590getenv(s: string): string
591{
592	if(s == "user")
593		return getuser();
594	return readfile("/env/"+s);
595}
596
597getuser(): string
598{
599	return readfile("/dev/user");
600}
601
602readfile(f: string): string
603{
604	fd := sys->open(f, Sys->OREAD);
605	if(fd != nil){
606		a := array[256] of byte;
607		n := sys->read(fd, a, len a);
608		if(n > 0)
609			return string a[0:n];
610	}
611	return nil;
612}
613
614getmode(p: string): (int, string)
615{
616	s: string;
617
618	(s, p) = getname(p);
619	if(s == nil || s == "-")
620		return (~0, p);
621	os := s;
622	m := 0;
623	if(s[0] == 'd'){
624		m |= Sys->DMDIR;
625		s = s[1:];
626	}
627	if(s[0] == 'a'){
628		m |= Sys->DMAPPEND;
629		s = s[1:];
630	}
631	if(s[0] == 'l'){
632		m |= Sys->DMEXCL;
633		s = s[1:];
634	}
635
636	for(i:=0; i<len s || i < 3; i++)
637		if(i >= len s || !(s[i]>='0' && s[i]<='7')){
638			warn(sys->sprint("bad mode specification %s", os));
639			return (~0, p);
640		}
641	(v, nil) := str->toint(s, 8);
642	return (m|v, p);
643}
644
645setusers()
646{
647	if(fskind != Kfs)
648		return;
649	file := ref File;
650	m := modes;
651	modes = 1;
652	file.uid = "adm";
653	file.gid = "adm";
654	file.mode = Sys->DMDIR|8r775;
655	file.new = "/adm";
656	file.elem = "adm";
657	file.old = nil;
658	setnames(file);
659	mkfile(file);
660	file.new = "/adm/users";
661	file.old = users;
662	file.elem = "users";
663	file.mode = 8r664;
664	setnames(file);
665	mkfile(file);
666	kfscmd("user");
667	mkfile(file);
668	file.mode = Sys->DMDIR|8r775;
669	file.new = "/adm";
670	file.old = "/adm";
671	file.elem = "adm";
672	setnames(file);
673	mkfile(file);
674	modes = m;
675}
676
677openkfscmd(name: string)
678{
679	if(fskind != Kfs)
680		return;
681	kname := sys->sprint("/chan/kfs.%s.cmd", name);
682	sfd = sys->open(kname, Sys->ORDWR);
683	if(sfd == nil){
684		fprint(stderr, "%s: can't open %q: %r\n", prog, kname);
685		quit("open kfscmd");
686	}
687}
688
689kfscmd(cmd: string)
690{
691	if(fskind != Kfs || sfd == nil)
692		return;
693	a := array of byte cmd;
694	if(sys->write(sfd, a, len a) != len a){
695		fprint(stderr, "%s: error writing %s: %r", prog, cmd);
696		return;
697	}
698	for(;;){
699		reply := array[4*1024] of byte;
700		n := sys->read(sfd, reply, len reply);
701		if(n <= 0)
702			return;
703		s := string reply[0:n];
704		if(s == "done" || s == "success")
705			return;
706		if(s == "unknown command"){
707			fprint(stderr, "%s: command %s not recognized\n", prog, cmd);
708			return;
709		}
710	}
711}
712
713error(s: string)
714{
715	fprint(stderr, "%s: %s:%d: %s\n", prog, proto, lineno, s);
716	kfscmd("disallow");
717	kfscmd("sync");
718	quit("error");
719}
720
721warn(s: string)
722{
723	fprint(stderr, "%s: %s:%d: %s\n", prog, proto, lineno, s);
724}
725
726printfile(f: ref File)
727{
728	if(f.old != nil)
729		fprint(stderr, "%q from %q %q %q %uo\n", f.new, f.old, f.uid, f.gid, f.mode);
730	else
731		fprint(stderr, "%q %q %q %uo\n", f.new, f.uid, f.gid, f.mode);
732}
733