gitee.com/mysnapcore/mysnapd@v0.1.0/asserts/fsbackstore.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2015-2022 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  // diskPrimaryPathComps computes the components of the path for an assertion.
   104  // The path will look like this: (all <comp> are query escaped)
   105  // <primaryPath0>/<primaryPath1>...[/0:<optPrimaryPath0>[/1:<optPrimaryPath1>]...]/<active>
   106  // The components #:<value> for the optional primary path values
   107  // appear only if their value is not the default.
   108  // This makes it so that assertions with default values have the same
   109  // paths as for snapd versions without those optional primary keys
   110  // yet.
   111  func diskPrimaryPathComps(assertType *AssertionType, primaryPath []string, active string) []string {
   112  	n := len(primaryPath)
   113  	comps := make([]string, 0, n+1)
   114  	// safety against '/' etc
   115  	noptional := -1
   116  	for i, comp := range primaryPath {
   117  		defl := assertType.OptionalPrimaryKeyDefaults[assertType.PrimaryKey[i]]
   118  		qvalue := url.QueryEscape(comp)
   119  		if defl != "" {
   120  			noptional++
   121  			if comp == defl {
   122  				continue
   123  			}
   124  			qvalue = fmt.Sprintf("%d:%s", noptional, qvalue)
   125  		}
   126  		comps = append(comps, qvalue)
   127  	}
   128  	comps = append(comps, active)
   129  	return comps
   130  }
   131  
   132  func (fsbs *filesystemBackstore) currentAssertion(assertType *AssertionType, primaryPath []string, maxFormat int) (Assertion, error) {
   133  	var a Assertion
   134  	namesCb := func(relpaths []string) error {
   135  		var err error
   136  		a, err = fsbs.pickLatestAssertion(assertType, relpaths, maxFormat)
   137  		if err == errNotFound {
   138  			return nil
   139  		}
   140  		return err
   141  	}
   142  
   143  	comps := diskPrimaryPathComps(assertType, primaryPath, "active*")
   144  	assertTypeTop := filepath.Join(fsbs.top, assertType.Name)
   145  	err := findWildcard(assertTypeTop, comps, 0, namesCb)
   146  	if err != nil {
   147  		return nil, fmt.Errorf("broken assertion storage, looking for %s: %v", assertType.Name, err)
   148  	}
   149  
   150  	if a == nil {
   151  		return nil, errNotFound
   152  	}
   153  
   154  	return a, nil
   155  }
   156  
   157  func (fsbs *filesystemBackstore) Put(assertType *AssertionType, assert Assertion) error {
   158  	fsbs.mu.Lock()
   159  	defer fsbs.mu.Unlock()
   160  
   161  	primaryPath := assert.Ref().PrimaryKey
   162  
   163  	curAssert, err := fsbs.currentAssertion(assertType, primaryPath, assertType.MaxSupportedFormat())
   164  	if err == nil {
   165  		curRev := curAssert.Revision()
   166  		rev := assert.Revision()
   167  		if curRev >= rev {
   168  			return &RevisionError{Current: curRev, Used: rev}
   169  		}
   170  	} else if err != errNotFound {
   171  		return err
   172  	}
   173  
   174  	formatnum := assert.Format()
   175  	activeFn := "active"
   176  	if formatnum > 0 {
   177  		activeFn = fmt.Sprintf("active.%d", formatnum)
   178  	}
   179  	diskPrimaryPath := filepath.Join(diskPrimaryPathComps(assertType, primaryPath, activeFn)...)
   180  	err = atomicWriteEntry(Encode(assert), false, fsbs.top, assertType.Name, diskPrimaryPath)
   181  	if err != nil {
   182  		return fmt.Errorf("broken assertion storage, cannot write assertion: %v", err)
   183  	}
   184  	return nil
   185  }
   186  
   187  func (fsbs *filesystemBackstore) Get(assertType *AssertionType, key []string, maxFormat int) (Assertion, error) {
   188  	fsbs.mu.RLock()
   189  	defer fsbs.mu.RUnlock()
   190  
   191  	if len(key) > len(assertType.PrimaryKey) {
   192  		return nil, fmt.Errorf("internal error: Backstore.Get given a key longer than expected for %q: %v", assertType.Name, key)
   193  	}
   194  
   195  	a, err := fsbs.currentAssertion(assertType, key, maxFormat)
   196  	if err == errNotFound {
   197  		return nil, &NotFoundError{Type: assertType}
   198  	}
   199  	return a, err
   200  }
   201  
   202  func (fsbs *filesystemBackstore) search(assertType *AssertionType, diskPattern []string, foundCb func(Assertion), maxFormat int) error {
   203  	assertTypeTop := filepath.Join(fsbs.top, assertType.Name)
   204  	candCb := func(diskPrimaryPaths []string) error {
   205  		a, err := fsbs.pickLatestAssertion(assertType, diskPrimaryPaths, maxFormat)
   206  		if err == errNotFound {
   207  			return nil
   208  		}
   209  		if err != nil {
   210  			return err
   211  		}
   212  		foundCb(a)
   213  		return nil
   214  	}
   215  	err := findWildcard(assertTypeTop, diskPattern, 0, candCb)
   216  	if err != nil {
   217  		return fmt.Errorf("broken assertion storage, searching for %s: %v", assertType.Name, err)
   218  	}
   219  	return nil
   220  }
   221  
   222  func (fsbs *filesystemBackstore) searchOptional(assertType *AssertionType, kopt, pattPos, firstOpt int, diskPattern []string, headers map[string]string, foundCb func(Assertion), maxFormat int) error {
   223  	if kopt == len(assertType.PrimaryKey) {
   224  		candCb := func(a Assertion) {
   225  			if searchMatch(a, headers) {
   226  				foundCb(a)
   227  			}
   228  		}
   229  
   230  		diskPattern[pattPos] = "active*"
   231  		return fsbs.search(assertType, diskPattern[:pattPos+1], candCb, maxFormat)
   232  	}
   233  	k := assertType.PrimaryKey[kopt]
   234  	keyVal := headers[k]
   235  	switch keyVal {
   236  	case "":
   237  		diskPattern[pattPos] = fmt.Sprintf("%d:*", kopt-firstOpt)
   238  		if err := fsbs.searchOptional(assertType, kopt+1, pattPos+1, firstOpt, diskPattern, headers, foundCb, maxFormat); err != nil {
   239  			return err
   240  		}
   241  		fallthrough
   242  	case assertType.OptionalPrimaryKeyDefaults[k]:
   243  		return fsbs.searchOptional(assertType, kopt+1, pattPos, firstOpt, diskPattern, headers, foundCb, maxFormat)
   244  	default:
   245  		diskPattern[pattPos] = fmt.Sprintf("%d:%s", kopt-firstOpt, url.QueryEscape(keyVal))
   246  		return fsbs.searchOptional(assertType, kopt+1, pattPos+1, firstOpt, diskPattern, headers, foundCb, maxFormat)
   247  	}
   248  }
   249  
   250  func (fsbs *filesystemBackstore) Search(assertType *AssertionType, headers map[string]string, foundCb func(Assertion), maxFormat int) error {
   251  	fsbs.mu.RLock()
   252  	defer fsbs.mu.RUnlock()
   253  
   254  	n := len(assertType.PrimaryKey)
   255  	nopt := len(assertType.OptionalPrimaryKeyDefaults)
   256  	diskPattern := make([]string, n+1)
   257  	for i, k := range assertType.PrimaryKey[:n-nopt] {
   258  		keyVal := headers[k]
   259  		if keyVal == "" {
   260  			diskPattern[i] = "*"
   261  		} else {
   262  			diskPattern[i] = url.QueryEscape(keyVal)
   263  		}
   264  	}
   265  	pattPos := n - nopt
   266  
   267  	return fsbs.searchOptional(assertType, pattPos, pattPos, pattPos, diskPattern, headers, foundCb, maxFormat)
   268  }
   269  
   270  // errFound marks the case an assertion was found
   271  var errFound = errors.New("found")
   272  
   273  func (fsbs *filesystemBackstore) SequenceMemberAfter(assertType *AssertionType, sequenceKey []string, after, maxFormat int) (SequenceMember, error) {
   274  	if !assertType.SequenceForming() {
   275  		panic(fmt.Sprintf("internal error: SequenceMemberAfter on non sequence-forming assertion type %s", assertType.Name))
   276  	}
   277  	if len(sequenceKey) != len(assertType.PrimaryKey)-1 {
   278  		return nil, fmt.Errorf("internal error: SequenceMemberAfter's sequence key argument length must be exactly 1 less than the assertion type primary key")
   279  	}
   280  
   281  	fsbs.mu.RLock()
   282  	defer fsbs.mu.RUnlock()
   283  
   284  	n := len(assertType.PrimaryKey)
   285  	diskPattern := make([]string, n+1)
   286  	for i, k := range sequenceKey {
   287  		diskPattern[i] = url.QueryEscape(k)
   288  	}
   289  	seqWildcard := "#>" // ascending sequence wildcard
   290  	if after == -1 {
   291  		// find the latest in sequence
   292  		// use descending sequence wildcard
   293  		seqWildcard = "#<"
   294  	}
   295  	diskPattern[n-1] = seqWildcard
   296  	diskPattern[n] = "active*"
   297  
   298  	var a Assertion
   299  	candCb := func(diskPrimaryPaths []string) error {
   300  		var err error
   301  		a, err = fsbs.pickLatestAssertion(assertType, diskPrimaryPaths, maxFormat)
   302  		if err == errNotFound {
   303  			return nil
   304  		}
   305  		if err != nil {
   306  			return err
   307  		}
   308  		return errFound
   309  	}
   310  
   311  	assertTypeTop := filepath.Join(fsbs.top, assertType.Name)
   312  	err := findWildcard(assertTypeTop, diskPattern, after, candCb)
   313  	if err == errFound {
   314  		return a.(SequenceMember), nil
   315  	}
   316  	if err != nil {
   317  		return nil, fmt.Errorf("broken assertion storage, searching for %s: %v", assertType.Name, err)
   318  	}
   319  
   320  	return nil, &NotFoundError{Type: assertType}
   321  }