polling.go

  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
  5package watch
  6
  7import (
  8	"os"
  9	"runtime"
 10	"time"
 11
 12	"github.com/nxadm/tail/util"
 13	"gopkg.in/tomb.v1"
 14)
 15
 16// PollingFileWatcher polls the file for changes.
 17type PollingFileWatcher struct {
 18	Filename string
 19	Size     int64
 20}
 21
 22func NewPollingFileWatcher(filename string) *PollingFileWatcher {
 23	fw := &PollingFileWatcher{filename, 0}
 24	return fw
 25}
 26
 27var POLL_DURATION time.Duration
 28
 29func (fw *PollingFileWatcher) BlockUntilExists(t *tomb.Tomb) error {
 30	for {
 31		if _, err := os.Stat(fw.Filename); err == nil {
 32			return nil
 33		} else if !os.IsNotExist(err) {
 34			return err
 35		}
 36		select {
 37		case <-time.After(POLL_DURATION):
 38			continue
 39		case <-t.Dying():
 40			return tomb.ErrDying
 41		}
 42	}
 43	panic("unreachable")
 44}
 45
 46func (fw *PollingFileWatcher) ChangeEvents(t *tomb.Tomb, pos int64) (*FileChanges, error) {
 47	origFi, err := os.Stat(fw.Filename)
 48	if err != nil {
 49		return nil, err
 50	}
 51
 52	changes := NewFileChanges()
 53	var prevModTime time.Time
 54
 55	// XXX: use tomb.Tomb to cleanly manage these goroutines. replace
 56	// the fatal (below) with tomb's Kill.
 57
 58	fw.Size = pos
 59
 60	go func() {
 61		prevSize := fw.Size
 62		for {
 63			select {
 64			case <-t.Dying():
 65				return
 66			default:
 67			}
 68
 69			time.Sleep(POLL_DURATION)
 70			fi, err := os.Stat(fw.Filename)
 71			if err != nil {
 72				// Windows cannot delete a file if a handle is still open (tail keeps one open)
 73				// so it gives access denied to anything trying to read it until all handles are released.
 74				if os.IsNotExist(err) || (runtime.GOOS == "windows" && os.IsPermission(err)) {
 75					// File does not exist (has been deleted).
 76					changes.NotifyDeleted()
 77					return
 78				}
 79
 80				// XXX: report this error back to the user
 81				util.Fatal("Failed to stat file %v: %v", fw.Filename, err)
 82			}
 83
 84			// File got moved/renamed?
 85			if !os.SameFile(origFi, fi) {
 86				changes.NotifyDeleted()
 87				return
 88			}
 89
 90			// File got truncated?
 91			fw.Size = fi.Size()
 92			if prevSize > 0 && prevSize > fw.Size {
 93				changes.NotifyTruncated()
 94				prevSize = fw.Size
 95				continue
 96			}
 97			// File got bigger?
 98			if prevSize > 0 && prevSize < fw.Size {
 99				changes.NotifyModified()
100				prevSize = fw.Size
101				continue
102			}
103			prevSize = fw.Size
104
105			// File was appended to (changed)?
106			modTime := fi.ModTime()
107			if modTime != prevModTime {
108				prevModTime = modTime
109				changes.NotifyModified()
110			}
111		}
112	}()
113
114	return changes, nil
115}
116
117func init() {
118	POLL_DURATION = 250 * time.Millisecond
119}