github.com/kubiko/snapd@v0.0.0-20201013125620-d4f3094d9ddf/asserts/fsbackstore.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2015-2020 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package asserts 21 22 import ( 23 "errors" 24 "fmt" 25 "net/url" 26 "os" 27 "path/filepath" 28 "strconv" 29 "strings" 30 "sync" 31 ) 32 33 // the default filesystem based backstore for assertions 34 35 const ( 36 assertionsLayoutVersion = "v0" 37 assertionsRoot = "asserts-" + assertionsLayoutVersion 38 ) 39 40 type filesystemBackstore struct { 41 top string 42 mu sync.RWMutex 43 } 44 45 // OpenFSBackstore opens a filesystem backed assertions backstore under path. 46 func OpenFSBackstore(path string) (Backstore, error) { 47 top := filepath.Join(path, assertionsRoot) 48 err := ensureTop(top) 49 if err != nil { 50 return nil, err 51 } 52 return &filesystemBackstore{top: top}, nil 53 } 54 55 // guarantees that result assertion is of the expected type (both in the AssertionType and go type sense) 56 func (fsbs *filesystemBackstore) readAssertion(assertType *AssertionType, diskPrimaryPath string) (Assertion, error) { 57 encoded, err := readEntry(fsbs.top, assertType.Name, diskPrimaryPath) 58 if os.IsNotExist(err) { 59 return nil, errNotFound 60 } 61 if err != nil { 62 return nil, fmt.Errorf("broken assertion storage, cannot read assertion: %v", err) 63 } 64 assert, err := Decode(encoded) 65 if err != nil { 66 return nil, fmt.Errorf("broken assertion storage, cannot decode assertion: %v", err) 67 } 68 if assert.Type() != assertType { 69 return nil, fmt.Errorf("assertion that is not of type %q under their storage tree", assertType.Name) 70 } 71 // because of Decode() construction assert has also the expected go type 72 return assert, nil 73 } 74 75 func (fsbs *filesystemBackstore) pickLatestAssertion(assertType *AssertionType, diskPrimaryPaths []string, maxFormat int) (a Assertion, er error) { 76 for _, diskPrimaryPath := range diskPrimaryPaths { 77 fn := filepath.Base(diskPrimaryPath) 78 parts := strings.SplitN(fn, ".", 2) 79 formatnum := 0 80 if len(parts) == 2 { 81 var err error 82 formatnum, err = strconv.Atoi(parts[1]) 83 if err != nil { 84 return nil, fmt.Errorf("invalid active assertion filename: %q", fn) 85 } 86 } 87 if formatnum <= maxFormat { 88 a1, err := fsbs.readAssertion(assertType, diskPrimaryPath) 89 if err != nil { 90 return nil, err 91 } 92 if a == nil || a1.Revision() > a.Revision() { 93 a = a1 94 } 95 } 96 } 97 if a == nil { 98 return nil, errNotFound 99 } 100 return a, nil 101 } 102 103 func diskPrimaryPathComps(primaryPath []string, active string) []string { 104 n := len(primaryPath) 105 comps := make([]string, n+1) 106 // safety against '/' etc 107 for i, comp := range primaryPath { 108 comps[i] = url.QueryEscape(comp) 109 } 110 comps[n] = active 111 return comps 112 } 113 114 func (fsbs *filesystemBackstore) currentAssertion(assertType *AssertionType, primaryPath []string, maxFormat int) (Assertion, error) { 115 var a Assertion 116 namesCb := func(relpaths []string) error { 117 var err error 118 a, err = fsbs.pickLatestAssertion(assertType, relpaths, maxFormat) 119 if err == errNotFound { 120 return nil 121 } 122 return err 123 } 124 125 comps := diskPrimaryPathComps(primaryPath, "active*") 126 assertTypeTop := filepath.Join(fsbs.top, assertType.Name) 127 err := findWildcard(assertTypeTop, comps, 0, namesCb) 128 if err != nil { 129 return nil, fmt.Errorf("broken assertion storage, looking for %s: %v", assertType.Name, err) 130 } 131 132 if a == nil { 133 return nil, errNotFound 134 } 135 136 return a, nil 137 } 138 139 func (fsbs *filesystemBackstore) Put(assertType *AssertionType, assert Assertion) error { 140 fsbs.mu.Lock() 141 defer fsbs.mu.Unlock() 142 143 primaryPath := assert.Ref().PrimaryKey 144 145 curAssert, err := fsbs.currentAssertion(assertType, primaryPath, assertType.MaxSupportedFormat()) 146 if err == nil { 147 curRev := curAssert.Revision() 148 rev := assert.Revision() 149 if curRev >= rev { 150 return &RevisionError{Current: curRev, Used: rev} 151 } 152 } else if err != errNotFound { 153 return err 154 } 155 156 formatnum := assert.Format() 157 activeFn := "active" 158 if formatnum > 0 { 159 activeFn = fmt.Sprintf("active.%d", formatnum) 160 } 161 diskPrimaryPath := filepath.Join(diskPrimaryPathComps(primaryPath, activeFn)...) 162 err = atomicWriteEntry(Encode(assert), false, fsbs.top, assertType.Name, diskPrimaryPath) 163 if err != nil { 164 return fmt.Errorf("broken assertion storage, cannot write assertion: %v", err) 165 } 166 return nil 167 } 168 169 func (fsbs *filesystemBackstore) Get(assertType *AssertionType, key []string, maxFormat int) (Assertion, error) { 170 fsbs.mu.RLock() 171 defer fsbs.mu.RUnlock() 172 173 a, err := fsbs.currentAssertion(assertType, key, maxFormat) 174 if err == errNotFound { 175 return nil, &NotFoundError{Type: assertType} 176 } 177 return a, err 178 } 179 180 func (fsbs *filesystemBackstore) search(assertType *AssertionType, diskPattern []string, foundCb func(Assertion), maxFormat int) error { 181 assertTypeTop := filepath.Join(fsbs.top, assertType.Name) 182 candCb := func(diskPrimaryPaths []string) error { 183 a, err := fsbs.pickLatestAssertion(assertType, diskPrimaryPaths, maxFormat) 184 if err == errNotFound { 185 return nil 186 } 187 if err != nil { 188 return err 189 } 190 foundCb(a) 191 return nil 192 } 193 err := findWildcard(assertTypeTop, diskPattern, 0, candCb) 194 if err != nil { 195 return fmt.Errorf("broken assertion storage, searching for %s: %v", assertType.Name, err) 196 } 197 return nil 198 } 199 200 func (fsbs *filesystemBackstore) Search(assertType *AssertionType, headers map[string]string, foundCb func(Assertion), maxFormat int) error { 201 fsbs.mu.RLock() 202 defer fsbs.mu.RUnlock() 203 204 n := len(assertType.PrimaryKey) 205 diskPattern := make([]string, n+1) 206 for i, k := range assertType.PrimaryKey { 207 keyVal := headers[k] 208 if keyVal == "" { 209 diskPattern[i] = "*" 210 } else { 211 diskPattern[i] = url.QueryEscape(keyVal) 212 } 213 } 214 diskPattern[n] = "active*" 215 216 candCb := func(a Assertion) { 217 if searchMatch(a, headers) { 218 foundCb(a) 219 } 220 } 221 return fsbs.search(assertType, diskPattern, candCb, maxFormat) 222 } 223 224 // errFound marks the case an assertion was found 225 var errFound = errors.New("found") 226 227 func (fsbs *filesystemBackstore) SequenceMemberAfter(assertType *AssertionType, sequenceKey []string, after, maxFormat int) (SequenceMember, error) { 228 if !assertType.SequenceForming() { 229 panic(fmt.Sprintf("internal error: SequenceMemberAfter on non sequence-forming assertion type %s", assertType.Name)) 230 } 231 if len(sequenceKey) != len(assertType.PrimaryKey)-1 { 232 return nil, fmt.Errorf("internal error: SequenceMemberAfter's sequence key argument length must be exactly 1 less than the assertion type primary key") 233 } 234 235 fsbs.mu.RLock() 236 defer fsbs.mu.RUnlock() 237 238 n := len(assertType.PrimaryKey) 239 diskPattern := make([]string, n+1) 240 for i, k := range sequenceKey { 241 diskPattern[i] = url.QueryEscape(k) 242 } 243 seqWildcard := "#>" // ascending sequence wildcard 244 if after == -1 { 245 // find the latest in sequence 246 // use descending sequence wildcard 247 seqWildcard = "#<" 248 } 249 diskPattern[n-1] = seqWildcard 250 diskPattern[n] = "active*" 251 252 var a Assertion 253 candCb := func(diskPrimaryPaths []string) error { 254 var err error 255 a, err = fsbs.pickLatestAssertion(assertType, diskPrimaryPaths, maxFormat) 256 if err == errNotFound { 257 return nil 258 } 259 if err != nil { 260 return err 261 } 262 return errFound 263 } 264 265 assertTypeTop := filepath.Join(fsbs.top, assertType.Name) 266 err := findWildcard(assertTypeTop, diskPattern, after, candCb) 267 if err == errFound { 268 return a.(SequenceMember), nil 269 } 270 if err != nil { 271 return nil, fmt.Errorf("broken assertion storage, searching for %s: %v", assertType.Name, err) 272 } 273 274 return nil, &NotFoundError{Type: assertType} 275 }