init
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
// Package cas is the content-addressed blob store. Every file any deployment
|
||||
// contains is stored once, named by the SHA-256 of its bytes, and shared by
|
||||
// every deployment and every project that references that content.
|
||||
//
|
||||
// Sharing is what makes redeploying a mostly-unchanged site nearly free, and it
|
||||
// is only safe because the store never takes a caller's word for a digest: see
|
||||
// Store.Put.
|
||||
package cas
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
const (
|
||||
// Size is a digest's length in bytes.
|
||||
Size = sha256.Size
|
||||
// HexLen is a digest's length in its hex form.
|
||||
HexLen = 2 * Size
|
||||
)
|
||||
|
||||
// Digest is the SHA-256 of a blob's contents.
|
||||
//
|
||||
// An array rather than a slice, so it can be a map key, compared with ==, and
|
||||
// copied out of a database buffer instead of aliasing one — database/sql reuses
|
||||
// the byte slices it scans into.
|
||||
type Digest [Size]byte
|
||||
|
||||
// ErrBadDigest rejects anything that is not a digest in the canonical form.
|
||||
var ErrBadDigest = errors.New("cas: not a 64-character lowercase hex sha-256")
|
||||
|
||||
// ParseDigest decodes the hex form used on the wire and in URLs.
|
||||
//
|
||||
// Strict about case rather than normalising, because a digest is simultaneously
|
||||
// a primary key in the blobs table and a component of a filesystem path.
|
||||
// Accepting two spellings of one value would mean two rows, two files, and
|
||||
// deduplication that quietly stops deduplicating.
|
||||
func ParseDigest(s string) (Digest, error) {
|
||||
var d Digest
|
||||
if len(s) != HexLen {
|
||||
return d, ErrBadDigest
|
||||
}
|
||||
for i := 0; i < len(s); i++ {
|
||||
// encoding/hex accepts uppercase; this loop is what makes lowercase the
|
||||
// only accepted spelling.
|
||||
c := s[i]
|
||||
if (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') {
|
||||
continue
|
||||
}
|
||||
return d, ErrBadDigest
|
||||
}
|
||||
if _, err := hex.Decode(d[:], []byte(s)); err != nil {
|
||||
return Digest{}, ErrBadDigest
|
||||
}
|
||||
return d, nil
|
||||
}
|
||||
|
||||
// FromBytes converts the raw 32 bytes stored in the database. It copies, so the
|
||||
// result does not alias the scan buffer it came from.
|
||||
func FromBytes(b []byte) (Digest, error) {
|
||||
var d Digest
|
||||
if len(b) != Size {
|
||||
return d, fmt.Errorf("cas: digest is %d bytes, want %d", len(b), Size)
|
||||
}
|
||||
copy(d[:], b)
|
||||
return d, nil
|
||||
}
|
||||
|
||||
// String is the canonical hex form.
|
||||
func (d Digest) String() string { return hex.EncodeToString(d[:]) }
|
||||
|
||||
// Bytes is the raw form stored in the database. The slice belongs to the
|
||||
// caller's copy of the digest, so writing to it cannot affect anything else.
|
||||
func (d Digest) Bytes() []byte { return d[:] }
|
||||
|
||||
// Rel is the blob's path inside the store, sharded two levels deep:
|
||||
// "ab/cd/abcd…". Two hex characters per level gives 65,536 leaf directories, so
|
||||
// even a store with millions of blobs keeps every directory small enough that
|
||||
// readdir and lookup stay fast on ext4 and xfs alike.
|
||||
func (d Digest) Rel() string {
|
||||
s := d.String()
|
||||
return s[0:2] + "/" + s[2:4] + "/" + s
|
||||
}
|
||||
|
||||
// Sum digests b.
|
||||
func Sum(b []byte) Digest { return sha256.Sum256(b) }
|
||||
Reference in New Issue
Block a user