github.com/nxadm/tail@v1.4.12-0.20231010141446-ba755e4d73b6/watch/polling.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  	"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.
    17  type PollingFileWatcher struct {
    18  	Filename string
    19  	Size     int64
    20  }
    21  
    22  func NewPollingFileWatcher(filename string) *PollingFileWatcher {
    23  	fw := &PollingFileWatcher{filename, 0}
    24  	return fw
    25  }
    26  
    27  var POLL_DURATION time.Duration
    28  
    29  func (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  
    46  func (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  
   117  func init() {
   118  	POLL_DURATION = 250 * time.Millisecond
   119  }