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