1// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
2// Use of this source code is governed by the MIT license that can be
3// found in the LICENSE file.
4
5// +build darwin,!kqueue
6
7package notify
8
9import (
10	"errors"
11	"strings"
12	"sync/atomic"
13)
14
15const (
16	failure = uint32(FSEventsMustScanSubDirs | FSEventsUserDropped | FSEventsKernelDropped)
17	filter  = uint32(FSEventsCreated | FSEventsRemoved | FSEventsRenamed |
18		FSEventsModified | FSEventsInodeMetaMod)
19)
20
21// FSEvent represents single file event. It is created out of values passed by
22// FSEvents to FSEventStreamCallback function.
23type FSEvent struct {
24	Path  string // real path of the file or directory
25	ID    uint64 // ID of the event (FSEventStreamEventId)
26	Flags uint32 // joint FSEvents* flags (FSEventStreamEventFlags)
27}
28
29// splitflags separates event flags from single set into slice of flags.
30func splitflags(set uint32) (e []uint32) {
31	for i := uint32(1); set != 0; i, set = i<<1, set>>1 {
32		if (set & 1) != 0 {
33			e = append(e, i)
34		}
35	}
36	return
37}
38
39// watch represents a filesystem watchpoint. It is a higher level abstraction
40// over FSEvents' stream, which implements filtering of file events based
41// on path and event set. It emulates non-recursive watch-point by filtering out
42// events which paths are more than 1 level deeper than the watched path.
43type watch struct {
44	// prev stores last event set  per path in order to filter out old flags
45	// for new events, which appratenly FSEvents likes to retain. It's a disgusting
46	// hack, it should be researched how to get rid of it.
47	prev    map[string]uint32
48	c       chan<- EventInfo
49	stream  *stream
50	path    string
51	events  uint32
52	isrec   int32
53	flushed bool
54}
55
56// Example format:
57//
58//   ~ $ (trigger command) # (event set) -> (effective event set)
59//
60// Heuristics:
61//
62// 1. Create event is removed when it was present in previous event set.
63// Example:
64//
65//   ~ $ echo > file # Create|Write -> Create|Write
66//   ~ $ echo > file # Create|Write|InodeMetaMod -> Write|InodeMetaMod
67//
68// 2. Remove event is removed if it was present in previouse event set.
69// Example:
70//
71//   ~ $ touch file # Create -> Create
72//   ~ $ rm file    # Create|Remove -> Remove
73//   ~ $ touch file # Create|Remove -> Create
74//
75// 3. Write event is removed if not followed by InodeMetaMod on existing
76// file. Example:
77//
78//   ~ $ echo > file   # Create|Write -> Create|Write
79//   ~ $ chmod +x file # Create|Write|ChangeOwner -> ChangeOwner
80//
81// 4. Write&InodeMetaMod is removed when effective event set contain Remove event.
82// Example:
83//
84//   ~ $ echo > file # Write|InodeMetaMod -> Write|InodeMetaMod
85//   ~ $ rm file     # Remove|Write|InodeMetaMod -> Remove
86//
87func (w *watch) strip(base string, set uint32) uint32 {
88	const (
89		write = FSEventsModified | FSEventsInodeMetaMod
90		both  = FSEventsCreated | FSEventsRemoved
91	)
92	switch w.prev[base] {
93	case FSEventsCreated:
94		set &^= FSEventsCreated
95		if set&FSEventsRemoved != 0 {
96			w.prev[base] = FSEventsRemoved
97			set &^= write
98		}
99	case FSEventsRemoved:
100		set &^= FSEventsRemoved
101		if set&FSEventsCreated != 0 {
102			w.prev[base] = FSEventsCreated
103		}
104	default:
105		switch set & both {
106		case FSEventsCreated:
107			w.prev[base] = FSEventsCreated
108		case FSEventsRemoved:
109			w.prev[base] = FSEventsRemoved
110			set &^= write
111		}
112	}
113	dbgprintf("split()=%v\n", Event(set))
114	return set
115}
116
117// Dispatch is a stream function which forwards given file events for the watched
118// path to underlying FileInfo channel.
119func (w *watch) Dispatch(ev []FSEvent) {
120	events := atomic.LoadUint32(&w.events)
121	isrec := (atomic.LoadInt32(&w.isrec) == 1)
122	for i := range ev {
123		if ev[i].Flags&FSEventsHistoryDone != 0 {
124			w.flushed = true
125			continue
126		}
127		if !w.flushed {
128			continue
129		}
130		dbgprintf("%v (0x%x) (%s, i=%d, ID=%d, len=%d)\n", Event(ev[i].Flags),
131			ev[i].Flags, ev[i].Path, i, ev[i].ID, len(ev))
132		if ev[i].Flags&failure != 0 {
133			// TODO(rjeczalik): missing error handling
134			continue
135		}
136		if !strings.HasPrefix(ev[i].Path, w.path) {
137			continue
138		}
139		n := len(w.path)
140		base := ""
141		if len(ev[i].Path) > n {
142			if ev[i].Path[n] != '/' {
143				continue
144			}
145			base = ev[i].Path[n+1:]
146			if !isrec && strings.IndexByte(base, '/') != -1 {
147				continue
148			}
149		}
150		// TODO(rjeczalik): get diff only from filtered events?
151		e := w.strip(string(base), ev[i].Flags) & events
152		if e == 0 {
153			continue
154		}
155		for _, e := range splitflags(e) {
156			dbgprintf("%d: single event: %v", ev[i].ID, Event(e))
157			w.c <- &event{
158				fse:   ev[i],
159				event: Event(e),
160			}
161		}
162	}
163}
164
165// Stop closes underlying FSEvents stream and stops dispatching events.
166func (w *watch) Stop() {
167	w.stream.Stop()
168	// TODO(rjeczalik): make (*stream).Stop flush synchronously undelivered events,
169	// so the following hack can be removed. It should flush all the streams
170	// concurrently as we care not to block too much here.
171	atomic.StoreUint32(&w.events, 0)
172	atomic.StoreInt32(&w.isrec, 0)
173}
174
175// fsevents implements Watcher and RecursiveWatcher interfaces backed by FSEvents
176// framework.
177type fsevents struct {
178	watches map[string]*watch
179	c       chan<- EventInfo
180}
181
182func newWatcher(c chan<- EventInfo) watcher {
183	return &fsevents{
184		watches: make(map[string]*watch),
185		c:       c,
186	}
187}
188
189func (fse *fsevents) watch(path string, event Event, isrec int32) (err error) {
190	if _, ok := fse.watches[path]; ok {
191		return errAlreadyWatched
192	}
193	w := &watch{
194		prev:   make(map[string]uint32),
195		c:      fse.c,
196		path:   path,
197		events: uint32(event),
198		isrec:  isrec,
199	}
200	w.stream = newStream(path, w.Dispatch)
201	if err = w.stream.Start(); err != nil {
202		return err
203	}
204	fse.watches[path] = w
205	return nil
206}
207
208func (fse *fsevents) unwatch(path string) (err error) {
209	w, ok := fse.watches[path]
210	if !ok {
211		return errNotWatched
212	}
213	w.stream.Stop()
214	delete(fse.watches, path)
215	return nil
216}
217
218// Watch implements Watcher interface. It fails with non-nil error when setting
219// the watch-point by FSEvents fails or with errAlreadyWatched error when
220// the given path is already watched.
221func (fse *fsevents) Watch(path string, event Event) error {
222	return fse.watch(path, event, 0)
223}
224
225// Unwatch implements Watcher interface. It fails with errNotWatched when
226// the given path is not being watched.
227func (fse *fsevents) Unwatch(path string) error {
228	return fse.unwatch(path)
229}
230
231// Rewatch implements Watcher interface. It fails with errNotWatched when
232// the given path is not being watched or with errInvalidEventSet when oldevent
233// does not match event set the watch-point currently holds.
234func (fse *fsevents) Rewatch(path string, oldevent, newevent Event) error {
235	w, ok := fse.watches[path]
236	if !ok {
237		return errNotWatched
238	}
239	if !atomic.CompareAndSwapUint32(&w.events, uint32(oldevent), uint32(newevent)) {
240		return errInvalidEventSet
241	}
242	atomic.StoreInt32(&w.isrec, 0)
243	return nil
244}
245
246// RecursiveWatch implements RecursiveWatcher interface. It fails with non-nil
247// error when setting the watch-point by FSEvents fails or with errAlreadyWatched
248// error when the given path is already watched.
249func (fse *fsevents) RecursiveWatch(path string, event Event) error {
250	return fse.watch(path, event, 1)
251}
252
253// RecursiveUnwatch implements RecursiveWatcher interface. It fails with
254// errNotWatched when the given path is not being watched.
255//
256// TODO(rjeczalik): fail if w.isrec == 0?
257func (fse *fsevents) RecursiveUnwatch(path string) error {
258	return fse.unwatch(path)
259}
260
261// RecrusiveRewatch implements RecursiveWatcher interface. It fails:
262//
263//   * with errNotWatched when the given path is not being watched
264//   * with errInvalidEventSet when oldevent does not match the current event set
265//   * with errAlreadyWatched when watch-point given by the oldpath was meant to
266//     be relocated to newpath, but the newpath is already watched
267//   * a non-nil error when setting the watch-point with FSEvents fails
268//
269// TODO(rjeczalik): Improve handling of watch-point relocation? See two TODOs
270// that follows.
271func (fse *fsevents) RecursiveRewatch(oldpath, newpath string, oldevent, newevent Event) error {
272	switch [2]bool{oldpath == newpath, oldevent == newevent} {
273	case [2]bool{true, true}:
274		w, ok := fse.watches[oldpath]
275		if !ok {
276			return errNotWatched
277		}
278		atomic.StoreInt32(&w.isrec, 1)
279		return nil
280	case [2]bool{true, false}:
281		w, ok := fse.watches[oldpath]
282		if !ok {
283			return errNotWatched
284		}
285		if !atomic.CompareAndSwapUint32(&w.events, uint32(oldevent), uint32(newevent)) {
286			return errors.New("invalid event state diff")
287		}
288		atomic.StoreInt32(&w.isrec, 1)
289		return nil
290	default:
291		// TODO(rjeczalik): rewatch newpath only if exists?
292		// TODO(rjeczalik): migrate w.prev to new watch?
293		if _, ok := fse.watches[newpath]; ok {
294			return errAlreadyWatched
295		}
296		if err := fse.Unwatch(oldpath); err != nil {
297			return err
298		}
299		// TODO(rjeczalik): revert unwatch if watch fails?
300		return fse.watch(newpath, newevent, 1)
301	}
302}
303
304// Close unwatches all watch-points.
305func (fse *fsevents) Close() error {
306	for _, w := range fse.watches {
307		w.Stop()
308	}
309	fse.watches = nil
310	return nil
311}
312