github.com/evdatsion/aphelion-dpos-bft@v0.32.1/libs/autofile/autofile.go (about)

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