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