xref: /inferno-os/appl/cmd/disk/prep/prep.b (revision a6011949be081a8fe1bec0713ce60c36beb3a351)
1implement Prep;
2
3#
4# prepare plan 9/inferno disk partition
5#
6
7include "sys.m";
8	sys: Sys;
9
10include "draw.m";
11
12include "bufio.m";
13	bufio: Bufio;
14	Iobuf: import bufio;
15
16include "disks.m";
17	disks: Disks;
18	Disk: import disks;
19
20include "pedit.m";
21	pedit: Pedit;
22	Edit, Part: import pedit;
23
24include "arg.m";
25
26Prep: module
27{
28	init:	fn(nil: ref Draw->Context, nil: list of string);
29};
30
31blank := 0;
32file := 0;
33doauto := 0;
34printflag := 0;
35opart: array of ref Part;
36secbuf: array of byte;
37osecbuf: array of byte;
38zeroes: array of byte;
39rdonly := 0;
40dowrite := 0;
41
42Prepedit: type Edit[string];
43
44edit: ref Edit;
45
46Auto: adt
47{
48	name:	string;
49	min:		big;
50	max:		big;
51	weight:	int;
52	alloc:	int;
53	size:		big;
54};
55
56KB: con big 1024;
57MB: con KB*KB;
58GB: con KB*MB;
59
60#
61# Order matters -- this is the layout order on disk.
62#
63auto: array of Auto = array[] of {
64	("9fat",		big 10*MB,	big 100*MB,	10, 0, big 0),
65	("nvram",	big 512,	big 512,	1, 0, big 0),
66	("fscfg",	big 512,	big 512,	1, 0, big 0),
67	("fs",		big 200*MB,	big 0,	10, 0, big 0),
68	("fossil",	big 200*MB,	big 0,	4, 0, big 0),
69	("arenas",	big 500*MB,	big 0,	20, 0, big 0),
70	("isect",	big 25*MB,	big 0,	1, 0, big 0),
71	("other",	big 200*MB,	big 0,	4, 0, big 0),
72	("swap",		big 100*MB,	big 512*MB,	1, 0, big 0),
73	("cache",	big 50*MB,	big 1*GB,	2, 0, big 0),
74};
75
76stderr: ref Sys->FD;
77
78init(nil: ref Draw->Context, args: list of string)
79{
80	sys = load Sys Sys->PATH;
81	bufio = load Bufio Bufio->PATH;
82	disks = load Disks Disks->PATH;
83	pedit = load Pedit Pedit->PATH;
84
85	sys->pctl(Sys->FORKFD, nil);
86	disks->init();
87	pedit->init();
88
89	edit = Edit.mk("sector");
90
91	edit.add = cmdadd;
92	edit.del = cmddel;
93	edit.okname = cmdokname;
94	edit.sum = cmdsum;
95	edit.write = cmdwrite;
96
97	stderr = sys->fildes(2);
98	secsize := 0;
99	arg := load Arg Arg->PATH;
100	arg->init(args);
101	arg->setusage("disk/prep [-bfprw] [-a partname]... [-s sectorsize] /dev/sdC0/plan9");
102	while((o := arg->opt()) != 0)
103		case o {
104		'a' =>
105			p := arg->earg();
106			for(i:=0; i<len auto; i++){
107				if(p == auto[i].name){
108					if(auto[i].alloc){
109						sys->fprint(stderr, "you said -a %s more than once.\n", p);
110						arg->usage();
111					}
112					auto[i].alloc = 1;
113					break;
114				}
115			}
116			if(i == len auto){
117				sys->fprint(stderr, "don't know how to create automatic partition %s\n", p);
118				arg->usage();
119			}
120			doauto = 1;
121		'b' =>
122			blank++;
123		'f' =>
124			file++;
125		'p' =>
126			printflag++;
127			rdonly++;
128		'r' =>
129			rdonly++;
130		's' =>
131			secsize = int arg->earg();
132		'w' =>
133			dowrite++;
134		* =>
135			arg->usage();
136		}
137	args = arg->argv();
138	if(len args != 1)
139		arg->usage();
140	arg = nil;
141
142	mode := Sys->ORDWR;
143	if(rdonly)
144		mode = Sys->OREAD;
145	disk := Disk.open(hd args, mode, file);
146	if(disk == nil) {
147		sys->fprint(stderr, "cannot open disk: %r\n");
148		exits("opendisk");
149	}
150
151	if(secsize != 0) {
152		disk.secsize = secsize;
153		disk.secs = disk.size / big secsize;
154	}
155	edit.end = disk.secs;
156
157	checkfat(disk);
158
159	secbuf = array[disk.secsize+1] of byte;
160	osecbuf = array[disk.secsize+1] of byte;
161	zeroes = array[disk.secsize+1] of {* => byte 0};
162	edit.disk = disk;
163
164	if(blank == 0)
165		rdpart(edit);
166
167	# save old partition table
168	opart = array[len edit.part] of ref Part;
169	opart[0:] = edit.part;
170
171	if(printflag) {
172		edit.runcmd("P");
173		exits(nil);
174	}
175
176	if(doauto)
177		autopart(edit);
178
179	if(dowrite) {
180		edit.runcmd("w");
181		exits(nil);
182	}
183
184	edit.runcmd("p");
185	for(;;) {
186		sys->fprint(stderr, ">>> ");
187		edit.runcmd(edit.getline());
188	}
189}
190
191cmdsum(edit: ref Edit, p: ref Part, a: big, b: big)
192{
193	c := ' ';
194	name := "empty";
195	if(p != nil){
196		if(p.changed)
197			c = '\'';
198		name = p.name;
199	}
200
201	sz := (b-a)*big edit.disk.secsize;
202	suf := "B ";
203	div := big 1;
204	if(sz >= big 1*GB){
205		suf = "GB";
206		div = GB;
207	}else if(sz >= big 1*MB){
208		suf = "MB";
209		div = MB;
210	}else if(sz >= big 1*KB){
211		suf = "KB";
212		div = KB;
213	}
214
215	if(div == big 1)
216		sys->print("%c %-12s %*bd %-*bd (%bd sectors, %bd %s)\n", c, name,
217			edit.disk.width, a, edit.disk.width, b, b-a, sz, suf);
218	else
219		sys->print("%c %-12s %*bd %-*bd (%bd sectors, %bd.%.2d %s)\n", c, name,
220			edit.disk.width, a, edit.disk.width, b, b-a,
221			sz/div, int (((sz%div)*big 100)/div), suf);
222}
223
224cmdadd(edit: ref Edit, name: string, start: big, end: big): string
225{
226	if(start < big 2 && name == "9fat")
227		return "overlaps with the pbs and/or the partition table";
228
229	return edit.addpart(mkpart(name, start, end, 1));
230}
231
232cmddel(edit: ref Edit, p: ref Part): string
233{
234	return edit.delpart(p);
235}
236
237cmdwrite(edit: ref Edit): string
238{
239	wrpart(edit);
240	return nil;
241}
242
243isfrog := array[256] of {
244	byte 1, byte 1, byte 1, byte 1, byte 1, byte 1, byte 1, byte 1,	# NUL
245	byte 1, byte 1, byte 1, byte 1, byte 1, byte 1, byte 1, byte 1,	# BKS
246	byte 1, byte 1, byte 1, byte 1, byte 1, byte 1, byte 1, byte 1,	# DLE
247	byte 1, byte 1, byte 1, byte 1, byte 1, byte 1, byte 1, byte 1,	# CAN
248	' ' =>	byte 1,
249	'/' =>	byte 1,
250	16r7f=>	byte 1,
251	* => byte 0
252};
253
254cmdokname(nil: ref Edit, elem: string): string
255{
256	for(i := 0; i < len elem; i++)
257		if(int isfrog[elem[i]])
258			return "bad character in name";
259	return nil;
260}
261
262mkpart(name: string, start: big, end: big, changed: int): ref Part
263{
264	p := ref Part;
265	p.name = name;
266	p.ctlname = name;
267	p.start = start;
268	p.end = end;
269	p.changed = changed;
270	p.ctlstart = big 0;
271	p.ctlend = big 0;
272	return p;
273}
274
275# plan9 partition table is first sector of the disk
276
277rdpart(edit: ref Edit)
278{
279	disk := edit.disk;
280	sys->seek(disk.fd, big disk.secsize, 0);
281	if(sys->readn(disk.fd, osecbuf, disk.secsize) != disk.secsize)
282		return;
283	osecbuf[disk.secsize] = byte 0;
284	secbuf[0:] = osecbuf;
285
286	for(i := 0; i < disk.secsize; i++)
287		if(secbuf[i] == byte 0)
288			break;
289
290	tab := string secbuf[0:i];
291	if(len tab < 4 || tab[0:4] != "part"){
292		sys->fprint(stderr, "no plan9 partition table found\n");
293		return;
294	}
295
296	waserr := 0;
297	(nline, lines) := sys->tokenize(tab, "\n");
298	for(i=0; i<nline; i++){
299		line := hd lines;
300		lines = tl lines;
301		if(len line < 4 || line[0:4] != "part"){
302			waserr = 1;
303			continue;
304		}
305
306		(nf, f) := sys->tokenize(line, " \t\r");
307		if(nf != 4 || hd f != "part"){
308			waserr = 1;
309			continue;
310		}
311
312		a := big hd tl tl f;
313		b := big hd tl tl tl f;
314		if(a >= b){
315			waserr = 1;
316			continue;
317		}
318
319		if((err := edit.addpart(mkpart(hd tl f, a, b, 0))) != nil) {
320			sys->fprint(stderr, "?%s: not continuing\n", err);
321			exits("partition");
322		}
323	}
324	if(waserr)
325		sys->fprint(stderr, "syntax error reading partition\n");
326}
327
328min(a, b: big): big
329{
330	if(a < b)
331		return a;
332	return b;
333}
334
335autopart(edit: ref Edit)
336{
337	if(len edit.part > 0) {
338		if(doauto)
339			sys->fprint(stderr, "partitions already exist; not repartitioning\n");
340		return;
341	}
342
343	secs := edit.disk.secs;
344	secsize := big edit.disk.secsize;
345	for(;;){
346		# compute total weights
347		totw := 0;
348		for(i:=0; i<len auto; i++){
349			if(auto[i].alloc==0 || auto[i].size != big 0)
350				continue;
351			totw += auto[i].weight;
352		}
353		if(totw == 0)
354			break;
355
356		if(secs <= big 0){
357			sys->fprint(stderr, "ran out of disk space during autopartition.\n");
358			return;
359		}
360
361		# assign any minimums for small disks
362		futz := 0;
363		for(i=0; i<len auto; i++){
364			if(auto[i].alloc==0 || auto[i].size != big 0)
365				continue;
366			s := (secs*big auto[i].weight)/big totw;
367			if(s < big auto[i].min/secsize){
368				auto[i].size = big auto[i].min/secsize;
369				secs -= auto[i].size;
370				futz = 1;
371				break;
372			}
373		}
374		if(futz)
375			continue;
376
377		# assign any maximums for big disks
378		futz = 0;
379		for(i=0; i<len auto; i++){
380			if(auto[i].alloc==0 || auto[i].size != big 0)
381				continue;
382			s := (secs*big auto[i].weight)/big totw;
383			if(auto[i].max != big 0 && s > auto[i].max/secsize){
384				auto[i].size = auto[i].max/secsize;
385				secs -= auto[i].size;
386				futz = 1;
387				break;
388			}
389		}
390		if(futz)
391			continue;
392
393		# finally, assign partition sizes according to weights
394		for(i=0; i<len auto; i++){
395			if(auto[i].alloc==0 || auto[i].size != big 0)
396				continue;
397			s := (secs*big auto[i].weight)/big totw;
398			auto[i].size = s;
399
400			# use entire disk even in face of rounding errors
401			secs -= auto[i].size;
402			totw -= auto[i].weight;
403		}
404	}
405
406	for(i:=0; i<len auto; i++)
407		if(auto[i].alloc)
408			sys->print("%s %bud\n", auto[i].name, auto[i].size);
409
410	s := big 0;
411	for(i=0; i<len auto; i++){
412		if(auto[i].alloc == 0)
413			continue;
414		if((err := edit.addpart(mkpart(auto[i].name, s, s+auto[i].size, 1))) != nil)
415			sys->fprint(stderr, "addpart %s: %s\n", auto[i].name, err);
416		s += auto[i].size;
417	}
418}
419
420restore(edit: ref Edit, ctlfd: ref Sys->FD)
421{
422	offset := edit.disk.offset;
423	sys->fprint(stderr, "attempting to restore partitions to previous state\n");
424	if(sys->seek(edit.disk.wfd, big edit.disk.secsize, 0) != big 0){
425		sys->fprint(stderr, "cannot restore: error seeking on disk: %r\n");
426		exits("inconsistent");
427	}
428
429	if(sys->write(edit.disk.wfd, osecbuf, edit.disk.secsize) != edit.disk.secsize){
430		sys->fprint(stderr, "cannot restore: couldn't write old partition table to disk: %r\n");
431		exits("inconsistent");
432	}
433
434	if(ctlfd != nil){
435		for(i:=0; i<len edit.part; i++)
436			sys->fprint(ctlfd, "delpart %s", edit.part[i].name);
437		for(i=0; i<len opart; i++){
438			if(sys->fprint(ctlfd, "part %s %bd %bd", opart[i].name, opart[i].start+offset, opart[i].end+offset) < 0){
439				sys->fprint(stderr, "restored disk partition table but not kernel table; reboot\n");
440				exits("inconsistent");
441			}
442		}
443	}
444	exits("restored");
445}
446
447wrpart(edit: ref Edit)
448{
449	disk := edit.disk;
450
451	secbuf[0:] = zeroes;
452	n := 0;
453	for(i:=0; i<len edit.part; i++){
454		a := sys->aprint("part %s %bd %bd\n",
455			edit.part[i].name, edit.part[i].start, edit.part[i].end);
456		if(n + len a > disk.secsize){
457			sys->fprint(stderr, "partition table bigger than sector (%d bytes)\n", disk.secsize);
458			exits("overflow");
459		}
460		secbuf[n:] = a;
461		n += len a;
462	}
463
464	if(sys->seek(disk.wfd, big disk.secsize, 0) != big disk.secsize){
465		sys->fprint(stderr, "error seeking to %d on disk: %r\n", disk.secsize);
466		exits("seek");
467	}
468
469	if(sys->write(disk.wfd, secbuf, disk.secsize) != disk.secsize){
470		sys->fprint(stderr, "error writing partition table to disk: %r\n");
471		restore(edit, nil);
472	}
473
474	if(edit.ctldiff(disk.ctlfd) < 0)
475		sys->fprint(stderr, "?warning: partitions could not be updated in devsd\n");
476}
477
478#
479# Look for a boot sector in sector 1, as would be
480# the case if editing /dev/sdC0/data when that
481# was really a bootable disk.
482#
483checkfat(disk: ref Disk)
484{
485	buf := array[32] of byte;
486
487	if(sys->seek(disk.fd, big disk.secsize, 0) != big disk.secsize ||
488	   sys->read(disk.fd, buf, len buf) < len buf)
489		return;
490
491	if(buf[0] != byte 16rEB || buf[1] != byte 16r3C || buf[2] != byte 16r90)
492		return;
493
494	sys->fprint(stderr,
495		"there's a fat partition where the\n"+
496		"plan9 partition table would go.\n"+
497		"if you really want to overwrite it, zero\n"+
498		"the second sector of the disk and try again\n");
499
500	exits("fat partition");
501}
502
503exits(s: string)
504{
505	if(s != nil)
506		raise "fail:"+s;
507	exit;
508}
509