URL
https://opencores.org/ocsvn/openrisc/openrisc/trunk
Subversion Repositories openrisc
[/] [openrisc/] [trunk/] [gnu-dev/] [or1k-gcc/] [libgo/] [go/] [exp/] [winfsnotify/] [winfsnotify.go] - Rev 747
Compare with Previous | Blame | View Log
// Copyright 2011 The Go Authors. All rights reserved.// Use of this source code is governed by a BSD-style// license that can be found in the LICENSE file.// +build windows// Package winfsnotify allows the user to receive// file system event notifications on Windows.package winfsnotifyimport ("errors""fmt""os""path/filepath""runtime""syscall""unsafe")// Event is the type of the notification messages// received on the watcher's Event channel.type Event struct {Mask uint32 // Mask of eventsCookie uint32 // Unique cookie associating related events (for rename)Name string // File name (optional)}const (opAddWatch = iotaopRemoveWatch)const (provisional uint64 = 1 << (32 + iota))type input struct {op intpath stringflags uint32reply chan error}type inode struct {handle syscall.Handlevolume uint32index uint64}type watch struct {ov syscall.Overlappedino *inode // i-numberpath string // Directory pathmask uint64 // Directory itself is being watched with these notify flagsnames map[string]uint64 // Map of names being watched and their notify flagsrename string // Remembers the old name while renaming a filebuf [4096]byte}type indexMap map[uint64]*watchtype watchMap map[uint32]indexMap// A Watcher waits for and receives event notifications// for a specific set of files and directories.type Watcher struct {port syscall.Handle // Handle to completion portwatches watchMap // Map of watches (key: i-number)input chan *input // Inputs to the reader are sent on this channelEvent chan *Event // Events are returned on this channelError chan error // Errors are sent on this channelisClosed bool // Set to true when Close() is first calledquit chan chan<- errorcookie uint32}// NewWatcher creates and returns a Watcher.func NewWatcher() (*Watcher, error) {port, e := syscall.CreateIoCompletionPort(syscall.InvalidHandle, 0, 0, 0)if e != nil {return nil, os.NewSyscallError("CreateIoCompletionPort", e)}w := &Watcher{port: port,watches: make(watchMap),input: make(chan *input, 1),Event: make(chan *Event, 50),Error: make(chan error),quit: make(chan chan<- error, 1),}go w.readEvents()return w, nil}// Close closes a Watcher.// It sends a message to the reader goroutine to quit and removes all watches// associated with the watcher.func (w *Watcher) Close() error {if w.isClosed {return nil}w.isClosed = true// Send "quit" message to the reader goroutinech := make(chan error)w.quit <- chif err := w.wakeupReader(); err != nil {return err}return <-ch}// AddWatch adds path to the watched file set.func (w *Watcher) AddWatch(path string, flags uint32) error {if w.isClosed {return errors.New("watcher already closed")}in := &input{op: opAddWatch,path: filepath.Clean(path),flags: flags,reply: make(chan error),}w.input <- inif err := w.wakeupReader(); err != nil {return err}return <-in.reply}// Watch adds path to the watched file set, watching all events.func (w *Watcher) Watch(path string) error {return w.AddWatch(path, FS_ALL_EVENTS)}// RemoveWatch removes path from the watched file set.func (w *Watcher) RemoveWatch(path string) error {in := &input{op: opRemoveWatch,path: filepath.Clean(path),reply: make(chan error),}w.input <- inif err := w.wakeupReader(); err != nil {return err}return <-in.reply}func (w *Watcher) wakeupReader() error {e := syscall.PostQueuedCompletionStatus(w.port, 0, 0, nil)if e != nil {return os.NewSyscallError("PostQueuedCompletionStatus", e)}return nil}func getDir(pathname string) (dir string, err error) {attr, e := syscall.GetFileAttributes(syscall.StringToUTF16Ptr(pathname))if e != nil {return "", os.NewSyscallError("GetFileAttributes", e)}if attr&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 {dir = pathname} else {dir, _ = filepath.Split(pathname)dir = filepath.Clean(dir)}return}func getIno(path string) (ino *inode, err error) {h, e := syscall.CreateFile(syscall.StringToUTF16Ptr(path),syscall.FILE_LIST_DIRECTORY,syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE,nil, syscall.OPEN_EXISTING,syscall.FILE_FLAG_BACKUP_SEMANTICS|syscall.FILE_FLAG_OVERLAPPED, 0)if e != nil {return nil, os.NewSyscallError("CreateFile", e)}var fi syscall.ByHandleFileInformationif e = syscall.GetFileInformationByHandle(h, &fi); e != nil {syscall.CloseHandle(h)return nil, os.NewSyscallError("GetFileInformationByHandle", e)}ino = &inode{handle: h,volume: fi.VolumeSerialNumber,index: uint64(fi.FileIndexHigh)<<32 | uint64(fi.FileIndexLow),}return ino, nil}// Must run within the I/O thread.func (m watchMap) get(ino *inode) *watch {if i := m[ino.volume]; i != nil {return i[ino.index]}return nil}// Must run within the I/O thread.func (m watchMap) set(ino *inode, watch *watch) {i := m[ino.volume]if i == nil {i = make(indexMap)m[ino.volume] = i}i[ino.index] = watch}// Must run within the I/O thread.func (w *Watcher) addWatch(pathname string, flags uint64) error {dir, err := getDir(pathname)if err != nil {return err}if flags&FS_ONLYDIR != 0 && pathname != dir {return nil}ino, err := getIno(dir)if err != nil {return err}watchEntry := w.watches.get(ino)if watchEntry == nil {if _, e := syscall.CreateIoCompletionPort(ino.handle, w.port, 0, 0); e != nil {syscall.CloseHandle(ino.handle)return os.NewSyscallError("CreateIoCompletionPort", e)}watchEntry = &watch{ino: ino,path: dir,names: make(map[string]uint64),}w.watches.set(ino, watchEntry)flags |= provisional} else {syscall.CloseHandle(ino.handle)}if pathname == dir {watchEntry.mask |= flags} else {watchEntry.names[filepath.Base(pathname)] |= flags}if err = w.startRead(watchEntry); err != nil {return err}if pathname == dir {watchEntry.mask &= ^provisional} else {watchEntry.names[filepath.Base(pathname)] &= ^provisional}return nil}// Must run within the I/O thread.func (w *Watcher) removeWatch(pathname string) error {dir, err := getDir(pathname)if err != nil {return err}ino, err := getIno(dir)if err != nil {return err}watch := w.watches.get(ino)if watch == nil {return fmt.Errorf("can't remove non-existent watch for: %s", pathname)}if pathname == dir {w.sendEvent(watch.path, watch.mask&FS_IGNORED)watch.mask = 0} else {name := filepath.Base(pathname)w.sendEvent(watch.path+"/"+name, watch.names[name]&FS_IGNORED)delete(watch.names, name)}return w.startRead(watch)}// Must run within the I/O thread.func (w *Watcher) deleteWatch(watch *watch) {for name, mask := range watch.names {if mask&provisional == 0 {w.sendEvent(watch.path+"/"+name, mask&FS_IGNORED)}delete(watch.names, name)}if watch.mask != 0 {if watch.mask&provisional == 0 {w.sendEvent(watch.path, watch.mask&FS_IGNORED)}watch.mask = 0}}// Must run within the I/O thread.func (w *Watcher) startRead(watch *watch) error {if e := syscall.CancelIo(watch.ino.handle); e != nil {w.Error <- os.NewSyscallError("CancelIo", e)w.deleteWatch(watch)}mask := toWindowsFlags(watch.mask)for _, m := range watch.names {mask |= toWindowsFlags(m)}if mask == 0 {if e := syscall.CloseHandle(watch.ino.handle); e != nil {w.Error <- os.NewSyscallError("CloseHandle", e)}delete(w.watches[watch.ino.volume], watch.ino.index)return nil}e := syscall.ReadDirectoryChanges(watch.ino.handle, &watch.buf[0],uint32(unsafe.Sizeof(watch.buf)), false, mask, nil, &watch.ov, 0)if e != nil {err := os.NewSyscallError("ReadDirectoryChanges", e)if e == syscall.ERROR_ACCESS_DENIED && watch.mask&provisional == 0 {// Watched directory was probably removedif w.sendEvent(watch.path, watch.mask&FS_DELETE_SELF) {if watch.mask&FS_ONESHOT != 0 {watch.mask = 0}}err = nil}w.deleteWatch(watch)w.startRead(watch)return err}return nil}// readEvents reads from the I/O completion port, converts the// received events into Event objects and sends them via the Event channel.// Entry point to the I/O thread.func (w *Watcher) readEvents() {var (n, key uint32ov *syscall.Overlapped)runtime.LockOSThread()for {e := syscall.GetQueuedCompletionStatus(w.port, &n, &key, &ov, syscall.INFINITE)watch := (*watch)(unsafe.Pointer(ov))if watch == nil {select {case ch := <-w.quit:for _, index := range w.watches {for _, watch := range index {w.deleteWatch(watch)w.startRead(watch)}}var err errorif e := syscall.CloseHandle(w.port); e != nil {err = os.NewSyscallError("CloseHandle", e)}close(w.Event)close(w.Error)ch <- errreturncase in := <-w.input:switch in.op {case opAddWatch:in.reply <- w.addWatch(in.path, uint64(in.flags))case opRemoveWatch:in.reply <- w.removeWatch(in.path)}default:}continue}switch e {case syscall.ERROR_ACCESS_DENIED:// Watched directory was probably removedw.sendEvent(watch.path, watch.mask&FS_DELETE_SELF)w.deleteWatch(watch)w.startRead(watch)continuecase syscall.ERROR_OPERATION_ABORTED:// CancelIo was called on this handlecontinuedefault:w.Error <- os.NewSyscallError("GetQueuedCompletionPort", e)continuecase nil:}var offset uint32for {if n == 0 {w.Event <- &Event{Mask: FS_Q_OVERFLOW}w.Error <- errors.New("short read in readEvents()")break}// Point "raw" to the event in the bufferraw := (*syscall.FileNotifyInformation)(unsafe.Pointer(&watch.buf[offset]))buf := (*[syscall.MAX_PATH]uint16)(unsafe.Pointer(&raw.FileName))name := syscall.UTF16ToString(buf[:raw.FileNameLength/2])fullname := watch.path + "/" + namevar mask uint64switch raw.Action {case syscall.FILE_ACTION_REMOVED:mask = FS_DELETE_SELFcase syscall.FILE_ACTION_MODIFIED:mask = FS_MODIFYcase syscall.FILE_ACTION_RENAMED_OLD_NAME:watch.rename = namecase syscall.FILE_ACTION_RENAMED_NEW_NAME:if watch.names[watch.rename] != 0 {watch.names[name] |= watch.names[watch.rename]delete(watch.names, watch.rename)mask = FS_MOVE_SELF}}sendNameEvent := func() {if w.sendEvent(fullname, watch.names[name]&mask) {if watch.names[name]&FS_ONESHOT != 0 {delete(watch.names, name)}}}if raw.Action != syscall.FILE_ACTION_RENAMED_NEW_NAME {sendNameEvent()}if raw.Action == syscall.FILE_ACTION_REMOVED {w.sendEvent(fullname, watch.names[name]&FS_IGNORED)delete(watch.names, name)}if w.sendEvent(fullname, watch.mask&toFSnotifyFlags(raw.Action)) {if watch.mask&FS_ONESHOT != 0 {watch.mask = 0}}if raw.Action == syscall.FILE_ACTION_RENAMED_NEW_NAME {fullname = watch.path + "/" + watch.renamesendNameEvent()}// Move to the next event in the bufferif raw.NextEntryOffset == 0 {break}offset += raw.NextEntryOffset}if err := w.startRead(watch); err != nil {w.Error <- err}}}func (w *Watcher) sendEvent(name string, mask uint64) bool {if mask == 0 {return false}event := &Event{Mask: uint32(mask), Name: name}if mask&FS_MOVE != 0 {if mask&FS_MOVED_FROM != 0 {w.cookie++}event.Cookie = w.cookie}select {case ch := <-w.quit:w.quit <- chcase w.Event <- event:}return true}// String formats the event e in the form// "filename: 0xEventMask = FS_ACCESS|FS_ATTRIB_|..."func (e *Event) String() string {var events stringm := e.Maskfor _, b := range eventBits {if m&b.Value != 0 {m &^= b.Valueevents += "|" + b.Name}}if m != 0 {events += fmt.Sprintf("|%#x", m)}if len(events) > 0 {events = " == " + events[1:]}return fmt.Sprintf("%q: %#x%s", e.Name, e.Mask, events)}func toWindowsFlags(mask uint64) uint32 {var m uint32if mask&FS_ACCESS != 0 {m |= syscall.FILE_NOTIFY_CHANGE_LAST_ACCESS}if mask&FS_MODIFY != 0 {m |= syscall.FILE_NOTIFY_CHANGE_LAST_WRITE}if mask&FS_ATTRIB != 0 {m |= syscall.FILE_NOTIFY_CHANGE_ATTRIBUTES}if mask&(FS_MOVE|FS_CREATE|FS_DELETE) != 0 {m |= syscall.FILE_NOTIFY_CHANGE_FILE_NAME | syscall.FILE_NOTIFY_CHANGE_DIR_NAME}return m}func toFSnotifyFlags(action uint32) uint64 {switch action {case syscall.FILE_ACTION_ADDED:return FS_CREATEcase syscall.FILE_ACTION_REMOVED:return FS_DELETEcase syscall.FILE_ACTION_MODIFIED:return FS_MODIFYcase syscall.FILE_ACTION_RENAMED_OLD_NAME:return FS_MOVED_FROMcase syscall.FILE_ACTION_RENAMED_NEW_NAME:return FS_MOVED_TO}return 0}const (// Options for AddWatchFS_ONESHOT = 0x80000000FS_ONLYDIR = 0x1000000// EventsFS_ACCESS = 0x1FS_ALL_EVENTS = 0xfffFS_ATTRIB = 0x4FS_CLOSE = 0x18FS_CREATE = 0x100FS_DELETE = 0x200FS_DELETE_SELF = 0x400FS_MODIFY = 0x2FS_MOVE = 0xc0FS_MOVED_FROM = 0x40FS_MOVED_TO = 0x80FS_MOVE_SELF = 0x800// Special eventsFS_IGNORED = 0x8000FS_Q_OVERFLOW = 0x4000)var eventBits = []struct {Value uint32Name string}{{FS_ACCESS, "FS_ACCESS"},{FS_ATTRIB, "FS_ATTRIB"},{FS_CREATE, "FS_CREATE"},{FS_DELETE, "FS_DELETE"},{FS_DELETE_SELF, "FS_DELETE_SELF"},{FS_MODIFY, "FS_MODIFY"},{FS_MOVED_FROM, "FS_MOVED_FROM"},{FS_MOVED_TO, "FS_MOVED_TO"},{FS_MOVE_SELF, "FS_MOVE_SELF"},{FS_IGNORED, "FS_IGNORED"},{FS_Q_OVERFLOW, "FS_Q_OVERFLOW"},}
