shithub: purgatorio

ref: a5cb451b299b03f44154fac5780b6a57ca130ce0
dir: /appl/cmd/disk/prep/pedit.b/

View raw version
implement Pedit;

#
# disk partition editor
#

include "sys.m";
	sys: Sys;

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

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

include "draw.m";
include "calc.tab.m";
	calc: Calc;

include "pedit.m";

Cmd: adt {
	c: int;
	f:	ref fn(e: ref Edit, a: array of string): string;
};

cmds: array of Cmd;

bin: ref Iobuf;

init()
{
	sys = load Sys Sys->PATH;
	calc = load Calc "/dis/disk/calc.tab.dis";
	bufio = load Bufio Bufio->PATH;
	disks = load Disks Disks->PATH;
	disks->init();

	bin = bufio->fopen(sys->fildes(0), Bufio->OREAD);
	cmds = array[] of {
		('.',	editdot),
		('a',	editadd),
		('d',	editdel),
		('?',	edithelp),
		('h',	edithelp),
		('P',	editctlprint),
		('p',	editprint),
		('w',	editwrite),
		('q',	editquit),
	};
}

Edit.mk(unit: string): ref Edit
{
	e := ref Edit;
	e.unit = unit;
	e.dot = big 0;
	e.end = big 0;
	e.changed = 0;
	e.warned = 0;
	e.lastcmd = 0;
	return e;
}

Edit.getline(edit: self ref Edit): string
{
	p := bin.gets('\n');
	if(p == nil){
		if(edit.changed)
			sys->fprint(sys->fildes(2), "?warning: changes not written\n");
		exit;
	}
	for(i := 0; i < len p; i++)
		if(!isspace(p[i]))
			break;
	if(i)
		return p[i:];
	return p;
}

Edit.findpart(edit: self ref Edit, name: string): ref Part
{
	for(i:=0; i<len edit.part; i++)
		if(edit.part[i].name == name)
			return edit.part[i];
	return nil;
}

okname(edit: ref Edit, name: string): string
{
	if(name[0] == '\0')
		return "partition has no name";

	for(i:=0; i<len edit.part; i++) {
		if(name == edit.part[i].name)
			return sys->sprint("already have partition with name '%s'", name);
	}
	return nil;
}

Edit.addpart(edit: self ref Edit, p: ref Part): string
{
	if((err := okname(edit, p.name)) != nil)
		return err;

	for(i:=0; i<len edit.part; i++) {
		if(p.start < edit.part[i].end && edit.part[i].start < p.end) {
			msg := sys->sprint("\"%s\" %bd-%bd overlaps with \"%s\" %bd-%bd",
				p.name, p.start, p.end,
				edit.part[i].name, edit.part[i].start, edit.part[i].end);
		#	return msg;
		}
	}

	if(len edit.part >= Maxpart)
		return "too many partitions";

	pa := array[i+1] of ref Part;
	pa[0:] = edit.part;
	edit.part = pa;

	edit.part[i] = p;
	for(; i > 0 && p.start < edit.part[i-1].start; i--) {
		edit.part[i] = edit.part[i-1];
		edit.part[i-1] = p;
	}

	if(p.changed)
		edit.changed = 1;
	return nil;
}

Edit.delpart(edit: self ref Edit, p: ref Part): string
{
	n := len edit.part;
	for(i:=0; i<n; i++)
		if(edit.part[i] == p)
			break;
	if(i >= n)
		raise "internal error: Part not found";
	n--;
	pa := array[n] of ref Part;
	if(n){
		pa[0:] = edit.part[0:i];
		if(i != n)
			pa[i:] = edit.part[i+1:];
	}
	edit.part = pa;
	edit.changed = 1;
	return nil;
}

editdot(edit: ref Edit, argv: array of string): string
{
	if(len argv == 1) {
		sys->print("\t. %bd\n", edit.dot);
		return nil;
	}

	if(len argv > 2)
		return "args";

	(ndot, err) := calc->parseexpr(argv[1], edit.dot, edit.end, edit.end);
	if(err != nil)
		return err;

	edit.dot = ndot;
	return nil;
}

