code: 9ferno

ref: 83246e296ea433b65b9d295b5e08fedd39ff1ab7
dir: /appl/cmd/disk/prep/prep.b/

View raw version
implement Prep;

#
# prepare plan 9/inferno disk partition
#

include "sys.m";
	sys: Sys;

include "draw.m";

include "bufio.m";
	bufio: Bufio;
	Iobuf: import bufio;

include "disks.m";
	disks: Disks;
	Disk: import disks;

include "pedit.m";
	pedit: Pedit;
	Edit, Part: import pedit;

include "arg.m";

Prep: module
{
	init:	fn(nil: ref Draw->Context, nil: list of string);
};

blank := 0;
file := 0;
doauto := 0;
printflag := 0;
opart: array of ref Part;
secbuf: array of byte;
osecbuf: array of byte;
zeroes: array of byte;
rdonly := 0;
dowrite := 0;

Prepedit: type Edit[string];

edit: ref Edit;

Auto: adt
{
	name:	string;
	min:		big;
	max:		big;
	weight:	int;
	alloc:	int;
	size:		big;
};

KB: con big 1024;
MB: con KB*KB;
GB: con KB*MB;

#
# Order matters -- this is the layout order on disk.
#
auto: array of Auto = array[] of {
	("9fat",		big 10*MB,	big 100*MB,	10, 0, big 0),
	("nvram",	big 512,	big 512,	1, 0, big 0),
	("fscfg",	big 512,	big 512,	1, 0, big 0),
	("fs",		big 200*MB,	big 0,	10, 0, big 0),
	("fossil",	big 200*MB,	big 0,	4, 0, big 0),
	("arenas",	big 500*MB,	big 0,	20, 0, big 0),
	("isect",	big 25*MB,	big 0,	1, 0, big 0),
	("other",	big 200*MB,	big 0,	4, 0, big 0),
	("swap",		big 100*MB,	big 512*MB,	1, 0, big 0),
	("cache",	big 50*MB,	big 1*GB,	2, 0, big 0),
};

stderr: ref Sys->FD;

init(nil: ref Draw->Context, args: list of string)
{
	sys = load Sys Sys->PATH;
	bufio = load Bufio Bufio->PATH;
	disks = load Disks Disks->PATH;
	pedit = load Pedit Pedit->PATH;

	sys->pctl(Sys->FORKFD, nil);
	disks->init();
	pedit->init();

	edit = Edit.mk("sector");

	edit.add = cmdadd;
	edit.del = cmddel;
	edit.okname = cmdokname;
	edit.sum = cmdsum;
	edit.write = cmdwrite;

	stderr = sys->fildes(2);
	secsize := 0;
	arg := load Arg Arg->PATH;
	arg->init(args);
	arg->setusage("disk/prep [-bfprw] [-a partname]... [-s sectorsize] /dev/sdC0/plan9");
	while((o := arg->opt()) != 0)
		case o {
		'a' =>
			p := arg->earg();
			for(i:=0; i<len auto; i++){
				if(p == auto[i].name){
					if(auto[i].alloc){
						sys->fprint(stderr, "you said -a %s more than once.\n", p);
						arg->usage();
					}
					auto[i].alloc = 1;
					break;
				}
			}
			if(i == len auto){
				sys->fprint(stderr, "don't know how to create automatic partition %s\n", p);
				arg->usage();
			}
			doauto = 1;
		'b' =>
			blank++;
		'f' =>
			file++;
		'p' =>
			printflag++;
			rdonly++;
		'r' =>
			rdonly++;
		's' =>
			secsize = int arg->earg();
		'w' =>
			dowrite++;
		* =>
			arg->usage();
		}
	args = arg->argv();
	if(len args != 1)
		arg->usage();
	arg = nil;

	mode := Sys->ORDWR;
	if(rdonly)
		mode = Sys->OREAD;
	disk := Disk.open(hd args, mode, file);
	if(disk == nil) {
		sys->fprint(stderr, "cannot open disk: %r\n");
		exits("opendisk");
	}

	if(secsize != 0) {
		disk.secsize = secsize;
		disk.secs = disk.size / big secsize;
	}
	edit.end = disk.secs;

	checkfat(disk);

	secbuf = array[disk.secsize+1] of byte;
	osecbuf = array[disk.secsize+1] of byte;
	zeroes = array[disk.secsize+1] of {* => byte 0};
	edit.disk = disk;

	if(blank == 0)
		rdpart(edit);

	# save old partition table
	opart = array[len edit.part] of ref Part;
	opart[0:] = edit.part;

	if(printflag) {
		edit.runcmd("P");
		exits(nil);
	}

	if(doauto)
		autopart(edit);

	if(dowrite) {
		edit.runcmd("w");
		exits(nil);
	}

	edit.runcmd("p");
	for(;;) {
		sys->fprint(stderr, ">>> ");
		edit.runcmd(edit.getline());
	}
}

