github.com/FabianKramm/notify@v0.9.3-0.20210719135015-4705c29227a1/watcher_readdcw.go (about) 1 // Copyright (c) 2014-2020 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 windows 6 7 package notify 8 9 import ( 10 "errors" 11 "runtime" 12 "sync" 13 "sync/atomic" 14 "syscall" 15 "unsafe" 16 ) 17 18 // readBufferSize defines the size of an array in which read statuses are stored. 19 // The buffer have to be DWORD-aligned and, if notify is used in monitoring a 20 // directory over the network, its size must not be greater than 64KB. Each of 21 // watched directories uses its own buffer for storing events. 22 const readBufferSize = 4096 23 24 // Since all operations which go through the Windows completion routine are done 25 // asynchronously, filter may set one of the constants below. They were defined 26 // in order to distinguish whether current folder should be re-registered in 27 // ReadDirectoryChangesW function or some control operations need to be executed. 28 const ( 29 stateRewatch uint32 = 1 << (28 + iota) 30 stateUnwatch 31 stateCPClose 32 ) 33 34 // Filter used in current implementation was split into four segments: 35 // - bits 0-11 store ReadDirectoryChangesW filters, 36 // - bits 12-19 store File notify actions, 37 // - bits 20-27 store notify specific events and flags, 38 // - bits 28-31 store states which are used in loop's FSM. 39 // Constants below are used as masks to retrieve only specific filter parts. 40 const ( 41 onlyNotifyChanges uint32 = 0x00000FFF 42 onlyNGlobalEvents uint32 = 0x0FF00000 43 onlyMachineStates uint32 = 0xF0000000 44 ) 45 46 // grip represents a single watched directory. It stores the data required by 47 // ReadDirectoryChangesW function. Only the filter, recursive, and handle members 48 // may by modified by watcher implementation. Rest of the them have to remain 49 // constant since they are used by Windows completion routine. This indicates that 50 // grip can be removed only when all operations on the file handle are finished. 51 type grip struct { 52 handle syscall.Handle 53 filter uint32 54 recursive bool 55 pathw []uint16 56 buffer [readBufferSize]byte 57 parent *watched 58 ovlapped *overlappedEx 59 } 60 61 // overlappedEx stores information used in asynchronous input and output. 62 // Additionally, overlappedEx contains a pointer to 'grip' item which is used in 63 // order to gather the structure in which the overlappedEx object was created. 64 type overlappedEx struct { 65 syscall.Overlapped 66 parent *grip 67 } 68 69 // newGrip creates a new file handle that can be used in overlapped operations. 70 // Then, the handle is associated with I/O completion port 'cph' and its value 71 // is stored in newly created 'grip' object. 72 func newGrip(cph syscall.Handle, parent *watched, filter uint32) (*grip, error) { 73 g := &grip{ 74 handle: syscall.InvalidHandle, 75 filter: filter, 76 recursive: parent.recursive, 77 pathw: parent.pathw, 78 parent: parent, 79 ovlapped: &overlappedEx{}, 80 } 81 if err := g.register(cph); err != nil { 82 return nil, err 83 } 84 g.ovlapped.parent = g 85 return g, nil 86 } 87 88 // NOTE : Thread safe 89 func (g *grip) register(cph syscall.Handle) (err error) { 90 if g.handle, err = syscall.CreateFile( 91 &g.pathw[0], 92 syscall.FILE_LIST_DIRECTORY, 93 syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE, 94 nil, 95 syscall.OPEN_EXISTING, 96 syscall.FILE_FLAG_BACKUP_SEMANTICS|syscall.FILE_FLAG_OVERLAPPED, 97 0, 98 ); err != nil { 99 return 100 } 101 if _, err = syscall.CreateIoCompletionPort(g.handle, cph, 0, 0); err != nil { 102 syscall.CloseHandle(g.handle) 103 return 104 } 105 return g.readDirChanges() 106 } 107 108 // readDirChanges tells the system to store file change information in grip's 109 // buffer. Directory changes that occur between calls to this function are added 110 // to the buffer and then, returned with the next call. 111 func (g *grip) readDirChanges() error { 112 handle := syscall.Handle(atomic.LoadUintptr((*uintptr)(&g.handle))) 113 if handle == syscall.InvalidHandle { 114 return nil // Handle was closed. 115 } 116 117 return syscall.ReadDirectoryChanges( 118 handle, 119 &g.buffer[0], 120 uint32(unsafe.Sizeof(g.buffer)), 121 g.recursive, 122 encode(g.filter), 123 nil, 124 (*syscall.Overlapped)(unsafe.Pointer(g.ovlapped)), 125 0, 126 ) 127 } 128 129 // encode transforms a generic filter, which contains platform independent and 130 // implementation specific bit fields, to value that can be used as NotifyFilter 131 // parameter in ReadDirectoryChangesW function. 132 func encode(filter uint32) uint32 { 133 e := Event(filter & (onlyNGlobalEvents | onlyNotifyChanges)) 134 if e&dirmarker != 0 { 135 return uint32(FileNotifyChangeDirName) 136 } 137 if e&Create != 0 { 138 e = (e ^ Create) | FileNotifyChangeFileName 139 } 140 if e&Remove != 0 { 141 e = (e ^ Remove) | FileNotifyChangeFileName 142 } 143 if e&Write != 0 { 144 e = (e ^ Write) | FileNotifyChangeAttributes | FileNotifyChangeSize | 145 FileNotifyChangeCreation | FileNotifyChangeSecurity 146 } 147 if e&Rename != 0 { 148 e = (e ^ Rename) | FileNotifyChangeFileName 149 } 150 return uint32(e) 151 } 152 153 // watched is made in order to check whether an action comes from a directory or 154 // file. This approach requires two file handlers per single monitored folder. The 155 // second grip handles actions which include creating or deleting a directory. If 156 // these processes are not monitored, only the first grip is created. 157 type watched struct { 158 filter uint32 159 recursive bool 160 count uint8 161 pathw []uint16 162 digrip [2]*grip 163 } 164 165 // newWatched creates a new watched instance. It splits the filter variable into 166 // two parts. The first part is responsible for watching all events which can be 167 // created for a file in watched directory structure and the second one watches 168 // only directory Create/Remove actions. If all operations succeed, the Create 169 // message is sent to I/O completion port queue for further processing. 170 func newWatched(cph syscall.Handle, filter uint32, recursive bool, 171 path string) (wd *watched, err error) { 172 wd = &watched{ 173 filter: filter, 174 recursive: recursive, 175 } 176 if wd.pathw, err = syscall.UTF16FromString(path); err != nil { 177 return 178 } 179 if err = wd.recreate(cph); err != nil { 180 return 181 } 182 return wd, nil 183 } 184 185 // TODO : doc 186 func (wd *watched) recreate(cph syscall.Handle) (err error) { 187 filefilter := wd.filter &^ uint32(FileNotifyChangeDirName) 188 if err = wd.updateGrip(0, cph, filefilter == 0, filefilter); err != nil { 189 return 190 } 191 dirfilter := wd.filter & uint32(FileNotifyChangeDirName|Create|Remove) 192 if err = wd.updateGrip(1, cph, dirfilter == 0, wd.filter|uint32(dirmarker)); err != nil { 193 return 194 } 195 wd.filter &^= onlyMachineStates 196 return 197 } 198 199 // TODO : doc 200 func (wd *watched) updateGrip(idx int, cph syscall.Handle, reset bool, 201 newflag uint32) (err error) { 202 if reset { 203 wd.digrip[idx] = nil 204 } else { 205 if wd.digrip[idx] == nil { 206 if wd.digrip[idx], err = newGrip(cph, wd, newflag); err != nil { 207 wd.closeHandle() 208 return 209 } 210 } else { 211 wd.digrip[idx].filter = newflag 212 wd.digrip[idx].recursive = wd.recursive 213 if err = wd.digrip[idx].register(cph); err != nil { 214 wd.closeHandle() 215 return 216 } 217 } 218 wd.count++ 219 } 220 return 221 } 222 223 // closeHandle closes handles that are stored in digrip array. Function always 224 // tries to close all of the handlers before it exits, even when there are errors 225 // returned from the operating system kernel. 226 func (wd *watched) closeHandle() (err error) { 227 for _, g := range wd.digrip { 228 if g == nil { 229 continue 230 } 231 232 for { 233 handle := syscall.Handle(atomic.LoadUintptr((*uintptr)(&g.handle))) 234 if handle == syscall.InvalidHandle { 235 break // Already closed. 236 } 237 238 e := syscall.CloseHandle(handle) 239 if e != nil && err == nil { 240 err = e 241 } 242 243 // Set invalid handle even when CloseHandle fails. This will leak 244 // the handle but, since we can't close it anyway, there won't be 245 // any difference. 246 if atomic.CompareAndSwapUintptr((*uintptr)(&g.handle), 247 (uintptr)(handle), (uintptr)(syscall.InvalidHandle)) { 248 break 249 } 250 } 251 } 252 return 253 } 254 255 // watcher implements Watcher interface. It stores a set of watched directories. 256 // All operations which remove watched objects from map `m` must be performed in 257 // loop goroutine since these structures are used internally by operating system. 258 type readdcw struct { 259 sync.Mutex 260 m map[string]*watched 261 cph syscall.Handle 262 start bool 263 wg sync.WaitGroup 264 c chan<- EventInfo 265 } 266 267 // NewWatcher creates new non-recursive watcher backed by ReadDirectoryChangesW. 268 func newWatcher(c chan<- EventInfo) watcher { 269 r := &readdcw{ 270 m: make(map[string]*watched), 271 cph: syscall.InvalidHandle, 272 c: c, 273 } 274 runtime.SetFinalizer(r, func(r *readdcw) { 275 if r.cph != syscall.InvalidHandle { 276 syscall.CloseHandle(r.cph) 277 } 278 }) 279 return r 280 } 281 282 // Watch implements notify.Watcher interface. 283 func (r *readdcw) Watch(path string, event Event) error { 284 return r.watch(path, event, false) 285 } 286 287 // RecursiveWatch implements notify.RecursiveWatcher interface. 288 func (r *readdcw) RecursiveWatch(path string, event Event) error { 289 return r.watch(path, event, true) 290 } 291 292 // watch inserts a directory to the group of watched folders. If watched folder 293 // already exists, function tries to rewatch it with new filters(NOT VALID). Moreover, 294 // watch starts the main event loop goroutine when called for the first time. 295 func (r *readdcw) watch(path string, event Event, recursive bool) error { 296 if event&^(All|fileNotifyChangeAll) != 0 { 297 return errors.New("notify: unknown event") 298 } 299 300 r.Lock() 301 defer r.Unlock() 302 303 if wd, ok := r.m[path]; ok { 304 dbgprint("watch: already exists") 305 wd.filter &^= stateUnwatch 306 return nil 307 } 308 309 if err := r.lazyinit(); err != nil { 310 return err 311 } 312 313 wd, err := newWatched(r.cph, uint32(event), recursive, path) 314 if err != nil { 315 return err 316 } 317 318 r.m[path] = wd 319 dbgprint("watch: new watch added") 320 321 return nil 322 } 323 324 // lazyinit creates an I/O completion port and starts the main event loop. 325 func (r *readdcw) lazyinit() (err error) { 326 invalid := uintptr(syscall.InvalidHandle) 327 328 if atomic.LoadUintptr((*uintptr)(&r.cph)) == invalid { 329 cph := syscall.InvalidHandle 330 if cph, err = syscall.CreateIoCompletionPort(cph, 0, 0, 0); err != nil { 331 return 332 } 333 334 r.cph, r.start = cph, true 335 go r.loop() 336 } 337 338 return 339 } 340 341 // TODO(pknap) : doc 342 func (r *readdcw) loop() { 343 var n, key uint32 344 var overlapped *syscall.Overlapped 345 for { 346 err := syscall.GetQueuedCompletionStatus(r.cph, &n, &key, &overlapped, syscall.INFINITE) 347 if key == stateCPClose { 348 r.Lock() 349 handle := r.cph 350 r.cph = syscall.InvalidHandle 351 r.Unlock() 352 syscall.CloseHandle(handle) 353 r.wg.Done() 354 return 355 } 356 if overlapped == nil { 357 // TODO: check key == rewatch delete or 0(panic) 358 continue 359 } 360 overEx := (*overlappedEx)(unsafe.Pointer(overlapped)) 361 if overEx == nil || overEx.parent == nil { 362 dbgprintf("incomplete completion status transferred=%d, overlapped=%#v, key=%#b", n, overEx, key) 363 continue 364 } else if n != 0 { 365 r.loopevent(n, overEx) 366 } 367 if err = overEx.parent.readDirChanges(); err != nil { 368 // TODO: error handling 369 } 370 r.loopstate(overEx) 371 } 372 } 373 374 // TODO(pknap) : doc 375 func (r *readdcw) loopstate(overEx *overlappedEx) { 376 r.Lock() 377 defer r.Unlock() 378 filter := overEx.parent.parent.filter 379 if filter&onlyMachineStates == 0 { 380 return 381 } 382 if overEx.parent.parent.count--; overEx.parent.parent.count == 0 { 383 switch filter & onlyMachineStates { 384 case stateRewatch: 385 dbgprint("loopstate rewatch") 386 overEx.parent.parent.recreate(r.cph) 387 case stateUnwatch: 388 dbgprint("loopstate unwatch") 389 overEx.parent.parent.closeHandle() 390 delete(r.m, syscall.UTF16ToString(overEx.parent.pathw)) 391 case stateCPClose: 392 default: 393 panic(`notify: windows loopstate logic error`) 394 } 395 } 396 } 397 398 // TODO(pknap) : doc 399 func (r *readdcw) loopevent(n uint32, overEx *overlappedEx) { 400 events := []*event{} 401 var currOffset uint32 402 for { 403 raw := (*syscall.FileNotifyInformation)(unsafe.Pointer(&overEx.parent.buffer[currOffset])) 404 name := syscall.UTF16ToString((*[syscall.MAX_LONG_PATH]uint16)(unsafe.Pointer(&raw.FileName))[:raw.FileNameLength>>1]) 405 events = append(events, &event{ 406 pathw: overEx.parent.pathw, 407 filter: overEx.parent.filter, 408 action: raw.Action, 409 name: name, 410 }) 411 if raw.NextEntryOffset == 0 { 412 break 413 } 414 if currOffset += raw.NextEntryOffset; currOffset >= n { 415 break 416 } 417 } 418 r.send(events) 419 } 420 421 // TODO(pknap) : doc 422 func (r *readdcw) send(es []*event) { 423 for _, e := range es { 424 var syse Event 425 if e.e, syse = decode(e.filter, e.action); e.e == 0 && syse == 0 { 426 continue 427 } 428 switch { 429 case e.action == syscall.FILE_ACTION_MODIFIED: 430 e.ftype = fTypeUnknown 431 case e.filter&uint32(dirmarker) != 0: 432 e.ftype = fTypeDirectory 433 default: 434 e.ftype = fTypeFile 435 } 436 switch { 437 case e.e == 0: 438 e.e = syse 439 case syse != 0: 440 r.c <- &event{ 441 pathw: e.pathw, 442 name: e.name, 443 ftype: e.ftype, 444 action: e.action, 445 filter: e.filter, 446 e: syse, 447 } 448 } 449 r.c <- e 450 } 451 } 452 453 // Rewatch implements notify.Rewatcher interface. 454 func (r *readdcw) Rewatch(path string, oldevent, newevent Event) error { 455 return r.rewatch(path, uint32(oldevent), uint32(newevent), false) 456 } 457 458 // RecursiveRewatch implements notify.RecursiveRewatcher interface. 459 func (r *readdcw) RecursiveRewatch(oldpath, newpath string, oldevent, 460 newevent Event) error { 461 if oldpath != newpath { 462 if err := r.unwatch(oldpath); err != nil { 463 return err 464 } 465 return r.watch(newpath, newevent, true) 466 } 467 return r.rewatch(newpath, uint32(oldevent), uint32(newevent), true) 468 } 469 470 // TODO : (pknap) doc. 471 func (r *readdcw) rewatch(path string, oldevent, newevent uint32, recursive bool) (err error) { 472 if Event(newevent)&^(All|fileNotifyChangeAll) != 0 { 473 return errors.New("notify: unknown event") 474 } 475 var wd *watched 476 r.Lock() 477 defer r.Unlock() 478 if wd, err = r.nonStateWatchedLocked(path); err != nil { 479 return 480 } 481 if wd.filter&(onlyNotifyChanges|onlyNGlobalEvents) != oldevent { 482 panic(`notify: windows re-watcher logic error`) 483 } 484 wd.filter = stateRewatch | newevent 485 wd.recursive, recursive = recursive, wd.recursive 486 if err = wd.closeHandle(); err != nil { 487 wd.filter = oldevent 488 wd.recursive = recursive 489 return 490 } 491 return 492 } 493 494 // TODO : pknap 495 func (r *readdcw) nonStateWatchedLocked(path string) (wd *watched, err error) { 496 wd, ok := r.m[path] 497 if !ok || wd == nil { 498 err = errors.New(`notify: ` + path + ` path is unwatched`) 499 return 500 } 501 if wd.filter&onlyMachineStates != 0 { 502 err = errors.New(`notify: another re/unwatching operation in progress`) 503 return 504 } 505 return 506 } 507 508 // Unwatch implements notify.Watcher interface. 509 func (r *readdcw) Unwatch(path string) error { 510 return r.unwatch(path) 511 } 512 513 // RecursiveUnwatch implements notify.RecursiveWatcher interface. 514 func (r *readdcw) RecursiveUnwatch(path string) error { 515 return r.unwatch(path) 516 } 517 518 // TODO : pknap 519 func (r *readdcw) unwatch(path string) (err error) { 520 var wd *watched 521 522 r.Lock() 523 defer r.Unlock() 524 if wd, err = r.nonStateWatchedLocked(path); err != nil { 525 return 526 } 527 528 wd.filter |= stateUnwatch 529 dbgprint("unwatch: set unwatch state") 530 531 if _, attrErr := syscall.GetFileAttributes(&wd.pathw[0]); attrErr != nil { 532 for _, g := range wd.digrip { 533 if g == nil { 534 continue 535 } 536 537 dbgprint("unwatch: posting") 538 if err = syscall.PostQueuedCompletionStatus(r.cph, 0, 0, (*syscall.Overlapped)(unsafe.Pointer(g.ovlapped))); err != nil { 539 wd.filter &^= stateUnwatch 540 return 541 } 542 } 543 } 544 545 return 546 } 547 548 // Close resets the whole watcher object, closes all existing file descriptors, 549 // and sends stateCPClose state as completion key to the main watcher's loop. 550 func (r *readdcw) Close() (err error) { 551 r.Lock() 552 if !r.start { 553 r.Unlock() 554 return nil 555 } 556 for _, wd := range r.m { 557 wd.filter &^= onlyMachineStates 558 wd.filter |= stateCPClose 559 if e := wd.closeHandle(); e != nil && err == nil { 560 err = e 561 } 562 } 563 r.start = false 564 r.Unlock() 565 r.wg.Add(1) 566 if e := syscall.PostQueuedCompletionStatus(r.cph, 0, stateCPClose, nil); e != nil && err == nil { 567 return e 568 } 569 r.wg.Wait() 570 return 571 } 572 573 // decode creates a notify event from both non-raw filter and action which was 574 // returned from completion routine. Function may return Event(0) in case when 575 // filter was replaced by a new value which does not contain fields that are 576 // valid with passed action. 577 func decode(filter, action uint32) (Event, Event) { 578 switch action { 579 case syscall.FILE_ACTION_ADDED: 580 return gensys(filter, Create, FileActionAdded) 581 case syscall.FILE_ACTION_REMOVED: 582 return gensys(filter, Remove, FileActionRemoved) 583 case syscall.FILE_ACTION_MODIFIED: 584 return gensys(filter, Write, FileActionModified) 585 case syscall.FILE_ACTION_RENAMED_OLD_NAME: 586 return gensys(filter, Rename, FileActionRenamedOldName) 587 case syscall.FILE_ACTION_RENAMED_NEW_NAME: 588 return gensys(filter, Rename, FileActionRenamedNewName) 589 } 590 dbgprintf("cannot decode internal mask: %d", action) 591 592 return 0, 0 593 } 594 595 // gensys decides whether the Windows action, system-independent event or both 596 // of them should be returned. Since the grip's filter may be atomically changed 597 // during watcher lifetime, it is possible that neither Windows nor notify masks 598 // are watched by the user when this function is called. 599 func gensys(filter uint32, ge, se Event) (gene, syse Event) { 600 isdir := filter&uint32(dirmarker) != 0 601 if isdir && filter&uint32(FileNotifyChangeDirName) != 0 || 602 !isdir && filter&uint32(FileNotifyChangeFileName) != 0 || 603 filter&uint32(fileNotifyChangeModified) != 0 { 604 syse = se 605 } 606 if filter&uint32(ge) != 0 { 607 gene = ge 608 } 609 return 610 }