github.com/checksum/notify@v0.0.0-20190119234841-59aa2d88664f/watcher_readdcw.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 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 belor. 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 return syscall.ReadDirectoryChanges( 113 g.handle, 114 &g.buffer[0], 115 uint32(unsafe.Sizeof(g.buffer)), 116 g.recursive, 117 encode(g.filter), 118 nil, 119 (*syscall.Overlapped)(unsafe.Pointer(g.ovlapped)), 120 0, 121 ) 122 } 123 124 // encode transforms a generic filter, which contains platform independent and 125 // implementation specific bit fields, to value that can be used as NotifyFilter 126 // parameter in ReadDirectoryChangesW function. 127 func encode(filter uint32) uint32 { 128 e := Event(filter & (onlyNGlobalEvents | onlyNotifyChanges)) 129 if e&dirmarker != 0 { 130 return uint32(FileNotifyChangeDirName) 131 } 132 if e&Create != 0 { 133 e = (e ^ Create) | FileNotifyChangeFileName 134 } 135 if e&Remove != 0 { 136 e = (e ^ Remove) | FileNotifyChangeFileName 137 } 138 if e&Write != 0 { 139 e = (e ^ Write) | FileNotifyChangeAttributes | FileNotifyChangeSize | 140 FileNotifyChangeCreation | FileNotifyChangeSecurity 141 } 142 if e&Rename != 0 { 143 e = (e ^ Rename) | FileNotifyChangeFileName 144 } 145 return uint32(e) 146 } 147 148 // watched is made in order to check whether an action comes from a directory or 149 // file. This approach requires two file handlers per single monitored folder. The 150 // second grip handles actions which include creating or deleting a directory. If 151 // these processes are not monitored, only the first grip is created. 152 type watched struct { 153 filter uint32 154 recursive bool 155 count uint8 156 pathw []uint16 157 digrip [2]*grip 158 } 159 160 // newWatched creates a new watched instance. It splits the filter variable into 161 // two parts. The first part is responsible for watching all events which can be 162 // created for a file in watched directory structure and the second one watches 163 // only directory Create/Remove actions. If all operations succeed, the Create 164 // message is sent to I/O completion port queue for further processing. 165 func newWatched(cph syscall.Handle, filter uint32, recursive bool, 166 path string) (wd *watched, err error) { 167 wd = &watched{ 168 filter: filter, 169 recursive: recursive, 170 } 171 if wd.pathw, err = syscall.UTF16FromString(path); err != nil { 172 return 173 } 174 if err = wd.recreate(cph); err != nil { 175 return 176 } 177 return wd, nil 178 } 179 180 // TODO : doc 181 func (wd *watched) recreate(cph syscall.Handle) (err error) { 182 filefilter := wd.filter &^ uint32(FileNotifyChangeDirName) 183 if err = wd.updateGrip(0, cph, filefilter == 0, filefilter); err != nil { 184 return 185 } 186 dirfilter := wd.filter & uint32(FileNotifyChangeDirName|Create|Remove) 187 if err = wd.updateGrip(1, cph, dirfilter == 0, wd.filter|uint32(dirmarker)); err != nil { 188 return 189 } 190 wd.filter &^= onlyMachineStates 191 return 192 } 193 194 // TODO : doc 195 func (wd *watched) updateGrip(idx int, cph syscall.Handle, reset bool, 196 newflag uint32) (err error) { 197 if reset { 198 wd.digrip[idx] = nil 199 } else { 200 if wd.digrip[idx] == nil { 201 if wd.digrip[idx], err = newGrip(cph, wd, newflag); err != nil { 202 wd.closeHandle() 203 return 204 } 205 } else { 206 wd.digrip[idx].filter = newflag 207 wd.digrip[idx].recursive = wd.recursive 208 if err = wd.digrip[idx].register(cph); err != nil { 209 wd.closeHandle() 210 return 211 } 212 } 213 wd.count++ 214 } 215 return 216 } 217 218 // closeHandle closes handles that are stored in digrip array. Function always 219 // tries to close all of the handlers before it exits, even when there are errors 220 // returned from the operating system kernel. 221 func (wd *watched) closeHandle() (err error) { 222 for _, g := range wd.digrip { 223 if g != nil && g.handle != syscall.InvalidHandle { 224 switch suberr := syscall.CloseHandle(g.handle); { 225 case suberr == nil: 226 g.handle = syscall.InvalidHandle 227 case err == nil: 228 err = suberr 229 } 230 } 231 } 232 return 233 } 234 235 // watcher implements Watcher interface. It stores a set of watched directories. 236 // All operations which remove watched objects from map `m` must be performed in 237 // loop goroutine since these structures are used internally by operating system. 238 type readdcw struct { 239 sync.Mutex 240 m map[string]*watched 241 cph syscall.Handle 242 start bool 243 wg sync.WaitGroup 244 c chan<- EventInfo 245 } 246 247 // NewWatcher creates new non-recursive watcher backed by ReadDirectoryChangesW. 248 func newWatcher(c chan<- EventInfo) watcher { 249 r := &readdcw{ 250 m: make(map[string]*watched), 251 cph: syscall.InvalidHandle, 252 c: c, 253 } 254 runtime.SetFinalizer(r, func(r *readdcw) { 255 if r.cph != syscall.InvalidHandle { 256 syscall.CloseHandle(r.cph) 257 } 258 }) 259 return r 260 } 261 262 // Watch implements notify.Watcher interface. 263 func (r *readdcw) Watch(path string, event Event) error { 264 return r.watch(path, event, false) 265 } 266 267 // RecursiveWatch implements notify.RecursiveWatcher interface. 268 func (r *readdcw) RecursiveWatch(path string, event Event) error { 269 return r.watch(path, event, true) 270 } 271 272 // watch inserts a directory to the group of watched folders. If watched folder 273 // already exists, function tries to rewatch it with new filters(NOT VALID). Moreover, 274 // watch starts the main event loop goroutine when called for the first time. 275 func (r *readdcw) watch(path string, event Event, recursive bool) (err error) { 276 if event&^(All|fileNotifyChangeAll) != 0 { 277 return errors.New("notify: unknown event") 278 } 279 r.Lock() 280 wd, ok := r.m[path] 281 r.Unlock() 282 if !ok { 283 if err = r.lazyinit(); err != nil { 284 return 285 } 286 r.Lock() 287 defer r.Unlock() 288 if wd, ok = r.m[path]; ok { 289 dbgprint("watch: exists already") 290 return 291 } 292 if wd, err = newWatched(r.cph, uint32(event), recursive, path); err != nil { 293 return 294 } 295 r.m[path] = wd 296 dbgprint("watch: new watch added") 297 } else { 298 dbgprint("watch: exists already") 299 } 300 return nil 301 } 302 303 // lazyinit creates an I/O completion port and starts the main event processing 304 // loop. This method uses Double-Checked Locking optimization. 305 func (r *readdcw) lazyinit() (err error) { 306 invalid := uintptr(syscall.InvalidHandle) 307 if atomic.LoadUintptr((*uintptr)(&r.cph)) == invalid { 308 r.Lock() 309 defer r.Unlock() 310 if atomic.LoadUintptr((*uintptr)(&r.cph)) == invalid { 311 cph := syscall.InvalidHandle 312 if cph, err = syscall.CreateIoCompletionPort(cph, 0, 0, 0); err != nil { 313 return 314 } 315 r.cph, r.start = cph, true 316 go r.loop() 317 } 318 } 319 return 320 } 321 322 // TODO(pknap) : doc 323 func (r *readdcw) loop() { 324 var n, key uint32 325 var overlapped *syscall.Overlapped 326 for { 327 err := syscall.GetQueuedCompletionStatus(r.cph, &n, &key, &overlapped, syscall.INFINITE) 328 if key == stateCPClose { 329 r.Lock() 330 handle := r.cph 331 r.cph = syscall.InvalidHandle 332 r.Unlock() 333 syscall.CloseHandle(handle) 334 r.wg.Done() 335 return 336 } 337 if overlapped == nil { 338 // TODO: check key == rewatch delete or 0(panic) 339 continue 340 } 341 overEx := (*overlappedEx)(unsafe.Pointer(overlapped)) 342 if n != 0 { 343 r.loopevent(n, overEx) 344 if err = overEx.parent.readDirChanges(); err != nil { 345 // TODO: error handling 346 } 347 } 348 r.loopstate(overEx) 349 } 350 } 351 352 // TODO(pknap) : doc 353 func (r *readdcw) loopstate(overEx *overlappedEx) { 354 r.Lock() 355 defer r.Unlock() 356 filter := overEx.parent.parent.filter 357 if filter&onlyMachineStates == 0 { 358 return 359 } 360 if overEx.parent.parent.count--; overEx.parent.parent.count == 0 { 361 switch filter & onlyMachineStates { 362 case stateRewatch: 363 dbgprint("loopstate rewatch") 364 overEx.parent.parent.recreate(r.cph) 365 case stateUnwatch: 366 dbgprint("loopstate unwatch") 367 delete(r.m, syscall.UTF16ToString(overEx.parent.pathw)) 368 case stateCPClose: 369 default: 370 panic(`notify: windows loopstate logic error`) 371 } 372 } 373 } 374 375 // TODO(pknap) : doc 376 func (r *readdcw) loopevent(n uint32, overEx *overlappedEx) { 377 events := []*event{} 378 var currOffset uint32 379 for { 380 raw := (*syscall.FileNotifyInformation)(unsafe.Pointer(&overEx.parent.buffer[currOffset])) 381 name := syscall.UTF16ToString((*[syscall.MAX_LONG_PATH]uint16)(unsafe.Pointer(&raw.FileName))[:raw.FileNameLength>>1]) 382 events = append(events, &event{ 383 pathw: overEx.parent.pathw, 384 filter: overEx.parent.filter, 385 action: raw.Action, 386 name: name, 387 }) 388 if raw.NextEntryOffset == 0 { 389 break 390 } 391 if currOffset += raw.NextEntryOffset; currOffset >= n { 392 break 393 } 394 } 395 r.send(events) 396 } 397 398 // TODO(pknap) : doc 399 func (r *readdcw) send(es []*event) { 400 for _, e := range es { 401 var syse Event 402 if e.e, syse = decode(e.filter, e.action); e.e == 0 && syse == 0 { 403 continue 404 } 405 switch { 406 case e.action == syscall.FILE_ACTION_MODIFIED: 407 e.ftype = fTypeUnknown 408 case e.filter&uint32(dirmarker) != 0: 409 e.ftype = fTypeDirectory 410 default: 411 e.ftype = fTypeFile 412 } 413 switch { 414 case e.e == 0: 415 e.e = syse 416 case syse != 0: 417 r.c <- &event{ 418 pathw: e.pathw, 419 name: e.name, 420 ftype: e.ftype, 421 action: e.action, 422 filter: e.filter, 423 e: syse, 424 } 425 } 426 r.c <- e 427 } 428 } 429 430 // Rewatch implements notify.Rewatcher interface. 431 func (r *readdcw) Rewatch(path string, oldevent, newevent Event) error { 432 return r.rewatch(path, uint32(oldevent), uint32(newevent), false) 433 } 434 435 // RecursiveRewatch implements notify.RecursiveRewatcher interface. 436 func (r *readdcw) RecursiveRewatch(oldpath, newpath string, oldevent, 437 newevent Event) error { 438 if oldpath != newpath { 439 if err := r.unwatch(oldpath); err != nil { 440 return err 441 } 442 return r.watch(newpath, newevent, true) 443 } 444 return r.rewatch(newpath, uint32(oldevent), uint32(newevent), true) 445 } 446 447 // TODO : (pknap) doc. 448 func (r *readdcw) rewatch(path string, oldevent, newevent uint32, recursive bool) (err error) { 449 if Event(newevent)&^(All|fileNotifyChangeAll) != 0 { 450 return errors.New("notify: unknown event") 451 } 452 var wd *watched 453 r.Lock() 454 defer r.Unlock() 455 if wd, err = r.nonStateWatchedLocked(path); err != nil { 456 return 457 } 458 if wd.filter&(onlyNotifyChanges|onlyNGlobalEvents) != oldevent { 459 panic(`notify: windows re-watcher logic error`) 460 } 461 wd.filter = stateRewatch | newevent 462 wd.recursive, recursive = recursive, wd.recursive 463 if err = wd.closeHandle(); err != nil { 464 wd.filter = oldevent 465 wd.recursive = recursive 466 return 467 } 468 return 469 } 470 471 // TODO : pknap 472 func (r *readdcw) nonStateWatchedLocked(path string) (wd *watched, err error) { 473 wd, ok := r.m[path] 474 if !ok || wd == nil { 475 err = errors.New(`notify: ` + path + ` path is unwatched`) 476 return 477 } 478 if wd.filter&onlyMachineStates != 0 { 479 err = errors.New(`notify: another re/unwatching operation in progress`) 480 return 481 } 482 return 483 } 484 485 // Unwatch implements notify.Watcher interface. 486 func (r *readdcw) Unwatch(path string) error { 487 return r.unwatch(path) 488 } 489 490 // RecursiveUnwatch implements notify.RecursiveWatcher interface. 491 func (r *readdcw) RecursiveUnwatch(path string) error { 492 return r.unwatch(path) 493 } 494 495 // TODO : pknap 496 func (r *readdcw) unwatch(path string) (err error) { 497 var wd *watched 498 r.Lock() 499 defer r.Unlock() 500 if wd, err = r.nonStateWatchedLocked(path); err != nil { 501 return 502 } 503 wd.filter |= stateUnwatch 504 if err = wd.closeHandle(); err != nil { 505 wd.filter &^= stateUnwatch 506 return 507 } 508 if _, attrErr := syscall.GetFileAttributes(&wd.pathw[0]); attrErr != nil { 509 for _, g := range wd.digrip { 510 if g != nil { 511 dbgprint("unwatch: posting") 512 if err = syscall.PostQueuedCompletionStatus(r.cph, 0, 0, (*syscall.Overlapped)(unsafe.Pointer(g.ovlapped))); err != nil { 513 wd.filter &^= stateUnwatch 514 return 515 } 516 } 517 } 518 } 519 return 520 } 521 522 // Close resets the whole watcher object, closes all existing file descriptors, 523 // and sends stateCPClose state as completion key to the main watcher's loop. 524 func (r *readdcw) Close() (err error) { 525 r.Lock() 526 if !r.start { 527 r.Unlock() 528 return nil 529 } 530 for _, wd := range r.m { 531 wd.filter &^= onlyMachineStates 532 wd.filter |= stateCPClose 533 if e := wd.closeHandle(); e != nil && err == nil { 534 err = e 535 } 536 } 537 r.start = false 538 r.Unlock() 539 r.wg.Add(1) 540 if e := syscall.PostQueuedCompletionStatus(r.cph, 0, stateCPClose, nil); e != nil && err == nil { 541 return e 542 } 543 r.wg.Wait() 544 return 545 } 546 547 // decode creates a notify event from both non-raw filter and action which was 548 // returned from completion routine. Function may return Event(0) in case when 549 // filter was replaced by a new value which does not contain fields that are 550 // valid with passed action. 551 func decode(filter, action uint32) (Event, Event) { 552 switch action { 553 case syscall.FILE_ACTION_ADDED: 554 return gensys(filter, Create, FileActionAdded) 555 case syscall.FILE_ACTION_REMOVED: 556 return gensys(filter, Remove, FileActionRemoved) 557 case syscall.FILE_ACTION_MODIFIED: 558 return gensys(filter, Write, FileActionModified) 559 case syscall.FILE_ACTION_RENAMED_OLD_NAME: 560 return gensys(filter, Rename, FileActionRenamedOldName) 561 case syscall.FILE_ACTION_RENAMED_NEW_NAME: 562 return gensys(filter, Rename, FileActionRenamedNewName) 563 } 564 panic(`notify: cannot decode internal mask`) 565 } 566 567 // gensys decides whether the Windows action, system-independent event or both 568 // of them should be returned. Since the grip's filter may be atomically changed 569 // during watcher lifetime, it is possible that neither Windows nor notify masks 570 // are watched by the user when this function is called. 571 func gensys(filter uint32, ge, se Event) (gene, syse Event) { 572 isdir := filter&uint32(dirmarker) != 0 573 if isdir && filter&uint32(FileNotifyChangeDirName) != 0 || 574 !isdir && filter&uint32(FileNotifyChangeFileName) != 0 || 575 filter&uint32(fileNotifyChangeModified) != 0 { 576 syse = se 577 } 578 if filter&uint32(ge) != 0 { 579 gene = ge 580 } 581 return 582 }