github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/tm2/pkg/autofile/autofile.go (about)

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