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 }