github.com/rigado/snapd@v2.42.5-go-mod+incompatible/asserts/fsbackstore.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2015-2016 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 "fmt" 24 "net/url" 25 "os" 26 "path/filepath" 27 "strconv" 28 "strings" 29 "sync" 30 ) 31 32 // the default filesystem based backstore for assertions 33 34 const ( 35 assertionsLayoutVersion = "v0" 36 assertionsRoot = "asserts-" + assertionsLayoutVersion 37 ) 38 39 type filesystemBackstore struct { 40 top string 41 mu sync.RWMutex 42 } 43 44 // OpenFSBackstore opens a filesystem backed assertions backstore under path. 45 func OpenFSBackstore(path string) (Backstore, error) { 46 top := filepath.Join(path, assertionsRoot) 47 err := ensureTop(top) 48 if err != nil { 49 return nil, err 50 } 51 return &filesystemBackstore{top: top}, nil 52 } 53 54 // guarantees that result assertion is of the expected type (both in the AssertionType and go type sense) 55 func (fsbs *filesystemBackstore) readAssertion(assertType *AssertionType, diskPrimaryPath string) (Assertion, error) { 56 encoded, err := readEntry(fsbs.top, assertType.Name, diskPrimaryPath) 57 if os.IsNotExist(err) { 58 return nil, errNotFound 59 } 60 if err != nil { 61 return nil, fmt.Errorf("broken assertion storage, cannot read assertion: %v", err) 62 } 63 assert, err := Decode(encoded) 64 if err != nil { 65 return nil, fmt.Errorf("broken assertion storage, cannot decode assertion: %v", err) 66 } 67 if assert.Type() != assertType { 68 return nil, fmt.Errorf("assertion that is not of type %q under their storage tree", assertType.Name) 69 } 70 // because of Decode() construction assert has also the expected go type 71 return assert, nil 72 } 73 74 func (fsbs *filesystemBackstore) pickLatestAssertion(assertType *AssertionType, diskPrimaryPaths []string, maxFormat int) (a Assertion, er error) { 75 for _, diskPrimaryPath := range diskPrimaryPaths { 76 fn := filepath.Base(diskPrimaryPath) 77 parts := strings.SplitN(fn, ".", 2) 78 formatnum := 0 79 if len(parts) == 2 { 80 var err error 81 formatnum, err = strconv.Atoi(parts[1]) 82 if err != nil { 83 return nil, fmt.Errorf("invalid active assertion filename: %q", fn) 84 } 85 } 86 if formatnum <= maxFormat { 87 a1, err := fsbs.readAssertion(assertType, diskPrimaryPath) 88 if err != nil { 89 return nil, err 90 } 91 if a == nil || a1.Revision() > a.Revision() { 92 a = a1 93 } 94 } 95 } 96 if a == nil { 97 return nil, errNotFound 98 } 99 return a, nil 100 } 101 102 func diskPrimaryPathComps(primaryPath []string, active string) []string { 103 n := len(primaryPath) 104 comps := make([]string, n+1) 105 // safety against '/' etc 106 for i, comp := range primaryPath { 107 comps[i] = url.QueryEscape(comp) 108 } 109 comps[n] = active 110 return comps 111 } 112 113 func (fsbs *filesystemBackstore) currentAssertion(assertType *AssertionType, primaryPath []string, maxFormat int) (Assertion, error) { 114 var a Assertion 115 namesCb := func(relpaths []string) error { 116 var err error 117 a, err = fsbs.pickLatestAssertion(assertType, relpaths, maxFormat) 118 if err == errNotFound { 119 return nil 120 } 121 return err 122 } 123 124 comps := diskPrimaryPathComps(primaryPath, "active*") 125 assertTypeTop := filepath.Join(fsbs.top, assertType.Name) 126 err := findWildcard(assertTypeTop, comps, namesCb) 127 if err != nil { 128 return nil, fmt.Errorf("broken assertion storage, looking for %s: %v", assertType.Name, err) 129 } 130 131 if a == nil { 132 return nil, errNotFound 133 } 134 135 return a, nil 136 } 137 138 func (fsbs *filesystemBackstore) Put(assertType *AssertionType, assert Assertion) error { 139 fsbs.mu.Lock() 140 defer fsbs.mu.Unlock() 141 142 primaryPath := assert.Ref().PrimaryKey 143 144 curAssert, err := fsbs.currentAssertion(assertType, primaryPath, assertType.MaxSupportedFormat()) 145 if err == nil { 146 curRev := curAssert.Revision() 147 rev := assert.Revision() 148 if curRev >= rev { 149 return &RevisionError{Current: curRev, Used: rev} 150 } 151 } else if err != errNotFound { 152 return err 153 } 154 155 formatnum := assert.Format() 156 activeFn := "active" 157 if formatnum > 0 { 158 activeFn = fmt.Sprintf("active.%d", formatnum) 159 } 160 diskPrimaryPath := filepath.Join(diskPrimaryPathComps(primaryPath, activeFn)...) 161 err = atomicWriteEntry(Encode(assert), false, fsbs.top, assertType.Name, diskPrimaryPath) 162 if err != nil { 163 return fmt.Errorf("broken assertion storage, cannot write assertion: %v", err) 164 } 165 return nil 166 } 167 168 func (fsbs *filesystemBackstore) Get(assertType *AssertionType, key []string, maxFormat int) (Assertion, error) { 169 fsbs.mu.RLock() 170 defer fsbs.mu.RUnlock() 171 172 a, err := fsbs.currentAssertion(assertType, key, maxFormat) 173 if err == errNotFound { 174 return nil, &NotFoundError{Type: assertType} 175 } 176 return a, err 177 } 178 179 func (fsbs *filesystemBackstore) search(assertType *AssertionType, diskPattern []string, foundCb func(Assertion), maxFormat int) error { 180 assertTypeTop := filepath.Join(fsbs.top, assertType.Name) 181 candCb := func(diskPrimaryPaths []string) error { 182 a, err := fsbs.pickLatestAssertion(assertType, diskPrimaryPaths, maxFormat) 183 if err == errNotFound { 184 return nil 185 } 186 if err != nil { 187 return err 188 } 189 foundCb(a) 190 return nil 191 } 192 err := findWildcard(assertTypeTop, diskPattern, candCb) 193 if err != nil { 194 return fmt.Errorf("broken assertion storage, searching for %s: %v", assertType.Name, err) 195 } 196 return nil 197 } 198 199 func (fsbs *filesystemBackstore) Search(assertType *AssertionType, headers map[string]string, foundCb func(Assertion), maxFormat int) error { 200 fsbs.mu.RLock() 201 defer fsbs.mu.RUnlock() 202 203 n := len(assertType.PrimaryKey) 204 diskPattern := make([]string, n+1) 205 for i, k := range assertType.PrimaryKey { 206 keyVal := headers[k] 207 if keyVal == "" { 208 diskPattern[i] = "*" 209 } else { 210 diskPattern[i] = url.QueryEscape(keyVal) 211 } 212 } 213 diskPattern[n] = "active*" 214 215 candCb := func(a Assertion) { 216 if searchMatch(a, headers) { 217 foundCb(a) 218 } 219 } 220 return fsbs.search(assertType, diskPattern, candCb, maxFormat) 221 }