aboutsummaryrefslogblamecommitdiffstats
path: root/vendor/github.com/rjeczalik/notify/watcher_fsevents.go
blob: 9062c17c7f4377fc8e8c25e9d50c389c83088cf2 (plain) (tree)






































































































































                                                                                               
                                






















































































































































































                                                                                                
// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.

// +build darwin,!kqueue

package notify

import (
    "errors"
    "strings"
    "sync/atomic"
)

// TODO(rjeczalik): get rid of calls to canonical, it's tree responsibility

const (
    failure = uint32(FSEventsMustScanSubDirs | FSEventsUserDropped | FSEventsKernelDropped)
    filter  = uint32(FSEventsCreated | FSEventsRemoved | FSEventsRenamed |
        FSEventsModified | FSEventsInodeMetaMod)
)

// FSEvent represents single file event. It is created out of values passed by
// FSEvents to FSEventStreamCallback function.
type FSEvent struct {
    Path  string // real path of the file or directory
    ID    uint64 // ID of the event (FSEventStreamEventId)
    Flags uint32 // joint FSEvents* flags (FSEventStreamEventFlags)
}

// splitflags separates event flags from single set into slice of flags.
func splitflags(set uint32) (e []uint32) {
    for i := uint32(1); set != 0; i, set = i<<1, set>>1 {
        if (set & 1) != 0 {
            e = append(e, i)
        }
    }
    return
}

// watch represents a filesystem watchpoint. It is a higher level abstraction
// over FSEvents' stream, which implements filtering of file events based
// on path and event set. It emulates non-recursive watch-point by filtering out
// events which paths are more than 1 level deeper than the watched path.
type watch struct {
    // prev stores last event set  per path in order to filter out old flags
    // for new events, which appratenly FSEvents likes to retain. It's a disgusting
    // hack, it should be researched how to get rid of it.
    prev    map[string]uint32
    c       chan<- EventInfo
    stream  *stream
    path    string
    events  uint32
    isrec   int32
    flushed bool
}

// Example format:
//
//   ~ $ (trigger command) # (event set) -> (effective event set)
//
// Heuristics:
//
// 1. Create event is removed when it was present in previous event set.
// Example:
//
//   ~ $ echo > file # Create|Write -> Create|Write
//   ~ $ echo > file # Create|Write|InodeMetaMod -> Write|InodeMetaMod
//
// 2. Remove event is removed if it was present in previouse event set.
// Example:
//
//   ~ $ touch file # Create -> Create
//   ~ $ rm file    # Create|Remove -> Remove
//   ~ $ touch file # Create|Remove -> Create
//
// 3. Write event is removed if not followed by InodeMetaMod on existing
// file. Example:
//
//   ~ $ echo > file   # Create|Write -> Create|Write
//   ~ $ chmod +x file # Create|Write|ChangeOwner -> ChangeOwner
//
// 4. Write&InodeMetaMod is removed when effective event set contain Remove event.
// Example:
//
//   ~ $ echo > file # Write|InodeMetaMod -> Write|InodeMetaMod
//   ~ $ rm file     # Remove|Write|InodeMetaMod -> Remove
//
func (w *watch) strip(base string, set uint32) uint32 {
    const (
        write = FSEventsModified | FSEventsInodeMetaMod
        both  = FSEventsCreated | FSEventsRemoved
    )
    switch w.prev[base] {
    case FSEventsCreated:
        set &^= FSEventsCreated
        if set&FSEventsRemoved != 0 {
            w.prev[base] = FSEventsRemoved
            set &^= write
        }
    case FSEventsRemoved:
        set &^= FSEventsRemoved
        if set&FSEventsCreated != 0 {
            w.prev[base] = FSEventsCreated
        }
    default:
        switch set & both {
        case FSEventsCreated:
            w.prev[base] = FSEventsCreated
        case FSEventsRemoved:
            w.prev[base] = FSEventsRemoved
            set &^= write
        }
    }
    dbgprintf("split()=%v\n", Event(set))
    return set
}

// Dispatch is a stream function which forwards given file events for the watched
// path to underlying FileInfo channel.
func (w *watch) Dispatch(ev []FSEvent) {
    events := atomic.LoadUint32(&w.events)
    isrec := (atomic.LoadInt32(&w.isrec) == 1)
    for i := range ev {
        if ev[i].Flags&FSEventsHistoryDone != 0 {
            w.flushed = true
            continue
        }
        if !w.flushed {
            continue
        }
        dbgprintf("%v (0x%x) (%s, i=%d, ID=%d, len=%d)\n", Event(ev[i].Flags),
            ev[i].Flags, ev[i].Path, i, ev[i].ID, len(ev))
        if ev[i].Flags&failure != 0 {
            // TODO(rjeczalik): missing error handling
            continue
        }
        if !strings.HasPrefix(ev[i].Path, w.path) {
            continue
        }
        n := len(w.path)
        base := ""
        if len(ev[i].Path) > n {
            if ev[i].Path[n] != '/' {
                continue
            }
            base = ev[i].Path[n+1:]
            if !isrec && strings.IndexByte(base, '/') != -1 {
                continue
            }
        }
        // TODO(rjeczalik): get diff only from filtered events?
        e := w.strip(string(base), ev[i].Flags) & events
        if e == 0 {
            continue
        }
        for _, e := range splitflags(e) {
            dbgprintf("%d: single event: %v", ev[i].ID, Event(e))
            w.c <- &event{
                fse:   ev[i],
                event: Event(e),
            }
        }
    }
}

