You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

218 lines
5.0 KiB

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