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 }