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); }