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