mirror of https://github.com/prometheus/prometheus
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
561 lines
13 KiB
561 lines
13 KiB
// Copyright 2011 The Go Authors. All rights reserved. |
|
// Use of this source code is governed by a BSD-style |
|
// license that can be found in the LICENSE file. |
|
|
|
// +build windows |
|
|
|
package fsnotify |
|
|
|
import ( |
|
"errors" |
|
"fmt" |
|
"os" |
|
"path/filepath" |
|
"runtime" |
|
"sync" |
|
"syscall" |
|
"unsafe" |
|
) |
|
|
|
// Watcher watches a set of files, delivering events to a channel. |
|
type Watcher struct { |
|
Events chan Event |
|
Errors chan error |
|
isClosed bool // Set to true when Close() is first called |
|
mu sync.Mutex // Map access |
|
port syscall.Handle // Handle to completion port |
|
watches watchMap // Map of watches (key: i-number) |
|
input chan *input // Inputs to the reader are sent on this channel |
|
quit chan chan<- error |
|
} |
|
|
|
// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events. |
|
func NewWatcher() (*Watcher, error) { |
|
port, e := syscall.CreateIoCompletionPort(syscall.InvalidHandle, 0, 0, 0) |
|
if e != nil { |
|
return nil, os.NewSyscallError("CreateIoCompletionPort", e) |
|
} |
|
w := &Watcher{ |
|
port: port, |
|
watches: make(watchMap), |
|
input: make(chan *input, 1), |
|
Events: make(chan Event, 50), |
|
Errors: make(chan error), |
|
quit: make(chan chan<- error, 1), |
|
} |
|
go w.readEvents() |
|
return w, nil |
|
} |
|
|
|
// Close removes all watches and closes the events channel. |
|
func (w *Watcher) Close() error { |
|
if w.isClosed { |
|
return nil |
|
} |
|
w.isClosed = true |
|
|
|
// Send "quit" message to the reader goroutine |
|
ch := make(chan error) |
|
w.quit <- ch |
|
if err := w.wakeupReader(); err != nil { |
|
return err |
|
} |
|
return <-ch |
|
} |
|
|
|
// Add starts watching the named file or directory (non-recursively). |
|
func (w *Watcher) Add(name string) error { |
|
if w.isClosed { |
|
return errors.New("watcher already closed") |
|
} |
|
in := &input{ |
|
op: opAddWatch, |
|
path: filepath.Clean(name), |
|
flags: sysFSALLEVENTS, |
|
reply: make(chan error), |
|
} |
|
w.input <- in |
|
if err := w.wakeupReader(); err != nil { |
|
return err |
|
} |
|
return <-in.reply |
|
} |
|
|
|
// Remove stops watching the the named file or directory (non-recursively). |
|
func (w *Watcher) Remove(name string) error { |
|
in := &input{ |
|
op: opRemoveWatch, |
|
path: filepath.Clean(name), |
|
reply: make(chan error), |
|
} |
|
w.input <- in |
|
if err := w.wakeupReader(); err != nil { |
|
return err |
|
} |
|
return <-in.reply |
|
} |
|
|
|
const ( |
|
// Options for AddWatch |
|
sysFSONESHOT = 0x80000000 |
|
sysFSONLYDIR = 0x1000000 |
|
|
|
// Events |
|
sysFSACCESS = 0x1 |
|
sysFSALLEVENTS = 0xfff |
|
sysFSATTRIB = 0x4 |
|
sysFSCLOSE = 0x18 |
|
sysFSCREATE = 0x100 |
|
sysFSDELETE = 0x200 |
|
sysFSDELETESELF = 0x400 |
|
sysFSMODIFY = 0x2 |
|
sysFSMOVE = 0xc0 |
|
sysFSMOVEDFROM = 0x40 |
|
sysFSMOVEDTO = 0x80 |
|
sysFSMOVESELF = 0x800 |
|
|
|
// Special events |
|
sysFSIGNORED = 0x8000 |
|
sysFSQOVERFLOW = 0x4000 |
|
) |
|
|
|
func newEvent(name string, mask uint32) Event { |
|
e := Event{Name: name} |
|
if mask&sysFSCREATE == sysFSCREATE || mask&sysFSMOVEDTO == sysFSMOVEDTO { |
|
e.Op |= Create |
|
} |
|
if mask&sysFSDELETE == sysFSDELETE || mask&sysFSDELETESELF == sysFSDELETESELF { |
|
e.Op |= Remove |
|
} |
|
if mask&sysFSMODIFY == sysFSMODIFY { |
|
e.Op |= Write |
|
} |
|
if mask&sysFSMOVE == sysFSMOVE || mask&sysFSMOVESELF == sysFSMOVESELF || mask&sysFSMOVEDFROM == sysFSMOVEDFROM { |
|
e.Op |= Rename |
|
} |
|
if mask&sysFSATTRIB == sysFSATTRIB { |
|
e.Op |= Chmod |
|
} |
|
return e |
|
} |
|
|
|
const ( |
|
opAddWatch = iota |
|
opRemoveWatch |
|
) |
|
|
|
const ( |
|
provisional uint64 = 1 << (32 + iota) |
|
) |
|
|
|
type input struct { |
|
op int |
|
path string |
|
flags uint32 |
|
reply chan error |
|
} |
|
|
|
type inode struct { |
|
handle syscall.Handle |
|
volume uint32 |
|
index uint64 |
|
} |
|
|
|
type watch struct { |
|
ov syscall.Overlapped |
|
ino *inode // i-number |
|
path string // Directory path |
|
mask uint64 // Directory itself is being watched with these notify flags |
|
names map[string]uint64 // Map of names being watched and their notify flags |
|
rename string // Remembers the old name while renaming a file |
|
buf [4096]byte |
|
} |
|
|
|
type indexMap map[uint64]*watch |
|
type watchMap map[uint32]indexMap |
|
|
|
func (w *Watcher) wakeupReader() error { |
|
e := syscall.PostQueuedCompletionStatus(w.port, 0, 0, nil) |
|
if e != nil { |
|
return os.NewSyscallError("PostQueuedCompletionStatus", e) |
|
} |
|
return nil |
|
} |
|
|
|
func getDir(pathname string) (dir string, err error) { |
|
attr, e := syscall.GetFileAttributes(syscall.StringToUTF16Ptr(pathname)) |
|
if e != nil { |
|
return "", os.NewSyscallError("GetFileAttributes", e) |
|
} |
|
if attr&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 { |
|
dir = pathname |
|
} else { |
|
dir, _ = filepath.Split(pathname) |
|
dir = filepath.Clean(dir) |
|
} |
|
return |
|
} |
|
|
|
func getIno(path string) (ino *inode, err error) { |
|
h, e := syscall.CreateFile(syscall.StringToUTF16Ptr(path), |
|
syscall.FILE_LIST_DIRECTORY, |
|
syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE, |
|
nil, syscall.OPEN_EXISTING, |
|
syscall.FILE_FLAG_BACKUP_SEMANTICS|syscall.FILE_FLAG_OVERLAPPED, 0) |
|
if e != nil { |
|
return nil, os.NewSyscallError("CreateFile", e) |
|
} |
|
var fi syscall.ByHandleFileInformation |
|
if e = syscall.GetFileInformationByHandle(h, &fi); e != nil { |
|
syscall.CloseHandle(h) |
|
return nil, os.NewSyscallError("GetFileInformationByHandle", e) |
|
} |
|
ino = &inode{ |
|
handle: h, |
|
volume: fi.VolumeSerialNumber, |
|
index: uint64(fi.FileIndexHigh)<<32 | uint64(fi.FileIndexLow), |
|
} |
|
return ino, nil |
|
} |
|
|
|
// Must run within the I/O thread. |
|
func (m watchMap) get(ino *inode) *watch { |
|
if i := m[ino.volume]; i != nil { |
|
return i[ino.index] |
|
} |
|
return nil |
|
} |
|
|
|
// Must run within the I/O thread. |
|
func (m watchMap) set(ino *inode, watch *watch) { |
|
i := m[ino.volume] |
|
if i == nil { |
|
i = make(indexMap) |
|
m[ino.volume] = i |
|
} |
|
i[ino.index] = watch |
|
} |
|
|
|
// Must run within the I/O thread. |
|
func (w *Watcher) addWatch(pathname string, flags uint64) error { |
|
dir, err := getDir(pathname) |
|
if err != nil { |
|
return err |
|
} |
|
if flags&sysFSONLYDIR != 0 && pathname != dir { |
|
return nil |
|
} |
|
ino, err := getIno(dir) |
|
if err != nil { |
|
return err |
|
} |
|
w.mu.Lock() |
|
watchEntry := w.watches.get(ino) |
|
w.mu.Unlock() |
|
if watchEntry == nil { |
|
if _, e := syscall.CreateIoCompletionPort(ino.handle, w.port, 0, 0); e != nil { |
|
syscall.CloseHandle(ino.handle) |
|
return os.NewSyscallError("CreateIoCompletionPort", e) |
|
} |
|
watchEntry = &watch{ |
|
ino: ino, |
|
path: dir, |
|
names: make(map[string]uint64), |
|
} |
|
w.mu.Lock() |
|
w.watches.set(ino, watchEntry) |
|
w.mu.Unlock() |
|
flags |= provisional |
|
} else { |
|
syscall.CloseHandle(ino.handle) |
|
} |
|
if pathname == dir { |
|
watchEntry.mask |= flags |
|
} else { |
|
watchEntry.names[filepath.Base(pathname)] |= flags |
|
} |
|
if err = w.startRead(watchEntry); err != nil { |
|
return err |
|
} |
|
if pathname == dir { |
|
watchEntry.mask &= ^provisional |
|
} else { |
|
watchEntry.names[filepath.Base(pathname)] &= ^provisional |
|
} |
|
return nil |
|
} |
|
|
|
// Must run within the I/O thread. |
|
func (w *Watcher) remWatch(pathname string) error { |
|
dir, err := getDir(pathname) |
|
if err != nil { |
|
return err |
|
} |
|
ino, err := getIno(dir) |
|
if err != nil { |
|
return err |
|
} |
|
w.mu.Lock() |
|
watch := w.watches.get(ino) |
|
w.mu.Unlock() |
|
if watch == nil { |
|
return fmt.Errorf("can't remove non-existent watch for: %s", pathname) |
|
} |
|
if pathname == dir { |
|
w.sendEvent(watch.path, watch.mask&sysFSIGNORED) |
|
watch.mask = 0 |
|
} else { |
|
name := filepath.Base(pathname) |
|
w.sendEvent(watch.path+"\\"+name, watch.names[name]&sysFSIGNORED) |
|
delete(watch.names, name) |
|
} |
|
return w.startRead(watch) |
|
} |
|
|
|
// Must run within the I/O thread. |
|
func (w *Watcher) deleteWatch(watch *watch) { |
|
for name, mask := range watch.names { |
|
if mask&provisional == 0 { |
|
w.sendEvent(watch.path+"\\"+name, mask&sysFSIGNORED) |
|
} |
|
delete(watch.names, name) |
|
} |
|
if watch.mask != 0 { |
|
if watch.mask&provisional == 0 { |
|
w.sendEvent(watch.path, watch.mask&sysFSIGNORED) |
|
} |
|
watch.mask = 0 |
|
} |
|
} |
|
|
|
// Must run within the I/O thread. |
|
func (w *Watcher) startRead(watch *watch) error { |
|
if e := syscall.CancelIo(watch.ino.handle); e != nil { |
|
w.Errors <- os.NewSyscallError("CancelIo", e) |
|
w.deleteWatch(watch) |
|
} |
|
mask := toWindowsFlags(watch.mask) |
|
for _, m := range watch.names { |
|
mask |= toWindowsFlags(m) |
|
} |
|
if mask == 0 { |
|
if e := syscall.CloseHandle(watch.ino.handle); e != nil { |
|
w.Errors <- os.NewSyscallError("CloseHandle", e) |
|
} |
|
w.mu.Lock() |
|
delete(w.watches[watch.ino.volume], watch.ino.index) |
|
w.mu.Unlock() |
|
return nil |
|
} |
|
e := syscall.ReadDirectoryChanges(watch.ino.handle, &watch.buf[0], |
|
uint32(unsafe.Sizeof(watch.buf)), false, mask, nil, &watch.ov, 0) |
|
if e != nil { |
|
err := os.NewSyscallError("ReadDirectoryChanges", e) |
|
if e == syscall.ERROR_ACCESS_DENIED && watch.mask&provisional == 0 { |
|
// Watched directory was probably removed |
|
if w.sendEvent(watch.path, watch.mask&sysFSDELETESELF) { |
|
if watch.mask&sysFSONESHOT != 0 { |
|
watch.mask = 0 |
|
} |
|
} |
|
err = nil |
|
} |
|
w.deleteWatch(watch) |
|
w.startRead(watch) |
|
return err |
|
} |
|
return nil |
|
} |
|
|
|
// readEvents reads from the I/O completion port, converts the |
|
// received events into Event objects and sends them via the Events channel. |
|
// Entry point to the I/O thread. |
|
func (w *Watcher) readEvents() { |
|
var ( |
|
n, key uint32 |
|
ov *syscall.Overlapped |
|
) |
|
runtime.LockOSThread() |
|
|
|
for { |
|
e := syscall.GetQueuedCompletionStatus(w.port, &n, &key, &ov, syscall.INFINITE) |
|
watch := (*watch)(unsafe.Pointer(ov)) |
|
|
|
if watch == nil { |
|
select { |
|
case ch := <-w.quit: |
|
w.mu.Lock() |
|
var indexes []indexMap |
|
for _, index := range w.watches { |
|
indexes = append(indexes, index) |
|
} |
|
w.mu.Unlock() |
|
for _, index := range indexes { |
|
for _, watch := range index { |
|
w.deleteWatch(watch) |
|
w.startRead(watch) |
|
} |
|
} |
|
var err error |
|
if e := syscall.CloseHandle(w.port); e != nil { |
|
err = os.NewSyscallError("CloseHandle", e) |
|
} |
|
close(w.Events) |
|
close(w.Errors) |
|
ch <- err |
|
return |
|
case in := <-w.input: |
|
switch in.op { |
|
case opAddWatch: |
|
in.reply <- w.addWatch(in.path, uint64(in.flags)) |
|
case opRemoveWatch: |
|
in.reply <- w.remWatch(in.path) |
|
} |
|
default: |
|
} |
|
continue |
|
} |
|
|
|
switch e { |
|
case syscall.ERROR_MORE_DATA: |
|
if watch == nil { |
|
w.Errors <- errors.New("ERROR_MORE_DATA has unexpectedly null lpOverlapped buffer") |
|
} else { |
|
// The i/o succeeded but the buffer is full. |
|
// In theory we should be building up a full packet. |
|
// In practice we can get away with just carrying on. |
|
n = uint32(unsafe.Sizeof(watch.buf)) |
|
} |
|
case syscall.ERROR_ACCESS_DENIED: |
|
// Watched directory was probably removed |
|
w.sendEvent(watch.path, watch.mask&sysFSDELETESELF) |
|
w.deleteWatch(watch) |
|
w.startRead(watch) |
|
continue |
|
case syscall.ERROR_OPERATION_ABORTED: |
|
// CancelIo was called on this handle |
|
continue |
|
default: |
|
w.Errors <- os.NewSyscallError("GetQueuedCompletionPort", e) |
|
continue |
|
case nil: |
|
} |
|
|
|
var offset uint32 |
|
for { |
|
if n == 0 { |
|
w.Events <- newEvent("", sysFSQOVERFLOW) |
|
w.Errors <- errors.New("short read in readEvents()") |
|
break |
|
} |
|
|
|
// Point "raw" to the event in the buffer |
|
raw := (*syscall.FileNotifyInformation)(unsafe.Pointer(&watch.buf[offset])) |
|
buf := (*[syscall.MAX_PATH]uint16)(unsafe.Pointer(&raw.FileName)) |
|
name := syscall.UTF16ToString(buf[:raw.FileNameLength/2]) |
|
fullname := watch.path + "\\" + name |
|
|
|
var mask uint64 |
|
switch raw.Action { |
|
case syscall.FILE_ACTION_REMOVED: |
|
mask = sysFSDELETESELF |
|
case syscall.FILE_ACTION_MODIFIED: |
|
mask = sysFSMODIFY |
|
case syscall.FILE_ACTION_RENAMED_OLD_NAME: |
|
watch.rename = name |
|
case syscall.FILE_ACTION_RENAMED_NEW_NAME: |
|
if watch.names[watch.rename] != 0 { |
|
watch.names[name] |= watch.names[watch.rename] |
|
delete(watch.names, watch.rename) |
|
mask = sysFSMOVESELF |
|
} |
|
} |
|
|
|
sendNameEvent := func() { |
|
if w.sendEvent(fullname, watch.names[name]&mask) { |
|
if watch.names[name]&sysFSONESHOT != 0 { |
|
delete(watch.names, name) |
|
} |
|
} |
|
} |
|
if raw.Action != syscall.FILE_ACTION_RENAMED_NEW_NAME { |
|
sendNameEvent() |
|
} |
|
if raw.Action == syscall.FILE_ACTION_REMOVED { |
|
w.sendEvent(fullname, watch.names[name]&sysFSIGNORED) |
|
delete(watch.names, name) |
|
} |
|
if w.sendEvent(fullname, watch.mask&toFSnotifyFlags(raw.Action)) { |
|
if watch.mask&sysFSONESHOT != 0 { |
|
watch.mask = 0 |
|
} |
|
} |
|
if raw.Action == syscall.FILE_ACTION_RENAMED_NEW_NAME { |
|
fullname = watch.path + "\\" + watch.rename |
|
sendNameEvent() |
|
} |
|
|
|
// Move to the next event in the buffer |
|
if raw.NextEntryOffset == 0 { |
|
break |
|
} |
|
offset += raw.NextEntryOffset |
|
|
|
// Error! |
|
if offset >= n { |
|
w.Errors <- errors.New("Windows system assumed buffer larger than it is, events have likely been missed.") |
|
break |
|
} |
|
} |
|
|
|
if err := w.startRead(watch); err != nil { |
|
w.Errors <- err |
|
} |
|
} |
|
} |
|
|
|
func (w *Watcher) sendEvent(name string, mask uint64) bool { |
|
if mask == 0 { |
|
return false |
|
} |
|
event := newEvent(name, uint32(mask)) |
|
select { |
|
case ch := <-w.quit: |
|
w.quit <- ch |
|
case w.Events <- event: |
|
} |
|
return true |
|
} |
|
|
|
func toWindowsFlags(mask uint64) uint32 { |
|
var m uint32 |
|
if mask&sysFSACCESS != 0 { |
|
m |= syscall.FILE_NOTIFY_CHANGE_LAST_ACCESS |
|
} |
|
if mask&sysFSMODIFY != 0 { |
|
m |= syscall.FILE_NOTIFY_CHANGE_LAST_WRITE |
|
} |
|
if mask&sysFSATTRIB != 0 { |
|
m |= syscall.FILE_NOTIFY_CHANGE_ATTRIBUTES |
|
} |
|
if mask&(sysFSMOVE|sysFSCREATE|sysFSDELETE) != 0 { |
|
m |= syscall.FILE_NOTIFY_CHANGE_FILE_NAME | syscall.FILE_NOTIFY_CHANGE_DIR_NAME |
|
} |
|
return m |
|
} |
|
|
|
func toFSnotifyFlags(action uint32) uint64 { |
|
switch action { |
|
case syscall.FILE_ACTION_ADDED: |
|
return sysFSCREATE |
|
case syscall.FILE_ACTION_REMOVED: |
|
return sysFSDELETE |
|
case syscall.FILE_ACTION_MODIFIED: |
|
return sysFSMODIFY |
|
case syscall.FILE_ACTION_RENAMED_OLD_NAME: |
|
return sysFSMOVEDFROM |
|
case syscall.FILE_ACTION_RENAMED_NEW_NAME: |
|
return sysFSMOVEDTO |
|
} |
|
return 0 |
|
}
|
|
|