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 }