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  }