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