1// Copyright 2011 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5// +build windows
6
7package fsnotify
8
9import (
10	"errors"
11	"fmt"
12	"os"
13	"path/filepath"
14	"runtime"
15	"sync"
16	"syscall"
17	"unsafe"
18)
19
20// Watcher watches a set of files, delivering events to a channel.
21type Watcher struct {
22	Events   chan Event
23	Errors   chan error
24	isClosed bool           // Set to true when Close() is first called
25	mu       sync.Mutex     // Map access
26	port     syscall.Handle // Handle to completion port
27	watches  watchMap       // Map of watches (key: i-number)
28	input    chan *input    // Inputs to the reader are sent on this channel
29	quit     chan chan<- error
30}
31
32// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events.
33func NewWatcher() (*Watcher, error) {
34	port, e := syscall.CreateIoCompletionPort(syscall.InvalidHandle, 0, 0, 0)
35	if e != nil {
36		return nil, os.NewSyscallError("CreateIoCompletionPort", e)
37	}
38	w := &Watcher{
39		port:    port,
40		watches: make(watchMap),
41		input:   make(chan *input, 1),
42		Events:  make(chan Event, 50),
43		Errors:  make(chan error),
44		quit:    make(chan chan<- error, 1),
45	}
46	go w.readEvents()
47	return w, nil
48}
49
50// Close removes all watches and closes the events channel.
51func (w *Watcher) Close() error {
52	if w.isClosed {
53		return nil
54	}
55	w.isClosed = true
56
57	// Send "quit" message to the reader goroutine
58	ch := make(chan error)
59	w.quit <- ch
60	if err := w.wakeupReader(); err != nil {
61		return err
62	}
63	return <-ch
64}
65
66// Add starts watching the named file or directory (non-recursively).
67func (w *Watcher) Add(name string) error {
68	if w.isClosed {
69		return errors.New("watcher already closed")
70	}
71	in := &input{
72		op:    opAddWatch,
73		path:  filepath.Clean(name),
74		flags: sysFSALLEVENTS,
75		reply: make(chan error),
76	}
77	w.input <- in
78	if err := w.wakeupReader(); err != nil {
79		return err
80	}
81	return <-in.reply
82}
83
84// Remove stops watching the the named file or directory (non-recursively).
85func (w *Watcher) Remove(name string) error {
86	in := &input{
87		op:    opRemoveWatch,
88		path:  filepath.Clean(name),
89		reply: make(chan error),
90	}
91	w.input <- in
92	if err := w.wakeupReader(); err != nil {
93		return err
94	}
95	return <-in.reply
96}
97
98const (
99	// Options for AddWatch
100	sysFSONESHOT = 0x80000000
101	sysFSONLYDIR = 0x1000000
102
103	// Events
104	sysFSACCESS     = 0x1
105	sysFSALLEVENTS  = 0xfff
106	sysFSATTRIB     = 0x4
107	sysFSCLOSE      = 0x18
108	sysFSCREATE     = 0x100
109	sysFSDELETE     = 0x200
110	sysFSDELETESELF = 0x400
111	sysFSMODIFY     = 0x2
112	sysFSMOVE       = 0xc0
113	sysFSMOVEDFROM  = 0x40
114	sysFSMOVEDTO    = 0x80
115	sysFSMOVESELF   = 0x800
116
117	// Special events
118	sysFSIGNORED   = 0x8000
119	sysFSQOVERFLOW = 0x4000
120)
121
122func newEvent(name string, mask uint32) Event {
123	e := Event{Name: name}
124	if mask&sysFSCREATE == sysFSCREATE || mask&sysFSMOVEDTO == sysFSMOVEDTO {
125		e.Op |= Create
126	}
127	if mask&sysFSDELETE == sysFSDELETE || mask&sysFSDELETESELF == sysFSDELETESELF {
128		e.Op |= Remove
129	}
130	if mask&sysFSMODIFY == sysFSMODIFY {
131		e.Op |= Write
132	}
133	if mask&sysFSMOVE == sysFSMOVE || mask&sysFSMOVESELF == sysFSMOVESELF || mask&sysFSMOVEDFROM == sysFSMOVEDFROM {
134		e.Op |= Rename
135	}
136	if mask&sysFSATTRIB == sysFSATTRIB {
137		e.Op |= Chmod
138	}
139	return e
140}
141
142const (
143	opAddWatch = iota
144	opRemoveWatch
145)
146
147const (
148	provisional uint64 = 1 << (32 + iota)
149)
150
151type input struct {
152	op    int
153	path  string
154	flags uint32
155	reply chan error
156}
157
158type inode struct {
159	handle syscall.Handle
160	volume uint32
161	index  uint64
162}
163
164type watch struct {
165	ov     syscall.Overlapped
166	ino    *inode            // i-number
167	path   string            // Directory path
168	mask   uint64            // Directory itself is being watched with these notify flags
169	names  map[string]uint64 // Map of names being watched and their notify flags
170	rename string            // Remembers the old name while renaming a file
171	buf    [4096]byte
172}
173
174type indexMap map[uint64]*watch
175type watchMap map[uint32]indexMap
176
177func (w *Watcher) wakeupReader() error {
178	e := syscall.PostQueuedCompletionStatus(w.port, 0, 0, nil)
179	if e != nil {
180		return os.NewSyscallError("PostQueuedCompletionStatus", e)
181	}
182	return nil
183}
184
185func getDir(pathname string) (dir string, err error) {
186	attr, e := syscall.GetFileAttributes(syscall.StringToUTF16Ptr(pathname))
187	if e != nil {
188		return "", os.NewSyscallError("GetFileAttributes", e)
189	}
190	if attr&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 {
191		dir = pathname
192	} else {
193		dir, _ = filepath.Split(pathname)
194		dir = filepath.Clean(dir)
195	}
196	return
197}
198
199func getIno(path string) (ino *inode, err error) {
200	h, e := syscall.CreateFile(syscall.StringToUTF16Ptr(path),
201		syscall.FILE_LIST_DIRECTORY,
202		syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE,
203		nil, syscall.OPEN_EXISTING,
204		syscall.FILE_FLAG_BACKUP_SEMANTICS|syscall.FILE_FLAG_OVERLAPPED, 0)
205	if e != nil {
206		return nil, os.NewSyscallError("CreateFile", e)
207	}
208	var fi syscall.ByHandleFileInformation
209	if e = syscall.GetFileInformationByHandle(h, &fi); e != nil {
210		syscall.CloseHandle(h)
211		return nil, os.NewSyscallError("GetFileInformationByHandle", e)
212	}
213	ino = &inode{
214		handle: h,
215		volume: fi.VolumeSerialNumber,
216		index:  uint64(fi.FileIndexHigh)<<32 | uint64(fi.FileIndexLow),
217	}
218	return ino, nil
219}
220
221// Must run within the I/O thread.
222func (m watchMap) get(ino *inode) *watch {
223	if i := m[ino.volume]; i != nil {
224		return i[ino.index]
225	}
226	return nil
227}
228
229// Must run within the I/O thread.
230func (m watchMap) set(ino *inode, watch *watch) {
231	i := m[ino.volume]
232	if i == nil {
233		i = make(indexMap)
234		m[ino.volume] = i
235	}
236	i[ino.index] = watch
237}
238
239// Must run within the I/O thread.
240func (w *Watcher) addWatch(pathname string, flags uint64) error {
241	dir, err := getDir(pathname)
242	if err != nil {
243		return err
244	}
245	if flags&sysFSONLYDIR != 0 && pathname != dir {
246		return nil
247	}
248	ino, err := getIno(dir)
249	if err != nil {
250		return err
251	}
252	w.mu.Lock()
253	watchEntry := w.watches.get(ino)
254	w.mu.Unlock()
255	if watchEntry == nil {
256		if _, e := syscall.CreateIoCompletionPort(ino.handle, w.port, 0, 0); e != nil {
257			syscall.CloseHandle(ino.handle)
258			return os.NewSyscallError("CreateIoCompletionPort", e)
259		}
260		watchEntry = &watch{
261			ino:   ino,
262			path:  dir,
263			names: make(map[string]uint64),
264		}
265		w.mu.Lock()
266		w.watches.set(ino, watchEntry)
267		w.mu.Unlock()
268		flags |= provisional
269	} else {
270		syscall.CloseHandle(ino.handle)
271	}
272	if pathname == dir {
273		watchEntry.mask |= flags
274	} else {
275		watchEntry.names[filepath.Base(pathname)] |= flags
276	}
277	if err = w.startRead(watchEntry); err != nil {
278		return err
279	}
280	if pathname == dir {
281		watchEntry.mask &= ^provisional
282	} else {
283		watchEntry.names[filepath.Base(pathname)] &= ^provisional
284	}
285	return nil
286}
287
288// Must run within the I/O thread.
289func (w *Watcher) remWatch(pathname string) error {
290	dir, err := getDir(pathname)
291	if err != nil {
292		return err
293	}
294	ino, err := getIno(dir)
295	if err != nil {
296		return err
297	}
298	w.mu.Lock()
299	watch := w.watches.get(ino)
300	w.mu.Unlock()
301	if watch == nil {
302		return fmt.Errorf("can't remove non-existent watch for: %s", pathname)
303	}
304	if pathname == dir {
305		w.sendEvent(watch.path, watch.mask&sysFSIGNORED)
306		watch.mask = 0
307	} else {
308		name := filepath.Base(pathname)
309		w.sendEvent(filepath.Join(watch.path, name), watch.names[name]&sysFSIGNORED)
310		delete(watch.names, name)
311	}
312	return w.startRead(watch)
313}
314
315// Must run within the I/O thread.
316func (w *Watcher) deleteWatch(watch *watch) {
317	for name, mask := range watch.names {
318		if mask&provisional == 0 {
319			w.sendEvent(filepath.Join(watch.path, name), mask&sysFSIGNORED)
320		}
321		delete(watch.names, name)
322	}
323	if watch.mask != 0 {
324		if watch.mask&provisional == 0 {
325			w.sendEvent(watch.path, watch.mask&sysFSIGNORED)
326		}
327		watch.mask = 0
328	}
329}
330
331// Must run within the I/O thread.
332func (w *Watcher) startRead(watch *watch) error {
333	if e := syscall.CancelIo(watch.ino.handle); e != nil {
334		w.Errors <- os.NewSyscallError("CancelIo", e)
335		w.deleteWatch(watch)
336	}
337	mask := toWindowsFlags(watch.mask)
338	for _, m := range watch.names {
339		mask |= toWindowsFlags(m)
340	}
341	if mask == 0 {
342		if e := syscall.CloseHandle(watch.ino.handle); e != nil {
343			w.Errors <- os.NewSyscallError("CloseHandle", e)
344		}
345		w.mu.Lock()
346		delete(w.watches[watch.ino.volume], watch.ino.index)
347		w.mu.Unlock()
348		return nil
349	}
350	e := syscall.ReadDirectoryChanges(watch.ino.handle, &watch.buf[0],
351		uint32(unsafe.Sizeof(watch.buf)), false, mask, nil, &watch.ov, 0)
352	if e != nil {
353		err := os.NewSyscallError("ReadDirectoryChanges", e)
354		if e == syscall.ERROR_ACCESS_DENIED && watch.mask&provisional == 0 {
355			// Watched directory was probably removed
356			if w.sendEvent(watch.path, watch.mask&sysFSDELETESELF) {
357				if watch.mask&sysFSONESHOT != 0 {
358					watch.mask = 0
359				}
360			}
361			err = nil
362		}
363		w.deleteWatch(watch)
364		w.startRead(watch)
365		return err
366	}
367	return nil
368}
369
370// readEvents reads from the I/O completion port, converts the
371// received events into Event objects and sends them via the Events channel.
372// Entry point to the I/O thread.
373func (w *Watcher) readEvents() {
374	var (
375		n, key uint32
376		ov     *syscall.Overlapped
377	)
378	runtime.LockOSThread()
379
380	for {
381		e := syscall.GetQueuedCompletionStatus(w.port, &n, &key, &ov, syscall.INFINITE)
382		watch := (*watch)(unsafe.Pointer(ov))
383
384		if watch == nil {
385			select {
386			case ch := <-w.quit:
387				w.mu.Lock()
388				var indexes []indexMap
389				for _, index := range w.watches {
390					indexes = append(indexes, index)
391				}
392				w.mu.Unlock()
393				for _, index := range indexes {
394					for _, watch := range index {
395						w.deleteWatch(watch)
396						w.startRead(watch)
397					}
398				}
399				var err error
400				if e := syscall.CloseHandle(w.port); e != nil {
401					err = os.NewSyscallError("CloseHandle", e)
402				}
403				close(w.Events)
404				close(w.Errors)
405				ch <- err
406				return
407			case in := <-w.input:
408				switch in.op {
409				case opAddWatch:
410					in.reply <- w.addWatch(in.path, uint64(in.flags))
411				case opRemoveWatch:
412					in.reply <- w.remWatch(in.path)
413				}
414			default:
415			}
416			continue
417		}
418
419		switch e {
420		case syscall.ERROR_MORE_DATA:
421			if watch == nil {
422				w.Errors <- errors.New("ERROR_MORE_DATA has unexpectedly null lpOverlapped buffer")
423			} else {
424				// The i/o succeeded but the buffer is full.
425				// In theory we should be building up a full packet.
426				// In practice we can get away with just carrying on.
427				n = uint32(unsafe.Sizeof(watch.buf))
428			}
429		case syscall.ERROR_ACCESS_DENIED:
430			// Watched directory was probably removed
431			w.sendEvent(watch.path, watch.mask&sysFSDELETESELF)
432			w.deleteWatch(watch)
433			w.startRead(watch)
434			continue
435		case syscall.ERROR_OPERATION_ABORTED:
436			// CancelIo was called on this handle
437			continue
438		default:
439			w.Errors <- os.NewSyscallError("GetQueuedCompletionPort", e)
440			continue
441		case nil:
442		}
443
444		var offset uint32
445		for {
446			if n == 0 {
447				w.Events <- newEvent("", sysFSQOVERFLOW)
448				w.Errors <- errors.New("short read in readEvents()")
449				break
450			}
451
452			// Point "raw" to the event in the buffer
453			raw := (*syscall.FileNotifyInformation)(unsafe.Pointer(&watch.buf[offset]))
454			buf := (*[syscall.MAX_PATH]uint16)(unsafe.Pointer(&raw.FileName))
455			name := syscall.UTF16ToString(buf[:raw.FileNameLength/2])
456			fullname := filepath.Join(watch.path, name)
457
458			var mask uint64
459			switch raw.Action {
460			case syscall.FILE_ACTION_REMOVED:
461				mask = sysFSDELETESELF
462			case syscall.FILE_ACTION_MODIFIED:
463				mask = sysFSMODIFY
464			case syscall.FILE_ACTION_RENAMED_OLD_NAME:
465				watch.rename = name
466			case syscall.FILE_ACTION_RENAMED_NEW_NAME:
467				if watch.names[watch.rename] != 0 {
468					watch.names[name] |= watch.names[watch.rename]
469					delete(watch.names, watch.rename)
470					mask = sysFSMOVESELF
471				}
472			}
473
474			sendNameEvent := func() {
475				if w.sendEvent(fullname, watch.names[name]&mask) {
476					if watch.names[name]&sysFSONESHOT != 0 {
477						delete(watch.names, name)
478					}
479				}
480			}
481			if raw.Action != syscall.FILE_ACTION_RENAMED_NEW_NAME {
482				sendNameEvent()
483			}
484			if raw.Action == syscall.FILE_ACTION_REMOVED {
485				w.sendEvent(fullname, watch.names[name]&sysFSIGNORED)
486				delete(watch.names, name)
487			}
488			if w.sendEvent(fullname, watch.mask&toFSnotifyFlags(raw.Action)) {
489				if watch.mask&sysFSONESHOT != 0 {
490					watch.mask = 0
491				}
492			}
493			if raw.Action == syscall.FILE_ACTION_RENAMED_NEW_NAME {
494				fullname = filepath.Join(watch.path, watch.rename)
495				sendNameEvent()
496			}
497
498			// Move to the next event in the buffer
499			if raw.NextEntryOffset == 0 {
500				break
501			}
502			offset += raw.NextEntryOffset
503
504			// Error!
505			if offset >= n {
506				w.Errors <- errors.New("Windows system assumed buffer larger than it is, events have likely been missed.")
507				break
508			}
509		}
510
511		if err := w.startRead(watch); err != nil {
512			w.Errors <- err
513		}
514	}
515}
516
517func (w *Watcher) sendEvent(name string, mask uint64) bool {
518	if mask == 0 {
519		return false
520	}
521	event := newEvent(name, uint32(mask))
522	select {
523	case ch := <-w.quit:
524		w.quit <- ch
525	case w.Events <- event:
526	}
527	return true
528}
529
530func toWindowsFlags(mask uint64) uint32 {
531	var m uint32
532	if mask&sysFSACCESS != 0 {
533		m |= syscall.FILE_NOTIFY_CHANGE_LAST_ACCESS
534	}
535	if mask&sysFSMODIFY != 0 {
536		m |= syscall.FILE_NOTIFY_CHANGE_LAST_WRITE
537	}
538	if mask&sysFSATTRIB != 0 {
539		m |= syscall.FILE_NOTIFY_CHANGE_ATTRIBUTES
540	}
541	if mask&(sysFSMOVE|sysFSCREATE|sysFSDELETE) != 0 {
542		m |= syscall.FILE_NOTIFY_CHANGE_FILE_NAME | syscall.FILE_NOTIFY_CHANGE_DIR_NAME
543	}
544	return m
545}
546
547func toFSnotifyFlags(action uint32) uint64 {
548	switch action {
549	case syscall.FILE_ACTION_ADDED:
550		return sysFSCREATE
551	case syscall.FILE_ACTION_REMOVED:
552		return sysFSDELETE
553	case syscall.FILE_ACTION_MODIFIED:
554		return sysFSMODIFY
555	case syscall.FILE_ACTION_RENAMED_OLD_NAME:
556		return sysFSMOVEDFROM
557	case syscall.FILE_ACTION_RENAMED_NEW_NAME:
558		return sysFSMOVEDTO
559	}
560	return 0
561}
562