editadd(edit: ref Edit, argv: array of string): string
{
	if(len argv < 2)
		return "args";

	name := argv[1];
	if((err := okname(edit, name)) != nil || edit.okname != nil && (err = edit.okname(edit, name)) != nil)
		return err;

	if(len argv >= 3)
		q := argv[2];
	else {
		sys->fprint(sys->fildes(2), "start %s: ", edit.unit);
		q = edit.getline();
	}
	start: big;
	(start, err) = calc->parseexpr(q, edit.dot, edit.end, edit.end);
	if(err != nil)
		return err;

	if(start < big 0 || start >= edit.end)
		return "start out of range";

	for(i:=0; i < len edit.part; i++) {
		if(edit.part[i].start <= start && start < edit.part[i].end)
			return sys->sprint("start %s in partition '%s'", edit.unit, edit.part[i].name);
	}

	maxend := edit.end;
	for(i=0; i < len edit.part; i++)
		if(start < edit.part[i].start && edit.part[i].start < maxend)
			maxend = edit.part[i].start;

	if(len argv >= 4)
		q = argv[3];
	else {
		sys->fprint(sys->fildes(2), "end [%bd..%bd] ", start, maxend);
		q = edit.getline();
	}
	end: big;
	(end, err) = calc->parseexpr(q, edit.dot, maxend, edit.end);
	if(err != nil)
		return err;

	if(start == end)
		return "size zero partition";

	if(end <= start || end > maxend)
		return "end out of range";

	if(len argv > 4)
		return "args";

	if((err = edit.add(edit, name, start, end)) != nil)
		return err;

	edit.dot = end;
	return nil;
}

editdel(edit: ref Edit, argv: array of string): string
{
	if(len argv != 2)
		return "args";

	if((p := edit.findpart(argv[1])) == nil)
		return "no such partition";

	return edit.del(edit, p);
}

helptext :=
	". [newdot] - display or set value of dot\n"+
	"a name [start [end]] - add partition\n"+
	"d name - delete partition\n"+
	"h - sys->print help message\n"+
	"p - sys->print partition table\n"+
	"P - sys->print commands to update sd(3) device\n"+
	"w - write partition table\n"+
	"q - quit\n";

edithelp(edit: ref Edit, nil: array of string): string
{
	sys->print("%s", helptext);
	if(edit.help != nil)
		return edit.help(edit);
	return nil;
}

editprint(edit: ref Edit, argv: array of string): string
{
	if(len argv != 1)
		return "args";

	lastend := big 0;
	part := edit.part;
	for(i:=0; i<len edit.part; i++) {
		if(lastend < part[i].start)
			edit.sum(edit, nil, lastend, part[i].start);
		edit.sum(edit, part[i], part[i].start, part[i].end);
		lastend = part[i].end;
	}
	if(lastend < edit.end)
		edit.sum(edit, nil, lastend, edit.end);
	return nil;
}

editwrite(edit: ref Edit, argv: array of string): string
{
	if(len argv != 1)
		return "args";

	if(edit.disk.rdonly)
		return "read only";

	err := edit.write(edit);
	if(err != nil)
		return err;
	for(i:=0; i<len edit.part; i++)
		edit.part[i].changed = 0;
	edit.changed = 0;
	return nil;
}

editquit(edit: ref Edit, argv: array of string): string
{
	if(len argv != 1) {
		edit.warned = 0;
		return "args";
	}

	if(edit.changed && (!edit.warned || edit.lastcmd != 'q')) {
		edit.warned = 1;
		return "changes unwritten";
	}

	exit;
}

editctlprint(edit: ref Edit, argv: array of string): string
{
	if(len argv != 1)
		return "args";

	if(edit.printctl != nil)
		edit.printctl(edit, sys->fildes(1));
	else
		edit.ctldiff(sys->fildes(1));
	return nil;
}

Edit.runcmd(edit: self ref Edit, cmd: string)
{
	(nf, fl) := sys->tokenize(cmd, " \t\n\r");
	if(nf < 1)
		return;
	f := array[nf] of string;
	for(nf = 0; fl != nil; fl = tl fl)
		f[nf++] = hd fl;
	if(len f[0] != 1) {
		sys->fprint(sys->fildes(2), "?\n");
		return;
	}

	err := "";
	for(i:=0; i<len cmds; i++) {
		if(cmds[i].c == f[0][0]) {
			op := cmds[i].f;
			err = op(edit, f);
			break;
		}
	}
	if(i == len cmds){
		if(edit.ext != nil)
			err = edit.ext(edit, f);
		else
			err = "unknown command";
	}
	if(err != nil) 
		sys->fprint(sys->fildes(2), "?%s\n", err);
	edit.lastcmd = f[0][0];
}

