1implement Tarfs; 2 3# 4# Copyright © 2003 Vita Nuova Holdings Limited. All rights reserved. 5# 6 7include "sys.m"; 8 sys: Sys; 9 10include "draw.m"; 11 12include "daytime.m"; 13 daytime: Daytime; 14 15include "arg.m"; 16 17include "styx.m"; 18 styx: Styx; 19 Tmsg, Rmsg: import styx; 20 21include "styxservers.m"; 22 styxservers: Styxservers; 23 Fid, Styxserver, Navigator, Navop: import styxservers; 24 Enotfound: import styxservers; 25 26Tarfs: module 27{ 28 init: fn(nil: ref Draw->Context, nil: list of string); 29}; 30 31File: adt { 32 x: int; 33 name: string; 34 mode: int; 35 uid: string; 36 gid: string; 37 mtime: int; 38 length: big; 39 offset: big; 40 parent: cyclic ref File; 41 children: cyclic list of ref File; 42 43 find: fn(f: self ref File, name: string): ref File; 44 enter: fn(d: self ref File, f: ref File); 45 stat: fn(d: self ref File): ref Sys->Dir; 46}; 47 48tarfd: ref Sys->FD; 49pflag: int; 50root: ref File; 51files: array of ref File; 52pathgen: int; 53 54error(s: string) 55{ 56 sys->fprint(sys->fildes(2), "tarfs: %s\n", s); 57 raise "fail:error"; 58} 59 60checkload[T](m: T, path: string) 61{ 62 if(m == nil) 63 error(sys->sprint("can't load %s: %r", path)); 64} 65 66init(nil: ref Draw->Context, args: list of string) 67{ 68 sys = load Sys Sys->PATH; 69 sys->pctl(Sys->FORKFD|Sys->NEWPGRP, nil); 70 styx = load Styx Styx->PATH; 71 checkload(styx, Styx->PATH); 72 styx->init(); 73 styxservers = load Styxservers Styxservers->PATH; 74 checkload(styxservers, Styxservers->PATH); 75 styxservers->init(styx); 76 daytime = load Daytime Daytime->PATH; 77 checkload(daytime, Daytime->PATH); 78 79 arg := load Arg Arg->PATH; 80 checkload(arg, Arg->PATH); 81 arg->setusage("tarfs [-a|-b|-ac|-bc] [-D] file mountpoint"); 82 arg->init(args); 83 flags := Sys->MREPL; 84 while((o := arg->opt()) != 0) 85 case o { 86 'a' => flags = Sys->MAFTER; 87 'b' => flags = Sys->MBEFORE; 88 'D' => styxservers->traceset(1); 89 'p' => pflag++; 90 * => arg->usage(); 91 } 92 args = arg->argv(); 93 if(len args != 2) 94 arg->usage(); 95 arg = nil; 96 97 file := hd args; 98 args = tl args; 99 mountpt := hd args; 100 101 sys->pctl(Sys->FORKFD, nil); 102 103 files = array[100] of ref File; 104 root = files[0] = ref File; 105 root.x = 0; 106 root.name = "/"; 107 root.mode = Sys->DMDIR | 8r555; 108 root.uid = "0"; 109 root.gid = "0"; 110 root.length = big 0; 111 root.offset = big 0; 112 root.mtime = 0; 113 pathgen = 1; 114 115 tarfd = sys->open(file, Sys->OREAD); 116 if(tarfd == nil) 117 error(sys->sprint("can't open %s: %r", file)); 118 if(readtar(tarfd) < 0) 119 error(sys->sprint("error reading %s: %r", file)); 120 121 fds := array[2] of ref Sys->FD; 122 if(sys->pipe(fds) < 0) 123 error(sys->sprint("can't create pipe: %r")); 124 125 navops := chan of ref Navop; 126 spawn navigator(navops); 127 128 (tchan, srv) := Styxserver.new(fds[0], Navigator.new(navops), big 0); 129 fds[0] = nil; 130 131 pidc := chan of int; 132 spawn server(tchan, srv, pidc, navops); 133 <-pidc; 134 135 if(sys->mount(fds[1], nil, mountpt, flags, nil) < 0) 136 error(sys->sprint("can't mount tarfs: %r")); 137} 138 139server(tchan: chan of ref Tmsg, srv: ref Styxserver, pidc: chan of int, navops: chan of ref Navop) 140{ 141 pidc <-= sys->pctl(Sys->FORKNS|Sys->NEWFD, 1::2::srv.fd.fd::tarfd.fd::nil); 142Serve: 143 while((gm := <-tchan) != nil){ 144 root.mtime = daytime->now(); 145 pick m := gm { 146 Readerror => 147 sys->fprint(sys->fildes(2), "tarfs: mount read error: %s\n", m.error); 148 break Serve; 149 Read => 150 (c, err) := srv.canread(m); 151 if(c == nil){ 152 srv.reply(ref Rmsg.Error(m.tag, err)); 153 break; 154 } 155 if(c.qtype & Sys->QTDIR){ 156 srv.default(m); # does readdir 157 break; 158 } 159 f := files[int c.path]; 160 n := m.count; 161 if(m.offset + big n > f.length) 162 n = int (f.length - m.offset); 163 if(n <= 0){ 164 srv.reply(ref Rmsg.Read(m.tag, nil)); 165 break; 166 } 167 a := array[n] of byte; 168 sys->seek(tarfd, f.offset+m.offset, 0); 169 n = sys->read(tarfd, a, len a); 170 if(n < 0) 171 srv.reply(ref Rmsg.Error(m.tag, sys->sprint("%r"))); 172 else 173 srv.reply(ref Rmsg.Read(m.tag, a[0:n])); 174 * => 175 srv.default(gm); 176 } 177 } 178 navops <-= nil; # shut down navigator 179} 180 181File.enter(dir: self ref File, f: ref File) 182{ 183 if(pathgen >= len files){ 184 t := array[pathgen+50] of ref File; 185 t[0:] = files; 186 files = t; 187 } 188 if(0) 189 sys->print("enter %s, %s [#%ux %bd]\n", dir.name, f.name, f.mode, f.length); 190 f.x = pathgen; 191 f.parent = dir; 192 dir.children = f :: dir.children; 193 files[pathgen++] = f; 194} 195 196File.find(f: self ref File, name: string): ref File 197{ 198 for(g := f.children; g != nil; g = tl g) 199 if((hd g).name == name) 200 return hd g; 201 return nil; 202} 203 204File.stat(f: self ref File): ref Sys->Dir 205{ 206 d := ref sys->zerodir; 207 d.mode = f.mode; 208 if(pflag) { 209 d.mode &= 16rff<<24; 210 d.mode |= 8r444; 211 if(f.mode & Sys->DMDIR) 212 d.mode |= 8r111; 213 } 214 d.qid.path = big f.x; 215 d.qid.qtype = f.mode>>24; 216 d.name = f.name; 217 d.uid = f.uid; 218 d.gid = f.gid; 219 d.muid = d.uid; 220 d.length = f.length; 221 d.mtime = f.mtime; 222 d.atime = root.mtime; 223 return d; 224} 225 226split(s: string): (string, string) 227{ 228 for(i := 0; i < len s; i++) 229 if(s[i] == '/'){ 230 for(j := i+1; j < len s && s[j] == '/';) 231 j++; 232 return (s[0:i], s[j:]); 233 } 234 return (nil, s); 235} 236 237putfile(f: ref File) 238{ 239 orign := n := f.name; 240 df := root; 241 for(;;){ 242 (d, rest) := split(n); 243 if(d == ".") { 244 n = rest; 245 continue; 246 } 247 if(d == "..") { 248 warn(sys->sprint("ignoring %q", orign)); 249 return; 250 } 251 if(d == nil || rest == nil){ 252 f.name = n; 253 break; 254 } 255 g := df.find(d); 256 if(g == nil){ 257 g = ref *f; 258 g.name = d; 259 g.mode |= Sys->DMDIR; 260 df.enter(g); 261 } 262 n = rest; 263 df = g; 264 } 265 if(f.name != "." && f.name != "..") 266 df.enter(f); 267} 268 269navigator(navops: chan of ref Navop) 270{ 271 while((m := <-navops) != nil){ 272 pick n := m { 273 Stat => 274 n.reply <-= (files[int n.path].stat(), nil); 275 Walk => 276 f := files[int n.path]; 277 if((f.mode & Sys->DMDIR) == 0){ 278 n.reply <-= (nil, "not a directory"); 279 break; 280 } 281 case n.name { 282 ".." => 283 if(f.parent != nil) 284 f = f.parent; 285 n.reply <-= (f.stat(), nil); 286 * => 287 f = f.find(n.name); 288 if(f != nil) 289 n.reply <-= (f.stat(), nil); 290 else 291 n.reply <-= (nil, Enotfound); 292 } 293 Readdir => 294 f := files[int n.path]; 295 if((f.mode & Sys->DMDIR) == 0){ 296 n.reply <-= (nil, "not a directory"); 297 break; 298 } 299 g := f.children; 300 for(i := n.offset; i > 0 && g != nil; i--) 301 g = tl g; 302 for(; --n.count >= 0 && g != nil; g = tl g) 303 n.reply <-= ((hd g).stat(), nil); 304 n.reply <-= (nil, nil); 305 } 306 } 307} 308 309Blocksize: con 512; 310Namelen: con 100; 311Userlen: con 32; 312 313Oname: con 0; 314Omode: con Namelen; 315Ouid: con Omode+8; 316Ogid: con Ouid+8; 317Osize: con Ogid+8; 318Omtime: con Osize+12; 319Ochksum: con Omtime+12; 320Olinkflag: con Ochksum+8; 321Olinkname: con Olinkflag+1; 322# POSIX extensions follow 323Omagic: con Olinkname+Namelen; # ustar 324Ouname: con Omagic+8; 325Ogname: con Ouname+Userlen; 326Omajor: con Ogname+Userlen; 327Ominor: con Omajor+8; 328Oend: con Ominor+8; 329 330readtar(fd: ref Sys->FD): int 331{ 332 buf := array[Blocksize] of byte; 333 offset := big 0; 334 for(;;){ 335 sys->seek(fd, offset, 0); 336 n := sys->read(fd, buf, len buf); 337 if(n == 0) 338 break; 339 if(n < 0) 340 return -1; 341 if(n < len buf){ 342 sys->werrstr(sys->sprint("short read: expected %d, got %d", len buf, n)); 343 return -1; 344 } 345 if(buf[0] == byte 0) 346 break; 347 offset += big Blocksize; 348 mode := int octal(buf[Omode:Ouid]); 349 linkflag := int buf[Olinkflag]; 350 # don't use linkname 351 if((mode & 8r170000) == 8r40000) 352 linkflag = '5'; 353 mode &= 8r777; 354 case linkflag { 355 '1' or '2' or 's' => # ignore links and symbolic links 356 continue; 357 '3' or '4' or '6' => # special file or fifo (leave them, but empty) 358 ; 359 '5' => 360 mode |= Sys->DMDIR; 361 } 362 f := ref File; 363 f.name = ascii(buf[Oname:Omode]); 364 while(len f.name > 0 && f.name[0] == '/') 365 f.name = f.name[1:]; 366 while(len f.name > 0 && f.name[len f.name-1] == '/'){ 367 mode |= Sys->DMDIR; 368 f.name = f.name[:len f.name-1]; 369 } 370 f.mode = mode; 371 f.uid = string octal(buf[Ouid:Ogid]); 372 f.gid = string octal(buf[Ogid:Osize]); 373 f.length = octal(buf[Osize:Omtime]); 374 if(f.length < big 0) 375 error(sys->sprint("tar file size is negative: %s", f.name)); 376 if(mode & Sys->DMDIR) 377 f.length = big 0; 378 f.mtime = int octal(buf[Omtime:Ochksum]); 379 sum := int octal(buf[Ochksum:Olinkflag]); 380 if(sum != checksum(buf)) 381 error(sys->sprint("checksum error on %s", f.name)); 382 f.offset = offset; 383 offset += f.length; 384 v := int (f.length % big Blocksize); 385 if(v != 0) 386 offset += big (Blocksize-v); 387 388 if(ascii(buf[Omagic:Ouname]) == "ustar" && string buf[Omagic+6:Omagic+8] == "00") { 389 f.uid = ascii(buf[Ouname:Ogname]); 390 f.gid = ascii(buf[Ogname:Omajor]); 391 } 392 393 putfile(f); 394 } 395 return 0; 396} 397 398ascii(b: array of byte): string 399{ 400 top := 0; 401 for(i := 0; i < len b && b[i] != byte 0; i++) 402 if(int b[i] >= 16r80) 403 top = 1; 404 if(top) 405 ; # TO DO: do it by hand if not utf-8 406 return string b[0:i]; 407} 408 409octal(b: array of byte): big 410{ 411 v := big 0; 412 for(i := 0; i < len b && b[i] == byte ' '; i++) 413 ; 414 for(; i < len b && b[i] != byte 0 && b[i] != byte ' '; i++){ 415 c := int b[i]; 416 if(!(c >= '0' && c <= '7')) 417 error(sys->sprint("bad octal value in tar header: %s (%c)", string b, c)); 418 v = (v<<3) | big (c-'0'); 419 } 420 return v; 421} 422 423checksum(b: array of byte): int 424{ 425 c := 0; 426 for(i := 0; i < Ochksum; i++) 427 c += int b[i]; 428 for(; i < Olinkflag; i++) 429 c += ' '; 430 for(; i < len b; i++) 431 c += int b[i]; 432 return c; 433} 434 435warn(s: string) 436{ 437 sys->fprint(sys->fildes(2), "%s\n", s); 438} 439