cmdsum(edit: ref Edit, p: ref Part, a: big, b: big)
{
	c := ' ';
	name := "empty";
	if(p != nil){
		if(p.changed)
			c = '\'';
		name = p.name;
	}

	sz := (b-a)*big edit.disk.secsize;
	suf := "B ";
	div := big 1;
	if(sz >= big 1*GB){
		suf = "GB";
		div = GB;
	}else if(sz >= big 1*MB){
		suf = "MB";
		div = MB;
	}else if(sz >= big 1*KB){
		suf = "KB";
		div = KB;
	}

	if(div == big 1)
		sys->print("%c %-12s %*bd %-*bd (%bd sectors, %bd %s)\n", c, name,
			edit.disk.width, a, edit.disk.width, b, b-a, sz, suf);
	else
		sys->print("%c %-12s %*bd %-*bd (%bd sectors, %bd.%.2d %s)\n", c, name,
			edit.disk.width, a, edit.disk.width, b, b-a,
			sz/div, int (((sz%div)*big 100)/div), suf);
}

cmdadd(edit: ref Edit, name: string, start: big, end: big): string
{
	if(start < big 2 && name == "9fat")
		return "overlaps with the pbs and/or the partition table";

	return edit.addpart(mkpart(name, start, end, 1));
}

cmddel(edit: ref Edit, p: ref Part): string
{
	return edit.delpart(p);
}

cmdwrite(edit: ref Edit): string
{
	wrpart(edit);
	return nil;
}

isfrog := array[256] of {
	byte 1, byte 1, byte 1, byte 1, byte 1, byte 1, byte 1, byte 1,	# NUL
	byte 1, byte 1, byte 1, byte 1, byte 1, byte 1, byte 1, byte 1,	# BKS
	byte 1, byte 1, byte 1, byte 1, byte 1, byte 1, byte 1, byte 1,	# DLE
	byte 1, byte 1, byte 1, byte 1, byte 1, byte 1, byte 1, byte 1,	# CAN
	' ' =>	byte 1,
	'/' =>	byte 1,
	16r7f=>	byte 1,
	* => byte 0
};

cmdokname(nil: ref Edit, elem: string): string
{
	for(i := 0; i < len elem; i++)
		if(int isfrog[elem[i]])
			return "bad character in name";
	return nil;
}

mkpart(name: string, start: big, end: big, changed: int): ref Part
{
	p := ref Part;
	p.name = name;
	p.ctlname = name;
	p.start = start;
	p.end = end;
	p.changed = changed;
	p.ctlstart = big 0;
	p.ctlend = big 0;
	return p;
}

# plan9 partition table is first sector of the disk

rdpart(edit: ref Edit)
{
	disk := edit.disk;
	sys->seek(disk.fd, big disk.secsize, 0);
	if(sys->readn(disk.fd, osecbuf, disk.secsize) != disk.secsize)
		return;
	osecbuf[disk.secsize] = byte 0;
	secbuf[0:] = osecbuf;

	for(i := 0; i < disk.secsize; i++)
		if(secbuf[i] == byte 0)
			break;

	tab := string secbuf[0:i];
	if(len tab < 4 || tab[0:4] != "part"){
		sys->fprint(stderr, "no plan9 partition table found\n");
		return;
	}

	waserr := 0;
	(nline, lines) := sys->tokenize(tab, "\n");
	for(i=0; i<nline; i++){
		line := hd lines;
		lines = tl lines;
		if(len line < 4 || line[0:4] != "part"){
			waserr = 1;
			continue;
		}

		(nf, f) := sys->tokenize(line, " \t\r");
		if(nf != 4 || hd f != "part"){
			waserr = 1;
			continue;
		}

		a := big hd tl tl f;
		b := big hd tl tl tl f;
		if(a >= b){
			waserr = 1;
			continue;
		}

		if((err := edit.addpart(mkpart(hd tl f, a, b, 0))) != nil) {
			sys->fprint(stderr, "?%s: not continuing\n", err);
			exits("partition");
		}
	}
	if(waserr)
		sys->fprint(stderr, "syntax error reading partition\n");
}

min(a, b: big): big
{
	if(a < b)
		return a;
	return b;
}

