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  }