implement FSproto; include "sys.m"; sys: Sys; Dir: import Sys; include "draw.m"; include "bufio.m"; bufio: Bufio; Iobuf: import bufio; include "string.m"; str: String; include "readdir.m"; readdir: Readdir; include "fsproto.m"; File: adt { new: string; elem: string; old: string; uid: string; gid: string; mode: int; }; Proto: adt { b: ref Iobuf; doquote: int; indent: int; lineno: int; newfile: string; oldfile: string; oldroot: string; ec: chan of Direntry; wc: chan of (string, string); walk: fn(w: self ref Proto, f: ref File, level: int); entry: fn(w: self ref Proto, old: string, new: string, d: ref Sys->Dir); warn: fn(w: self ref Proto, s: string); fatal: fn(w: self ref Proto, s: string); }; init(): string { sys = load Sys Sys->PATH; bufio = load Bufio Bufio->PATH; if(bufio == nil) return sys->sprint("%r"); str = load String String->PATH; if(str == nil) return sys->sprint("%r"); readdir = load Readdir Readdir->PATH; if(readdir == nil) return sys->sprint("%r"); return nil; } readprotofile(proto: string, root: string, entries: chan of Direntry, warnings: chan of (string, string)): string { b := bufio->open(proto, Sys->OREAD); if(b == nil) return sys->sprint("%r"); rdproto(b, root, entries, warnings); return nil; } readprotostring(proto: string, root: string, entries: chan of Direntry, warnings: chan of (string, string)) { rdproto(bufio->sopen(proto), root, entries, warnings); } rdproto(b: ref Iobuf, root: string, entries: chan of Direntry, warnings: chan of (string, string)): string { w := ref Proto; w.b = b; w.doquote = 1; w.ec = entries; w.wc = warnings; w.oldroot = root; w.lineno = 0; w.indent = 0; file := ref File; file.mode = 0; spawn walker(w, file); return nil; } walker(w: ref Proto, file: ref File) { w.walk(file, -1); w.entry(nil, nil, nil); } Proto.entry(w: self ref Proto, old: string, new: string, d: ref Sys->Dir) { if(w.ec != nil) w.ec <-= (old, new, d); } Proto.warn(w: self ref Proto, s: string) { if(w.wc != nil) w.wc <-= (w.oldfile, s); else sys->fprint(sys->fildes(2), "warning: %s\n", s); } Proto.fatal(w: self ref Proto, s: string) { if(w.wc != nil) w.wc <-= (w.oldfile, s); else sys->fprint(sys->fildes(2), "fatal error: %s\n", s); w.ec <-= (nil, nil, nil); exit; } Proto.walk(w: self ref Proto, me: ref File, level: int) { (child, fp) := getfile(w, me); if(child == nil) return; if(child.elem == "+" || child.elem == "*" || child.elem == "%"){ rec := child.elem[0] == '+'; filesonly := child.elem[0] == '%'; child.new = me.new; setnames(w, child); mktree(w, child, rec, filesonly); (child, fp) = getfile(w, me); } while(child != nil && w.indent > level){ if(mkfile(w, child)) w.walk(child, w.indent); (child, fp) = getfile(w, me); } if(child != nil){ w.b.seek(big fp, 0); w.lineno--; } } mktree(w: ref Proto, me: ref File, rec: int, filesonly: int) { fd := sys->open(w.oldfile, Sys->OREAD); if(fd == nil){ w.warn(sys->sprint("can't open %s: %r", w.oldfile)); return; } child := ref *me; (d, n) := readdir->init(w.oldfile, Readdir->NAME|Readdir->COMPACT); for(i := 0; i < n; i++) { if(filesonly && (d[i].mode & Sys->DMDIR)) continue; child.new = mkpath(me.new, d[i].name); if(me.old != nil) child.old = mkpath(me.old, d[i].name); child.elem = d[i].name; setnames(w, child); if(copyfile(w, child, d[i]) && rec) mktree(w, child, rec, filesonly); } } mkfile(w: ref Proto, f: ref File): int { (i, dir) := sys->stat(w.oldfile); if(i < 0){ w.warn(sys->sprint("can't stat file %s: %r", w.oldfile)); skipdir(w); return 0; } return copyfile(w, f, ref dir); } copyfile(w: ref Proto, f: ref File, d: ref Dir): int { d.name = f.elem; if(f.mode != ~0){ if((d.mode&Sys->DMDIR) != (f.mode&Sys->DMDIR)) w.warn(sys->sprint("inconsistent mode for %s", f.new)); else d.mode = f.mode; } w.entry(w.oldfile, w.newfile, d); return (d.mode & Sys->DMDIR) != 0; } setnames(w: ref Proto, f: ref File) { w.newfile = f.new; if(f.old != nil){ if(f.old[0] == '/') w.oldfile = mkpath(w.oldroot, f.old); else w.oldfile = f.old; }else w.oldfile = mkpath(w.oldroot, f.new); } # # skip all files in the proto that # could be in the current dir # skipdir(w: ref Proto) { if(w.indent < 0) return; b := w.b; level := w.indent; for(;;){ w.indent = 0; fp := b.offset(); p := b.gets('\n'); if(p != nil && p[len p - 1] != '\n') p += "\n"; w.lineno++; if(p == nil){ w.indent = -1; return; } for(j := 0; (c := p[j++]) != '\n';) if(c == ' ') w.indent++; else if(c == '\t') w.indent += 8; else break; if(w.indent <= level){ b.seek(fp, 0); w.lineno--; return; } } } getfile(w: ref Proto, old: ref File): (ref File, int) { p, elem: string; c: int; if(w.indent < 0) return (nil, 0); b := w.b; fp := int b.offset(); do { w.indent = 0; p = b.gets('\n'); if(p != nil && p[len p - 1] != '\n') p += "\n"; w.lineno++; if(p == nil){ w.indent = -1; return (nil, 0); } for(; (c = p[0]) != '\n'; p = p[1:]) if(c == ' ') w.indent++; else if(c == '\t') w.indent += 8; else break; } while(c == '\n' || c == '#'); (elem, p) = getname(w, p); if(p == nil) return (nil, 0); f := ref File; f.new = mkpath(old.new, elem); (nil, f.elem) = str->splitr(f.new, "/"); if(f.elem == nil) w.fatal(sys->sprint("can't find file name component of %s", f.new)); (f.mode, p) = getmode(w, p); if(p == nil) return (nil, 0); (f.uid, p) = getname(w, p); if(p == nil) return (nil, 0); if(f.uid == nil) f.uid = "-"; (f.gid, p) = getname(w, p); if(p == nil) return (nil, 0); if(f.gid == nil) f.gid = "-"; f.old = getpath(p); if(f.old == "-") f.old = nil; if(f.old == nil && old.old != nil) f.old = mkpath(old.old, elem); setnames(w, f); return (f, fp); } getpath(p: string): string { for(; (c := p[0]) == ' ' || c == '\t'; p = p[1:]) ; for(n := 0; (c = p[n]) != '\n' && c != ' ' && c != '\t'; n++) ; return p[0:n]; } getname(w: ref Proto, p: string): (string, string) { for(; (c := p[0]) == ' ' || c == '\t'; p = p[1:]) ; i := 0; s := ""; quoted := 0; for(; (c = p[0]) != '\n' && (c != ' ' && c != '\t' || quoted); p = p[1:]){ if(quoted && c == '\'' && p[1] == '\'') p = p[1:]; else if(c == '\'' && w.doquote){ quoted = !quoted; continue; } s[i++] = c; } if(len s > 0 && s[0] == '$'){ s = getenv(s[1:]); if(s == nil) w.warn(sys->sprint("can't read environment variable %s", s)); } return (s, p); } getenv(s: string): string { if(s == "user") return readfile("/dev/user"); # more accurate? return readfile("/env/"+s); } readfile(f: string): string { fd := sys->open(f, Sys->OREAD); if(fd != nil){ a := array[256] of byte; n := sys->read(fd, a, len a); if(n > 0) return string a[0:n]; } return nil; } getmode(w: ref Proto, p: string): (int, string) { s: string; (s, p) = getname(w, p); if(s == nil || s == "-") return (~0, p); m := 0; if(s[0] == 'd'){ m |= Sys->DMDIR; s = s[1:]; } if(s[0] == 'a'){ m |= Sys->DMAPPEND; s = s[1:]; } if(s[0] == 'l'){ m |= Sys->DMEXCL; s = s[1:]; } for(i:=0; i= len s || !(s[i]>='0' && s[i]<='7')){ w.warn(sys->sprint("bad mode specification %s", s)); return (~0, p); } (v, nil) := str->toint(s, 8); return (m|v, p); } mkpath(prefix, elem: string): string { slash1 := slash2 := 0; if(len prefix > 0) slash1 = prefix[len prefix - 1] == '/'; if(len elem > 0) slash2 = elem[0] == '/'; if(slash1 && slash2) return prefix+elem[1:]; if(!slash1 && !slash2) return prefix+"/"+elem; return prefix+elem; }