// Stop closes underlying FSEvents stream and stops dispatching events.
func (w *watch) Stop() {
    w.stream.Stop()
    // TODO(rjeczalik): make (*stream).Stop flush synchronously undelivered events,
    // so the following hack can be removed. It should flush all the streams
    // concurrently as we care not to block too much here.
    atomic.StoreUint32(&w.events, 0)
    atomic.StoreInt32(&w.isrec, 0)
}

// fsevents implements Watcher and RecursiveWatcher interfaces backed by FSEvents
// framework.
type fsevents struct {
    watches map[string]*watch
    c       chan<- EventInfo
}

func newWatcher(c chan<- EventInfo) watcher {
    return &fsevents{
        watches: make(map[string]*watch),
        c:       c,
    }
}

func (fse *fsevents) watch(path string, event Event, isrec int32) (err error) {
    if path, err = canonical(path); err != nil {
        return err
    }
    if _, ok := fse.watches[path]; ok {
        return errAlreadyWatched
    }
    w := &watch{
        prev:   make(map[string]uint32),
        c:      fse.c,
        path:   path,
        events: uint32(event),
        isrec:  isrec,
    }
    w.stream = newStream(path, w.Dispatch)
    if err = w.stream.Start(); err != nil {
        return err
    }
    fse.watches[path] = w
    return nil
}

func (fse *fsevents) unwatch(path string) (err error) {
    if path, err = canonical(path); err != nil {
        return
    }
    w, ok := fse.watches[path]
    if !ok {
        return errNotWatched
    }
    w.stream.Stop()
    delete(fse.watches, path)
    return nil
}

// Watch implements Watcher interface. It fails with non-nil error when setting
// the watch-point by FSEvents fails or with errAlreadyWatched error when
// the given path is already watched.
func (fse *fsevents) Watch(path string, event Event) error {
    return fse.watch(path, event, 0)
}

// Unwatch implements Watcher interface. It fails with errNotWatched when
// the given path is not being watched.
func (fse *fsevents) Unwatch(path string) error {
    return fse.unwatch(path)
}

// Rewatch implements Watcher interface. It fails with errNotWatched when
// the given path is not being watched or with errInvalidEventSet when oldevent
// does not match event set the watch-point currently holds.
func (fse *fsevents) Rewatch(path string, oldevent, newevent Event) error {
    w, ok := fse.watches[path]
    if !ok {
        return errNotWatched
    }
    if !atomic.CompareAndSwapUint32(&w.events, uint32(oldevent), uint32(newevent)) {
        return errInvalidEventSet
    }
    atomic.StoreInt32(&w.isrec, 0)
    return nil
}

// RecursiveWatch implements RecursiveWatcher interface. It fails with non-nil
// error when setting the watch-point by FSEvents fails or with errAlreadyWatched
// error when the given path is already watched.
func (fse *fsevents) RecursiveWatch(path string, event Event) error {
    return fse.watch(path, event, 1)
}

// RecursiveUnwatch implements RecursiveWatcher interface. It fails with
// errNotWatched when the given path is not being watched.
//
// TODO(rjeczalik): fail if w.isrec == 0?
func (fse *fsevents) RecursiveUnwatch(path string) error {
    return fse.unwatch(path)
}

// RecrusiveRewatch implements RecursiveWatcher interface. It fails:
//
//   * with errNotWatched when the given path is not being watched
//   * with errInvalidEventSet when oldevent does not match the current event set
//   * with errAlreadyWatched when watch-point given by the oldpath was meant to
//     be relocated to newpath, but the newpath is already watched
//   * a non-nil error when setting the watch-point with FSEvents fails
//
// TODO(rjeczalik): Improve handling of watch-point relocation? See two TODOs
// that follows.
func (fse *fsevents) RecursiveRewatch(oldpath, newpath string, oldevent, newevent Event) error {
    switch [2]bool{oldpath == newpath, oldevent == newevent} {
    case [2]bool{true, true}:
        w, ok := fse.watches[oldpath]
        if !ok {
            return errNotWatched
        }
        atomic.StoreInt32(&w.isrec, 1)
        return nil
    case [2]bool{true, false}:
        w, ok := fse.watches[oldpath]
        if !ok {
            return errNotWatched
        }
        if !atomic.CompareAndSwapUint32(&w.events, uint32(oldevent), uint32(newevent)) {
            return errors.New("invalid event state diff")
        }
        atomic.StoreInt32(&w.isrec, 1)
        return nil
    default:
        // TODO(rjeczalik): rewatch newpath only if exists?
        // TODO(rjeczalik): migrate w.prev to new watch?
        if _, ok := fse.watches[newpath]; ok {
            return errAlreadyWatched
        }
        if err := fse.Unwatch(oldpath); err != nil {
            return err
        }
        // TODO(rjeczalik): revert unwatch if watch fails?
        return fse.watch(newpath, newevent, 1)
    }
}

// Close unwatches all watch-points.
func (fse *fsevents) Close() error {
    for _, w := range fse.watches {
        w.Stop()
    }
    fse.watches = nil
    return nil
}