carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package release
2
3import (
4 "context"
5 "crypto/sha256"
6 "encoding/hex"
7 "errors"
8 "fmt"
9 "io"
10 "os"
11
12 "github.com/carlosframework/platform/internal/safefile"
13)
14
15// ErrTooLarge: the object exceeded the caller's limit. The reader is cut at
16// limit+1 bytes, so an object EXACTLY at the limit is told from one over it.
17var ErrTooLarge = errors.New("store: object larger than the caller's limit")
18
19// FetchToFile streams key into dst with a running sha256, never holding the
20// object in memory: temp file, fsync, rename, directory fsync. Any failure
21// removes the temp and leaves dst untouched.
22func FetchToFile(ctx context.Context, s Store, key, dst string, limit int64, wantSHA256 string) (err error) {
23 rc, _, err := OpenObject(ctx, s, key)
24 if err != nil {
25 return err
26 }
27 defer rc.Close()
28 return safefile.Write(dst, 0o600, func(f io.Writer) error {
29 h := sha256.New()
30 n, err := io.Copy(io.MultiWriter(f, h), io.LimitReader(rc, limit+1))
31 if err != nil {
32 return fmt.Errorf("fetch %s: %w", key, err)
33 }
34 if n > limit {
35 return fmt.Errorf("fetch %s: %w (limit %d)", key, ErrTooLarge, limit)
36 }
37 if got := hex.EncodeToString(h.Sum(nil)); got != wantSHA256 {
38 return fmt.Errorf("fetch %s: sha256 %s does not match the signed %s", key, got, wantSHA256)
39 }
40 return nil
41 })
42}
43
44// syncDir makes a rename durable: the directory entry is what fsync(file)
45// does not cover.
46func syncDir(dir string) error {
47 d, err := os.Open(dir)
48 if err != nil {
49 return err
50 }
51 defer d.Close()
52 return d.Sync()
53}
54