carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package hostfiles
2
3import (
4 "crypto/sha256"
5 "encoding/hex"
6 "errors"
7 "io"
8 "os"
9 "path/filepath"
10
11 "github.com/carlosframework/platform/internal/instancefs"
12)
13
14// Present reports, read-only, whether dir and envPath hold exactly what m
15// names (region HA ruling 52, VerifyLocal): every live, non-skipped,
16// non-torn entry that Restore would write present as a regular file at
17// its size and sha256, and the env file a regular file at its platform
18// entry's size and sha256. A live entry Restore supersedes (a file and an
19// entry beneath it, resolved by sequence: superseded) is not looked for,
20// so a restore that Restore reports complete is present. Extra files are
21// not checked: the app may write after P's opening, and Present is called
22// only before it.
23//
24// A manifest without a live env entry is not evidence that the host has
25// no env file (platform items have no tombstone: StagePlatformDelete drops
26// the item, so a deleted env reads as one never published). envAbsent is
27// the caller's own positive evidence that it has none; only then is an
28// absent env file present, and any file at envPath is not. Without it, a
29// manifest with no env entry, or a tombstone (which no publisher writes),
30// is never present.
31func Present(dir, envPath string, m Manifest, envAbsent bool) (bool, error) {
32 loser := superseded(m)
33 for rel, e := range m.Entries {
34 if e.Deleted || e.Torn || e.Skipped != "" || loser[rel] {
35 continue
36 }
37 ok, err := presentFile(dir, filepath.FromSlash(rel), e.Size, e.SHA256)
38 if err != nil || !ok {
39 return false, err
40 }
41 }
42 // The env file is the platform entry's exact bytes (Astra round 2
43 // finding 9): a regular file at its size and digest.
44 envDir, envName := filepath.Dir(envPath), filepath.Base(envPath)
45 e, ok := m.Platform[EnvItem]
46 switch {
47 case ok && (e.Deleted || e.Torn || e.Skipped != "" || e.SHA256 == ""):
48 return false, nil
49 case ok:
50 return presentFile(envDir, envName, e.Size, e.SHA256)
51 case !envAbsent:
52 return false, nil
53 }
54 // No env file is present only on positive evidence, decided in one
55 // place (instancefs.OpenBeneathOrAbsent): the directory opened once,
56 // with no symlink at any component, and the name was not found beneath
57 // that same descriptor. A directory that is missing or vanishes in
58 // between is not that evidence.
59 f, absent, err := instancefs.OpenBeneathOrAbsent(envDir, envName)
60 switch {
61 case absent:
62 return true, nil
63 case err == nil:
64 f.Close()
65 return false, nil
66 case errors.Is(err, instancefs.ErrDirUnopened):
67 if errors.Is(err, os.ErrNotExist) {
68 return false, nil
69 }
70 return false, err // a linked directory: refused, never read through
71 case notThere(err):
72 return false, nil // something that is not a regular file: not "none"
73 }
74 return false, err
75}
76
77// notThere is what instancefs.OpenBeneath answers for a name that holds
78// something other than a regular file reached without a link: a symlink
79// at the name or beneath dir, a directory, a FIFO or a device. None of
80// those is the file a restore wrote, so the file is not present; none of
81// them is read.
82func notThere(err error) bool {
83 return errors.Is(err, instancefs.ErrSymlink) || errors.Is(err, instancefs.ErrNotRegular)
84}
85
86// presentFile reports whether rel beneath dir is a regular file of size
87// bytes whose sha256 is sum. Both dir and everything beneath it are the
88// tenant's to write, so it opens through main's one safe open for that
89// ground (instancefs.OpenBeneathOrAbsent: openat2 with no symlink at any
90// component, O_NONBLOCK, a regular file only) and reads no more than size
91// bytes plus one, so a file grown or swapped after the look cannot make it
92// read without bound.
93func presentFile(dir, rel string, size int64, sum string) (bool, error) {
94 f, absent, err := instancefs.OpenBeneathOrAbsent(dir, rel)
95 switch {
96 case absent:
97 return false, nil
98 case errors.Is(err, instancefs.ErrDirUnopened):
99 if errors.Is(err, os.ErrNotExist) {
100 return false, nil // nothing restored yet
101 }
102 return false, err // a linked root: refused, never read through
103 case notThere(err):
104 return false, nil
105 case err != nil:
106 return false, err
107 }
108 defer f.Close()
109 info, err := f.Stat()
110 if err != nil {
111 return false, err
112 }
113 if info.Size() != size {
114 return false, nil
115 }
116 h := sha256.New()
117 n, err := io.Copy(h, io.LimitReader(f, size+1))
118 if err != nil {
119 return false, err
120 }
121 return n == size && hex.EncodeToString(h.Sum(nil)) == sum, nil
122}
123