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 }