package autofile import ( "os" "os/signal" "path/filepath" "sync" "syscall" "time" tmrand "github.com/tendermint/tendermint/libs/rand" ) /* AutoFile usage // Create/Append to ./autofile_test af, err := OpenAutoFile("autofile_test") if err != nil { panic(err) } // Stream of writes. // During this time, the file may be moved e.g. by logRotate. for i := 0; i < 60; i++ { af.Write([]byte(Fmt("LOOP(%v)", i))) time.Sleep(time.Second) } // Close the AutoFile err = af.Close() if err != nil { panic(err) } */ const ( autoFileClosePeriod = 1000 * time.Millisecond autoFilePerms = os.FileMode(0600) ) // AutoFile automatically closes and re-opens file for writing. The file is // automatically setup to close itself every 1s and upon receiving SIGHUP. // // This is useful for using a log file with the logrotate tool. type AutoFile struct { ID string Path string closeTicker *time.Ticker closeTickerStopc chan struct{} // closed when closeTicker is stopped hupc chan os.Signal mtx sync.Mutex file *os.File } // OpenAutoFile creates an AutoFile in the path (with random ID). If there is // an error, it will be of type *PathError or *ErrPermissionsChanged (if file's // permissions got changed (should be 0600)). func OpenAutoFile(path string) (*AutoFile, error) { var err error path, err = filepath.Abs(path) if err != nil { return nil, err } af := &AutoFile{ ID: tmrand.Str(12) + ":" + path, Path: path, closeTicker: time.NewTicker(autoFileClosePeriod), closeTickerStopc: make(chan struct{}), } if err := af.openFile(); err != nil { af.Close() return nil, err } // Close file on SIGHUP. af.hupc = make(chan os.Signal, 1) signal.Notify(af.hupc, syscall.SIGHUP) go func() { for range af.hupc { _ = af.closeFile() } }() go af.closeFileRoutine() return af, nil } // Close shuts down the closing goroutine, SIGHUP handler and closes the // AutoFile. func (af *AutoFile) Close() error { af.closeTicker.Stop() close(af.closeTickerStopc) if af.hupc != nil { close(af.hupc) } return af.closeFile() } func (af *AutoFile) closeFileRoutine() { for { select { case <-af.closeTicker.C: _ = af.closeFile() case <-af.closeTickerStopc: return } } } func (af *AutoFile) closeFile() (err error) { af.mtx.Lock() defer af.mtx.Unlock() file := af.file if file == nil { return nil } af.file = nil return file.Close() } // Write writes len(b) bytes to the AutoFile. It returns the number of bytes // written and an error, if any. Write returns a non-nil error when n != // len(b). // Opens AutoFile if needed. func (af *AutoFile) Write(b []byte) (n int, err error) { af.mtx.Lock() defer af.mtx.Unlock() if af.file == nil { if err = af.openFile(); err != nil { return } } n, err = af.file.Write(b) return } // Sync commits the current contents of the file to stable storage. Typically, // this means flushing the file system's in-memory copy of recently written // data to disk. // Opens AutoFile if needed. func (af *AutoFile) Sync() error { af.mtx.Lock() defer af.mtx.Unlock() if af.file == nil { if err := af.openFile(); err != nil { return err } } return af.file.Sync() } func (af *AutoFile) openFile() error { file, err := os.OpenFile(af.Path, os.O_RDWR|os.O_CREATE|os.O_APPEND, autoFilePerms) if err != nil { return err } // fileInfo, err := file.Stat() // if err != nil { // return err // } // if fileInfo.Mode() != autoFilePerms { // return errors.NewErrPermissionsChanged(file.Name(), fileInfo.Mode(), autoFilePerms) // } af.file = file return nil } // Size returns the size of the AutoFile. It returns -1 and an error if fails // get stats or open file. // Opens AutoFile if needed. func (af *AutoFile) Size() (int64, error) { af.mtx.Lock() defer af.mtx.Unlock() if af.file == nil { if err := af.openFile(); err != nil { return -1, err } } stat, err := af.file.Stat() if err != nil { return -1, err } return stat.Size(), nil }