Last active
July 9, 2025 14:27
-
-
Save pravdomil/bc6c24815057bc8c6c0472cb0aedc748 to your computer and use it in GitHub Desktop.
A Go CLI that computes, verifies and initializes SHA-256 checksums for files on macOS by storing the digest in the user.checksum.sha256 extended attribute.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"bytes" | |
"crypto/sha256" | |
"errors" | |
"fmt" | |
"io" | |
"os" | |
"path/filepath" | |
"golang.org/x/sys/unix" | |
) | |
const attrName = "user.checksum.sha256" | |
// computeSHA256Binary returns the raw 32-byte SHA-256 digest of the file at path. | |
func computeSHA256Binary(path string) ([]byte, error) { | |
f, err := os.Open(path) | |
if err != nil { | |
return nil, err | |
} | |
defer f.Close() | |
h := sha256.New() | |
if _, err := io.Copy(h, f); err != nil { | |
return nil, err | |
} | |
return h.Sum(nil), nil | |
} | |
// getXAttrBinary reads exactly 32 bytes from the named xattr (assuming SHA-256 raw digest). | |
// Returns ENOATTR if the attribute is not present. | |
func getXAttrBinary(path, attr string) ([]byte, error) { | |
buf := make([]byte, 32) | |
n, err := unix.Lgetxattr(path, attr, buf) | |
if err != nil { | |
return nil, err | |
} | |
return buf[:n], nil | |
} | |
// setXAttrBinary writes the raw bytes into the named xattr (using Lsetxattr so symlinks are not followed). | |
func setXAttrBinary(path, attr string, data []byte) error { | |
return unix.Lsetxattr(path, attr, data, 0) | |
} | |
// verifyFile reads (or initializes) the SHA-256 checksum xattr on the given file. | |
func verifyFile(path string) (stored []byte, actual []byte, err error) { | |
// Try to read existing checksum attribute | |
stored, err = getXAttrBinary(path, attrName) | |
if err != nil && !errors.Is(err, unix.ENOATTR) { | |
// some error other than “no attribute” | |
return nil, nil, err | |
} | |
missing := errors.Is(err, unix.ENOATTR) | |
// Compute the actual checksum | |
actual, err = computeSHA256Binary(path) | |
if err != nil { | |
return nil, nil, err | |
} | |
if missing { | |
// No attr → set it to the computed value | |
if err = setXAttrBinary(path, attrName, actual); err != nil { | |
return nil, nil, err | |
} | |
// stored stays nil, actual is what we just set | |
return nil, actual, nil | |
} | |
// Attr existed → return both for comparison | |
return stored, actual, nil | |
} | |
func main() { | |
if len(os.Args) < 2 { | |
fmt.Fprintf(os.Stderr, "Usage: %s <file-or-dir> [...]\n", filepath.Base(os.Args[0])) | |
os.Exit(1) | |
} | |
var failures []string | |
for _, root := range os.Args[1:] { | |
err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error { | |
if err != nil { | |
return err | |
} | |
if !info.Mode().IsRegular() { | |
return nil | |
} | |
stored, actual, err := verifyFile(path) | |
if err != nil { | |
return err | |
} | |
if stored == nil { | |
fmt.Printf("➕ %s\n", path) | |
} else if bytes.Equal(stored, actual) { | |
fmt.Printf("✔ %s\n", path) | |
} else { | |
fmt.Printf("❌ %s\n", path) | |
failures = append(failures, path) | |
} | |
return nil | |
}) | |
if err != nil { | |
fmt.Fprintf(os.Stderr, "Error walking %s: %v\n", root, err) | |
os.Exit(1) | |
} | |
} | |
if len(failures) > 0 { | |
fmt.Fprintln(os.Stderr, "Failed checksums:") | |
for _, f := range failures { | |
fmt.Fprintln(os.Stderr, f) | |
} | |
os.Exit(1) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment