| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223 |
- // Copyright (c) 2014 The fileutil Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style
- // license that can be found in the LICENSE file.
- // Package fileutil collects some file utility functions.
- package fileutil
- import (
- "fmt"
- "io"
- "os"
- "path/filepath"
- "runtime"
- "strconv"
- "sync"
- "time"
- )
- // GoMFile is a concurrent access safe version of MFile.
- type GoMFile struct {
- mfile *MFile
- mutex sync.Mutex
- }
- // NewGoMFile return a newly created GoMFile.
- func NewGoMFile(fname string, flag int, perm os.FileMode, delta_ns int64) (m *GoMFile, err error) {
- m = &GoMFile{}
- if m.mfile, err = NewMFile(fname, flag, perm, delta_ns); err != nil {
- m = nil
- }
- return
- }
- func (m *GoMFile) File() (file *os.File, err error) {
- m.mutex.Lock()
- defer m.mutex.Unlock()
- return m.mfile.File()
- }
- func (m *GoMFile) SetChanged() {
- m.mutex.Lock()
- defer m.mutex.Unlock()
- m.mfile.SetChanged()
- }
- func (m *GoMFile) SetHandler(h MFileHandler) {
- m.mutex.Lock()
- defer m.mutex.Unlock()
- m.mfile.SetHandler(h)
- }
- // MFileHandler resolves modifications of File.
- // Possible File context is expected to be a part of the handler's closure.
- type MFileHandler func(*os.File) error
- // MFile represents an os.File with a guard/handler on change/modification.
- // Example use case is an app with a configuration file which can be modified at any time
- // and have to be reloaded in such event prior to performing something configurable by that
- // file. The checks are made only on access to the MFile file by
- // File() and a time threshold/hysteresis value can be chosen on creating a new MFile.
- type MFile struct {
- file *os.File
- handler MFileHandler
- t0 int64
- delta int64
- ctime int64
- }
- // NewMFile returns a newly created MFile or Error if any.
- // The fname, flag and perm parameters have the same meaning as in os.Open.
- // For meaning of the delta_ns parameter please see the (m *MFile) File() docs.
- func NewMFile(fname string, flag int, perm os.FileMode, delta_ns int64) (m *MFile, err error) {
- m = &MFile{}
- m.t0 = time.Now().UnixNano()
- if m.file, err = os.OpenFile(fname, flag, perm); err != nil {
- return
- }
- var fi os.FileInfo
- if fi, err = m.file.Stat(); err != nil {
- return
- }
- m.ctime = fi.ModTime().UnixNano()
- m.delta = delta_ns
- runtime.SetFinalizer(m, func(m *MFile) {
- m.file.Close()
- })
- return
- }
- // SetChanged forces next File() to unconditionally handle modification of the wrapped os.File.
- func (m *MFile) SetChanged() {
- m.ctime = -1
- }
- // SetHandler sets a function to be invoked when modification of MFile is to be processed.
- func (m *MFile) SetHandler(h MFileHandler) {
- m.handler = h
- }
- // File returns an os.File from MFile. If time elapsed between the last invocation of this function
- // and now is at least delta_ns ns (a parameter of NewMFile) then the file is checked for
- // change/modification. For delta_ns == 0 the modification is checked w/o getting os.Time().
- // If a change is detected a handler is invoked on the MFile file.
- // Any of these steps can produce an Error. If that happens the function returns nil, Error.
- func (m *MFile) File() (file *os.File, err error) {
- var now int64
- mustCheck := m.delta == 0
- if !mustCheck {
- now = time.Now().UnixNano()
- mustCheck = now-m.t0 > m.delta
- }
- if mustCheck { // check interval reached
- var fi os.FileInfo
- if fi, err = m.file.Stat(); err != nil {
- return
- }
- if fi.ModTime().UnixNano() != m.ctime { // modification detected
- if m.handler == nil {
- return nil, fmt.Errorf("no handler set for modified file %q", m.file.Name())
- }
- if err = m.handler(m.file); err != nil {
- return
- }
- m.ctime = fi.ModTime().UnixNano()
- }
- m.t0 = now
- }
- return m.file, nil
- }
- // Read reads buf from r. It will either fill the full buf or fail.
- // It wraps the functionality of an io.Reader which may return less bytes than requested,
- // but may block if not all data are ready for the io.Reader.
- func Read(r io.Reader, buf []byte) (err error) {
- have := 0
- remain := len(buf)
- got := 0
- for remain > 0 {
- if got, err = r.Read(buf[have:]); err != nil {
- return
- }
- remain -= got
- have += got
- }
- return
- }
- // "os" and/or "syscall" extensions
- // FadviseAdvice is used by Fadvise.
- type FadviseAdvice int
- // FAdviseAdvice values.
- const (
- // $ grep FADV /usr/include/bits/fcntl.h
- POSIX_FADV_NORMAL FadviseAdvice = iota // No further special treatment.
- POSIX_FADV_RANDOM // Expect random page references.
- POSIX_FADV_SEQUENTIAL // Expect sequential page references.
- POSIX_FADV_WILLNEED // Will need these pages.
- POSIX_FADV_DONTNEED // Don't need these pages.
- POSIX_FADV_NOREUSE // Data will be accessed once.
- )
- // TempFile creates a new temporary file in the directory dir with a name
- // ending with suffix, basename starting with prefix, opens the file for
- // reading and writing, and returns the resulting *os.File. If dir is the
- // empty string, TempFile uses the default directory for temporary files (see
- // os.TempDir). Multiple programs calling TempFile simultaneously will not
- // choose the same file. The caller can use f.Name() to find the pathname of
- // the file. It is the caller's responsibility to remove the file when no
- // longer needed.
- //
- // NOTE: This function differs from ioutil.TempFile.
- func TempFile(dir, prefix, suffix string) (f *os.File, err error) {
- if dir == "" {
- dir = os.TempDir()
- }
- nconflict := 0
- for i := 0; i < 10000; i++ {
- name := filepath.Join(dir, prefix+nextInfix()+suffix)
- f, err = os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600)
- if os.IsExist(err) {
- if nconflict++; nconflict > 10 {
- rand = reseed()
- }
- continue
- }
- break
- }
- return
- }
- // Random number state.
- // We generate random temporary file names so that there's a good
- // chance the file doesn't exist yet - keeps the number of tries in
- // TempFile to a minimum.
- var rand uint32
- var randmu sync.Mutex
- func reseed() uint32 {
- return uint32(time.Now().UnixNano() + int64(os.Getpid()))
- }
- func nextInfix() string {
- randmu.Lock()
- r := rand
- if r == 0 {
- r = reseed()
- }
- r = r*1664525 + 1013904223 // constants from Numerical Recipes
- rand = r
- randmu.Unlock()
- return strconv.Itoa(int(1e9 + r%1e9))[1:]
- }
|