551 lines
9.5 KiB
C
551 lines
9.5 KiB
C
/*
|
|
* disk partition editor
|
|
*/
|
|
#include <u.h>
|
|
#include <lib9.h>
|
|
#include <bio.h>
|
|
#include <ctype.h>
|
|
#include <disk.h>
|
|
#include "edit.h"
|
|
|
|
#undef write
|
|
|
|
char*
|
|
getline(Edit *edit)
|
|
{
|
|
static int inited;
|
|
static Biobuf bin;
|
|
char *p;
|
|
int n;
|
|
|
|
if(!inited){
|
|
Binit(&bin, 0, OREAD);
|
|
inited = 1;
|
|
}
|
|
p = Brdline(&bin, '\n');
|
|
n = Blinelen(&bin);
|
|
if(p == nil || n < 1){
|
|
if(edit->changed)
|
|
fprint(2, "?warning: changes not written\n");
|
|
exits(0);
|
|
}
|
|
p[n - 1] = '\0';
|
|
while(isspace(*p))
|
|
p++;
|
|
return p;
|
|
}
|
|
|
|
Part*
|
|
findpart(Edit *edit, char *name)
|
|
{
|
|
int i;
|
|
|
|
for(i=0; i<edit->npart; i++)
|
|
if(strcmp(edit->part[i]->name, name) == 0)
|
|
return edit->part[i];
|
|
return nil;
|
|
}
|
|
|
|
static char*
|
|
okname(Edit *edit, char *name)
|
|
{
|
|
int i;
|
|
static char msg[100];
|
|
|
|
if(name[0] == '\0')
|
|
return "partition has no name";
|
|
|
|
// if(strlen(name) >= NAMELEN)
|
|
// return "name too int32_t";
|
|
//
|
|
for(i=0; i<edit->npart; i++) {
|
|
if(strcmp(name, edit->part[i]->name) == 0) {
|
|
sprint(msg, "already have partition with name \"%s\"", name);
|
|
return msg;
|
|
}
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
char*
|
|
addpart(Edit *edit, Part *p)
|
|
{
|
|
int i;
|
|
static char msg[100];
|
|
char *err;
|
|
|
|
if(err = okname(edit, p->name))
|
|
return err;
|
|
|
|
for(i=0; i<edit->npart; i++) {
|
|
if(p->start < edit->part[i]->end && edit->part[i]->start < p->end) {
|
|
sprint(msg, "\"%s\" %lld-%lld overlaps with \"%s\" %lld-%lld",
|
|
p->name, p->start, p->end,
|
|
edit->part[i]->name, edit->part[i]->start, edit->part[i]->end);
|
|
// return msg;
|
|
}
|
|
}
|
|
|
|
if(edit->npart >= nelem(edit->part))
|
|
return "too many partitions";
|
|
|
|
edit->part[i=edit->npart++] = 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;
|
|
}
|
|
|
|
char*
|
|
delpart(Edit *edit, Part *p)
|
|
{
|
|
int i;
|
|
|
|
for(i=0; i<edit->npart; i++)
|
|
if(edit->part[i] == p)
|
|
break;
|
|
assert(i < edit->npart);
|
|
edit->npart--;
|
|
for(; i<edit->npart; i++)
|
|
edit->part[i] = edit->part[i+1];
|
|
|
|
edit->changed = 1;
|
|
return nil;
|
|
}
|
|
|
|
static char*
|
|
editdot(Edit *edit, int argc, char **argv)
|
|
{
|
|
char *err;
|
|
int64_t ndot;
|
|
|
|
if(argc == 1) {
|
|
print("\t. %lld\n", edit->dot);
|
|
return nil;
|
|
}
|
|
|
|
if(argc > 2)
|
|
return "args";
|
|
|
|
if(err = parseexpr(argv[1], edit->dot, edit->end, edit->end, edit->unitsz, &ndot))
|
|
return err;
|
|
|
|
edit->dot = ndot;
|
|
return nil;
|
|
}
|
|
|
|
static char*
|
|
editadd(Edit *edit, int argc, char **argv)
|
|
{
|
|
char *name, *err, *q;
|
|
static char msg[100];
|
|
int64_t start, end, maxend;
|
|
int i;
|
|
|
|
if(argc < 2)
|
|
return "args";
|
|
|
|
name = estrdup(argv[1]);
|
|
if((err = okname(edit, name)) || (err = edit->okname(edit, name)))
|
|
return err;
|
|
|
|
if(argc >= 3)
|
|
q = argv[2];
|
|
else {
|
|
fprint(2, "start %s: ", edit->unit);
|
|
q = getline(edit);
|
|
}
|
|
if(err = parseexpr(q, edit->dot, edit->end, edit->end, edit->unitsz, &start))
|
|
return err;
|
|
|
|
if(start < 0 || start >= edit->end)
|
|
return "start out of range";
|
|
|
|
for(i=0; i < edit->npart; i++) {
|
|
if(edit->part[i]->start <= start && start < edit->part[i]->end) {
|
|
sprint(msg, "start %s in partition \"%s\"", edit->unit, edit->part[i]->name);
|
|
return msg;
|
|
}
|
|
}
|
|
|
|
maxend = edit->end;
|
|
for(i=0; i < edit->npart; i++)
|
|
if(start < edit->part[i]->start && edit->part[i]->start < maxend)
|
|
maxend = edit->part[i]->start;
|
|
|
|
if(argc >= 4)
|
|
q = argv[3];
|
|
else {
|
|
fprint(2, "end [%lld..%lld] ", start, maxend);
|
|
q = getline(edit);
|
|
}
|
|
if(err = parseexpr(q, edit->dot, maxend, edit->end, edit->unitsz, &end))
|
|
return err;
|
|
|
|
if(start == end)
|
|
return "size zero partition";
|
|
|
|
if(end <= start || end > maxend)
|
|
return "end out of range";
|
|
|
|
if(argc > 4)
|
|
return "args";
|
|
|
|
if(err = edit->add(edit, name, start, end))
|
|
return err;
|
|
|
|
edit->dot = end;
|
|
return nil;
|
|
}
|
|
|
|
static char*
|
|
editdel(Edit *edit, int argc, char **argv)
|
|
{
|
|
Part *p;
|
|
|
|
if(argc != 2)
|
|
return "args";
|
|
|
|
if((p = findpart(edit, argv[1])) == nil)
|
|
return "no such partition";
|
|
|
|
return edit->del(edit, p);
|
|
}
|
|
|
|
static char *helptext =
|
|
". [newdot] - display or set value of dot\n"
|
|
"a name [start [end]] - add partition\n"
|
|
"d name - delete partition\n"
|
|
"h - print help message\n"
|
|
"p - print partition table\n"
|
|
"P - print commands to update sd(3) device\n"
|
|
"w - write partition table\n"
|
|
"q - quit\n";
|
|
|
|
static char*
|
|
edithelp(Edit *edit, int _, char** __)
|
|
{
|
|
print("%s", helptext);
|
|
if(edit->help)
|
|
return edit->help(edit);
|
|
return nil;
|
|
}
|
|
|
|
static char*
|
|
editprint(Edit *edit, int argc, char** _)
|
|
{
|
|
int64_t lastend;
|
|
int i;
|
|
Part **part;
|
|
|
|
if(argc != 1)
|
|
return "args";
|
|
|
|
lastend = 0;
|
|
part = edit->part;
|
|
for(i=0; i<edit->npart; 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;
|
|
}
|
|
|
|
char*
|
|
editwrite(Edit *edit, int argc, char** _)
|
|
{
|
|
int i;
|
|
char *err;
|
|
|
|
if(argc != 1)
|
|
return "args";
|
|
|
|
if(edit->disk->rdonly)
|
|
return "read only";
|
|
|
|
err = edit->write(edit);
|
|
if(err)
|
|
return err;
|
|
for(i=0; i<edit->npart; i++)
|
|
edit->part[i]->changed = 0;
|
|
edit->changed = 0;
|
|
return nil;
|
|
}
|
|
|
|
static char*
|
|
editquit(Edit *edit, int argc, char** _)
|
|
{
|
|
if(argc != 1) {
|
|
return "args";
|
|
}
|
|
|
|
if(edit->changed && (!edit->warned || edit->lastcmd != 'q')) {
|
|
edit->warned = 1;
|
|
return "changes unwritten";
|
|
}
|
|
|
|
exits(0);
|
|
return nil; /* not reached */
|
|
}
|
|
|
|
char*
|
|
editctlprint(Edit *edit, int argc, char ** _)
|
|
{
|
|
if(argc != 1)
|
|
return "args";
|
|
|
|
if(edit->printctl)
|
|
edit->printctl(edit, 1);
|
|
else
|
|
ctldiff(edit, 1);
|
|
return nil;
|
|
}
|
|
|
|
typedef struct Cmd Cmd;
|
|
struct Cmd {
|
|
char c;
|
|
char *(*fn)(Edit*, int ,char**);
|
|
};
|
|
|
|
Cmd cmds[] = {
|
|
'.', editdot,
|
|
'a', editadd,
|
|
'd', editdel,
|
|
'?', edithelp,
|
|
'h', edithelp,
|
|
'P', editctlprint,
|
|
'p', editprint,
|
|
'w', editwrite,
|
|
'q', editquit,
|
|
};
|
|
|
|
void
|
|
runcmd(Edit *edit, char *cmd)
|
|
{
|
|
char *f[10], *err;
|
|
int i, nf;
|
|
|
|
while(*cmd && isspace(*cmd))
|
|
cmd++;
|
|
|
|
nf = tokenize(cmd, f, nelem(f));
|
|
if(nf >= 10) {
|
|
fprint(2, "?\n");
|
|
return;
|
|
}
|
|
|
|
if(nf < 1)
|
|
return;
|
|
if(strlen(f[0]) != 1) {
|
|
fprint(2, "?\n");
|
|
return;
|
|
}
|
|
|
|
err = nil;
|
|
for(i=0; i<nelem(cmds); i++) {
|
|
if(cmds[i].c == f[0][0]) {
|
|
err = cmds[i].fn(edit, nf, f);
|
|
break;
|
|
}
|
|
}
|
|
if(i == nelem(cmds)){
|
|
if(edit->ext)
|
|
err = edit->ext(edit, nf, f);
|
|
else
|
|
err = "unknown command";
|
|
}
|
|
if(err)
|
|
fprint(2, "?%s\n", err);
|
|
edit->lastcmd = f[0][0];
|
|
}
|
|
|
|
static Part*
|
|
ctlmkpart(char *name, int64_t start, int64_t end, int changed)
|
|
{
|
|
Part *p;
|
|
|
|
p = emalloc(sizeof(*p));
|
|
p->name = estrdup(name);
|
|
p->ctlname = estrdup(name);
|
|
p->start = start;
|
|
p->end = end;
|
|
p->changed = changed;
|
|
return p;
|
|
}
|
|
|
|
static void
|
|
rdctlpart(Edit *edit)
|
|
{
|
|
int i, nline, nf;
|
|
char *line[128];
|
|
char buf[4096];
|
|
int64_t a, b;
|
|
char *f[5];
|
|
Disk *disk;
|
|
|
|
disk = edit->disk;
|
|
edit->nctlpart = 0;
|
|
seek(disk->ctlfd, 0, 0);
|
|
if(readn(disk->ctlfd, buf, sizeof buf) <= 0) {
|
|
return;
|
|
}
|
|
|
|
nline = getfields(buf, line, nelem(line), 1, "\n");
|
|
for(i=0; i<nline; i++){
|
|
if(strncmp(line[i], "part ", 5) != 0)
|
|
continue;
|
|
|
|
nf = getfields(line[i], f, nelem(f), 1, " \t\r");
|
|
if(nf != 4 || strcmp(f[0], "part") != 0)
|
|
break;
|
|
|
|
a = strtoll(f[2], 0, 0);
|
|
b = strtoll(f[3], 0, 0);
|
|
|
|
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(strcmp(f[1], disk->part) == 0)
|
|
continue;
|
|
|
|
if(edit->nctlpart >= nelem(edit->ctlpart)) {
|
|
fprint(2, "?too many partitions in ctl file\n");
|
|
exits("ctlpart");
|
|
}
|
|
edit->ctlpart[edit->nctlpart++] = ctlmkpart(f[1], a, b, 0);
|
|
}
|
|
}
|
|
|
|
static int64_t
|
|
ctlstart(Part *p)
|
|
{
|
|
if(p->ctlstart)
|
|
return p->ctlstart;
|
|
return p->start;
|
|
}
|
|
|
|
static int64_t
|
|
ctlend(Part *p)
|
|
{
|
|
if(p->ctlend)
|
|
return p->ctlend;
|
|
return p->end;
|
|
}
|
|
|
|
static int
|
|
areequiv(Part *p, Part *q)
|
|
{
|
|
if(p->ctlname[0]=='\0' || q->ctlname[0]=='\0')
|
|
return 0;
|
|
|
|
return strcmp(p->ctlname, q->ctlname) == 0
|
|
&& ctlstart(p) == ctlstart(q) && ctlend(p) == ctlend(q);
|
|
}
|
|
|
|
static void
|
|
unchange(Edit *edit, Part *p)
|
|
{
|
|
int i;
|
|
Part *q;
|
|
|
|
for(i=0; i<edit->nctlpart; i++) {
|
|
q = edit->ctlpart[i];
|
|
if(p->start <= q->start && q->end <= p->end) {
|
|
q->changed = 0;
|
|
}
|
|
}
|
|
assert(p->changed == 0);
|
|
}
|
|
|
|
int
|
|
ctldiff(Edit *edit, int ctlfd)
|
|
{
|
|
int i, j, waserr;
|
|
Part *p;
|
|
int64_t offset;
|
|
|
|
rdctlpart(edit);
|
|
|
|
/* everything is bogus until we prove otherwise */
|
|
for(i=0; i<edit->nctlpart; i++)
|
|
edit->ctlpart[i]->changed = 1;
|
|
|
|
/*
|
|
* partitions with same info have not changed,
|
|
* and neither have partitions inside them.
|
|
*/
|
|
for(i=0; i<edit->nctlpart; i++)
|
|
for(j=0; j<edit->npart; 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<edit->nctlpart; i++) {
|
|
p = edit->ctlpart[i];
|
|
if(p->changed)
|
|
if(fprint(ctlfd, "delpart %s\n", p->ctlname)<0) {
|
|
fprint(2, "delpart failed: %s: %r\n", p->ctlname);
|
|
waserr = -1;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* add all the partitions from the real list;
|
|
* this is okay since adding a parition with
|
|
* information identical to what is there is a no-op.
|
|
*/
|
|
offset = edit->disk->offset;
|
|
for(i=0; i<edit->npart; i++) {
|
|
p = edit->part[i];
|
|
if(p->ctlname[0]) {
|
|
if(fprint(ctlfd, "part %s %lld %lld\n", p->ctlname, offset+ctlstart(p), offset+ctlend(p)) < 0) {
|
|
fprint(2, "adding part failed: %s: %r\n", p->ctlname);
|
|
waserr = -1;
|
|
}
|
|
}
|
|
}
|
|
return waserr;
|
|
}
|
|
|
|
void*
|
|
emalloc(uint32_t sz)
|
|
{
|
|
void *v;
|
|
|
|
v = malloc(sz);
|
|
if(v == nil)
|
|
sysfatal("malloc %lud fails", sz);
|
|
memset(v, 0, sz);
|
|
return v;
|
|
}
|
|
|
|
char*
|
|
estrdup(char *s)
|
|
{
|
|
s = strdup(s);
|
|
if(s == nil)
|
|
sysfatal("strdup (%.10s) fails", s);
|
|
return s;
|
|
}
|
|
|