github.com/Cloud-Foundations/Dominator@v0.3.4/lib/fsutil/watchFile.go (about)

     1  //go:build !windows
     2  // +build !windows
     3  
     4  package fsutil
     5  
     6  import (
     7  	"io"
     8  	"os"
     9  	"syscall"
    10  	"time"
    11  
    12  	"github.com/Cloud-Foundations/Dominator/lib/log"
    13  	"github.com/Cloud-Foundations/Dominator/lib/wsyscall"
    14  )
    15  
    16  var stopChannel = make(chan struct{})
    17  
    18  func watchFile(pathname string, logger log.Logger) <-chan io.ReadCloser {
    19  	readCloserChannel := make(chan io.ReadCloser, 1)
    20  	notifyChannel := watchFileWithFsNotify(pathname, logger)
    21  	go watchFileForever(pathname, readCloserChannel, notifyChannel, logger)
    22  	return readCloserChannel
    23  }
    24  
    25  func watchFileStop() {
    26  	watchFileStopWithFsNotify()
    27  	select {
    28  	case stopChannel <- struct{}{}:
    29  	default:
    30  	}
    31  }
    32  
    33  func watchFileForever(pathname string, readCloserChannel chan<- io.ReadCloser,
    34  	notifyChannel <-chan struct{}, logger log.Logger) {
    35  	interval := time.Second
    36  	if notifyChannel != nil {
    37  		interval = 15 * time.Second
    38  	}
    39  	intervalTimer := time.NewTimer(0)
    40  	var lastStat syscall.Stat_t
    41  	lastFd := -1
    42  	for {
    43  		select {
    44  		case <-intervalTimer.C:
    45  		case <-notifyChannel:
    46  			if !intervalTimer.Stop() {
    47  				<-intervalTimer.C
    48  			}
    49  			if lastFd >= 0 {
    50  				syscall.Close(lastFd)
    51  			}
    52  			lastFd = -1
    53  		case <-stopChannel:
    54  			if lastFd >= 0 {
    55  				syscall.Close(lastFd)
    56  			}
    57  			close(readCloserChannel)
    58  			return
    59  		}
    60  		intervalTimer = time.NewTimer(interval)
    61  		var stat syscall.Stat_t
    62  		if err := syscall.Stat(pathname, &stat); err != nil {
    63  			if logger != nil {
    64  				logger.Printf("Error stating file: %s: %s\n", pathname, err)
    65  			}
    66  			continue
    67  		}
    68  		if stat.Ino != lastStat.Ino {
    69  			if file, err := os.Open(pathname); err != nil {
    70  				if logger != nil {
    71  					logger.Printf("Error opening file: %s: %s\n", pathname, err)
    72  				}
    73  				continue
    74  			} else {
    75  				// By holding onto the file, we guarantee that the inode number
    76  				// for the file we've opened cannot be reused until we've seen
    77  				// a new inode.
    78  				if lastFd >= 0 {
    79  					syscall.Close(lastFd)
    80  				}
    81  				lastFd, _ = wsyscall.Dup(int(file.Fd()))
    82  				readCloserChannel <- file // Must happen after FD is duplicated.
    83  				lastStat = stat
    84  			}
    85  		}
    86  	}
    87  }