aboutsummaryrefslogtreecommitdiffhomepage
path: root/internal/pkg/dir.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/pkg/dir.go')
-rw-r--r--internal/pkg/dir.go68
1 files changed, 68 insertions, 0 deletions
diff --git a/internal/pkg/dir.go b/internal/pkg/dir.go
new file mode 100644
index 00000000..794fdf5e
--- /dev/null
+++ b/internal/pkg/dir.go
@@ -0,0 +1,68 @@
+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()), ".")
+}