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