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