From 1cc26be54493704eceac094224feab6487a89217 Mon Sep 17 00:00:00 2001 From: Dan Enman Date: Fri, 5 Feb 2016 12:37:01 -0400 Subject: [PATCH 1/9] Use exec.Command() with ...args - Use `out` for error output: `err` is the just exit code --- dockermachine.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dockermachine.go b/dockermachine.go index 3306f52..d034ef8 100644 --- a/dockermachine.go +++ b/dockermachine.go @@ -31,13 +31,13 @@ func RunSSHCommand(machineName, command string, verbose bool) (out []byte, err e if verbose { fmt.Println(`docker-machine ssh ` + machineName + ` '` + command + `'`) } - return exec.Command("/bin/sh", "-c", `docker-machine ssh `+machineName+` '`+command+`'`).CombinedOutput() + return exec.Command("docker-machine", "ssh", machineName, command).CombinedOutput() } func GetSSHPort(machineName string) (port uint, err error) { - out, err := exec.Command("/bin/sh", "-c", `docker-machine inspect `+machineName).CombinedOutput() + out, err := exec.Command("docker-machine", "inspect", machineName).CombinedOutput() if err != nil { - return 0, err + return 0, fmt.Errorf("%s", out) } return PortFromMachineJSON(out) From 2de31bd5fbb2c40e297b4a40472d44fa4a9a2200 Mon Sep 17 00:00:00 2001 From: Dan Enman Date: Fri, 5 Feb 2016 15:36:55 -0400 Subject: [PATCH 2/9] Use os/user for HomeDir --- rsync.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/rsync.go b/rsync.go index f20d072..f58b473 100644 --- a/rsync.go +++ b/rsync.go @@ -4,6 +4,7 @@ import ( "fmt" "os" "os/exec" + "os/user" "path/filepath" "strings" ) @@ -35,8 +36,11 @@ func Sync(via string, port uint, src, dst string, verbose bool) { args = append(args, via) } else { machineName := via - homePath := os.Getenv("HOME") - args = append(args, fmt.Sprintf(`-e 'ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=quiet -i "%s" -p %v'`, filepath.Join(homePath, "/.docker/machine/machines", machineName, "id_rsa"), port)) + u, err := user.Current() + if err != nil { + panic(fmt.Sprintf("Unable to load current user's profile: %s", err)) + } + args = append(args, fmt.Sprintf(`-e 'ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=quiet -i "%s" -p %v'`, filepath.Join(u.HomeDir, "/.docker/machine/machines", machineName, "id_rsa"), port)) args = append(args, "--rsync-path='sudo rsync'") args = append(args, src, "docker@localhost:"+dst) } From 7bbdd80289e5e0679fc6a0c73a006293ca71e43a Mon Sep 17 00:00:00 2001 From: Dan Enman Date: Mon, 8 Feb 2016 10:02:14 -0400 Subject: [PATCH 3/9] Add docker-rsync.exe to .gitignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 40a81ee..85eb6e6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ docker-rsync -/build \ No newline at end of file +docker-rsync.exe +/build From 38f9f6ed678ac503feb7574f6272ca5db130895c Mon Sep 17 00:00:00 2001 From: Dan Enman Date: Mon, 8 Feb 2016 10:14:35 -0400 Subject: [PATCH 4/9] Separate Exec funcs for Windows and OS X --- exec.go | 16 ++++++++++++++++ exec_windows.go | 16 ++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 exec.go create mode 100644 exec_windows.go diff --git a/exec.go b/exec.go new file mode 100644 index 0000000..a63a50d --- /dev/null +++ b/exec.go @@ -0,0 +1,16 @@ +// +build !windows + +package main + +import ( + "fmt" + "os/exec" + "strings" +) + +func Exec(cmd string, args ...string) *exec.Cmd { + shCmd := fmt.Sprintf("%s %s", cmd, strings.Join(args, " ")) + a := []string{"-c", shCmd} + + return exec.Command("/bin/sh", a...) +} diff --git a/exec_windows.go b/exec_windows.go new file mode 100644 index 0000000..17dd010 --- /dev/null +++ b/exec_windows.go @@ -0,0 +1,16 @@ +// +build windows + +package main + +import ( + "fmt" + "os/exec" + "strings" +) + +func Exec(cmd string, args ...string) *exec.Cmd { + shCmd := fmt.Sprintf("%s %s", cmd, strings.Join(args, " ")) + a := []string{"-Command", shCmd} + + return exec.Command("powershell", a...) +} From e9c52e6df3bf058f5ddab4968157dc8ea4ab98f0 Mon Sep 17 00:00:00 2001 From: Dan Enman Date: Mon, 8 Feb 2016 10:15:11 -0400 Subject: [PATCH 5/9] Add Watch() for Windows platforms --- watch.go | 2 ++ watch_windows.go | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 watch_windows.go diff --git a/watch.go b/watch.go index 86e6fb4..59916f5 100644 --- a/watch.go +++ b/watch.go @@ -1,3 +1,5 @@ +// +build !windows + package main import ( diff --git a/watch_windows.go b/watch_windows.go new file mode 100644 index 0000000..a919ea1 --- /dev/null +++ b/watch_windows.go @@ -0,0 +1,38 @@ +// +build windows + +package main + +import ( + "fmt" + "golang.org/x/exp/winfsnotify" +) + +var noteDescription = map[uint32]string{ + winfsnotify.FS_CREATE: "Created", + winfsnotify.FS_DELETE: "Removed", + winfsnotify.FS_MODIFY: "Modified", + winfsnotify.FS_MOVE: "Renamed", +} + +func Watch(path string, eventHandler func(id uint64, path string, flags []string)) { + w, err := winfsnotify.NewWatcher() + if err != nil { + panic(fmt.Sprintf("Cannot start watcher: %s", err)) + } + + err = w.Watch(path) + if err != nil { + panic(fmt.Sprintf("Cannot watch path: %s", err)) + } + + for { + select { + case event := <-w.Event: + fmt.Printf("Got event %#v\n\n", event) + + if mask, ok := noteDescription[event.Mask]; ok { + go eventHandler(uint64(event.Mask), event.Name, []string{mask}) + } + } + } +} From 2f3f9e358791c49be36b05df0d45f625cfbb2716 Mon Sep 17 00:00:00 2001 From: Dan Enman Date: Mon, 8 Feb 2016 10:16:07 -0400 Subject: [PATCH 6/9] Use platform specific Exec() --- dockermachine.go | 5 ++--- rsync.go | 6 +----- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/dockermachine.go b/dockermachine.go index d034ef8..d6a555b 100644 --- a/dockermachine.go +++ b/dockermachine.go @@ -4,7 +4,6 @@ import ( "encoding/json" "fmt" "os" - "os/exec" ) func Provision(machineName string, verbose bool) { @@ -31,11 +30,11 @@ func RunSSHCommand(machineName, command string, verbose bool) (out []byte, err e if verbose { fmt.Println(`docker-machine ssh ` + machineName + ` '` + command + `'`) } - return exec.Command("docker-machine", "ssh", machineName, command).CombinedOutput() + return Exec("docker-machine", "ssh", machineName, command).CombinedOutput() } func GetSSHPort(machineName string) (port uint, err error) { - out, err := exec.Command("docker-machine", "inspect", machineName).CombinedOutput() + out, err := Exec("docker-machine", "inspect", machineName).CombinedOutput() if err != nil { return 0, fmt.Errorf("%s", out) } diff --git a/rsync.go b/rsync.go index f58b473..0c07a6c 100644 --- a/rsync.go +++ b/rsync.go @@ -3,7 +3,6 @@ package main import ( "fmt" "os" - "os/exec" "os/user" "path/filepath" "strings" @@ -45,10 +44,7 @@ func Sync(via string, port uint, src, dst string, verbose bool) { args = append(args, src, "docker@localhost:"+dst) } - command := "rsync " + strings.Join(args, " ") - - // fmt.Println("/bin/sh", "-c", command) - cmd := exec.Command("/bin/sh", "-c", command) + cmd := Exec("rsync", args...) if verbose { cmd.Stdout = os.Stdout From 5eb81174f2a6231ed39b1a70e932aeb38daddeac Mon Sep 17 00:00:00 2001 From: Dan Enman Date: Mon, 8 Feb 2016 10:16:27 -0400 Subject: [PATCH 7/9] Use u.HomeDir for home directory --- rsync.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/rsync.go b/rsync.go index 0c07a6c..8a4cccd 100644 --- a/rsync.go +++ b/rsync.go @@ -39,7 +39,11 @@ func Sync(via string, port uint, src, dst string, verbose bool) { if err != nil { panic(fmt.Sprintf("Unable to load current user's profile: %s", err)) } - args = append(args, fmt.Sprintf(`-e 'ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=quiet -i "%s" -p %v'`, filepath.Join(u.HomeDir, "/.docker/machine/machines", machineName, "id_rsa"), port)) + + sshKeyFile := filepath.Join(u.HomeDir, "/.docker/machine/machines/", machineName, "id_rsa") + sshArg := fmt.Sprintf(`-e "ssh -o StrictHostKeyChecking=no -i %s -p %v"`, sshKeyFile, port) + + args = append(args, sshArg) args = append(args, "--rsync-path='sudo rsync'") args = append(args, src, "docker@localhost:"+dst) } From 2038f269bc349a14ac6ad60c3cfe0d61c97b6d97 Mon Sep 17 00:00:00 2001 From: Dan Enman Date: Mon, 8 Feb 2016 10:31:57 -0400 Subject: [PATCH 8/9] Add golang.org/x/exp/winfsnotify to Godeps --- Godeps/Godeps.json | 6 +- .../_workspace/src/golang.org/x/exp/LICENSE | 27 + .../_workspace/src/golang.org/x/exp/PATENTS | 22 + .../vendor/github.com/BurntSushi/xgb/LICENSE | 42 ++ .../x/exp/winfsnotify/winfsnotify.go | 580 ++++++++++++++++++ 5 files changed, 676 insertions(+), 1 deletion(-) create mode 100755 Godeps/_workspace/src/golang.org/x/exp/LICENSE create mode 100755 Godeps/_workspace/src/golang.org/x/exp/PATENTS create mode 100755 Godeps/_workspace/src/golang.org/x/exp/shiny/vendor/github.com/BurntSushi/xgb/LICENSE create mode 100755 Godeps/_workspace/src/golang.org/x/exp/winfsnotify/winfsnotify.go diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index cc133a1..e656e21 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -1,10 +1,14 @@ { - "ImportPath": "github.com/mattes/docker-rsync", + "ImportPath": "github.com/synack/docker-rsync", "GoVersion": "go1.4.2", "Deps": [ { "ImportPath": "github.com/mattes/fsevents", "Rev": "38da907b88aa3480b3a6fef2772dfa3d2b39ea1c" + }, + { + "ImportPath": "golang.org/x/exp/winfsnotify", + "Rev": "8f78588f428986c92002fbdb61c861ad1b6764c2" } ] } diff --git a/Godeps/_workspace/src/golang.org/x/exp/LICENSE b/Godeps/_workspace/src/golang.org/x/exp/LICENSE new file mode 100755 index 0000000..6a66aea --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/exp/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/Godeps/_workspace/src/golang.org/x/exp/PATENTS b/Godeps/_workspace/src/golang.org/x/exp/PATENTS new file mode 100755 index 0000000..7330990 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/exp/PATENTS @@ -0,0 +1,22 @@ +Additional IP Rights Grant (Patents) + +"This implementation" means the copyrightable works distributed by +Google as part of the Go project. + +Google hereby grants to You a perpetual, worldwide, non-exclusive, +no-charge, royalty-free, irrevocable (except as stated in this section) +patent license to make, have made, use, offer to sell, sell, import, +transfer and otherwise run, modify and propagate the contents of this +implementation of Go, where such license applies only to those patent +claims, both currently owned or controlled by Google and acquired in +the future, licensable by Google that are necessarily infringed by this +implementation of Go. This grant does not include claims that would be +infringed only as a consequence of further modification of this +implementation. If you or your agent or exclusive licensee institute or +order or agree to the institution of patent litigation against any +entity (including a cross-claim or counterclaim in a lawsuit) alleging +that this implementation of Go or any code incorporated within this +implementation of Go constitutes direct or contributory patent +infringement, or inducement of patent infringement, then any patent +rights granted to you under this License for this implementation of Go +shall terminate as of the date such litigation is filed. diff --git a/Godeps/_workspace/src/golang.org/x/exp/shiny/vendor/github.com/BurntSushi/xgb/LICENSE b/Godeps/_workspace/src/golang.org/x/exp/shiny/vendor/github.com/BurntSushi/xgb/LICENSE new file mode 100755 index 0000000..d99cd90 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/exp/shiny/vendor/github.com/BurntSushi/xgb/LICENSE @@ -0,0 +1,42 @@ +// Copyright (c) 2009 The XGB Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Subject to the terms and conditions of this License, Google hereby +// grants to You a perpetual, worldwide, non-exclusive, no-charge, +// royalty-free, irrevocable (except as stated in this section) patent +// license to make, have made, use, offer to sell, sell, import, and +// otherwise transfer this implementation of XGB, where such license +// applies only to those patent claims licensable by Google that are +// necessarily infringed by use of this implementation of XGB. If You +// institute patent litigation against any entity (including a +// cross-claim or counterclaim in a lawsuit) alleging that this +// implementation of XGB or a Contribution incorporated within this +// implementation of XGB constitutes direct or contributory patent +// infringement, then any patent licenses granted to You under this +// License for this implementation of XGB shall terminate as of the date +// such litigation is filed. diff --git a/Godeps/_workspace/src/golang.org/x/exp/winfsnotify/winfsnotify.go b/Godeps/_workspace/src/golang.org/x/exp/winfsnotify/winfsnotify.go new file mode 100755 index 0000000..16264af --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/exp/winfsnotify/winfsnotify.go @@ -0,0 +1,580 @@ +// 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 winfsnotify + +import ( + "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 events + Cookie uint32 // Unique cookie associating related events (for rename) + Name string // File name (optional) +} + +const ( + opAddWatch = iota + opRemoveWatch +) + +const ( + provisional uint64 = 1 << (32 + iota) +) + +type input struct { + op int + path string + flags uint32 + reply chan error +} + +type inode struct { + handle syscall.Handle + volume uint32 + index uint64 +} + +type watch struct { + ov syscall.Overlapped + ino *inode // i-number + path string // Directory path + mask uint64 // Directory itself is being watched with these notify flags + names map[string]uint64 // Map of names being watched and their notify flags + rename string // Remembers the old name while renaming a file + buf [4096]byte +} + +type indexMap map[uint64]*watch +type 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 port + watches watchMap // Map of watches (key: i-number) + input chan *input // Inputs to the reader are sent on this channel + Event chan *Event // Events are returned on this channel + Error chan error // Errors are sent on this channel + isClosed bool // Set to true when Close() is first called + quit chan chan<- error + cookie 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 goroutine + ch := make(chan error) + w.quit <- ch + if 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 <- in + if 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 <- in + if 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) { + pathnamep, e := syscall.UTF16PtrFromString(pathname) + if e != nil { + return "", e + } + attr, e := syscall.GetFileAttributes(pathnamep) + 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) { + pathp, e := syscall.UTF16PtrFromString(path) + if e != nil { + return nil, e + } + h, e := syscall.CreateFile(pathp, + 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.ByHandleFileInformation + if 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 removed + if 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 uint32 + ov *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 error + if e := syscall.CloseHandle(w.port); e != nil { + err = os.NewSyscallError("CloseHandle", e) + } + close(w.Event) + close(w.Error) + ch <- err + return + case 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 removed + w.sendEvent(watch.path, watch.mask&FS_DELETE_SELF) + w.deleteWatch(watch) + w.startRead(watch) + continue + case syscall.ERROR_OPERATION_ABORTED: + // CancelIo was called on this handle + continue + default: + w.Error <- os.NewSyscallError("GetQueuedCompletionPort", e) + continue + case nil: + } + + var offset uint32 + for { + 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 buffer + raw := (*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 + "/" + name + + var mask uint64 + switch raw.Action { + case syscall.FILE_ACTION_REMOVED: + mask = FS_DELETE_SELF + case syscall.FILE_ACTION_MODIFIED: + mask = FS_MODIFY + case syscall.FILE_ACTION_RENAMED_OLD_NAME: + watch.rename = name + case 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.rename + sendNameEvent() + } + + // Move to the next event in the buffer + if 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 <- ch + case 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 string + m := e.Mask + for _, b := range eventBits { + if m&b.Value != 0 { + m &^= b.Value + events += "|" + 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 uint32 + if 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_CREATE + case syscall.FILE_ACTION_REMOVED: + return FS_DELETE + case syscall.FILE_ACTION_MODIFIED: + return FS_MODIFY + case syscall.FILE_ACTION_RENAMED_OLD_NAME: + return FS_MOVED_FROM + case syscall.FILE_ACTION_RENAMED_NEW_NAME: + return FS_MOVED_TO + } + return 0 +} + +const ( + // Options for AddWatch + FS_ONESHOT = 0x80000000 + FS_ONLYDIR = 0x1000000 + + // Events + FS_ACCESS = 0x1 + FS_ALL_EVENTS = 0xfff + FS_ATTRIB = 0x4 + FS_CLOSE = 0x18 + FS_CREATE = 0x100 + FS_DELETE = 0x200 + FS_DELETE_SELF = 0x400 + FS_MODIFY = 0x2 + FS_MOVE = 0xc0 + FS_MOVED_FROM = 0x40 + FS_MOVED_TO = 0x80 + FS_MOVE_SELF = 0x800 + + // Special events + FS_IGNORED = 0x8000 + FS_Q_OVERFLOW = 0x4000 +) + +var eventBits = []struct { + Value uint32 + Name 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"}, +} From 6211fbd3a3065d539636871ff3a0c7ce05e3dd96 Mon Sep 17 00:00:00 2001 From: Daniel Enman Date: Mon, 8 Feb 2016 13:44:12 -0400 Subject: [PATCH 9/9] Remove left over fmt.Printf --- watch_windows.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/watch_windows.go b/watch_windows.go index a919ea1..87eeee2 100644 --- a/watch_windows.go +++ b/watch_windows.go @@ -28,8 +28,6 @@ func Watch(path string, eventHandler func(id uint64, path string, flags []string for { select { case event := <-w.Event: - fmt.Printf("Got event %#v\n\n", event) - if mask, ok := noteDescription[event.Mask]; ok { go eventHandler(uint64(event.Mask), event.Name, []string{mask}) }