github.com/DFWallet/tendermint-cosmos@v0.0.2/libs/autofile/autofile.go (about)

     1  package autofile
     2  
     3  import (
     4  	"os"
     5  	"os/signal"
     6  	"path/filepath"
     7  	"sync"
     8  	"syscall"
     9  	"time"
    10  
    11  	tmrand "github.com/DFWallet/tendermint-cosmos/libs/rand"
    12  )
    13  
    14  /* AutoFile usage
    15  
    16  // Create/Append to ./autofile_test
    17  af, err := OpenAutoFile("autofile_test")
    18  if err != nil {
    19  	panic(err)
    20  }
    21  
    22  // Stream of writes.
    23  // During this time, the file may be moved e.g. by logRotate.
    24  for i := 0; i < 60; i++ {
    25  	af.Write([]byte(Fmt("LOOP(%v)", i)))
    26  	time.Sleep(time.Second)
    27  }
    28  
    29  // Close the AutoFile
    30  err = af.Close()
    31  if err != nil {
    32  	panic(err)
    33  }
    34  */
    35  
    36  const (
    37  	autoFileClosePeriod = 1000 * time.Millisecond
    38  	autoFilePerms       = os.FileMode(0600)
    39  )
    40  
    41  // AutoFile automatically closes and re-opens file for writing. The file is
    42  // automatically setup to close itself every 1s and upon receiving SIGHUP.
    43  //
    44  // This is useful for using a log file with the logrotate tool.
    45  type AutoFile struct {
    46  	ID   string
    47  	Path string
    48  
    49  	closeTicker      *time.Ticker
    50  	closeTickerStopc chan struct{} // closed when closeTicker is stopped
    51  	hupc             chan os.Signal
    52  
    53  	mtx  sync.Mutex
    54  	file *os.File
    55  }
    56  
    57  // OpenAutoFile creates an AutoFile in the path (with random ID). If there is
    58  // an error, it will be of type *PathError or *ErrPermissionsChanged (if file's
    59  // permissions got changed (should be 0600)).
    60  func OpenAutoFile(path string) (*AutoFile, error) {
    61  	var err error
    62  	path, err = filepath.Abs(path)
    63  	if err != nil {
    64  		return nil, err
    65  	}
    66  	af := &AutoFile{
    67  		ID:               tmrand.Str(12) + ":" + path,
    68  		Path:             path,
    69  		closeTicker:      time.NewTicker(autoFileClosePeriod),
    70  		closeTickerStopc: make(chan struct{}),
    71  	}
    72  	if err := af.openFile(); err != nil {
    73  		af.Close()
    74  		return nil, err
    75  	}
    76  
    77  	// Close file on SIGHUP.
    78  	af.hupc = make(chan os.Signal, 1)
    79  	signal.Notify(af.hupc, syscall.SIGHUP)
    80  	go func() {
    81  		for range af.hupc {
    82  			_ = af.closeFile()
    83  		}
    84  	}()
    85  
    86  	go af.closeFileRoutine()
    87  
    88  	return af, nil
    89  }
    90  
    91  // Close shuts down the closing goroutine, SIGHUP handler and closes the
    92  // AutoFile.
    93  func (af *AutoFile) Close() error {
    94  	af.closeTicker.Stop()
    95  	close(af.closeTickerStopc)
    96  	if af.hupc != nil {
    97  		close(af.hupc)
    98  	}
    99  	return af.closeFile()
   100  }
   101  
   102  func (af *AutoFile) closeFileRoutine() {
   103  	for {
   104  		select {
   105  		case <-af.closeTicker.C:
   106  			_ = af.closeFile()
   107  		case <-af.closeTickerStopc:
   108  			return
   109  		}
   110  	}
   111  }
   112  
   113  func (af *AutoFile) closeFile() (err error) {
   114  	af.mtx.Lock()
   115  	defer af.mtx.Unlock()
   116  
   117  	file := af.file
   118  	if file == nil {
   119  		return nil
   120  	}
   121  
   122  	af.file = nil
   123  	return file.Close()
   124  }
   125  
   126  // Write writes len(b) bytes to the AutoFile. It returns the number of bytes
   127  // written and an error, if any. Write returns a non-nil error when n !=
   128  // len(b).
   129  // Opens AutoFile if needed.
   130  func (af *AutoFile) Write(b []byte) (n int, err error) {
   131  	af.mtx.Lock()
   132  	defer af.mtx.Unlock()
   133  
   134  	if af.file == nil {
   135  		if err = af.openFile(); err != nil {
   136  			return
   137  		}
   138  	}
   139  
   140  	n, err = af.file.Write(b)
   141  	return
   142  }
   143  
   144  // Sync commits the current contents of the file to stable storage. Typically,
   145  // this means flushing the file system's in-memory copy of recently written
   146  // data to disk.
   147  // Opens AutoFile if needed.
   148  func (af *AutoFile) Sync() error {
   149  	af.mtx.Lock()
   150  	defer af.mtx.Unlock()
   151  
   152  	if af.file == nil {
   153  		if err := af.openFile(); err != nil {
   154  			return err
   155  		}
   156  	}
   157  	return af.file.Sync()
   158  }
   159  
   160  func (af *AutoFile) openFile() error {
   161  	file, err := os.OpenFile(af.Path, os.O_RDWR|os.O_CREATE|os.O_APPEND, autoFilePerms)
   162  	if err != nil {
   163  		return err
   164  	}
   165  	// fileInfo, err := file.Stat()
   166  	// if err != nil {
   167  	// 	return err
   168  	// }
   169  	// if fileInfo.Mode() != autoFilePerms {
   170  	// 	return errors.NewErrPermissionsChanged(file.Name(), fileInfo.Mode(), autoFilePerms)
   171  	// }
   172  	af.file = file
   173  	return nil
   174  }
   175  
   176  // Size returns the size of the AutoFile. It returns -1 and an error if fails
   177  // get stats or open file.
   178  // Opens AutoFile if needed.
   179  func (af *AutoFile) Size() (int64, error) {
   180  	af.mtx.Lock()
   181  	defer af.mtx.Unlock()
   182  
   183  	if af.file == nil {
   184  		if err := af.openFile(); err != nil {
   185  			return -1, err
   186  		}
   187  	}
   188  
   189  	stat, err := af.file.Stat()
   190  	if err != nil {
   191  		return -1, err
   192  	}
   193  	return stat.Size(), nil
   194  }