github.com/yacovm/fabric@v2.0.0-alpha.0.20191128145320-c5d4087dc723+incompatible/core/chaincode/persistence/persistence.go (about)

     1  /*
     2  Copyright IBM Corp. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package persistence
     8  
     9  import (
    10  	"encoding/hex"
    11  	"fmt"
    12  	"io/ioutil"
    13  	"os"
    14  	"path/filepath"
    15  	"regexp"
    16  
    17  	"github.com/hyperledger/fabric/common/chaincode"
    18  	"github.com/hyperledger/fabric/common/flogging"
    19  	"github.com/hyperledger/fabric/common/util"
    20  
    21  	"github.com/pkg/errors"
    22  )
    23  
    24  var logger = flogging.MustGetLogger("chaincode.persistence")
    25  
    26  // IOReadWriter defines the interface needed for reading, writing, removing, and
    27  // checking for existence of a specified file
    28  type IOReadWriter interface {
    29  	ReadDir(string) ([]os.FileInfo, error)
    30  	ReadFile(string) ([]byte, error)
    31  	Remove(name string) error
    32  	WriteFile(string, string, []byte) error
    33  	MakeDir(string, os.FileMode) error
    34  	Exists(path string) (bool, error)
    35  }
    36  
    37  // FilesystemIO is the production implementation of the IOWriter interface
    38  type FilesystemIO struct {
    39  }
    40  
    41  // WriteFile writes a file to the filesystem; it does so atomically
    42  // by first writing to a temp file and then renaming the file so that
    43  // if the operation crashes midway we're not stuck with a bad package
    44  func (f *FilesystemIO) WriteFile(path, name string, data []byte) error {
    45  	if path == "" {
    46  		return errors.New("empty path not allowed")
    47  	}
    48  	tmpFile, err := ioutil.TempFile(path, ".ccpackage.")
    49  	if err != nil {
    50  		return errors.Wrapf(err, "error creating temp file in directory '%s'", path)
    51  	}
    52  	defer os.Remove(tmpFile.Name())
    53  
    54  	if n, err := tmpFile.Write(data); err != nil || n != len(data) {
    55  		if err == nil {
    56  			err = errors.Errorf(
    57  				"failed to write the entire content of the file, expected %d, wrote %d",
    58  				len(data), n)
    59  		}
    60  		return errors.Wrapf(err, "error writing to temp file '%s'", tmpFile.Name())
    61  	}
    62  
    63  	if err := tmpFile.Close(); err != nil {
    64  		return errors.Wrapf(err, "error closing temp file '%s'", tmpFile.Name())
    65  	}
    66  
    67  	if err := os.Rename(tmpFile.Name(), filepath.Join(path, name)); err != nil {
    68  		return errors.Wrapf(err, "error renaming temp file '%s'", tmpFile.Name())
    69  	}
    70  
    71  	return nil
    72  }
    73  
    74  // Remove removes a file from the filesystem - used for rolling back an in-flight
    75  // Save operation upon a failure
    76  func (f *FilesystemIO) Remove(name string) error {
    77  	return os.Remove(name)
    78  }
    79  
    80  // ReadFile reads a file from the filesystem
    81  func (f *FilesystemIO) ReadFile(filename string) ([]byte, error) {
    82  	return ioutil.ReadFile(filename)
    83  }
    84  
    85  // ReadDir reads a directory from the filesystem
    86  func (f *FilesystemIO) ReadDir(dirname string) ([]os.FileInfo, error) {
    87  	return ioutil.ReadDir(dirname)
    88  }
    89  
    90  // MakeDir makes a directory on the filesystem (and any
    91  // necessary parent directories).
    92  func (f *FilesystemIO) MakeDir(dirname string, mode os.FileMode) error {
    93  	return os.MkdirAll(dirname, mode)
    94  }
    95  
    96  // Exists checks whether a file exists
    97  func (*FilesystemIO) Exists(path string) (bool, error) {
    98  	_, err := os.Stat(path)
    99  	if err == nil {
   100  		return true, nil
   101  	}
   102  	if os.IsNotExist(err) {
   103  		return false, nil
   104  	}
   105  
   106  	return false, errors.Wrapf(err, "could not determine whether file '%s' exists", path)
   107  }
   108  
   109  // Store holds the information needed for persisting a chaincode install package
   110  type Store struct {
   111  	Path       string
   112  	ReadWriter IOReadWriter
   113  }
   114  
   115  // NewStore creates a new chaincode persistence store using
   116  // the provided path on the filesystem.
   117  func NewStore(path string) *Store {
   118  	store := &Store{
   119  		Path:       path,
   120  		ReadWriter: &FilesystemIO{},
   121  	}
   122  	store.Initialize()
   123  	return store
   124  }
   125  
   126  // Initialize checks for the existence of the _lifecycle chaincodes
   127  // directory and creates it if it has not yet been created.
   128  func (s *Store) Initialize() {
   129  	var (
   130  		exists bool
   131  		err    error
   132  	)
   133  	if exists, err = s.ReadWriter.Exists(s.Path); exists {
   134  		return
   135  	}
   136  	if err != nil {
   137  		panic(fmt.Sprintf("Initialization of chaincode store failed: %s", err))
   138  	}
   139  	if err = s.ReadWriter.MakeDir(s.Path, 0750); err != nil {
   140  		panic(fmt.Sprintf("Could not create _lifecycle chaincodes install path: %s", err))
   141  	}
   142  }
   143  
   144  // Save persists chaincode install package bytes. It returns
   145  // the hash of the chaincode install package
   146  func (s *Store) Save(label string, ccInstallPkg []byte) (string, error) {
   147  	hash := util.ComputeSHA256(ccInstallPkg)
   148  	packageID := packageID(label, hash)
   149  
   150  	ccInstallPkgFileName := CCFileName(packageID)
   151  	ccInstallPkgFilePath := filepath.Join(s.Path, ccInstallPkgFileName)
   152  
   153  	if exists, _ := s.ReadWriter.Exists(ccInstallPkgFilePath); exists {
   154  		// chaincode install package was already installed
   155  		return packageID, nil
   156  	}
   157  
   158  	if err := s.ReadWriter.WriteFile(s.Path, ccInstallPkgFileName, ccInstallPkg); err != nil {
   159  		err = errors.Wrapf(err, "error writing chaincode install package to %s", ccInstallPkgFilePath)
   160  		logger.Error(err.Error())
   161  		return "", err
   162  	}
   163  
   164  	return packageID, nil
   165  }
   166  
   167  // Load loads a persisted chaincode install package bytes with
   168  // the given packageID.
   169  func (s *Store) Load(packageID string) ([]byte, error) {
   170  	ccInstallPkgPath := filepath.Join(s.Path, CCFileName(packageID))
   171  
   172  	exists, err := s.ReadWriter.Exists(ccInstallPkgPath)
   173  	if err != nil {
   174  		return nil, errors.Wrapf(err, "could not determine whether chaincode install package '%s' exists", packageID)
   175  	}
   176  	if !exists {
   177  		return nil, &CodePackageNotFoundErr{
   178  			PackageID: packageID,
   179  		}
   180  	}
   181  
   182  	ccInstallPkg, err := s.ReadWriter.ReadFile(ccInstallPkgPath)
   183  	if err != nil {
   184  		err = errors.Wrapf(err, "error reading chaincode install package at %s", ccInstallPkgPath)
   185  		return nil, err
   186  	}
   187  
   188  	return ccInstallPkg, nil
   189  }
   190  
   191  // Delete deletes a persisted chaincode.  Note, there is no locking,
   192  // so this should only be performed if the chaincode has already
   193  // been marked built.
   194  func (s *Store) Delete(packageID string) error {
   195  	ccInstallPkgPath := filepath.Join(s.Path, CCFileName(packageID))
   196  	return s.ReadWriter.Remove(ccInstallPkgPath)
   197  }
   198  
   199  // CodePackageNotFoundErr is the error returned when a code package cannot
   200  // be found in the persistence store
   201  type CodePackageNotFoundErr struct {
   202  	PackageID string
   203  }
   204  
   205  func (e CodePackageNotFoundErr) Error() string {
   206  	return fmt.Sprintf("chaincode install package '%s' not found", e.PackageID)
   207  }
   208  
   209  // ListInstalledChaincodes returns an array with information about the
   210  // chaincodes installed in the persistence store
   211  func (s *Store) ListInstalledChaincodes() ([]chaincode.InstalledChaincode, error) {
   212  	files, err := s.ReadWriter.ReadDir(s.Path)
   213  	if err != nil {
   214  		return nil, errors.Wrapf(err, "error reading chaincode directory at %s", s.Path)
   215  	}
   216  
   217  	installedChaincodes := []chaincode.InstalledChaincode{}
   218  	for _, file := range files {
   219  		if instCC, isInstCC := installedChaincodeFromFilename(file.Name()); isInstCC {
   220  			installedChaincodes = append(installedChaincodes, instCC)
   221  		}
   222  	}
   223  	return installedChaincodes, nil
   224  }
   225  
   226  // GetChaincodeInstallPath returns the path where chaincodes
   227  // are installed
   228  func (s *Store) GetChaincodeInstallPath() string {
   229  	return s.Path
   230  }
   231  
   232  func packageID(label string, hash []byte) string {
   233  	return fmt.Sprintf("%s:%x", label, hash)
   234  }
   235  
   236  func CCFileName(packageID string) string {
   237  	return packageID + ".tar.gz"
   238  }
   239  
   240  var packageFileMatcher = regexp.MustCompile("^(.+):([0-9abcdef]+)[.]tar[.]gz$")
   241  
   242  func installedChaincodeFromFilename(fileName string) (chaincode.InstalledChaincode, bool) {
   243  	matches := packageFileMatcher.FindStringSubmatch(fileName)
   244  	if len(matches) == 3 {
   245  		label := matches[1]
   246  		hash, _ := hex.DecodeString(matches[2])
   247  		packageID := packageID(label, hash)
   248  
   249  		return chaincode.InstalledChaincode{
   250  			Label:     label,
   251  			Hash:      hash,
   252  			PackageID: packageID,
   253  		}, true
   254  	}
   255  
   256  	return chaincode.InstalledChaincode{}, false
   257  }