summaryrefslogtreecommitdiff
path: root/appl/cmd/gettar.b
diff options
context:
space:
mode:
Diffstat (limited to 'appl/cmd/gettar.b')
-rw-r--r--appl/cmd/gettar.b248
1 files changed, 248 insertions, 0 deletions
diff --git a/appl/cmd/gettar.b b/appl/cmd/gettar.b
new file mode 100644
index 00000000..4429ab24
--- /dev/null
+++ b/appl/cmd/gettar.b
@@ -0,0 +1,248 @@
+implement Gettar;
+
+include "sys.m";
+ sys: Sys;
+ print, sprint, fprint: import sys;
+ stdin, stderr: ref sys->FD;
+
+include "draw.m";
+
+include "arg.m";
+
+TBLOCK: con 512; # tar logical blocksize
+
+Header: adt{
+ name: string;
+ size: int;
+ mode: int;
+ mtime: int;
+ skip: int;
+};
+
+Gettar: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+error(mess: string)
+{
+ fprint(stderr,"gettar: %s\n",mess);
+ raise "fail:error";
+}
+
+verbose := 0;
+NBLOCK: con 20; # traditional blocking factor for efficient read
+tarbuf := array[NBLOCK*TBLOCK] of byte; # static buffer
+nblock := NBLOCK; # how many blocks of data are in tarbuf
+recno := NBLOCK; # how many blocks in tarbuf have been consumed
+
+getblock(): array of byte
+{
+ if(recno>=nblock){
+ i := sys->read(stdin,tarbuf,TBLOCK*NBLOCK);
+ if(i==0)
+ return nil;
+ if(i<0)
+ error(sys->sprint("read error: %r"));
+ if(i%TBLOCK!=0)
+ error("blocksize error");
+ nblock = i/TBLOCK;
+ recno = 0;
+ }
+ recno++;
+ return tarbuf[(recno-1)*TBLOCK:recno*TBLOCK];
+}
+
+
+octal(b:array of byte): int
+{
+ sum := 0;
+ for(i:=0; i<len b; i++){
+ bi := int b[i];
+ if(bi==' ') continue;
+ if(bi==0) break;
+ sum = 8*sum + bi-'0';
+ }
+ return sum;
+}
+
+nullterm(b:array of byte): string
+{
+ for(i:=0; i<len b; i++)
+ if(b[i]==byte 0) break;
+ return string b[0:i];
+}
+
+getdir(): ref Header
+{
+ dblock := getblock();
+ if(len dblock==0)
+ return nil;
+ if(dblock[0]==byte 0)
+ return nil;
+
+ name := nullterm(dblock[0:100]);
+ if(int dblock[345]!=0)
+ name = nullterm(dblock[345:500])+"/"+name;
+ if(!absolute){
+ if(name[0] == '#')
+ name = "./"+name;
+ else if(name[0] == '/')
+ name = "."+name;
+ }
+
+ magic := string(dblock[257:262]);
+ if(magic[0]!=0 && magic!="ustar")
+ error("bad magic "+name);
+ chksum := octal(dblock[148:156]);
+ for(ci:=148; ci<156; ci++)
+ dblock[ci] = byte ' ';
+ for(i:=0; i<TBLOCK; i++)
+ chksum -= int dblock[i];
+ if(chksum!=0)
+ error("directory checksum error "+name);
+
+ skip := 1;
+ size := 0;
+ mode := 0;
+ mtime := 0;
+ case int dblock[156]{
+ '0' or '7' or 0 =>
+ skip = 0;
+ size = octal(dblock[124:136]);
+ mode = 8r777 & octal(dblock[100: 108]);
+ mtime = octal(dblock[136:148]);
+ '1' =>
+ fprint(stderr,"gettar: skipping link %s -> %s\n",name,string(dblock[157:257]));
+ '2' or 's' =>
+ fprint(stderr,"gettar: skipping symlink %s\n",name);
+ '3' or '4' or '6' =>
+ fprint(stderr,"gettar: skipping special file %s\n",name);
+ '5' =>
+ if(name[(len name)-1]=='/')
+ checkdir(name+".");
+ else
+ checkdir(name+"/.");
+ * =>
+ error(sprint("unrecognized typeflag %d for %s",int dblock[156],name));
+ }
+ return ref Header(name, size, mode, mtime, skip);
+}
+
+keep := 0;
+absolute := 0;
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ stdin = sys->fildes(0);
+ stderr = sys->fildes(2);
+ ofile: ref sys->FD;
+
+ arg := load Arg Arg->PATH;
+ arg->init(args);
+ arg->setusage("gettar [-kTRv] [file ...]");
+ while((o := arg->opt()) != 0)
+ case o {
+ 'k' => keep = 1;
+ 'v' => verbose = 1;
+ 'R' => absolute = 1;
+ * => arg->usage();
+ }
+ args = arg->argv();
+ arg = nil;
+
+ while((file := getdir())!=nil){
+ if(!file.skip){
+ if((args == nil || matched(file.name, args)) && !(keep && exists(file.name))){
+ if(verbose)
+ sys->fprint(stderr, "%s\n", file.name);
+ checkdir(file.name);
+ ofile = sys->create(file.name, Sys->OWRITE, 8r666);
+ if(ofile==nil){
+ fprint(stderr, "gettar: cannot create %s: %r\n",file.name);
+ file.skip = 1;
+ }
+ }else
+ file.skip = 1;
+ }
+ bytes := file.size;
+ blocks := (bytes+TBLOCK-1)/TBLOCK;
+ if(file.skip){
+ for(; blocks>0; blocks--)
+ getblock();
+ continue;
+ }
+
+ for(; blocks>0; blocks--){
+ buf := getblock();
+ nwrite := bytes;
+ if(nwrite>TBLOCK)
+ nwrite = TBLOCK;
+ if(sys->write(ofile,buf,nwrite)!=nwrite)
+ error(sprint("write error for %s: %r",file.name));
+ bytes -= nwrite;
+ }
+ ofile = nil;
+ stat := sys->nulldir;
+ stat.mode = file.mode;
+ stat.mtime = file.mtime;
+ rc := sys->wstat(file.name,stat);
+ if(rc<0){
+ # try just the mode
+ stat.mtime = ~0;
+ rc = sys->wstat(file.name, stat);
+ if(rc < 0)
+ fprint(stderr,"gettar: cannot set mode/mtime %s %#o %ud: %r\n",file.name, file.mode, file.mtime);
+ }
+ }
+}
+
+checkdir(name: string)
+{
+ (nc,compl) := sys->tokenize(name,"/");
+ path := "";
+ while(compl!=nil){
+ comp := hd compl;
+ if(comp=="..")
+ error(".. pathnames forbidden");
+ if(nc>1){
+ if(path=="")
+ path = comp;
+ else
+ path += "/"+comp;
+ (rc,stat) := sys->stat(path);
+ if(rc<0){
+ fd := sys->create(path,Sys->OREAD,Sys->DMDIR+8r777);
+ if(fd==nil)
+ error(sprint("cannot mkdir %s: %r",path));
+ fd = nil;
+ }else if(stat.mode&Sys->DMDIR==0)
+ error(sprint("found non-directory at %s",path));
+ }
+ nc--; compl = tl compl;
+ }
+}
+
+exists(path: string): int
+{
+ return sys->stat(path).t0 >= 0;
+}
+
+matched(n: string, names: list of string): int
+{
+ for(; names != nil; names = tl names){
+ p := hd names;
+ if(prefix(p, n))
+ return 1;
+ }
+ return 0;
+}
+
+prefix(p: string, s: string): int
+{
+ l := len p;
+ if(l > len s)
+ return 0;
+ return p == s[0:l] && (l == len s || s[l] == '/');
+}