//go:build unix package hostfiles import ( "context" "errors" "golang.org/x/sys/unix" ) // syncTree walks the tree beneath root by descriptor and calls visit for // every entry that is not a directory, with the open directory that holds // it, its name there, and its path relative to root. It never follows a // symlink and never blocks on a FIFO: a directory is entered only through // openDir. // // The walk is bounded (withinTreeCap): a path past MaxPathBytes refuses // the whole walk with ErrTooDeep, never a // silent skip, so a tree no restore could recreate is never published as // complete, and a chain of directories cannot make the walk's memory grow // with the square of its depth. ctx is checked on entering every // directory, so a deep tree with no files still stops when cancelled. func syncTree(ctx context.Context, root *dirHandle, p *Publisher, skip func(string, bool) bool, visit func(d *dirHandle, name, rel string) error) error { return walkDir(ctx, root, "", skip, visit) } func walkDir(ctx context.Context, d *dirHandle, prefix string, skip func(string, bool) bool, visit func(d *dirHandle, name, rel string) error) error { if err := ctx.Err(); err != nil { return err } names, err := d.names() if err != nil { return err } for _, name := range names { rel := name if prefix != "" { rel = prefix + "/" + name } st, err := d.look(name) if errors.Is(err, unix.ENOENT) { continue } if err != nil { return err } if !isDir(st) { if skipped(skip, rel, false) { continue } if err := withinTreeCap(rel); err != nil { return err } if err := visit(d, name, rel); err != nil { return err } continue } if skipped(skip, rel, true) { continue } if err := withinTreeCap(rel); err != nil { return err } betweenLookAndOpen() sub, err := d.openDir(name) if err != nil { if errors.Is(err, unix.ENOENT) { continue } if notADirectory(err) { // It was a directory a moment ago and is something else // now. Let readStable say what: it will not follow or // block on it either. if err := visit(d, name, rel); err != nil { return err } continue } return err } err = walkDir(ctx, sub, rel, skip, visit) sub.Close() if err != nil { return err } } return nil }