autopart(edit: ref Edit)
{
	if(len edit.part > 0) {
		if(doauto)
			sys->fprint(stderr, "partitions already exist; not repartitioning\n");
		return;
	}

	secs := edit.disk.secs;
	secsize := big edit.disk.secsize;
	for(;;){
		# compute total weights
		totw := 0;
		for(i:=0; i<len auto; i++){
			if(auto[i].alloc==0 || auto[i].size != big 0)
				continue;
			totw += auto[i].weight;
		}
		if(totw == 0)
			break;

		if(secs <= big 0){
			sys->fprint(stderr, "ran out of disk space during autopartition.\n");
			return;
		}

		# assign any minimums for small disks
		futz := 0;
		for(i=0; i<len auto; i++){
			if(auto[i].alloc==0 || auto[i].size != big 0)
				continue;
			s := (secs*big auto[i].weight)/big totw;
			if(s < big auto[i].min/secsize){
				auto[i].size = big auto[i].min/secsize;
				secs -= auto[i].size;
				futz = 1;
				break;
			}
		}
		if(futz)
			continue;

		# assign any maximums for big disks
		futz = 0;
		for(i=0; i<len auto; i++){
			if(auto[i].alloc==0 || auto[i].size != big 0)
				continue;
			s := (secs*big auto[i].weight)/big totw;
			if(auto[i].max != big 0 && s > auto[i].max/secsize){
				auto[i].size = auto[i].max/secsize;
				secs -= auto[i].size;
				futz = 1;
				break;
			}
		}
		if(futz)
			continue;

		# finally, assign partition sizes according to weights
		for(i=0; i<len auto; i++){
			if(auto[i].alloc==0 || auto[i].size != big 0)
				continue;
			s := (secs*big auto[i].weight)/big totw;
			auto[i].size = s;

			# use entire disk even in face of rounding errors
			secs -= auto[i].size;
			totw -= auto[i].weight;
		}
	}

	for(i:=0; i<len auto; i++)
		if(auto[i].alloc)
			sys->print("%s %bud\n", auto[i].name, auto[i].size);

	s := big 0;
	for(i=0; i<len auto; i++){
		if(auto[i].alloc == 0)
			continue;
		if((err := edit.addpart(mkpart(auto[i].name, s, s+auto[i].size, 1))) != nil)
			sys->fprint(stderr, "addpart %s: %s\n", auto[i].name, err);
		s += auto[i].size;
	}
}

restore(edit: ref Edit, ctlfd: ref Sys->FD)
{
	offset := edit.disk.offset;
	sys->fprint(stderr, "attempting to restore partitions to previous state\n");
	if(sys->seek(edit.disk.wfd, big edit.disk.secsize, 0) != big 0){
		sys->fprint(stderr, "cannot restore: error seeking on disk: %r\n");
		exits("inconsistent");
	}

	if(sys->write(edit.disk.wfd, osecbuf, edit.disk.secsize) != edit.disk.secsize){
		sys->fprint(stderr, "cannot restore: couldn't write old partition table to disk: %r\n");
		exits("inconsistent");
	}

	if(ctlfd != nil){
		for(i:=0; i<len edit.part; i++)
			sys->fprint(ctlfd, "delpart %s", edit.part[i].name);
		for(i=0; i<len opart; i++){
			if(sys->fprint(ctlfd, "part %s %bd %bd", opart[i].name, opart[i].start+offset, opart[i].end+offset) < 0){
				sys->fprint(stderr, "restored disk partition table but not kernel table; reboot\n");
				exits("inconsistent");
			}
		}
	}
	exits("restored");
}

wrpart(edit: ref Edit)
{
	disk := edit.disk;

	secbuf[0:] = zeroes;
	n := 0;
	for(i:=0; i<len edit.part; i++){
		a := sys->aprint("part %s %bd %bd\n", 
			edit.part[i].name, edit.part[i].start, edit.part[i].end);
		if(n + len a > disk.secsize){
			sys->fprint(stderr, "partition table bigger than sector (%d bytes)\n", disk.secsize);
			exits("overflow");
		}
		secbuf[n:] = a;
		n += len a;
	}

	if(sys->seek(disk.wfd, big disk.secsize, 0) != big disk.secsize){
		sys->fprint(stderr, "error seeking to %d on disk: %r\n", disk.secsize);
		exits("seek");
	}

	if(sys->write(disk.wfd, secbuf, disk.secsize) != disk.secsize){
		sys->fprint(stderr, "error writing partition table to disk: %r\n");
		restore(edit, nil);
	}

	if(edit.ctldiff(disk.ctlfd) < 0)
		sys->fprint(stderr, "?warning: partitions could not be updated in devsd\n");
}

#
# Look for a boot sector in sector 1, as would be
# the case if editing /dev/sdC0/data when that
# was really a bootable disk.
#
checkfat(disk: ref Disk)
{
	buf := array[32] of byte;

	if(sys->seek(disk.fd, big disk.secsize, 0) != big disk.secsize ||
	   sys->read(disk.fd, buf, len buf) < len buf)
		return;

	if(buf[0] != byte 16rEB || buf[1] != byte 16r3C || buf[2] != byte 16r90)
		return;

	sys->fprint(stderr, 
		"there's a fat partition where the\n"+
		"plan9 partition table would go.\n"+
		"if you really want to overwrite it, zero\n"+
		"the second sector of the disk and try again\n");

	exits("fat partition");
}

exits(s: string)
{
	if(s != nil)
		raise "fail:"+s;
	exit;
}