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