gopkg.in/hpcloud/tail.v1@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.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 RemoveWatch(fw.Filename)
    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  					return
    92  				}
    93  			case <-t.Dying():
    94  				return
    95  			}
    96  
    97  			switch {
    98  			case evt.Op&fsnotify.Remove == fsnotify.Remove:
    99  				fallthrough
   100  
   101  			case evt.Op&fsnotify.Rename == fsnotify.Rename:
   102  				changes.NotifyDeleted()
   103  				return
   104  
   105  			case evt.Op&fsnotify.Write == fsnotify.Write:
   106  				fi, err := os.Stat(fw.Filename)
   107  				if err != nil {
   108  					if os.IsNotExist(err) {
   109  						changes.NotifyDeleted()
   110  						return
   111  					}
   112  					// XXX: report this error back to the user
   113  					util.Fatal("Failed to stat file %v: %v", fw.Filename, err)
   114  				}
   115  				fw.Size = fi.Size()
   116  
   117  				if prevSize > 0 && prevSize > fw.Size {
   118  					changes.NotifyTruncated()
   119  				} else {
   120  					changes.NotifyModified()
   121  				}
   122  				prevSize = fw.Size
   123  			}
   124  		}
   125  	}()
   126  
   127  	return changes, nil
   128  }