github.com/BGrewell/tail@v1.0.1-0.20210309152823-689d25348e0e/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/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) error {
    87  	return 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) error {
    94  	return remove(&watchInfo{
    95  		op:    fsnotify.Create,
    96  		fname: fname,
    97  	})
    98  }
    99  
   100  func remove(winfo *watchInfo) error {
   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  	shared.mux.Unlock()
   112  
   113  	shared.remove <- winfo
   114  	return <-shared.error
   115  }
   116  
   117  // Events returns a channel to which FileEvents corresponding to the input filename
   118  // will be sent. This channel will be closed when removeWatch is called on this
   119  // filename.
   120  func Events(fname string) <-chan fsnotify.Event {
   121  	shared.mux.Lock()
   122  	defer shared.mux.Unlock()
   123  
   124  	return shared.chans[fname]
   125  }
   126  
   127  // Cleanup removes the watch for the input filename if necessary.
   128  func Cleanup(fname string) error {
   129  	return RemoveWatch(fname)
   130  }
   131  
   132  // watchFlags calls fsnotify.WatchFlags for the input filename and flags, creating
   133  // a new Watcher if the previous Watcher was closed.
   134  func (shared *InotifyTracker) addWatch(winfo *watchInfo) error {
   135  	shared.mux.Lock()
   136  	defer shared.mux.Unlock()
   137  
   138  	if shared.chans[winfo.fname] == nil {
   139  		shared.chans[winfo.fname] = make(chan fsnotify.Event)
   140  	}
   141  	if shared.done[winfo.fname] == nil {
   142  		shared.done[winfo.fname] = make(chan bool)
   143  	}
   144  
   145  	fname := winfo.fname
   146  	if winfo.isCreate() {
   147  		// Watch for new files to be created in the parent directory.
   148  		fname = filepath.Dir(fname)
   149  	}
   150  
   151  	var err error
   152  	// already in inotify watch
   153  	if shared.watchNums[fname] == 0 {
   154  		err = shared.watcher.Add(fname)
   155  	}
   156  	if err == nil {
   157  		shared.watchNums[fname]++
   158  	}
   159  	return err
   160  }
   161  
   162  // removeWatch calls fsnotify.RemoveWatch for the input filename and closes the
   163  // corresponding events channel.
   164  func (shared *InotifyTracker) removeWatch(winfo *watchInfo) error {
   165  	shared.mux.Lock()
   166  
   167  	ch := shared.chans[winfo.fname]
   168  	if ch != nil {
   169  		delete(shared.chans, winfo.fname)
   170  		close(ch)
   171  	}
   172  
   173  	fname := winfo.fname
   174  	if winfo.isCreate() {
   175  		// Watch for new files to be created in the parent directory.
   176  		fname = filepath.Dir(fname)
   177  	}
   178  	shared.watchNums[fname]--
   179  	watchNum := shared.watchNums[fname]
   180  	if watchNum == 0 {
   181  		delete(shared.watchNums, fname)
   182  	}
   183  	shared.mux.Unlock()
   184  
   185  	var err error
   186  	// If we were the last ones to watch this file, unsubscribe from inotify.
   187  	// This needs to happen after releasing the lock because fsnotify waits
   188  	// synchronously for the kernel to acknowledge the removal of the watch
   189  	// for this file, which causes us to deadlock if we still held the lock.
   190  	if watchNum == 0 {
   191  		err = shared.watcher.Remove(fname)
   192  	}
   193  
   194  	return err
   195  }
   196  
   197  // sendEvent sends the input event to the appropriate Tail.
   198  func (shared *InotifyTracker) sendEvent(event fsnotify.Event) {
   199  	name := filepath.Clean(event.Name)
   200  
   201  	shared.mux.Lock()
   202  	ch := shared.chans[name]
   203  	done := shared.done[name]
   204  	shared.mux.Unlock()
   205  
   206  	if ch != nil && done != nil {
   207  		select {
   208  		case ch <- event:
   209  		case <-done:
   210  		}
   211  	}
   212  }
   213  
   214  // run starts the goroutine in which the shared struct reads events from its
   215  // Watcher's Event channel and sends the events to the appropriate Tail.
   216  func (shared *InotifyTracker) run() {
   217  	watcher, err := fsnotify.NewWatcher()
   218  	if err != nil {
   219  		util.Fatal("failed to create Watcher")
   220  	}
   221  	shared.watcher = watcher
   222  
   223  	for {
   224  		select {
   225  		case winfo := <-shared.watch:
   226  			shared.error <- shared.addWatch(winfo)
   227  
   228  		case winfo := <-shared.remove:
   229  			shared.error <- shared.removeWatch(winfo)
   230  
   231  		case event, open := <-shared.watcher.Events:
   232  			if !open {
   233  				return
   234  			}
   235  			shared.sendEvent(event)
   236  
   237  		case err, open := <-shared.watcher.Errors:
   238  			if !open {
   239  				return
   240  			} else if err != nil {
   241  				sysErr, ok := err.(*os.SyscallError)
   242  				if !ok || sysErr.Err != syscall.EINTR {
   243  					logger.Printf("Error in Watcher Error channel: %s", err)
   244  				}
   245  			}
   246  		}
   247  	}
   248  }