isspace(c: int): int
{
	return c == ' ' || c == '\t' || c == '\n' || c == '\r';
}

ctlmkpart(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.ctlstart = big 0;
	p.ctlend = big 0;
	p.changed = changed;
	return p;
}

rdctlpart(edit: ref Edit)
{
	disk := edit.disk;
	edit.ctlpart = array[0] of ref Part;
	sys->seek(disk.ctlfd, big 0, 0);
	buf := array[4096] of byte;
	if(sys->readn(disk.ctlfd, buf, len buf) <= 0)
		return;
	for(i := 0; i < len buf; i++)
		if(buf[i] == byte 0)
			break;

	(nline, lines) := sys->tokenize(string buf[0:i], "\n\r");
	edit.ctlpart = array[nline] of ref Part;	# upper bound
	npart := 0;
	for(i=0; i<nline; i++){
		line := hd lines;
		lines = tl lines;
		if(len line < 5 || line[0:5] != "part ")
			continue;

		(nf, f) := sys->tokenize(line, " \t");
		if(nf != 4 || hd f != "part")
			break;

		a := big hd tl tl f;
		b := big hd tl tl tl f;

		if(a >= b)
			break;

		# only gather partitions contained in the disk partition we are editing
		if(a < disk.offset ||  disk.offset+disk.secs < b)
			continue;

		a -= disk.offset;
		b -= disk.offset;

		# the partition we are editing does not count
		if(hd tl f == disk.part)
			continue;

		edit.ctlpart[npart++] = ctlmkpart(hd tl f, a, b, 0);
	}
	if(npart != len edit.ctlpart)
		edit.ctlpart = edit.ctlpart[0:npart];
}

ctlstart(p: ref Part): big
{
	if(p.ctlstart != big 0)
		return p.ctlstart;
	return p.start;
}

ctlend(p: ref Part): big
{
	if(p.ctlend != big 0)
		return p.ctlend;
	return p.end;
}

areequiv(p: ref Part, q: ref Part): int
{
	if(p.ctlname == nil || q.ctlname == nil)
		return 0;
	return p.ctlname == q.ctlname &&
			ctlstart(p) == ctlstart(q) && ctlend(p) == ctlend(q);
}

unchange(edit: ref Edit, p: ref Part)
{
	for(i:=0; i<len edit.ctlpart; i++) {
		q := edit.ctlpart[i];
		if(p.start <= q.start && q.end <= p.end)
			q.changed = 0;
	}
	if(p.changed)
		raise "internal error: Part unchanged";
}

Edit.ctldiff(edit: self ref Edit, ctlfd: ref Sys->FD): int
{
	rdctlpart(edit);

	# everything is bogus until we prove otherwise
	for(i:=0; i<len edit.ctlpart; i++)
		edit.ctlpart[i].changed = 1;

	#
	# partitions with same info have not changed,
	# and neither have partitions inside them.
	#
	for(i=0; i<len edit.ctlpart; i++)
		for(j:=0; j<len edit.part; j++)
			if(areequiv(edit.ctlpart[i], edit.part[j])) {
				unchange(edit, edit.ctlpart[i]);
				break;
			}

	waserr := 0;
	#
	# delete all the changed partitions except data (we'll add them back if necessary) 
	#
	for(i=0; i<len edit.ctlpart; i++) {
		p := edit.ctlpart[i];
		if(p.changed)
		if(sys->fprint(ctlfd, "delpart %s\n", p.ctlname)<0) {
			sys->fprint(sys->fildes(2), "delpart failed: %s: %r\n", p.ctlname);
			waserr = -1;
		}
	}

	#
	# add all the partitions from the real list;
	# this is okay since adding a partition with
	# information identical to what is there is a no-op.
	#
	offset := edit.disk.offset;
	for(i=0; i<len edit.part; i++) {
		p := edit.part[i];
		if(p.ctlname != nil) {
			if(sys->fprint(ctlfd, "part %s %bd %bd\n", p.ctlname, offset+ctlstart(p), offset+ctlend(p)) < 0) {
				sys->fprint(sys->fildes(2), "adding part failed: %s: %r\n", p.ctlname);
				waserr = -1;
			}
		}
	}
	return waserr;
}