inotify.go

  1// Copyright (c) 2019 FOSS contributors of https://github.com/nxadm/tail
  2// Copyright (c) 2015 HPE Software Inc. All rights reserved.
  3// Copyright (c) 2013 ActiveState Software Inc. All rights reserved.
  4
  5package watch
  6
  7import (
  8	"fmt"
  9	"os"
 10	"path/filepath"
 11
 12	"github.com/nxadm/tail/util"
 13
 14    "github.com/fsnotify/fsnotify"
 15	"gopkg.in/tomb.v1"
 16)
 17
 18// InotifyFileWatcher uses inotify to monitor file changes.
 19type InotifyFileWatcher struct {
 20	Filename string
 21	Size     int64
 22}
 23
 24func NewInotifyFileWatcher(filename string) *InotifyFileWatcher {
 25	fw := &InotifyFileWatcher{filepath.Clean(filename), 0}
 26	return fw
 27}
 28
 29func (fw *InotifyFileWatcher) BlockUntilExists(t *tomb.Tomb) error {
 30	err := WatchCreate(fw.Filename)
 31	if err != nil {
 32		return err
 33	}
 34	defer RemoveWatchCreate(fw.Filename)
 35
 36	// Do a real check now as the file might have been created before
 37	// calling `WatchFlags` above.
 38	if _, err = os.Stat(fw.Filename); !os.IsNotExist(err) {
 39		// file exists, or stat returned an error.
 40		return err
 41	}
 42
 43	events := Events(fw.Filename)
 44
 45	for {
 46		select {
 47		case evt, ok := <-events:
 48			if !ok {
 49				return fmt.Errorf("inotify watcher has been closed")
 50			}
 51			evtName, err := filepath.Abs(evt.Name)
 52			if err != nil {
 53				return err
 54			}
 55			fwFilename, err := filepath.Abs(fw.Filename)
 56			if err != nil {
 57				return err
 58			}
 59			if evtName == fwFilename {
 60				return nil
 61			}
 62		case <-t.Dying():
 63			return tomb.ErrDying
 64		}
 65	}
 66	panic("unreachable")
 67}
 68
 69func (fw *InotifyFileWatcher) ChangeEvents(t *tomb.Tomb, pos int64) (*FileChanges, error) {
 70	err := Watch(fw.Filename)
 71	if err != nil {
 72		return nil, err
 73	}
 74
 75	changes := NewFileChanges()
 76	fw.Size = pos
 77
 78	go func() {
 79
 80		events := Events(fw.Filename)
 81
 82		for {
 83			prevSize := fw.Size
 84
 85			var evt fsnotify.Event
 86			var ok bool
 87
 88			select {
 89			case evt, ok = <-events:
 90				if !ok {
 91					RemoveWatch(fw.Filename)
 92					return
 93				}
 94			case <-t.Dying():
 95				RemoveWatch(fw.Filename)
 96				return
 97			}
 98
 99			switch {
100			case evt.Op&fsnotify.Remove == fsnotify.Remove:
101				fallthrough
102
103			case evt.Op&fsnotify.Rename == fsnotify.Rename:
104				RemoveWatch(fw.Filename)
105				changes.NotifyDeleted()
106				return
107
108			//With an open fd, unlink(fd) - inotify returns IN_ATTRIB (==fsnotify.Chmod)
109			case evt.Op&fsnotify.Chmod == fsnotify.Chmod:
110				fallthrough
111
112			case evt.Op&fsnotify.Write == fsnotify.Write:
113				fi, err := os.Stat(fw.Filename)
114				if err != nil {
115					if os.IsNotExist(err) {
116						RemoveWatch(fw.Filename)
117						changes.NotifyDeleted()
118						return
119					}
120					// XXX: report this error back to the user
121					util.Fatal("Failed to stat file %v: %v", fw.Filename, err)
122				}
123				fw.Size = fi.Size()
124
125				if prevSize > 0 && prevSize > fw.Size {
126					changes.NotifyTruncated()
127				} else {
128					changes.NotifyModified()
129				}
130				prevSize = fw.Size
131			}
132		}
133	}()
134
135	return changes, nil
136}