func (fs *Fs) chkWalk(p string, mkall bool) error { if fs.ai == nil { return nil } els := zx.Elems(p) rp := "/" for len(els) > 0 { d, err := fs.stat(rp, false) rp = fpath.Join(rp, els[0]) if err != nil { if !mkall || !zx.IsNotExist(err) { return err } path := fpath.Join(fs.root, rp) if e := os.Mkdir(path, 0755); e != nil { return err } } if !d.CanWalk(fs.ai) { return fmt.Errorf("%s: %s", d["path"], zx.ErrPerm) } if len(els) == 1 { return nil } els = els[1:] } return nil }
// exp p -> // /a/b /x -> false, true // /a/b /a -> false, false // /a/b /a/b/c -> false, true func pathMatch(exp, p string) (value, pruned bool, err error) { if len(exp) > 0 && exp[0] != '/' { m, err := filepath.Match(exp, p) return m, false, err } els := zx.Elems(exp) pels := zx.Elems(p) if len(pels) > len(els) { return false, true, nil } for i := 0; i < len(pels); i++ { m, err := filepath.Match(els[i], pels[i]) if !m { return false, true, err } } return len(pels) == len(els), false, nil }
// Walk to rid, and return it r/wlocked (unless there's an error) with at least its meta updated. func (fs *Cfs) walk(rid string, isrlock bool) (f *cFile, left []string, e error) { fs.dprintf("walk %s...\n", rid) els := zx.Elems(rid) left = els defer func() { if e != nil { fs.dprintf("walkerr %s left %v\n", rid, left) } else { fs.dprintf("walked %s left %v\n", rid, left) } if e == nil && len(left) > 0 { panic("cfs walk: left and no error") } }() path := "/" for { isr := len(els) > 0 || isrlock nf, err := fs.cwalk(path, isr) if err != nil { return f, left, err } left = els f = nf if len(els) > 0 { path = zx.Path(path, els[0]) els = els[1:] } if len(left) == 0 { if err := f.needMeta(isr); err != nil { // lock has been released if dbg.IsNotExist(err) { // discovered in needMeta that the file is gone // we raced, so try again f, left, err = fs.walk(rid, isrlock) } return f, left, err } return f, left, nil } if err := f.needData(isr, nil); err != nil { // lock has been released if dbg.IsNotExist(err) { // discovered in needMeta that the file is gone // we raced, so try again f, left, err = fs.walk(rid, isrlock) } return f, left, err } if !fs.NoPermCheck && !f.d.CanWalk(fs.ai) { f.unlockc(isr) return f, left, fmt.Errorf("%s: %s", f, dbg.ErrPerm) } f.unlockc(isr) } }
// Walk to the given rid checking out perms when t.ai is not nil. // Returns also the gid and mode even when no perms are checked. func (t *Lfs) canWalkTo(rid string, forwhat int) (string, uint64, error) { if !t.readattrs || rid == "/Ctl" { return dbg.Usr, 0777, nil } noperm := t.NoPermCheck || t.ai == nil elems := zx.Elems(rid) fpath := t.path pgid := "" var pmode uint64 if len(elems) == 0 { elems = append(elems, "/") } for len(elems) > 0 { if noperm { // skip to just the final step if len(elems) > 1 { fpath = zx.Path(t.path, path.Dir(rid)) elems = elems[len(elems)-1:] } } pd := zx.Dir{ "name": elems[0], } fpath = zx.Path(fpath, elems[0]) elems = elems[1:] st, err := os.Stat(fpath) if err != nil { return "", 0, err } mode := st.Mode() m := int64(mode & 0777) pd["mode"] = "0" + strconv.FormatInt(m, 8) t.fileAttrs(fpath, pd) if !noperm && len(elems) > 0 && !pd.CanWalk(t.ai) { return "", 0, dbg.ErrPerm } if len(elems) == 0 { pgid = pd["Gid"] pmode = uint64(pd.Int("mode")) } if !noperm && len(elems) == 0 && forwhat != 0 { if !pd.Can(t.ai, forwhat) { return "", 0, dbg.ErrPerm } } } if pgid == "" { pgid = dbg.Usr } if pmode == 0 { pmode = 0775 } return pgid, pmode, nil }
func (t *Fs) walk(rid string) (File, error) { rid, err := zx.AbsPath(rid) if err != nil { return nil, err } if rid == "/" { return t.root, nil } if rid == "/Ctl" { return nil, nil } els := zx.Elems(rid) f := t.root p := "/" var d zx.Dir for _, e := range els { t.Dprintf("walk %s %s...\n", f, e) d, err = f.Stat() if err != nil { t.Dprintf("\tstat: %s\n", f, err) return nil, err } if d["type"] != "d" { t.Dprintf("\tnot dir\n") return nil, fmt.Errorf("%s: %s", p, dbg.ErrNotDir) } if !t.NoPermCheck && !d.CanWalk(t.ai) { t.Dprintf("\tno perm\n") return nil, fmt.Errorf("%s: %s", p, dbg.ErrPerm) } wf, ok := f.(Walker) if !ok { t.Dprintf("\tnot walker\n") return nil, fmt.Errorf("%s: %s: %s", p, e, dbg.ErrNotExist) } f, err = wf.Walk(e) if err != nil { t.Dprintf("\twalk: %s\n", err) return nil, err } p = zx.Path(p, e) t.Dprintf("walked %s\n", f) } return f, nil }
func setUids(d zx.Dir) { if d["Uid"] == "" { u := dbg.Usr if strings.HasPrefix(d["path"], "/usr/") { els := zx.Elems(d["path"]) if len(els) > 1 { u = els[1] } } d["Uid"] = u } if d["Gid"] == "" { d["Gid"] = d["Uid"] } if d["Wuid"] == "" { d["Wuid"] = d["Uid"] } }
// if the error is a file not found, then file is returned with // the parent file and left contains what's left to walk. func (t *Fs) walk(rid string, pfp **mFile) (cf *mFile, left []string, e error) { els := zx.Elems(rid) f := t.root for len(els) > 0 { if !t.noPerms() && !f.d.CanWalk(t.ai) { return f, els, fmt.Errorf("%s: %s", f, dbg.ErrPerm) } if len(els) == 1 && pfp != nil { *pfp = f } nf, err := f.walk1(els[0]) if err != nil { return f, els, err } f = nf els = els[1:] } return f, nil, nil }
func find(dump, dpref, rel string, dc chan<- zx.Dir, ufile zx.Dir) { droot := fpath.Join(dump, dpref) years, err := cmd.GetDir(droot) if err != nil { cmd.Warn("%s", err) return } for i := len(years) - 1; i >= 0; i-- { year := years[i]["name"] if ignored(year, "") { continue } ypath := years[i]["path"] days, err := cmd.GetDir(ypath) if err != nil { cmd.Warn("%s: %s", ypath, err) continue } lastsz, lastmt, lastm := "", "", "" for j := len(days) - 1; j >= 0; j-- { day := days[j]["name"] if ignored(year, day) { continue } fpath := fpath.Join(days[j]["path"], rel) d, err := cmd.Stat(fpath) if err != nil { if !force { cmd.Dprintf("find: %s", err) return } continue } newm, newsz, newmt := d["mode"], d["size"], d["mtime"] if newsz == lastsz && newmt == lastmt && newm == lastm { continue } lastm, lastsz, lastmt = newm, newsz, newmt d["upath"] = ufile["path"] d["uupath"] = ufile["upath"] if ok := dc <- d; !ok { return } if !all { return } } } } func report(dc chan zx.Dir, donec chan bool) { last := "" for d := range dc { if last == "" { last = d["Upath"] if last == "" { last = d["path"] } } p := d["path"] cmd.Dprintf("found '%s'\n", p) var err error switch { case xcmd != "": _, err = cmd.Printf("%s %s %s\n", xcmd, p, last) case dflag: dcmd := fmt.Sprintf(`9 diff -n %s %s`, p, last) _, err = cmd.Printf("%s\n", dcmd) if err != nil { cmd.Warn("diff: %s", err) continue } case lflag: _, err = cmd.Printf("%s\n", d.Fmt()) case cflag: _, err = cmd.Printf("cp %s %s\n", p, d["Upath"]) default: _, err = cmd.Printf("%s\n", d["path"]) } if err != nil { close(dc, err) } last = p } close(donec, cerror(dc)) } func hist(in <-chan face{}) error { dc := make(chan zx.Dir) ec := make(chan bool) go report(dc, ec) var sts error for m := range in { switch m := m.(type) { case zx.Dir: cmd.Dprintf("got %T %s\n", m, m["path"]) file := m["path"] if m["upath"] == "" { m["upath"] = m["path"] } ddir := dump dpref := "" rel := "" switch { case zx.HasPrefix(file, "/zx"): if ddir == "" { ddir = "/dump" } dpref = "/zx" rel = zx.Suffix(file, "/zx") case zx.HasPrefix(file, "/u/gosrc/src/clive"): if ddir == "" { ddir = "/u/dump" } dpref = "clive" rel = zx.Suffix(file, "/u/gosrc/src/clive") case zx.HasPrefix(file, "/u"): if ddir == "" { ddir = "/u/dump" } els := zx.Elems(file) if len(els) < 3 { cmd.Warn("%s: too few path elements", m["upath"]) sts = errNoDump continue } dpref = els[1] rel = zx.Path(els[2:]...) default: cmd.Warn("%s: %s", m["upath"], errNoDump) sts = errNoDump continue } find(ddir, dpref, rel, dc, m.Dup()) default: cmd.Dprintf("got %T\n", m) } } close(dc, cerror(in)) <-ec if sts == nil { sts = cerror(ec) } if sts == nil { sts = cerror(in) } return sts } // Run cnt in the current app context. func main() { c := cmd.AppCtx() cmd.UnixIO("err") opts.NewFlag("D", "debug", &c.Debug) opts.NewFlag("f", "force search past file removals", &force) opts.NewFlag("l", "produce a long listing (or print just the name)", &lflag) opts.NewFlag("c", "copy the file from the dump", &cflag) opts.NewFlag("d", "print file differences", &dflag) opts.NewFlag("x", "cmd: print lines to execute this command between versions", &xcmd) opts.NewFlag("a", "list all copies that differ, not just the last one.", &all) opts.NewFlag("p", "dumpdir: path to dump (default is /dump or /u/dump)", &dump) t := time.Now() when := t opts.NewFlag("w", "date: backward search start time (default is now)", &when) ux := false opts.NewFlag("u", "unix IO", &ux) args := opts.Parse() if (all && cflag) || (force && !all) { cmd.Warn("incompatible flags") opts.Usage() } if ux { cmd.UnixIO("out") } lastyear = "" lastday = "" if !t.Equal(when) { y := when.Year() m := when.Month() d := when.Day() if y == 0 { y = t.Year() } lastyear = fmt.Sprintf("%04d", y) lastday = fmt.Sprintf("%02d%02d", m, d) } if len(args) != 0 { cmd.SetIn("in", cmd.Dirs(args...)) } in := cmd.In("in") if err := hist(in); err != nil { cmd.Fatal(err) } }
func (f *File) files(rc chan<- *File) error { if ok := rc <- f; !ok { return cerror(rc) } for _, c := range f.Child { if err := c.files(rc); err != nil { return err } } return nil } // Enumerate all files in db func (db *DB) Files() <-chan *File { rc := make(chan *File) if db == nil || db.Root == nil { close(rc) } else { go func() { close(rc, db.Root.files(rc)) }() } return rc } // Debug dump func (db *DB) DumpTo(w io.Writer) { fmt.Fprintf(w, "%s\n", db) if db == nil { return } fc := db.Files() for f := range fc { fmt.Fprintf(w, "%s\n", f) } } // Walk to the given path func (db *DB) Walk(elems ...string) (*File, error) { f := db.Root for _, e := range elems { cf, err := f.Walk1(e) if err != nil { return nil, err } f = cf } return f, nil } type byName []*File func (b byName) Len() int { return len(b) } func (b byName) Less(i, j int) bool { return b[i].D["name"] < b[j].D["name"] } func (b byName) Swap(i, j int) { b[i], b[j] = b[j], b[i] } // add or update the entry for a dir into db. func (db *DB) Add(d zx.Dir) error { f := &File{ D: d, } elems := zx.Elems(d["path"]) if len(elems) == 0 { db.Root = f return nil } parent := elems[0 : len(elems)-1] name := elems[len(elems)-1] pdir := zx.Path(parent...) if pdir != db.lastpdir || db.lastpf == nil { pf, err := db.Walk(parent...) if err != nil { dprintf("add: can't walk: %s\n", err) return err } db.lastpdir = pdir db.lastpf = pf } vprintf("add %s to %s\n", name, db.lastpf) child := db.lastpf.Child for _, cf := range child { if cf.D["name"] == name { cf.D = d if cf.D["type"] != "d" { cf.Child = nil } return nil } } child = append(child, f) if n := len(child); n > 1 && child[n-1].D["name"] < child[n-2].D["name"] { sort.Sort(byName(child)) } db.lastpf.Child = child return nil } // part of zx.Finder type Finder interface { Find(path, pred string, spref, dpref string, depth0 int) <-chan zx.Dir } // Scan the underlying tree and re-build the metadata db. // Dials the tree if necessary. // Only the first error is reported. func (db *DB) scan(fs Finder) error { dprintf("scan /,%s\n", db.Pred) dc := fs.Find("/", db.Pred, "", "", 0) db.lastpdir = "" db.lastpf = nil db.Root = nil var err error for d := range dc { if d["path"] == "/Ctl" || d["path"] == "/Chg" { continue } vprintf("add %s\n", d) if e := db.Add(d); err == nil && e != nil { err = nil } } if err != nil { return err } return cerror(dc) } // Send the db through c. // The channel must preserve message boundaries and is not closed by this function. // The db name is first sent and then one packed dir per file recorded in the db. // An empty msg is sent to signal the end of the stream of dir entries func (db *DB) SendTo(c chan<- []byte) error { if ok := c <- []byte(db.Name); !ok { return cerror(c) } if ok := c <- []byte(db.Pred); !ok { return cerror(c) } fc := db.Files() var err error for f := range fc { if f == nil || f.D == nil { err = errors.New("nil file sent") break } if _, err = f.D.Send(c); err != nil { break } } if err != nil { close(fc, err) } else { err = cerror(fc) } c <- []byte{} return err } // Receive a db from c assuming it was sent in the same format used by SendTo. func RecvDBFrom(c <-chan []byte) (*DB, error) { nm, ok1 := <-c pred, ok2 := <-c if !ok1 || !ok2 { return nil, cerror(c) } db := &DB{ Name: string(nm), Pred: string(pred), } db.lastpdir = "" db.lastpf = nil db.Root = nil for { d, err := zx.RecvDir(c) if err != nil || len(d) == 0 { return db, err } if d["path"] == "/Ctl" || d["path"] == "/Chg" { continue } dprintf("add %s\n", d) if err := db.Add(d); err != nil { return db, err } } } // Save a db to a local file func (db *DB) Save(fname string) error { tname := fname + "~" fd, err := os.Create(tname) if err != nil { return err } dc := make(chan []byte) go func() { close(dc, db.SendTo(dc)) }() _, _, err = nchan.WriteMsgsTo(fd, dc) fd.Close() close(dc, err) if err != nil { return err } return os.Rename(tname, fname) } func LoadDB(fname string) (*DB, error) { fd, err := os.Open(fname) if err != nil { return nil, err } dc := make(chan []byte) go func() { _, _, err := nchan.ReadMsgsFrom(fd, dc) close(dc, err) }() db, err := RecvDBFrom(dc) close(dc, err) return db, err }