github.com/hechain20/hechain@v0.0.0-20220316014945-b544036ba106/orderer/common/filerepo/filerepo.go (about) 1 /* 2 Copyright hechain. All Rights Reserved. 3 4 SPDX-License-Identifier: Apache-2.0 5 */ 6 7 package filerepo 8 9 import ( 10 "io/ioutil" 11 "os" 12 "path/filepath" 13 "strings" 14 "sync" 15 16 "github.com/hechain20/hechain/internal/fileutil" 17 "github.com/pkg/errors" 18 ) 19 20 const ( 21 repoFilePermPrivateRW os.FileMode = 0o600 22 defaultTransientFileMarker = "~" 23 ) 24 25 // Repo manages filesystem operations for saving files marked by the fileSuffix 26 // in order to support crash fault tolerance for components that need it by maintaining 27 // a file repo structure storing intermediate state. 28 type Repo struct { 29 mu sync.Mutex 30 fileRepoDir string 31 fileSuffix string 32 transientFileMarker string 33 } 34 35 // New initializes a new file repo at repoParentDir/fileSuffix. 36 // All file system operations on the returned file repo are thread safe. 37 func New(repoParentDir, fileSuffix string) (*Repo, error) { 38 if err := validateFileSuffix(fileSuffix); err != nil { 39 return nil, err 40 } 41 42 fileRepoDir := filepath.Join(repoParentDir, fileSuffix) 43 44 if _, err := fileutil.CreateDirIfMissing(fileRepoDir); err != nil { 45 return nil, err 46 } 47 48 if err := fileutil.SyncDir(repoParentDir); err != nil { 49 return nil, err 50 } 51 52 files, err := ioutil.ReadDir(fileRepoDir) 53 if err != nil { 54 return nil, err 55 } 56 57 // Remove existing transient files in the repo 58 transientFilePattern := "*" + fileSuffix + defaultTransientFileMarker 59 for _, f := range files { 60 isTransientFile, err := filepath.Match(transientFilePattern, f.Name()) 61 if err != nil { 62 return nil, err 63 } 64 if isTransientFile { 65 if err := os.Remove(filepath.Join(fileRepoDir, f.Name())); err != nil { 66 return nil, errors.Wrapf(err, "error cleaning up transient files") 67 } 68 } 69 } 70 71 if err := fileutil.SyncDir(fileRepoDir); err != nil { 72 return nil, err 73 } 74 75 return &Repo{ 76 transientFileMarker: defaultTransientFileMarker, 77 fileSuffix: fileSuffix, 78 fileRepoDir: fileRepoDir, 79 }, nil 80 } 81 82 // Save atomically persists the content to suffix/baseName+suffix file by first writing it 83 // to a tmp file marked by the transientFileMarker and then moves the file to the final 84 // destination indicated by the FileSuffix. 85 func (r *Repo) Save(baseName string, content []byte) error { 86 r.mu.Lock() 87 defer r.mu.Unlock() 88 89 fileName := r.baseToFileName(baseName) 90 dest := r.baseToFilePath(baseName) 91 92 if _, err := os.Stat(dest); err == nil { 93 return os.ErrExist 94 } 95 96 tmpFileName := fileName + r.transientFileMarker 97 if err := fileutil.CreateAndSyncFileAtomically(r.fileRepoDir, tmpFileName, fileName, content, repoFilePermPrivateRW); err != nil { 98 return err 99 } 100 101 return fileutil.SyncDir(r.fileRepoDir) 102 } 103 104 // Remove removes the file associated with baseName from the file system. 105 func (r *Repo) Remove(baseName string) error { 106 r.mu.Lock() 107 defer r.mu.Unlock() 108 109 filePath := r.baseToFilePath(baseName) 110 111 if err := os.RemoveAll(filePath); err != nil { 112 return err 113 } 114 115 return fileutil.SyncDir(r.fileRepoDir) 116 } 117 118 // Read reads the file in the fileRepo associated with baseName's contents. 119 func (r *Repo) Read(baseName string) ([]byte, error) { 120 r.mu.Lock() 121 defer r.mu.Unlock() 122 123 filePath := r.baseToFilePath(baseName) 124 return ioutil.ReadFile(filePath) 125 } 126 127 // List parses the directory and produce a list of file names, filtered by suffix. 128 func (r *Repo) List() ([]string, error) { 129 r.mu.Lock() 130 defer r.mu.Unlock() 131 132 var repoFiles []string 133 134 files, err := ioutil.ReadDir(r.fileRepoDir) 135 if err != nil { 136 return nil, err 137 } 138 139 for _, f := range files { 140 isFileSuffix, err := filepath.Match("*"+r.fileSuffix, f.Name()) 141 if err != nil { 142 return nil, err 143 } 144 if isFileSuffix { 145 repoFiles = append(repoFiles, f.Name()) 146 } 147 } 148 149 return repoFiles, nil 150 } 151 152 // FileToBaseName strips the suffix from the file name to get the associated channel name. 153 func (r *Repo) FileToBaseName(fileName string) string { 154 baseFile := filepath.Base(fileName) 155 156 return strings.TrimSuffix(baseFile, "."+r.fileSuffix) 157 } 158 159 func (r *Repo) baseToFilePath(baseName string) string { 160 return filepath.Join(r.fileRepoDir, r.baseToFileName(baseName)) 161 } 162 163 func (r *Repo) baseToFileName(baseName string) string { 164 return baseName + "." + r.fileSuffix 165 } 166 167 func validateFileSuffix(fileSuffix string) error { 168 if len(fileSuffix) == 0 { 169 return errors.New("fileSuffix illegal, cannot be empty") 170 } 171 172 if strings.Contains(fileSuffix, string(os.PathSeparator)) { 173 return errors.Errorf("fileSuffix [%s] illegal, cannot contain os path separator", fileSuffix) 174 } 175 176 return nil 177 }