github.com/mattolenik/notify@v0.9.1/watcher_readdcw.go (about) 1 // Copyright (c) 2014-2018 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 n != 0 { 362 r.loopevent(n, overEx) 363 if err = overEx.parent.readDirChanges(); err != nil { 364 // TODO: error handling 365 } 366 } 367 r.loopstate(overEx) 368 } 369 } 370 371 // TODO(pknap) : doc 372 func (r *readdcw) loopstate(overEx *overlappedEx) { 373 r.Lock() 374 defer r.Unlock() 375 filter := overEx.parent.parent.filter 376 if filter&onlyMachineStates == 0 { 377 return 378 } 379 if overEx.parent.parent.count--; overEx.parent.parent.count == 0 { 380 switch filter & onlyMachineStates { 381 case stateRewatch: 382 dbgprint("loopstate rewatch") 383 overEx.parent.parent.recreate(r.cph) 384 case stateUnwatch: 385 dbgprint("loopstate unwatch") 386 overEx.parent.parent.closeHandle() 387 delete(r.m, syscall.UTF16ToString(overEx.parent.pathw)) 388 case stateCPClose: 389 default: 390 panic(`notify: windows loopstate logic error`) 391 } 392 } 393 } 394 395 // TODO(pknap) : doc 396 func (r *readdcw) loopevent(n uint32, overEx *overlappedEx) { 397 events := []*event{} 398 var currOffset uint32 399 for { 400 raw := (*syscall.FileNotifyInformation)(unsafe.Pointer(&overEx.parent.buffer[currOffset])) 401 name := syscall.UTF16ToString((*[syscall.MAX_LONG_PATH]uint16)(unsafe.Pointer(&raw.FileName))[:raw.FileNameLength>>1]) 402 events = append(events, &event{ 403 pathw: overEx.parent.pathw, 404 filter: overEx.parent.filter, 405 action: raw.Action, 406 name: name, 407 }) 408 if raw.NextEntryOffset == 0 { 409 break 410 } 411 if currOffset += raw.NextEntryOffset; currOffset >= n { 412 break 413 } 414 } 415 r.send(events) 416 } 417 418 // TODO(pknap) : doc 419 func (r *readdcw) send(es []*event) { 420 for _, e := range es { 421 var syse Event 422 if e.e, syse = decode(e.filter, e.action); e.e == 0 && syse == 0 { 423 continue 424 } 425 switch { 426 case e.action == syscall.FILE_ACTION_MODIFIED: 427 e.ftype = fTypeUnknown 428 case e.filter&uint32(dirmarker) != 0: 429 e.ftype = fTypeDirectory 430 default: 431 e.ftype = fTypeFile 432 } 433 switch { 434 case e.e == 0: 435 e.e = syse 436 case syse != 0: 437 r.c <- &event{ 438 pathw: e.pathw, 439 name: e.name, 440 ftype: e.ftype, 441 action: e.action, 442 filter: e.filter, 443 e: syse, 444 } 445 } 446 r.c <- e 447 } 448 } 449 450 // Rewatch implements notify.Rewatcher interface. 451 func (r *readdcw) Rewatch(path string, oldevent, newevent Event) error { 452 return r.rewatch(path, uint32(oldevent), uint32(newevent), false) 453 } 454 455 // RecursiveRewatch implements notify.RecursiveRewatcher interface. 456 func (r *readdcw) RecursiveRewatch(oldpath, newpath string, oldevent, 457 newevent Event) error { 458 if oldpath != newpath { 459 if err := r.unwatch(oldpath); err != nil { 460 return err 461 } 462 return r.watch(newpath, newevent, true) 463 } 464 return r.rewatch(newpath, uint32(oldevent), uint32(newevent), true) 465 } 466 467 // TODO : (pknap) doc. 468 func (r *readdcw) rewatch(path string, oldevent, newevent uint32, recursive bool) (err error) { 469 if Event(newevent)&^(All|fileNotifyChangeAll) != 0 { 470 return errors.New("notify: unknown event") 471 } 472 var wd *watched 473 r.Lock() 474 defer r.Unlock() 475 if wd, err = r.nonStateWatchedLocked(path); err != nil { 476 return 477 } 478 if wd.filter&(onlyNotifyChanges|onlyNGlobalEvents) != oldevent { 479 panic(`notify: windows re-watcher logic error`) 480 } 481 wd.filter = stateRewatch | newevent 482 wd.recursive, recursive = recursive, wd.recursive 483 if err = wd.closeHandle(); err != nil { 484 wd.filter = oldevent 485 wd.recursive = recursive 486 return 487 } 488 return 489 } 490 491 // TODO : pknap 492 func (r *readdcw) nonStateWatchedLocked(path string) (wd *watched, err error) { 493 wd, ok := r.m[path] 494 if !ok || wd == nil { 495 err = errors.New(`notify: ` + path + ` path is unwatched`) 496 return 497 } 498 if wd.filter&onlyMachineStates != 0 { 499 err = errors.New(`notify: another re/unwatching operation in progress`) 500 return 501 } 502 return 503 } 504 505 // Unwatch implements notify.Watcher interface. 506 func (r *readdcw) Unwatch(path string) error { 507 return r.unwatch(path) 508 } 509 510 // RecursiveUnwatch implements notify.RecursiveWatcher interface. 511 func (r *readdcw) RecursiveUnwatch(path string) error { 512 return r.unwatch(path) 513 } 514 515 // TODO : pknap 516 func (r *readdcw) unwatch(path string) (err error) { 517 var wd *watched 518 519 r.Lock() 520 defer r.Unlock() 521 if wd, err = r.nonStateWatchedLocked(path); err != nil { 522 return 523 } 524 525 wd.filter |= stateUnwatch 526 dbgprint("unwatch: set unwatch state") 527 528 if _, attrErr := syscall.GetFileAttributes(&wd.pathw[0]); attrErr != nil { 529 for _, g := range wd.digrip { 530 if g == nil { 531 continue 532 } 533 534 dbgprint("unwatch: posting") 535 if err = syscall.PostQueuedCompletionStatus(r.cph, 0, 0, (*syscall.Overlapped)(unsafe.Pointer(g.ovlapped))); err != nil { 536 wd.filter &^= stateUnwatch 537 return 538 } 539 } 540 } 541 542 return 543 } 544 545 // Close resets the whole watcher object, closes all existing file descriptors, 546 // and sends stateCPClose state as completion key to the main watcher's loop. 547 func (r *readdcw) Close() (err error) { 548 r.Lock() 549 if !r.start { 550 r.Unlock() 551 return nil 552 } 553 for _, wd := range r.m { 554 wd.filter &^= onlyMachineStates 555 wd.filter |= stateCPClose 556 if e := wd.closeHandle(); e != nil && err == nil { 557 err = e 558 } 559 } 560 r.start = false 561 r.Unlock() 562 r.wg.Add(1) 563 if e := syscall.PostQueuedCompletionStatus(r.cph, 0, stateCPClose, nil); e != nil && err == nil { 564 return e 565 } 566 r.wg.Wait() 567 return 568 } 569 570 // decode creates a notify event from both non-raw filter and action which was 571 // returned from completion routine. Function may return Event(0) in case when 572 // filter was replaced by a new value which does not contain fields that are 573 // valid with passed action. 574 func decode(filter, action uint32) (Event, Event) { 575 switch action { 576 case syscall.FILE_ACTION_ADDED: 577 return gensys(filter, Create, FileActionAdded) 578 case syscall.FILE_ACTION_REMOVED: 579 return gensys(filter, Remove, FileActionRemoved) 580 case syscall.FILE_ACTION_MODIFIED: 581 return gensys(filter, Write, FileActionModified) 582 case syscall.FILE_ACTION_RENAMED_OLD_NAME: 583 return gensys(filter, Rename, FileActionRenamedOldName) 584 case syscall.FILE_ACTION_RENAMED_NEW_NAME: 585 return gensys(filter, Rename, FileActionRenamedNewName) 586 } 587 panic(`notify: cannot decode internal mask`) 588 } 589 590 // gensys decides whether the Windows action, system-independent event or both 591 // of them should be returned. Since the grip's filter may be atomically changed 592 // during watcher lifetime, it is possible that neither Windows nor notify masks 593 // are watched by the user when this function is called. 594 func gensys(filter uint32, ge, se Event) (gene, syse Event) { 595 isdir := filter&uint32(dirmarker) != 0 596 if isdir && filter&uint32(FileNotifyChangeDirName) != 0 || 597 !isdir && filter&uint32(FileNotifyChangeFileName) != 0 || 598 filter&uint32(fileNotifyChangeModified) != 0 { 599 syse = se 600 } 601 if filter&uint32(ge) != 0 { 602 gene = ge 603 } 604 return 605 }