github.com/liumingmin/tail@v1.0.0/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/hpcloud/tail/util"
    12  
    13  	"gopkg.in/fsnotify/fsnotify.v1"
    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  		defer func() {
    79  			recover()
    80  		}()
    81  
    82  		events := Events(fw.Filename)
    83  
    84  		for {
    85  			prevSize := fw.Size
    86  
    87  			var evt fsnotify.Event
    88  			var ok bool
    89  
    90  			select {
    91  			case evt, ok = <-events:
    92  				if !ok {
    93  					RemoveWatch(fw.Filename)
    94  					return
    95  				}
    96  			case <-t.Dying():
    97  				RemoveWatch(fw.Filename)
    98  				return
    99  			}
   100  
   101  			switch {
   102  			case evt.Op&fsnotify.Remove == fsnotify.Remove:
   103  				fallthrough
   104  
   105  			case evt.Op&fsnotify.Rename == fsnotify.Rename:
   106  				RemoveWatch(fw.Filename)
   107  				changes.NotifyDeleted()
   108  				return
   109  
   110  			//With an open fd, unlink(fd) - inotify returns IN_ATTRIB (==fsnotify.Chmod)
   111  			case evt.Op&fsnotify.Chmod == fsnotify.Chmod:
   112  				fallthrough
   113  
   114  			case evt.Op&fsnotify.Write == fsnotify.Write:
   115  				fi, err := os.Stat(fw.Filename)
   116  				if err != nil {
   117  					if os.IsNotExist(err) {
   118  						RemoveWatch(fw.Filename)
   119  						changes.NotifyDeleted()
   120  						return
   121  					}
   122  					// XXX: report this error back to the user
   123  					util.Fatal("Failed to stat file %v: %v", fw.Filename, err)
   124  				}
   125  				fw.Size = fi.Size()
   126  
   127  				if prevSize > 0 && prevSize > fw.Size {
   128  					changes.NotifyTruncated()
   129  				} else {
   130  					changes.NotifyModified()
   131  				}
   132  				prevSize = fw.Size
   133  			}
   134  		}
   135  	}()
   136  
   137  	return changes, nil
   138  }