package pkg import ( "crypto/sha512" "encoding/gob" "io" "io/fs" "os" "hakurei.app/container/check" ) // FlatEntry is the representation of a directory entry via [Flatten]. type FlatEntry struct { Name string // base name of the file Mode fs.FileMode // file mode bits Data []byte // file content or symlink destination } // Flatten writes a deterministic representation of the contents of fsys to w. // The resulting data can be hashed to produce a deterministic checksum for the // directory. func Flatten(fsys fs.FS, root string, w io.Writer) error { e := gob.NewEncoder(w) return fs.WalkDir(fsys, root, func(path string, d fs.DirEntry, err error) error { if err != nil { return err } var fi fs.FileInfo fi, err = d.Info() if err != nil { return err } ent := FlatEntry{ Name: fi.Name(), Mode: fi.Mode(), } if ent.Mode.IsRegular() { if ent.Data, err = fs.ReadFile(fsys, path); err != nil { return err } } else if ent.Mode&fs.ModeSymlink != 0 { var newpath string if newpath, err = fs.ReadLink(fsys, path); err != nil { return err } ent.Data = []byte(newpath) } return e.Encode(&ent) }) } // HashFS returns a checksum produced by hashing the result of [Flatten]. func HashFS(fsys fs.FS, root string) (Checksum, error) { h := sha512.New384() if err := Flatten(fsys, root, h); err != nil { return Checksum{}, err } return (Checksum)(h.Sum(nil)), nil } // HashDir returns a checksum produced by hashing the result of [Flatten]. func HashDir(pathname *check.Absolute) (Checksum, error) { return HashFS(os.DirFS(pathname.String()), ".") }