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  }