ref: babf901b4a508c3ec5d1f89655f10377bbdf9637
dir: /appl/cmd/disk/mkext.b/
implement Mkext;
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;
Mkext: module
{
init: fn(nil: ref Draw->Context, nil: list of string);
};
LEN: con Sys->ATOMICIO;
NFLDS: con 6; # filename, modes, uid, gid, mtime, bytes
bin: ref Iobuf;
uflag := 0;
tflag := 0;
hflag := 0;
vflag := 0;
fflag := 0;
stderr: ref Sys->FD;
bout: ref Iobuf;
argv0 := "mkext";
init(nil: ref Draw->Context, args: list of string)
{
sys = load Sys Sys->PATH;
stderr = sys->fildes(2);
bufio = load Bufio Bufio->PATH;
if(bufio == nil)
error(sys->sprint("cannot load %s: %r\n", Bufio->PATH));
str = load String String->PATH;
if(str == nil)
error(sys->sprint("cannot load %s: %r\n", String->PATH));
arg = load Arg Arg->PATH;
if(arg == nil)
error(sys->sprint("cannot load %s: %r\n", Arg->PATH));
destdir := "";
arg->init(args);
arg->setusage("mkext [-h] [-d destdir] [-T] [-u] [-v] [file ...]");
while((c := arg->opt()) != 0)
case c {
'd' =>
destdir = arg->earg();
'f' =>
fflag = 1;
'h' =>
hflag = 1;
bout = bufio->fopen(sys->fildes(1), Sys->OWRITE);
if(bout == nil)
error(sys->sprint("can't access standard output: %r"));
'u' =>
uflag = 1;
tflag = 1;
't' or 'T' =>
tflag = 1;
'v' =>
vflag = 1;
* =>
arg->usage();
}
args = arg->argv();
bin = bufio->fopen(sys->fildes(0), Sys->OREAD);
if(bin == nil)
error(sys->sprint("can't access standard input: %r"));
while((p := bin.gets('\n')) != nil){
if(p == "end of archive\n"){
fprint(stderr, "done\n");
quit(nil);
}
fields := str->unquoted(p);
if(len fields != NFLDS){
warn("too few fields in file header");
continue;
}
name := hd fields;
fields = tl fields;
(mode, nil) := str->toint(hd fields, 8);
fields = tl fields;
uid := hd fields;
fields = tl fields;
gid := hd fields;
fields = tl fields;
(mtime, nil) := str->toint(hd fields, 10);
fields = tl fields;
(bytes, nil) := str->tobig(hd fields, 10);
if(args != nil){
if(!selected(name, args)){
if(bytes != big 0)
seekpast(bytes);
continue;
}
mkdirs(destdir, name);
}
name = destdir+name;
if(hflag){
bout.puts(sys->sprint("%q %s %s %s %ud %bd\n",
name, octal(mode), uid, gid, mtime, bytes));
if(bytes != big 0)
seekpast(bytes);
continue;
}
if(mode & Sys->DMDIR)
mkdir(name, mode, mtime, uid, gid);
else
extract(name, mode, mtime, uid, gid, bytes);
}
fprint(stderr, "premature end of archive\n");
quit("eof");
}
quit(s: string)
{
if(bout != nil)
bout.flush();
if(s != nil)
raise "fail: "+s;
exit;
}
fileprefix(prefix, s: string): int
{
n := len prefix;
m := len s;
if(n > m || !str->prefix(prefix, s))
return 0;
if(m > n && s[n] != '/')
return 0;
return 1;
}
selected(s: string, args: list of string): int
{
for(; args != nil; args = tl args)
if(fileprefix(hd args, s))
return 1;
return 0;
}
mkdirs(basedir, name: string)
{
(nil, names) := sys->tokenize(name, "/");
while(names != nil) {
#sys->print("mkdir %s\n", basedir);
create(basedir, Sys->OREAD, 8r775|Sys->DMDIR);
if(tl names == nil)
break;
basedir = basedir + "/" + hd names;
names = tl names;
}
}
mkdir(name: string, mode: int, mtime: int, uid: string, gid: string)
{
d: Dir;
i: int;
fd := create(name, Sys->OREAD, mode);
if(fd == nil){
(i, d) = sys->stat(name);
if(i < 0 || !(d.mode & Sys->DMDIR)){
warn(sys->sprint("can't make directory %s: %r", name));
return;
}
}else{
(i, d) = sys->fstat(fd);
if(i < 0)
warn(sys->sprint("can't stat %s: %r", name));
fd = nil;
}
d = sys->nulldir;
(nil, p) := str->splitr(name, "/");
if(p == nil)
p = name;
d.name = p;
if(tflag)
d.mtime = mtime;
if(uflag){
d.uid = uid;
d.gid = gid;
}
d.mode = mode;
if(sys->wstat(name, d) < 0)
warn(sys->sprint("can't set modes for %s: %r", name));
if(uflag){
(i, d) = sys->stat(name);
if(i < 0)
warn(sys->sprint("can't reread modes for %s: %r", name));
if(d.mtime != mtime)
warn(sys->sprint("%s: time mismatch %ud %ud\n", name, mtime, d.mtime));
if(uid != d.uid)
warn(sys->sprint("%s: uid mismatch %s %s", name, uid, d.uid));
if(gid != d.gid)
warn(sys->sprint("%s: gid mismatch %s %s", name, gid, d.gid));
}
}
extract(name: string, mode: int, mtime: int, uid: string, gid: string, bytes: big)
{
n: int;
if(vflag)
sys->print("x %s %bd bytes\n", name, bytes);
sfd := create(name, Sys->OWRITE, mode);
if(sfd == nil) {
if(!fflag || sys->remove(name) == -1 ||
(sfd = create(name, Sys->OWRITE, mode)) == nil) {
warn(sys->sprint("can't make file %s: %r", name));
seekpast(bytes);
return;
}
}
b := bufio->fopen(sfd, Bufio->OWRITE);
if (b == nil) {
warn(sys->sprint("can't open file %s for bufio : %r", name));
seekpast(bytes);
return;
}
buf := array [LEN] of byte;
for(tot := big 0; tot < bytes; tot += big n){
n = len buf;
if(tot + big n > bytes)
n = int(bytes - tot);
n = bin.read(buf, n);
if(n <= 0)
error(sys->sprint("premature eof reading %s", name));
if(b.write(buf, n) != n)
warn(sys->sprint("error writing %s: %r", name));
}
(i, nil) := sys->fstat(b.fd);
if(i < 0)
warn(sys->sprint("can't stat %s: %r", name));
d := sys->nulldir;
(nil, p) := str->splitr(name, "/");
if(p == nil)
p = name;
d.name = p;
if(tflag)
d.mtime = mtime;
if(uflag){
d.uid = uid;
d.gid = gid;
}
d.mode = mode;
if(b.flush() == Bufio->ERROR)
warn(sys->sprint("error writing %s: %r", name));
if(sys->fwstat(b.fd, d) < 0)
warn(sys->sprint("can't set modes for %s: %r", name));
if(uflag){
(i, d) = sys->fstat(b.fd);
if(i < 0)
warn(sys->sprint("can't reread modes for %s: %r", name));
if(d.mtime != mtime)
warn(sys->sprint("%s: time mismatch %ud %ud\n", name, mtime, d.mtime));
if(d.uid != uid)
warn(sys->sprint("%s: uid mismatch %s %s", name, uid, d.uid));
if(d.gid != gid)
warn(sys->sprint("%s: gid mismatch %s %s", name, gid, d.gid));
}
b.close();
}
seekpast(bytes: big)
{
n: int;
buf := array [LEN] of byte;
for(tot := big 0; tot < bytes; tot += big n){
n = len buf;
if(tot + big n > bytes)
n = int(bytes - tot);
n = bin.read(buf, n);
if(n <= 0)
error("premature eof");
}
}
error(s: string)
{
fprint(stderr, "%s: %s\n", argv0, s);
quit("error");
}
warn(s: string)
{
fprint(stderr, "%s: %s\n", argv0, s);
}
octal(i: int): string
{
s := "";
do {
t: string;
t[0] = '0' + (i&7);
s = t+s;
} while((i = (i>>3)&~(7<<29)) != 0);
return s;
}
parent(name : string) : string
{
slash := -1;
for (i := 0; i < len name; i++)
if (name[i] == '/')
slash = i;
if (slash > 0)
return name[0:slash];
return "/";
}
create(name : string, rw : int, mode : int) : ref Sys->FD
{
fd := sys->create(name, rw, mode);
if (fd == nil) {
p := parent(name);
(ok, d) := sys->stat(p);
if (ok < 0)
return nil;
omode := d.mode;
d = sys->nulldir;
d.mode = omode | 8r222; # ensure parent is writable
if(sys->wstat(p, d) < 0) {
warn(sys->sprint("can't set modes for %s: %r", p));
return nil;
}
fd = sys->create(name, rw, mode);
d.mode = omode;
sys->wstat(p, d);
}
return fd;
}