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