ref: babf901b4a508c3ec5d1f89655f10377bbdf9637
dir: /appl/cmd/disk/mkfs.b/
implement Mkfs;
include "sys.m";
sys: Sys;
Dir, sprint, fprint: import sys;
include "draw.m";
include "bufio.m";
bufio: Bufio;
Iobuf: import bufio;
include "string.m";
str: String;
include "arg.m";
arg: Arg;
Mkfs: module
{
init: fn(nil: ref Draw->Context, nil: list of string);
};
LEN: con Sys->ATOMICIO;
HUNKS: con 128;
Kfs, Fs, Archive: con iota; # types of destination file sytems
File: adt {
new: string;
elem: string;
old: string;
uid: string;
gid: string;
mode: int;
};
b: ref Iobuf;
bout: ref Iobuf; # stdout when writing archive
newfile: string;
oldfile: string;
proto: string;
cputype: string;
users: string;
oldroot: string;
newroot: string;
prog := "mkfs";
lineno := 0;
buf: array of byte;
zbuf: array of byte;
buflen := 1024-8;
indent: int;
verb: int;
modes: int;
ream: int;
debug: int;
xflag: int;
sfd: ref Sys->FD;
fskind: int; # Kfs, Fs, Archive
user: string;
stderr: ref Sys->FD;
usrid, grpid : string;
setuid: int;
init(nil: ref Draw->Context, args: list of string)
{
sys = load Sys Sys->PATH;
bufio = load Bufio Bufio->PATH;
str = load String String->PATH;
arg = load Arg Arg->PATH;
sys->pctl(Sys->NEWPGRP|Sys->FORKNS|Sys->FORKFD, nil);
stderr = sys->fildes(2);
if(arg == nil)
error(sys->sprint("can't load %q: %r", Arg->PATH));
user = getuser();
if(user == nil)
user = "none";
name := "";
file := ref File;
file.new = "";
file.old = nil;
file.mode = 0;
oldroot = "";
newroot = "/n/kfs";
users = nil;
fskind = Kfs; # i suspect Inferno default should be different
arg->init(args);
arg->setusage("mkfs [-aprvxS] [-d root] [-n kfscmdname] [-s src-fs] [-u userfile] [-z n] [-G group] [-U user] proto ...");
while((c := arg->opt()) != 0)
case c {
'a' =>
fskind = Archive;
newroot = "";
bout = bufio->fopen(sys->fildes(1), Sys->OWRITE);
if(bout == nil)
error(sys->sprint("can't open standard output for archive: %r"));
'd' =>
fskind = Fs;
newroot = arg->earg();
'D' =>
debug = 1;
'n' =>
name = arg->earg();
'p' =>
modes = 1;
'q' =>
;
'r' =>
ream = 1;
's' =>
oldroot = arg->earg();
'u' =>
users = arg->earg();
'v' =>
verb = 1;
'x' =>
xflag = 1;
'z' =>
(buflen, nil) = str->toint(arg->earg(), 10);
buflen -= 8; # qid.path and tag at end of each kfs block
'U' =>
usrid = arg->earg();
'G' =>
grpid = arg->earg();
'S' =>
setuid = 1;
* =>
arg->usage();
}
args = arg->argv();
if(args == nil)
arg->usage();
buf = array [buflen] of byte;
zbuf = array [buflen] of { * => byte 0 };
if(name != nil)
openkfscmd(name);
kfscmd("allow");
if(users != nil){
proto = "users"; # for diagnostics
setusers();
}
cputype = getenv("cputype");
if(cputype == nil)
cputype = "dis";
errs := 0;
for(; args != nil; args = tl args){
proto = hd args;
fprint(stderr, "processing %s\n", proto);
b = bufio->open(proto, Sys->OREAD);
if(b == nil){
fprint(stderr, "%s: can't open %q: %r: skipping\n", prog, proto);
errs++;
continue;
}
lineno = 0;
indent = 0;
mkfs(file, -1);
b.close();
}
fprint(stderr, "file system made\n");
kfscmd("disallow");
kfscmd("sync");
if(errs)
quit("skipped protos");
if(fskind == Archive){
bout.puts("end of archive\n");
if(bout.flush() == Bufio->ERROR)
error(sys->sprint("write error: %r"));
}
}
quit(why: string)
{
if(bout != nil)
bout.flush();
if(why != nil)
raise "fail:"+why;
exit;
}
mkfs(me: ref File, level: int)
{
(child, fp) := getfile(me);
if(child == nil)
return;
if(child.elem == "+" || child.elem == "*" || child.elem == "%"){
rec := child.elem[0] == '+';
filesonly := child.elem[0] == '%';
child.new = me.new;
setnames(child);
mktree(child, rec, filesonly);
(child, fp) = getfile(me);
}
while(child != nil && indent > level){
if(mkfile(child))
mkfs(child, indent);
(child, fp) = getfile(me);
}
if(child != nil){
b.seek(fp, 0);
lineno--;
}
}
mktree(me: ref File, rec: int, filesonly: int)
{
fd := sys->open(oldfile, Sys->OREAD);
if(fd == nil){
warn(sys->sprint("can't open %q: %r", oldfile));
return;
}
child := ref *me;
r := ref Rec(nil, 0);
for(;;){
(n, d) := sys->dirread(fd);
if(n <= 0)
break;
for(i := 0; i < n; i++)
if (!recall(d[i].name, r)) {
if(filesonly && d[i].mode & Sys->DMDIR)
continue;
child.new = mkpath(me.new, d[i].name);
if(me.old != nil)
child.old = mkpath(me.old, d[i].name);
child.elem = d[i].name;
setnames(child);
if(copyfile(child, ref d[i], 1) && rec)
mktree(child, rec, filesonly);
}
}
}
# Recall namespace fix
# -- remove duplicates (could use Readdir->init(,Readdir->COMPACT))
# obc
Rec: adt
{
ad: array of string;
l: int;
};
AL : con HUNKS;
recall(e : string, r : ref Rec) : int
{
if (r.ad == nil) r.ad = array[AL] of string;
# double array
if (r.l >= len r.ad) {
nar := array[2*(len r.ad)] of string;
nar[0:] = r.ad;
r.ad = nar;
}
for(i := 0; i < r.l; i++)
if (r.ad[i] == e) return 1;
r.ad[r.l++] = e;
return 0;
}
mkfile(f: ref File): int
{
(i, dir) := sys->stat(oldfile);
if(i < 0){
warn(sys->sprint("can't stat file %q: %r", oldfile));
skipdir();
return 0;
}
return copyfile(f, ref dir, 0);
}
copyfile(f: ref File, d: ref Dir, permonly: int): int
{
mode: int;
if(xflag && bout != nil){
bout.puts(sys->sprint("%q\t%d\t%bd\n", f.new, d.mtime, d.length));
return (d.mode & Sys->DMDIR) != 0;
}
d.name = f.elem;
if(d.dtype != 'M' && d.dtype != 'U'){ # hmm... Indeed!
d.uid = "inferno";
d.gid = "inferno";
mode = (d.mode >> 6) & 7;
d.mode |= mode | (mode << 3);
}
if(f.uid != "-")
d.uid = f.uid;
if(f.gid != "-")
d.gid = f.gid;
if(fskind == Fs && !setuid){ # new system: set to nil
d.uid = user;
d.gid = user;
}
if (usrid != nil)
d.uid = usrid;
if (grpid != nil)
d.gid = grpid;
if(f.mode != ~0){
if(permonly)
d.mode = (d.mode & ~8r666) | (f.mode & 8r666);
else if((d.mode&Sys->DMDIR) != (f.mode&Sys->DMDIR))
warn(sys->sprint("inconsistent mode for %s", f.new));
else
d.mode = f.mode;
}
if(!uptodate(d, newfile)){
if(d.mode & Sys->DMDIR)
mkdir(d);
else {
if(verb)
fprint(stderr, "%q\n", f.new);
copy(d);
}
}else if(modes){
nd := sys->nulldir;
nd.mode = d.mode;
nd.mtime = d.mtime;
nd.gid = d.gid;
if(sys->wstat(newfile, nd) < 0)
warn(sys->sprint("can't set modes for %q: %r", f.new));
# do the uid separately since different file systems object
nd = sys->nulldir;
nd.uid = d.uid;
sys->wstat(newfile, nd);
}
return (d.mode & Sys->DMDIR) != 0;
}
# check if file to is up to date with
# respect to the file represented by df
uptodate(df: ref Dir, newf: string): int
{
if(fskind == Archive || ream)
return 0;
(i, dt) := sys->stat(newf);
if(i < 0)
return 0;
return dt.mtime >= df.mtime;
}
copy(d: ref Dir)
{
t: ref Sys->FD;
n: int;
f := sys->open(oldfile, Sys->OREAD);
if(f == nil){
warn(sys->sprint("can't open %q: %r", oldfile));
return;
}
t = nil;
if(fskind == Archive)
arch(d);
else{
(dname, fname) := str->splitr(newfile, "/");
if(fname == nil)
error(sys->sprint("internal temporary file error (%s)", dname));
cptmp := dname+"__mkfstmp";
t = sys->create(cptmp, Sys->OWRITE, 8r666);
if(t == nil){
warn(sys->sprint("can't create %q: %r", newfile));
return;
}
}
for(tot := big 0;; tot += big n){
n = sys->read(f, buf, buflen);
if(n < 0){
warn(sys->sprint("can't read %q: %r", oldfile));
break;
}
if(n == 0)
break;
if(fskind == Archive){
if(bout.write(buf, n) != n)
error(sys->sprint("write error: %r"));
}else if(buf[0:buflen] == zbuf[0:buflen]){
if(sys->seek(t, big buflen, 1) < big 0)
error(sys->sprint("can't write zeros to %q: %r", newfile));
}else if(sys->write(t, buf, n) < n)
error(sys->sprint("can't write %q: %r", newfile));
}
f = nil;
if(tot != d.length){
warn(sys->sprint("wrong number bytes written to %s (was %bd should be %bd)",
newfile, tot, d.length));
if(fskind == Archive){
warn("seeking to proper position");
bout.seek(d.length - tot, 1);
}
}
if(fskind == Archive)
return;
sys->remove(newfile);
nd := sys->nulldir;
nd.name = d.name;
nd.mode = d.mode;
nd.mtime = d.mtime;
if(sys->fwstat(t, nd) < 0)
error(sys->sprint("can't move tmp file to %q: %r", newfile));
nd = sys->nulldir;
nd.gid = d.gid;
if(sys->fwstat(t, nd) < 0)
warn(sys->sprint("can't set group id of %q to %q: %r", newfile, d.gid));
nd.gid = nil;
nd.uid = d.uid;
sys->fwstat(t, nd);
}
mkdir(d: ref Dir)
{
if(fskind == Archive){
arch(d);
return;
}
fd := sys->create(newfile, Sys->OREAD, d.mode);
nd := sys->nulldir;
nd.mode = d.mode;
nd.gid = d.gid;
nd.mtime = d.mtime;
if(fd == nil){
(i, d1) := sys->stat(newfile);
if(i < 0 || !(d1.mode & Sys->DMDIR))
error(sys->sprint("can't create %q", newfile));
if(sys->wstat(newfile, nd) < 0)
warn(sys->sprint("can't set modes for %q: %r", newfile));
nd = sys->nulldir;
nd.uid = d.uid;
sys->wstat(newfile, nd);
return;
}
if(sys->fwstat(fd, nd) < 0)
warn(sys->sprint("can't set modes for %q: %r", newfile));
nd = sys->nulldir;
nd.uid = d.uid;
sys->fwstat(fd, nd);
}
arch(d: ref Dir)
{
bout.puts(sys->sprint("%q %uo %q %q %ud %bd\n",
newfile, d.mode, d.uid, d.gid, d.mtime, d.length));
}
mkpath(prefix, elem: string): string
{
return sys->sprint("%s/%s", prefix, elem);
}
setnames(f: ref File)
{
newfile = newroot+f.new;
if(f.old != nil){
if(f.old[0] == '/')
oldfile = oldroot+f.old;
else
oldfile = f.old;
}else
oldfile = oldroot+f.new;
}
#
# skip all files in the proto that
# could be in the current dir
#
skipdir()
{
if(indent < 0)
return;
level := indent;
for(;;){
indent = 0;
fp := b.offset();
p := b.gets('\n');
lineno++;
if(p == nil){
indent = -1;
return;
}
for(j := 0; (c := p[j++]) != '\n';)
if(c == ' ')
indent++;
else if(c == '\t')
indent += 8;
else
break;
if(indent <= level){
b.seek(fp, 0);
lineno--;
return;
}
}
}
getfile(old: ref File): (ref File, big)
{
f: ref File;
p, elem: string;
c: int;
if(indent < 0)
return (nil, big 0);
fp := b.offset();
do {
indent = 0;
p = b.gets('\n');
lineno++;
if(p == nil){
indent = -1;
return (nil, big 0);
}
for(; (c = p[0]) != '\n'; p = p[1:])
if(c == ' ')
indent++;
else if(c == '\t')
indent += 8;
else
break;
} while(c == '\n' || c == '#');
f = ref File;
(elem, p) = getname(p);
if(debug)
fprint(stderr, "getfile: %q root %q\n", elem, old.new);
f.new = mkpath(old.new, elem);
(nil, f.elem) = str->splitr(f.new, "/");
if(f.elem == nil)
error(sys->sprint("can't find file name component of %q", f.new));
(f.mode, p) = getmode(p);
(f.uid, p) = getname(p);
if(f.uid == nil)
f.uid = "-";
(f.gid, p) = getname(p);
if(f.gid == nil)
f.gid = "-";
f.old = getpath(p);
if(f.old == "-")
f.old = nil;
setnames(f);
if(debug)
printfile(f);
return (f, fp);
}
getpath(p: string): string
{
for(i := 0; i < len p && (p[i] == ' ' || p[i] == '\t'); i++)
;
for(n := i; n < len p && (c := p[n]) != '\n' && c != ' ' && c != '\t'; n++)
;
return p[i:n];
}
getname(p: string): (string, string)
{
for(i := 0; i < len p && (p[0] == ' ' || p[0] == '\t'); i++)
;
s := "";
quoted := 0;
for(; i < len p && (c := p[i]) != '\n' && (c != ' ' && c != '\t' || quoted); i++){
if(c == '\''){
if(i+1 >= len p || p[i+1] != '\''){
quoted = !quoted;
continue;
}
i++;
}
s[len s] = c;
}
if(len s > 0 && s[0] == '$'){
s = getenv(s[1:]);
if(s == nil)
error(sys->sprint("can't read environment variable %q", s));
}
return (s, p[i:]);
}
getenv(s: string): string
{
if(s == "user")
return getuser();
return readfile("/env/"+s);
}
getuser(): string
{
return readfile("/dev/user");
}
readfile(f: string): string
{
fd := sys->open(f, Sys->OREAD);
if(fd != nil){
a := array[256] of byte;
n := sys->read(fd, a, len a);
if(n > 0)
return string a[0:n];
}
return nil;
}
getmode(p: string): (int, string)
{
s: string;
(s, p) = getname(p);
if(s == nil || s == "-")
return (~0, p);
os := s;
m := 0;
if(s[0] == 'd'){
m |= Sys->DMDIR;
s = s[1:];
}
if(s[0] == 'a'){
m |= Sys->DMAPPEND;
s = s[1:];
}
if(s[0] == 'l'){
m |= Sys->DMEXCL;
s = s[1:];
}
for(i:=0; i<len s || i < 3; i++)
if(i >= len s || !(s[i]>='0' && s[i]<='7')){
warn(sys->sprint("bad mode specification %s", os));
return (~0, p);
}
(v, nil) := str->toint(s, 8);
return (m|v, p);
}
setusers()
{
if(fskind != Kfs)
return;
file := ref File;
m := modes;
modes = 1;
file.uid = "adm";
file.gid = "adm";
file.mode = Sys->DMDIR|8r775;
file.new = "/adm";
file.elem = "adm";
file.old = nil;
setnames(file);
mkfile(file);
file.new = "/adm/users";
file.old = users;
file.elem = "users";
file.mode = 8r664;
setnames(file);
mkfile(file);
kfscmd("user");
mkfile(file);
file.mode = Sys->DMDIR|8r775;
file.new = "/adm";
file.old = "/adm";
file.elem = "adm";
setnames(file);
mkfile(file);
modes = m;
}
openkfscmd(name: string)
{
if(fskind != Kfs)
return;
kname := sys->sprint("/chan/kfs.%s.cmd", name);
sfd = sys->open(kname, Sys->ORDWR);
if(sfd == nil){
fprint(stderr, "%s: can't open %q: %r\n", prog, kname);
quit("open kfscmd");
}
}
kfscmd(cmd: string)
{
if(fskind != Kfs || sfd == nil)
return;
a := array of byte cmd;
if(sys->write(sfd, a, len a) != len a){
fprint(stderr, "%s: error writing %s: %r", prog, cmd);
return;
}
for(;;){
reply := array[4*1024] of byte;
n := sys->read(sfd, reply, len reply);
if(n <= 0)
return;
s := string reply[0:n];
if(s == "done" || s == "success")
return;
if(s == "unknown command"){
fprint(stderr, "%s: command %s not recognized\n", prog, cmd);
return;
}
}
}
error(s: string)
{
fprint(stderr, "%s: %s:%d: %s\n", prog, proto, lineno, s);
kfscmd("disallow");
kfscmd("sync");
quit("error");
}
warn(s: string)
{
fprint(stderr, "%s: %s:%d: %s\n", prog, proto, lineno, s);
}
printfile(f: ref File)
{
if(f.old != nil)
fprint(stderr, "%q from %q %q %q %uo\n", f.new, f.old, f.uid, f.gid, f.mode);
else
fprint(stderr, "%q %q %q %uo\n", f.new, f.uid, f.gid, f.mode);
}