ref: babf901b4a508c3ec5d1f89655f10377bbdf9637
dir: /appl/cmd/tarfs.b/
implement Tarfs;
#
# Copyright © 2003 Vita Nuova Holdings Limited. All rights reserved.
#
include "sys.m";
sys: Sys;
include "draw.m";
include "daytime.m";
daytime: Daytime;
include "arg.m";
include "styx.m";
styx: Styx;
Tmsg, Rmsg: import styx;
include "styxservers.m";
styxservers: Styxservers;
Fid, Styxserver, Navigator, Navop: import styxservers;
Enotfound: import styxservers;
Tarfs: module
{
init: fn(nil: ref Draw->Context, nil: list of string);
};
File: adt {
x: int;
name: string;
mode: int;
uid: string;
gid: string;
mtime: int;
length: big;
offset: big;
parent: cyclic ref File;
children: cyclic list of ref File;
find: fn(f: self ref File, name: string): ref File;
enter: fn(d: self ref File, f: ref File);
stat: fn(d: self ref File): ref Sys->Dir;
};
tarfd: ref Sys->FD;
pflag: int;
root: ref File;
files: array of ref File;
pathgen: int;
error(s: string)
{
sys->fprint(sys->fildes(2), "tarfs: %s\n", s);
raise "fail:error";
}
checkload[T](m: T, path: string)
{
if(m == nil)
error(sys->sprint("can't load %s: %r", path));
}
init(nil: ref Draw->Context, args: list of string)
{
sys = load Sys Sys->PATH;
sys->pctl(Sys->FORKFD|Sys->NEWPGRP, nil);
styx = load Styx Styx->PATH;
checkload(styx, Styx->PATH);
styx->init();
styxservers = load Styxservers Styxservers->PATH;
checkload(styxservers, Styxservers->PATH);
styxservers->init(styx);
daytime = load Daytime Daytime->PATH;
checkload(daytime, Daytime->PATH);
arg := load Arg Arg->PATH;
checkload(arg, Arg->PATH);
arg->setusage("tarfs [-a|-b|-ac|-bc] [-D] file mountpoint");
arg->init(args);
flags := Sys->MREPL;
while((o := arg->opt()) != 0)
case o {
'a' => flags = Sys->MAFTER;
'b' => flags = Sys->MBEFORE;
'D' => styxservers->traceset(1);
'p' => pflag++;
* => arg->usage();
}
args = arg->argv();
if(len args != 2)
arg->usage();
arg = nil;
file := hd args;
args = tl args;
mountpt := hd args;
sys->pctl(Sys->FORKFD, nil);
files = array[100] of ref File;
root = files[0] = ref File;
root.x = 0;
root.name = "/";
root.mode = Sys->DMDIR | 8r555;
root.uid = "0";
root.gid = "0";
root.length = big 0;
root.offset = big 0;
root.mtime = 0;
pathgen = 1;
tarfd = sys->open(file, Sys->OREAD);
if(tarfd == nil)
error(sys->sprint("can't open %s: %r", file));
if(readtar(tarfd) < 0)
error(sys->sprint("error reading %s: %r", file));
fds := array[2] of ref Sys->FD;
if(sys->pipe(fds) < 0)
error(sys->sprint("can't create pipe: %r"));
navops := chan of ref Navop;
spawn navigator(navops);
(tchan, srv) := Styxserver.new(fds[0], Navigator.new(navops), big 0);
fds[0] = nil;
pidc := chan of int;
spawn server(tchan, srv, pidc, navops);
<-pidc;
if(sys->mount(fds[1], nil, mountpt, flags, nil) < 0)
error(sys->sprint("can't mount tarfs: %r"));
}
server(tchan: chan of ref Tmsg, srv: ref Styxserver, pidc: chan of int, navops: chan of ref Navop)
{
pidc <-= sys->pctl(Sys->FORKNS|Sys->NEWFD, 1::2::srv.fd.fd::tarfd.fd::nil);
Serve:
while((gm := <-tchan) != nil){
root.mtime = daytime->now();
pick m := gm {
Readerror =>
sys->fprint(sys->fildes(2), "tarfs: mount read error: %s\n", m.error);
break Serve;
Read =>
(c, err) := srv.canread(m);
if(c == nil){
srv.reply(ref Rmsg.Error(m.tag, err));
break;
}
if(c.qtype & Sys->QTDIR){
srv.default(m); # does readdir
break;
}
f := files[int c.path];
n := m.count;
if(m.offset + big n > f.length)
n = int (f.length - m.offset);
if(n <= 0){
srv.reply(ref Rmsg.Read(m.tag, nil));
break;
}
a := array[n] of byte;
sys->seek(tarfd, f.offset+m.offset, 0);
n = sys->read(tarfd, a, len a);
if(n < 0)
srv.reply(ref Rmsg.Error(m.tag, sys->sprint("%r")));
else
srv.reply(ref Rmsg.Read(m.tag, a[0:n]));
* =>
srv.default(gm);
}
}
navops <-= nil; # shut down navigator
}
File.enter(dir: self ref File, f: ref File)
{
if(pathgen >= len files){
t := array[pathgen+50] of ref File;
t[0:] = files;
files = t;
}
if(0)
sys->print("enter %s, %s [#%ux %bd]\n", dir.name, f.name, f.mode, f.length);
f.x = pathgen;
f.parent = dir;
dir.children = f :: dir.children;
files[pathgen++] = f;
}
File.find(f: self ref File, name: string): ref File
{
for(g := f.children; g != nil; g = tl g)
if((hd g).name == name)
return hd g;
return nil;
}
File.stat(f: self ref File): ref Sys->Dir
{
d := ref sys->zerodir;
d.mode = f.mode;
if(pflag) {
d.mode &= 16rff<<24;
d.mode |= 8r444;
if(f.mode & Sys->DMDIR)
d.mode |= 8r111;
}
d.qid.path = big f.x;
d.qid.qtype = f.mode>>24;
d.name = f.name;
d.uid = f.uid;
d.gid = f.gid;
d.muid = d.uid;
d.length = f.length;
d.mtime = f.mtime;
d.atime = root.mtime;
return d;
}
split(s: string): (string, string)
{
for(i := 0; i < len s; i++)
if(s[i] == '/'){
for(j := i+1; j < len s && s[j] == '/';)
j++;
return (s[0:i], s[j:]);
}
return (nil, s);
}
putfile(f: ref File)
{
orign := n := f.name;
df := root;
for(;;){
(d, rest) := split(n);
if(d == ".") {
n = rest;
continue;
}
if(d == "..") {
warn(sys->sprint("ignoring %q", orign));
return;
}
if(d == nil || rest == nil){
f.name = n;
break;
}
g := df.find(d);
if(g == nil){
g = ref *f;
g.name = d;
g.mode |= Sys->DMDIR;
df.enter(g);
}
n = rest;
df = g;
}
if(f.name != "." && f.name != "..")
df.enter(f);
}
navigator(navops: chan of ref Navop)
{
while((m := <-navops) != nil){
pick n := m {
Stat =>
n.reply <-= (files[int n.path].stat(), nil);
Walk =>
f := files[int n.path];
if((f.mode & Sys->DMDIR) == 0){
n.reply <-= (nil, "not a directory");
break;
}
case n.name {
".." =>
if(f.parent != nil)
f = f.parent;
n.reply <-= (f.stat(), nil);
* =>
f = f.find(n.name);
if(f != nil)
n.reply <-= (f.stat(), nil);
else
n.reply <-= (nil, Enotfound);
}
Readdir =>
f := files[int n.path];
if((f.mode & Sys->DMDIR) == 0){
n.reply <-= (nil, "not a directory");
break;
}
g := f.children;
for(i := n.offset; i > 0 && g != nil; i--)
g = tl g;
for(; --n.count >= 0 && g != nil; g = tl g)
n.reply <-= ((hd g).stat(), nil);
n.reply <-= (nil, nil);
}
}
}
Blocksize: con 512;
Namelen: con 100;
Userlen: con 32;
Oname: con 0;
Omode: con Namelen;
Ouid: con Omode+8;
Ogid: con Ouid+8;
Osize: con Ogid+8;
Omtime: con Osize+12;
Ochksum: con Omtime+12;
Olinkflag: con Ochksum+8;
Olinkname: con Olinkflag+1;
# POSIX extensions follow
Omagic: con Olinkname+Namelen; # ustar
Ouname: con Omagic+8;
Ogname: con Ouname+Userlen;
Omajor: con Ogname+Userlen;
Ominor: con Omajor+8;
Oend: con Ominor+8;
readtar(fd: ref Sys->FD): int
{
buf := array[Blocksize] of byte;
offset := big 0;
for(;;){
sys->seek(fd, offset, 0);
n := sys->read(fd, buf, len buf);
if(n == 0)
break;
if(n < 0)
return -1;
if(n < len buf){
sys->werrstr(sys->sprint("short read: expected %d, got %d", len buf, n));
return -1;
}
if(buf[0] == byte 0)
break;
offset += big Blocksize;
mode := int octal(buf[Omode:Ouid]);
linkflag := int buf[Olinkflag];
# don't use linkname
if((mode & 8r170000) == 8r40000)
linkflag = '5';
mode &= 8r777;
case linkflag {
'1' or '2' or 's' => # ignore links and symbolic links
continue;
'3' or '4' or '6' => # special file or fifo (leave them, but empty)
;
'5' =>
mode |= Sys->DMDIR;
}
f := ref File;
f.name = ascii(buf[Oname:Omode]);
while(len f.name > 0 && f.name[0] == '/')
f.name = f.name[1:];
while(len f.name > 0 && f.name[len f.name-1] == '/'){
mode |= Sys->DMDIR;
f.name = f.name[:len f.name-1];
}
f.mode = mode;
f.uid = string octal(buf[Ouid:Ogid]);
f.gid = string octal(buf[Ogid:Osize]);
f.length = octal(buf[Osize:Omtime]);
if(f.length < big 0)
error(sys->sprint("tar file size is negative: %s", f.name));
if(mode & Sys->DMDIR)
f.length = big 0;
f.mtime = int octal(buf[Omtime:Ochksum]);
sum := int octal(buf[Ochksum:Olinkflag]);
if(sum != checksum(buf))
error(sys->sprint("checksum error on %s", f.name));
f.offset = offset;
offset += f.length;
v := int (f.length % big Blocksize);
if(v != 0)
offset += big (Blocksize-v);
if(ascii(buf[Omagic:Ouname]) == "ustar" && string buf[Omagic+6:Omagic+8] == "00") {
f.uid = ascii(buf[Ouname:Ogname]);
f.gid = ascii(buf[Ogname:Omajor]);
}
putfile(f);
}
return 0;
}
ascii(b: array of byte): string
{
top := 0;
for(i := 0; i < len b && b[i] != byte 0; i++)
if(int b[i] >= 16r80)
top = 1;
if(top)
; # TO DO: do it by hand if not utf-8
return string b[0:i];
}
octal(b: array of byte): big
{
v := big 0;
for(i := 0; i < len b && b[i] == byte ' '; i++)
;
for(; i < len b && b[i] != byte 0 && b[i] != byte ' '; i++){
c := int b[i];
if(!(c >= '0' && c <= '7'))
error(sys->sprint("bad octal value in tar header: %s (%c)", string b, c));
v = (v<<3) | big (c-'0');
}
return v;
}
checksum(b: array of byte): int
{
c := 0;
for(i := 0; i < Ochksum; i++)
c += int b[i];
for(; i < Olinkflag; i++)
c += ' ';
for(; i < len b; i++)
c += int b[i];
return c;
}
warn(s: string)
{
sys->fprint(sys->fildes(2), "%s\n", s);
}