fileutil.go 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. // Copyright (c) 2014 The fileutil Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. // Package fileutil collects some file utility functions.
  5. package fileutil
  6. import (
  7. "fmt"
  8. "io"
  9. "os"
  10. "path/filepath"
  11. "runtime"
  12. "strconv"
  13. "sync"
  14. "time"
  15. )
  16. // GoMFile is a concurrent access safe version of MFile.
  17. type GoMFile struct {
  18. mfile *MFile
  19. mutex sync.Mutex
  20. }
  21. // NewGoMFile return a newly created GoMFile.
  22. func NewGoMFile(fname string, flag int, perm os.FileMode, delta_ns int64) (m *GoMFile, err error) {
  23. m = &GoMFile{}
  24. if m.mfile, err = NewMFile(fname, flag, perm, delta_ns); err != nil {
  25. m = nil
  26. }
  27. return
  28. }
  29. func (m *GoMFile) File() (file *os.File, err error) {
  30. m.mutex.Lock()
  31. defer m.mutex.Unlock()
  32. return m.mfile.File()
  33. }
  34. func (m *GoMFile) SetChanged() {
  35. m.mutex.Lock()
  36. defer m.mutex.Unlock()
  37. m.mfile.SetChanged()
  38. }
  39. func (m *GoMFile) SetHandler(h MFileHandler) {
  40. m.mutex.Lock()
  41. defer m.mutex.Unlock()
  42. m.mfile.SetHandler(h)
  43. }
  44. // MFileHandler resolves modifications of File.
  45. // Possible File context is expected to be a part of the handler's closure.
  46. type MFileHandler func(*os.File) error
  47. // MFile represents an os.File with a guard/handler on change/modification.
  48. // Example use case is an app with a configuration file which can be modified at any time
  49. // and have to be reloaded in such event prior to performing something configurable by that
  50. // file. The checks are made only on access to the MFile file by
  51. // File() and a time threshold/hysteresis value can be chosen on creating a new MFile.
  52. type MFile struct {
  53. file *os.File
  54. handler MFileHandler
  55. t0 int64
  56. delta int64
  57. ctime int64
  58. }
  59. // NewMFile returns a newly created MFile or Error if any.
  60. // The fname, flag and perm parameters have the same meaning as in os.Open.
  61. // For meaning of the delta_ns parameter please see the (m *MFile) File() docs.
  62. func NewMFile(fname string, flag int, perm os.FileMode, delta_ns int64) (m *MFile, err error) {
  63. m = &MFile{}
  64. m.t0 = time.Now().UnixNano()
  65. if m.file, err = os.OpenFile(fname, flag, perm); err != nil {
  66. return
  67. }
  68. var fi os.FileInfo
  69. if fi, err = m.file.Stat(); err != nil {
  70. return
  71. }
  72. m.ctime = fi.ModTime().UnixNano()
  73. m.delta = delta_ns
  74. runtime.SetFinalizer(m, func(m *MFile) {
  75. m.file.Close()
  76. })
  77. return
  78. }
  79. // SetChanged forces next File() to unconditionally handle modification of the wrapped os.File.
  80. func (m *MFile) SetChanged() {
  81. m.ctime = -1
  82. }
  83. // SetHandler sets a function to be invoked when modification of MFile is to be processed.
  84. func (m *MFile) SetHandler(h MFileHandler) {
  85. m.handler = h
  86. }
  87. // File returns an os.File from MFile. If time elapsed between the last invocation of this function
  88. // and now is at least delta_ns ns (a parameter of NewMFile) then the file is checked for
  89. // change/modification. For delta_ns == 0 the modification is checked w/o getting os.Time().
  90. // If a change is detected a handler is invoked on the MFile file.
  91. // Any of these steps can produce an Error. If that happens the function returns nil, Error.
  92. func (m *MFile) File() (file *os.File, err error) {
  93. var now int64
  94. mustCheck := m.delta == 0
  95. if !mustCheck {
  96. now = time.Now().UnixNano()
  97. mustCheck = now-m.t0 > m.delta
  98. }
  99. if mustCheck { // check interval reached
  100. var fi os.FileInfo
  101. if fi, err = m.file.Stat(); err != nil {
  102. return
  103. }
  104. if fi.ModTime().UnixNano() != m.ctime { // modification detected
  105. if m.handler == nil {
  106. return nil, fmt.Errorf("no handler set for modified file %q", m.file.Name())
  107. }
  108. if err = m.handler(m.file); err != nil {
  109. return
  110. }
  111. m.ctime = fi.ModTime().UnixNano()
  112. }
  113. m.t0 = now
  114. }
  115. return m.file, nil
  116. }
  117. // Read reads buf from r. It will either fill the full buf or fail.
  118. // It wraps the functionality of an io.Reader which may return less bytes than requested,
  119. // but may block if not all data are ready for the io.Reader.
  120. func Read(r io.Reader, buf []byte) (err error) {
  121. have := 0
  122. remain := len(buf)
  123. got := 0
  124. for remain > 0 {
  125. if got, err = r.Read(buf[have:]); err != nil {
  126. return
  127. }
  128. remain -= got
  129. have += got
  130. }
  131. return
  132. }
  133. // "os" and/or "syscall" extensions
  134. // FadviseAdvice is used by Fadvise.
  135. type FadviseAdvice int
  136. // FAdviseAdvice values.
  137. const (
  138. // $ grep FADV /usr/include/bits/fcntl.h
  139. POSIX_FADV_NORMAL FadviseAdvice = iota // No further special treatment.
  140. POSIX_FADV_RANDOM // Expect random page references.
  141. POSIX_FADV_SEQUENTIAL // Expect sequential page references.
  142. POSIX_FADV_WILLNEED // Will need these pages.
  143. POSIX_FADV_DONTNEED // Don't need these pages.
  144. POSIX_FADV_NOREUSE // Data will be accessed once.
  145. )
  146. // TempFile creates a new temporary file in the directory dir with a name
  147. // ending with suffix, basename starting with prefix, opens the file for
  148. // reading and writing, and returns the resulting *os.File. If dir is the
  149. // empty string, TempFile uses the default directory for temporary files (see
  150. // os.TempDir). Multiple programs calling TempFile simultaneously will not
  151. // choose the same file. The caller can use f.Name() to find the pathname of
  152. // the file. It is the caller's responsibility to remove the file when no
  153. // longer needed.
  154. //
  155. // NOTE: This function differs from ioutil.TempFile.
  156. func TempFile(dir, prefix, suffix string) (f *os.File, err error) {
  157. if dir == "" {
  158. dir = os.TempDir()
  159. }
  160. nconflict := 0
  161. for i := 0; i < 10000; i++ {
  162. name := filepath.Join(dir, prefix+nextInfix()+suffix)
  163. f, err = os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600)
  164. if os.IsExist(err) {
  165. if nconflict++; nconflict > 10 {
  166. rand = reseed()
  167. }
  168. continue
  169. }
  170. break
  171. }
  172. return
  173. }
  174. // Random number state.
  175. // We generate random temporary file names so that there's a good
  176. // chance the file doesn't exist yet - keeps the number of tries in
  177. // TempFile to a minimum.
  178. var rand uint32
  179. var randmu sync.Mutex
  180. func reseed() uint32 {
  181. return uint32(time.Now().UnixNano() + int64(os.Getpid()))
  182. }
  183. func nextInfix() string {
  184. randmu.Lock()
  185. r := rand
  186. if r == 0 {
  187. r = reseed()
  188. }
  189. r = r*1664525 + 1013904223 // constants from Numerical Recipes
  190. rand = r
  191. randmu.Unlock()
  192. return strconv.Itoa(int(1e9 + r%1e9))[1:]
  193. }