github.com/Hellyna/notify@v0.0.0-20210101060149-8ebdd4ef22cf/watcher_kqueue.go (about)

     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,ios darwin,!cgo dragonfly freebsd netbsd openbsd
     6  
     7  package notify
     8  
     9  import (
    10  	"fmt"
    11  	"os"
    12  	"syscall"
    13  )
    14  
    15  // newTrigger returns implementation of trigger.
    16  func newTrigger(pthLkp map[string]*watched) trigger {
    17  	return &kq{
    18  		pthLkp: pthLkp,
    19  		idLkp:  make(map[int]*watched),
    20  	}
    21  }
    22  
    23  // kq is a structure implementing trigger for kqueue.
    24  type kq struct {
    25  	// fd is a kqueue file descriptor
    26  	fd int
    27  	// pipefds are file descriptors used to stop `Kevent` call.
    28  	pipefds [2]int
    29  	// idLkp is a data structure mapping file descriptors with data about watching
    30  	// represented by them files/directories.
    31  	idLkp map[int]*watched
    32  	// pthLkp is a structure mapping monitored files/dir with data about them,
    33  	// shared with parent trg structure
    34  	pthLkp map[string]*watched
    35  }
    36  
    37  // watched is a data structure representing watched file/directory.
    38  type watched struct {
    39  	trgWatched
    40  	// fd is a file descriptor for watched file/directory.
    41  	fd int
    42  }
    43  
    44  // Stop implements trigger.
    45  func (k *kq) Stop() (err error) {
    46  	// trigger event used to interrupt Kevent call.
    47  	_, err = syscall.Write(k.pipefds[1], []byte{0x00})
    48  	return
    49  }
    50  
    51  // Close implements trigger.
    52  func (k *kq) Close() error {
    53  	return syscall.Close(k.fd)
    54  }
    55  
    56  // NewWatched implements trigger.
    57  func (*kq) NewWatched(p string, fi os.FileInfo) (*watched, error) {
    58  	fd, err := syscall.Open(p, syscall.O_NONBLOCK|syscall.O_RDONLY, 0)
    59  	if err != nil {
    60  		// BSDs can't open symlinks and return an error if the symlink
    61  		// cannot be followed - ignore it instead of failing. See e.g.
    62  		// https://github.com/libinotify-kqueue/libinotify-kqueue/blob/a822c8f1d75404fe3132f695a898dcd42fe8afbc/patches/freebsd11-O_SYMLINK.patch
    63  		if os.IsNotExist(err) && fi.Mode()&os.ModeSymlink == os.ModeSymlink {
    64  			return nil, errSkip
    65  		}
    66  		return nil, err
    67  	}
    68  	return &watched{
    69  		trgWatched: trgWatched{p: p, fi: fi},
    70  		fd:         fd,
    71  	}, nil
    72  }
    73  
    74  // Record implements trigger.
    75  func (k *kq) Record(w *watched) {
    76  	k.idLkp[w.fd], k.pthLkp[w.p] = w, w
    77  }
    78  
    79  // Del implements trigger.
    80  func (k *kq) Del(w *watched) {
    81  	syscall.Close(w.fd)
    82  	delete(k.idLkp, w.fd)
    83  	delete(k.pthLkp, w.p)
    84  }
    85  
    86  func inter2kq(n interface{}) syscall.Kevent_t {
    87  	kq, ok := n.(syscall.Kevent_t)
    88  	if !ok {
    89  		panic(fmt.Sprintf("kqueue: type should be Kevent_t, %T instead", n))
    90  	}
    91  	return kq
    92  }
    93  
    94  // Init implements trigger.
    95  func (k *kq) Init() (err error) {
    96  	if k.fd, err = syscall.Kqueue(); err != nil {
    97  		return
    98  	}
    99  	// Creates pipe used to stop `Kevent` call by registering it,
   100  	// watching read end and writing to other end of it.
   101  	if err = syscall.Pipe(k.pipefds[:]); err != nil {
   102  		return nonil(err, k.Close())
   103  	}
   104  	var kevn [1]syscall.Kevent_t
   105  	syscall.SetKevent(&kevn[0], k.pipefds[0], syscall.EVFILT_READ, syscall.EV_ADD)
   106  	if _, err = syscall.Kevent(k.fd, kevn[:], nil, nil); err != nil {
   107  		return nonil(err, k.Close())
   108  	}
   109  	return
   110  }
   111  
   112  // Unwatch implements trigger.
   113  func (k *kq) Unwatch(w *watched) (err error) {
   114  	var kevn [1]syscall.Kevent_t
   115  	syscall.SetKevent(&kevn[0], w.fd, syscall.EVFILT_VNODE, syscall.EV_DELETE)
   116  
   117  	_, err = syscall.Kevent(k.fd, kevn[:], nil, nil)
   118  	return
   119  }
   120  
   121  // Watch implements trigger.
   122  func (k *kq) Watch(fi os.FileInfo, w *watched, e int64) (err error) {
   123  	var kevn [1]syscall.Kevent_t
   124  	syscall.SetKevent(&kevn[0], w.fd, syscall.EVFILT_VNODE,
   125  		syscall.EV_ADD|syscall.EV_CLEAR)
   126  	kevn[0].Fflags = uint32(e)
   127  
   128  	_, err = syscall.Kevent(k.fd, kevn[:], nil, nil)
   129  	return
   130  }
   131  
   132  // Wait implements trigger.
   133  func (k *kq) Wait() (interface{}, error) {
   134  	var (
   135  		kevn [1]syscall.Kevent_t
   136  		err  error
   137  	)
   138  	kevn[0] = syscall.Kevent_t{}
   139  	_, err = syscall.Kevent(k.fd, nil, kevn[:], nil)
   140  
   141  	return kevn[0], err
   142  }
   143  
   144  // Watched implements trigger.
   145  func (k *kq) Watched(n interface{}) (*watched, int64, error) {
   146  	kevn, ok := n.(syscall.Kevent_t)
   147  	if !ok {
   148  		panic(fmt.Sprintf("kq: type should be syscall.Kevent_t, %T instead", kevn))
   149  	}
   150  	if _, ok = k.idLkp[int(kevn.Ident)]; !ok {
   151  		return nil, 0, errNotWatched
   152  	}
   153  	return k.idLkp[int(kevn.Ident)], int64(kevn.Fflags), nil
   154  }
   155  
   156  // IsStop implements trigger.
   157  func (k *kq) IsStop(n interface{}, err error) bool {
   158  	return int(inter2kq(n).Ident) == k.pipefds[0]
   159  }
   160  
   161  func init() {
   162  	encode = func(e Event, dir bool) (o int64) {
   163  		// Create event is not supported by kqueue. Instead NoteWrite event will
   164  		// be registered for a directory. If this event will be reported on dir
   165  		// which is to be monitored for Create, dir will be rescanned
   166  		// and Create events will be generated and returned for new files.
   167  		// In case of files, if not requested NoteRename event is reported,
   168  		// it will be ignored.
   169  		o = int64(e &^ Create)
   170  		if (e&Create != 0 && dir) || e&Write != 0 {
   171  			o = (o &^ int64(Write)) | int64(NoteWrite)
   172  		}
   173  		if e&Rename != 0 {
   174  			o = (o &^ int64(Rename)) | int64(NoteRename)
   175  		}
   176  		if e&Remove != 0 {
   177  			o = (o &^ int64(Remove)) | int64(NoteDelete)
   178  		}
   179  		return
   180  	}
   181  	nat2not = map[Event]Event{
   182  		NoteWrite:  Write,
   183  		NoteRename: Rename,
   184  		NoteDelete: Remove,
   185  		NoteExtend: Event(0),
   186  		NoteAttrib: Event(0),
   187  		NoteRevoke: Event(0),
   188  		NoteLink:   Event(0),
   189  	}
   190  	not2nat = map[Event]Event{
   191  		Write:  NoteWrite,
   192  		Rename: NoteRename,
   193  		Remove: NoteDelete,
   194  	}
   195  }