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