github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/exp/fsnotify/fsnotify_windows.go (about) 1 // Copyright 2011 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package fsnotify 6 7 import ( 8 "errors" 9 "fmt" 10 "os" 11 "path/filepath" 12 "runtime" 13 "sync" 14 "syscall" 15 "unsafe" 16 ) 17 18 const ( 19 // Options for AddWatch 20 sys_FS_ONESHOT = 0x80000000 21 sys_FS_ONLYDIR = 0x1000000 22 23 // Events 24 sys_FS_ACCESS = 0x1 25 sys_FS_ALL_EVENTS = 0xfff 26 sys_FS_ATTRIB = 0x4 27 sys_FS_CLOSE = 0x18 28 sys_FS_CREATE = 0x100 29 sys_FS_DELETE = 0x200 30 sys_FS_DELETE_SELF = 0x400 31 sys_FS_MODIFY = 0x2 32 sys_FS_MOVE = 0xc0 33 sys_FS_MOVED_FROM = 0x40 34 sys_FS_MOVED_TO = 0x80 35 sys_FS_MOVE_SELF = 0x800 36 37 // Special events 38 sys_FS_IGNORED = 0x8000 39 sys_FS_Q_OVERFLOW = 0x4000 40 ) 41 42 const ( 43 // TODO(nj): Use syscall.ERROR_MORE_DATA from ztypes_windows in Go 1.3+ 44 sys_ERROR_MORE_DATA syscall.Errno = 234 45 ) 46 47 // Event is the type of the notification messages 48 // received on the watcher's Event channel. 49 type FileEvent struct { 50 mask uint32 // Mask of events 51 cookie uint32 // Unique cookie associating related events (for rename) 52 Name string // File name (optional) 53 } 54 55 // IsCreate reports whether the FileEvent was triggered by a creation 56 func (e *FileEvent) IsCreate() bool { return (e.mask & sys_FS_CREATE) == sys_FS_CREATE } 57 58 // IsDelete reports whether the FileEvent was triggered by a delete 59 func (e *FileEvent) IsDelete() bool { 60 return ((e.mask&sys_FS_DELETE) == sys_FS_DELETE || (e.mask&sys_FS_DELETE_SELF) == sys_FS_DELETE_SELF) 61 } 62 63 // IsModify reports whether the FileEvent was triggered by a file modification or attribute change 64 func (e *FileEvent) IsModify() bool { 65 return ((e.mask&sys_FS_MODIFY) == sys_FS_MODIFY || (e.mask&sys_FS_ATTRIB) == sys_FS_ATTRIB) 66 } 67 68 // IsRename reports whether the FileEvent was triggered by a change name 69 func (e *FileEvent) IsRename() bool { 70 return ((e.mask&sys_FS_MOVE) == sys_FS_MOVE || (e.mask&sys_FS_MOVE_SELF) == sys_FS_MOVE_SELF || (e.mask&sys_FS_MOVED_FROM) == sys_FS_MOVED_FROM || (e.mask&sys_FS_MOVED_TO) == sys_FS_MOVED_TO) 71 } 72 73 // IsAttrib reports whether the FileEvent was triggered by a change in the file metadata. 74 func (e *FileEvent) IsAttrib() bool { 75 return (e.mask & sys_FS_ATTRIB) == sys_FS_ATTRIB 76 } 77 78 const ( 79 opAddWatch = iota 80 opRemoveWatch 81 ) 82 83 const ( 84 provisional uint64 = 1 << (32 + iota) 85 ) 86 87 type input struct { 88 op int 89 path string 90 flags uint32 91 reply chan error 92 } 93 94 type inode struct { 95 handle syscall.Handle 96 volume uint32 97 index uint64 98 } 99 100 type watch struct { 101 ov syscall.Overlapped 102 ino *inode // i-number 103 path string // Directory path 104 mask uint64 // Directory itself is being watched with these notify flags 105 names map[string]uint64 // Map of names being watched and their notify flags 106 rename string // Remembers the old name while renaming a file 107 buf [4096]byte 108 } 109 110 type indexMap map[uint64]*watch 111 type watchMap map[uint32]indexMap 112 113 // A Watcher waits for and receives event notifications 114 // for a specific set of files and directories. 115 type Watcher struct { 116 mu sync.Mutex // Map access 117 port syscall.Handle // Handle to completion port 118 watches watchMap // Map of watches (key: i-number) 119 input chan *input // Inputs to the reader are sent on this channel 120 Event chan *FileEvent // Events are returned on this channel 121 Error chan error // Errors are sent on this channel 122 isClosed bool // Set to true when Close() is first called 123 quit chan chan<- error 124 cookie uint32 125 } 126 127 // NewWatcher creates and returns a Watcher. 128 func NewWatcher() (*Watcher, error) { 129 port, e := syscall.CreateIoCompletionPort(syscall.InvalidHandle, 0, 0, 0) 130 if e != nil { 131 return nil, os.NewSyscallError("CreateIoCompletionPort", e) 132 } 133 w := &Watcher{ 134 port: port, 135 watches: make(watchMap), 136 input: make(chan *input, 1), 137 Event: make(chan *FileEvent, 50), 138 Error: make(chan error), 139 quit: make(chan chan<- error, 1), 140 } 141 go w.readEvents() 142 return w, nil 143 } 144 145 // Close closes a Watcher. 146 // It sends a message to the reader goroutine to quit and removes all watches 147 // associated with the watcher. 148 func (w *Watcher) Close() error { 149 if w.isClosed { 150 return nil 151 } 152 w.isClosed = true 153 154 // Send "quit" message to the reader goroutine 155 ch := make(chan error) 156 w.quit <- ch 157 if err := w.wakeupReader(); err != nil { 158 return err 159 } 160 return <-ch 161 } 162 163 // AddWatch adds path to the watched file set. 164 func (w *Watcher) AddWatch(path string, flags uint32) error { 165 if w.isClosed { 166 return errors.New("watcher already closed") 167 } 168 in := &input{ 169 op: opAddWatch, 170 path: filepath.Clean(path), 171 flags: flags, 172 reply: make(chan error), 173 } 174 w.input <- in 175 if err := w.wakeupReader(); err != nil { 176 return err 177 } 178 return <-in.reply 179 } 180 181 // Watch adds path to the watched file set, watching all events. 182 func (w *Watcher) watch(path string) error { 183 return w.AddWatch(path, sys_FS_ALL_EVENTS) 184 } 185 186 // RemoveWatch removes path from the watched file set. 187 func (w *Watcher) removeWatch(path string) error { 188 in := &input{ 189 op: opRemoveWatch, 190 path: filepath.Clean(path), 191 reply: make(chan error), 192 } 193 w.input <- in 194 if err := w.wakeupReader(); err != nil { 195 return err 196 } 197 return <-in.reply 198 } 199 200 func (w *Watcher) wakeupReader() error { 201 e := syscall.PostQueuedCompletionStatus(w.port, 0, 0, nil) 202 if e != nil { 203 return os.NewSyscallError("PostQueuedCompletionStatus", e) 204 } 205 return nil 206 } 207 208 func getDir(pathname string) (dir string, err error) { 209 attr, e := syscall.GetFileAttributes(syscall.StringToUTF16Ptr(pathname)) 210 if e != nil { 211 return "", os.NewSyscallError("GetFileAttributes", e) 212 } 213 if attr&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 { 214 dir = pathname 215 } else { 216 dir, _ = filepath.Split(pathname) 217 dir = filepath.Clean(dir) 218 } 219 return 220 } 221 222 func getIno(path string) (ino *inode, err error) { 223 h, e := syscall.CreateFile(syscall.StringToUTF16Ptr(path), 224 syscall.FILE_LIST_DIRECTORY, 225 syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE, 226 nil, syscall.OPEN_EXISTING, 227 syscall.FILE_FLAG_BACKUP_SEMANTICS|syscall.FILE_FLAG_OVERLAPPED, 0) 228 if e != nil { 229 return nil, os.NewSyscallError("CreateFile", e) 230 } 231 var fi syscall.ByHandleFileInformation 232 if e = syscall.GetFileInformationByHandle(h, &fi); e != nil { 233 syscall.CloseHandle(h) 234 return nil, os.NewSyscallError("GetFileInformationByHandle", e) 235 } 236 ino = &inode{ 237 handle: h, 238 volume: fi.VolumeSerialNumber, 239 index: uint64(fi.FileIndexHigh)<<32 | uint64(fi.FileIndexLow), 240 } 241 return ino, nil 242 } 243 244 // Must run within the I/O thread. 245 func (m watchMap) get(ino *inode) *watch { 246 if i := m[ino.volume]; i != nil { 247 return i[ino.index] 248 } 249 return nil 250 } 251 252 // Must run within the I/O thread. 253 func (m watchMap) set(ino *inode, watch *watch) { 254 i := m[ino.volume] 255 if i == nil { 256 i = make(indexMap) 257 m[ino.volume] = i 258 } 259 i[ino.index] = watch 260 } 261 262 // Must run within the I/O thread. 263 func (w *Watcher) addWatch(pathname string, flags uint64) error { 264 dir, err := getDir(pathname) 265 if err != nil { 266 return err 267 } 268 if flags&sys_FS_ONLYDIR != 0 && pathname != dir { 269 return nil 270 } 271 ino, err := getIno(dir) 272 if err != nil { 273 return err 274 } 275 w.mu.Lock() 276 watchEntry := w.watches.get(ino) 277 w.mu.Unlock() 278 if watchEntry == nil { 279 if _, e := syscall.CreateIoCompletionPort(ino.handle, w.port, 0, 0); e != nil { 280 syscall.CloseHandle(ino.handle) 281 return os.NewSyscallError("CreateIoCompletionPort", e) 282 } 283 watchEntry = &watch{ 284 ino: ino, 285 path: dir, 286 names: make(map[string]uint64), 287 } 288 w.mu.Lock() 289 w.watches.set(ino, watchEntry) 290 w.mu.Unlock() 291 flags |= provisional 292 } else { 293 syscall.CloseHandle(ino.handle) 294 } 295 if pathname == dir { 296 watchEntry.mask |= flags 297 } else { 298 watchEntry.names[filepath.Base(pathname)] |= flags 299 } 300 if err = w.startRead(watchEntry); err != nil { 301 return err 302 } 303 if pathname == dir { 304 watchEntry.mask &= ^provisional 305 } else { 306 watchEntry.names[filepath.Base(pathname)] &= ^provisional 307 } 308 return nil 309 } 310 311 // Must run within the I/O thread. 312 func (w *Watcher) remWatch(pathname string) error { 313 dir, err := getDir(pathname) 314 if err != nil { 315 return err 316 } 317 ino, err := getIno(dir) 318 if err != nil { 319 return err 320 } 321 w.mu.Lock() 322 watch := w.watches.get(ino) 323 w.mu.Unlock() 324 if watch == nil { 325 return fmt.Errorf("can't remove non-existent watch for: %s", pathname) 326 } 327 if pathname == dir { 328 w.sendEvent(watch.path, watch.mask&sys_FS_IGNORED) 329 watch.mask = 0 330 } else { 331 name := filepath.Base(pathname) 332 w.sendEvent(watch.path+"\\"+name, watch.names[name]&sys_FS_IGNORED) 333 delete(watch.names, name) 334 } 335 return w.startRead(watch) 336 } 337 338 // Must run within the I/O thread. 339 func (w *Watcher) deleteWatch(watch *watch) { 340 for name, mask := range watch.names { 341 if mask&provisional == 0 { 342 w.sendEvent(watch.path+"\\"+name, mask&sys_FS_IGNORED) 343 } 344 delete(watch.names, name) 345 } 346 if watch.mask != 0 { 347 if watch.mask&provisional == 0 { 348 w.sendEvent(watch.path, watch.mask&sys_FS_IGNORED) 349 } 350 watch.mask = 0 351 } 352 } 353 354 // Must run within the I/O thread. 355 func (w *Watcher) startRead(watch *watch) error { 356 if e := syscall.CancelIo(watch.ino.handle); e != nil { 357 w.Error <- os.NewSyscallError("CancelIo", e) 358 w.deleteWatch(watch) 359 } 360 mask := toWindowsFlags(watch.mask) 361 for _, m := range watch.names { 362 mask |= toWindowsFlags(m) 363 } 364 if mask == 0 { 365 if e := syscall.CloseHandle(watch.ino.handle); e != nil { 366 w.Error <- os.NewSyscallError("CloseHandle", e) 367 } 368 w.mu.Lock() 369 delete(w.watches[watch.ino.volume], watch.ino.index) 370 w.mu.Unlock() 371 return nil 372 } 373 e := syscall.ReadDirectoryChanges(watch.ino.handle, &watch.buf[0], 374 uint32(unsafe.Sizeof(watch.buf)), false, mask, nil, &watch.ov, 0) 375 if e != nil { 376 err := os.NewSyscallError("ReadDirectoryChanges", e) 377 if e == syscall.ERROR_ACCESS_DENIED && watch.mask&provisional == 0 { 378 // Watched directory was probably removed 379 if w.sendEvent(watch.path, watch.mask&sys_FS_DELETE_SELF) { 380 if watch.mask&sys_FS_ONESHOT != 0 { 381 watch.mask = 0 382 } 383 } 384 err = nil 385 } 386 w.deleteWatch(watch) 387 w.startRead(watch) 388 return err 389 } 390 return nil 391 } 392 393 // readEvents reads from the I/O completion port, converts the 394 // received events into Event objects and sends them via the Event channel. 395 // Entry point to the I/O thread. 396 func (w *Watcher) readEvents() { 397 var ( 398 n, key uint32 399 ov *syscall.Overlapped 400 ) 401 runtime.LockOSThread() 402 403 for { 404 e := syscall.GetQueuedCompletionStatus(w.port, &n, &key, &ov, syscall.INFINITE) 405 watch := (*watch)(unsafe.Pointer(ov)) 406 407 if watch == nil { 408 select { 409 case ch := <-w.quit: 410 w.mu.Lock() 411 var indexes []indexMap 412 for _, index := range w.watches { 413 indexes = append(indexes, index) 414 } 415 w.mu.Unlock() 416 for _, index := range indexes { 417 for _, watch := range index { 418 w.deleteWatch(watch) 419 w.startRead(watch) 420 } 421 } 422 var err error 423 if e := syscall.CloseHandle(w.port); e != nil { 424 err = os.NewSyscallError("CloseHandle", e) 425 } 426 close(w.Event) 427 close(w.Error) 428 ch <- err 429 return 430 case in := <-w.input: 431 switch in.op { 432 case opAddWatch: 433 in.reply <- w.addWatch(in.path, uint64(in.flags)) 434 case opRemoveWatch: 435 in.reply <- w.remWatch(in.path) 436 } 437 default: 438 } 439 continue 440 } 441 442 switch e { 443 case sys_ERROR_MORE_DATA: 444 if watch == nil { 445 w.Error <- errors.New("ERROR_MORE_DATA has unexpectedly null lpOverlapped buffer") 446 } else { 447 // The i/o succeeded but the buffer is full. 448 // In theory we should be building up a full packet. 449 // In practice we can get away with just carrying on. 450 n = uint32(unsafe.Sizeof(watch.buf)) 451 } 452 case syscall.ERROR_ACCESS_DENIED: 453 // Watched directory was probably removed 454 w.sendEvent(watch.path, watch.mask&sys_FS_DELETE_SELF) 455 w.deleteWatch(watch) 456 w.startRead(watch) 457 continue 458 case syscall.ERROR_OPERATION_ABORTED: 459 // CancelIo was called on this handle 460 continue 461 default: 462 w.Error <- os.NewSyscallError("GetQueuedCompletionPort", e) 463 continue 464 case nil: 465 } 466 467 var offset uint32 468 for { 469 if n == 0 { 470 w.Event <- &FileEvent{mask: sys_FS_Q_OVERFLOW} 471 w.Error <- errors.New("short read in readEvents()") 472 break 473 } 474 475 // Point "raw" to the event in the buffer 476 raw := (*syscall.FileNotifyInformation)(unsafe.Pointer(&watch.buf[offset])) 477 buf := (*[syscall.MAX_PATH]uint16)(unsafe.Pointer(&raw.FileName)) 478 name := syscall.UTF16ToString(buf[:raw.FileNameLength/2]) 479 fullname := watch.path + "\\" + name 480 481 var mask uint64 482 switch raw.Action { 483 case syscall.FILE_ACTION_REMOVED: 484 mask = sys_FS_DELETE_SELF 485 case syscall.FILE_ACTION_MODIFIED: 486 mask = sys_FS_MODIFY 487 case syscall.FILE_ACTION_RENAMED_OLD_NAME: 488 watch.rename = name 489 case syscall.FILE_ACTION_RENAMED_NEW_NAME: 490 if watch.names[watch.rename] != 0 { 491 watch.names[name] |= watch.names[watch.rename] 492 delete(watch.names, watch.rename) 493 mask = sys_FS_MOVE_SELF 494 } 495 } 496 497 sendNameEvent := func() { 498 if w.sendEvent(fullname, watch.names[name]&mask) { 499 if watch.names[name]&sys_FS_ONESHOT != 0 { 500 delete(watch.names, name) 501 } 502 } 503 } 504 if raw.Action != syscall.FILE_ACTION_RENAMED_NEW_NAME { 505 sendNameEvent() 506 } 507 if raw.Action == syscall.FILE_ACTION_REMOVED { 508 w.sendEvent(fullname, watch.names[name]&sys_FS_IGNORED) 509 delete(watch.names, name) 510 } 511 if w.sendEvent(fullname, watch.mask&toFSnotifyFlags(raw.Action)) { 512 if watch.mask&sys_FS_ONESHOT != 0 { 513 watch.mask = 0 514 } 515 } 516 if raw.Action == syscall.FILE_ACTION_RENAMED_NEW_NAME { 517 fullname = watch.path + "\\" + watch.rename 518 sendNameEvent() 519 } 520 521 // Move to the next event in the buffer 522 if raw.NextEntryOffset == 0 { 523 break 524 } 525 offset += raw.NextEntryOffset 526 527 // Error! 528 if offset >= n { 529 w.Error <- errors.New("Windows system assumed buffer larger than it is, events have likely been missed.") 530 break 531 } 532 } 533 534 if err := w.startRead(watch); err != nil { 535 w.Error <- err 536 } 537 } 538 } 539 540 func (w *Watcher) sendEvent(name string, mask uint64) bool { 541 if mask == 0 { 542 return false 543 } 544 event := &FileEvent{mask: uint32(mask), Name: name} 545 if mask&sys_FS_MOVE != 0 { 546 if mask&sys_FS_MOVED_FROM != 0 { 547 w.cookie++ 548 } 549 event.cookie = w.cookie 550 } 551 select { 552 case ch := <-w.quit: 553 w.quit <- ch 554 case w.Event <- event: 555 } 556 return true 557 } 558 559 func toWindowsFlags(mask uint64) uint32 { 560 var m uint32 561 if mask&sys_FS_ACCESS != 0 { 562 m |= syscall.FILE_NOTIFY_CHANGE_LAST_ACCESS 563 } 564 if mask&sys_FS_MODIFY != 0 { 565 m |= syscall.FILE_NOTIFY_CHANGE_LAST_WRITE 566 } 567 if mask&sys_FS_ATTRIB != 0 { 568 m |= syscall.FILE_NOTIFY_CHANGE_ATTRIBUTES 569 } 570 if mask&(sys_FS_MOVE|sys_FS_CREATE|sys_FS_DELETE) != 0 { 571 m |= syscall.FILE_NOTIFY_CHANGE_FILE_NAME | syscall.FILE_NOTIFY_CHANGE_DIR_NAME 572 } 573 return m 574 } 575 576 func toFSnotifyFlags(action uint32) uint64 { 577 switch action { 578 case syscall.FILE_ACTION_ADDED: 579 return sys_FS_CREATE 580 case syscall.FILE_ACTION_REMOVED: 581 return sys_FS_DELETE 582 case syscall.FILE_ACTION_MODIFIED: 583 return sys_FS_MODIFY 584 case syscall.FILE_ACTION_RENAMED_OLD_NAME: 585 return sys_FS_MOVED_FROM 586 case syscall.FILE_ACTION_RENAMED_NEW_NAME: 587 return sys_FS_MOVED_TO 588 } 589 return 0 590 }