github.com/bgrewell/tail@v1.0.0/watch/inotify_tracker.go (about)

     1  // Copyright (c) 2015 HPE Software Inc. All rights reserved.
     2  // Copyright (c) 2013 ActiveState Software Inc. All rights reserved.
     3  
     4  package watch
     5  
     6  import (
     7  	"log"
     8  	"os"
     9  	"path/filepath"
    10  	"sync"
    11  	"syscall"
    12  
    13  	"github.com/hpcloud/tail/util"
    14  
    15  	"gopkg.in/fsnotify.v1"
    16  )
    17  
    18  type InotifyTracker struct {
    19  	mux       sync.Mutex
    20  	watcher   *fsnotify.Watcher
    21  	chans     map[string]chan fsnotify.Event
    22  	done      map[string]chan bool
    23  	watchNums map[string]int
    24  	watch     chan *watchInfo
    25  	remove    chan *watchInfo
    26  	error     chan error
    27  }
    28  
    29  type watchInfo struct {
    30  	op    fsnotify.Op
    31  	fname string
    32  }
    33  
    34  func (this *watchInfo) isCreate() bool {
    35  	return this.op == fsnotify.Create
    36  }
    37  
    38  var (
    39  	// globally shared InotifyTracker; ensures only one fsnotify.Watcher is used
    40  	shared *InotifyTracker
    41  
    42  	// these are used to ensure the shared InotifyTracker is run exactly once
    43  	once  = sync.Once{}
    44  	goRun = func() {
    45  		shared = &InotifyTracker{
    46  			mux:       sync.Mutex{},
    47  			chans:     make(map[string]chan fsnotify.Event),
    48  			done:      make(map[string]chan bool),
    49  			watchNums: make(map[string]int),
    50  			watch:     make(chan *watchInfo),
    51  			remove:    make(chan *watchInfo),
    52  			error:     make(chan error),
    53  		}
    54  		go shared.run()
    55  	}
    56  
    57  	logger = log.New(os.Stderr, "", log.LstdFlags)
    58  )
    59  
    60  // Watch signals the run goroutine to begin watching the input filename
    61  func Watch(fname string) error {
    62  	return watch(&watchInfo{
    63  		fname: fname,
    64  	})
    65  }
    66  
    67  // Watch create signals the run goroutine to begin watching the input filename
    68  // if call the WatchCreate function, don't call the Cleanup, call the RemoveWatchCreate
    69  func WatchCreate(fname string) error {
    70  	return watch(&watchInfo{
    71  		op:    fsnotify.Create,
    72  		fname: fname,
    73  	})
    74  }
    75  
    76  func watch(winfo *watchInfo) error {
    77  	// start running the shared InotifyTracker if not already running
    78  	once.Do(goRun)
    79  
    80  	winfo.fname = filepath.Clean(winfo.fname)
    81  	shared.watch <- winfo
    82  	return <-shared.error
    83  }
    84  
    85  // RemoveWatch signals the run goroutine to remove the watch for the input filename
    86  func RemoveWatch(fname string) {
    87  	remove(&watchInfo{
    88  		fname: fname,
    89  	})
    90  }
    91  
    92  // RemoveWatch create signals the run goroutine to remove the watch for the input filename
    93  func RemoveWatchCreate(fname string) {
    94  	remove(&watchInfo{
    95  		op:    fsnotify.Create,
    96  		fname: fname,
    97  	})
    98  }
    99  
   100  func remove(winfo *watchInfo) {
   101  	// start running the shared InotifyTracker if not already running
   102  	once.Do(goRun)
   103  
   104  	winfo.fname = filepath.Clean(winfo.fname)
   105  	shared.mux.Lock()
   106  	done := shared.done[winfo.fname]
   107  	if done != nil {
   108  		delete(shared.done, winfo.fname)
   109  		close(done)
   110  	}
   111  
   112  	fname := winfo.fname
   113  	if winfo.isCreate() {
   114  		// Watch for new files to be created in the parent directory.
   115  		fname = filepath.Dir(fname)
   116  	}
   117  	shared.watchNums[fname]--
   118  	watchNum := shared.watchNums[fname]
   119  	if watchNum == 0 {
   120  		delete(shared.watchNums, fname)
   121  	}
   122  	shared.mux.Unlock()
   123  
   124  	// If we were the last ones to watch this file, unsubscribe from inotify.
   125  	// This needs to happen after releasing the lock because fsnotify waits
   126  	// synchronously for the kernel to acknowledge the removal of the watch
   127  	// for this file, which causes us to deadlock if we still held the lock.
   128  	if watchNum == 0 {
   129  		shared.watcher.Remove(fname)
   130  	}
   131  	shared.remove <- winfo
   132  }
   133  
   134  // Events returns a channel to which FileEvents corresponding to the input filename
   135  // will be sent. This channel will be closed when removeWatch is called on this
   136  // filename.
   137  func Events(fname string) <-chan fsnotify.Event {
   138  	shared.mux.Lock()
   139  	defer shared.mux.Unlock()
   140  
   141  	return shared.chans[fname]
   142  }
   143  
   144  // Cleanup removes the watch for the input filename if necessary.
   145  func Cleanup(fname string) {
   146  	RemoveWatch(fname)
   147  }
   148  
   149  // watchFlags calls fsnotify.WatchFlags for the input filename and flags, creating
   150  // a new Watcher if the previous Watcher was closed.
   151  func (shared *InotifyTracker) addWatch(winfo *watchInfo) error {
   152  	shared.mux.Lock()
   153  	defer shared.mux.Unlock()
   154  
   155  	if shared.chans[winfo.fname] == nil {
   156  		shared.chans[winfo.fname] = make(chan fsnotify.Event)
   157  		shared.done[winfo.fname] = make(chan bool)
   158  	}
   159  
   160  	fname := winfo.fname
   161  	if winfo.isCreate() {
   162  		// Watch for new files to be created in the parent directory.
   163  		fname = filepath.Dir(fname)
   164  	}
   165  
   166  	// already in inotify watch
   167  	if shared.watchNums[fname] > 0 {
   168  		shared.watchNums[fname]++
   169  		if winfo.isCreate() {
   170  			shared.watchNums[winfo.fname]++
   171  		}
   172  		return nil
   173  	}
   174  
   175  	err := shared.watcher.Add(fname)
   176  	if err == nil {
   177  		shared.watchNums[fname]++
   178  		if winfo.isCreate() {
   179  			shared.watchNums[winfo.fname]++
   180  		}
   181  	}
   182  	return err
   183  }
   184  
   185  // removeWatch calls fsnotify.RemoveWatch for the input filename and closes the
   186  // corresponding events channel.
   187  func (shared *InotifyTracker) removeWatch(winfo *watchInfo) {
   188  	shared.mux.Lock()
   189  	defer shared.mux.Unlock()
   190  
   191  	ch := shared.chans[winfo.fname]
   192  	if ch == nil {
   193  		return
   194  	}
   195  
   196  	delete(shared.chans, winfo.fname)
   197  	close(ch)
   198  
   199  	if !winfo.isCreate() {
   200  		return
   201  	}
   202  
   203  	shared.watchNums[winfo.fname]--
   204  	if shared.watchNums[winfo.fname] == 0 {
   205  		delete(shared.watchNums, winfo.fname)
   206  	}
   207  }
   208  
   209  // sendEvent sends the input event to the appropriate Tail.
   210  func (shared *InotifyTracker) sendEvent(event fsnotify.Event) {
   211  	name := filepath.Clean(event.Name)
   212  
   213  	shared.mux.Lock()
   214  	ch := shared.chans[name]
   215  	done := shared.done[name]
   216  	shared.mux.Unlock()
   217  
   218  	if ch != nil && done != nil {
   219  		select {
   220  		case ch <- event:
   221  		case <-done:
   222  		}
   223  	}
   224  }
   225  
   226  // run starts the goroutine in which the shared struct reads events from its
   227  // Watcher's Event channel and sends the events to the appropriate Tail.
   228  func (shared *InotifyTracker) run() {
   229  	watcher, err := fsnotify.NewWatcher()
   230  	if err != nil {
   231  		util.Fatal("failed to create Watcher")
   232  	}
   233  	shared.watcher = watcher
   234  
   235  	for {
   236  		select {
   237  		case winfo := <-shared.watch:
   238  			shared.error <- shared.addWatch(winfo)
   239  
   240  		case winfo := <-shared.remove:
   241  			shared.removeWatch(winfo)
   242  
   243  		case event, open := <-shared.watcher.Events:
   244  			if !open {
   245  				return
   246  			}
   247  			shared.sendEvent(event)
   248  
   249  		case err, open := <-shared.watcher.Errors:
   250  			if !open {
   251  				return
   252  			} else if err != nil {
   253  				sysErr, ok := err.(*os.SyscallError)
   254  				if !ok || sysErr.Err != syscall.EINTR {
   255  					logger.Printf("Error in Watcher Error channel: %s", err)
   256  				}
   257  			}
   258  		}
   259  	}
   260  }