github.com/DiversionCompany/notify@v0.9.9/watcher_inotify.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 linux 6 // +build linux 7 8 package notify 9 10 import ( 11 "bytes" 12 "errors" 13 "path/filepath" 14 "runtime" 15 "sync" 16 "sync/atomic" 17 "unsafe" 18 19 "golang.org/x/sys/unix" 20 ) 21 22 // eventBufferSize defines the size of the buffer given to read(2) function. One 23 // should not depend on this value, since it was arbitrary chosen and may be 24 // changed in the future. 25 const eventBufferSize = 64 * (unix.SizeofInotifyEvent + unix.PathMax + 1) 26 27 // consumersCount defines the number of consumers in producer-consumer based 28 // implementation. Each consumer is run in a separate goroutine and has read 29 // access to watched files map. 30 const consumersCount = 2 31 32 const invalidDescriptor = -1 33 34 // watched is a pair of file path and inotify mask used as a value in 35 // watched files map. 36 type watched struct { 37 path string 38 mask uint32 39 } 40 41 // inotify implements Watcher interface. 42 type inotify struct { 43 sync.RWMutex // protects inotify.m map 44 m map[int32]*watched // watch descriptor to watched object 45 fd int32 // inotify file descriptor 46 pipefd []int // pipe's read and write descriptors 47 epfd int // epoll descriptor 48 epes []unix.EpollEvent // epoll events 49 buffer [eventBufferSize]byte // inotify event buffer 50 wg sync.WaitGroup // wait group used to close main loop 51 c chan<- EventInfo // event dispatcher channel 52 } 53 54 // NewWatcher creates new non-recursive inotify backed by inotify. 55 func newWatcher(c chan<- EventInfo) watcher { 56 i := &inotify{ 57 m: make(map[int32]*watched), 58 fd: invalidDescriptor, 59 pipefd: []int{invalidDescriptor, invalidDescriptor}, 60 epfd: invalidDescriptor, 61 epes: make([]unix.EpollEvent, 0), 62 c: c, 63 } 64 runtime.SetFinalizer(i, func(i *inotify) { 65 i.epollclose() 66 if i.fd != invalidDescriptor { 67 unix.Close(int(i.fd)) 68 } 69 }) 70 return i 71 } 72 73 // Watch implements notify.watcher interface. 74 func (i *inotify) Watch(path string, e Event) error { 75 return i.watch(path, e) 76 } 77 78 // Rewatch implements notify.watcher interface. 79 func (i *inotify) Rewatch(path string, _, newevent Event) error { 80 return i.watch(path, newevent) 81 } 82 83 // watch adds a new watcher to the set of watched objects or modifies the existing 84 // one. If called for the first time, this function initializes inotify filesystem 85 // monitor and starts producer-consumers goroutines. 86 func (i *inotify) watch(path string, e Event) (err error) { 87 if e&^(All|Event(unix.IN_ALL_EVENTS)) != 0 { 88 return errors.New("notify: unknown event") 89 } 90 if err = i.lazyinit(); err != nil { 91 dbgprintf("failed to lazyinit: %v\n", err) 92 return 93 } 94 iwd, err := unix.InotifyAddWatch(int(i.fd), path, encode(e)) 95 if err != nil { 96 dbgprintf("failed to add watch on %s: %v\n", path, err) 97 return 98 } 99 dbgprintf("added watch on wd=%d ('%s')\n", iwd, path) 100 i.Lock() 101 if wd, ok := i.m[int32(iwd)]; !ok { 102 i.m[int32(iwd)] = &watched{path: path, mask: uint32(e)} 103 } else { 104 wd.path = path 105 wd.mask = uint32(e) 106 } 107 i.Unlock() 108 return nil 109 } 110 111 // lazyinit sets up all required file descriptors and starts 1+consumersCount 112 // goroutines. The producer goroutine blocks until file-system notifications 113 // occur. Then, all events are read from system buffer and sent to consumer 114 // goroutines which construct valid notify events. This method uses 115 // Double-Checked Locking optimization. 116 func (i *inotify) lazyinit() error { 117 if atomic.LoadInt32(&i.fd) == invalidDescriptor { 118 i.Lock() 119 defer i.Unlock() 120 if atomic.LoadInt32(&i.fd) == invalidDescriptor { 121 fd, err := unix.InotifyInit1(unix.IN_CLOEXEC) 122 if err != nil { 123 return err 124 } 125 i.fd = int32(fd) 126 if err = i.epollinit(); err != nil { 127 _, _ = i.epollclose(), unix.Close(int(fd)) // Ignore errors. 128 i.fd = invalidDescriptor 129 return err 130 } 131 esch := make(chan []*event) 132 go i.loop(esch) 133 i.wg.Add(consumersCount) 134 for n := 0; n < consumersCount; n++ { 135 go i.send(esch) 136 } 137 } 138 } 139 return nil 140 } 141 142 // epollinit opens an epoll file descriptor and creates a pipe which will be 143 // used to wake up the epoll_wait(2) function. Then, file descriptor associated 144 // with inotify event queue and the read end of the pipe are added to epoll set. 145 // Note that `fd` member must be set before this function is called. 146 func (i *inotify) epollinit() (err error) { 147 if i.epfd, err = unix.EpollCreate1(0); err != nil { 148 return 149 } 150 if err = unix.Pipe(i.pipefd); err != nil { 151 return 152 } 153 i.epes = []unix.EpollEvent{ 154 {Events: unix.EPOLLIN, Fd: i.fd}, 155 {Events: unix.EPOLLIN, Fd: int32(i.pipefd[0])}, 156 } 157 if err = unix.EpollCtl(i.epfd, unix.EPOLL_CTL_ADD, int(i.fd), &i.epes[0]); err != nil { 158 return 159 } 160 return unix.EpollCtl(i.epfd, unix.EPOLL_CTL_ADD, i.pipefd[0], &i.epes[1]) 161 } 162 163 // epollclose closes the file descriptor created by the call to epoll_create(2) 164 // and two file descriptors opened by pipe(2) function. 165 func (i *inotify) epollclose() (err error) { 166 if i.epfd != invalidDescriptor { 167 if err = unix.Close(i.epfd); err == nil { 168 i.epfd = invalidDescriptor 169 } 170 } 171 for n, fd := range i.pipefd { 172 if fd != invalidDescriptor { 173 switch e := unix.Close(fd); { 174 case e != nil && err == nil: 175 err = e 176 case e == nil: 177 i.pipefd[n] = invalidDescriptor 178 } 179 } 180 } 181 return 182 } 183 184 // loop blocks until either inotify or pipe file descriptor is ready for I/O. 185 // All read operations triggered by filesystem notifications are forwarded to 186 // one of the event's consumers. If pipe fd became ready, loop function closes 187 // all file descriptors opened by lazyinit method and returns afterwards. 188 func (i *inotify) loop(esch chan<- []*event) { 189 epes := make([]unix.EpollEvent, 1) 190 fd := atomic.LoadInt32(&i.fd) 191 for { 192 switch _, err := unix.EpollWait(i.epfd, epes, -1); err { 193 case nil: 194 switch epes[0].Fd { 195 case fd: 196 esch <- i.read() 197 epes[0].Fd = 0 198 case int32(i.pipefd[0]): 199 i.Lock() 200 defer i.Unlock() 201 if err = unix.Close(int(fd)); err != nil && err != unix.EINTR { 202 panic("notify: close(2) error " + err.Error()) 203 } 204 atomic.StoreInt32(&i.fd, invalidDescriptor) 205 if err = i.epollclose(); err != nil && err != unix.EINTR { 206 panic("notify: epollclose error " + err.Error()) 207 } 208 close(esch) 209 return 210 } 211 case unix.EINTR: 212 continue 213 default: // We should never reach this line. 214 panic("notify: epoll_wait(2) error " + err.Error()) 215 } 216 } 217 } 218 219 // read reads events from an inotify file descriptor. It does not handle errors 220 // returned from read(2) function since they are not critical to watcher logic. 221 func (i *inotify) read() (es []*event) { 222 n, err := unix.Read(int(i.fd), i.buffer[:]) 223 if err != nil || n < unix.SizeofInotifyEvent { 224 return 225 } 226 var sys *unix.InotifyEvent 227 nmin := n - unix.SizeofInotifyEvent 228 for pos, path := 0, ""; pos <= nmin; { 229 sys = (*unix.InotifyEvent)(unsafe.Pointer(&i.buffer[pos])) 230 pos += unix.SizeofInotifyEvent 231 if path = ""; sys.Len > 0 { 232 endpos := pos + int(sys.Len) 233 path = string(bytes.TrimRight(i.buffer[pos:endpos], "\x00")) 234 pos = endpos 235 } 236 es = append(es, &event{ 237 sys: unix.InotifyEvent{ 238 Wd: sys.Wd, 239 Mask: sys.Mask, 240 Cookie: sys.Cookie, 241 }, 242 path: path, 243 }) 244 } 245 return 246 } 247 248 // send is a consumer function which sends events to event dispatcher channel. 249 // It is run in a separate goroutine in order to not block loop method when 250 // possibly expensive write operations are performed on inotify map. 251 func (i *inotify) send(esch <-chan []*event) { 252 for es := range esch { 253 for _, e := range i.transform(es) { 254 if e != nil { 255 i.c <- e 256 } 257 } 258 } 259 i.wg.Done() 260 } 261 262 func logEvent(prefix string, e *event, idx int, len int) { 263 dbgprintf("%s %v (0x%x) ('%s', i=%d, wd=%d, cookie=%d, len=%d)\n", 264 prefix, 265 Event(e.sys.Mask), 266 e.sys.Mask, e.path, idx, e.sys.Wd, e.sys.Cookie, len) 267 } 268 269 // transform prepares events read from inotify file descriptor for sending to 270 // user. It removes invalid events and these which are no longer present in 271 // inotify map. This method may also split one raw event into two different ones 272 // when system-dependent result is required. 273 func (i *inotify) transform(es []*event) []*event { 274 var multi []*event 275 i.RLock() 276 for idx, e := range es { 277 if e.sys.Mask&(unix.IN_IGNORED|unix.IN_Q_OVERFLOW) != 0 { 278 es[idx] = nil 279 logEvent("ignored_event", e, idx, len(es)) 280 continue 281 } 282 wd, ok := i.m[e.sys.Wd] 283 if !ok || e.sys.Mask&encode(Event(wd.mask)) == 0 { 284 es[idx] = nil 285 logEvent("undecoded_event", e, idx, len(es)) 286 continue 287 } 288 if e.path == "" { 289 e.path = wd.path 290 } else { 291 e.path = filepath.Join(wd.path, e.path) 292 } 293 logEvent("received_event", e, idx, len(es)) 294 multi = append(multi, decode(Event(wd.mask), e)) 295 if e.event == 0 { 296 es[idx] = nil 297 } 298 } 299 i.RUnlock() 300 es = append(es, multi...) 301 return es 302 } 303 304 // encode converts notify system-independent events to valid inotify mask 305 // which can be passed to inotify_add_watch(2) function. 306 func encode(e Event) uint32 { 307 if e&Create != 0 { 308 e = (e ^ Create) | InCreate | InMovedTo 309 } 310 if e&Remove != 0 { 311 e = (e ^ Remove) | InDelete | InDeleteSelf 312 } 313 if e&Write != 0 { 314 e = (e ^ Write) | InModify 315 } 316 if e&Rename != 0 { 317 e = (e ^ Rename) | InMovedFrom | InMoveSelf 318 } 319 return uint32(e) 320 } 321 322 // decode uses internally stored mask to distinguish whether system-independent 323 // or system-dependent event is requested. The first one is created by modifying 324 // `e` argument. decode method sets e.event value to 0 when an event should be 325 // skipped. System-dependent event is set as the function's return value which 326 // can be nil when the event should not be passed on. 327 func decode(mask Event, e *event) (syse *event) { 328 if sysmask := uint32(mask) & e.sys.Mask; sysmask != 0 { 329 syse = &event{sys: unix.InotifyEvent{ 330 Wd: e.sys.Wd, 331 Mask: e.sys.Mask, 332 Cookie: e.sys.Cookie, 333 }, event: Event(sysmask), path: e.path} 334 } 335 imask := encode(mask) 336 switch { 337 case mask&Create != 0 && imask&uint32(InCreate|InMovedTo)&e.sys.Mask != 0: 338 e.event = Create 339 case mask&Remove != 0 && imask&uint32(InDelete|InDeleteSelf)&e.sys.Mask != 0: 340 e.event = Remove 341 case mask&Write != 0 && imask&uint32(InModify)&e.sys.Mask != 0: 342 e.event = Write 343 case mask&Rename != 0 && imask&uint32(InMovedFrom|InMoveSelf)&e.sys.Mask != 0: 344 e.event = Rename 345 default: 346 e.event = 0 347 } 348 return 349 } 350 351 // Unwatch implements notify.watcher interface. It looks for watch descriptor 352 // related to registered path and if found, calls inotify_rm_watch(2) function. 353 // This method is allowed to return EINVAL error when concurrently requested to 354 // delete identical path. 355 func (i *inotify) Unwatch(path string) (err error) { 356 iwd := int32(invalidDescriptor) 357 i.RLock() 358 for iwdkey, wd := range i.m { 359 if wd.path == path { 360 iwd = iwdkey 361 break 362 } 363 } 364 i.RUnlock() 365 if iwd == invalidDescriptor { 366 return errors.New("notify: path " + path + " is already watched") 367 } 368 fd := atomic.LoadInt32(&i.fd) 369 if err = removeInotifyWatch(fd, iwd); err != nil { 370 return 371 } 372 i.Lock() 373 delete(i.m, iwd) 374 i.Unlock() 375 return nil 376 } 377 378 // Close implements notify.watcher interface. It removes all existing watch 379 // descriptors and wakes up producer goroutine by sending data to the write end 380 // of the pipe. The function waits for a signal from producer which means that 381 // all operations on current monitoring instance are done. 382 func (i *inotify) Close() (err error) { 383 i.Lock() 384 if fd := atomic.LoadInt32(&i.fd); fd == invalidDescriptor { 385 i.Unlock() 386 return nil 387 } 388 for iwd := range i.m { 389 if e := removeInotifyWatch(i.fd, iwd); e != nil && err == nil { 390 err = e 391 } 392 delete(i.m, iwd) 393 } 394 switch _, errwrite := unix.Write(i.pipefd[1], []byte{0x00}); { 395 case errwrite != nil && err == nil: 396 err = errwrite 397 fallthrough 398 case errwrite != nil: 399 i.Unlock() 400 default: 401 i.Unlock() 402 i.wg.Wait() 403 } 404 return 405 } 406 407 // if path was removed, notify already removed the watch and returns EINVAL error 408 func removeInotifyWatch(fd int32, iwd int32) (err error) { 409 if _, err = unix.InotifyRmWatch(int(fd), uint32(iwd)); err != nil && err != unix.EINVAL { 410 return 411 } 412 return nil 413 }