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