github.com/nxadm/tail@v1.4.12-0.20231010141446-ba755e4d73b6/watch/inotify.go (about)

     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  
     5  package watch
     6  
     7  import (
     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.
    19  type InotifyFileWatcher struct {
    20  	Filename string
    21  	Size     int64
    22  }
    23  
    24  func NewInotifyFileWatcher(filename string) *InotifyFileWatcher {
    25  	fw := &InotifyFileWatcher{filepath.Clean(filename), 0}
    26  	return fw
    27  }
    28  
    29  func (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  
    69  func (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  }