commit
c3873a60e6
71 changed files with 11507 additions and 475 deletions
18
Gopkg.lock
generated
18
Gopkg.lock
generated
|
@ -66,6 +66,12 @@
|
||||||
revision = "5741799b275a3c4a5a9623a993576d7545cf7b5c"
|
revision = "5741799b275a3c4a5a9623a993576d7545cf7b5c"
|
||||||
version = "v2.4.0"
|
version = "v2.4.0"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "github.com/fsnotify/fsnotify"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "629574ca2a5df945712d3079857300b5e4da0236"
|
||||||
|
version = "v1.4.2"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
name = "github.com/fullsailor/pkcs7"
|
name = "github.com/fullsailor/pkcs7"
|
||||||
|
@ -313,6 +319,12 @@
|
||||||
packages = [".","xfs"]
|
packages = [".","xfs"]
|
||||||
revision = "a6e9df898b1336106c743392c48ee0b71f5c4efa"
|
revision = "a6e9df898b1336106c743392c48ee0b71f5c4efa"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
name = "github.com/spf13/afero"
|
||||||
|
packages = [".","mem"]
|
||||||
|
revision = "5660eeed305fe5f69c8fc6cf899132a459a97064"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/spf13/pflag"
|
name = "github.com/spf13/pflag"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
|
@ -421,7 +433,7 @@
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "k8s.io/client-go"
|
name = "k8s.io/client-go"
|
||||||
packages = ["discovery","discovery/fake","kubernetes","kubernetes/fake","kubernetes/scheme","kubernetes/typed/admissionregistration/v1alpha1","kubernetes/typed/admissionregistration/v1alpha1/fake","kubernetes/typed/apps/v1","kubernetes/typed/apps/v1/fake","kubernetes/typed/apps/v1beta1","kubernetes/typed/apps/v1beta1/fake","kubernetes/typed/apps/v1beta2","kubernetes/typed/apps/v1beta2/fake","kubernetes/typed/authentication/v1","kubernetes/typed/authentication/v1/fake","kubernetes/typed/authentication/v1beta1","kubernetes/typed/authentication/v1beta1/fake","kubernetes/typed/authorization/v1","kubernetes/typed/authorization/v1/fake","kubernetes/typed/authorization/v1beta1","kubernetes/typed/authorization/v1beta1/fake","kubernetes/typed/autoscaling/v1","kubernetes/typed/autoscaling/v1/fake","kubernetes/typed/autoscaling/v2beta1","kubernetes/typed/autoscaling/v2beta1/fake","kubernetes/typed/batch/v1","kubernetes/typed/batch/v1/fake","kubernetes/typed/batch/v1beta1","kubernetes/typed/batch/v1beta1/fake","kubernetes/typed/batch/v2alpha1","kubernetes/typed/batch/v2alpha1/fake","kubernetes/typed/certificates/v1beta1","kubernetes/typed/certificates/v1beta1/fake","kubernetes/typed/core/v1","kubernetes/typed/core/v1/fake","kubernetes/typed/extensions/v1beta1","kubernetes/typed/extensions/v1beta1/fake","kubernetes/typed/networking/v1","kubernetes/typed/networking/v1/fake","kubernetes/typed/policy/v1beta1","kubernetes/typed/policy/v1beta1/fake","kubernetes/typed/rbac/v1","kubernetes/typed/rbac/v1/fake","kubernetes/typed/rbac/v1alpha1","kubernetes/typed/rbac/v1alpha1/fake","kubernetes/typed/rbac/v1beta1","kubernetes/typed/rbac/v1beta1/fake","kubernetes/typed/scheduling/v1alpha1","kubernetes/typed/scheduling/v1alpha1/fake","kubernetes/typed/settings/v1alpha1","kubernetes/typed/settings/v1alpha1/fake","kubernetes/typed/storage/v1","kubernetes/typed/storage/v1/fake","kubernetes/typed/storage/v1beta1","kubernetes/typed/storage/v1beta1/fake","pkg/version","plugin/pkg/client/auth","plugin/pkg/client/auth/azure","plugin/pkg/client/auth/gcp","plugin/pkg/client/auth/oidc","plugin/pkg/client/auth/openstack","rest","rest/watch","testing","third_party/forked/golang/template","tools/auth","tools/cache","tools/cache/testing","tools/clientcmd","tools/clientcmd/api","tools/clientcmd/api/latest","tools/clientcmd/api/v1","tools/leaderelection","tools/leaderelection/resourcelock","tools/metrics","tools/pager","tools/record","tools/reference","tools/remotecommand","transport","transport/spdy","util/buffer","util/cert","util/cert/triple","util/exec","util/flowcontrol","util/homedir","util/integer","util/jsonpath","util/retry","util/workqueue"]
|
packages = ["discovery","discovery/fake","kubernetes","kubernetes/fake","kubernetes/scheme","kubernetes/typed/admissionregistration/v1alpha1","kubernetes/typed/admissionregistration/v1alpha1/fake","kubernetes/typed/apps/v1","kubernetes/typed/apps/v1/fake","kubernetes/typed/apps/v1beta1","kubernetes/typed/apps/v1beta1/fake","kubernetes/typed/apps/v1beta2","kubernetes/typed/apps/v1beta2/fake","kubernetes/typed/authentication/v1","kubernetes/typed/authentication/v1/fake","kubernetes/typed/authentication/v1beta1","kubernetes/typed/authentication/v1beta1/fake","kubernetes/typed/authorization/v1","kubernetes/typed/authorization/v1/fake","kubernetes/typed/authorization/v1beta1","kubernetes/typed/authorization/v1beta1/fake","kubernetes/typed/autoscaling/v1","kubernetes/typed/autoscaling/v1/fake","kubernetes/typed/autoscaling/v2beta1","kubernetes/typed/autoscaling/v2beta1/fake","kubernetes/typed/batch/v1","kubernetes/typed/batch/v1/fake","kubernetes/typed/batch/v1beta1","kubernetes/typed/batch/v1beta1/fake","kubernetes/typed/batch/v2alpha1","kubernetes/typed/batch/v2alpha1/fake","kubernetes/typed/certificates/v1beta1","kubernetes/typed/certificates/v1beta1/fake","kubernetes/typed/core/v1","kubernetes/typed/core/v1/fake","kubernetes/typed/extensions/v1beta1","kubernetes/typed/extensions/v1beta1/fake","kubernetes/typed/networking/v1","kubernetes/typed/networking/v1/fake","kubernetes/typed/policy/v1beta1","kubernetes/typed/policy/v1beta1/fake","kubernetes/typed/rbac/v1","kubernetes/typed/rbac/v1/fake","kubernetes/typed/rbac/v1alpha1","kubernetes/typed/rbac/v1alpha1/fake","kubernetes/typed/rbac/v1beta1","kubernetes/typed/rbac/v1beta1/fake","kubernetes/typed/scheduling/v1alpha1","kubernetes/typed/scheduling/v1alpha1/fake","kubernetes/typed/settings/v1alpha1","kubernetes/typed/settings/v1alpha1/fake","kubernetes/typed/storage/v1","kubernetes/typed/storage/v1/fake","kubernetes/typed/storage/v1beta1","kubernetes/typed/storage/v1beta1/fake","pkg/version","plugin/pkg/client/auth","plugin/pkg/client/auth/azure","plugin/pkg/client/auth/gcp","plugin/pkg/client/auth/oidc","plugin/pkg/client/auth/openstack","rest","rest/watch","testing","third_party/forked/golang/template","tools/auth","tools/cache","tools/clientcmd","tools/clientcmd/api","tools/clientcmd/api/latest","tools/clientcmd/api/v1","tools/leaderelection","tools/leaderelection/resourcelock","tools/metrics","tools/pager","tools/record","tools/reference","tools/remotecommand","transport","transport/spdy","util/buffer","util/cert","util/cert/triple","util/exec","util/flowcontrol","util/homedir","util/integer","util/jsonpath","util/retry","util/workqueue"]
|
||||||
revision = "076e344c86e52f088b78615f815b245f6d613537"
|
revision = "076e344c86e52f088b78615f815b245f6d613537"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
|
@ -432,7 +444,7 @@
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "k8s.io/kubernetes"
|
name = "k8s.io/kubernetes"
|
||||||
packages = ["pkg/api","pkg/api/helper","pkg/api/install","pkg/api/service","pkg/api/v1","pkg/api/v1/helper","pkg/api/v1/pod","pkg/api/validation","pkg/apis/extensions","pkg/apis/networking","pkg/capabilities","pkg/cloudprovider","pkg/controller","pkg/features","pkg/kubelet/apis","pkg/kubelet/apis/cri/v1alpha1/runtime","pkg/kubelet/container","pkg/kubelet/types","pkg/kubelet/util/format","pkg/kubelet/util/ioutils","pkg/kubelet/util/sliceutils","pkg/security/apparmor","pkg/serviceaccount","pkg/util/file","pkg/util/hash","pkg/util/io","pkg/util/mount","pkg/util/net/sets","pkg/util/nsenter","pkg/util/parsers","pkg/util/pointer","pkg/util/sysctl","pkg/util/taints","pkg/volume","pkg/volume/util","third_party/forked/golang/expansion"]
|
packages = ["pkg/api","pkg/api/helper","pkg/api/install","pkg/api/service","pkg/api/v1","pkg/api/v1/helper","pkg/api/v1/pod","pkg/api/validation","pkg/apis/extensions","pkg/apis/networking","pkg/capabilities","pkg/cloudprovider","pkg/controller","pkg/features","pkg/kubelet/apis","pkg/kubelet/apis/cri/v1alpha1/runtime","pkg/kubelet/container","pkg/kubelet/types","pkg/kubelet/util/format","pkg/kubelet/util/ioutils","pkg/kubelet/util/sliceutils","pkg/security/apparmor","pkg/serviceaccount","pkg/util/file","pkg/util/filesystem","pkg/util/hash","pkg/util/io","pkg/util/mount","pkg/util/net/sets","pkg/util/nsenter","pkg/util/parsers","pkg/util/pointer","pkg/util/sysctl","pkg/util/taints","pkg/volume","pkg/volume/util","third_party/forked/golang/expansion"]
|
||||||
revision = "88975e98d6f4a84929a243abeb772de16399a4e9"
|
revision = "88975e98d6f4a84929a243abeb772de16399a4e9"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
|
@ -444,6 +456,6 @@
|
||||||
[solve-meta]
|
[solve-meta]
|
||||||
analyzer-name = "dep"
|
analyzer-name = "dep"
|
||||||
analyzer-version = 1
|
analyzer-version = 1
|
||||||
inputs-digest = "e4478c928d7b640014a4b33204ea0684e5ea543b24094a1a4a35a416446beb9b"
|
inputs-digest = "6afb98c9a0bdde143167e3ad0ac1735954c2aa4ca5c60402198afbf57a0f953f"
|
||||||
solver-name = "gps-cdcl"
|
solver-name = "gps-cdcl"
|
||||||
solver-version = 1
|
solver-version = 1
|
||||||
|
|
|
@ -18,22 +18,21 @@ package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/golang/glog"
|
|
||||||
"github.com/ncabatoff/process-exporter/proc"
|
"github.com/ncabatoff/process-exporter/proc"
|
||||||
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Name returns the healthcheck name
|
// Name returns the healthcheck name
|
||||||
func (n NGINXController) Name() string {
|
func (n NGINXController) Name() string {
|
||||||
return "Ingress Controller"
|
return "nginx-ingress-controller"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check returns if the nginx healthz endpoint is returning ok (status code 200)
|
// Check returns if the nginx healthz endpoint is returning ok (status code 200)
|
||||||
func (n NGINXController) Check(_ *http.Request) error {
|
func (n *NGINXController) Check(_ *http.Request) error {
|
||||||
res, err := http.Get(fmt.Sprintf("http://0.0.0.0:%v%v", n.cfg.ListenPorts.Status, ngxHealthPath))
|
res, err := http.Get(fmt.Sprintf("http://0.0.0.0:%v%v", n.cfg.ListenPorts.Status, ngxHealthPath))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -46,23 +45,17 @@ func (n NGINXController) Check(_ *http.Request) error {
|
||||||
// check the nginx master process is running
|
// check the nginx master process is running
|
||||||
fs, err := proc.NewFS("/proc")
|
fs, err := proc.NewFS("/proc")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Errorf("%v", err)
|
return errors.Wrap(err, "unexpected error reading /proc directory")
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
f, err := ioutil.ReadFile("/run/nginx.pid")
|
f, err := n.fileSystem.ReadFile("/run/nginx.pid")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Errorf("%v", err)
|
return errors.Wrap(err, "unexpected error reading /run/nginx.pid")
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
pid, err := strconv.Atoi(strings.TrimRight(string(f), "\r\n"))
|
pid, err := strconv.Atoi(strings.TrimRight(string(f), "\r\n"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return errors.Wrap(err, "unexpected error reading the PID from /run/nginx.pid")
|
||||||
}
|
}
|
||||||
_, err = fs.NewProc(pid)
|
_, err = fs.NewProc(pid)
|
||||||
if err != nil {
|
|
||||||
glog.Errorf("%v", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return err
|
||||||
}
|
}
|
||||||
|
|
137
pkg/ingress/controller/checker_test.go
Normal file
137
pkg/ingress/controller/checker_test.go
Normal file
|
@ -0,0 +1,137 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"os/exec"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"k8s.io/apiserver/pkg/server/healthz"
|
||||||
|
"k8s.io/kubernetes/pkg/util/filesystem"
|
||||||
|
|
||||||
|
ngx_config "k8s.io/ingress-nginx/pkg/ingress/controller/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNginxCheck(t *testing.T) {
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
fmt.Fprintf(w, "ok")
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
// port to be used in the check
|
||||||
|
p := server.Listener.Addr().(*net.TCPAddr).Port
|
||||||
|
|
||||||
|
// mock filesystem
|
||||||
|
fs := filesystem.NewFakeFs()
|
||||||
|
|
||||||
|
n := &NGINXController{
|
||||||
|
cfg: &Configuration{
|
||||||
|
ListenPorts: &ngx_config.ListenPorts{
|
||||||
|
Status: p,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fileSystem: fs,
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("no pid or process", func(t *testing.T) {
|
||||||
|
if err := callHealthz(true, mux); err == nil {
|
||||||
|
t.Errorf("expected an error but none returned")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// create required files
|
||||||
|
fs.MkdirAll("/run", 0655)
|
||||||
|
pidFile, err := fs.Create("/run/nginx.pid")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("no process", func(t *testing.T) {
|
||||||
|
if err := callHealthz(true, mux); err == nil {
|
||||||
|
t.Errorf("expected an error but none returned")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// start dummy process to use the PID
|
||||||
|
cmd := exec.Command("sleep", "3600")
|
||||||
|
cmd.Start()
|
||||||
|
pid := cmd.Process.Pid
|
||||||
|
defer cmd.Process.Kill()
|
||||||
|
go func() {
|
||||||
|
cmd.Wait()
|
||||||
|
}()
|
||||||
|
|
||||||
|
pidFile.Write([]byte(fmt.Sprintf("%v", pid)))
|
||||||
|
pidFile.Close()
|
||||||
|
|
||||||
|
healthz.InstallHandler(mux, n)
|
||||||
|
|
||||||
|
t.Run("valid request", func(t *testing.T) {
|
||||||
|
if err := callHealthz(false, mux); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
pidFile, err = fs.Create("/run/nginx.pid")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
pidFile.Write([]byte(fmt.Sprintf("%v", pid)))
|
||||||
|
pidFile.Close()
|
||||||
|
|
||||||
|
t.Run("valid request", func(t *testing.T) {
|
||||||
|
if err := callHealthz(true, mux); err == nil {
|
||||||
|
t.Errorf("expected an error but none returned")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid port", func(t *testing.T) {
|
||||||
|
n.cfg.ListenPorts.Status = 9000
|
||||||
|
if err := callHealthz(true, mux); err == nil {
|
||||||
|
t.Errorf("expected an error but none returned")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func callHealthz(expErr bool, mux *http.ServeMux) error {
|
||||||
|
req, err := http.NewRequest("GET", "http://localhost:8080/healthz", nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
mux.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
if expErr && w.Code != http.StatusInternalServerError {
|
||||||
|
return fmt.Errorf("expected an error")
|
||||||
|
}
|
||||||
|
|
||||||
|
if w.Body.String() != "ok" {
|
||||||
|
return fmt.Errorf("healthz error: %v", w.Body.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
if w.Code != http.StatusOK {
|
||||||
|
return fmt.Errorf("expected status code 200 but %v returned", w.Code)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -40,6 +40,7 @@ import (
|
||||||
v1core "k8s.io/client-go/kubernetes/typed/core/v1"
|
v1core "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||||
"k8s.io/client-go/tools/record"
|
"k8s.io/client-go/tools/record"
|
||||||
"k8s.io/client-go/util/flowcontrol"
|
"k8s.io/client-go/util/flowcontrol"
|
||||||
|
"k8s.io/kubernetes/pkg/util/filesystem"
|
||||||
|
|
||||||
"k8s.io/ingress-nginx/pkg/ingress"
|
"k8s.io/ingress-nginx/pkg/ingress"
|
||||||
"k8s.io/ingress-nginx/pkg/ingress/annotations/class"
|
"k8s.io/ingress-nginx/pkg/ingress/annotations/class"
|
||||||
|
@ -110,6 +111,8 @@ func NewNGINXController(config *Configuration) *NGINXController {
|
||||||
|
|
||||||
stopCh: make(chan struct{}),
|
stopCh: make(chan struct{}),
|
||||||
stopLock: &sync.Mutex{},
|
stopLock: &sync.Mutex{},
|
||||||
|
|
||||||
|
fileSystem: filesystem.DefaultFs{},
|
||||||
}
|
}
|
||||||
|
|
||||||
n.stats = newStatsCollector(config.Namespace, config.IngressClass, n.binary, n.cfg.ListenPorts.Status)
|
n.stats = newStatsCollector(config.Namespace, config.IngressClass, n.binary, n.cfg.ListenPorts.Status)
|
||||||
|
@ -223,6 +226,8 @@ type NGINXController struct {
|
||||||
Proxy *TCPProxy
|
Proxy *TCPProxy
|
||||||
|
|
||||||
backendDefaults defaults.Backend
|
backendDefaults defaults.Backend
|
||||||
|
|
||||||
|
fileSystem filesystem.Filesystem
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start start a new NGINX master process running in foreground.
|
// Start start a new NGINX master process running in foreground.
|
||||||
|
@ -324,7 +329,7 @@ func (n *NGINXController) Stop() error {
|
||||||
|
|
||||||
// Wait for the Nginx process disappear
|
// Wait for the Nginx process disappear
|
||||||
timer := time.NewTicker(time.Second * 1)
|
timer := time.NewTicker(time.Second * 1)
|
||||||
for _ = range timer.C {
|
for range timer.C {
|
||||||
if !process.IsNginxRunning() {
|
if !process.IsNginxRunning() {
|
||||||
glog.Info("NGINX process has stopped")
|
glog.Info("NGINX process has stopped")
|
||||||
timer.Stop()
|
timer.Stop()
|
||||||
|
|
|
@ -17,11 +17,14 @@ limitations under the License.
|
||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"syscall"
|
||||||
|
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
|
|
||||||
"github.com/imdario/mergo"
|
"github.com/imdario/mergo"
|
||||||
|
|
||||||
api "k8s.io/api/core/v1"
|
api "k8s.io/api/core/v1"
|
||||||
|
"k8s.io/kubernetes/pkg/util/sysctl"
|
||||||
|
|
||||||
"k8s.io/ingress-nginx/pkg/ingress"
|
"k8s.io/ingress-nginx/pkg/ingress"
|
||||||
)
|
)
|
||||||
|
@ -53,3 +56,38 @@ func mergeLocationAnnotations(loc *ingress.Location, anns map[string]interface{}
|
||||||
glog.Errorf("unexpected error merging extracted annotations in location type: %v", err)
|
glog.Errorf("unexpected error merging extracted annotations in location type: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// sysctlSomaxconn returns the value of net.core.somaxconn, i.e.
|
||||||
|
// maximum number of connections that can be queued for acceptance
|
||||||
|
// http://nginx.org/en/docs/http/ngx_http_core_module.html#listen
|
||||||
|
func sysctlSomaxconn() int {
|
||||||
|
maxConns, err := sysctl.New().GetSysctl("net/core/somaxconn")
|
||||||
|
if err != nil || maxConns < 512 {
|
||||||
|
glog.V(3).Infof("system net.core.somaxconn=%v (using system default)", maxConns)
|
||||||
|
return 511
|
||||||
|
}
|
||||||
|
|
||||||
|
return maxConns
|
||||||
|
}
|
||||||
|
|
||||||
|
// sysctlFSFileMax returns the value of fs.file-max, i.e.
|
||||||
|
// maximum number of open file descriptors
|
||||||
|
func sysctlFSFileMax() int {
|
||||||
|
var rLimit syscall.Rlimit
|
||||||
|
err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit)
|
||||||
|
if err != nil {
|
||||||
|
glog.Errorf("unexpected error reading system maximum number of open file descriptors (RLIMIT_NOFILE): %v", err)
|
||||||
|
// returning 0 means don't render the value
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return int(rLimit.Max)
|
||||||
|
}
|
||||||
|
|
||||||
|
func intInSlice(i int, list []int) bool {
|
||||||
|
for _, v := range list {
|
||||||
|
if v == i {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
|
@ -81,3 +81,37 @@ func TestMergeLocationAnnotations(t *testing.T) {
|
||||||
t.Errorf("%s should be removed after mergeLocationAnnotations", DeniedKeyName)
|
t.Errorf("%s should be removed after mergeLocationAnnotations", DeniedKeyName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIntInSlice(t *testing.T) {
|
||||||
|
fooTests := []struct {
|
||||||
|
i int
|
||||||
|
list []int
|
||||||
|
er bool
|
||||||
|
}{
|
||||||
|
{1, []int{1, 2}, true},
|
||||||
|
{3, []int{1, 2}, false},
|
||||||
|
{1, nil, false},
|
||||||
|
{0, nil, false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, fooTest := range fooTests {
|
||||||
|
r := intInSlice(fooTest.i, fooTest.list)
|
||||||
|
if r != fooTest.er {
|
||||||
|
t.Errorf("returned %t but expected %t for s=%v & list=%v", r, fooTest.er, fooTest.i, fooTest.list)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSysctlFSFileMax(t *testing.T) {
|
||||||
|
i := sysctlFSFileMax()
|
||||||
|
if i < 1 {
|
||||||
|
t.Errorf("returned %v but expected > 0", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSysctlSomaxconn(t *testing.T) {
|
||||||
|
i := sysctlSomaxconn()
|
||||||
|
if i < 511 {
|
||||||
|
t.Errorf("returned %v but expected >= 511", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,60 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2015 The Kubernetes Authors.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package controller
|
|
||||||
|
|
||||||
import (
|
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"github.com/golang/glog"
|
|
||||||
|
|
||||||
"k8s.io/kubernetes/pkg/util/sysctl"
|
|
||||||
)
|
|
||||||
|
|
||||||
// sysctlSomaxconn returns the value of net.core.somaxconn, i.e.
|
|
||||||
// maximum number of connections that can be queued for acceptance
|
|
||||||
// http://nginx.org/en/docs/http/ngx_http_core_module.html#listen
|
|
||||||
func sysctlSomaxconn() int {
|
|
||||||
maxConns, err := sysctl.New().GetSysctl("net/core/somaxconn")
|
|
||||||
if err != nil || maxConns < 512 {
|
|
||||||
glog.V(3).Infof("system net.core.somaxconn=%v (using system default)", maxConns)
|
|
||||||
return 511
|
|
||||||
}
|
|
||||||
|
|
||||||
return maxConns
|
|
||||||
}
|
|
||||||
|
|
||||||
// sysctlFSFileMax returns the value of fs.file-max, i.e.
|
|
||||||
// maximum number of open file descriptors
|
|
||||||
func sysctlFSFileMax() int {
|
|
||||||
var rLimit syscall.Rlimit
|
|
||||||
err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit)
|
|
||||||
if err != nil {
|
|
||||||
glog.Errorf("unexpected error reading system maximum number of open file descriptors (RLIMIT_NOFILE): %v", err)
|
|
||||||
// returning 0 means don't render the value
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return int(rLimit.Max)
|
|
||||||
}
|
|
||||||
|
|
||||||
func intInSlice(i int, list []int) bool {
|
|
||||||
for _, v := range list {
|
|
||||||
if v == i {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
5
vendor/github.com/fsnotify/fsnotify/.editorconfig
generated
vendored
Normal file
5
vendor/github.com/fsnotify/fsnotify/.editorconfig
generated
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
indent_style = tab
|
||||||
|
indent_size = 4
|
6
vendor/github.com/fsnotify/fsnotify/.gitignore
generated
vendored
Normal file
6
vendor/github.com/fsnotify/fsnotify/.gitignore
generated
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
# Setup a Global .gitignore for OS and editor generated files:
|
||||||
|
# https://help.github.com/articles/ignoring-files
|
||||||
|
# git config --global core.excludesfile ~/.gitignore_global
|
||||||
|
|
||||||
|
.vagrant
|
||||||
|
*.sublime-project
|
28
vendor/github.com/fsnotify/fsnotify/.travis.yml
generated
vendored
Normal file
28
vendor/github.com/fsnotify/fsnotify/.travis.yml
generated
vendored
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
sudo: false
|
||||||
|
language: go
|
||||||
|
|
||||||
|
go:
|
||||||
|
- 1.6.3
|
||||||
|
- tip
|
||||||
|
|
||||||
|
matrix:
|
||||||
|
allow_failures:
|
||||||
|
- go: tip
|
||||||
|
|
||||||
|
before_script:
|
||||||
|
- go get -u github.com/golang/lint/golint
|
||||||
|
|
||||||
|
script:
|
||||||
|
- go test -v --race ./...
|
||||||
|
|
||||||
|
after_script:
|
||||||
|
- test -z "$(gofmt -s -l -w . | tee /dev/stderr)"
|
||||||
|
- test -z "$(golint ./... | tee /dev/stderr)"
|
||||||
|
- go vet ./...
|
||||||
|
|
||||||
|
os:
|
||||||
|
- linux
|
||||||
|
- osx
|
||||||
|
|
||||||
|
notifications:
|
||||||
|
email: false
|
46
vendor/github.com/fsnotify/fsnotify/AUTHORS
generated
vendored
Normal file
46
vendor/github.com/fsnotify/fsnotify/AUTHORS
generated
vendored
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
# Names should be added to this file as
|
||||||
|
# Name or Organization <email address>
|
||||||
|
# The email address is not required for organizations.
|
||||||
|
|
||||||
|
# You can update this list using the following command:
|
||||||
|
#
|
||||||
|
# $ git shortlog -se | awk '{print $2 " " $3 " " $4}'
|
||||||
|
|
||||||
|
# Please keep the list sorted.
|
||||||
|
|
||||||
|
Adrien Bustany <adrien@bustany.org>
|
||||||
|
Amit Krishnan <amit.krishnan@oracle.com>
|
||||||
|
Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
|
||||||
|
Bruno Bigras <bigras.bruno@gmail.com>
|
||||||
|
Caleb Spare <cespare@gmail.com>
|
||||||
|
Case Nelson <case@teammating.com>
|
||||||
|
Chris Howey <chris@howey.me> <howeyc@gmail.com>
|
||||||
|
Christoffer Buchholz <christoffer.buchholz@gmail.com>
|
||||||
|
Daniel Wagner-Hall <dawagner@gmail.com>
|
||||||
|
Dave Cheney <dave@cheney.net>
|
||||||
|
Evan Phoenix <evan@fallingsnow.net>
|
||||||
|
Francisco Souza <f@souza.cc>
|
||||||
|
Hari haran <hariharan.uno@gmail.com>
|
||||||
|
John C Barstow
|
||||||
|
Kelvin Fo <vmirage@gmail.com>
|
||||||
|
Ken-ichirou MATSUZAWA <chamas@h4.dion.ne.jp>
|
||||||
|
Matt Layher <mdlayher@gmail.com>
|
||||||
|
Nathan Youngman <git@nathany.com>
|
||||||
|
Patrick <patrick@dropbox.com>
|
||||||
|
Paul Hammond <paul@paulhammond.org>
|
||||||
|
Pawel Knap <pawelknap88@gmail.com>
|
||||||
|
Pieter Droogendijk <pieter@binky.org.uk>
|
||||||
|
Pursuit92 <JoshChase@techpursuit.net>
|
||||||
|
Riku Voipio <riku.voipio@linaro.org>
|
||||||
|
Rob Figueiredo <robfig@gmail.com>
|
||||||
|
Slawek Ligus <root@ooz.ie>
|
||||||
|
Soge Zhang <zhssoge@gmail.com>
|
||||||
|
Tiffany Jernigan <tiffany.jernigan@intel.com>
|
||||||
|
Tilak Sharma <tilaks@google.com>
|
||||||
|
Travis Cline <travis.cline@gmail.com>
|
||||||
|
Tudor Golubenco <tudor.g@gmail.com>
|
||||||
|
Yukang <moorekang@gmail.com>
|
||||||
|
bronze1man <bronze1man@gmail.com>
|
||||||
|
debrando <denis.brandolini@gmail.com>
|
||||||
|
henrikedwards <henrik.edwards@gmail.com>
|
||||||
|
铁哥 <guotie.9@gmail.com>
|
307
vendor/github.com/fsnotify/fsnotify/CHANGELOG.md
generated
vendored
Normal file
307
vendor/github.com/fsnotify/fsnotify/CHANGELOG.md
generated
vendored
Normal file
|
@ -0,0 +1,307 @@
|
||||||
|
# Changelog
|
||||||
|
|
||||||
|
## v1.4.2 / 2016-10-10
|
||||||
|
|
||||||
|
* Linux: use InotifyInit1 with IN_CLOEXEC to stop leaking a file descriptor to a child process when using fork/exec [#178](https://github.com/fsnotify/fsnotify/pull/178) (thanks @pattyshack)
|
||||||
|
|
||||||
|
## v1.4.1 / 2016-10-04
|
||||||
|
|
||||||
|
* Fix flaky inotify stress test on Linux [#177](https://github.com/fsnotify/fsnotify/pull/177) (thanks @pattyshack)
|
||||||
|
|
||||||
|
## v1.4.0 / 2016-10-01
|
||||||
|
|
||||||
|
* add a String() method to Event.Op [#165](https://github.com/fsnotify/fsnotify/pull/165) (thanks @oozie)
|
||||||
|
|
||||||
|
## v1.3.1 / 2016-06-28
|
||||||
|
|
||||||
|
* Windows: fix for double backslash when watching the root of a drive [#151](https://github.com/fsnotify/fsnotify/issues/151) (thanks @brunoqc)
|
||||||
|
|
||||||
|
## v1.3.0 / 2016-04-19
|
||||||
|
|
||||||
|
* Support linux/arm64 by [patching](https://go-review.googlesource.com/#/c/21971/) x/sys/unix and switching to to it from syscall (thanks @suihkulokki) [#135](https://github.com/fsnotify/fsnotify/pull/135)
|
||||||
|
|
||||||
|
## v1.2.10 / 2016-03-02
|
||||||
|
|
||||||
|
* Fix golint errors in windows.go [#121](https://github.com/fsnotify/fsnotify/pull/121) (thanks @tiffanyfj)
|
||||||
|
|
||||||
|
## v1.2.9 / 2016-01-13
|
||||||
|
|
||||||
|
kqueue: Fix logic for CREATE after REMOVE [#111](https://github.com/fsnotify/fsnotify/pull/111) (thanks @bep)
|
||||||
|
|
||||||
|
## v1.2.8 / 2015-12-17
|
||||||
|
|
||||||
|
* kqueue: fix race condition in Close [#105](https://github.com/fsnotify/fsnotify/pull/105) (thanks @djui for reporting the issue and @ppknap for writing a failing test)
|
||||||
|
* inotify: fix race in test
|
||||||
|
* enable race detection for continuous integration (Linux, Mac, Windows)
|
||||||
|
|
||||||
|
## v1.2.5 / 2015-10-17
|
||||||
|
|
||||||
|
* inotify: use epoll_create1 for arm64 support (requires Linux 2.6.27 or later) [#100](https://github.com/fsnotify/fsnotify/pull/100) (thanks @suihkulokki)
|
||||||
|
* inotify: fix path leaks [#73](https://github.com/fsnotify/fsnotify/pull/73) (thanks @chamaken)
|
||||||
|
* kqueue: watch for rename events on subdirectories [#83](https://github.com/fsnotify/fsnotify/pull/83) (thanks @guotie)
|
||||||
|
* kqueue: avoid infinite loops from symlinks cycles [#101](https://github.com/fsnotify/fsnotify/pull/101) (thanks @illicitonion)
|
||||||
|
|
||||||
|
## v1.2.1 / 2015-10-14
|
||||||
|
|
||||||
|
* kqueue: don't watch named pipes [#98](https://github.com/fsnotify/fsnotify/pull/98) (thanks @evanphx)
|
||||||
|
|
||||||
|
## v1.2.0 / 2015-02-08
|
||||||
|
|
||||||
|
* inotify: use epoll to wake up readEvents [#66](https://github.com/fsnotify/fsnotify/pull/66) (thanks @PieterD)
|
||||||
|
* inotify: closing watcher should now always shut down goroutine [#63](https://github.com/fsnotify/fsnotify/pull/63) (thanks @PieterD)
|
||||||
|
* kqueue: close kqueue after removing watches, fixes [#59](https://github.com/fsnotify/fsnotify/issues/59)
|
||||||
|
|
||||||
|
## v1.1.1 / 2015-02-05
|
||||||
|
|
||||||
|
* inotify: Retry read on EINTR [#61](https://github.com/fsnotify/fsnotify/issues/61) (thanks @PieterD)
|
||||||
|
|
||||||
|
## v1.1.0 / 2014-12-12
|
||||||
|
|
||||||
|
* kqueue: rework internals [#43](https://github.com/fsnotify/fsnotify/pull/43)
|
||||||
|
* add low-level functions
|
||||||
|
* only need to store flags on directories
|
||||||
|
* less mutexes [#13](https://github.com/fsnotify/fsnotify/issues/13)
|
||||||
|
* done can be an unbuffered channel
|
||||||
|
* remove calls to os.NewSyscallError
|
||||||
|
* More efficient string concatenation for Event.String() [#52](https://github.com/fsnotify/fsnotify/pull/52) (thanks @mdlayher)
|
||||||
|
* kqueue: fix regression in rework causing subdirectories to be watched [#48](https://github.com/fsnotify/fsnotify/issues/48)
|
||||||
|
* kqueue: cleanup internal watch before sending remove event [#51](https://github.com/fsnotify/fsnotify/issues/51)
|
||||||
|
|
||||||
|
## v1.0.4 / 2014-09-07
|
||||||
|
|
||||||
|
* kqueue: add dragonfly to the build tags.
|
||||||
|
* Rename source code files, rearrange code so exported APIs are at the top.
|
||||||
|
* Add done channel to example code. [#37](https://github.com/fsnotify/fsnotify/pull/37) (thanks @chenyukang)
|
||||||
|
|
||||||
|
## v1.0.3 / 2014-08-19
|
||||||
|
|
||||||
|
* [Fix] Windows MOVED_TO now translates to Create like on BSD and Linux. [#36](https://github.com/fsnotify/fsnotify/issues/36)
|
||||||
|
|
||||||
|
## v1.0.2 / 2014-08-17
|
||||||
|
|
||||||
|
* [Fix] Missing create events on OS X. [#14](https://github.com/fsnotify/fsnotify/issues/14) (thanks @zhsso)
|
||||||
|
* [Fix] Make ./path and path equivalent. (thanks @zhsso)
|
||||||
|
|
||||||
|
## v1.0.0 / 2014-08-15
|
||||||
|
|
||||||
|
* [API] Remove AddWatch on Windows, use Add.
|
||||||
|
* Improve documentation for exported identifiers. [#30](https://github.com/fsnotify/fsnotify/issues/30)
|
||||||
|
* Minor updates based on feedback from golint.
|
||||||
|
|
||||||
|
## dev / 2014-07-09
|
||||||
|
|
||||||
|
* Moved to [github.com/fsnotify/fsnotify](https://github.com/fsnotify/fsnotify).
|
||||||
|
* Use os.NewSyscallError instead of returning errno (thanks @hariharan-uno)
|
||||||
|
|
||||||
|
## dev / 2014-07-04
|
||||||
|
|
||||||
|
* kqueue: fix incorrect mutex used in Close()
|
||||||
|
* Update example to demonstrate usage of Op.
|
||||||
|
|
||||||
|
## dev / 2014-06-28
|
||||||
|
|
||||||
|
* [API] Don't set the Write Op for attribute notifications [#4](https://github.com/fsnotify/fsnotify/issues/4)
|
||||||
|
* Fix for String() method on Event (thanks Alex Brainman)
|
||||||
|
* Don't build on Plan 9 or Solaris (thanks @4ad)
|
||||||
|
|
||||||
|
## dev / 2014-06-21
|
||||||
|
|
||||||
|
* Events channel of type Event rather than *Event.
|
||||||
|
* [internal] use syscall constants directly for inotify and kqueue.
|
||||||
|
* [internal] kqueue: rename events to kevents and fileEvent to event.
|
||||||
|
|
||||||
|
## dev / 2014-06-19
|
||||||
|
|
||||||
|
* Go 1.3+ required on Windows (uses syscall.ERROR_MORE_DATA internally).
|
||||||
|
* [internal] remove cookie from Event struct (unused).
|
||||||
|
* [internal] Event struct has the same definition across every OS.
|
||||||
|
* [internal] remove internal watch and removeWatch methods.
|
||||||
|
|
||||||
|
## dev / 2014-06-12
|
||||||
|
|
||||||
|
* [API] Renamed Watch() to Add() and RemoveWatch() to Remove().
|
||||||
|
* [API] Pluralized channel names: Events and Errors.
|
||||||
|
* [API] Renamed FileEvent struct to Event.
|
||||||
|
* [API] Op constants replace methods like IsCreate().
|
||||||
|
|
||||||
|
## dev / 2014-06-12
|
||||||
|
|
||||||
|
* Fix data race on kevent buffer (thanks @tilaks) [#98](https://github.com/howeyc/fsnotify/pull/98)
|
||||||
|
|
||||||
|
## dev / 2014-05-23
|
||||||
|
|
||||||
|
* [API] Remove current implementation of WatchFlags.
|
||||||
|
* current implementation doesn't take advantage of OS for efficiency
|
||||||
|
* provides little benefit over filtering events as they are received, but has extra bookkeeping and mutexes
|
||||||
|
* no tests for the current implementation
|
||||||
|
* not fully implemented on Windows [#93](https://github.com/howeyc/fsnotify/issues/93#issuecomment-39285195)
|
||||||
|
|
||||||
|
## v0.9.3 / 2014-12-31
|
||||||
|
|
||||||
|
* kqueue: cleanup internal watch before sending remove event [#51](https://github.com/fsnotify/fsnotify/issues/51)
|
||||||
|
|
||||||
|
## v0.9.2 / 2014-08-17
|
||||||
|
|
||||||
|
* [Backport] Fix missing create events on OS X. [#14](https://github.com/fsnotify/fsnotify/issues/14) (thanks @zhsso)
|
||||||
|
|
||||||
|
## v0.9.1 / 2014-06-12
|
||||||
|
|
||||||
|
* Fix data race on kevent buffer (thanks @tilaks) [#98](https://github.com/howeyc/fsnotify/pull/98)
|
||||||
|
|
||||||
|
## v0.9.0 / 2014-01-17
|
||||||
|
|
||||||
|
* IsAttrib() for events that only concern a file's metadata [#79][] (thanks @abustany)
|
||||||
|
* [Fix] kqueue: fix deadlock [#77][] (thanks @cespare)
|
||||||
|
* [NOTICE] Development has moved to `code.google.com/p/go.exp/fsnotify` in preparation for inclusion in the Go standard library.
|
||||||
|
|
||||||
|
## v0.8.12 / 2013-11-13
|
||||||
|
|
||||||
|
* [API] Remove FD_SET and friends from Linux adapter
|
||||||
|
|
||||||
|
## v0.8.11 / 2013-11-02
|
||||||
|
|
||||||
|
* [Doc] Add Changelog [#72][] (thanks @nathany)
|
||||||
|
* [Doc] Spotlight and double modify events on OS X [#62][] (reported by @paulhammond)
|
||||||
|
|
||||||
|
## v0.8.10 / 2013-10-19
|
||||||
|
|
||||||
|
* [Fix] kqueue: remove file watches when parent directory is removed [#71][] (reported by @mdwhatcott)
|
||||||
|
* [Fix] kqueue: race between Close and readEvents [#70][] (reported by @bernerdschaefer)
|
||||||
|
* [Doc] specify OS-specific limits in README (thanks @debrando)
|
||||||
|
|
||||||
|
## v0.8.9 / 2013-09-08
|
||||||
|
|
||||||
|
* [Doc] Contributing (thanks @nathany)
|
||||||
|
* [Doc] update package path in example code [#63][] (thanks @paulhammond)
|
||||||
|
* [Doc] GoCI badge in README (Linux only) [#60][]
|
||||||
|
* [Doc] Cross-platform testing with Vagrant [#59][] (thanks @nathany)
|
||||||
|
|
||||||
|
## v0.8.8 / 2013-06-17
|
||||||
|
|
||||||
|
* [Fix] Windows: handle `ERROR_MORE_DATA` on Windows [#49][] (thanks @jbowtie)
|
||||||
|
|
||||||
|
## v0.8.7 / 2013-06-03
|
||||||
|
|
||||||
|
* [API] Make syscall flags internal
|
||||||
|
* [Fix] inotify: ignore event changes
|
||||||
|
* [Fix] race in symlink test [#45][] (reported by @srid)
|
||||||
|
* [Fix] tests on Windows
|
||||||
|
* lower case error messages
|
||||||
|
|
||||||
|
## v0.8.6 / 2013-05-23
|
||||||
|
|
||||||
|
* kqueue: Use EVT_ONLY flag on Darwin
|
||||||
|
* [Doc] Update README with full example
|
||||||
|
|
||||||
|
## v0.8.5 / 2013-05-09
|
||||||
|
|
||||||
|
* [Fix] inotify: allow monitoring of "broken" symlinks (thanks @tsg)
|
||||||
|
|
||||||
|
## v0.8.4 / 2013-04-07
|
||||||
|
|
||||||
|
* [Fix] kqueue: watch all file events [#40][] (thanks @ChrisBuchholz)
|
||||||
|
|
||||||
|
## v0.8.3 / 2013-03-13
|
||||||
|
|
||||||
|
* [Fix] inoitfy/kqueue memory leak [#36][] (reported by @nbkolchin)
|
||||||
|
* [Fix] kqueue: use fsnFlags for watching a directory [#33][] (reported by @nbkolchin)
|
||||||
|
|
||||||
|
## v0.8.2 / 2013-02-07
|
||||||
|
|
||||||
|
* [Doc] add Authors
|
||||||
|
* [Fix] fix data races for map access [#29][] (thanks @fsouza)
|
||||||
|
|
||||||
|
## v0.8.1 / 2013-01-09
|
||||||
|
|
||||||
|
* [Fix] Windows path separators
|
||||||
|
* [Doc] BSD License
|
||||||
|
|
||||||
|
## v0.8.0 / 2012-11-09
|
||||||
|
|
||||||
|
* kqueue: directory watching improvements (thanks @vmirage)
|
||||||
|
* inotify: add `IN_MOVED_TO` [#25][] (requested by @cpisto)
|
||||||
|
* [Fix] kqueue: deleting watched directory [#24][] (reported by @jakerr)
|
||||||
|
|
||||||
|
## v0.7.4 / 2012-10-09
|
||||||
|
|
||||||
|
* [Fix] inotify: fixes from https://codereview.appspot.com/5418045/ (ugorji)
|
||||||
|
* [Fix] kqueue: preserve watch flags when watching for delete [#21][] (reported by @robfig)
|
||||||
|
* [Fix] kqueue: watch the directory even if it isn't a new watch (thanks @robfig)
|
||||||
|
* [Fix] kqueue: modify after recreation of file
|
||||||
|
|
||||||
|
## v0.7.3 / 2012-09-27
|
||||||
|
|
||||||
|
* [Fix] kqueue: watch with an existing folder inside the watched folder (thanks @vmirage)
|
||||||
|
* [Fix] kqueue: no longer get duplicate CREATE events
|
||||||
|
|
||||||
|
## v0.7.2 / 2012-09-01
|
||||||
|
|
||||||
|
* kqueue: events for created directories
|
||||||
|
|
||||||
|
## v0.7.1 / 2012-07-14
|
||||||
|
|
||||||
|
* [Fix] for renaming files
|
||||||
|
|
||||||
|
## v0.7.0 / 2012-07-02
|
||||||
|
|
||||||
|
* [Feature] FSNotify flags
|
||||||
|
* [Fix] inotify: Added file name back to event path
|
||||||
|
|
||||||
|
## v0.6.0 / 2012-06-06
|
||||||
|
|
||||||
|
* kqueue: watch files after directory created (thanks @tmc)
|
||||||
|
|
||||||
|
## v0.5.1 / 2012-05-22
|
||||||
|
|
||||||
|
* [Fix] inotify: remove all watches before Close()
|
||||||
|
|
||||||
|
## v0.5.0 / 2012-05-03
|
||||||
|
|
||||||
|
* [API] kqueue: return errors during watch instead of sending over channel
|
||||||
|
* kqueue: match symlink behavior on Linux
|
||||||
|
* inotify: add `DELETE_SELF` (requested by @taralx)
|
||||||
|
* [Fix] kqueue: handle EINTR (reported by @robfig)
|
||||||
|
* [Doc] Godoc example [#1][] (thanks @davecheney)
|
||||||
|
|
||||||
|
## v0.4.0 / 2012-03-30
|
||||||
|
|
||||||
|
* Go 1 released: build with go tool
|
||||||
|
* [Feature] Windows support using winfsnotify
|
||||||
|
* Windows does not have attribute change notifications
|
||||||
|
* Roll attribute notifications into IsModify
|
||||||
|
|
||||||
|
## v0.3.0 / 2012-02-19
|
||||||
|
|
||||||
|
* kqueue: add files when watch directory
|
||||||
|
|
||||||
|
## v0.2.0 / 2011-12-30
|
||||||
|
|
||||||
|
* update to latest Go weekly code
|
||||||
|
|
||||||
|
## v0.1.0 / 2011-10-19
|
||||||
|
|
||||||
|
* kqueue: add watch on file creation to match inotify
|
||||||
|
* kqueue: create file event
|
||||||
|
* inotify: ignore `IN_IGNORED` events
|
||||||
|
* event String()
|
||||||
|
* linux: common FileEvent functions
|
||||||
|
* initial commit
|
||||||
|
|
||||||
|
[#79]: https://github.com/howeyc/fsnotify/pull/79
|
||||||
|
[#77]: https://github.com/howeyc/fsnotify/pull/77
|
||||||
|
[#72]: https://github.com/howeyc/fsnotify/issues/72
|
||||||
|
[#71]: https://github.com/howeyc/fsnotify/issues/71
|
||||||
|
[#70]: https://github.com/howeyc/fsnotify/issues/70
|
||||||
|
[#63]: https://github.com/howeyc/fsnotify/issues/63
|
||||||
|
[#62]: https://github.com/howeyc/fsnotify/issues/62
|
||||||
|
[#60]: https://github.com/howeyc/fsnotify/issues/60
|
||||||
|
[#59]: https://github.com/howeyc/fsnotify/issues/59
|
||||||
|
[#49]: https://github.com/howeyc/fsnotify/issues/49
|
||||||
|
[#45]: https://github.com/howeyc/fsnotify/issues/45
|
||||||
|
[#40]: https://github.com/howeyc/fsnotify/issues/40
|
||||||
|
[#36]: https://github.com/howeyc/fsnotify/issues/36
|
||||||
|
[#33]: https://github.com/howeyc/fsnotify/issues/33
|
||||||
|
[#29]: https://github.com/howeyc/fsnotify/issues/29
|
||||||
|
[#25]: https://github.com/howeyc/fsnotify/issues/25
|
||||||
|
[#24]: https://github.com/howeyc/fsnotify/issues/24
|
||||||
|
[#21]: https://github.com/howeyc/fsnotify/issues/21
|
77
vendor/github.com/fsnotify/fsnotify/CONTRIBUTING.md
generated
vendored
Normal file
77
vendor/github.com/fsnotify/fsnotify/CONTRIBUTING.md
generated
vendored
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
# Contributing
|
||||||
|
|
||||||
|
## Issues
|
||||||
|
|
||||||
|
* Request features and report bugs using the [GitHub Issue Tracker](https://github.com/fsnotify/fsnotify/issues).
|
||||||
|
* Please indicate the platform you are using fsnotify on.
|
||||||
|
* A code example to reproduce the problem is appreciated.
|
||||||
|
|
||||||
|
## Pull Requests
|
||||||
|
|
||||||
|
### Contributor License Agreement
|
||||||
|
|
||||||
|
fsnotify is derived from code in the [golang.org/x/exp](https://godoc.org/golang.org/x/exp) package and it may be included [in the standard library](https://github.com/fsnotify/fsnotify/issues/1) in the future. Therefore fsnotify carries the same [LICENSE](https://github.com/fsnotify/fsnotify/blob/master/LICENSE) as Go. Contributors retain their copyright, so you need to fill out a short form before we can accept your contribution: [Google Individual Contributor License Agreement](https://developers.google.com/open-source/cla/individual).
|
||||||
|
|
||||||
|
Please indicate that you have signed the CLA in your pull request.
|
||||||
|
|
||||||
|
### How fsnotify is Developed
|
||||||
|
|
||||||
|
* Development is done on feature branches.
|
||||||
|
* Tests are run on BSD, Linux, OS X and Windows.
|
||||||
|
* Pull requests are reviewed and [applied to master][am] using [hub][].
|
||||||
|
* Maintainers may modify or squash commits rather than asking contributors to.
|
||||||
|
* To issue a new release, the maintainers will:
|
||||||
|
* Update the CHANGELOG
|
||||||
|
* Tag a version, which will become available through gopkg.in.
|
||||||
|
|
||||||
|
### How to Fork
|
||||||
|
|
||||||
|
For smooth sailing, always use the original import path. Installing with `go get` makes this easy.
|
||||||
|
|
||||||
|
1. Install from GitHub (`go get -u github.com/fsnotify/fsnotify`)
|
||||||
|
2. Create your feature branch (`git checkout -b my-new-feature`)
|
||||||
|
3. Ensure everything works and the tests pass (see below)
|
||||||
|
4. Commit your changes (`git commit -am 'Add some feature'`)
|
||||||
|
|
||||||
|
Contribute upstream:
|
||||||
|
|
||||||
|
1. Fork fsnotify on GitHub
|
||||||
|
2. Add your remote (`git remote add fork git@github.com:mycompany/repo.git`)
|
||||||
|
3. Push to the branch (`git push fork my-new-feature`)
|
||||||
|
4. Create a new Pull Request on GitHub
|
||||||
|
|
||||||
|
This workflow is [thoroughly explained by Katrina Owen](https://splice.com/blog/contributing-open-source-git-repositories-go/).
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
|
||||||
|
fsnotify uses build tags to compile different code on Linux, BSD, OS X, and Windows.
|
||||||
|
|
||||||
|
Before doing a pull request, please do your best to test your changes on multiple platforms, and list which platforms you were able/unable to test on.
|
||||||
|
|
||||||
|
To aid in cross-platform testing there is a Vagrantfile for Linux and BSD.
|
||||||
|
|
||||||
|
* Install [Vagrant](http://www.vagrantup.com/) and [VirtualBox](https://www.virtualbox.org/)
|
||||||
|
* Setup [Vagrant Gopher](https://github.com/nathany/vagrant-gopher) in your `src` folder.
|
||||||
|
* Run `vagrant up` from the project folder. You can also setup just one box with `vagrant up linux` or `vagrant up bsd` (note: the BSD box doesn't support Windows hosts at this time, and NFS may prompt for your host OS password)
|
||||||
|
* Once setup, you can run the test suite on a given OS with a single command `vagrant ssh linux -c 'cd fsnotify/fsnotify; go test'`.
|
||||||
|
* When you're done, you will want to halt or destroy the Vagrant boxes.
|
||||||
|
|
||||||
|
Notice: fsnotify file system events won't trigger in shared folders. The tests get around this limitation by using the /tmp directory.
|
||||||
|
|
||||||
|
Right now there is no equivalent solution for Windows and OS X, but there are Windows VMs [freely available from Microsoft](http://www.modern.ie/en-us/virtualization-tools#downloads).
|
||||||
|
|
||||||
|
### Maintainers
|
||||||
|
|
||||||
|
Help maintaining fsnotify is welcome. To be a maintainer:
|
||||||
|
|
||||||
|
* Submit a pull request and sign the CLA as above.
|
||||||
|
* You must be able to run the test suite on Mac, Windows, Linux and BSD.
|
||||||
|
|
||||||
|
To keep master clean, the fsnotify project uses the "apply mail" workflow outlined in Nathaniel Talbott's post ["Merge pull request" Considered Harmful][am]. This requires installing [hub][].
|
||||||
|
|
||||||
|
All code changes should be internal pull requests.
|
||||||
|
|
||||||
|
Releases are tagged using [Semantic Versioning](http://semver.org/).
|
||||||
|
|
||||||
|
[hub]: https://github.com/github/hub
|
||||||
|
[am]: http://blog.spreedly.com/2014/06/24/merge-pull-request-considered-harmful/#.VGa5yZPF_Zs
|
28
vendor/github.com/fsnotify/fsnotify/LICENSE
generated
vendored
Normal file
28
vendor/github.com/fsnotify/fsnotify/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
Copyright (c) 2012 The Go Authors. All rights reserved.
|
||||||
|
Copyright (c) 2012 fsnotify 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.
|
50
vendor/github.com/fsnotify/fsnotify/README.md
generated
vendored
Normal file
50
vendor/github.com/fsnotify/fsnotify/README.md
generated
vendored
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
# File system notifications for Go
|
||||||
|
|
||||||
|
[](https://godoc.org/github.com/fsnotify/fsnotify) [](https://goreportcard.com/report/github.com/fsnotify/fsnotify)
|
||||||
|
|
||||||
|
fsnotify utilizes [golang.org/x/sys](https://godoc.org/golang.org/x/sys) rather than `syscall` from the standard library. Ensure you have the latest version installed by running:
|
||||||
|
|
||||||
|
```console
|
||||||
|
go get -u golang.org/x/sys/...
|
||||||
|
```
|
||||||
|
|
||||||
|
Cross platform: Windows, Linux, BSD and OS X.
|
||||||
|
|
||||||
|
|Adapter |OS |Status |
|
||||||
|
|----------|----------|----------|
|
||||||
|
|inotify |Linux 2.6.27 or later, Android\*|Supported [](https://travis-ci.org/fsnotify/fsnotify)|
|
||||||
|
|kqueue |BSD, OS X, iOS\*|Supported [](https://travis-ci.org/fsnotify/fsnotify)|
|
||||||
|
|ReadDirectoryChangesW|Windows|Supported [](https://ci.appveyor.com/project/NathanYoungman/fsnotify/branch/master)|
|
||||||
|
|FSEvents |OS X |[Planned](https://github.com/fsnotify/fsnotify/issues/11)|
|
||||||
|
|FEN |Solaris 11 |[In Progress](https://github.com/fsnotify/fsnotify/issues/12)|
|
||||||
|
|fanotify |Linux 2.6.37+ | |
|
||||||
|
|USN Journals |Windows |[Maybe](https://github.com/fsnotify/fsnotify/issues/53)|
|
||||||
|
|Polling |*All* |[Maybe](https://github.com/fsnotify/fsnotify/issues/9)|
|
||||||
|
|
||||||
|
\* Android and iOS are untested.
|
||||||
|
|
||||||
|
Please see [the documentation](https://godoc.org/github.com/fsnotify/fsnotify) for usage. Consult the [Wiki](https://github.com/fsnotify/fsnotify/wiki) for the FAQ and further information.
|
||||||
|
|
||||||
|
## API stability
|
||||||
|
|
||||||
|
fsnotify is a fork of [howeyc/fsnotify](https://godoc.org/github.com/howeyc/fsnotify) with a new API as of v1.0. The API is based on [this design document](http://goo.gl/MrYxyA).
|
||||||
|
|
||||||
|
All [releases](https://github.com/fsnotify/fsnotify/releases) are tagged based on [Semantic Versioning](http://semver.org/). Further API changes are [planned](https://github.com/fsnotify/fsnotify/milestones), and will be tagged with a new major revision number.
|
||||||
|
|
||||||
|
Go 1.6 supports dependencies located in the `vendor/` folder. Unless you are creating a library, it is recommended that you copy fsnotify into `vendor/github.com/fsnotify/fsnotify` within your project, and likewise for `golang.org/x/sys`.
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Please refer to [CONTRIBUTING][] before opening an issue or pull request.
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
See [example_test.go](https://github.com/fsnotify/fsnotify/blob/master/example_test.go).
|
||||||
|
|
||||||
|
[contributing]: https://github.com/fsnotify/fsnotify/blob/master/CONTRIBUTING.md
|
||||||
|
|
||||||
|
## Related Projects
|
||||||
|
|
||||||
|
* [notify](https://github.com/rjeczalik/notify)
|
||||||
|
* [fsevents](https://github.com/fsnotify/fsevents)
|
||||||
|
|
42
vendor/github.com/fsnotify/fsnotify/example_test.go
generated
vendored
Normal file
42
vendor/github.com/fsnotify/fsnotify/example_test.go
generated
vendored
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
// Copyright 2012 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 !plan9
|
||||||
|
|
||||||
|
package fsnotify_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/fsnotify/fsnotify"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ExampleNewWatcher() {
|
||||||
|
watcher, err := fsnotify.NewWatcher()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer watcher.Close()
|
||||||
|
|
||||||
|
done := make(chan bool)
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case event := <-watcher.Events:
|
||||||
|
log.Println("event:", event)
|
||||||
|
if event.Op&fsnotify.Write == fsnotify.Write {
|
||||||
|
log.Println("modified file:", event.Name)
|
||||||
|
}
|
||||||
|
case err := <-watcher.Errors:
|
||||||
|
log.Println("error:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
err = watcher.Add("/tmp/foo")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
<-done
|
||||||
|
}
|
37
vendor/github.com/fsnotify/fsnotify/fen.go
generated
vendored
Normal file
37
vendor/github.com/fsnotify/fsnotify/fen.go
generated
vendored
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
// Copyright 2010 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 solaris
|
||||||
|
|
||||||
|
package fsnotify
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Watcher watches a set of files, delivering events to a channel.
|
||||||
|
type Watcher struct {
|
||||||
|
Events chan Event
|
||||||
|
Errors chan error
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events.
|
||||||
|
func NewWatcher() (*Watcher, error) {
|
||||||
|
return nil, errors.New("FEN based watcher not yet supported for fsnotify\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close removes all watches and closes the events channel.
|
||||||
|
func (w *Watcher) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add starts watching the named file or directory (non-recursively).
|
||||||
|
func (w *Watcher) Add(name string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove stops watching the the named file or directory (non-recursively).
|
||||||
|
func (w *Watcher) Remove(name string) error {
|
||||||
|
return nil
|
||||||
|
}
|
62
vendor/github.com/fsnotify/fsnotify/fsnotify.go
generated
vendored
Normal file
62
vendor/github.com/fsnotify/fsnotify/fsnotify.go
generated
vendored
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
// Copyright 2012 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 !plan9
|
||||||
|
|
||||||
|
// Package fsnotify provides a platform-independent interface for file system notifications.
|
||||||
|
package fsnotify
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Event represents a single file system notification.
|
||||||
|
type Event struct {
|
||||||
|
Name string // Relative path to the file or directory.
|
||||||
|
Op Op // File operation that triggered the event.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Op describes a set of file operations.
|
||||||
|
type Op uint32
|
||||||
|
|
||||||
|
// These are the generalized file operations that can trigger a notification.
|
||||||
|
const (
|
||||||
|
Create Op = 1 << iota
|
||||||
|
Write
|
||||||
|
Remove
|
||||||
|
Rename
|
||||||
|
Chmod
|
||||||
|
)
|
||||||
|
|
||||||
|
func (op Op) String() string {
|
||||||
|
// Use a buffer for efficient string concatenation
|
||||||
|
var buffer bytes.Buffer
|
||||||
|
|
||||||
|
if op&Create == Create {
|
||||||
|
buffer.WriteString("|CREATE")
|
||||||
|
}
|
||||||
|
if op&Remove == Remove {
|
||||||
|
buffer.WriteString("|REMOVE")
|
||||||
|
}
|
||||||
|
if op&Write == Write {
|
||||||
|
buffer.WriteString("|WRITE")
|
||||||
|
}
|
||||||
|
if op&Rename == Rename {
|
||||||
|
buffer.WriteString("|RENAME")
|
||||||
|
}
|
||||||
|
if op&Chmod == Chmod {
|
||||||
|
buffer.WriteString("|CHMOD")
|
||||||
|
}
|
||||||
|
if buffer.Len() == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return buffer.String()[1:] // Strip leading pipe
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a string representation of the event in the form
|
||||||
|
// "file: REMOVE|WRITE|..."
|
||||||
|
func (e Event) String() string {
|
||||||
|
return fmt.Sprintf("%q: %s", e.Name, e.Op.String())
|
||||||
|
}
|
40
vendor/github.com/fsnotify/fsnotify/fsnotify_test.go
generated
vendored
Normal file
40
vendor/github.com/fsnotify/fsnotify/fsnotify_test.go
generated
vendored
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
// Copyright 2016 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 !plan9
|
||||||
|
|
||||||
|
package fsnotify
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestEventStringWithValue(t *testing.T) {
|
||||||
|
for opMask, expectedString := range map[Op]string{
|
||||||
|
Chmod | Create: `"/usr/someFile": CREATE|CHMOD`,
|
||||||
|
Rename: `"/usr/someFile": RENAME`,
|
||||||
|
Remove: `"/usr/someFile": REMOVE`,
|
||||||
|
Write | Chmod: `"/usr/someFile": WRITE|CHMOD`,
|
||||||
|
} {
|
||||||
|
event := Event{Name: "/usr/someFile", Op: opMask}
|
||||||
|
if event.String() != expectedString {
|
||||||
|
t.Fatalf("Expected %s, got: %v", expectedString, event.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEventOpStringWithValue(t *testing.T) {
|
||||||
|
expectedOpString := "WRITE|CHMOD"
|
||||||
|
event := Event{Name: "someFile", Op: Write | Chmod}
|
||||||
|
if event.Op.String() != expectedOpString {
|
||||||
|
t.Fatalf("Expected %s, got: %v", expectedOpString, event.Op.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEventOpStringWithNoValue(t *testing.T) {
|
||||||
|
expectedOpString := ""
|
||||||
|
event := Event{Name: "testFile", Op: 0}
|
||||||
|
if event.Op.String() != expectedOpString {
|
||||||
|
t.Fatalf("Expected %s, got: %v", expectedOpString, event.Op.String())
|
||||||
|
}
|
||||||
|
}
|
325
vendor/github.com/fsnotify/fsnotify/inotify.go
generated
vendored
Normal file
325
vendor/github.com/fsnotify/fsnotify/inotify.go
generated
vendored
Normal file
|
@ -0,0 +1,325 @@
|
||||||
|
// Copyright 2010 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 linux
|
||||||
|
|
||||||
|
package fsnotify
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Watcher watches a set of files, delivering events to a channel.
|
||||||
|
type Watcher struct {
|
||||||
|
Events chan Event
|
||||||
|
Errors chan error
|
||||||
|
mu sync.Mutex // Map access
|
||||||
|
cv *sync.Cond // sync removing on rm_watch with IN_IGNORE
|
||||||
|
fd int
|
||||||
|
poller *fdPoller
|
||||||
|
watches map[string]*watch // Map of inotify watches (key: path)
|
||||||
|
paths map[int]string // Map of watched paths (key: watch descriptor)
|
||||||
|
done chan struct{} // Channel for sending a "quit message" to the reader goroutine
|
||||||
|
doneResp chan struct{} // Channel to respond to Close
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events.
|
||||||
|
func NewWatcher() (*Watcher, error) {
|
||||||
|
// Create inotify fd
|
||||||
|
fd, errno := unix.InotifyInit1(unix.IN_CLOEXEC)
|
||||||
|
if fd == -1 {
|
||||||
|
return nil, errno
|
||||||
|
}
|
||||||
|
// Create epoll
|
||||||
|
poller, err := newFdPoller(fd)
|
||||||
|
if err != nil {
|
||||||
|
unix.Close(fd)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
w := &Watcher{
|
||||||
|
fd: fd,
|
||||||
|
poller: poller,
|
||||||
|
watches: make(map[string]*watch),
|
||||||
|
paths: make(map[int]string),
|
||||||
|
Events: make(chan Event),
|
||||||
|
Errors: make(chan error),
|
||||||
|
done: make(chan struct{}),
|
||||||
|
doneResp: make(chan struct{}),
|
||||||
|
}
|
||||||
|
w.cv = sync.NewCond(&w.mu)
|
||||||
|
|
||||||
|
go w.readEvents()
|
||||||
|
return w, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Watcher) isClosed() bool {
|
||||||
|
select {
|
||||||
|
case <-w.done:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close removes all watches and closes the events channel.
|
||||||
|
func (w *Watcher) Close() error {
|
||||||
|
if w.isClosed() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send 'close' signal to goroutine, and set the Watcher to closed.
|
||||||
|
close(w.done)
|
||||||
|
|
||||||
|
// Wake up goroutine
|
||||||
|
w.poller.wake()
|
||||||
|
|
||||||
|
// Wait for goroutine to close
|
||||||
|
<-w.doneResp
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add starts watching the named file or directory (non-recursively).
|
||||||
|
func (w *Watcher) Add(name string) error {
|
||||||
|
name = filepath.Clean(name)
|
||||||
|
if w.isClosed() {
|
||||||
|
return errors.New("inotify instance already closed")
|
||||||
|
}
|
||||||
|
|
||||||
|
const agnosticEvents = unix.IN_MOVED_TO | unix.IN_MOVED_FROM |
|
||||||
|
unix.IN_CREATE | unix.IN_ATTRIB | unix.IN_MODIFY |
|
||||||
|
unix.IN_MOVE_SELF | unix.IN_DELETE | unix.IN_DELETE_SELF
|
||||||
|
|
||||||
|
var flags uint32 = agnosticEvents
|
||||||
|
|
||||||
|
w.mu.Lock()
|
||||||
|
watchEntry, found := w.watches[name]
|
||||||
|
w.mu.Unlock()
|
||||||
|
if found {
|
||||||
|
watchEntry.flags |= flags
|
||||||
|
flags |= unix.IN_MASK_ADD
|
||||||
|
}
|
||||||
|
wd, errno := unix.InotifyAddWatch(w.fd, name, flags)
|
||||||
|
if wd == -1 {
|
||||||
|
return errno
|
||||||
|
}
|
||||||
|
|
||||||
|
w.mu.Lock()
|
||||||
|
w.watches[name] = &watch{wd: uint32(wd), flags: flags}
|
||||||
|
w.paths[wd] = name
|
||||||
|
w.mu.Unlock()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove stops watching the named file or directory (non-recursively).
|
||||||
|
func (w *Watcher) Remove(name string) error {
|
||||||
|
name = filepath.Clean(name)
|
||||||
|
|
||||||
|
// Fetch the watch.
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
watch, ok := w.watches[name]
|
||||||
|
|
||||||
|
// Remove it from inotify.
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("can't remove non-existent inotify watch for: %s", name)
|
||||||
|
}
|
||||||
|
// inotify_rm_watch will return EINVAL if the file has been deleted;
|
||||||
|
// the inotify will already have been removed.
|
||||||
|
// watches and pathes are deleted in ignoreLinux() implicitly and asynchronously
|
||||||
|
// by calling inotify_rm_watch() below. e.g. readEvents() goroutine receives IN_IGNORE
|
||||||
|
// so that EINVAL means that the wd is being rm_watch()ed or its file removed
|
||||||
|
// by another thread and we have not received IN_IGNORE event.
|
||||||
|
success, errno := unix.InotifyRmWatch(w.fd, watch.wd)
|
||||||
|
if success == -1 {
|
||||||
|
// TODO: Perhaps it's not helpful to return an error here in every case.
|
||||||
|
// the only two possible errors are:
|
||||||
|
// EBADF, which happens when w.fd is not a valid file descriptor of any kind.
|
||||||
|
// EINVAL, which is when fd is not an inotify descriptor or wd is not a valid watch descriptor.
|
||||||
|
// Watch descriptors are invalidated when they are removed explicitly or implicitly;
|
||||||
|
// explicitly by inotify_rm_watch, implicitly when the file they are watching is deleted.
|
||||||
|
return errno
|
||||||
|
}
|
||||||
|
|
||||||
|
// wait until ignoreLinux() deleting maps
|
||||||
|
exists := true
|
||||||
|
for exists {
|
||||||
|
w.cv.Wait()
|
||||||
|
_, exists = w.watches[name]
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type watch struct {
|
||||||
|
wd uint32 // Watch descriptor (as returned by the inotify_add_watch() syscall)
|
||||||
|
flags uint32 // inotify flags of this watch (see inotify(7) for the list of valid flags)
|
||||||
|
}
|
||||||
|
|
||||||
|
// readEvents reads from the inotify file descriptor, converts the
|
||||||
|
// received events into Event objects and sends them via the Events channel
|
||||||
|
func (w *Watcher) readEvents() {
|
||||||
|
var (
|
||||||
|
buf [unix.SizeofInotifyEvent * 4096]byte // Buffer for a maximum of 4096 raw events
|
||||||
|
n int // Number of bytes read with read()
|
||||||
|
errno error // Syscall errno
|
||||||
|
ok bool // For poller.wait
|
||||||
|
)
|
||||||
|
|
||||||
|
defer close(w.doneResp)
|
||||||
|
defer close(w.Errors)
|
||||||
|
defer close(w.Events)
|
||||||
|
defer unix.Close(w.fd)
|
||||||
|
defer w.poller.close()
|
||||||
|
|
||||||
|
for {
|
||||||
|
// See if we have been closed.
|
||||||
|
if w.isClosed() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ok, errno = w.poller.wait()
|
||||||
|
if errno != nil {
|
||||||
|
select {
|
||||||
|
case w.Errors <- errno:
|
||||||
|
case <-w.done:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
n, errno = unix.Read(w.fd, buf[:])
|
||||||
|
// If a signal interrupted execution, see if we've been asked to close, and try again.
|
||||||
|
// http://man7.org/linux/man-pages/man7/signal.7.html :
|
||||||
|
// "Before Linux 3.8, reads from an inotify(7) file descriptor were not restartable"
|
||||||
|
if errno == unix.EINTR {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// unix.Read might have been woken up by Close. If so, we're done.
|
||||||
|
if w.isClosed() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if n < unix.SizeofInotifyEvent {
|
||||||
|
var err error
|
||||||
|
if n == 0 {
|
||||||
|
// If EOF is received. This should really never happen.
|
||||||
|
err = io.EOF
|
||||||
|
} else if n < 0 {
|
||||||
|
// If an error occurred while reading.
|
||||||
|
err = errno
|
||||||
|
} else {
|
||||||
|
// Read was too short.
|
||||||
|
err = errors.New("notify: short read in readEvents()")
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case w.Errors <- err:
|
||||||
|
case <-w.done:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var offset uint32
|
||||||
|
// We don't know how many events we just read into the buffer
|
||||||
|
// While the offset points to at least one whole event...
|
||||||
|
for offset <= uint32(n-unix.SizeofInotifyEvent) {
|
||||||
|
// Point "raw" to the event in the buffer
|
||||||
|
raw := (*unix.InotifyEvent)(unsafe.Pointer(&buf[offset]))
|
||||||
|
|
||||||
|
mask := uint32(raw.Mask)
|
||||||
|
nameLen := uint32(raw.Len)
|
||||||
|
// If the event happened to the watched directory or the watched file, the kernel
|
||||||
|
// doesn't append the filename to the event, but we would like to always fill the
|
||||||
|
// the "Name" field with a valid filename. We retrieve the path of the watch from
|
||||||
|
// the "paths" map.
|
||||||
|
w.mu.Lock()
|
||||||
|
name := w.paths[int(raw.Wd)]
|
||||||
|
w.mu.Unlock()
|
||||||
|
if nameLen > 0 {
|
||||||
|
// Point "bytes" at the first byte of the filename
|
||||||
|
bytes := (*[unix.PathMax]byte)(unsafe.Pointer(&buf[offset+unix.SizeofInotifyEvent]))
|
||||||
|
// The filename is padded with NULL bytes. TrimRight() gets rid of those.
|
||||||
|
name += "/" + strings.TrimRight(string(bytes[0:nameLen]), "\000")
|
||||||
|
}
|
||||||
|
|
||||||
|
event := newEvent(name, mask)
|
||||||
|
|
||||||
|
// Send the events that are not ignored on the events channel
|
||||||
|
if !event.ignoreLinux(w, raw.Wd, mask) {
|
||||||
|
select {
|
||||||
|
case w.Events <- event:
|
||||||
|
case <-w.done:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move to the next event in the buffer
|
||||||
|
offset += unix.SizeofInotifyEvent + nameLen
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Certain types of events can be "ignored" and not sent over the Events
|
||||||
|
// channel. Such as events marked ignore by the kernel, or MODIFY events
|
||||||
|
// against files that do not exist.
|
||||||
|
func (e *Event) ignoreLinux(w *Watcher, wd int32, mask uint32) bool {
|
||||||
|
// Ignore anything the inotify API says to ignore
|
||||||
|
if mask&unix.IN_IGNORED == unix.IN_IGNORED {
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
name := w.paths[int(wd)]
|
||||||
|
delete(w.paths, int(wd))
|
||||||
|
delete(w.watches, name)
|
||||||
|
w.cv.Broadcast()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the event is not a DELETE or RENAME, the file must exist.
|
||||||
|
// Otherwise the event is ignored.
|
||||||
|
// *Note*: this was put in place because it was seen that a MODIFY
|
||||||
|
// event was sent after the DELETE. This ignores that MODIFY and
|
||||||
|
// assumes a DELETE will come or has come if the file doesn't exist.
|
||||||
|
if !(e.Op&Remove == Remove || e.Op&Rename == Rename) {
|
||||||
|
_, statErr := os.Lstat(e.Name)
|
||||||
|
return os.IsNotExist(statErr)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// newEvent returns an platform-independent Event based on an inotify mask.
|
||||||
|
func newEvent(name string, mask uint32) Event {
|
||||||
|
e := Event{Name: name}
|
||||||
|
if mask&unix.IN_CREATE == unix.IN_CREATE || mask&unix.IN_MOVED_TO == unix.IN_MOVED_TO {
|
||||||
|
e.Op |= Create
|
||||||
|
}
|
||||||
|
if mask&unix.IN_DELETE_SELF == unix.IN_DELETE_SELF || mask&unix.IN_DELETE == unix.IN_DELETE {
|
||||||
|
e.Op |= Remove
|
||||||
|
}
|
||||||
|
if mask&unix.IN_MODIFY == unix.IN_MODIFY {
|
||||||
|
e.Op |= Write
|
||||||
|
}
|
||||||
|
if mask&unix.IN_MOVE_SELF == unix.IN_MOVE_SELF || mask&unix.IN_MOVED_FROM == unix.IN_MOVED_FROM {
|
||||||
|
e.Op |= Rename
|
||||||
|
}
|
||||||
|
if mask&unix.IN_ATTRIB == unix.IN_ATTRIB {
|
||||||
|
e.Op |= Chmod
|
||||||
|
}
|
||||||
|
return e
|
||||||
|
}
|
187
vendor/github.com/fsnotify/fsnotify/inotify_poller.go
generated
vendored
Normal file
187
vendor/github.com/fsnotify/fsnotify/inotify_poller.go
generated
vendored
Normal file
|
@ -0,0 +1,187 @@
|
||||||
|
// Copyright 2015 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 linux
|
||||||
|
|
||||||
|
package fsnotify
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
type fdPoller struct {
|
||||||
|
fd int // File descriptor (as returned by the inotify_init() syscall)
|
||||||
|
epfd int // Epoll file descriptor
|
||||||
|
pipe [2]int // Pipe for waking up
|
||||||
|
}
|
||||||
|
|
||||||
|
func emptyPoller(fd int) *fdPoller {
|
||||||
|
poller := new(fdPoller)
|
||||||
|
poller.fd = fd
|
||||||
|
poller.epfd = -1
|
||||||
|
poller.pipe[0] = -1
|
||||||
|
poller.pipe[1] = -1
|
||||||
|
return poller
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new inotify poller.
|
||||||
|
// This creates an inotify handler, and an epoll handler.
|
||||||
|
func newFdPoller(fd int) (*fdPoller, error) {
|
||||||
|
var errno error
|
||||||
|
poller := emptyPoller(fd)
|
||||||
|
defer func() {
|
||||||
|
if errno != nil {
|
||||||
|
poller.close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
poller.fd = fd
|
||||||
|
|
||||||
|
// Create epoll fd
|
||||||
|
poller.epfd, errno = unix.EpollCreate1(0)
|
||||||
|
if poller.epfd == -1 {
|
||||||
|
return nil, errno
|
||||||
|
}
|
||||||
|
// Create pipe; pipe[0] is the read end, pipe[1] the write end.
|
||||||
|
errno = unix.Pipe2(poller.pipe[:], unix.O_NONBLOCK)
|
||||||
|
if errno != nil {
|
||||||
|
return nil, errno
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register inotify fd with epoll
|
||||||
|
event := unix.EpollEvent{
|
||||||
|
Fd: int32(poller.fd),
|
||||||
|
Events: unix.EPOLLIN,
|
||||||
|
}
|
||||||
|
errno = unix.EpollCtl(poller.epfd, unix.EPOLL_CTL_ADD, poller.fd, &event)
|
||||||
|
if errno != nil {
|
||||||
|
return nil, errno
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register pipe fd with epoll
|
||||||
|
event = unix.EpollEvent{
|
||||||
|
Fd: int32(poller.pipe[0]),
|
||||||
|
Events: unix.EPOLLIN,
|
||||||
|
}
|
||||||
|
errno = unix.EpollCtl(poller.epfd, unix.EPOLL_CTL_ADD, poller.pipe[0], &event)
|
||||||
|
if errno != nil {
|
||||||
|
return nil, errno
|
||||||
|
}
|
||||||
|
|
||||||
|
return poller, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait using epoll.
|
||||||
|
// Returns true if something is ready to be read,
|
||||||
|
// false if there is not.
|
||||||
|
func (poller *fdPoller) wait() (bool, error) {
|
||||||
|
// 3 possible events per fd, and 2 fds, makes a maximum of 6 events.
|
||||||
|
// I don't know whether epoll_wait returns the number of events returned,
|
||||||
|
// or the total number of events ready.
|
||||||
|
// I decided to catch both by making the buffer one larger than the maximum.
|
||||||
|
events := make([]unix.EpollEvent, 7)
|
||||||
|
for {
|
||||||
|
n, errno := unix.EpollWait(poller.epfd, events, -1)
|
||||||
|
if n == -1 {
|
||||||
|
if errno == unix.EINTR {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return false, errno
|
||||||
|
}
|
||||||
|
if n == 0 {
|
||||||
|
// If there are no events, try again.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if n > 6 {
|
||||||
|
// This should never happen. More events were returned than should be possible.
|
||||||
|
return false, errors.New("epoll_wait returned more events than I know what to do with")
|
||||||
|
}
|
||||||
|
ready := events[:n]
|
||||||
|
epollhup := false
|
||||||
|
epollerr := false
|
||||||
|
epollin := false
|
||||||
|
for _, event := range ready {
|
||||||
|
if event.Fd == int32(poller.fd) {
|
||||||
|
if event.Events&unix.EPOLLHUP != 0 {
|
||||||
|
// This should not happen, but if it does, treat it as a wakeup.
|
||||||
|
epollhup = true
|
||||||
|
}
|
||||||
|
if event.Events&unix.EPOLLERR != 0 {
|
||||||
|
// If an error is waiting on the file descriptor, we should pretend
|
||||||
|
// something is ready to read, and let unix.Read pick up the error.
|
||||||
|
epollerr = true
|
||||||
|
}
|
||||||
|
if event.Events&unix.EPOLLIN != 0 {
|
||||||
|
// There is data to read.
|
||||||
|
epollin = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if event.Fd == int32(poller.pipe[0]) {
|
||||||
|
if event.Events&unix.EPOLLHUP != 0 {
|
||||||
|
// Write pipe descriptor was closed, by us. This means we're closing down the
|
||||||
|
// watcher, and we should wake up.
|
||||||
|
}
|
||||||
|
if event.Events&unix.EPOLLERR != 0 {
|
||||||
|
// If an error is waiting on the pipe file descriptor.
|
||||||
|
// This is an absolute mystery, and should never ever happen.
|
||||||
|
return false, errors.New("Error on the pipe descriptor.")
|
||||||
|
}
|
||||||
|
if event.Events&unix.EPOLLIN != 0 {
|
||||||
|
// This is a regular wakeup, so we have to clear the buffer.
|
||||||
|
err := poller.clearWake()
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if epollhup || epollerr || epollin {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close the write end of the poller.
|
||||||
|
func (poller *fdPoller) wake() error {
|
||||||
|
buf := make([]byte, 1)
|
||||||
|
n, errno := unix.Write(poller.pipe[1], buf)
|
||||||
|
if n == -1 {
|
||||||
|
if errno == unix.EAGAIN {
|
||||||
|
// Buffer is full, poller will wake.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return errno
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (poller *fdPoller) clearWake() error {
|
||||||
|
// You have to be woken up a LOT in order to get to 100!
|
||||||
|
buf := make([]byte, 100)
|
||||||
|
n, errno := unix.Read(poller.pipe[0], buf)
|
||||||
|
if n == -1 {
|
||||||
|
if errno == unix.EAGAIN {
|
||||||
|
// Buffer is empty, someone else cleared our wake.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return errno
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close all poller file descriptors, but not the one passed to it.
|
||||||
|
func (poller *fdPoller) close() {
|
||||||
|
if poller.pipe[1] != -1 {
|
||||||
|
unix.Close(poller.pipe[1])
|
||||||
|
}
|
||||||
|
if poller.pipe[0] != -1 {
|
||||||
|
unix.Close(poller.pipe[0])
|
||||||
|
}
|
||||||
|
if poller.epfd != -1 {
|
||||||
|
unix.Close(poller.epfd)
|
||||||
|
}
|
||||||
|
}
|
229
vendor/github.com/fsnotify/fsnotify/inotify_poller_test.go
generated
vendored
Normal file
229
vendor/github.com/fsnotify/fsnotify/inotify_poller_test.go
generated
vendored
Normal file
|
@ -0,0 +1,229 @@
|
||||||
|
// Copyright 2015 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 linux
|
||||||
|
|
||||||
|
package fsnotify
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
type testFd [2]int
|
||||||
|
|
||||||
|
func makeTestFd(t *testing.T) testFd {
|
||||||
|
var tfd testFd
|
||||||
|
errno := unix.Pipe(tfd[:])
|
||||||
|
if errno != nil {
|
||||||
|
t.Fatalf("Failed to create pipe: %v", errno)
|
||||||
|
}
|
||||||
|
return tfd
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tfd testFd) fd() int {
|
||||||
|
return tfd[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tfd testFd) closeWrite(t *testing.T) {
|
||||||
|
errno := unix.Close(tfd[1])
|
||||||
|
if errno != nil {
|
||||||
|
t.Fatalf("Failed to close write end of pipe: %v", errno)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tfd testFd) put(t *testing.T) {
|
||||||
|
buf := make([]byte, 10)
|
||||||
|
_, errno := unix.Write(tfd[1], buf)
|
||||||
|
if errno != nil {
|
||||||
|
t.Fatalf("Failed to write to pipe: %v", errno)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tfd testFd) get(t *testing.T) {
|
||||||
|
buf := make([]byte, 10)
|
||||||
|
_, errno := unix.Read(tfd[0], buf)
|
||||||
|
if errno != nil {
|
||||||
|
t.Fatalf("Failed to read from pipe: %v", errno)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tfd testFd) close() {
|
||||||
|
unix.Close(tfd[1])
|
||||||
|
unix.Close(tfd[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
func makePoller(t *testing.T) (testFd, *fdPoller) {
|
||||||
|
tfd := makeTestFd(t)
|
||||||
|
poller, err := newFdPoller(tfd.fd())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create poller: %v", err)
|
||||||
|
}
|
||||||
|
return tfd, poller
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPollerWithBadFd(t *testing.T) {
|
||||||
|
_, err := newFdPoller(-1)
|
||||||
|
if err != unix.EBADF {
|
||||||
|
t.Fatalf("Expected EBADF, got: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPollerWithData(t *testing.T) {
|
||||||
|
tfd, poller := makePoller(t)
|
||||||
|
defer tfd.close()
|
||||||
|
defer poller.close()
|
||||||
|
|
||||||
|
tfd.put(t)
|
||||||
|
ok, err := poller.wait()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("poller failed: %v", err)
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("expected poller to return true")
|
||||||
|
}
|
||||||
|
tfd.get(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPollerWithWakeup(t *testing.T) {
|
||||||
|
tfd, poller := makePoller(t)
|
||||||
|
defer tfd.close()
|
||||||
|
defer poller.close()
|
||||||
|
|
||||||
|
err := poller.wake()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("wake failed: %v", err)
|
||||||
|
}
|
||||||
|
ok, err := poller.wait()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("poller failed: %v", err)
|
||||||
|
}
|
||||||
|
if ok {
|
||||||
|
t.Fatalf("expected poller to return false")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPollerWithClose(t *testing.T) {
|
||||||
|
tfd, poller := makePoller(t)
|
||||||
|
defer tfd.close()
|
||||||
|
defer poller.close()
|
||||||
|
|
||||||
|
tfd.closeWrite(t)
|
||||||
|
ok, err := poller.wait()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("poller failed: %v", err)
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("expected poller to return true")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPollerWithWakeupAndData(t *testing.T) {
|
||||||
|
tfd, poller := makePoller(t)
|
||||||
|
defer tfd.close()
|
||||||
|
defer poller.close()
|
||||||
|
|
||||||
|
tfd.put(t)
|
||||||
|
err := poller.wake()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("wake failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// both data and wakeup
|
||||||
|
ok, err := poller.wait()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("poller failed: %v", err)
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("expected poller to return true")
|
||||||
|
}
|
||||||
|
|
||||||
|
// data is still in the buffer, wakeup is cleared
|
||||||
|
ok, err = poller.wait()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("poller failed: %v", err)
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("expected poller to return true")
|
||||||
|
}
|
||||||
|
|
||||||
|
tfd.get(t)
|
||||||
|
// data is gone, only wakeup now
|
||||||
|
err = poller.wake()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("wake failed: %v", err)
|
||||||
|
}
|
||||||
|
ok, err = poller.wait()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("poller failed: %v", err)
|
||||||
|
}
|
||||||
|
if ok {
|
||||||
|
t.Fatalf("expected poller to return false")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPollerConcurrent(t *testing.T) {
|
||||||
|
tfd, poller := makePoller(t)
|
||||||
|
defer tfd.close()
|
||||||
|
defer poller.close()
|
||||||
|
|
||||||
|
oks := make(chan bool)
|
||||||
|
live := make(chan bool)
|
||||||
|
defer close(live)
|
||||||
|
go func() {
|
||||||
|
defer close(oks)
|
||||||
|
for {
|
||||||
|
ok, err := poller.wait()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("poller failed: %v", err)
|
||||||
|
}
|
||||||
|
oks <- ok
|
||||||
|
if !<-live {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Try a write
|
||||||
|
select {
|
||||||
|
case <-time.After(50 * time.Millisecond):
|
||||||
|
case <-oks:
|
||||||
|
t.Fatalf("poller did not wait")
|
||||||
|
}
|
||||||
|
tfd.put(t)
|
||||||
|
if !<-oks {
|
||||||
|
t.Fatalf("expected true")
|
||||||
|
}
|
||||||
|
tfd.get(t)
|
||||||
|
live <- true
|
||||||
|
|
||||||
|
// Try a wakeup
|
||||||
|
select {
|
||||||
|
case <-time.After(50 * time.Millisecond):
|
||||||
|
case <-oks:
|
||||||
|
t.Fatalf("poller did not wait")
|
||||||
|
}
|
||||||
|
err := poller.wake()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("wake failed: %v", err)
|
||||||
|
}
|
||||||
|
if <-oks {
|
||||||
|
t.Fatalf("expected false")
|
||||||
|
}
|
||||||
|
live <- true
|
||||||
|
|
||||||
|
// Try a close
|
||||||
|
select {
|
||||||
|
case <-time.After(50 * time.Millisecond):
|
||||||
|
case <-oks:
|
||||||
|
t.Fatalf("poller did not wait")
|
||||||
|
}
|
||||||
|
tfd.closeWrite(t)
|
||||||
|
if !<-oks {
|
||||||
|
t.Fatalf("expected true")
|
||||||
|
}
|
||||||
|
tfd.get(t)
|
||||||
|
}
|
360
vendor/github.com/fsnotify/fsnotify/inotify_test.go
generated
vendored
Normal file
360
vendor/github.com/fsnotify/fsnotify/inotify_test.go
generated
vendored
Normal file
|
@ -0,0 +1,360 @@
|
||||||
|
// Copyright 2015 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 linux
|
||||||
|
|
||||||
|
package fsnotify
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestInotifyCloseRightAway(t *testing.T) {
|
||||||
|
w, err := NewWatcher()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create watcher")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close immediately; it won't even reach the first unix.Read.
|
||||||
|
w.Close()
|
||||||
|
|
||||||
|
// Wait for the close to complete.
|
||||||
|
<-time.After(50 * time.Millisecond)
|
||||||
|
isWatcherReallyClosed(t, w)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInotifyCloseSlightlyLater(t *testing.T) {
|
||||||
|
w, err := NewWatcher()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create watcher")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait until readEvents has reached unix.Read, and Close.
|
||||||
|
<-time.After(50 * time.Millisecond)
|
||||||
|
w.Close()
|
||||||
|
|
||||||
|
// Wait for the close to complete.
|
||||||
|
<-time.After(50 * time.Millisecond)
|
||||||
|
isWatcherReallyClosed(t, w)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInotifyCloseSlightlyLaterWithWatch(t *testing.T) {
|
||||||
|
testDir := tempMkdir(t)
|
||||||
|
defer os.RemoveAll(testDir)
|
||||||
|
|
||||||
|
w, err := NewWatcher()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create watcher")
|
||||||
|
}
|
||||||
|
w.Add(testDir)
|
||||||
|
|
||||||
|
// Wait until readEvents has reached unix.Read, and Close.
|
||||||
|
<-time.After(50 * time.Millisecond)
|
||||||
|
w.Close()
|
||||||
|
|
||||||
|
// Wait for the close to complete.
|
||||||
|
<-time.After(50 * time.Millisecond)
|
||||||
|
isWatcherReallyClosed(t, w)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInotifyCloseAfterRead(t *testing.T) {
|
||||||
|
testDir := tempMkdir(t)
|
||||||
|
defer os.RemoveAll(testDir)
|
||||||
|
|
||||||
|
w, err := NewWatcher()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create watcher")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = w.Add(testDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to add .")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate an event.
|
||||||
|
os.Create(filepath.Join(testDir, "somethingSOMETHINGsomethingSOMETHING"))
|
||||||
|
|
||||||
|
// Wait for readEvents to read the event, then close the watcher.
|
||||||
|
<-time.After(50 * time.Millisecond)
|
||||||
|
w.Close()
|
||||||
|
|
||||||
|
// Wait for the close to complete.
|
||||||
|
<-time.After(50 * time.Millisecond)
|
||||||
|
isWatcherReallyClosed(t, w)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isWatcherReallyClosed(t *testing.T, w *Watcher) {
|
||||||
|
select {
|
||||||
|
case err, ok := <-w.Errors:
|
||||||
|
if ok {
|
||||||
|
t.Fatalf("w.Errors is not closed; readEvents is still alive after closing (error: %v)", err)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
t.Fatalf("w.Errors would have blocked; readEvents is still alive!")
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case _, ok := <-w.Events:
|
||||||
|
if ok {
|
||||||
|
t.Fatalf("w.Events is not closed; readEvents is still alive after closing")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
t.Fatalf("w.Events would have blocked; readEvents is still alive!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInotifyCloseCreate(t *testing.T) {
|
||||||
|
testDir := tempMkdir(t)
|
||||||
|
defer os.RemoveAll(testDir)
|
||||||
|
|
||||||
|
w, err := NewWatcher()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create watcher: %v", err)
|
||||||
|
}
|
||||||
|
defer w.Close()
|
||||||
|
|
||||||
|
err = w.Add(testDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to add testDir: %v", err)
|
||||||
|
}
|
||||||
|
h, err := os.Create(filepath.Join(testDir, "testfile"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create file in testdir: %v", err)
|
||||||
|
}
|
||||||
|
h.Close()
|
||||||
|
select {
|
||||||
|
case _ = <-w.Events:
|
||||||
|
case err := <-w.Errors:
|
||||||
|
t.Fatalf("Error from watcher: %v", err)
|
||||||
|
case <-time.After(50 * time.Millisecond):
|
||||||
|
t.Fatalf("Took too long to wait for event")
|
||||||
|
}
|
||||||
|
|
||||||
|
// At this point, we've received one event, so the goroutine is ready.
|
||||||
|
// It's also blocking on unix.Read.
|
||||||
|
// Now we try to swap the file descriptor under its nose.
|
||||||
|
w.Close()
|
||||||
|
w, err = NewWatcher()
|
||||||
|
defer w.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create second watcher: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
<-time.After(50 * time.Millisecond)
|
||||||
|
err = w.Add(testDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error adding testDir again: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This test verifies the watcher can keep up with file creations/deletions
|
||||||
|
// when under load.
|
||||||
|
func TestInotifyStress(t *testing.T) {
|
||||||
|
maxNumToCreate := 1000
|
||||||
|
|
||||||
|
testDir := tempMkdir(t)
|
||||||
|
defer os.RemoveAll(testDir)
|
||||||
|
testFilePrefix := filepath.Join(testDir, "testfile")
|
||||||
|
|
||||||
|
w, err := NewWatcher()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create watcher: %v", err)
|
||||||
|
}
|
||||||
|
defer w.Close()
|
||||||
|
|
||||||
|
err = w.Add(testDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to add testDir: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
doneChan := make(chan struct{})
|
||||||
|
// The buffer ensures that the file generation goroutine is never blocked.
|
||||||
|
errChan := make(chan error, 2*maxNumToCreate)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for i := 0; i < maxNumToCreate; i++ {
|
||||||
|
testFile := fmt.Sprintf("%s%d", testFilePrefix, i)
|
||||||
|
|
||||||
|
handle, err := os.Create(testFile)
|
||||||
|
if err != nil {
|
||||||
|
errChan <- fmt.Errorf("Create failed: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
err = handle.Close()
|
||||||
|
if err != nil {
|
||||||
|
errChan <- fmt.Errorf("Close failed: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we delete a newly created file too quickly, inotify will skip the
|
||||||
|
// create event and only send the delete event.
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
|
||||||
|
for i := 0; i < maxNumToCreate; i++ {
|
||||||
|
testFile := fmt.Sprintf("%s%d", testFilePrefix, i)
|
||||||
|
err = os.Remove(testFile)
|
||||||
|
if err != nil {
|
||||||
|
errChan <- fmt.Errorf("Remove failed: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
close(doneChan)
|
||||||
|
}()
|
||||||
|
|
||||||
|
creates := 0
|
||||||
|
removes := 0
|
||||||
|
|
||||||
|
finished := false
|
||||||
|
after := time.After(10 * time.Second)
|
||||||
|
for !finished {
|
||||||
|
select {
|
||||||
|
case <-after:
|
||||||
|
t.Fatalf("Not done")
|
||||||
|
case <-doneChan:
|
||||||
|
finished = true
|
||||||
|
case err := <-errChan:
|
||||||
|
t.Fatalf("Got an error from file creator goroutine: %v", err)
|
||||||
|
case err := <-w.Errors:
|
||||||
|
t.Fatalf("Got an error from watcher: %v", err)
|
||||||
|
case evt := <-w.Events:
|
||||||
|
if !strings.HasPrefix(evt.Name, testFilePrefix) {
|
||||||
|
t.Fatalf("Got an event for an unknown file: %s", evt.Name)
|
||||||
|
}
|
||||||
|
if evt.Op == Create {
|
||||||
|
creates++
|
||||||
|
}
|
||||||
|
if evt.Op == Remove {
|
||||||
|
removes++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Drain remaining events from channels
|
||||||
|
count := 0
|
||||||
|
for count < 10 {
|
||||||
|
select {
|
||||||
|
case err := <-errChan:
|
||||||
|
t.Fatalf("Got an error from file creator goroutine: %v", err)
|
||||||
|
case err := <-w.Errors:
|
||||||
|
t.Fatalf("Got an error from watcher: %v", err)
|
||||||
|
case evt := <-w.Events:
|
||||||
|
if !strings.HasPrefix(evt.Name, testFilePrefix) {
|
||||||
|
t.Fatalf("Got an event for an unknown file: %s", evt.Name)
|
||||||
|
}
|
||||||
|
if evt.Op == Create {
|
||||||
|
creates++
|
||||||
|
}
|
||||||
|
if evt.Op == Remove {
|
||||||
|
removes++
|
||||||
|
}
|
||||||
|
count = 0
|
||||||
|
default:
|
||||||
|
count++
|
||||||
|
// Give the watcher chances to fill the channels.
|
||||||
|
time.Sleep(time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if creates-removes > 1 || creates-removes < -1 {
|
||||||
|
t.Fatalf("Creates and removes should not be off by more than one: %d creates, %d removes", creates, removes)
|
||||||
|
}
|
||||||
|
if creates < 50 {
|
||||||
|
t.Fatalf("Expected at least 50 creates, got %d", creates)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInotifyRemoveTwice(t *testing.T) {
|
||||||
|
testDir := tempMkdir(t)
|
||||||
|
defer os.RemoveAll(testDir)
|
||||||
|
testFile := filepath.Join(testDir, "testfile")
|
||||||
|
|
||||||
|
handle, err := os.Create(testFile)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Create failed: %v", err)
|
||||||
|
}
|
||||||
|
handle.Close()
|
||||||
|
|
||||||
|
w, err := NewWatcher()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create watcher: %v", err)
|
||||||
|
}
|
||||||
|
defer w.Close()
|
||||||
|
|
||||||
|
err = w.Add(testFile)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to add testFile: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.Remove(testFile)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to remove testFile: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = w.Remove(testFile)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("no error on removing invalid file")
|
||||||
|
}
|
||||||
|
s1 := fmt.Sprintf("%s", err)
|
||||||
|
|
||||||
|
err = w.Remove(testFile)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("no error on removing invalid file")
|
||||||
|
}
|
||||||
|
s2 := fmt.Sprintf("%s", err)
|
||||||
|
|
||||||
|
if s1 != s2 {
|
||||||
|
t.Fatalf("receive different error - %s / %s", s1, s2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInotifyInnerMapLength(t *testing.T) {
|
||||||
|
testDir := tempMkdir(t)
|
||||||
|
defer os.RemoveAll(testDir)
|
||||||
|
testFile := filepath.Join(testDir, "testfile")
|
||||||
|
|
||||||
|
handle, err := os.Create(testFile)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Create failed: %v", err)
|
||||||
|
}
|
||||||
|
handle.Close()
|
||||||
|
|
||||||
|
w, err := NewWatcher()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create watcher: %v", err)
|
||||||
|
}
|
||||||
|
defer w.Close()
|
||||||
|
|
||||||
|
err = w.Add(testFile)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to add testFile: %v", err)
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
for err := range w.Errors {
|
||||||
|
t.Fatalf("error received: %s", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
err = os.Remove(testFile)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to remove testFile: %v", err)
|
||||||
|
}
|
||||||
|
_ = <-w.Events // consume Remove event
|
||||||
|
<-time.After(50 * time.Millisecond) // wait IN_IGNORE propagated
|
||||||
|
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
if len(w.watches) != 0 {
|
||||||
|
t.Fatalf("Expected watches len is 0, but got: %d, %v", len(w.watches), w.watches)
|
||||||
|
}
|
||||||
|
if len(w.paths) != 0 {
|
||||||
|
t.Fatalf("Expected paths len is 0, but got: %d, %v", len(w.paths), w.paths)
|
||||||
|
}
|
||||||
|
}
|
147
vendor/github.com/fsnotify/fsnotify/integration_darwin_test.go
generated
vendored
Normal file
147
vendor/github.com/fsnotify/fsnotify/integration_darwin_test.go
generated
vendored
Normal file
|
@ -0,0 +1,147 @@
|
||||||
|
// Copyright 2016 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.
|
||||||
|
|
||||||
|
package fsnotify
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
// testExchangedataForWatcher tests the watcher with the exchangedata operation on OS X.
|
||||||
|
//
|
||||||
|
// This is widely used for atomic saves on OS X, e.g. TextMate and in Apple's NSDocument.
|
||||||
|
//
|
||||||
|
// See https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man2/exchangedata.2.html
|
||||||
|
// Also see: https://github.com/textmate/textmate/blob/cd016be29489eba5f3c09b7b70b06da134dda550/Frameworks/io/src/swap_file_data.cc#L20
|
||||||
|
func testExchangedataForWatcher(t *testing.T, watchDir bool) {
|
||||||
|
// Create directory to watch
|
||||||
|
testDir1 := tempMkdir(t)
|
||||||
|
|
||||||
|
// For the intermediate file
|
||||||
|
testDir2 := tempMkdir(t)
|
||||||
|
|
||||||
|
defer os.RemoveAll(testDir1)
|
||||||
|
defer os.RemoveAll(testDir2)
|
||||||
|
|
||||||
|
resolvedFilename := "TestFsnotifyEvents.file"
|
||||||
|
|
||||||
|
// TextMate does:
|
||||||
|
//
|
||||||
|
// 1. exchangedata (intermediate, resolved)
|
||||||
|
// 2. unlink intermediate
|
||||||
|
//
|
||||||
|
// Let's try to simulate that:
|
||||||
|
resolved := filepath.Join(testDir1, resolvedFilename)
|
||||||
|
intermediate := filepath.Join(testDir2, resolvedFilename+"~")
|
||||||
|
|
||||||
|
// Make sure we create the file before we start watching
|
||||||
|
createAndSyncFile(t, resolved)
|
||||||
|
|
||||||
|
watcher := newWatcher(t)
|
||||||
|
|
||||||
|
// Test both variants in isolation
|
||||||
|
if watchDir {
|
||||||
|
addWatch(t, watcher, testDir1)
|
||||||
|
} else {
|
||||||
|
addWatch(t, watcher, resolved)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Receive errors on the error channel on a separate goroutine
|
||||||
|
go func() {
|
||||||
|
for err := range watcher.Errors {
|
||||||
|
t.Fatalf("error received: %s", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Receive events on the event channel on a separate goroutine
|
||||||
|
eventstream := watcher.Events
|
||||||
|
var removeReceived counter
|
||||||
|
var createReceived counter
|
||||||
|
|
||||||
|
done := make(chan bool)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for event := range eventstream {
|
||||||
|
// Only count relevant events
|
||||||
|
if event.Name == filepath.Clean(resolved) {
|
||||||
|
if event.Op&Remove == Remove {
|
||||||
|
removeReceived.increment()
|
||||||
|
}
|
||||||
|
if event.Op&Create == Create {
|
||||||
|
createReceived.increment()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.Logf("event received: %s", event)
|
||||||
|
}
|
||||||
|
done <- true
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Repeat to make sure the watched file/directory "survives" the REMOVE/CREATE loop.
|
||||||
|
for i := 1; i <= 3; i++ {
|
||||||
|
// The intermediate file is created in a folder outside the watcher
|
||||||
|
createAndSyncFile(t, intermediate)
|
||||||
|
|
||||||
|
// 1. Swap
|
||||||
|
if err := unix.Exchangedata(intermediate, resolved, 0); err != nil {
|
||||||
|
t.Fatalf("[%d] exchangedata failed: %s", i, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(50 * time.Millisecond)
|
||||||
|
|
||||||
|
// 2. Delete the intermediate file
|
||||||
|
err := os.Remove(intermediate)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("[%d] remove %s failed: %s", i, intermediate, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(50 * time.Millisecond)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// We expect this event to be received almost immediately, but let's wait 500 ms to be sure
|
||||||
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
|
||||||
|
// The events will be (CHMOD + REMOVE + CREATE) X 2. Let's focus on the last two:
|
||||||
|
if removeReceived.value() < 3 {
|
||||||
|
t.Fatal("fsnotify remove events have not been received after 500 ms")
|
||||||
|
}
|
||||||
|
|
||||||
|
if createReceived.value() < 3 {
|
||||||
|
t.Fatal("fsnotify create events have not been received after 500 ms")
|
||||||
|
}
|
||||||
|
|
||||||
|
watcher.Close()
|
||||||
|
t.Log("waiting for the event channel to become closed...")
|
||||||
|
select {
|
||||||
|
case <-done:
|
||||||
|
t.Log("event channel closed")
|
||||||
|
case <-time.After(2 * time.Second):
|
||||||
|
t.Fatal("event stream was not closed after 2 seconds")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestExchangedataInWatchedDir test exchangedata operation on file in watched dir.
|
||||||
|
func TestExchangedataInWatchedDir(t *testing.T) {
|
||||||
|
testExchangedataForWatcher(t, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestExchangedataInWatchedDir test exchangedata operation on watched file.
|
||||||
|
func TestExchangedataInWatchedFile(t *testing.T) {
|
||||||
|
testExchangedataForWatcher(t, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func createAndSyncFile(t *testing.T, filepath string) {
|
||||||
|
f1, err := os.OpenFile(filepath, os.O_WRONLY|os.O_CREATE, 0666)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("creating %s failed: %s", filepath, err)
|
||||||
|
}
|
||||||
|
f1.Sync()
|
||||||
|
f1.Close()
|
||||||
|
}
|
1237
vendor/github.com/fsnotify/fsnotify/integration_test.go
generated
vendored
Normal file
1237
vendor/github.com/fsnotify/fsnotify/integration_test.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
503
vendor/github.com/fsnotify/fsnotify/kqueue.go
generated
vendored
Normal file
503
vendor/github.com/fsnotify/fsnotify/kqueue.go
generated
vendored
Normal file
|
@ -0,0 +1,503 @@
|
||||||
|
// Copyright 2010 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 freebsd openbsd netbsd dragonfly darwin
|
||||||
|
|
||||||
|
package fsnotify
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Watcher watches a set of files, delivering events to a channel.
|
||||||
|
type Watcher struct {
|
||||||
|
Events chan Event
|
||||||
|
Errors chan error
|
||||||
|
done chan bool // Channel for sending a "quit message" to the reader goroutine
|
||||||
|
|
||||||
|
kq int // File descriptor (as returned by the kqueue() syscall).
|
||||||
|
|
||||||
|
mu sync.Mutex // Protects access to watcher data
|
||||||
|
watches map[string]int // Map of watched file descriptors (key: path).
|
||||||
|
externalWatches map[string]bool // Map of watches added by user of the library.
|
||||||
|
dirFlags map[string]uint32 // Map of watched directories to fflags used in kqueue.
|
||||||
|
paths map[int]pathInfo // Map file descriptors to path names for processing kqueue events.
|
||||||
|
fileExists map[string]bool // Keep track of if we know this file exists (to stop duplicate create events).
|
||||||
|
isClosed bool // Set to true when Close() is first called
|
||||||
|
}
|
||||||
|
|
||||||
|
type pathInfo struct {
|
||||||
|
name string
|
||||||
|
isDir bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events.
|
||||||
|
func NewWatcher() (*Watcher, error) {
|
||||||
|
kq, err := kqueue()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
w := &Watcher{
|
||||||
|
kq: kq,
|
||||||
|
watches: make(map[string]int),
|
||||||
|
dirFlags: make(map[string]uint32),
|
||||||
|
paths: make(map[int]pathInfo),
|
||||||
|
fileExists: make(map[string]bool),
|
||||||
|
externalWatches: make(map[string]bool),
|
||||||
|
Events: make(chan Event),
|
||||||
|
Errors: make(chan error),
|
||||||
|
done: make(chan bool),
|
||||||
|
}
|
||||||
|
|
||||||
|
go w.readEvents()
|
||||||
|
return w, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close removes all watches and closes the events channel.
|
||||||
|
func (w *Watcher) Close() error {
|
||||||
|
w.mu.Lock()
|
||||||
|
if w.isClosed {
|
||||||
|
w.mu.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
w.isClosed = true
|
||||||
|
w.mu.Unlock()
|
||||||
|
|
||||||
|
// copy paths to remove while locked
|
||||||
|
w.mu.Lock()
|
||||||
|
var pathsToRemove = make([]string, 0, len(w.watches))
|
||||||
|
for name := range w.watches {
|
||||||
|
pathsToRemove = append(pathsToRemove, name)
|
||||||
|
}
|
||||||
|
w.mu.Unlock()
|
||||||
|
// unlock before calling Remove, which also locks
|
||||||
|
|
||||||
|
var err error
|
||||||
|
for _, name := range pathsToRemove {
|
||||||
|
if e := w.Remove(name); e != nil && err == nil {
|
||||||
|
err = e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send "quit" message to the reader goroutine:
|
||||||
|
w.done <- true
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add starts watching the named file or directory (non-recursively).
|
||||||
|
func (w *Watcher) Add(name string) error {
|
||||||
|
w.mu.Lock()
|
||||||
|
w.externalWatches[name] = true
|
||||||
|
w.mu.Unlock()
|
||||||
|
_, err := w.addWatch(name, noteAllEvents)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove stops watching the the named file or directory (non-recursively).
|
||||||
|
func (w *Watcher) Remove(name string) error {
|
||||||
|
name = filepath.Clean(name)
|
||||||
|
w.mu.Lock()
|
||||||
|
watchfd, ok := w.watches[name]
|
||||||
|
w.mu.Unlock()
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("can't remove non-existent kevent watch for: %s", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
const registerRemove = unix.EV_DELETE
|
||||||
|
if err := register(w.kq, []int{watchfd}, registerRemove, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
unix.Close(watchfd)
|
||||||
|
|
||||||
|
w.mu.Lock()
|
||||||
|
isDir := w.paths[watchfd].isDir
|
||||||
|
delete(w.watches, name)
|
||||||
|
delete(w.paths, watchfd)
|
||||||
|
delete(w.dirFlags, name)
|
||||||
|
w.mu.Unlock()
|
||||||
|
|
||||||
|
// Find all watched paths that are in this directory that are not external.
|
||||||
|
if isDir {
|
||||||
|
var pathsToRemove []string
|
||||||
|
w.mu.Lock()
|
||||||
|
for _, path := range w.paths {
|
||||||
|
wdir, _ := filepath.Split(path.name)
|
||||||
|
if filepath.Clean(wdir) == name {
|
||||||
|
if !w.externalWatches[path.name] {
|
||||||
|
pathsToRemove = append(pathsToRemove, path.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.mu.Unlock()
|
||||||
|
for _, name := range pathsToRemove {
|
||||||
|
// Since these are internal, not much sense in propagating error
|
||||||
|
// to the user, as that will just confuse them with an error about
|
||||||
|
// a path they did not explicitly watch themselves.
|
||||||
|
w.Remove(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Watch all events (except NOTE_EXTEND, NOTE_LINK, NOTE_REVOKE)
|
||||||
|
const noteAllEvents = unix.NOTE_DELETE | unix.NOTE_WRITE | unix.NOTE_ATTRIB | unix.NOTE_RENAME
|
||||||
|
|
||||||
|
// keventWaitTime to block on each read from kevent
|
||||||
|
var keventWaitTime = durationToTimespec(100 * time.Millisecond)
|
||||||
|
|
||||||
|
// addWatch adds name to the watched file set.
|
||||||
|
// The flags are interpreted as described in kevent(2).
|
||||||
|
// Returns the real path to the file which was added, if any, which may be different from the one passed in the case of symlinks.
|
||||||
|
func (w *Watcher) addWatch(name string, flags uint32) (string, error) {
|
||||||
|
var isDir bool
|
||||||
|
// Make ./name and name equivalent
|
||||||
|
name = filepath.Clean(name)
|
||||||
|
|
||||||
|
w.mu.Lock()
|
||||||
|
if w.isClosed {
|
||||||
|
w.mu.Unlock()
|
||||||
|
return "", errors.New("kevent instance already closed")
|
||||||
|
}
|
||||||
|
watchfd, alreadyWatching := w.watches[name]
|
||||||
|
// We already have a watch, but we can still override flags.
|
||||||
|
if alreadyWatching {
|
||||||
|
isDir = w.paths[watchfd].isDir
|
||||||
|
}
|
||||||
|
w.mu.Unlock()
|
||||||
|
|
||||||
|
if !alreadyWatching {
|
||||||
|
fi, err := os.Lstat(name)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't watch sockets.
|
||||||
|
if fi.Mode()&os.ModeSocket == os.ModeSocket {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't watch named pipes.
|
||||||
|
if fi.Mode()&os.ModeNamedPipe == os.ModeNamedPipe {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Follow Symlinks
|
||||||
|
// Unfortunately, Linux can add bogus symlinks to watch list without
|
||||||
|
// issue, and Windows can't do symlinks period (AFAIK). To maintain
|
||||||
|
// consistency, we will act like everything is fine. There will simply
|
||||||
|
// be no file events for broken symlinks.
|
||||||
|
// Hence the returns of nil on errors.
|
||||||
|
if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
|
||||||
|
name, err = filepath.EvalSymlinks(name)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
w.mu.Lock()
|
||||||
|
_, alreadyWatching = w.watches[name]
|
||||||
|
w.mu.Unlock()
|
||||||
|
|
||||||
|
if alreadyWatching {
|
||||||
|
return name, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
fi, err = os.Lstat(name)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watchfd, err = unix.Open(name, openMode, 0700)
|
||||||
|
if watchfd == -1 {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
isDir = fi.IsDir()
|
||||||
|
}
|
||||||
|
|
||||||
|
const registerAdd = unix.EV_ADD | unix.EV_CLEAR | unix.EV_ENABLE
|
||||||
|
if err := register(w.kq, []int{watchfd}, registerAdd, flags); err != nil {
|
||||||
|
unix.Close(watchfd)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !alreadyWatching {
|
||||||
|
w.mu.Lock()
|
||||||
|
w.watches[name] = watchfd
|
||||||
|
w.paths[watchfd] = pathInfo{name: name, isDir: isDir}
|
||||||
|
w.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
if isDir {
|
||||||
|
// Watch the directory if it has not been watched before,
|
||||||
|
// or if it was watched before, but perhaps only a NOTE_DELETE (watchDirectoryFiles)
|
||||||
|
w.mu.Lock()
|
||||||
|
|
||||||
|
watchDir := (flags&unix.NOTE_WRITE) == unix.NOTE_WRITE &&
|
||||||
|
(!alreadyWatching || (w.dirFlags[name]&unix.NOTE_WRITE) != unix.NOTE_WRITE)
|
||||||
|
// Store flags so this watch can be updated later
|
||||||
|
w.dirFlags[name] = flags
|
||||||
|
w.mu.Unlock()
|
||||||
|
|
||||||
|
if watchDir {
|
||||||
|
if err := w.watchDirectoryFiles(name); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return name, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// readEvents reads from kqueue and converts the received kevents into
|
||||||
|
// Event values that it sends down the Events channel.
|
||||||
|
func (w *Watcher) readEvents() {
|
||||||
|
eventBuffer := make([]unix.Kevent_t, 10)
|
||||||
|
|
||||||
|
for {
|
||||||
|
// See if there is a message on the "done" channel
|
||||||
|
select {
|
||||||
|
case <-w.done:
|
||||||
|
err := unix.Close(w.kq)
|
||||||
|
if err != nil {
|
||||||
|
w.Errors <- err
|
||||||
|
}
|
||||||
|
close(w.Events)
|
||||||
|
close(w.Errors)
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get new events
|
||||||
|
kevents, err := read(w.kq, eventBuffer, &keventWaitTime)
|
||||||
|
// EINTR is okay, the syscall was interrupted before timeout expired.
|
||||||
|
if err != nil && err != unix.EINTR {
|
||||||
|
w.Errors <- err
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush the events we received to the Events channel
|
||||||
|
for len(kevents) > 0 {
|
||||||
|
kevent := &kevents[0]
|
||||||
|
watchfd := int(kevent.Ident)
|
||||||
|
mask := uint32(kevent.Fflags)
|
||||||
|
w.mu.Lock()
|
||||||
|
path := w.paths[watchfd]
|
||||||
|
w.mu.Unlock()
|
||||||
|
event := newEvent(path.name, mask)
|
||||||
|
|
||||||
|
if path.isDir && !(event.Op&Remove == Remove) {
|
||||||
|
// Double check to make sure the directory exists. This can happen when
|
||||||
|
// we do a rm -fr on a recursively watched folders and we receive a
|
||||||
|
// modification event first but the folder has been deleted and later
|
||||||
|
// receive the delete event
|
||||||
|
if _, err := os.Lstat(event.Name); os.IsNotExist(err) {
|
||||||
|
// mark is as delete event
|
||||||
|
event.Op |= Remove
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if event.Op&Rename == Rename || event.Op&Remove == Remove {
|
||||||
|
w.Remove(event.Name)
|
||||||
|
w.mu.Lock()
|
||||||
|
delete(w.fileExists, event.Name)
|
||||||
|
w.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
if path.isDir && event.Op&Write == Write && !(event.Op&Remove == Remove) {
|
||||||
|
w.sendDirectoryChangeEvents(event.Name)
|
||||||
|
} else {
|
||||||
|
// Send the event on the Events channel
|
||||||
|
w.Events <- event
|
||||||
|
}
|
||||||
|
|
||||||
|
if event.Op&Remove == Remove {
|
||||||
|
// Look for a file that may have overwritten this.
|
||||||
|
// For example, mv f1 f2 will delete f2, then create f2.
|
||||||
|
if path.isDir {
|
||||||
|
fileDir := filepath.Clean(event.Name)
|
||||||
|
w.mu.Lock()
|
||||||
|
_, found := w.watches[fileDir]
|
||||||
|
w.mu.Unlock()
|
||||||
|
if found {
|
||||||
|
// make sure the directory exists before we watch for changes. When we
|
||||||
|
// do a recursive watch and perform rm -fr, the parent directory might
|
||||||
|
// have gone missing, ignore the missing directory and let the
|
||||||
|
// upcoming delete event remove the watch from the parent directory.
|
||||||
|
if _, err := os.Lstat(fileDir); err == nil {
|
||||||
|
w.sendDirectoryChangeEvents(fileDir)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
filePath := filepath.Clean(event.Name)
|
||||||
|
if fileInfo, err := os.Lstat(filePath); err == nil {
|
||||||
|
w.sendFileCreatedEventIfNew(filePath, fileInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move to next event
|
||||||
|
kevents = kevents[1:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// newEvent returns an platform-independent Event based on kqueue Fflags.
|
||||||
|
func newEvent(name string, mask uint32) Event {
|
||||||
|
e := Event{Name: name}
|
||||||
|
if mask&unix.NOTE_DELETE == unix.NOTE_DELETE {
|
||||||
|
e.Op |= Remove
|
||||||
|
}
|
||||||
|
if mask&unix.NOTE_WRITE == unix.NOTE_WRITE {
|
||||||
|
e.Op |= Write
|
||||||
|
}
|
||||||
|
if mask&unix.NOTE_RENAME == unix.NOTE_RENAME {
|
||||||
|
e.Op |= Rename
|
||||||
|
}
|
||||||
|
if mask&unix.NOTE_ATTRIB == unix.NOTE_ATTRIB {
|
||||||
|
e.Op |= Chmod
|
||||||
|
}
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCreateEvent(name string) Event {
|
||||||
|
return Event{Name: name, Op: Create}
|
||||||
|
}
|
||||||
|
|
||||||
|
// watchDirectoryFiles to mimic inotify when adding a watch on a directory
|
||||||
|
func (w *Watcher) watchDirectoryFiles(dirPath string) error {
|
||||||
|
// Get all files
|
||||||
|
files, err := ioutil.ReadDir(dirPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, fileInfo := range files {
|
||||||
|
filePath := filepath.Join(dirPath, fileInfo.Name())
|
||||||
|
filePath, err = w.internalWatch(filePath, fileInfo)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
w.mu.Lock()
|
||||||
|
w.fileExists[filePath] = true
|
||||||
|
w.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendDirectoryEvents searches the directory for newly created files
|
||||||
|
// and sends them over the event channel. This functionality is to have
|
||||||
|
// the BSD version of fsnotify match Linux inotify which provides a
|
||||||
|
// create event for files created in a watched directory.
|
||||||
|
func (w *Watcher) sendDirectoryChangeEvents(dirPath string) {
|
||||||
|
// Get all files
|
||||||
|
files, err := ioutil.ReadDir(dirPath)
|
||||||
|
if err != nil {
|
||||||
|
w.Errors <- err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search for new files
|
||||||
|
for _, fileInfo := range files {
|
||||||
|
filePath := filepath.Join(dirPath, fileInfo.Name())
|
||||||
|
err := w.sendFileCreatedEventIfNew(filePath, fileInfo)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendFileCreatedEvent sends a create event if the file isn't already being tracked.
|
||||||
|
func (w *Watcher) sendFileCreatedEventIfNew(filePath string, fileInfo os.FileInfo) (err error) {
|
||||||
|
w.mu.Lock()
|
||||||
|
_, doesExist := w.fileExists[filePath]
|
||||||
|
w.mu.Unlock()
|
||||||
|
if !doesExist {
|
||||||
|
// Send create event
|
||||||
|
w.Events <- newCreateEvent(filePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// like watchDirectoryFiles (but without doing another ReadDir)
|
||||||
|
filePath, err = w.internalWatch(filePath, fileInfo)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
w.mu.Lock()
|
||||||
|
w.fileExists[filePath] = true
|
||||||
|
w.mu.Unlock()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Watcher) internalWatch(name string, fileInfo os.FileInfo) (string, error) {
|
||||||
|
if fileInfo.IsDir() {
|
||||||
|
// mimic Linux providing delete events for subdirectories
|
||||||
|
// but preserve the flags used if currently watching subdirectory
|
||||||
|
w.mu.Lock()
|
||||||
|
flags := w.dirFlags[name]
|
||||||
|
w.mu.Unlock()
|
||||||
|
|
||||||
|
flags |= unix.NOTE_DELETE | unix.NOTE_RENAME
|
||||||
|
return w.addWatch(name, flags)
|
||||||
|
}
|
||||||
|
|
||||||
|
// watch file to mimic Linux inotify
|
||||||
|
return w.addWatch(name, noteAllEvents)
|
||||||
|
}
|
||||||
|
|
||||||
|
// kqueue creates a new kernel event queue and returns a descriptor.
|
||||||
|
func kqueue() (kq int, err error) {
|
||||||
|
kq, err = unix.Kqueue()
|
||||||
|
if kq == -1 {
|
||||||
|
return kq, err
|
||||||
|
}
|
||||||
|
return kq, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// register events with the queue
|
||||||
|
func register(kq int, fds []int, flags int, fflags uint32) error {
|
||||||
|
changes := make([]unix.Kevent_t, len(fds))
|
||||||
|
|
||||||
|
for i, fd := range fds {
|
||||||
|
// SetKevent converts int to the platform-specific types:
|
||||||
|
unix.SetKevent(&changes[i], fd, unix.EVFILT_VNODE, flags)
|
||||||
|
changes[i].Fflags = fflags
|
||||||
|
}
|
||||||
|
|
||||||
|
// register the events
|
||||||
|
success, err := unix.Kevent(kq, changes, nil, nil)
|
||||||
|
if success == -1 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// read retrieves pending events, or waits until an event occurs.
|
||||||
|
// A timeout of nil blocks indefinitely, while 0 polls the queue.
|
||||||
|
func read(kq int, events []unix.Kevent_t, timeout *unix.Timespec) ([]unix.Kevent_t, error) {
|
||||||
|
n, err := unix.Kevent(kq, nil, events, timeout)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return events[0:n], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// durationToTimespec prepares a timeout value
|
||||||
|
func durationToTimespec(d time.Duration) unix.Timespec {
|
||||||
|
return unix.NsecToTimespec(d.Nanoseconds())
|
||||||
|
}
|
11
vendor/github.com/fsnotify/fsnotify/open_mode_bsd.go
generated
vendored
Normal file
11
vendor/github.com/fsnotify/fsnotify/open_mode_bsd.go
generated
vendored
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
// Copyright 2013 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 freebsd openbsd netbsd dragonfly
|
||||||
|
|
||||||
|
package fsnotify
|
||||||
|
|
||||||
|
import "golang.org/x/sys/unix"
|
||||||
|
|
||||||
|
const openMode = unix.O_NONBLOCK | unix.O_RDONLY
|
12
vendor/github.com/fsnotify/fsnotify/open_mode_darwin.go
generated
vendored
Normal file
12
vendor/github.com/fsnotify/fsnotify/open_mode_darwin.go
generated
vendored
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
// Copyright 2013 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 darwin
|
||||||
|
|
||||||
|
package fsnotify
|
||||||
|
|
||||||
|
import "golang.org/x/sys/unix"
|
||||||
|
|
||||||
|
// note: this constant is not defined on BSD
|
||||||
|
const openMode = unix.O_EVTONLY
|
561
vendor/github.com/fsnotify/fsnotify/windows.go
generated
vendored
Normal file
561
vendor/github.com/fsnotify/fsnotify/windows.go
generated
vendored
Normal file
|
@ -0,0 +1,561 @@
|
||||||
|
// 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 fsnotify
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"sync"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Watcher watches a set of files, delivering events to a channel.
|
||||||
|
type Watcher struct {
|
||||||
|
Events chan Event
|
||||||
|
Errors chan error
|
||||||
|
isClosed bool // Set to true when Close() is first called
|
||||||
|
mu sync.Mutex // Map access
|
||||||
|
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
|
||||||
|
quit chan chan<- error
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events.
|
||||||
|
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),
|
||||||
|
Events: make(chan Event, 50),
|
||||||
|
Errors: make(chan error),
|
||||||
|
quit: make(chan chan<- error, 1),
|
||||||
|
}
|
||||||
|
go w.readEvents()
|
||||||
|
return w, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close removes all watches and closes the events channel.
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add starts watching the named file or directory (non-recursively).
|
||||||
|
func (w *Watcher) Add(name string) error {
|
||||||
|
if w.isClosed {
|
||||||
|
return errors.New("watcher already closed")
|
||||||
|
}
|
||||||
|
in := &input{
|
||||||
|
op: opAddWatch,
|
||||||
|
path: filepath.Clean(name),
|
||||||
|
flags: sysFSALLEVENTS,
|
||||||
|
reply: make(chan error),
|
||||||
|
}
|
||||||
|
w.input <- in
|
||||||
|
if err := w.wakeupReader(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return <-in.reply
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove stops watching the the named file or directory (non-recursively).
|
||||||
|
func (w *Watcher) Remove(name string) error {
|
||||||
|
in := &input{
|
||||||
|
op: opRemoveWatch,
|
||||||
|
path: filepath.Clean(name),
|
||||||
|
reply: make(chan error),
|
||||||
|
}
|
||||||
|
w.input <- in
|
||||||
|
if err := w.wakeupReader(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return <-in.reply
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Options for AddWatch
|
||||||
|
sysFSONESHOT = 0x80000000
|
||||||
|
sysFSONLYDIR = 0x1000000
|
||||||
|
|
||||||
|
// Events
|
||||||
|
sysFSACCESS = 0x1
|
||||||
|
sysFSALLEVENTS = 0xfff
|
||||||
|
sysFSATTRIB = 0x4
|
||||||
|
sysFSCLOSE = 0x18
|
||||||
|
sysFSCREATE = 0x100
|
||||||
|
sysFSDELETE = 0x200
|
||||||
|
sysFSDELETESELF = 0x400
|
||||||
|
sysFSMODIFY = 0x2
|
||||||
|
sysFSMOVE = 0xc0
|
||||||
|
sysFSMOVEDFROM = 0x40
|
||||||
|
sysFSMOVEDTO = 0x80
|
||||||
|
sysFSMOVESELF = 0x800
|
||||||
|
|
||||||
|
// Special events
|
||||||
|
sysFSIGNORED = 0x8000
|
||||||
|
sysFSQOVERFLOW = 0x4000
|
||||||
|
)
|
||||||
|
|
||||||
|
func newEvent(name string, mask uint32) Event {
|
||||||
|
e := Event{Name: name}
|
||||||
|
if mask&sysFSCREATE == sysFSCREATE || mask&sysFSMOVEDTO == sysFSMOVEDTO {
|
||||||
|
e.Op |= Create
|
||||||
|
}
|
||||||
|
if mask&sysFSDELETE == sysFSDELETE || mask&sysFSDELETESELF == sysFSDELETESELF {
|
||||||
|
e.Op |= Remove
|
||||||
|
}
|
||||||
|
if mask&sysFSMODIFY == sysFSMODIFY {
|
||||||
|
e.Op |= Write
|
||||||
|
}
|
||||||
|
if mask&sysFSMOVE == sysFSMOVE || mask&sysFSMOVESELF == sysFSMOVESELF || mask&sysFSMOVEDFROM == sysFSMOVEDFROM {
|
||||||
|
e.Op |= Rename
|
||||||
|
}
|
||||||
|
if mask&sysFSATTRIB == sysFSATTRIB {
|
||||||
|
e.Op |= Chmod
|
||||||
|
}
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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.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&sysFSONLYDIR != 0 && pathname != dir {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
ino, err := getIno(dir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
w.mu.Lock()
|
||||||
|
watchEntry := w.watches.get(ino)
|
||||||
|
w.mu.Unlock()
|
||||||
|
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.mu.Lock()
|
||||||
|
w.watches.set(ino, watchEntry)
|
||||||
|
w.mu.Unlock()
|
||||||
|
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) remWatch(pathname string) error {
|
||||||
|
dir, err := getDir(pathname)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ino, err := getIno(dir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
w.mu.Lock()
|
||||||
|
watch := w.watches.get(ino)
|
||||||
|
w.mu.Unlock()
|
||||||
|
if watch == nil {
|
||||||
|
return fmt.Errorf("can't remove non-existent watch for: %s", pathname)
|
||||||
|
}
|
||||||
|
if pathname == dir {
|
||||||
|
w.sendEvent(watch.path, watch.mask&sysFSIGNORED)
|
||||||
|
watch.mask = 0
|
||||||
|
} else {
|
||||||
|
name := filepath.Base(pathname)
|
||||||
|
w.sendEvent(filepath.Join(watch.path, name), watch.names[name]&sysFSIGNORED)
|
||||||
|
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(filepath.Join(watch.path, name), mask&sysFSIGNORED)
|
||||||
|
}
|
||||||
|
delete(watch.names, name)
|
||||||
|
}
|
||||||
|
if watch.mask != 0 {
|
||||||
|
if watch.mask&provisional == 0 {
|
||||||
|
w.sendEvent(watch.path, watch.mask&sysFSIGNORED)
|
||||||
|
}
|
||||||
|
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.Errors <- 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.Errors <- os.NewSyscallError("CloseHandle", e)
|
||||||
|
}
|
||||||
|
w.mu.Lock()
|
||||||
|
delete(w.watches[watch.ino.volume], watch.ino.index)
|
||||||
|
w.mu.Unlock()
|
||||||
|
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&sysFSDELETESELF) {
|
||||||
|
if watch.mask&sysFSONESHOT != 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 Events 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:
|
||||||
|
w.mu.Lock()
|
||||||
|
var indexes []indexMap
|
||||||
|
for _, index := range w.watches {
|
||||||
|
indexes = append(indexes, index)
|
||||||
|
}
|
||||||
|
w.mu.Unlock()
|
||||||
|
for _, index := range indexes {
|
||||||
|
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.Events)
|
||||||
|
close(w.Errors)
|
||||||
|
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.remWatch(in.path)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch e {
|
||||||
|
case syscall.ERROR_MORE_DATA:
|
||||||
|
if watch == nil {
|
||||||
|
w.Errors <- errors.New("ERROR_MORE_DATA has unexpectedly null lpOverlapped buffer")
|
||||||
|
} else {
|
||||||
|
// The i/o succeeded but the buffer is full.
|
||||||
|
// In theory we should be building up a full packet.
|
||||||
|
// In practice we can get away with just carrying on.
|
||||||
|
n = uint32(unsafe.Sizeof(watch.buf))
|
||||||
|
}
|
||||||
|
case syscall.ERROR_ACCESS_DENIED:
|
||||||
|
// Watched directory was probably removed
|
||||||
|
w.sendEvent(watch.path, watch.mask&sysFSDELETESELF)
|
||||||
|
w.deleteWatch(watch)
|
||||||
|
w.startRead(watch)
|
||||||
|
continue
|
||||||
|
case syscall.ERROR_OPERATION_ABORTED:
|
||||||
|
// CancelIo was called on this handle
|
||||||
|
continue
|
||||||
|
default:
|
||||||
|
w.Errors <- os.NewSyscallError("GetQueuedCompletionPort", e)
|
||||||
|
continue
|
||||||
|
case nil:
|
||||||
|
}
|
||||||
|
|
||||||
|
var offset uint32
|
||||||
|
for {
|
||||||
|
if n == 0 {
|
||||||
|
w.Events <- newEvent("", sysFSQOVERFLOW)
|
||||||
|
w.Errors <- 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 := filepath.Join(watch.path, name)
|
||||||
|
|
||||||
|
var mask uint64
|
||||||
|
switch raw.Action {
|
||||||
|
case syscall.FILE_ACTION_REMOVED:
|
||||||
|
mask = sysFSDELETESELF
|
||||||
|
case syscall.FILE_ACTION_MODIFIED:
|
||||||
|
mask = sysFSMODIFY
|
||||||
|
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 = sysFSMOVESELF
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sendNameEvent := func() {
|
||||||
|
if w.sendEvent(fullname, watch.names[name]&mask) {
|
||||||
|
if watch.names[name]&sysFSONESHOT != 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]&sysFSIGNORED)
|
||||||
|
delete(watch.names, name)
|
||||||
|
}
|
||||||
|
if w.sendEvent(fullname, watch.mask&toFSnotifyFlags(raw.Action)) {
|
||||||
|
if watch.mask&sysFSONESHOT != 0 {
|
||||||
|
watch.mask = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if raw.Action == syscall.FILE_ACTION_RENAMED_NEW_NAME {
|
||||||
|
fullname = filepath.Join(watch.path, watch.rename)
|
||||||
|
sendNameEvent()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move to the next event in the buffer
|
||||||
|
if raw.NextEntryOffset == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
offset += raw.NextEntryOffset
|
||||||
|
|
||||||
|
// Error!
|
||||||
|
if offset >= n {
|
||||||
|
w.Errors <- errors.New("Windows system assumed buffer larger than it is, events have likely been missed.")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := w.startRead(watch); err != nil {
|
||||||
|
w.Errors <- err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Watcher) sendEvent(name string, mask uint64) bool {
|
||||||
|
if mask == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
event := newEvent(name, uint32(mask))
|
||||||
|
select {
|
||||||
|
case ch := <-w.quit:
|
||||||
|
w.quit <- ch
|
||||||
|
case w.Events <- event:
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func toWindowsFlags(mask uint64) uint32 {
|
||||||
|
var m uint32
|
||||||
|
if mask&sysFSACCESS != 0 {
|
||||||
|
m |= syscall.FILE_NOTIFY_CHANGE_LAST_ACCESS
|
||||||
|
}
|
||||||
|
if mask&sysFSMODIFY != 0 {
|
||||||
|
m |= syscall.FILE_NOTIFY_CHANGE_LAST_WRITE
|
||||||
|
}
|
||||||
|
if mask&sysFSATTRIB != 0 {
|
||||||
|
m |= syscall.FILE_NOTIFY_CHANGE_ATTRIBUTES
|
||||||
|
}
|
||||||
|
if mask&(sysFSMOVE|sysFSCREATE|sysFSDELETE) != 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 sysFSCREATE
|
||||||
|
case syscall.FILE_ACTION_REMOVED:
|
||||||
|
return sysFSDELETE
|
||||||
|
case syscall.FILE_ACTION_MODIFIED:
|
||||||
|
return sysFSMODIFY
|
||||||
|
case syscall.FILE_ACTION_RENAMED_OLD_NAME:
|
||||||
|
return sysFSMOVEDFROM
|
||||||
|
case syscall.FILE_ACTION_RENAMED_NEW_NAME:
|
||||||
|
return sysFSMOVEDTO
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
21
vendor/github.com/spf13/afero/.travis.yml
generated
vendored
Normal file
21
vendor/github.com/spf13/afero/.travis.yml
generated
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
sudo: false
|
||||||
|
language: go
|
||||||
|
|
||||||
|
go:
|
||||||
|
- 1.7.5
|
||||||
|
- 1.8
|
||||||
|
- tip
|
||||||
|
|
||||||
|
os:
|
||||||
|
- linux
|
||||||
|
- osx
|
||||||
|
|
||||||
|
matrix:
|
||||||
|
allow_failures:
|
||||||
|
- go: tip
|
||||||
|
fast_finish: true
|
||||||
|
|
||||||
|
script:
|
||||||
|
- go build
|
||||||
|
- go test -race -v ./...
|
||||||
|
|
174
vendor/github.com/spf13/afero/LICENSE.txt
generated
vendored
Normal file
174
vendor/github.com/spf13/afero/LICENSE.txt
generated
vendored
Normal file
|
@ -0,0 +1,174 @@
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor 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 the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
452
vendor/github.com/spf13/afero/README.md
generated
vendored
Normal file
452
vendor/github.com/spf13/afero/README.md
generated
vendored
Normal file
|
@ -0,0 +1,452 @@
|
||||||
|

|
||||||
|
|
||||||
|
A FileSystem Abstraction System for Go
|
||||||
|
|
||||||
|
[](https://travis-ci.org/spf13/afero) [](https://ci.appveyor.com/project/spf13/afero) [](https://godoc.org/github.com/spf13/afero) [](https://gitter.im/spf13/afero?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||||
|
|
||||||
|
# Overview
|
||||||
|
|
||||||
|
Afero is an filesystem framework providing a simple, uniform and universal API
|
||||||
|
interacting with any filesystem, as an abstraction layer providing interfaces,
|
||||||
|
types and methods. Afero has an exceptionally clean interface and simple design
|
||||||
|
without needless constructors or initialization methods.
|
||||||
|
|
||||||
|
Afero is also a library providing a base set of interoperable backend
|
||||||
|
filesystems that make it easy to work with afero while retaining all the power
|
||||||
|
and benefit of the os and ioutil packages.
|
||||||
|
|
||||||
|
Afero provides significant improvements over using the os package alone, most
|
||||||
|
notably the ability to create mock and testing filesystems without relying on the disk.
|
||||||
|
|
||||||
|
It is suitable for use in a any situation where you would consider using the OS
|
||||||
|
package as it provides an additional abstraction that makes it easy to use a
|
||||||
|
memory backed file system during testing. It also adds support for the http
|
||||||
|
filesystem for full interoperability.
|
||||||
|
|
||||||
|
|
||||||
|
## Afero Features
|
||||||
|
|
||||||
|
* A single consistent API for accessing a variety of filesystems
|
||||||
|
* Interoperation between a variety of file system types
|
||||||
|
* A set of interfaces to encourage and enforce interoperability between backends
|
||||||
|
* An atomic cross platform memory backed file system
|
||||||
|
* Support for compositional (union) file systems by combining multiple file systems acting as one
|
||||||
|
* Specialized backends which modify existing filesystems (Read Only, Regexp filtered)
|
||||||
|
* A set of utility functions ported from io, ioutil & hugo to be afero aware
|
||||||
|
|
||||||
|
|
||||||
|
# Using Afero
|
||||||
|
|
||||||
|
Afero is easy to use and easier to adopt.
|
||||||
|
|
||||||
|
A few different ways you could use Afero:
|
||||||
|
|
||||||
|
* Use the interfaces alone to define you own file system.
|
||||||
|
* Wrap for the OS packages.
|
||||||
|
* Define different filesystems for different parts of your application.
|
||||||
|
* Use Afero for mock filesystems while testing
|
||||||
|
|
||||||
|
## Step 1: Install Afero
|
||||||
|
|
||||||
|
First use go get to install the latest version of the library.
|
||||||
|
|
||||||
|
$ go get github.com/spf13/afero
|
||||||
|
|
||||||
|
Next include Afero in your application.
|
||||||
|
```go
|
||||||
|
import "github.com/spf13/afero"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Step 2: Declare a backend
|
||||||
|
|
||||||
|
First define a package variable and set it to a pointer to a filesystem.
|
||||||
|
```go
|
||||||
|
var AppFs = afero.NewMemMapFs()
|
||||||
|
|
||||||
|
or
|
||||||
|
|
||||||
|
var AppFs = afero.NewOsFs()
|
||||||
|
```
|
||||||
|
It is important to note that if you repeat the composite literal you
|
||||||
|
will be using a completely new and isolated filesystem. In the case of
|
||||||
|
OsFs it will still use the same underlying filesystem but will reduce
|
||||||
|
the ability to drop in other filesystems as desired.
|
||||||
|
|
||||||
|
## Step 3: Use it like you would the OS package
|
||||||
|
|
||||||
|
Throughout your application use any function and method like you normally
|
||||||
|
would.
|
||||||
|
|
||||||
|
So if my application before had:
|
||||||
|
```go
|
||||||
|
os.Open('/tmp/foo')
|
||||||
|
```
|
||||||
|
We would replace it with:
|
||||||
|
```go
|
||||||
|
AppFs.Open('/tmp/foo')
|
||||||
|
```
|
||||||
|
|
||||||
|
`AppFs` being the variable we defined above.
|
||||||
|
|
||||||
|
|
||||||
|
## List of all available functions
|
||||||
|
|
||||||
|
File System Methods Available:
|
||||||
|
```go
|
||||||
|
Chmod(name string, mode os.FileMode) : error
|
||||||
|
Chtimes(name string, atime time.Time, mtime time.Time) : error
|
||||||
|
Create(name string) : File, error
|
||||||
|
Mkdir(name string, perm os.FileMode) : error
|
||||||
|
MkdirAll(path string, perm os.FileMode) : error
|
||||||
|
Name() : string
|
||||||
|
Open(name string) : File, error
|
||||||
|
OpenFile(name string, flag int, perm os.FileMode) : File, error
|
||||||
|
Remove(name string) : error
|
||||||
|
RemoveAll(path string) : error
|
||||||
|
Rename(oldname, newname string) : error
|
||||||
|
Stat(name string) : os.FileInfo, error
|
||||||
|
```
|
||||||
|
File Interfaces and Methods Available:
|
||||||
|
```go
|
||||||
|
io.Closer
|
||||||
|
io.Reader
|
||||||
|
io.ReaderAt
|
||||||
|
io.Seeker
|
||||||
|
io.Writer
|
||||||
|
io.WriterAt
|
||||||
|
|
||||||
|
Name() : string
|
||||||
|
Readdir(count int) : []os.FileInfo, error
|
||||||
|
Readdirnames(n int) : []string, error
|
||||||
|
Stat() : os.FileInfo, error
|
||||||
|
Sync() : error
|
||||||
|
Truncate(size int64) : error
|
||||||
|
WriteString(s string) : ret int, err error
|
||||||
|
```
|
||||||
|
In some applications it may make sense to define a new package that
|
||||||
|
simply exports the file system variable for easy access from anywhere.
|
||||||
|
|
||||||
|
## Using Afero's utility functions
|
||||||
|
|
||||||
|
Afero provides a set of functions to make it easier to use the underlying file systems.
|
||||||
|
These functions have been primarily ported from io & ioutil with some developed for Hugo.
|
||||||
|
|
||||||
|
The afero utilities support all afero compatible backends.
|
||||||
|
|
||||||
|
The list of utilities includes:
|
||||||
|
|
||||||
|
```go
|
||||||
|
DirExists(path string) (bool, error)
|
||||||
|
Exists(path string) (bool, error)
|
||||||
|
FileContainsBytes(filename string, subslice []byte) (bool, error)
|
||||||
|
GetTempDir(subPath string) string
|
||||||
|
IsDir(path string) (bool, error)
|
||||||
|
IsEmpty(path string) (bool, error)
|
||||||
|
ReadDir(dirname string) ([]os.FileInfo, error)
|
||||||
|
ReadFile(filename string) ([]byte, error)
|
||||||
|
SafeWriteReader(path string, r io.Reader) (err error)
|
||||||
|
TempDir(dir, prefix string) (name string, err error)
|
||||||
|
TempFile(dir, prefix string) (f File, err error)
|
||||||
|
Walk(root string, walkFn filepath.WalkFunc) error
|
||||||
|
WriteFile(filename string, data []byte, perm os.FileMode) error
|
||||||
|
WriteReader(path string, r io.Reader) (err error)
|
||||||
|
```
|
||||||
|
For a complete list see [Afero's GoDoc](https://godoc.org/github.com/spf13/afero)
|
||||||
|
|
||||||
|
They are available under two different approaches to use. You can either call
|
||||||
|
them directly where the first parameter of each function will be the file
|
||||||
|
system, or you can declare a new `Afero`, a custom type used to bind these
|
||||||
|
functions as methods to a given filesystem.
|
||||||
|
|
||||||
|
### Calling utilities directly
|
||||||
|
|
||||||
|
```go
|
||||||
|
fs := new(afero.MemMapFs)
|
||||||
|
f, err := afero.TempFile(fs,"", "ioutil-test")
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### Calling via Afero
|
||||||
|
|
||||||
|
```go
|
||||||
|
fs := afero.NewMemMapFs()
|
||||||
|
afs := &afero.Afero{Fs: fs}
|
||||||
|
f, err := afs.TempFile("", "ioutil-test")
|
||||||
|
```
|
||||||
|
|
||||||
|
## Using Afero for Testing
|
||||||
|
|
||||||
|
There is a large benefit to using a mock filesystem for testing. It has a
|
||||||
|
completely blank state every time it is initialized and can be easily
|
||||||
|
reproducible regardless of OS. You could create files to your heart’s content
|
||||||
|
and the file access would be fast while also saving you from all the annoying
|
||||||
|
issues with deleting temporary files, Windows file locking, etc. The MemMapFs
|
||||||
|
backend is perfect for testing.
|
||||||
|
|
||||||
|
* Much faster than performing I/O operations on disk
|
||||||
|
* Avoid security issues and permissions
|
||||||
|
* Far more control. 'rm -rf /' with confidence
|
||||||
|
* Test setup is far more easier to do
|
||||||
|
* No test cleanup needed
|
||||||
|
|
||||||
|
One way to accomplish this is to define a variable as mentioned above.
|
||||||
|
In your application this will be set to afero.NewOsFs() during testing you
|
||||||
|
can set it to afero.NewMemMapFs().
|
||||||
|
|
||||||
|
It wouldn't be uncommon to have each test initialize a blank slate memory
|
||||||
|
backend. To do this I would define my `appFS = afero.NewOsFs()` somewhere
|
||||||
|
appropriate in my application code. This approach ensures that Tests are order
|
||||||
|
independent, with no test relying on the state left by an earlier test.
|
||||||
|
|
||||||
|
Then in my tests I would initialize a new MemMapFs for each test:
|
||||||
|
```go
|
||||||
|
func TestExist(t *testing.T) {
|
||||||
|
appFS := afero.NewMemMapFs()
|
||||||
|
// create test files and directories
|
||||||
|
appFS.MkdirAll("src/a", 0755)
|
||||||
|
afero.WriteFile(appFS, "src/a/b", []byte("file b"), 0644)
|
||||||
|
afero.WriteFile(appFS, "src/c", []byte("file c"), 0644)
|
||||||
|
name := "src/c"
|
||||||
|
_, err := appFS.Stat(name)
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
t.Errorf("file \"%s\" does not exist.\n", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
# Available Backends
|
||||||
|
|
||||||
|
## Operating System Native
|
||||||
|
|
||||||
|
### OsFs
|
||||||
|
|
||||||
|
The first is simply a wrapper around the native OS calls. This makes it
|
||||||
|
very easy to use as all of the calls are the same as the existing OS
|
||||||
|
calls. It also makes it trivial to have your code use the OS during
|
||||||
|
operation and a mock filesystem during testing or as needed.
|
||||||
|
|
||||||
|
```go
|
||||||
|
appfs := afero.NewOsFs()
|
||||||
|
appfs.MkdirAll("src/a", 0755))
|
||||||
|
```
|
||||||
|
|
||||||
|
## Memory Backed Storage
|
||||||
|
|
||||||
|
### MemMapFs
|
||||||
|
|
||||||
|
Afero also provides a fully atomic memory backed filesystem perfect for use in
|
||||||
|
mocking and to speed up unnecessary disk io when persistence isn’t
|
||||||
|
necessary. It is fully concurrent and will work within go routines
|
||||||
|
safely.
|
||||||
|
|
||||||
|
```go
|
||||||
|
mm := afero.NewMemMapFs()
|
||||||
|
mm.MkdirAll("src/a", 0755))
|
||||||
|
```
|
||||||
|
|
||||||
|
#### InMemoryFile
|
||||||
|
|
||||||
|
As part of MemMapFs, Afero also provides an atomic, fully concurrent memory
|
||||||
|
backed file implementation. This can be used in other memory backed file
|
||||||
|
systems with ease. Plans are to add a radix tree memory stored file
|
||||||
|
system using InMemoryFile.
|
||||||
|
|
||||||
|
## Network Interfaces
|
||||||
|
|
||||||
|
### SftpFs
|
||||||
|
|
||||||
|
Afero has experimental support for secure file transfer protocol (sftp). Which can
|
||||||
|
be used to perform file operations over a encrypted channel.
|
||||||
|
|
||||||
|
## Filtering Backends
|
||||||
|
|
||||||
|
### BasePathFs
|
||||||
|
|
||||||
|
The BasePathFs restricts all operations to a given path within an Fs.
|
||||||
|
The given file name to the operations on this Fs will be prepended with
|
||||||
|
the base path before calling the source Fs.
|
||||||
|
|
||||||
|
```go
|
||||||
|
bp := afero.NewBasePathFs(afero.NewOsFs(), "/base/path")
|
||||||
|
```
|
||||||
|
|
||||||
|
### ReadOnlyFs
|
||||||
|
|
||||||
|
A thin wrapper around the source Fs providing a read only view.
|
||||||
|
|
||||||
|
```go
|
||||||
|
fs := afero.NewReadOnlyFs(afero.NewOsFs())
|
||||||
|
_, err := fs.Create("/file.txt")
|
||||||
|
// err = syscall.EPERM
|
||||||
|
```
|
||||||
|
|
||||||
|
# RegexpFs
|
||||||
|
|
||||||
|
A filtered view on file names, any file NOT matching
|
||||||
|
the passed regexp will be treated as non-existing.
|
||||||
|
Files not matching the regexp provided will not be created.
|
||||||
|
Directories are not filtered.
|
||||||
|
|
||||||
|
```go
|
||||||
|
fs := afero.NewRegexpFs(afero.NewMemMapFs(), regexp.MustCompile(`\.txt$`))
|
||||||
|
_, err := fs.Create("/file.html")
|
||||||
|
// err = syscall.ENOENT
|
||||||
|
```
|
||||||
|
|
||||||
|
### HttpFs
|
||||||
|
|
||||||
|
Afero provides an http compatible backend which can wrap any of the existing
|
||||||
|
backends.
|
||||||
|
|
||||||
|
The Http package requires a slightly specific version of Open which
|
||||||
|
returns an http.File type.
|
||||||
|
|
||||||
|
Afero provides an httpFs file system which satisfies this requirement.
|
||||||
|
Any Afero FileSystem can be used as an httpFs.
|
||||||
|
|
||||||
|
```go
|
||||||
|
httpFs := afero.NewHttpFs(<ExistingFS>)
|
||||||
|
fileserver := http.FileServer(httpFs.Dir(<PATH>)))
|
||||||
|
http.Handle("/", fileserver)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Composite Backends
|
||||||
|
|
||||||
|
Afero provides the ability have two filesystems (or more) act as a single
|
||||||
|
file system.
|
||||||
|
|
||||||
|
### CacheOnReadFs
|
||||||
|
|
||||||
|
The CacheOnReadFs will lazily make copies of any accessed files from the base
|
||||||
|
layer into the overlay. Subsequent reads will be pulled from the overlay
|
||||||
|
directly permitting the request is within the cache duration of when it was
|
||||||
|
created in the overlay.
|
||||||
|
|
||||||
|
If the base filesystem is writeable, any changes to files will be
|
||||||
|
done first to the base, then to the overlay layer. Write calls to open file
|
||||||
|
handles like `Write()` or `Truncate()` to the overlay first.
|
||||||
|
|
||||||
|
To writing files to the overlay only, you can use the overlay Fs directly (not
|
||||||
|
via the union Fs).
|
||||||
|
|
||||||
|
Cache files in the layer for the given time.Duration, a cache duration of 0
|
||||||
|
means "forever" meaning the file will not be re-requested from the base ever.
|
||||||
|
|
||||||
|
A read-only base will make the overlay also read-only but still copy files
|
||||||
|
from the base to the overlay when they're not present (or outdated) in the
|
||||||
|
caching layer.
|
||||||
|
|
||||||
|
```go
|
||||||
|
base := afero.NewOsFs()
|
||||||
|
layer := afero.NewMemMapFs()
|
||||||
|
ufs := afero.NewCacheOnReadFs(base, layer, 100 * time.Second)
|
||||||
|
```
|
||||||
|
|
||||||
|
### CopyOnWriteFs()
|
||||||
|
|
||||||
|
The CopyOnWriteFs is a read only base file system with a potentially
|
||||||
|
writeable layer on top.
|
||||||
|
|
||||||
|
Read operations will first look in the overlay and if not found there, will
|
||||||
|
serve the file from the base.
|
||||||
|
|
||||||
|
Changes to the file system will only be made in the overlay.
|
||||||
|
|
||||||
|
Any attempt to modify a file found only in the base will copy the file to the
|
||||||
|
overlay layer before modification (including opening a file with a writable
|
||||||
|
handle).
|
||||||
|
|
||||||
|
Removing and Renaming files present only in the base layer is not currently
|
||||||
|
permitted. If a file is present in the base layer and the overlay, only the
|
||||||
|
overlay will be removed/renamed.
|
||||||
|
|
||||||
|
```go
|
||||||
|
base := afero.NewOsFs()
|
||||||
|
roBase := afero.NewReadOnlyFs(base)
|
||||||
|
ufs := afero.NewCopyOnWriteFs(roBase, afero.NewMemMapFs())
|
||||||
|
|
||||||
|
fh, _ = ufs.Create("/home/test/file2.txt")
|
||||||
|
fh.WriteString("This is a test")
|
||||||
|
fh.Close()
|
||||||
|
```
|
||||||
|
|
||||||
|
In this example all write operations will only occur in memory (MemMapFs)
|
||||||
|
leaving the base filesystem (OsFs) untouched.
|
||||||
|
|
||||||
|
|
||||||
|
## Desired/possible backends
|
||||||
|
|
||||||
|
The following is a short list of possible backends we hope someone will
|
||||||
|
implement:
|
||||||
|
|
||||||
|
* SSH
|
||||||
|
* ZIP
|
||||||
|
* TAR
|
||||||
|
* S3
|
||||||
|
|
||||||
|
# About the project
|
||||||
|
|
||||||
|
## What's in the name
|
||||||
|
|
||||||
|
Afero comes from the latin roots Ad-Facere.
|
||||||
|
|
||||||
|
**"Ad"** is a prefix meaning "to".
|
||||||
|
|
||||||
|
**"Facere"** is a form of the root "faciō" making "make or do".
|
||||||
|
|
||||||
|
The literal meaning of afero is "to make" or "to do" which seems very fitting
|
||||||
|
for a library that allows one to make files and directories and do things with them.
|
||||||
|
|
||||||
|
The English word that shares the same roots as Afero is "affair". Affair shares
|
||||||
|
the same concept but as a noun it means "something that is made or done" or "an
|
||||||
|
object of a particular type".
|
||||||
|
|
||||||
|
It's also nice that unlike some of my other libraries (hugo, cobra, viper) it
|
||||||
|
Googles very well.
|
||||||
|
|
||||||
|
## Release Notes
|
||||||
|
|
||||||
|
* **0.10.0** 2015.12.10
|
||||||
|
* Full compatibility with Windows
|
||||||
|
* Introduction of afero utilities
|
||||||
|
* Test suite rewritten to work cross platform
|
||||||
|
* Normalize paths for MemMapFs
|
||||||
|
* Adding Sync to the file interface
|
||||||
|
* **Breaking Change** Walk and ReadDir have changed parameter order
|
||||||
|
* Moving types used by MemMapFs to a subpackage
|
||||||
|
* General bugfixes and improvements
|
||||||
|
* **0.9.0** 2015.11.05
|
||||||
|
* New Walk function similar to filepath.Walk
|
||||||
|
* MemMapFs.OpenFile handles O_CREATE, O_APPEND, O_TRUNC
|
||||||
|
* MemMapFs.Remove now really deletes the file
|
||||||
|
* InMemoryFile.Readdir and Readdirnames work correctly
|
||||||
|
* InMemoryFile functions lock it for concurrent access
|
||||||
|
* Test suite improvements
|
||||||
|
* **0.8.0** 2014.10.28
|
||||||
|
* First public version
|
||||||
|
* Interfaces feel ready for people to build using
|
||||||
|
* Interfaces satisfy all known uses
|
||||||
|
* MemMapFs passes the majority of the OS test suite
|
||||||
|
* OsFs passes the majority of the OS test suite
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
1. Fork it
|
||||||
|
2. Create your feature branch (`git checkout -b my-new-feature`)
|
||||||
|
3. Commit your changes (`git commit -am 'Add some feature'`)
|
||||||
|
4. Push to the branch (`git push origin my-new-feature`)
|
||||||
|
5. Create new Pull Request
|
||||||
|
|
||||||
|
## Contributors
|
||||||
|
|
||||||
|
Names in no particular order:
|
||||||
|
|
||||||
|
* [spf13](https://github.com/spf13)
|
||||||
|
* [jaqx0r](https://github.com/jaqx0r)
|
||||||
|
* [mbertschler](https://github.com/mbertschler)
|
||||||
|
* [xor-gate](https://github.com/xor-gate)
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Afero is released under the Apache 2.0 license. See
|
||||||
|
[LICENSE.txt](https://github.com/spf13/afero/blob/master/LICENSE.txt)
|
108
vendor/github.com/spf13/afero/afero.go
generated
vendored
Normal file
108
vendor/github.com/spf13/afero/afero.go
generated
vendored
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
// Copyright © 2014 Steve Francia <spf@spf13.com>.
|
||||||
|
// Copyright 2013 tsuru authors. All rights reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// Package afero provides types and methods for interacting with the filesystem,
|
||||||
|
// as an abstraction layer.
|
||||||
|
|
||||||
|
// Afero also provides a few implementations that are mostly interoperable. One that
|
||||||
|
// uses the operating system filesystem, one that uses memory to store files
|
||||||
|
// (cross platform) and an interface that should be implemented if you want to
|
||||||
|
// provide your own filesystem.
|
||||||
|
|
||||||
|
package afero
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Afero struct {
|
||||||
|
Fs
|
||||||
|
}
|
||||||
|
|
||||||
|
// File represents a file in the filesystem.
|
||||||
|
type File interface {
|
||||||
|
io.Closer
|
||||||
|
io.Reader
|
||||||
|
io.ReaderAt
|
||||||
|
io.Seeker
|
||||||
|
io.Writer
|
||||||
|
io.WriterAt
|
||||||
|
|
||||||
|
Name() string
|
||||||
|
Readdir(count int) ([]os.FileInfo, error)
|
||||||
|
Readdirnames(n int) ([]string, error)
|
||||||
|
Stat() (os.FileInfo, error)
|
||||||
|
Sync() error
|
||||||
|
Truncate(size int64) error
|
||||||
|
WriteString(s string) (ret int, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fs is the filesystem interface.
|
||||||
|
//
|
||||||
|
// Any simulated or real filesystem should implement this interface.
|
||||||
|
type Fs interface {
|
||||||
|
// Create creates a file in the filesystem, returning the file and an
|
||||||
|
// error, if any happens.
|
||||||
|
Create(name string) (File, error)
|
||||||
|
|
||||||
|
// Mkdir creates a directory in the filesystem, return an error if any
|
||||||
|
// happens.
|
||||||
|
Mkdir(name string, perm os.FileMode) error
|
||||||
|
|
||||||
|
// MkdirAll creates a directory path and all parents that does not exist
|
||||||
|
// yet.
|
||||||
|
MkdirAll(path string, perm os.FileMode) error
|
||||||
|
|
||||||
|
// Open opens a file, returning it or an error, if any happens.
|
||||||
|
Open(name string) (File, error)
|
||||||
|
|
||||||
|
// OpenFile opens a file using the given flags and the given mode.
|
||||||
|
OpenFile(name string, flag int, perm os.FileMode) (File, error)
|
||||||
|
|
||||||
|
// Remove removes a file identified by name, returning an error, if any
|
||||||
|
// happens.
|
||||||
|
Remove(name string) error
|
||||||
|
|
||||||
|
// RemoveAll removes a directory path and any children it contains. It
|
||||||
|
// does not fail if the path does not exist (return nil).
|
||||||
|
RemoveAll(path string) error
|
||||||
|
|
||||||
|
// Rename renames a file.
|
||||||
|
Rename(oldname, newname string) error
|
||||||
|
|
||||||
|
// Stat returns a FileInfo describing the named file, or an error, if any
|
||||||
|
// happens.
|
||||||
|
Stat(name string) (os.FileInfo, error)
|
||||||
|
|
||||||
|
// The name of this FileSystem
|
||||||
|
Name() string
|
||||||
|
|
||||||
|
//Chmod changes the mode of the named file to mode.
|
||||||
|
Chmod(name string, mode os.FileMode) error
|
||||||
|
|
||||||
|
//Chtimes changes the access and modification times of the named file
|
||||||
|
Chtimes(name string, atime time.Time, mtime time.Time) error
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrFileClosed = errors.New("File is closed")
|
||||||
|
ErrOutOfRange = errors.New("Out of range")
|
||||||
|
ErrTooLarge = errors.New("Too large")
|
||||||
|
ErrFileNotFound = os.ErrNotExist
|
||||||
|
ErrFileExists = os.ErrExist
|
||||||
|
ErrDestinationExists = os.ErrExist
|
||||||
|
)
|
699
vendor/github.com/spf13/afero/afero_test.go
generated
vendored
Normal file
699
vendor/github.com/spf13/afero/afero_test.go
generated
vendored
Normal file
|
@ -0,0 +1,699 @@
|
||||||
|
// Copyright © 2014 Steve Francia <spf@spf13.com>.
|
||||||
|
// Copyright 2009 The Go Authors. All rights reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package afero
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var testName = "test.txt"
|
||||||
|
var Fss = []Fs{&MemMapFs{}, &OsFs{}}
|
||||||
|
|
||||||
|
var testRegistry map[Fs][]string = make(map[Fs][]string)
|
||||||
|
|
||||||
|
func testDir(fs Fs) string {
|
||||||
|
name, err := TempDir(fs, "", "afero")
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprint("unable to work with test dir", err))
|
||||||
|
}
|
||||||
|
testRegistry[fs] = append(testRegistry[fs], name)
|
||||||
|
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
|
func tmpFile(fs Fs) File {
|
||||||
|
x, err := TempFile(fs, "", "afero")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprint("unable to work with temp file", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
testRegistry[fs] = append(testRegistry[fs], x.Name())
|
||||||
|
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
|
||||||
|
//Read with length 0 should not return EOF.
|
||||||
|
func TestRead0(t *testing.T) {
|
||||||
|
for _, fs := range Fss {
|
||||||
|
f := tmpFile(fs)
|
||||||
|
defer f.Close()
|
||||||
|
f.WriteString("Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.")
|
||||||
|
|
||||||
|
var b []byte
|
||||||
|
// b := make([]byte, 0)
|
||||||
|
n, err := f.Read(b)
|
||||||
|
if n != 0 || err != nil {
|
||||||
|
t.Errorf("%v: Read(0) = %d, %v, want 0, nil", fs.Name(), n, err)
|
||||||
|
}
|
||||||
|
f.Seek(0, 0)
|
||||||
|
b = make([]byte, 100)
|
||||||
|
n, err = f.Read(b)
|
||||||
|
if n <= 0 || err != nil {
|
||||||
|
t.Errorf("%v: Read(100) = %d, %v, want >0, nil", fs.Name(), n, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOpenFile(t *testing.T) {
|
||||||
|
defer removeAllTestFiles(t)
|
||||||
|
for _, fs := range Fss {
|
||||||
|
tmp := testDir(fs)
|
||||||
|
path := filepath.Join(tmp, testName)
|
||||||
|
|
||||||
|
f, err := fs.OpenFile(path, os.O_RDWR|os.O_CREATE, 0600)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(fs.Name(), "OpenFile (O_CREATE) failed:", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
io.WriteString(f, "initial")
|
||||||
|
f.Close()
|
||||||
|
|
||||||
|
f, err = fs.OpenFile(path, os.O_WRONLY|os.O_APPEND, 0600)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(fs.Name(), "OpenFile (O_APPEND) failed:", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
io.WriteString(f, "|append")
|
||||||
|
f.Close()
|
||||||
|
|
||||||
|
f, err = fs.OpenFile(path, os.O_RDONLY, 0600)
|
||||||
|
contents, _ := ioutil.ReadAll(f)
|
||||||
|
expectedContents := "initial|append"
|
||||||
|
if string(contents) != expectedContents {
|
||||||
|
t.Errorf("%v: appending, expected '%v', got: '%v'", fs.Name(), expectedContents, string(contents))
|
||||||
|
}
|
||||||
|
f.Close()
|
||||||
|
|
||||||
|
f, err = fs.OpenFile(path, os.O_RDWR|os.O_TRUNC, 0600)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(fs.Name(), "OpenFile (O_TRUNC) failed:", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
contents, _ = ioutil.ReadAll(f)
|
||||||
|
if string(contents) != "" {
|
||||||
|
t.Errorf("%v: expected truncated file, got: '%v'", fs.Name(), string(contents))
|
||||||
|
}
|
||||||
|
f.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreate(t *testing.T) {
|
||||||
|
defer removeAllTestFiles(t)
|
||||||
|
for _, fs := range Fss {
|
||||||
|
tmp := testDir(fs)
|
||||||
|
path := filepath.Join(tmp, testName)
|
||||||
|
|
||||||
|
f, err := fs.Create(path)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(fs.Name(), "Create failed:", err)
|
||||||
|
f.Close()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
io.WriteString(f, "initial")
|
||||||
|
f.Close()
|
||||||
|
|
||||||
|
f, err = fs.Create(path)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(fs.Name(), "Create failed:", err)
|
||||||
|
f.Close()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
secondContent := "second create"
|
||||||
|
io.WriteString(f, secondContent)
|
||||||
|
f.Close()
|
||||||
|
|
||||||
|
f, err = fs.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(fs.Name(), "Open failed:", err)
|
||||||
|
f.Close()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
buf, err := ReadAll(f)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(fs.Name(), "ReadAll failed:", err)
|
||||||
|
f.Close()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if string(buf) != secondContent {
|
||||||
|
t.Error(fs.Name(), "Content should be", "\""+secondContent+"\" but is \""+string(buf)+"\"")
|
||||||
|
f.Close()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
f.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMemFileRead(t *testing.T) {
|
||||||
|
f := tmpFile(new(MemMapFs))
|
||||||
|
// f := MemFileCreate("testfile")
|
||||||
|
f.WriteString("abcd")
|
||||||
|
f.Seek(0, 0)
|
||||||
|
b := make([]byte, 8)
|
||||||
|
n, err := f.Read(b)
|
||||||
|
if n != 4 {
|
||||||
|
t.Errorf("didn't read all bytes: %v %v %v", n, err, b)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("err is not nil: %v %v %v", n, err, b)
|
||||||
|
}
|
||||||
|
n, err = f.Read(b)
|
||||||
|
if n != 0 {
|
||||||
|
t.Errorf("read more bytes: %v %v %v", n, err, b)
|
||||||
|
}
|
||||||
|
if err != io.EOF {
|
||||||
|
t.Errorf("error is not EOF: %v %v %v", n, err, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRename(t *testing.T) {
|
||||||
|
defer removeAllTestFiles(t)
|
||||||
|
for _, fs := range Fss {
|
||||||
|
tDir := testDir(fs)
|
||||||
|
from := filepath.Join(tDir, "/renamefrom")
|
||||||
|
to := filepath.Join(tDir, "/renameto")
|
||||||
|
exists := filepath.Join(tDir, "/renameexists")
|
||||||
|
file, err := fs.Create(from)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%s: open %q failed: %v", fs.Name(), to, err)
|
||||||
|
}
|
||||||
|
if err = file.Close(); err != nil {
|
||||||
|
t.Errorf("%s: close %q failed: %v", fs.Name(), to, err)
|
||||||
|
}
|
||||||
|
file, err = fs.Create(exists)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%s: open %q failed: %v", fs.Name(), to, err)
|
||||||
|
}
|
||||||
|
if err = file.Close(); err != nil {
|
||||||
|
t.Errorf("%s: close %q failed: %v", fs.Name(), to, err)
|
||||||
|
}
|
||||||
|
err = fs.Rename(from, to)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%s: rename %q, %q failed: %v", fs.Name(), to, from, err)
|
||||||
|
}
|
||||||
|
file, err = fs.Create(from)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%s: open %q failed: %v", fs.Name(), to, err)
|
||||||
|
}
|
||||||
|
if err = file.Close(); err != nil {
|
||||||
|
t.Errorf("%s: close %q failed: %v", fs.Name(), to, err)
|
||||||
|
}
|
||||||
|
err = fs.Rename(from, exists)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s: rename %q, %q failed: %v", fs.Name(), exists, from, err)
|
||||||
|
}
|
||||||
|
names, err := readDirNames(fs, tDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s: readDirNames error: %v", fs.Name(), err)
|
||||||
|
}
|
||||||
|
found := false
|
||||||
|
for _, e := range names {
|
||||||
|
if e == "renamefrom" {
|
||||||
|
t.Error("File is still called renamefrom")
|
||||||
|
}
|
||||||
|
if e == "renameto" {
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
t.Error("File was not renamed to renameto")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = fs.Stat(to)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s: stat %q failed: %v", fs.Name(), to, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRemove(t *testing.T) {
|
||||||
|
for _, fs := range Fss {
|
||||||
|
|
||||||
|
x, err := TempFile(fs, "", "afero")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(fmt.Sprint("unable to work with temp file", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
path := x.Name()
|
||||||
|
x.Close()
|
||||||
|
|
||||||
|
tDir := filepath.Dir(path)
|
||||||
|
|
||||||
|
err = fs.Remove(path)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%v: Remove() failed: %v", fs.Name(), err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = fs.Stat(path)
|
||||||
|
if !os.IsNotExist(err) {
|
||||||
|
t.Errorf("%v: Remove() didn't remove file", fs.Name())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deleting non-existent file should raise error
|
||||||
|
err = fs.Remove(path)
|
||||||
|
if !os.IsNotExist(err) {
|
||||||
|
t.Errorf("%v: Remove() didn't raise error for non-existent file", fs.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := fs.Open(tDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("TestDir should still exist:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
names, err := f.Readdirnames(-1)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("Readdirnames failed:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, e := range names {
|
||||||
|
if e == testName {
|
||||||
|
t.Error("File was not removed from parent directory")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTruncate(t *testing.T) {
|
||||||
|
defer removeAllTestFiles(t)
|
||||||
|
for _, fs := range Fss {
|
||||||
|
f := tmpFile(fs)
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
checkSize(t, f, 0)
|
||||||
|
f.Write([]byte("hello, world\n"))
|
||||||
|
checkSize(t, f, 13)
|
||||||
|
f.Truncate(10)
|
||||||
|
checkSize(t, f, 10)
|
||||||
|
f.Truncate(1024)
|
||||||
|
checkSize(t, f, 1024)
|
||||||
|
f.Truncate(0)
|
||||||
|
checkSize(t, f, 0)
|
||||||
|
_, err := f.Write([]byte("surprise!"))
|
||||||
|
if err == nil {
|
||||||
|
checkSize(t, f, 13+9) // wrote at offset past where hello, world was.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSeek(t *testing.T) {
|
||||||
|
defer removeAllTestFiles(t)
|
||||||
|
for _, fs := range Fss {
|
||||||
|
f := tmpFile(fs)
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
const data = "hello, world\n"
|
||||||
|
io.WriteString(f, data)
|
||||||
|
|
||||||
|
type test struct {
|
||||||
|
in int64
|
||||||
|
whence int
|
||||||
|
out int64
|
||||||
|
}
|
||||||
|
var tests = []test{
|
||||||
|
{0, 1, int64(len(data))},
|
||||||
|
{0, 0, 0},
|
||||||
|
{5, 0, 5},
|
||||||
|
{0, 2, int64(len(data))},
|
||||||
|
{0, 0, 0},
|
||||||
|
{-1, 2, int64(len(data)) - 1},
|
||||||
|
{1 << 33, 0, 1 << 33},
|
||||||
|
{1 << 33, 2, 1<<33 + int64(len(data))},
|
||||||
|
}
|
||||||
|
for i, tt := range tests {
|
||||||
|
off, err := f.Seek(tt.in, tt.whence)
|
||||||
|
if off != tt.out || err != nil {
|
||||||
|
if e, ok := err.(*os.PathError); ok && e.Err == syscall.EINVAL && tt.out > 1<<32 {
|
||||||
|
// Reiserfs rejects the big seeks.
|
||||||
|
// http://code.google.com/p/go/issues/detail?id=91
|
||||||
|
break
|
||||||
|
}
|
||||||
|
t.Errorf("#%d: Seek(%v, %v) = %v, %v want %v, nil", i, tt.in, tt.whence, off, err, tt.out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReadAt(t *testing.T) {
|
||||||
|
defer removeAllTestFiles(t)
|
||||||
|
for _, fs := range Fss {
|
||||||
|
f := tmpFile(fs)
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
const data = "hello, world\n"
|
||||||
|
io.WriteString(f, data)
|
||||||
|
|
||||||
|
b := make([]byte, 5)
|
||||||
|
n, err := f.ReadAt(b, 7)
|
||||||
|
if err != nil || n != len(b) {
|
||||||
|
t.Fatalf("ReadAt 7: %d, %v", n, err)
|
||||||
|
}
|
||||||
|
if string(b) != "world" {
|
||||||
|
t.Fatalf("ReadAt 7: have %q want %q", string(b), "world")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWriteAt(t *testing.T) {
|
||||||
|
defer removeAllTestFiles(t)
|
||||||
|
for _, fs := range Fss {
|
||||||
|
f := tmpFile(fs)
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
const data = "hello, world\n"
|
||||||
|
io.WriteString(f, data)
|
||||||
|
|
||||||
|
n, err := f.WriteAt([]byte("WORLD"), 7)
|
||||||
|
if err != nil || n != 5 {
|
||||||
|
t.Fatalf("WriteAt 7: %d, %v", n, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
f2, err := fs.Open(f.Name())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%v: ReadFile %s: %v", fs.Name(), f.Name(), err)
|
||||||
|
}
|
||||||
|
defer f2.Close()
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
buf.ReadFrom(f2)
|
||||||
|
b := buf.Bytes()
|
||||||
|
if string(b) != "hello, WORLD\n" {
|
||||||
|
t.Fatalf("after write: have %q want %q", string(b), "hello, WORLD\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupTestDir(t *testing.T, fs Fs) string {
|
||||||
|
path := testDir(fs)
|
||||||
|
return setupTestFiles(t, fs, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupTestDirRoot(t *testing.T, fs Fs) string {
|
||||||
|
path := testDir(fs)
|
||||||
|
setupTestFiles(t, fs, path)
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupTestDirReusePath(t *testing.T, fs Fs, path string) string {
|
||||||
|
testRegistry[fs] = append(testRegistry[fs], path)
|
||||||
|
return setupTestFiles(t, fs, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupTestFiles(t *testing.T, fs Fs, path string) string {
|
||||||
|
testSubDir := filepath.Join(path, "more", "subdirectories", "for", "testing", "we")
|
||||||
|
err := fs.MkdirAll(testSubDir, 0700)
|
||||||
|
if err != nil && !os.IsExist(err) {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := fs.Create(filepath.Join(testSubDir, "testfile1"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
f.WriteString("Testfile 1 content")
|
||||||
|
f.Close()
|
||||||
|
|
||||||
|
f, err = fs.Create(filepath.Join(testSubDir, "testfile2"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
f.WriteString("Testfile 2 content")
|
||||||
|
f.Close()
|
||||||
|
|
||||||
|
f, err = fs.Create(filepath.Join(testSubDir, "testfile3"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
f.WriteString("Testfile 3 content")
|
||||||
|
f.Close()
|
||||||
|
|
||||||
|
f, err = fs.Create(filepath.Join(testSubDir, "testfile4"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
f.WriteString("Testfile 4 content")
|
||||||
|
f.Close()
|
||||||
|
return testSubDir
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReaddirnames(t *testing.T) {
|
||||||
|
defer removeAllTestFiles(t)
|
||||||
|
for _, fs := range Fss {
|
||||||
|
testSubDir := setupTestDir(t, fs)
|
||||||
|
tDir := filepath.Dir(testSubDir)
|
||||||
|
|
||||||
|
root, err := fs.Open(tDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(fs.Name(), tDir, err)
|
||||||
|
}
|
||||||
|
defer root.Close()
|
||||||
|
|
||||||
|
namesRoot, err := root.Readdirnames(-1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(fs.Name(), namesRoot, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sub, err := fs.Open(testSubDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer sub.Close()
|
||||||
|
|
||||||
|
namesSub, err := sub.Readdirnames(-1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(fs.Name(), namesSub, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
findNames(fs, t, tDir, testSubDir, namesRoot, namesSub)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReaddirSimple(t *testing.T) {
|
||||||
|
defer removeAllTestFiles(t)
|
||||||
|
for _, fs := range Fss {
|
||||||
|
testSubDir := setupTestDir(t, fs)
|
||||||
|
tDir := filepath.Dir(testSubDir)
|
||||||
|
|
||||||
|
root, err := fs.Open(tDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer root.Close()
|
||||||
|
|
||||||
|
rootInfo, err := root.Readdir(1)
|
||||||
|
if err != nil {
|
||||||
|
t.Log(myFileInfo(rootInfo))
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rootInfo, err = root.Readdir(5)
|
||||||
|
if err != io.EOF {
|
||||||
|
t.Log(myFileInfo(rootInfo))
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sub, err := fs.Open(testSubDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer sub.Close()
|
||||||
|
|
||||||
|
subInfo, err := sub.Readdir(5)
|
||||||
|
if err != nil {
|
||||||
|
t.Log(myFileInfo(subInfo))
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReaddir(t *testing.T) {
|
||||||
|
defer removeAllTestFiles(t)
|
||||||
|
for num := 0; num < 6; num++ {
|
||||||
|
outputs := make([]string, len(Fss))
|
||||||
|
infos := make([]string, len(Fss))
|
||||||
|
for i, fs := range Fss {
|
||||||
|
testSubDir := setupTestDir(t, fs)
|
||||||
|
//tDir := filepath.Dir(testSubDir)
|
||||||
|
root, err := fs.Open(testSubDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer root.Close()
|
||||||
|
|
||||||
|
for j := 0; j < 6; j++ {
|
||||||
|
info, err := root.Readdir(num)
|
||||||
|
outputs[i] += fmt.Sprintf("%v Error: %v\n", myFileInfo(info), err)
|
||||||
|
infos[i] += fmt.Sprintln(len(info), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fail := false
|
||||||
|
for i, o := range infos {
|
||||||
|
if i == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if o != infos[i-1] {
|
||||||
|
fail = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if fail {
|
||||||
|
t.Log("Readdir outputs not equal for Readdir(", num, ")")
|
||||||
|
for i, o := range outputs {
|
||||||
|
t.Log(Fss[i].Name())
|
||||||
|
t.Log(o)
|
||||||
|
}
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type myFileInfo []os.FileInfo
|
||||||
|
|
||||||
|
func (m myFileInfo) String() string {
|
||||||
|
out := "Fileinfos:\n"
|
||||||
|
for _, e := range m {
|
||||||
|
out += " " + e.Name() + "\n"
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReaddirAll(t *testing.T) {
|
||||||
|
defer removeAllTestFiles(t)
|
||||||
|
for _, fs := range Fss {
|
||||||
|
testSubDir := setupTestDir(t, fs)
|
||||||
|
tDir := filepath.Dir(testSubDir)
|
||||||
|
|
||||||
|
root, err := fs.Open(tDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer root.Close()
|
||||||
|
|
||||||
|
rootInfo, err := root.Readdir(-1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
var namesRoot = []string{}
|
||||||
|
for _, e := range rootInfo {
|
||||||
|
namesRoot = append(namesRoot, e.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
sub, err := fs.Open(testSubDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer sub.Close()
|
||||||
|
|
||||||
|
subInfo, err := sub.Readdir(-1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
var namesSub = []string{}
|
||||||
|
for _, e := range subInfo {
|
||||||
|
namesSub = append(namesSub, e.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
findNames(fs, t, tDir, testSubDir, namesRoot, namesSub)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func findNames(fs Fs, t *testing.T, tDir, testSubDir string, root, sub []string) {
|
||||||
|
var foundRoot bool
|
||||||
|
for _, e := range root {
|
||||||
|
f, err := fs.Open(filepath.Join(tDir, e))
|
||||||
|
if err != nil {
|
||||||
|
t.Error("Open", filepath.Join(tDir, e), ":", err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
if equal(e, "we") {
|
||||||
|
foundRoot = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !foundRoot {
|
||||||
|
t.Logf("Names root: %v", root)
|
||||||
|
t.Logf("Names sub: %v", sub)
|
||||||
|
t.Error("Didn't find subdirectory we")
|
||||||
|
}
|
||||||
|
|
||||||
|
var found1, found2 bool
|
||||||
|
for _, e := range sub {
|
||||||
|
f, err := fs.Open(filepath.Join(testSubDir, e))
|
||||||
|
if err != nil {
|
||||||
|
t.Error("Open", filepath.Join(testSubDir, e), ":", err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
if equal(e, "testfile1") {
|
||||||
|
found1 = true
|
||||||
|
}
|
||||||
|
if equal(e, "testfile2") {
|
||||||
|
found2 = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found1 {
|
||||||
|
t.Logf("Names root: %v", root)
|
||||||
|
t.Logf("Names sub: %v", sub)
|
||||||
|
t.Error("Didn't find testfile1")
|
||||||
|
}
|
||||||
|
if !found2 {
|
||||||
|
t.Logf("Names root: %v", root)
|
||||||
|
t.Logf("Names sub: %v", sub)
|
||||||
|
t.Error("Didn't find testfile2")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeAllTestFiles(t *testing.T) {
|
||||||
|
for fs, list := range testRegistry {
|
||||||
|
for _, path := range list {
|
||||||
|
if err := fs.RemoveAll(path); err != nil {
|
||||||
|
t.Error(fs.Name(), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
testRegistry = make(map[Fs][]string)
|
||||||
|
}
|
||||||
|
|
||||||
|
func equal(name1, name2 string) (r bool) {
|
||||||
|
switch runtime.GOOS {
|
||||||
|
case "windows":
|
||||||
|
r = strings.ToLower(name1) == strings.ToLower(name2)
|
||||||
|
default:
|
||||||
|
r = name1 == name2
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkSize(t *testing.T, f File, size int64) {
|
||||||
|
dir, err := f.Stat()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Stat %q (looking for size %d): %s", f.Name(), size, err)
|
||||||
|
}
|
||||||
|
if dir.Size() != size {
|
||||||
|
t.Errorf("Stat %q: size %d want %d", f.Name(), dir.Size(), size)
|
||||||
|
}
|
||||||
|
}
|
15
vendor/github.com/spf13/afero/appveyor.yml
generated
vendored
Normal file
15
vendor/github.com/spf13/afero/appveyor.yml
generated
vendored
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
version: '{build}'
|
||||||
|
clone_folder: C:\gopath\src\github.com\spf13\afero
|
||||||
|
environment:
|
||||||
|
GOPATH: C:\gopath
|
||||||
|
build_script:
|
||||||
|
- cmd: >-
|
||||||
|
go version
|
||||||
|
|
||||||
|
go env
|
||||||
|
|
||||||
|
go get -v github.com/spf13/afero/...
|
||||||
|
|
||||||
|
go build github.com/spf13/afero
|
||||||
|
test_script:
|
||||||
|
- cmd: go test -race -v github.com/spf13/afero/...
|
145
vendor/github.com/spf13/afero/basepath.go
generated
vendored
Normal file
145
vendor/github.com/spf13/afero/basepath.go
generated
vendored
Normal file
|
@ -0,0 +1,145 @@
|
||||||
|
package afero
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The BasePathFs restricts all operations to a given path within an Fs.
|
||||||
|
// The given file name to the operations on this Fs will be prepended with
|
||||||
|
// the base path before calling the base Fs.
|
||||||
|
// Any file name (after filepath.Clean()) outside this base path will be
|
||||||
|
// treated as non existing file.
|
||||||
|
//
|
||||||
|
// Note that it does not clean the error messages on return, so you may
|
||||||
|
// reveal the real path on errors.
|
||||||
|
type BasePathFs struct {
|
||||||
|
source Fs
|
||||||
|
path string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBasePathFs(source Fs, path string) Fs {
|
||||||
|
return &BasePathFs{source: source, path: path}
|
||||||
|
}
|
||||||
|
|
||||||
|
// on a file outside the base path it returns the given file name and an error,
|
||||||
|
// else the given file with the base path prepended
|
||||||
|
func (b *BasePathFs) RealPath(name string) (path string, err error) {
|
||||||
|
if err := validateBasePathName(name); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
bpath := filepath.Clean(b.path)
|
||||||
|
path = filepath.Clean(filepath.Join(bpath, name))
|
||||||
|
if !strings.HasPrefix(path, bpath) {
|
||||||
|
return name, os.ErrNotExist
|
||||||
|
}
|
||||||
|
|
||||||
|
return path, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateBasePathName(name string) error {
|
||||||
|
if runtime.GOOS != "windows" {
|
||||||
|
// Not much to do here;
|
||||||
|
// the virtual file paths all look absolute on *nix.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// On Windows a common mistake would be to provide an absolute OS path
|
||||||
|
// We could strip out the base part, but that would not be very portable.
|
||||||
|
if filepath.IsAbs(name) {
|
||||||
|
return &os.PathError{Op: "realPath", Path: name, Err: errors.New("got a real OS path instead of a virtual")}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BasePathFs) Chtimes(name string, atime, mtime time.Time) (err error) {
|
||||||
|
if name, err = b.RealPath(name); err != nil {
|
||||||
|
return &os.PathError{Op: "chtimes", Path: name, Err: err}
|
||||||
|
}
|
||||||
|
return b.source.Chtimes(name, atime, mtime)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BasePathFs) Chmod(name string, mode os.FileMode) (err error) {
|
||||||
|
if name, err = b.RealPath(name); err != nil {
|
||||||
|
return &os.PathError{Op: "chmod", Path: name, Err: err}
|
||||||
|
}
|
||||||
|
return b.source.Chmod(name, mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BasePathFs) Name() string {
|
||||||
|
return "BasePathFs"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BasePathFs) Stat(name string) (fi os.FileInfo, err error) {
|
||||||
|
if name, err = b.RealPath(name); err != nil {
|
||||||
|
return nil, &os.PathError{Op: "stat", Path: name, Err: err}
|
||||||
|
}
|
||||||
|
return b.source.Stat(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BasePathFs) Rename(oldname, newname string) (err error) {
|
||||||
|
if oldname, err = b.RealPath(oldname); err != nil {
|
||||||
|
return &os.PathError{Op: "rename", Path: oldname, Err: err}
|
||||||
|
}
|
||||||
|
if newname, err = b.RealPath(newname); err != nil {
|
||||||
|
return &os.PathError{Op: "rename", Path: newname, Err: err}
|
||||||
|
}
|
||||||
|
return b.source.Rename(oldname, newname)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BasePathFs) RemoveAll(name string) (err error) {
|
||||||
|
if name, err = b.RealPath(name); err != nil {
|
||||||
|
return &os.PathError{Op: "remove_all", Path: name, Err: err}
|
||||||
|
}
|
||||||
|
return b.source.RemoveAll(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BasePathFs) Remove(name string) (err error) {
|
||||||
|
if name, err = b.RealPath(name); err != nil {
|
||||||
|
return &os.PathError{Op: "remove", Path: name, Err: err}
|
||||||
|
}
|
||||||
|
return b.source.Remove(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BasePathFs) OpenFile(name string, flag int, mode os.FileMode) (f File, err error) {
|
||||||
|
if name, err = b.RealPath(name); err != nil {
|
||||||
|
return nil, &os.PathError{Op: "openfile", Path: name, Err: err}
|
||||||
|
}
|
||||||
|
return b.source.OpenFile(name, flag, mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BasePathFs) Open(name string) (f File, err error) {
|
||||||
|
if name, err = b.RealPath(name); err != nil {
|
||||||
|
return nil, &os.PathError{Op: "open", Path: name, Err: err}
|
||||||
|
}
|
||||||
|
return b.source.Open(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BasePathFs) Mkdir(name string, mode os.FileMode) (err error) {
|
||||||
|
if name, err = b.RealPath(name); err != nil {
|
||||||
|
return &os.PathError{Op: "mkdir", Path: name, Err: err}
|
||||||
|
}
|
||||||
|
return b.source.Mkdir(name, mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BasePathFs) MkdirAll(name string, mode os.FileMode) (err error) {
|
||||||
|
if name, err = b.RealPath(name); err != nil {
|
||||||
|
return &os.PathError{Op: "mkdir", Path: name, Err: err}
|
||||||
|
}
|
||||||
|
return b.source.MkdirAll(name, mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BasePathFs) Create(name string) (f File, err error) {
|
||||||
|
if name, err = b.RealPath(name); err != nil {
|
||||||
|
return nil, &os.PathError{Op: "create", Path: name, Err: err}
|
||||||
|
}
|
||||||
|
return b.source.Create(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// vim: ts=4 sw=4 noexpandtab nolist syn=go
|
142
vendor/github.com/spf13/afero/basepath_test.go
generated
vendored
Normal file
142
vendor/github.com/spf13/afero/basepath_test.go
generated
vendored
Normal file
|
@ -0,0 +1,142 @@
|
||||||
|
package afero
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBasePath(t *testing.T) {
|
||||||
|
baseFs := &MemMapFs{}
|
||||||
|
baseFs.MkdirAll("/base/path/tmp", 0777)
|
||||||
|
bp := NewBasePathFs(baseFs, "/base/path")
|
||||||
|
|
||||||
|
if _, err := bp.Create("/tmp/foo"); err != nil {
|
||||||
|
t.Errorf("Failed to set real path")
|
||||||
|
}
|
||||||
|
|
||||||
|
if fh, err := bp.Create("../tmp/bar"); err == nil {
|
||||||
|
t.Errorf("succeeded in creating %s ...", fh.Name())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBasePathRoot(t *testing.T) {
|
||||||
|
baseFs := &MemMapFs{}
|
||||||
|
baseFs.MkdirAll("/base/path/foo/baz", 0777)
|
||||||
|
baseFs.MkdirAll("/base/path/boo/", 0777)
|
||||||
|
bp := NewBasePathFs(baseFs, "/base/path")
|
||||||
|
|
||||||
|
rd, err := ReadDir(bp, string(os.PathSeparator))
|
||||||
|
|
||||||
|
if len(rd) != 2 {
|
||||||
|
t.Errorf("base path doesn't respect root")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRealPath(t *testing.T) {
|
||||||
|
fs := NewOsFs()
|
||||||
|
baseDir, err := TempDir(fs, "", "base")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("error creating tempDir", err)
|
||||||
|
}
|
||||||
|
defer fs.RemoveAll(baseDir)
|
||||||
|
anotherDir, err := TempDir(fs, "", "another")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("error creating tempDir", err)
|
||||||
|
}
|
||||||
|
defer fs.RemoveAll(anotherDir)
|
||||||
|
|
||||||
|
bp := NewBasePathFs(fs, baseDir).(*BasePathFs)
|
||||||
|
|
||||||
|
subDir := filepath.Join(baseDir, "s1")
|
||||||
|
|
||||||
|
realPath, err := bp.RealPath("/s1")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Got error %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if realPath != subDir {
|
||||||
|
t.Errorf("Expected \n%s got \n%s", subDir, realPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
_, err = bp.RealPath(anotherDir)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("Expected error")
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// on *nix we have no way of just looking at the path and tell that anotherDir
|
||||||
|
// is not inside the base file system.
|
||||||
|
// The user will receive an os.ErrNotExist later.
|
||||||
|
surrealPath, err := bp.RealPath(anotherDir)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Got error %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
excpected := filepath.Join(baseDir, anotherDir)
|
||||||
|
|
||||||
|
if surrealPath != excpected {
|
||||||
|
t.Errorf("Expected \n%s got \n%s", excpected, surrealPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNestedBasePaths(t *testing.T) {
|
||||||
|
type dirSpec struct {
|
||||||
|
Dir1, Dir2, Dir3 string
|
||||||
|
}
|
||||||
|
dirSpecs := []dirSpec{
|
||||||
|
dirSpec{Dir1: "/", Dir2: "/", Dir3: "/"},
|
||||||
|
dirSpec{Dir1: "/", Dir2: "/path2", Dir3: "/"},
|
||||||
|
dirSpec{Dir1: "/path1/dir", Dir2: "/path2/dir/", Dir3: "/path3/dir"},
|
||||||
|
dirSpec{Dir1: "C:/path1", Dir2: "path2/dir", Dir3: "/path3/dir/"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ds := range dirSpecs {
|
||||||
|
memFs := NewMemMapFs()
|
||||||
|
level1Fs := NewBasePathFs(memFs, ds.Dir1)
|
||||||
|
level2Fs := NewBasePathFs(level1Fs, ds.Dir2)
|
||||||
|
level3Fs := NewBasePathFs(level2Fs, ds.Dir3)
|
||||||
|
|
||||||
|
type spec struct {
|
||||||
|
BaseFs Fs
|
||||||
|
FileName string
|
||||||
|
}
|
||||||
|
specs := []spec{
|
||||||
|
spec{BaseFs: level3Fs, FileName: "f.txt"},
|
||||||
|
spec{BaseFs: level2Fs, FileName: "f.txt"},
|
||||||
|
spec{BaseFs: level1Fs, FileName: "f.txt"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, s := range specs {
|
||||||
|
if err := s.BaseFs.MkdirAll(s.FileName, 0755); err != nil {
|
||||||
|
t.Errorf("Got error %s", err.Error())
|
||||||
|
}
|
||||||
|
if _, err := s.BaseFs.Stat(s.FileName); err != nil {
|
||||||
|
t.Errorf("Got error %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.BaseFs == level3Fs {
|
||||||
|
pathToExist := filepath.Join(ds.Dir3, s.FileName)
|
||||||
|
if _, err := level2Fs.Stat(pathToExist); err != nil {
|
||||||
|
t.Errorf("Got error %s (path %s)", err.Error(), pathToExist)
|
||||||
|
}
|
||||||
|
} else if s.BaseFs == level2Fs {
|
||||||
|
pathToExist := filepath.Join(ds.Dir2, ds.Dir3, s.FileName)
|
||||||
|
if _, err := level1Fs.Stat(pathToExist); err != nil {
|
||||||
|
t.Errorf("Got error %s (path %s)", err.Error(), pathToExist)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
290
vendor/github.com/spf13/afero/cacheOnReadFs.go
generated
vendored
Normal file
290
vendor/github.com/spf13/afero/cacheOnReadFs.go
generated
vendored
Normal file
|
@ -0,0 +1,290 @@
|
||||||
|
package afero
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// If the cache duration is 0, cache time will be unlimited, i.e. once
|
||||||
|
// a file is in the layer, the base will never be read again for this file.
|
||||||
|
//
|
||||||
|
// For cache times greater than 0, the modification time of a file is
|
||||||
|
// checked. Note that a lot of file system implementations only allow a
|
||||||
|
// resolution of a second for timestamps... or as the godoc for os.Chtimes()
|
||||||
|
// states: "The underlying filesystem may truncate or round the values to a
|
||||||
|
// less precise time unit."
|
||||||
|
//
|
||||||
|
// This caching union will forward all write calls also to the base file
|
||||||
|
// system first. To prevent writing to the base Fs, wrap it in a read-only
|
||||||
|
// filter - Note: this will also make the overlay read-only, for writing files
|
||||||
|
// in the overlay, use the overlay Fs directly, not via the union Fs.
|
||||||
|
type CacheOnReadFs struct {
|
||||||
|
base Fs
|
||||||
|
layer Fs
|
||||||
|
cacheTime time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCacheOnReadFs(base Fs, layer Fs, cacheTime time.Duration) Fs {
|
||||||
|
return &CacheOnReadFs{base: base, layer: layer, cacheTime: cacheTime}
|
||||||
|
}
|
||||||
|
|
||||||
|
type cacheState int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// not present in the overlay, unknown if it exists in the base:
|
||||||
|
cacheMiss cacheState = iota
|
||||||
|
// present in the overlay and in base, base file is newer:
|
||||||
|
cacheStale
|
||||||
|
// present in the overlay - with cache time == 0 it may exist in the base,
|
||||||
|
// with cacheTime > 0 it exists in the base and is same age or newer in the
|
||||||
|
// overlay
|
||||||
|
cacheHit
|
||||||
|
// happens if someone writes directly to the overlay without
|
||||||
|
// going through this union
|
||||||
|
cacheLocal
|
||||||
|
)
|
||||||
|
|
||||||
|
func (u *CacheOnReadFs) cacheStatus(name string) (state cacheState, fi os.FileInfo, err error) {
|
||||||
|
var lfi, bfi os.FileInfo
|
||||||
|
lfi, err = u.layer.Stat(name)
|
||||||
|
if err == nil {
|
||||||
|
if u.cacheTime == 0 {
|
||||||
|
return cacheHit, lfi, nil
|
||||||
|
}
|
||||||
|
if lfi.ModTime().Add(u.cacheTime).Before(time.Now()) {
|
||||||
|
bfi, err = u.base.Stat(name)
|
||||||
|
if err != nil {
|
||||||
|
return cacheLocal, lfi, nil
|
||||||
|
}
|
||||||
|
if bfi.ModTime().After(lfi.ModTime()) {
|
||||||
|
return cacheStale, bfi, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cacheHit, lfi, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == syscall.ENOENT || os.IsNotExist(err) {
|
||||||
|
return cacheMiss, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return cacheMiss, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *CacheOnReadFs) copyToLayer(name string) error {
|
||||||
|
return copyToLayer(u.base, u.layer, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *CacheOnReadFs) Chtimes(name string, atime, mtime time.Time) error {
|
||||||
|
st, _, err := u.cacheStatus(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch st {
|
||||||
|
case cacheLocal:
|
||||||
|
case cacheHit:
|
||||||
|
err = u.base.Chtimes(name, atime, mtime)
|
||||||
|
case cacheStale, cacheMiss:
|
||||||
|
if err := u.copyToLayer(name); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = u.base.Chtimes(name, atime, mtime)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return u.layer.Chtimes(name, atime, mtime)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *CacheOnReadFs) Chmod(name string, mode os.FileMode) error {
|
||||||
|
st, _, err := u.cacheStatus(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch st {
|
||||||
|
case cacheLocal:
|
||||||
|
case cacheHit:
|
||||||
|
err = u.base.Chmod(name, mode)
|
||||||
|
case cacheStale, cacheMiss:
|
||||||
|
if err := u.copyToLayer(name); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = u.base.Chmod(name, mode)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return u.layer.Chmod(name, mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *CacheOnReadFs) Stat(name string) (os.FileInfo, error) {
|
||||||
|
st, fi, err := u.cacheStatus(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
switch st {
|
||||||
|
case cacheMiss:
|
||||||
|
return u.base.Stat(name)
|
||||||
|
default: // cacheStale has base, cacheHit and cacheLocal the layer os.FileInfo
|
||||||
|
return fi, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *CacheOnReadFs) Rename(oldname, newname string) error {
|
||||||
|
st, _, err := u.cacheStatus(oldname)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch st {
|
||||||
|
case cacheLocal:
|
||||||
|
case cacheHit:
|
||||||
|
err = u.base.Rename(oldname, newname)
|
||||||
|
case cacheStale, cacheMiss:
|
||||||
|
if err := u.copyToLayer(oldname); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = u.base.Rename(oldname, newname)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return u.layer.Rename(oldname, newname)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *CacheOnReadFs) Remove(name string) error {
|
||||||
|
st, _, err := u.cacheStatus(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch st {
|
||||||
|
case cacheLocal:
|
||||||
|
case cacheHit, cacheStale, cacheMiss:
|
||||||
|
err = u.base.Remove(name)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return u.layer.Remove(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *CacheOnReadFs) RemoveAll(name string) error {
|
||||||
|
st, _, err := u.cacheStatus(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch st {
|
||||||
|
case cacheLocal:
|
||||||
|
case cacheHit, cacheStale, cacheMiss:
|
||||||
|
err = u.base.RemoveAll(name)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return u.layer.RemoveAll(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *CacheOnReadFs) OpenFile(name string, flag int, perm os.FileMode) (File, error) {
|
||||||
|
st, _, err := u.cacheStatus(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
switch st {
|
||||||
|
case cacheLocal, cacheHit:
|
||||||
|
default:
|
||||||
|
if err := u.copyToLayer(name); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if flag&(os.O_WRONLY|syscall.O_RDWR|os.O_APPEND|os.O_CREATE|os.O_TRUNC) != 0 {
|
||||||
|
bfi, err := u.base.OpenFile(name, flag, perm)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
lfi, err := u.layer.OpenFile(name, flag, perm)
|
||||||
|
if err != nil {
|
||||||
|
bfi.Close() // oops, what if O_TRUNC was set and file opening in the layer failed...?
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &UnionFile{base: bfi, layer: lfi}, nil
|
||||||
|
}
|
||||||
|
return u.layer.OpenFile(name, flag, perm)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *CacheOnReadFs) Open(name string) (File, error) {
|
||||||
|
st, fi, err := u.cacheStatus(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch st {
|
||||||
|
case cacheLocal:
|
||||||
|
return u.layer.Open(name)
|
||||||
|
|
||||||
|
case cacheMiss:
|
||||||
|
bfi, err := u.base.Stat(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if bfi.IsDir() {
|
||||||
|
return u.base.Open(name)
|
||||||
|
}
|
||||||
|
if err := u.copyToLayer(name); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return u.layer.Open(name)
|
||||||
|
|
||||||
|
case cacheStale:
|
||||||
|
if !fi.IsDir() {
|
||||||
|
if err := u.copyToLayer(name); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return u.layer.Open(name)
|
||||||
|
}
|
||||||
|
case cacheHit:
|
||||||
|
if !fi.IsDir() {
|
||||||
|
return u.layer.Open(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// the dirs from cacheHit, cacheStale fall down here:
|
||||||
|
bfile, _ := u.base.Open(name)
|
||||||
|
lfile, err := u.layer.Open(name)
|
||||||
|
if err != nil && bfile == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &UnionFile{base: bfile, layer: lfile}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *CacheOnReadFs) Mkdir(name string, perm os.FileMode) error {
|
||||||
|
err := u.base.Mkdir(name, perm)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return u.layer.MkdirAll(name, perm) // yes, MkdirAll... we cannot assume it exists in the cache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *CacheOnReadFs) Name() string {
|
||||||
|
return "CacheOnReadFs"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *CacheOnReadFs) MkdirAll(name string, perm os.FileMode) error {
|
||||||
|
err := u.base.MkdirAll(name, perm)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return u.layer.MkdirAll(name, perm)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *CacheOnReadFs) Create(name string) (File, error) {
|
||||||
|
bfh, err := u.base.Create(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
lfh, err := u.layer.Create(name)
|
||||||
|
if err != nil {
|
||||||
|
// oops, see comment about OS_TRUNC above, should we remove? then we have to
|
||||||
|
// remember if the file did not exist before
|
||||||
|
bfh.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &UnionFile{base: bfh, layer: lfh}, nil
|
||||||
|
}
|
404
vendor/github.com/spf13/afero/composite_test.go
generated
vendored
Normal file
404
vendor/github.com/spf13/afero/composite_test.go
generated
vendored
Normal file
|
@ -0,0 +1,404 @@
|
||||||
|
package afero
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var tempDirs []string
|
||||||
|
|
||||||
|
func NewTempOsBaseFs(t *testing.T) Fs {
|
||||||
|
name, err := TempDir(NewOsFs(), "", "")
|
||||||
|
if err != nil {
|
||||||
|
t.Error("error creating tempDir", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tempDirs = append(tempDirs, name)
|
||||||
|
|
||||||
|
return NewBasePathFs(NewOsFs(), name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func CleanupTempDirs(t *testing.T) {
|
||||||
|
osfs := NewOsFs()
|
||||||
|
type ev struct {
|
||||||
|
path string
|
||||||
|
e error
|
||||||
|
}
|
||||||
|
|
||||||
|
errs := []ev{}
|
||||||
|
|
||||||
|
for _, x := range tempDirs {
|
||||||
|
err := osfs.RemoveAll(x)
|
||||||
|
if err != nil {
|
||||||
|
errs = append(errs, ev{path: x, e: err})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, e := range errs {
|
||||||
|
fmt.Println("error removing tempDir", e.path, e.e)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Error("error cleaning up tempDirs")
|
||||||
|
}
|
||||||
|
tempDirs = []string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnionCreateExisting(t *testing.T) {
|
||||||
|
base := &MemMapFs{}
|
||||||
|
roBase := &ReadOnlyFs{source: base}
|
||||||
|
|
||||||
|
ufs := NewCopyOnWriteFs(roBase, &MemMapFs{})
|
||||||
|
|
||||||
|
base.MkdirAll("/home/test", 0777)
|
||||||
|
fh, _ := base.Create("/home/test/file.txt")
|
||||||
|
fh.WriteString("This is a test")
|
||||||
|
fh.Close()
|
||||||
|
|
||||||
|
fh, err := ufs.OpenFile("/home/test/file.txt", os.O_RDWR, 0666)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Failed to open file r/w: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = fh.Write([]byte("####"))
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Failed to write file: %s", err)
|
||||||
|
}
|
||||||
|
fh.Seek(0, 0)
|
||||||
|
data, err := ioutil.ReadAll(fh)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Failed to read file: %s", err)
|
||||||
|
}
|
||||||
|
if string(data) != "#### is a test" {
|
||||||
|
t.Errorf("Got wrong data")
|
||||||
|
}
|
||||||
|
fh.Close()
|
||||||
|
|
||||||
|
fh, _ = base.Open("/home/test/file.txt")
|
||||||
|
data, err = ioutil.ReadAll(fh)
|
||||||
|
if string(data) != "This is a test" {
|
||||||
|
t.Errorf("Got wrong data in base file")
|
||||||
|
}
|
||||||
|
fh.Close()
|
||||||
|
|
||||||
|
fh, err = ufs.Create("/home/test/file.txt")
|
||||||
|
switch err {
|
||||||
|
case nil:
|
||||||
|
if fi, _ := fh.Stat(); fi.Size() != 0 {
|
||||||
|
t.Errorf("Create did not truncate file")
|
||||||
|
}
|
||||||
|
fh.Close()
|
||||||
|
default:
|
||||||
|
t.Errorf("Create failed on existing file")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnionMergeReaddir(t *testing.T) {
|
||||||
|
base := &MemMapFs{}
|
||||||
|
roBase := &ReadOnlyFs{source: base}
|
||||||
|
|
||||||
|
ufs := &CopyOnWriteFs{base: roBase, layer: &MemMapFs{}}
|
||||||
|
|
||||||
|
base.MkdirAll("/home/test", 0777)
|
||||||
|
fh, _ := base.Create("/home/test/file.txt")
|
||||||
|
fh.WriteString("This is a test")
|
||||||
|
fh.Close()
|
||||||
|
|
||||||
|
fh, _ = ufs.Create("/home/test/file2.txt")
|
||||||
|
fh.WriteString("This is a test")
|
||||||
|
fh.Close()
|
||||||
|
|
||||||
|
fh, _ = ufs.Open("/home/test")
|
||||||
|
files, err := fh.Readdirnames(-1)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Readdirnames failed")
|
||||||
|
}
|
||||||
|
if len(files) != 2 {
|
||||||
|
t.Errorf("Got wrong number of files: %v", files)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExistingDirectoryCollisionReaddir(t *testing.T) {
|
||||||
|
base := &MemMapFs{}
|
||||||
|
roBase := &ReadOnlyFs{source: base}
|
||||||
|
overlay := &MemMapFs{}
|
||||||
|
|
||||||
|
ufs := &CopyOnWriteFs{base: roBase, layer: overlay}
|
||||||
|
|
||||||
|
base.MkdirAll("/home/test", 0777)
|
||||||
|
fh, _ := base.Create("/home/test/file.txt")
|
||||||
|
fh.WriteString("This is a test")
|
||||||
|
fh.Close()
|
||||||
|
|
||||||
|
overlay.MkdirAll("home/test", 0777)
|
||||||
|
fh, _ = overlay.Create("/home/test/file2.txt")
|
||||||
|
fh.WriteString("This is a test")
|
||||||
|
fh.Close()
|
||||||
|
|
||||||
|
fh, _ = ufs.Create("/home/test/file3.txt")
|
||||||
|
fh.WriteString("This is a test")
|
||||||
|
fh.Close()
|
||||||
|
|
||||||
|
fh, _ = ufs.Open("/home/test")
|
||||||
|
files, err := fh.Readdirnames(-1)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Readdirnames failed")
|
||||||
|
}
|
||||||
|
if len(files) != 3 {
|
||||||
|
t.Errorf("Got wrong number of files in union: %v", files)
|
||||||
|
}
|
||||||
|
|
||||||
|
fh, _ = overlay.Open("/home/test")
|
||||||
|
files, err = fh.Readdirnames(-1)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Readdirnames failed")
|
||||||
|
}
|
||||||
|
if len(files) != 2 {
|
||||||
|
t.Errorf("Got wrong number of files in overlay: %v", files)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNestedDirBaseReaddir(t *testing.T) {
|
||||||
|
base := &MemMapFs{}
|
||||||
|
roBase := &ReadOnlyFs{source: base}
|
||||||
|
overlay := &MemMapFs{}
|
||||||
|
|
||||||
|
ufs := &CopyOnWriteFs{base: roBase, layer: overlay}
|
||||||
|
|
||||||
|
base.MkdirAll("/home/test/foo/bar", 0777)
|
||||||
|
fh, _ := base.Create("/home/test/file.txt")
|
||||||
|
fh.WriteString("This is a test")
|
||||||
|
fh.Close()
|
||||||
|
|
||||||
|
fh, _ = base.Create("/home/test/foo/file2.txt")
|
||||||
|
fh.WriteString("This is a test")
|
||||||
|
fh.Close()
|
||||||
|
fh, _ = base.Create("/home/test/foo/bar/file3.txt")
|
||||||
|
fh.WriteString("This is a test")
|
||||||
|
fh.Close()
|
||||||
|
|
||||||
|
overlay.MkdirAll("/", 0777)
|
||||||
|
|
||||||
|
// Opening something only in the base
|
||||||
|
fh, _ = ufs.Open("/home/test/foo")
|
||||||
|
list, err := fh.Readdir(-1)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Readdir failed %s", err)
|
||||||
|
}
|
||||||
|
if len(list) != 2 {
|
||||||
|
for _, x := range list {
|
||||||
|
fmt.Println(x.Name())
|
||||||
|
}
|
||||||
|
t.Errorf("Got wrong number of files in union: %v", len(list))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNestedDirOverlayReaddir(t *testing.T) {
|
||||||
|
base := &MemMapFs{}
|
||||||
|
roBase := &ReadOnlyFs{source: base}
|
||||||
|
overlay := &MemMapFs{}
|
||||||
|
|
||||||
|
ufs := &CopyOnWriteFs{base: roBase, layer: overlay}
|
||||||
|
|
||||||
|
base.MkdirAll("/", 0777)
|
||||||
|
overlay.MkdirAll("/home/test/foo/bar", 0777)
|
||||||
|
fh, _ := overlay.Create("/home/test/file.txt")
|
||||||
|
fh.WriteString("This is a test")
|
||||||
|
fh.Close()
|
||||||
|
fh, _ = overlay.Create("/home/test/foo/file2.txt")
|
||||||
|
fh.WriteString("This is a test")
|
||||||
|
fh.Close()
|
||||||
|
fh, _ = overlay.Create("/home/test/foo/bar/file3.txt")
|
||||||
|
fh.WriteString("This is a test")
|
||||||
|
fh.Close()
|
||||||
|
|
||||||
|
// Opening nested dir only in the overlay
|
||||||
|
fh, _ = ufs.Open("/home/test/foo")
|
||||||
|
list, err := fh.Readdir(-1)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Readdir failed %s", err)
|
||||||
|
}
|
||||||
|
if len(list) != 2 {
|
||||||
|
for _, x := range list {
|
||||||
|
fmt.Println(x.Name())
|
||||||
|
}
|
||||||
|
t.Errorf("Got wrong number of files in union: %v", len(list))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNestedDirOverlayOsFsReaddir(t *testing.T) {
|
||||||
|
defer CleanupTempDirs(t)
|
||||||
|
base := NewTempOsBaseFs(t)
|
||||||
|
roBase := &ReadOnlyFs{source: base}
|
||||||
|
overlay := NewTempOsBaseFs(t)
|
||||||
|
|
||||||
|
ufs := &CopyOnWriteFs{base: roBase, layer: overlay}
|
||||||
|
|
||||||
|
base.MkdirAll("/", 0777)
|
||||||
|
overlay.MkdirAll("/home/test/foo/bar", 0777)
|
||||||
|
fh, _ := overlay.Create("/home/test/file.txt")
|
||||||
|
fh.WriteString("This is a test")
|
||||||
|
fh.Close()
|
||||||
|
fh, _ = overlay.Create("/home/test/foo/file2.txt")
|
||||||
|
fh.WriteString("This is a test")
|
||||||
|
fh.Close()
|
||||||
|
fh, _ = overlay.Create("/home/test/foo/bar/file3.txt")
|
||||||
|
fh.WriteString("This is a test")
|
||||||
|
fh.Close()
|
||||||
|
|
||||||
|
// Opening nested dir only in the overlay
|
||||||
|
fh, _ = ufs.Open("/home/test/foo")
|
||||||
|
list, err := fh.Readdir(-1)
|
||||||
|
fh.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Readdir failed %s", err)
|
||||||
|
}
|
||||||
|
if len(list) != 2 {
|
||||||
|
for _, x := range list {
|
||||||
|
fmt.Println(x.Name())
|
||||||
|
}
|
||||||
|
t.Errorf("Got wrong number of files in union: %v", len(list))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCopyOnWriteFsWithOsFs(t *testing.T) {
|
||||||
|
defer CleanupTempDirs(t)
|
||||||
|
base := NewTempOsBaseFs(t)
|
||||||
|
roBase := &ReadOnlyFs{source: base}
|
||||||
|
overlay := NewTempOsBaseFs(t)
|
||||||
|
|
||||||
|
ufs := &CopyOnWriteFs{base: roBase, layer: overlay}
|
||||||
|
|
||||||
|
base.MkdirAll("/home/test", 0777)
|
||||||
|
fh, _ := base.Create("/home/test/file.txt")
|
||||||
|
fh.WriteString("This is a test")
|
||||||
|
fh.Close()
|
||||||
|
|
||||||
|
overlay.MkdirAll("home/test", 0777)
|
||||||
|
fh, _ = overlay.Create("/home/test/file2.txt")
|
||||||
|
fh.WriteString("This is a test")
|
||||||
|
fh.Close()
|
||||||
|
|
||||||
|
fh, _ = ufs.Create("/home/test/file3.txt")
|
||||||
|
fh.WriteString("This is a test")
|
||||||
|
fh.Close()
|
||||||
|
|
||||||
|
fh, _ = ufs.Open("/home/test")
|
||||||
|
files, err := fh.Readdirnames(-1)
|
||||||
|
fh.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Readdirnames failed")
|
||||||
|
}
|
||||||
|
if len(files) != 3 {
|
||||||
|
t.Errorf("Got wrong number of files in union: %v", files)
|
||||||
|
}
|
||||||
|
|
||||||
|
fh, _ = overlay.Open("/home/test")
|
||||||
|
files, err = fh.Readdirnames(-1)
|
||||||
|
fh.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Readdirnames failed")
|
||||||
|
}
|
||||||
|
if len(files) != 2 {
|
||||||
|
t.Errorf("Got wrong number of files in overlay: %v", files)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnionCacheWrite(t *testing.T) {
|
||||||
|
base := &MemMapFs{}
|
||||||
|
layer := &MemMapFs{}
|
||||||
|
|
||||||
|
ufs := NewCacheOnReadFs(base, layer, 0)
|
||||||
|
|
||||||
|
base.Mkdir("/data", 0777)
|
||||||
|
|
||||||
|
fh, err := ufs.Create("/data/file.txt")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Failed to create file")
|
||||||
|
}
|
||||||
|
_, err = fh.Write([]byte("This is a test"))
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Failed to write file")
|
||||||
|
}
|
||||||
|
|
||||||
|
fh.Seek(0, os.SEEK_SET)
|
||||||
|
buf := make([]byte, 4)
|
||||||
|
_, err = fh.Read(buf)
|
||||||
|
fh.Write([]byte(" IS A"))
|
||||||
|
fh.Close()
|
||||||
|
|
||||||
|
baseData, _ := ReadFile(base, "/data/file.txt")
|
||||||
|
layerData, _ := ReadFile(layer, "/data/file.txt")
|
||||||
|
if string(baseData) != string(layerData) {
|
||||||
|
t.Errorf("Different data: %s <=> %s", baseData, layerData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnionCacheExpire(t *testing.T) {
|
||||||
|
base := &MemMapFs{}
|
||||||
|
layer := &MemMapFs{}
|
||||||
|
ufs := &CacheOnReadFs{base: base, layer: layer, cacheTime: 1 * time.Second}
|
||||||
|
|
||||||
|
base.Mkdir("/data", 0777)
|
||||||
|
|
||||||
|
fh, err := ufs.Create("/data/file.txt")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Failed to create file")
|
||||||
|
}
|
||||||
|
_, err = fh.Write([]byte("This is a test"))
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Failed to write file")
|
||||||
|
}
|
||||||
|
fh.Close()
|
||||||
|
|
||||||
|
fh, _ = base.Create("/data/file.txt")
|
||||||
|
// sleep some time, so we really get a different time.Now() on write...
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
fh.WriteString("Another test")
|
||||||
|
fh.Close()
|
||||||
|
|
||||||
|
data, _ := ReadFile(ufs, "/data/file.txt")
|
||||||
|
if string(data) != "Another test" {
|
||||||
|
t.Errorf("cache time failed: <%s>", data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCacheOnReadFsNotInLayer(t *testing.T) {
|
||||||
|
base := NewMemMapFs()
|
||||||
|
layer := NewMemMapFs()
|
||||||
|
fs := NewCacheOnReadFs(base, layer, 0)
|
||||||
|
|
||||||
|
fh, err := base.Create("/file.txt")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("unable to create file: ", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
txt := []byte("This is a test")
|
||||||
|
fh.Write(txt)
|
||||||
|
fh.Close()
|
||||||
|
|
||||||
|
fh, err = fs.Open("/file.txt")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("could not open file: ", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := ReadAll(fh)
|
||||||
|
fh.Close()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("could not read file: ", err)
|
||||||
|
} else if !bytes.Equal(txt, b) {
|
||||||
|
t.Fatalf("wanted file text %q, got %q", txt, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
fh, err = layer.Open("/file.txt")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("could not open file from layer: ", err)
|
||||||
|
}
|
||||||
|
fh.Close()
|
||||||
|
}
|
22
vendor/github.com/spf13/afero/const_bsds.go
generated
vendored
Normal file
22
vendor/github.com/spf13/afero/const_bsds.go
generated
vendored
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
// Copyright © 2016 Steve Francia <spf@spf13.com>.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// +build darwin openbsd freebsd netbsd dragonfly
|
||||||
|
|
||||||
|
package afero
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
const BADFD = syscall.EBADF
|
25
vendor/github.com/spf13/afero/const_win_unix.go
generated
vendored
Normal file
25
vendor/github.com/spf13/afero/const_win_unix.go
generated
vendored
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
// Copyright © 2016 Steve Francia <spf@spf13.com>.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
// +build !darwin
|
||||||
|
// +build !openbsd
|
||||||
|
// +build !freebsd
|
||||||
|
// +build !dragonfly
|
||||||
|
// +build !netbsd
|
||||||
|
|
||||||
|
package afero
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
const BADFD = syscall.EBADFD
|
253
vendor/github.com/spf13/afero/copyOnWriteFs.go
generated
vendored
Normal file
253
vendor/github.com/spf13/afero/copyOnWriteFs.go
generated
vendored
Normal file
|
@ -0,0 +1,253 @@
|
||||||
|
package afero
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The CopyOnWriteFs is a union filesystem: a read only base file system with
|
||||||
|
// a possibly writeable layer on top. Changes to the file system will only
|
||||||
|
// be made in the overlay: Changing an existing file in the base layer which
|
||||||
|
// is not present in the overlay will copy the file to the overlay ("changing"
|
||||||
|
// includes also calls to e.g. Chtimes() and Chmod()).
|
||||||
|
//
|
||||||
|
// Reading directories is currently only supported via Open(), not OpenFile().
|
||||||
|
type CopyOnWriteFs struct {
|
||||||
|
base Fs
|
||||||
|
layer Fs
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCopyOnWriteFs(base Fs, layer Fs) Fs {
|
||||||
|
return &CopyOnWriteFs{base: base, layer: layer}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if the file is not in the overlay
|
||||||
|
func (u *CopyOnWriteFs) isBaseFile(name string) (bool, error) {
|
||||||
|
if _, err := u.layer.Stat(name); err == nil {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
_, err := u.base.Stat(name)
|
||||||
|
if err != nil {
|
||||||
|
if oerr, ok := err.(*os.PathError); ok {
|
||||||
|
if oerr.Err == os.ErrNotExist || oerr.Err == syscall.ENOENT || oerr.Err == syscall.ENOTDIR {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err == syscall.ENOENT {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *CopyOnWriteFs) copyToLayer(name string) error {
|
||||||
|
return copyToLayer(u.base, u.layer, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *CopyOnWriteFs) Chtimes(name string, atime, mtime time.Time) error {
|
||||||
|
b, err := u.isBaseFile(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if b {
|
||||||
|
if err := u.copyToLayer(name); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return u.layer.Chtimes(name, atime, mtime)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *CopyOnWriteFs) Chmod(name string, mode os.FileMode) error {
|
||||||
|
b, err := u.isBaseFile(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if b {
|
||||||
|
if err := u.copyToLayer(name); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return u.layer.Chmod(name, mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *CopyOnWriteFs) Stat(name string) (os.FileInfo, error) {
|
||||||
|
fi, err := u.layer.Stat(name)
|
||||||
|
if err != nil {
|
||||||
|
origErr := err
|
||||||
|
if e, ok := err.(*os.PathError); ok {
|
||||||
|
err = e.Err
|
||||||
|
}
|
||||||
|
if err == syscall.ENOENT || err == syscall.ENOTDIR {
|
||||||
|
return u.base.Stat(name)
|
||||||
|
}
|
||||||
|
return nil, origErr
|
||||||
|
}
|
||||||
|
return fi, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Renaming files present only in the base layer is not permitted
|
||||||
|
func (u *CopyOnWriteFs) Rename(oldname, newname string) error {
|
||||||
|
b, err := u.isBaseFile(oldname)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if b {
|
||||||
|
return syscall.EPERM
|
||||||
|
}
|
||||||
|
return u.layer.Rename(oldname, newname)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Removing files present only in the base layer is not permitted. If
|
||||||
|
// a file is present in the base layer and the overlay, only the overlay
|
||||||
|
// will be removed.
|
||||||
|
func (u *CopyOnWriteFs) Remove(name string) error {
|
||||||
|
err := u.layer.Remove(name)
|
||||||
|
switch err {
|
||||||
|
case syscall.ENOENT:
|
||||||
|
_, err = u.base.Stat(name)
|
||||||
|
if err == nil {
|
||||||
|
return syscall.EPERM
|
||||||
|
}
|
||||||
|
return syscall.ENOENT
|
||||||
|
default:
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *CopyOnWriteFs) RemoveAll(name string) error {
|
||||||
|
err := u.layer.RemoveAll(name)
|
||||||
|
switch err {
|
||||||
|
case syscall.ENOENT:
|
||||||
|
_, err = u.base.Stat(name)
|
||||||
|
if err == nil {
|
||||||
|
return syscall.EPERM
|
||||||
|
}
|
||||||
|
return syscall.ENOENT
|
||||||
|
default:
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *CopyOnWriteFs) OpenFile(name string, flag int, perm os.FileMode) (File, error) {
|
||||||
|
b, err := u.isBaseFile(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if flag&(os.O_WRONLY|os.O_RDWR|os.O_APPEND|os.O_CREATE|os.O_TRUNC) != 0 {
|
||||||
|
if b {
|
||||||
|
if err = u.copyToLayer(name); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return u.layer.OpenFile(name, flag, perm)
|
||||||
|
}
|
||||||
|
|
||||||
|
dir := filepath.Dir(name)
|
||||||
|
isaDir, err := IsDir(u.base, dir)
|
||||||
|
if err != nil && !os.IsNotExist(err) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if isaDir {
|
||||||
|
if err = u.layer.MkdirAll(dir, 0777); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return u.layer.OpenFile(name, flag, perm)
|
||||||
|
}
|
||||||
|
|
||||||
|
isaDir, err = IsDir(u.layer, dir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if isaDir {
|
||||||
|
return u.layer.OpenFile(name, flag, perm)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, &os.PathError{Op: "open", Path: name, Err: syscall.ENOTDIR} // ...or os.ErrNotExist?
|
||||||
|
}
|
||||||
|
if b {
|
||||||
|
return u.base.OpenFile(name, flag, perm)
|
||||||
|
}
|
||||||
|
return u.layer.OpenFile(name, flag, perm)
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function handles the 9 different possibilities caused
|
||||||
|
// by the union which are the intersection of the following...
|
||||||
|
// layer: doesn't exist, exists as a file, and exists as a directory
|
||||||
|
// base: doesn't exist, exists as a file, and exists as a directory
|
||||||
|
func (u *CopyOnWriteFs) Open(name string) (File, error) {
|
||||||
|
// Since the overlay overrides the base we check that first
|
||||||
|
b, err := u.isBaseFile(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If overlay doesn't exist, return the base (base state irrelevant)
|
||||||
|
if b {
|
||||||
|
return u.base.Open(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If overlay is a file, return it (base state irrelevant)
|
||||||
|
dir, err := IsDir(u.layer, name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !dir {
|
||||||
|
return u.layer.Open(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overlay is a directory, base state now matters.
|
||||||
|
// Base state has 3 states to check but 2 outcomes:
|
||||||
|
// A. It's a file or non-readable in the base (return just the overlay)
|
||||||
|
// B. It's an accessible directory in the base (return a UnionFile)
|
||||||
|
|
||||||
|
// If base is file or nonreadable, return overlay
|
||||||
|
dir, err = IsDir(u.base, name)
|
||||||
|
if !dir || err != nil {
|
||||||
|
return u.layer.Open(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Both base & layer are directories
|
||||||
|
// Return union file (if opens are without error)
|
||||||
|
bfile, bErr := u.base.Open(name)
|
||||||
|
lfile, lErr := u.layer.Open(name)
|
||||||
|
|
||||||
|
// If either have errors at this point something is very wrong. Return nil and the errors
|
||||||
|
if bErr != nil || lErr != nil {
|
||||||
|
return nil, fmt.Errorf("BaseErr: %v\nOverlayErr: %v", bErr, lErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &UnionFile{base: bfile, layer: lfile}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *CopyOnWriteFs) Mkdir(name string, perm os.FileMode) error {
|
||||||
|
dir, err := IsDir(u.base, name)
|
||||||
|
if err != nil {
|
||||||
|
return u.layer.MkdirAll(name, perm)
|
||||||
|
}
|
||||||
|
if dir {
|
||||||
|
return syscall.EEXIST
|
||||||
|
}
|
||||||
|
return u.layer.MkdirAll(name, perm)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *CopyOnWriteFs) Name() string {
|
||||||
|
return "CopyOnWriteFs"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *CopyOnWriteFs) MkdirAll(name string, perm os.FileMode) error {
|
||||||
|
dir, err := IsDir(u.base, name)
|
||||||
|
if err != nil {
|
||||||
|
return u.layer.MkdirAll(name, perm)
|
||||||
|
}
|
||||||
|
if dir {
|
||||||
|
return syscall.EEXIST
|
||||||
|
}
|
||||||
|
return u.layer.MkdirAll(name, perm)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *CopyOnWriteFs) Create(name string) (File, error) {
|
||||||
|
return u.OpenFile(name, os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0666)
|
||||||
|
}
|
23
vendor/github.com/spf13/afero/copyOnWriteFs_test.go
generated
vendored
Normal file
23
vendor/github.com/spf13/afero/copyOnWriteFs_test.go
generated
vendored
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
package afero
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestCopyOnWrite(t *testing.T) {
|
||||||
|
var fs Fs
|
||||||
|
var err error
|
||||||
|
base := NewOsFs()
|
||||||
|
roBase := NewReadOnlyFs(base)
|
||||||
|
ufs := NewCopyOnWriteFs(roBase, NewMemMapFs())
|
||||||
|
fs = ufs
|
||||||
|
err = fs.MkdirAll("nonexistent/directory/", 0744)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err = fs.Create("nonexistent/directory/newfile")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
110
vendor/github.com/spf13/afero/httpFs.go
generated
vendored
Normal file
110
vendor/github.com/spf13/afero/httpFs.go
generated
vendored
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
// Copyright © 2014 Steve Francia <spf@spf13.com>.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package afero
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type httpDir struct {
|
||||||
|
basePath string
|
||||||
|
fs HttpFs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d httpDir) Open(name string) (http.File, error) {
|
||||||
|
if filepath.Separator != '/' && strings.IndexRune(name, filepath.Separator) >= 0 ||
|
||||||
|
strings.Contains(name, "\x00") {
|
||||||
|
return nil, errors.New("http: invalid character in file path")
|
||||||
|
}
|
||||||
|
dir := string(d.basePath)
|
||||||
|
if dir == "" {
|
||||||
|
dir = "."
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := d.fs.Open(filepath.Join(dir, filepath.FromSlash(path.Clean("/"+name))))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return f, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type HttpFs struct {
|
||||||
|
source Fs
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHttpFs(source Fs) *HttpFs {
|
||||||
|
return &HttpFs{source: source}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h HttpFs) Dir(s string) *httpDir {
|
||||||
|
return &httpDir{basePath: s, fs: h}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h HttpFs) Name() string { return "h HttpFs" }
|
||||||
|
|
||||||
|
func (h HttpFs) Create(name string) (File, error) {
|
||||||
|
return h.source.Create(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h HttpFs) Chmod(name string, mode os.FileMode) error {
|
||||||
|
return h.source.Chmod(name, mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h HttpFs) Chtimes(name string, atime time.Time, mtime time.Time) error {
|
||||||
|
return h.source.Chtimes(name, atime, mtime)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h HttpFs) Mkdir(name string, perm os.FileMode) error {
|
||||||
|
return h.source.Mkdir(name, perm)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h HttpFs) MkdirAll(path string, perm os.FileMode) error {
|
||||||
|
return h.source.MkdirAll(path, perm)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h HttpFs) Open(name string) (http.File, error) {
|
||||||
|
f, err := h.source.Open(name)
|
||||||
|
if err == nil {
|
||||||
|
if httpfile, ok := f.(http.File); ok {
|
||||||
|
return httpfile, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h HttpFs) OpenFile(name string, flag int, perm os.FileMode) (File, error) {
|
||||||
|
return h.source.OpenFile(name, flag, perm)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h HttpFs) Remove(name string) error {
|
||||||
|
return h.source.Remove(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h HttpFs) RemoveAll(path string) error {
|
||||||
|
return h.source.RemoveAll(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h HttpFs) Rename(oldname, newname string) error {
|
||||||
|
return h.source.Rename(oldname, newname)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h HttpFs) Stat(name string) (os.FileInfo, error) {
|
||||||
|
return h.source.Stat(name)
|
||||||
|
}
|
230
vendor/github.com/spf13/afero/ioutil.go
generated
vendored
Normal file
230
vendor/github.com/spf13/afero/ioutil.go
generated
vendored
Normal file
|
@ -0,0 +1,230 @@
|
||||||
|
// Copyright ©2015 The Go Authors
|
||||||
|
// Copyright ©2015 Steve Francia <spf@spf13.com>
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package afero
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// byName implements sort.Interface.
|
||||||
|
type byName []os.FileInfo
|
||||||
|
|
||||||
|
func (f byName) Len() int { return len(f) }
|
||||||
|
func (f byName) Less(i, j int) bool { return f[i].Name() < f[j].Name() }
|
||||||
|
func (f byName) Swap(i, j int) { f[i], f[j] = f[j], f[i] }
|
||||||
|
|
||||||
|
// ReadDir reads the directory named by dirname and returns
|
||||||
|
// a list of sorted directory entries.
|
||||||
|
func (a Afero) ReadDir(dirname string) ([]os.FileInfo, error) {
|
||||||
|
return ReadDir(a.Fs, dirname)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadDir(fs Fs, dirname string) ([]os.FileInfo, error) {
|
||||||
|
f, err := fs.Open(dirname)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
list, err := f.Readdir(-1)
|
||||||
|
f.Close()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sort.Sort(byName(list))
|
||||||
|
return list, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadFile reads the file named by filename and returns the contents.
|
||||||
|
// A successful call returns err == nil, not err == EOF. Because ReadFile
|
||||||
|
// reads the whole file, it does not treat an EOF from Read as an error
|
||||||
|
// to be reported.
|
||||||
|
func (a Afero) ReadFile(filename string) ([]byte, error) {
|
||||||
|
return ReadFile(a.Fs, filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadFile(fs Fs, filename string) ([]byte, error) {
|
||||||
|
f, err := fs.Open(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
// It's a good but not certain bet that FileInfo will tell us exactly how much to
|
||||||
|
// read, so let's try it but be prepared for the answer to be wrong.
|
||||||
|
var n int64
|
||||||
|
|
||||||
|
if fi, err := f.Stat(); err == nil {
|
||||||
|
// Don't preallocate a huge buffer, just in case.
|
||||||
|
if size := fi.Size(); size < 1e9 {
|
||||||
|
n = size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// As initial capacity for readAll, use n + a little extra in case Size is zero,
|
||||||
|
// and to avoid another allocation after Read has filled the buffer. The readAll
|
||||||
|
// call will read into its allocated internal buffer cheaply. If the size was
|
||||||
|
// wrong, we'll either waste some space off the end or reallocate as needed, but
|
||||||
|
// in the overwhelmingly common case we'll get it just right.
|
||||||
|
return readAll(f, n+bytes.MinRead)
|
||||||
|
}
|
||||||
|
|
||||||
|
// readAll reads from r until an error or EOF and returns the data it read
|
||||||
|
// from the internal buffer allocated with a specified capacity.
|
||||||
|
func readAll(r io.Reader, capacity int64) (b []byte, err error) {
|
||||||
|
buf := bytes.NewBuffer(make([]byte, 0, capacity))
|
||||||
|
// If the buffer overflows, we will get bytes.ErrTooLarge.
|
||||||
|
// Return that as an error. Any other panic remains.
|
||||||
|
defer func() {
|
||||||
|
e := recover()
|
||||||
|
if e == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if panicErr, ok := e.(error); ok && panicErr == bytes.ErrTooLarge {
|
||||||
|
err = panicErr
|
||||||
|
} else {
|
||||||
|
panic(e)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
_, err = buf.ReadFrom(r)
|
||||||
|
return buf.Bytes(), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadAll reads from r until an error or EOF and returns the data it read.
|
||||||
|
// A successful call returns err == nil, not err == EOF. Because ReadAll is
|
||||||
|
// defined to read from src until EOF, it does not treat an EOF from Read
|
||||||
|
// as an error to be reported.
|
||||||
|
func ReadAll(r io.Reader) ([]byte, error) {
|
||||||
|
return readAll(r, bytes.MinRead)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteFile writes data to a file named by filename.
|
||||||
|
// If the file does not exist, WriteFile creates it with permissions perm;
|
||||||
|
// otherwise WriteFile truncates it before writing.
|
||||||
|
func (a Afero) WriteFile(filename string, data []byte, perm os.FileMode) error {
|
||||||
|
return WriteFile(a.Fs, filename, data, perm)
|
||||||
|
}
|
||||||
|
|
||||||
|
func WriteFile(fs Fs, filename string, data []byte, perm os.FileMode) error {
|
||||||
|
f, err := fs.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
n, err := f.Write(data)
|
||||||
|
if err == nil && n < len(data) {
|
||||||
|
err = io.ErrShortWrite
|
||||||
|
}
|
||||||
|
if err1 := f.Close(); err == nil {
|
||||||
|
err = err1
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Random number state.
|
||||||
|
// We generate random temporary file names so that there's a good
|
||||||
|
// chance the file doesn't exist yet - keeps the number of tries in
|
||||||
|
// TempFile to a minimum.
|
||||||
|
var rand uint32
|
||||||
|
var randmu sync.Mutex
|
||||||
|
|
||||||
|
func reseed() uint32 {
|
||||||
|
return uint32(time.Now().UnixNano() + int64(os.Getpid()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func nextSuffix() string {
|
||||||
|
randmu.Lock()
|
||||||
|
r := rand
|
||||||
|
if r == 0 {
|
||||||
|
r = reseed()
|
||||||
|
}
|
||||||
|
r = r*1664525 + 1013904223 // constants from Numerical Recipes
|
||||||
|
rand = r
|
||||||
|
randmu.Unlock()
|
||||||
|
return strconv.Itoa(int(1e9 + r%1e9))[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// TempFile creates a new temporary file in the directory dir
|
||||||
|
// with a name beginning with prefix, opens the file for reading
|
||||||
|
// and writing, and returns the resulting *File.
|
||||||
|
// If dir is the empty string, TempFile uses the default directory
|
||||||
|
// for temporary files (see os.TempDir).
|
||||||
|
// Multiple programs calling TempFile simultaneously
|
||||||
|
// will not choose the same file. The caller can use f.Name()
|
||||||
|
// to find the pathname of the file. It is the caller's responsibility
|
||||||
|
// to remove the file when no longer needed.
|
||||||
|
func (a Afero) TempFile(dir, prefix string) (f File, err error) {
|
||||||
|
return TempFile(a.Fs, dir, prefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TempFile(fs Fs, dir, prefix string) (f File, err error) {
|
||||||
|
if dir == "" {
|
||||||
|
dir = os.TempDir()
|
||||||
|
}
|
||||||
|
|
||||||
|
nconflict := 0
|
||||||
|
for i := 0; i < 10000; i++ {
|
||||||
|
name := filepath.Join(dir, prefix+nextSuffix())
|
||||||
|
f, err = fs.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600)
|
||||||
|
if os.IsExist(err) {
|
||||||
|
if nconflict++; nconflict > 10 {
|
||||||
|
randmu.Lock()
|
||||||
|
rand = reseed()
|
||||||
|
randmu.Unlock()
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TempDir creates a new temporary directory in the directory dir
|
||||||
|
// with a name beginning with prefix and returns the path of the
|
||||||
|
// new directory. If dir is the empty string, TempDir uses the
|
||||||
|
// default directory for temporary files (see os.TempDir).
|
||||||
|
// Multiple programs calling TempDir simultaneously
|
||||||
|
// will not choose the same directory. It is the caller's responsibility
|
||||||
|
// to remove the directory when no longer needed.
|
||||||
|
func (a Afero) TempDir(dir, prefix string) (name string, err error) {
|
||||||
|
return TempDir(a.Fs, dir, prefix)
|
||||||
|
}
|
||||||
|
func TempDir(fs Fs, dir, prefix string) (name string, err error) {
|
||||||
|
if dir == "" {
|
||||||
|
dir = os.TempDir()
|
||||||
|
}
|
||||||
|
|
||||||
|
nconflict := 0
|
||||||
|
for i := 0; i < 10000; i++ {
|
||||||
|
try := filepath.Join(dir, prefix+nextSuffix())
|
||||||
|
err = fs.Mkdir(try, 0700)
|
||||||
|
if os.IsExist(err) {
|
||||||
|
if nconflict++; nconflict > 10 {
|
||||||
|
randmu.Lock()
|
||||||
|
rand = reseed()
|
||||||
|
randmu.Unlock()
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
name = try
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
112
vendor/github.com/spf13/afero/ioutil_test.go
generated
vendored
Normal file
112
vendor/github.com/spf13/afero/ioutil_test.go
generated
vendored
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
// ©2015 The Go Authors
|
||||||
|
// Copyright ©2015 Steve Francia <spf@spf13.com>
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package afero
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func checkSizePath(t *testing.T, path string, size int64) {
|
||||||
|
dir, err := testFS.Stat(path)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Stat %q (looking for size %d): %s", path, size, err)
|
||||||
|
}
|
||||||
|
if dir.Size() != size {
|
||||||
|
t.Errorf("Stat %q: size %d want %d", path, dir.Size(), size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReadFile(t *testing.T) {
|
||||||
|
testFS = &MemMapFs{}
|
||||||
|
fsutil := &Afero{Fs: testFS}
|
||||||
|
|
||||||
|
testFS.Create("this_exists.go")
|
||||||
|
filename := "rumpelstilzchen"
|
||||||
|
contents, err := fsutil.ReadFile(filename)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("ReadFile %s: error expected, none found", filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
filename = "this_exists.go"
|
||||||
|
contents, err = fsutil.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ReadFile %s: %v", filename, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
checkSizePath(t, filename, int64(len(contents)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWriteFile(t *testing.T) {
|
||||||
|
testFS = &MemMapFs{}
|
||||||
|
fsutil := &Afero{Fs: testFS}
|
||||||
|
f, err := fsutil.TempFile("", "ioutil-test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
filename := f.Name()
|
||||||
|
data := "Programming today is a race between software engineers striving to " +
|
||||||
|
"build bigger and better idiot-proof programs, and the Universe trying " +
|
||||||
|
"to produce bigger and better idiots. So far, the Universe is winning."
|
||||||
|
|
||||||
|
if err := fsutil.WriteFile(filename, []byte(data), 0644); err != nil {
|
||||||
|
t.Fatalf("WriteFile %s: %v", filename, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
contents, err := fsutil.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ReadFile %s: %v", filename, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if string(contents) != data {
|
||||||
|
t.Fatalf("contents = %q\nexpected = %q", string(contents), data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// cleanup
|
||||||
|
f.Close()
|
||||||
|
testFS.Remove(filename) // ignore error
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReadDir(t *testing.T) {
|
||||||
|
testFS = &MemMapFs{}
|
||||||
|
testFS.Mkdir("/i-am-a-dir", 0777)
|
||||||
|
testFS.Create("/this_exists.go")
|
||||||
|
dirname := "rumpelstilzchen"
|
||||||
|
_, err := ReadDir(testFS, dirname)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("ReadDir %s: error expected, none found", dirname)
|
||||||
|
}
|
||||||
|
|
||||||
|
dirname = ".."
|
||||||
|
list, err := ReadDir(testFS, dirname)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ReadDir %s: %v", dirname, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
foundFile := false
|
||||||
|
foundSubDir := false
|
||||||
|
for _, dir := range list {
|
||||||
|
switch {
|
||||||
|
case !dir.IsDir() && dir.Name() == "this_exists.go":
|
||||||
|
foundFile = true
|
||||||
|
case dir.IsDir() && dir.Name() == "i-am-a-dir":
|
||||||
|
foundSubDir = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !foundFile {
|
||||||
|
t.Fatalf("ReadDir %s: this_exists.go file not found", dirname)
|
||||||
|
}
|
||||||
|
if !foundSubDir {
|
||||||
|
t.Fatalf("ReadDir %s: i-am-a-dir directory not found", dirname)
|
||||||
|
}
|
||||||
|
}
|
110
vendor/github.com/spf13/afero/match.go
generated
vendored
Normal file
110
vendor/github.com/spf13/afero/match.go
generated
vendored
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
// Copyright © 2014 Steve Francia <spf@spf13.com>.
|
||||||
|
// Copyright 2009 The Go Authors. All rights reserved.
|
||||||
|
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package afero
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Glob returns the names of all files matching pattern or nil
|
||||||
|
// if there is no matching file. The syntax of patterns is the same
|
||||||
|
// as in Match. The pattern may describe hierarchical names such as
|
||||||
|
// /usr/*/bin/ed (assuming the Separator is '/').
|
||||||
|
//
|
||||||
|
// Glob ignores file system errors such as I/O errors reading directories.
|
||||||
|
// The only possible returned error is ErrBadPattern, when pattern
|
||||||
|
// is malformed.
|
||||||
|
//
|
||||||
|
// This was adapted from (http://golang.org/pkg/path/filepath) and uses several
|
||||||
|
// built-ins from that package.
|
||||||
|
func Glob(fs Fs, pattern string) (matches []string, err error) {
|
||||||
|
if !hasMeta(pattern) {
|
||||||
|
// afero does not support Lstat directly.
|
||||||
|
if _, err = lstatIfOs(fs, pattern); err != nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return []string{pattern}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
dir, file := filepath.Split(pattern)
|
||||||
|
switch dir {
|
||||||
|
case "":
|
||||||
|
dir = "."
|
||||||
|
case string(filepath.Separator):
|
||||||
|
// nothing
|
||||||
|
default:
|
||||||
|
dir = dir[0 : len(dir)-1] // chop off trailing separator
|
||||||
|
}
|
||||||
|
|
||||||
|
if !hasMeta(dir) {
|
||||||
|
return glob(fs, dir, file, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
var m []string
|
||||||
|
m, err = Glob(fs, dir)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, d := range m {
|
||||||
|
matches, err = glob(fs, d, file, matches)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// glob searches for files matching pattern in the directory dir
|
||||||
|
// and appends them to matches. If the directory cannot be
|
||||||
|
// opened, it returns the existing matches. New matches are
|
||||||
|
// added in lexicographical order.
|
||||||
|
func glob(fs Fs, dir, pattern string, matches []string) (m []string, e error) {
|
||||||
|
m = matches
|
||||||
|
fi, err := fs.Stat(dir)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !fi.IsDir() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
d, err := fs.Open(dir)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer d.Close()
|
||||||
|
|
||||||
|
names, _ := d.Readdirnames(-1)
|
||||||
|
sort.Strings(names)
|
||||||
|
|
||||||
|
for _, n := range names {
|
||||||
|
matched, err := filepath.Match(pattern, n)
|
||||||
|
if err != nil {
|
||||||
|
return m, err
|
||||||
|
}
|
||||||
|
if matched {
|
||||||
|
m = append(m, filepath.Join(dir, n))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// hasMeta reports whether path contains any of the magic characters
|
||||||
|
// recognized by Match.
|
||||||
|
func hasMeta(path string) bool {
|
||||||
|
// TODO(niemeyer): Should other magic characters be added here?
|
||||||
|
return strings.IndexAny(path, "*?[") >= 0
|
||||||
|
}
|
183
vendor/github.com/spf13/afero/match_test.go
generated
vendored
Normal file
183
vendor/github.com/spf13/afero/match_test.go
generated
vendored
Normal file
|
@ -0,0 +1,183 @@
|
||||||
|
// Copyright © 2014 Steve Francia <spf@spf13.com>.
|
||||||
|
// Copyright 2009 The Go Authors. All rights reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package afero
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// contains returns true if vector contains the string s.
|
||||||
|
func contains(vector []string, s string) bool {
|
||||||
|
for _, elem := range vector {
|
||||||
|
if elem == s {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupGlobDirRoot(t *testing.T, fs Fs) string {
|
||||||
|
path := testDir(fs)
|
||||||
|
setupGlobFiles(t, fs, path)
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupGlobDirReusePath(t *testing.T, fs Fs, path string) string {
|
||||||
|
testRegistry[fs] = append(testRegistry[fs], path)
|
||||||
|
return setupGlobFiles(t, fs, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupGlobFiles(t *testing.T, fs Fs, path string) string {
|
||||||
|
testSubDir := filepath.Join(path, "globs", "bobs")
|
||||||
|
err := fs.MkdirAll(testSubDir, 0700)
|
||||||
|
if err != nil && !os.IsExist(err) {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := fs.Create(filepath.Join(testSubDir, "/matcher"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
f.WriteString("Testfile 1 content")
|
||||||
|
f.Close()
|
||||||
|
|
||||||
|
f, err = fs.Create(filepath.Join(testSubDir, "/../submatcher"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
f.WriteString("Testfile 2 content")
|
||||||
|
f.Close()
|
||||||
|
|
||||||
|
f, err = fs.Create(filepath.Join(testSubDir, "/../../match"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
f.WriteString("Testfile 3 content")
|
||||||
|
f.Close()
|
||||||
|
|
||||||
|
return testSubDir
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGlob(t *testing.T) {
|
||||||
|
defer removeAllTestFiles(t)
|
||||||
|
var testDir string
|
||||||
|
for i, fs := range Fss {
|
||||||
|
if i == 0 {
|
||||||
|
testDir = setupGlobDirRoot(t, fs)
|
||||||
|
} else {
|
||||||
|
setupGlobDirReusePath(t, fs, testDir)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var globTests = []struct {
|
||||||
|
pattern, result string
|
||||||
|
}{
|
||||||
|
{testDir + "/globs/bobs/matcher", testDir + "/globs/bobs/matcher"},
|
||||||
|
{testDir + "/globs/*/mat?her", testDir + "/globs/bobs/matcher"},
|
||||||
|
{testDir + "/globs/bobs/../*", testDir + "/globs/submatcher"},
|
||||||
|
{testDir + "/match", testDir + "/match"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, fs := range Fss {
|
||||||
|
|
||||||
|
for _, tt := range globTests {
|
||||||
|
pattern := tt.pattern
|
||||||
|
result := tt.result
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
pattern = filepath.Clean(pattern)
|
||||||
|
result = filepath.Clean(result)
|
||||||
|
}
|
||||||
|
matches, err := Glob(fs, pattern)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Glob error for %q: %s", pattern, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !contains(matches, result) {
|
||||||
|
t.Errorf("Glob(%#q) = %#v want %v", pattern, matches, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, pattern := range []string{"no_match", "../*/no_match"} {
|
||||||
|
matches, err := Glob(fs, pattern)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Glob error for %q: %s", pattern, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(matches) != 0 {
|
||||||
|
t.Errorf("Glob(%#q) = %#v want []", pattern, matches)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGlobSymlink(t *testing.T) {
|
||||||
|
defer removeAllTestFiles(t)
|
||||||
|
|
||||||
|
fs := &OsFs{}
|
||||||
|
testDir := setupGlobDirRoot(t, fs)
|
||||||
|
|
||||||
|
err := os.Symlink("target", filepath.Join(testDir, "symlink"))
|
||||||
|
if err != nil {
|
||||||
|
t.Skipf("skipping on %s", runtime.GOOS)
|
||||||
|
}
|
||||||
|
|
||||||
|
var globSymlinkTests = []struct {
|
||||||
|
path, dest string
|
||||||
|
brokenLink bool
|
||||||
|
}{
|
||||||
|
{"test1", "link1", false},
|
||||||
|
{"test2", "link2", true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range globSymlinkTests {
|
||||||
|
path := filepath.Join(testDir, tt.path)
|
||||||
|
dest := filepath.Join(testDir, tt.dest)
|
||||||
|
f, err := fs.Create(path)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := f.Close(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = os.Symlink(path, dest)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if tt.brokenLink {
|
||||||
|
// Break the symlink.
|
||||||
|
fs.Remove(path)
|
||||||
|
}
|
||||||
|
matches, err := Glob(fs, dest)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("GlobSymlink error for %q: %s", dest, err)
|
||||||
|
}
|
||||||
|
if !contains(matches, dest) {
|
||||||
|
t.Errorf("Glob(%#q) = %#v want %v", dest, matches, dest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func TestGlobError(t *testing.T) {
|
||||||
|
for _, fs := range Fss {
|
||||||
|
_, err := Glob(fs, "[7]")
|
||||||
|
if err != nil {
|
||||||
|
t.Error("expected error for bad pattern; got none")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
37
vendor/github.com/spf13/afero/mem/dir.go
generated
vendored
Normal file
37
vendor/github.com/spf13/afero/mem/dir.go
generated
vendored
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
// Copyright © 2014 Steve Francia <spf@spf13.com>.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package mem
|
||||||
|
|
||||||
|
type Dir interface {
|
||||||
|
Len() int
|
||||||
|
Names() []string
|
||||||
|
Files() []*FileData
|
||||||
|
Add(*FileData)
|
||||||
|
Remove(*FileData)
|
||||||
|
}
|
||||||
|
|
||||||
|
func RemoveFromMemDir(dir *FileData, f *FileData) {
|
||||||
|
dir.memDir.Remove(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddToMemDir(dir *FileData, f *FileData) {
|
||||||
|
dir.memDir.Add(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func InitializeDir(d *FileData) {
|
||||||
|
if d.memDir == nil {
|
||||||
|
d.dir = true
|
||||||
|
d.memDir = &DirMap{}
|
||||||
|
}
|
||||||
|
}
|
43
vendor/github.com/spf13/afero/mem/dirmap.go
generated
vendored
Normal file
43
vendor/github.com/spf13/afero/mem/dirmap.go
generated
vendored
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
// Copyright © 2015 Steve Francia <spf@spf13.com>.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package mem
|
||||||
|
|
||||||
|
import "sort"
|
||||||
|
|
||||||
|
type DirMap map[string]*FileData
|
||||||
|
|
||||||
|
func (m DirMap) Len() int { return len(m) }
|
||||||
|
func (m DirMap) Add(f *FileData) { m[f.name] = f }
|
||||||
|
func (m DirMap) Remove(f *FileData) { delete(m, f.name) }
|
||||||
|
func (m DirMap) Files() (files []*FileData) {
|
||||||
|
for _, f := range m {
|
||||||
|
files = append(files, f)
|
||||||
|
}
|
||||||
|
sort.Sort(filesSorter(files))
|
||||||
|
return files
|
||||||
|
}
|
||||||
|
|
||||||
|
// implement sort.Interface for []*FileData
|
||||||
|
type filesSorter []*FileData
|
||||||
|
|
||||||
|
func (s filesSorter) Len() int { return len(s) }
|
||||||
|
func (s filesSorter) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||||
|
func (s filesSorter) Less(i, j int) bool { return s[i].name < s[j].name }
|
||||||
|
|
||||||
|
func (m DirMap) Names() (names []string) {
|
||||||
|
for x := range m {
|
||||||
|
names = append(names, x)
|
||||||
|
}
|
||||||
|
return names
|
||||||
|
}
|
311
vendor/github.com/spf13/afero/mem/file.go
generated
vendored
Normal file
311
vendor/github.com/spf13/afero/mem/file.go
generated
vendored
Normal file
|
@ -0,0 +1,311 @@
|
||||||
|
// Copyright © 2015 Steve Francia <spf@spf13.com>.
|
||||||
|
// Copyright 2013 tsuru authors. All rights reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package mem
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
)
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
const FilePathSeparator = string(filepath.Separator)
|
||||||
|
|
||||||
|
type File struct {
|
||||||
|
// atomic requires 64-bit alignment for struct field access
|
||||||
|
at int64
|
||||||
|
readDirCount int64
|
||||||
|
closed bool
|
||||||
|
readOnly bool
|
||||||
|
fileData *FileData
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFileHandle(data *FileData) *File {
|
||||||
|
return &File{fileData: data}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewReadOnlyFileHandle(data *FileData) *File {
|
||||||
|
return &File{fileData: data, readOnly: true}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f File) Data() *FileData {
|
||||||
|
return f.fileData
|
||||||
|
}
|
||||||
|
|
||||||
|
type FileData struct {
|
||||||
|
sync.Mutex
|
||||||
|
name string
|
||||||
|
data []byte
|
||||||
|
memDir Dir
|
||||||
|
dir bool
|
||||||
|
mode os.FileMode
|
||||||
|
modtime time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *FileData) Name() string {
|
||||||
|
d.Lock()
|
||||||
|
defer d.Unlock()
|
||||||
|
return d.name
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateFile(name string) *FileData {
|
||||||
|
return &FileData{name: name, mode: os.ModeTemporary, modtime: time.Now()}
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateDir(name string) *FileData {
|
||||||
|
return &FileData{name: name, memDir: &DirMap{}, dir: true}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ChangeFileName(f *FileData, newname string) {
|
||||||
|
f.Lock()
|
||||||
|
f.name = newname
|
||||||
|
f.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetMode(f *FileData, mode os.FileMode) {
|
||||||
|
f.Lock()
|
||||||
|
f.mode = mode
|
||||||
|
f.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetModTime(f *FileData, mtime time.Time) {
|
||||||
|
f.Lock()
|
||||||
|
setModTime(f, mtime)
|
||||||
|
f.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func setModTime(f *FileData, mtime time.Time) {
|
||||||
|
f.modtime = mtime
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetFileInfo(f *FileData) *FileInfo {
|
||||||
|
return &FileInfo{f}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *File) Open() error {
|
||||||
|
atomic.StoreInt64(&f.at, 0)
|
||||||
|
atomic.StoreInt64(&f.readDirCount, 0)
|
||||||
|
f.fileData.Lock()
|
||||||
|
f.closed = false
|
||||||
|
f.fileData.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *File) Close() error {
|
||||||
|
f.fileData.Lock()
|
||||||
|
f.closed = true
|
||||||
|
if !f.readOnly {
|
||||||
|
setModTime(f.fileData, time.Now())
|
||||||
|
}
|
||||||
|
f.fileData.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *File) Name() string {
|
||||||
|
return f.fileData.Name()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *File) Stat() (os.FileInfo, error) {
|
||||||
|
return &FileInfo{f.fileData}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *File) Sync() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *File) Readdir(count int) (res []os.FileInfo, err error) {
|
||||||
|
var outLength int64
|
||||||
|
|
||||||
|
f.fileData.Lock()
|
||||||
|
files := f.fileData.memDir.Files()[f.readDirCount:]
|
||||||
|
if count > 0 {
|
||||||
|
if len(files) < count {
|
||||||
|
outLength = int64(len(files))
|
||||||
|
} else {
|
||||||
|
outLength = int64(count)
|
||||||
|
}
|
||||||
|
if len(files) == 0 {
|
||||||
|
err = io.EOF
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
outLength = int64(len(files))
|
||||||
|
}
|
||||||
|
f.readDirCount += outLength
|
||||||
|
f.fileData.Unlock()
|
||||||
|
|
||||||
|
res = make([]os.FileInfo, outLength)
|
||||||
|
for i := range res {
|
||||||
|
res[i] = &FileInfo{files[i]}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *File) Readdirnames(n int) (names []string, err error) {
|
||||||
|
fi, err := f.Readdir(n)
|
||||||
|
names = make([]string, len(fi))
|
||||||
|
for i, f := range fi {
|
||||||
|
_, names[i] = filepath.Split(f.Name())
|
||||||
|
}
|
||||||
|
return names, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *File) Read(b []byte) (n int, err error) {
|
||||||
|
f.fileData.Lock()
|
||||||
|
defer f.fileData.Unlock()
|
||||||
|
if f.closed == true {
|
||||||
|
return 0, ErrFileClosed
|
||||||
|
}
|
||||||
|
if len(b) > 0 && int(f.at) == len(f.fileData.data) {
|
||||||
|
return 0, io.EOF
|
||||||
|
}
|
||||||
|
if len(f.fileData.data)-int(f.at) >= len(b) {
|
||||||
|
n = len(b)
|
||||||
|
} else {
|
||||||
|
n = len(f.fileData.data) - int(f.at)
|
||||||
|
}
|
||||||
|
copy(b, f.fileData.data[f.at:f.at+int64(n)])
|
||||||
|
atomic.AddInt64(&f.at, int64(n))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *File) ReadAt(b []byte, off int64) (n int, err error) {
|
||||||
|
atomic.StoreInt64(&f.at, off)
|
||||||
|
return f.Read(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *File) Truncate(size int64) error {
|
||||||
|
if f.closed == true {
|
||||||
|
return ErrFileClosed
|
||||||
|
}
|
||||||
|
if f.readOnly {
|
||||||
|
return &os.PathError{Op: "truncate", Path: f.fileData.name, Err: errors.New("file handle is read only")}
|
||||||
|
}
|
||||||
|
if size < 0 {
|
||||||
|
return ErrOutOfRange
|
||||||
|
}
|
||||||
|
if size > int64(len(f.fileData.data)) {
|
||||||
|
diff := size - int64(len(f.fileData.data))
|
||||||
|
f.fileData.data = append(f.fileData.data, bytes.Repeat([]byte{00}, int(diff))...)
|
||||||
|
} else {
|
||||||
|
f.fileData.data = f.fileData.data[0:size]
|
||||||
|
}
|
||||||
|
setModTime(f.fileData, time.Now())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *File) Seek(offset int64, whence int) (int64, error) {
|
||||||
|
if f.closed == true {
|
||||||
|
return 0, ErrFileClosed
|
||||||
|
}
|
||||||
|
switch whence {
|
||||||
|
case 0:
|
||||||
|
atomic.StoreInt64(&f.at, offset)
|
||||||
|
case 1:
|
||||||
|
atomic.AddInt64(&f.at, int64(offset))
|
||||||
|
case 2:
|
||||||
|
atomic.StoreInt64(&f.at, int64(len(f.fileData.data))+offset)
|
||||||
|
}
|
||||||
|
return f.at, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *File) Write(b []byte) (n int, err error) {
|
||||||
|
if f.readOnly {
|
||||||
|
return 0, &os.PathError{Op: "write", Path: f.fileData.name, Err: errors.New("file handle is read only")}
|
||||||
|
}
|
||||||
|
n = len(b)
|
||||||
|
cur := atomic.LoadInt64(&f.at)
|
||||||
|
f.fileData.Lock()
|
||||||
|
defer f.fileData.Unlock()
|
||||||
|
diff := cur - int64(len(f.fileData.data))
|
||||||
|
var tail []byte
|
||||||
|
if n+int(cur) < len(f.fileData.data) {
|
||||||
|
tail = f.fileData.data[n+int(cur):]
|
||||||
|
}
|
||||||
|
if diff > 0 {
|
||||||
|
f.fileData.data = append(bytes.Repeat([]byte{00}, int(diff)), b...)
|
||||||
|
f.fileData.data = append(f.fileData.data, tail...)
|
||||||
|
} else {
|
||||||
|
f.fileData.data = append(f.fileData.data[:cur], b...)
|
||||||
|
f.fileData.data = append(f.fileData.data, tail...)
|
||||||
|
}
|
||||||
|
setModTime(f.fileData, time.Now())
|
||||||
|
|
||||||
|
atomic.StoreInt64(&f.at, int64(len(f.fileData.data)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *File) WriteAt(b []byte, off int64) (n int, err error) {
|
||||||
|
atomic.StoreInt64(&f.at, off)
|
||||||
|
return f.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *File) WriteString(s string) (ret int, err error) {
|
||||||
|
return f.Write([]byte(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *File) Info() *FileInfo {
|
||||||
|
return &FileInfo{f.fileData}
|
||||||
|
}
|
||||||
|
|
||||||
|
type FileInfo struct {
|
||||||
|
*FileData
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements os.FileInfo
|
||||||
|
func (s *FileInfo) Name() string {
|
||||||
|
s.Lock()
|
||||||
|
_, name := filepath.Split(s.name)
|
||||||
|
s.Unlock()
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
func (s *FileInfo) Mode() os.FileMode {
|
||||||
|
s.Lock()
|
||||||
|
defer s.Unlock()
|
||||||
|
return s.mode
|
||||||
|
}
|
||||||
|
func (s *FileInfo) ModTime() time.Time {
|
||||||
|
s.Lock()
|
||||||
|
defer s.Unlock()
|
||||||
|
return s.modtime
|
||||||
|
}
|
||||||
|
func (s *FileInfo) IsDir() bool {
|
||||||
|
s.Lock()
|
||||||
|
defer s.Unlock()
|
||||||
|
return s.dir
|
||||||
|
}
|
||||||
|
func (s *FileInfo) Sys() interface{} { return nil }
|
||||||
|
func (s *FileInfo) Size() int64 {
|
||||||
|
if s.IsDir() {
|
||||||
|
return int64(42)
|
||||||
|
}
|
||||||
|
s.Lock()
|
||||||
|
defer s.Unlock()
|
||||||
|
return int64(len(s.data))
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrFileClosed = errors.New("File is closed")
|
||||||
|
ErrOutOfRange = errors.New("Out of range")
|
||||||
|
ErrTooLarge = errors.New("Too large")
|
||||||
|
ErrFileNotFound = os.ErrNotExist
|
||||||
|
ErrFileExists = os.ErrExist
|
||||||
|
ErrDestinationExists = os.ErrExist
|
||||||
|
)
|
154
vendor/github.com/spf13/afero/mem/file_test.go
generated
vendored
Normal file
154
vendor/github.com/spf13/afero/mem/file_test.go
generated
vendored
Normal file
|
@ -0,0 +1,154 @@
|
||||||
|
package mem
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFileDataNameRace(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
const someName = "someName"
|
||||||
|
const someOtherName = "someOtherName"
|
||||||
|
d := FileData{
|
||||||
|
name: someName,
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.Name() != someName {
|
||||||
|
t.Errorf("Failed to read correct Name, was %v", d.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
ChangeFileName(&d, someOtherName)
|
||||||
|
if d.Name() != someOtherName {
|
||||||
|
t.Errorf("Failed to set Name, was %v", d.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
ChangeFileName(&d, someName)
|
||||||
|
}()
|
||||||
|
|
||||||
|
if d.Name() != someName && d.Name() != someOtherName {
|
||||||
|
t.Errorf("Failed to read either Name, was %v", d.Name())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFileDataModTimeRace(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
someTime := time.Now()
|
||||||
|
someOtherTime := someTime.Add(1 * time.Minute)
|
||||||
|
|
||||||
|
d := FileData{
|
||||||
|
modtime: someTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
s := FileInfo{
|
||||||
|
FileData: &d,
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.ModTime() != someTime {
|
||||||
|
t.Errorf("Failed to read correct value, was %v", s.ModTime())
|
||||||
|
}
|
||||||
|
|
||||||
|
SetModTime(&d, someOtherTime)
|
||||||
|
if s.ModTime() != someOtherTime {
|
||||||
|
t.Errorf("Failed to set ModTime, was %v", s.ModTime())
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
SetModTime(&d, someTime)
|
||||||
|
}()
|
||||||
|
|
||||||
|
if s.ModTime() != someTime && s.ModTime() != someOtherTime {
|
||||||
|
t.Errorf("Failed to read either modtime, was %v", s.ModTime())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFileDataModeRace(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
const someMode = 0777
|
||||||
|
const someOtherMode = 0660
|
||||||
|
|
||||||
|
d := FileData{
|
||||||
|
mode: someMode,
|
||||||
|
}
|
||||||
|
|
||||||
|
s := FileInfo{
|
||||||
|
FileData: &d,
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.Mode() != someMode {
|
||||||
|
t.Errorf("Failed to read correct value, was %v", s.Mode())
|
||||||
|
}
|
||||||
|
|
||||||
|
SetMode(&d, someOtherMode)
|
||||||
|
if s.Mode() != someOtherMode {
|
||||||
|
t.Errorf("Failed to set Mode, was %v", s.Mode())
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
SetMode(&d, someMode)
|
||||||
|
}()
|
||||||
|
|
||||||
|
if s.Mode() != someMode && s.Mode() != someOtherMode {
|
||||||
|
t.Errorf("Failed to read either mode, was %v", s.Mode())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFileDataIsDirRace(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
d := FileData{
|
||||||
|
dir: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
s := FileInfo{
|
||||||
|
FileData: &d,
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.IsDir() != true {
|
||||||
|
t.Errorf("Failed to read correct value, was %v", s.IsDir())
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
s.Lock()
|
||||||
|
d.dir = false
|
||||||
|
s.Unlock()
|
||||||
|
}()
|
||||||
|
|
||||||
|
//just logging the value to trigger a read:
|
||||||
|
t.Logf("Value is %v", s.IsDir())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFileDataSizeRace(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
const someData = "Hello"
|
||||||
|
const someOtherDataSize = "Hello World"
|
||||||
|
|
||||||
|
d := FileData{
|
||||||
|
data: []byte(someData),
|
||||||
|
dir: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
s := FileInfo{
|
||||||
|
FileData: &d,
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.Size() != int64(len(someData)) {
|
||||||
|
t.Errorf("Failed to read correct value, was %v", s.Size())
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
s.Lock()
|
||||||
|
d.data = []byte(someOtherDataSize)
|
||||||
|
s.Unlock()
|
||||||
|
}()
|
||||||
|
|
||||||
|
//just logging the value to trigger a read:
|
||||||
|
t.Logf("Value is %v", s.Size())
|
||||||
|
|
||||||
|
//Testing the Dir size case
|
||||||
|
d.dir = true
|
||||||
|
if s.Size() != int64(42) {
|
||||||
|
t.Errorf("Failed to read correct value for dir, was %v", s.Size())
|
||||||
|
}
|
||||||
|
}
|
365
vendor/github.com/spf13/afero/memmap.go
generated
vendored
Normal file
365
vendor/github.com/spf13/afero/memmap.go
generated
vendored
Normal file
|
@ -0,0 +1,365 @@
|
||||||
|
// Copyright © 2014 Steve Francia <spf@spf13.com>.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package afero
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/spf13/afero/mem"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MemMapFs struct {
|
||||||
|
mu sync.RWMutex
|
||||||
|
data map[string]*mem.FileData
|
||||||
|
init sync.Once
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMemMapFs() Fs {
|
||||||
|
return &MemMapFs{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MemMapFs) getData() map[string]*mem.FileData {
|
||||||
|
m.init.Do(func() {
|
||||||
|
m.data = make(map[string]*mem.FileData)
|
||||||
|
// Root should always exist, right?
|
||||||
|
// TODO: what about windows?
|
||||||
|
m.data[FilePathSeparator] = mem.CreateDir(FilePathSeparator)
|
||||||
|
})
|
||||||
|
return m.data
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*MemMapFs) Name() string { return "MemMapFS" }
|
||||||
|
|
||||||
|
func (m *MemMapFs) Create(name string) (File, error) {
|
||||||
|
name = normalizePath(name)
|
||||||
|
m.mu.Lock()
|
||||||
|
file := mem.CreateFile(name)
|
||||||
|
m.getData()[name] = file
|
||||||
|
m.registerWithParent(file)
|
||||||
|
m.mu.Unlock()
|
||||||
|
return mem.NewFileHandle(file), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MemMapFs) unRegisterWithParent(fileName string) error {
|
||||||
|
f, err := m.lockfreeOpen(fileName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
parent := m.findParent(f)
|
||||||
|
if parent == nil {
|
||||||
|
log.Panic("parent of ", f.Name(), " is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
parent.Lock()
|
||||||
|
mem.RemoveFromMemDir(parent, f)
|
||||||
|
parent.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MemMapFs) findParent(f *mem.FileData) *mem.FileData {
|
||||||
|
pdir, _ := filepath.Split(f.Name())
|
||||||
|
pdir = filepath.Clean(pdir)
|
||||||
|
pfile, err := m.lockfreeOpen(pdir)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return pfile
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MemMapFs) registerWithParent(f *mem.FileData) {
|
||||||
|
if f == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
parent := m.findParent(f)
|
||||||
|
if parent == nil {
|
||||||
|
pdir := filepath.Dir(filepath.Clean(f.Name()))
|
||||||
|
err := m.lockfreeMkdir(pdir, 0777)
|
||||||
|
if err != nil {
|
||||||
|
//log.Println("Mkdir error:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
parent, err = m.lockfreeOpen(pdir)
|
||||||
|
if err != nil {
|
||||||
|
//log.Println("Open after Mkdir error:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parent.Lock()
|
||||||
|
mem.InitializeDir(parent)
|
||||||
|
mem.AddToMemDir(parent, f)
|
||||||
|
parent.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MemMapFs) lockfreeMkdir(name string, perm os.FileMode) error {
|
||||||
|
name = normalizePath(name)
|
||||||
|
x, ok := m.getData()[name]
|
||||||
|
if ok {
|
||||||
|
// Only return ErrFileExists if it's a file, not a directory.
|
||||||
|
i := mem.FileInfo{FileData: x}
|
||||||
|
if !i.IsDir() {
|
||||||
|
return ErrFileExists
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
item := mem.CreateDir(name)
|
||||||
|
m.getData()[name] = item
|
||||||
|
m.registerWithParent(item)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MemMapFs) Mkdir(name string, perm os.FileMode) error {
|
||||||
|
name = normalizePath(name)
|
||||||
|
|
||||||
|
m.mu.RLock()
|
||||||
|
_, ok := m.getData()[name]
|
||||||
|
m.mu.RUnlock()
|
||||||
|
if ok {
|
||||||
|
return &os.PathError{Op: "mkdir", Path: name, Err: ErrFileExists}
|
||||||
|
}
|
||||||
|
|
||||||
|
m.mu.Lock()
|
||||||
|
item := mem.CreateDir(name)
|
||||||
|
m.getData()[name] = item
|
||||||
|
m.registerWithParent(item)
|
||||||
|
m.mu.Unlock()
|
||||||
|
|
||||||
|
m.Chmod(name, perm|os.ModeDir)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MemMapFs) MkdirAll(path string, perm os.FileMode) error {
|
||||||
|
err := m.Mkdir(path, perm)
|
||||||
|
if err != nil {
|
||||||
|
if err.(*os.PathError).Err == ErrFileExists {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle some relative paths
|
||||||
|
func normalizePath(path string) string {
|
||||||
|
path = filepath.Clean(path)
|
||||||
|
|
||||||
|
switch path {
|
||||||
|
case ".":
|
||||||
|
return FilePathSeparator
|
||||||
|
case "..":
|
||||||
|
return FilePathSeparator
|
||||||
|
default:
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MemMapFs) Open(name string) (File, error) {
|
||||||
|
f, err := m.open(name)
|
||||||
|
if f != nil {
|
||||||
|
return mem.NewReadOnlyFileHandle(f), err
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MemMapFs) openWrite(name string) (File, error) {
|
||||||
|
f, err := m.open(name)
|
||||||
|
if f != nil {
|
||||||
|
return mem.NewFileHandle(f), err
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MemMapFs) open(name string) (*mem.FileData, error) {
|
||||||
|
name = normalizePath(name)
|
||||||
|
|
||||||
|
m.mu.RLock()
|
||||||
|
f, ok := m.getData()[name]
|
||||||
|
m.mu.RUnlock()
|
||||||
|
if !ok {
|
||||||
|
return nil, &os.PathError{Op: "open", Path: name, Err: ErrFileNotFound}
|
||||||
|
}
|
||||||
|
return f, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MemMapFs) lockfreeOpen(name string) (*mem.FileData, error) {
|
||||||
|
name = normalizePath(name)
|
||||||
|
f, ok := m.getData()[name]
|
||||||
|
if ok {
|
||||||
|
return f, nil
|
||||||
|
} else {
|
||||||
|
return nil, ErrFileNotFound
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MemMapFs) OpenFile(name string, flag int, perm os.FileMode) (File, error) {
|
||||||
|
chmod := false
|
||||||
|
file, err := m.openWrite(name)
|
||||||
|
if os.IsNotExist(err) && (flag&os.O_CREATE > 0) {
|
||||||
|
file, err = m.Create(name)
|
||||||
|
chmod = true
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if flag == os.O_RDONLY {
|
||||||
|
file = mem.NewReadOnlyFileHandle(file.(*mem.File).Data())
|
||||||
|
}
|
||||||
|
if flag&os.O_APPEND > 0 {
|
||||||
|
_, err = file.Seek(0, os.SEEK_END)
|
||||||
|
if err != nil {
|
||||||
|
file.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if flag&os.O_TRUNC > 0 && flag&(os.O_RDWR|os.O_WRONLY) > 0 {
|
||||||
|
err = file.Truncate(0)
|
||||||
|
if err != nil {
|
||||||
|
file.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if chmod {
|
||||||
|
m.Chmod(name, perm)
|
||||||
|
}
|
||||||
|
return file, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MemMapFs) Remove(name string) error {
|
||||||
|
name = normalizePath(name)
|
||||||
|
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
|
|
||||||
|
if _, ok := m.getData()[name]; ok {
|
||||||
|
err := m.unRegisterWithParent(name)
|
||||||
|
if err != nil {
|
||||||
|
return &os.PathError{Op: "remove", Path: name, Err: err}
|
||||||
|
}
|
||||||
|
delete(m.getData(), name)
|
||||||
|
} else {
|
||||||
|
return &os.PathError{Op: "remove", Path: name, Err: os.ErrNotExist}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MemMapFs) RemoveAll(path string) error {
|
||||||
|
path = normalizePath(path)
|
||||||
|
m.mu.Lock()
|
||||||
|
m.unRegisterWithParent(path)
|
||||||
|
m.mu.Unlock()
|
||||||
|
|
||||||
|
m.mu.RLock()
|
||||||
|
defer m.mu.RUnlock()
|
||||||
|
|
||||||
|
for p, _ := range m.getData() {
|
||||||
|
if strings.HasPrefix(p, path) {
|
||||||
|
m.mu.RUnlock()
|
||||||
|
m.mu.Lock()
|
||||||
|
delete(m.getData(), p)
|
||||||
|
m.mu.Unlock()
|
||||||
|
m.mu.RLock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MemMapFs) Rename(oldname, newname string) error {
|
||||||
|
oldname = normalizePath(oldname)
|
||||||
|
newname = normalizePath(newname)
|
||||||
|
|
||||||
|
if oldname == newname {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
m.mu.RLock()
|
||||||
|
defer m.mu.RUnlock()
|
||||||
|
if _, ok := m.getData()[oldname]; ok {
|
||||||
|
m.mu.RUnlock()
|
||||||
|
m.mu.Lock()
|
||||||
|
m.unRegisterWithParent(oldname)
|
||||||
|
fileData := m.getData()[oldname]
|
||||||
|
delete(m.getData(), oldname)
|
||||||
|
mem.ChangeFileName(fileData, newname)
|
||||||
|
m.getData()[newname] = fileData
|
||||||
|
m.registerWithParent(fileData)
|
||||||
|
m.mu.Unlock()
|
||||||
|
m.mu.RLock()
|
||||||
|
} else {
|
||||||
|
return &os.PathError{Op: "rename", Path: oldname, Err: ErrFileNotFound}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MemMapFs) Stat(name string) (os.FileInfo, error) {
|
||||||
|
f, err := m.Open(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
fi := mem.GetFileInfo(f.(*mem.File).Data())
|
||||||
|
return fi, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MemMapFs) Chmod(name string, mode os.FileMode) error {
|
||||||
|
name = normalizePath(name)
|
||||||
|
|
||||||
|
m.mu.RLock()
|
||||||
|
f, ok := m.getData()[name]
|
||||||
|
m.mu.RUnlock()
|
||||||
|
if !ok {
|
||||||
|
return &os.PathError{Op: "chmod", Path: name, Err: ErrFileNotFound}
|
||||||
|
}
|
||||||
|
|
||||||
|
m.mu.Lock()
|
||||||
|
mem.SetMode(f, mode)
|
||||||
|
m.mu.Unlock()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MemMapFs) Chtimes(name string, atime time.Time, mtime time.Time) error {
|
||||||
|
name = normalizePath(name)
|
||||||
|
|
||||||
|
m.mu.RLock()
|
||||||
|
f, ok := m.getData()[name]
|
||||||
|
m.mu.RUnlock()
|
||||||
|
if !ok {
|
||||||
|
return &os.PathError{Op: "chtimes", Path: name, Err: ErrFileNotFound}
|
||||||
|
}
|
||||||
|
|
||||||
|
m.mu.Lock()
|
||||||
|
mem.SetModTime(f, mtime)
|
||||||
|
m.mu.Unlock()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MemMapFs) List() {
|
||||||
|
for _, x := range m.data {
|
||||||
|
y := mem.FileInfo{FileData: x}
|
||||||
|
fmt.Println(x.Name(), y.Size())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// func debugMemMapList(fs Fs) {
|
||||||
|
// if x, ok := fs.(*MemMapFs); ok {
|
||||||
|
// x.List()
|
||||||
|
// }
|
||||||
|
// }
|
421
vendor/github.com/spf13/afero/memmap_test.go
generated
vendored
Normal file
421
vendor/github.com/spf13/afero/memmap_test.go
generated
vendored
Normal file
|
@ -0,0 +1,421 @@
|
||||||
|
package afero
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNormalizePath(t *testing.T) {
|
||||||
|
type test struct {
|
||||||
|
input string
|
||||||
|
expected string
|
||||||
|
}
|
||||||
|
|
||||||
|
data := []test{
|
||||||
|
{".", FilePathSeparator},
|
||||||
|
{"./", FilePathSeparator},
|
||||||
|
{"..", FilePathSeparator},
|
||||||
|
{"../", FilePathSeparator},
|
||||||
|
{"./..", FilePathSeparator},
|
||||||
|
{"./../", FilePathSeparator},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, d := range data {
|
||||||
|
cpath := normalizePath(d.input)
|
||||||
|
if d.expected != cpath {
|
||||||
|
t.Errorf("Test %d failed. Expected %q got %q", i, d.expected, cpath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPathErrors(t *testing.T) {
|
||||||
|
path := filepath.Join(".", "some", "path")
|
||||||
|
path2 := filepath.Join(".", "different", "path")
|
||||||
|
fs := NewMemMapFs()
|
||||||
|
perm := os.FileMode(0755)
|
||||||
|
|
||||||
|
// relevant functions:
|
||||||
|
// func (m *MemMapFs) Chmod(name string, mode os.FileMode) error
|
||||||
|
// func (m *MemMapFs) Chtimes(name string, atime time.Time, mtime time.Time) error
|
||||||
|
// func (m *MemMapFs) Create(name string) (File, error)
|
||||||
|
// func (m *MemMapFs) Mkdir(name string, perm os.FileMode) error
|
||||||
|
// func (m *MemMapFs) MkdirAll(path string, perm os.FileMode) error
|
||||||
|
// func (m *MemMapFs) Open(name string) (File, error)
|
||||||
|
// func (m *MemMapFs) OpenFile(name string, flag int, perm os.FileMode) (File, error)
|
||||||
|
// func (m *MemMapFs) Remove(name string) error
|
||||||
|
// func (m *MemMapFs) Rename(oldname, newname string) error
|
||||||
|
// func (m *MemMapFs) Stat(name string) (os.FileInfo, error)
|
||||||
|
|
||||||
|
err := fs.Chmod(path, perm)
|
||||||
|
checkPathError(t, err, "Chmod")
|
||||||
|
|
||||||
|
err = fs.Chtimes(path, time.Now(), time.Now())
|
||||||
|
checkPathError(t, err, "Chtimes")
|
||||||
|
|
||||||
|
// fs.Create doesn't return an error
|
||||||
|
|
||||||
|
err = fs.Mkdir(path2, perm)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
err = fs.Mkdir(path2, perm)
|
||||||
|
checkPathError(t, err, "Mkdir")
|
||||||
|
|
||||||
|
err = fs.MkdirAll(path2, perm)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("MkdirAll:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = fs.Open(path)
|
||||||
|
checkPathError(t, err, "Open")
|
||||||
|
|
||||||
|
_, err = fs.OpenFile(path, os.O_RDWR, perm)
|
||||||
|
checkPathError(t, err, "OpenFile")
|
||||||
|
|
||||||
|
err = fs.Remove(path)
|
||||||
|
checkPathError(t, err, "Remove")
|
||||||
|
|
||||||
|
err = fs.RemoveAll(path)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("RemoveAll:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = fs.Rename(path, path2)
|
||||||
|
checkPathError(t, err, "Rename")
|
||||||
|
|
||||||
|
_, err = fs.Stat(path)
|
||||||
|
checkPathError(t, err, "Stat")
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkPathError(t *testing.T, err error, op string) {
|
||||||
|
pathErr, ok := err.(*os.PathError)
|
||||||
|
if !ok {
|
||||||
|
t.Error(op+":", err, "is not a os.PathError")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, ok = pathErr.Err.(*os.PathError)
|
||||||
|
if ok {
|
||||||
|
t.Error(op+":", err, "contains another os.PathError")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure Permissions are set on OpenFile/Mkdir/MkdirAll
|
||||||
|
func TestPermSet(t *testing.T) {
|
||||||
|
const fileName = "/myFileTest"
|
||||||
|
const dirPath = "/myDirTest"
|
||||||
|
const dirPathAll = "/my/path/to/dir"
|
||||||
|
|
||||||
|
const fileMode = os.FileMode(0765)
|
||||||
|
// directories will also have the directory bit set
|
||||||
|
const dirMode = fileMode | os.ModeDir
|
||||||
|
|
||||||
|
fs := NewMemMapFs()
|
||||||
|
|
||||||
|
// Test Openfile
|
||||||
|
f, err := fs.OpenFile(fileName, os.O_CREATE, fileMode)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("OpenFile Create failed: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
f.Close()
|
||||||
|
|
||||||
|
s, err := fs.Stat(fileName)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Stat failed: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if s.Mode().String() != fileMode.String() {
|
||||||
|
t.Errorf("Permissions Incorrect: %s != %s", s.Mode().String(), fileMode.String())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test Mkdir
|
||||||
|
err = fs.Mkdir(dirPath, dirMode)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("MkDir Create failed: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s, err = fs.Stat(dirPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Stat failed: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// sets File
|
||||||
|
if s.Mode().String() != dirMode.String() {
|
||||||
|
t.Errorf("Permissions Incorrect: %s != %s", s.Mode().String(), dirMode.String())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test MkdirAll
|
||||||
|
err = fs.MkdirAll(dirPathAll, dirMode)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("MkDir Create failed: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s, err = fs.Stat(dirPathAll)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Stat failed: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if s.Mode().String() != dirMode.String() {
|
||||||
|
t.Errorf("Permissions Incorrect: %s != %s", s.Mode().String(), dirMode.String())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fails if multiple file objects use the same file.at counter in MemMapFs
|
||||||
|
func TestMultipleOpenFiles(t *testing.T) {
|
||||||
|
defer removeAllTestFiles(t)
|
||||||
|
const fileName = "afero-demo2.txt"
|
||||||
|
|
||||||
|
var data = make([][]byte, len(Fss))
|
||||||
|
|
||||||
|
for i, fs := range Fss {
|
||||||
|
dir := testDir(fs)
|
||||||
|
path := filepath.Join(dir, fileName)
|
||||||
|
fh1, err := fs.Create(path)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("fs.Create failed: " + err.Error())
|
||||||
|
}
|
||||||
|
_, err = fh1.Write([]byte("test"))
|
||||||
|
if err != nil {
|
||||||
|
t.Error("fh.Write failed: " + err.Error())
|
||||||
|
}
|
||||||
|
_, err = fh1.Seek(0, os.SEEK_SET)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fh2, err := fs.OpenFile(path, os.O_RDWR, 0777)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("fs.OpenFile failed: " + err.Error())
|
||||||
|
}
|
||||||
|
_, err = fh2.Seek(0, os.SEEK_END)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
_, err = fh2.Write([]byte("data"))
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
err = fh2.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = fh1.Write([]byte("data"))
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
err = fh1.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
// the file now should contain "datadata"
|
||||||
|
data[i], err = ReadFile(fs, path)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, fs := range Fss {
|
||||||
|
if i == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if string(data[0]) != string(data[i]) {
|
||||||
|
t.Errorf("%s and %s don't behave the same\n"+
|
||||||
|
"%s: \"%s\"\n%s: \"%s\"\n",
|
||||||
|
Fss[0].Name(), fs.Name(), Fss[0].Name(), data[0], fs.Name(), data[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test if file.Write() fails when opened as read only
|
||||||
|
func TestReadOnly(t *testing.T) {
|
||||||
|
defer removeAllTestFiles(t)
|
||||||
|
const fileName = "afero-demo.txt"
|
||||||
|
|
||||||
|
for _, fs := range Fss {
|
||||||
|
dir := testDir(fs)
|
||||||
|
path := filepath.Join(dir, fileName)
|
||||||
|
|
||||||
|
f, err := fs.Create(path)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(fs.Name()+":", "fs.Create failed: "+err.Error())
|
||||||
|
}
|
||||||
|
_, err = f.Write([]byte("test"))
|
||||||
|
if err != nil {
|
||||||
|
t.Error(fs.Name()+":", "Write failed: "+err.Error())
|
||||||
|
}
|
||||||
|
f.Close()
|
||||||
|
|
||||||
|
f, err = fs.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("fs.Open failed: " + err.Error())
|
||||||
|
}
|
||||||
|
_, err = f.Write([]byte("data"))
|
||||||
|
if err == nil {
|
||||||
|
t.Error(fs.Name()+":", "No write error")
|
||||||
|
}
|
||||||
|
f.Close()
|
||||||
|
|
||||||
|
f, err = fs.OpenFile(path, os.O_RDONLY, 0644)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("fs.Open failed: " + err.Error())
|
||||||
|
}
|
||||||
|
_, err = f.Write([]byte("data"))
|
||||||
|
if err == nil {
|
||||||
|
t.Error(fs.Name()+":", "No write error")
|
||||||
|
}
|
||||||
|
f.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWriteCloseTime(t *testing.T) {
|
||||||
|
defer removeAllTestFiles(t)
|
||||||
|
const fileName = "afero-demo.txt"
|
||||||
|
|
||||||
|
for _, fs := range Fss {
|
||||||
|
dir := testDir(fs)
|
||||||
|
path := filepath.Join(dir, fileName)
|
||||||
|
|
||||||
|
f, err := fs.Create(path)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(fs.Name()+":", "fs.Create failed: "+err.Error())
|
||||||
|
}
|
||||||
|
f.Close()
|
||||||
|
|
||||||
|
f, err = fs.Create(path)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(fs.Name()+":", "fs.Create failed: "+err.Error())
|
||||||
|
}
|
||||||
|
fi, err := f.Stat()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(fs.Name()+":", "Stat failed: "+err.Error())
|
||||||
|
}
|
||||||
|
timeBefore := fi.ModTime()
|
||||||
|
|
||||||
|
// sorry for the delay, but we have to make sure time advances,
|
||||||
|
// also on non Un*x systems...
|
||||||
|
switch runtime.GOOS {
|
||||||
|
case "windows":
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
case "darwin":
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
default: // depending on the FS, this may work with < 1 second, on my old ext3 it does not
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = f.Write([]byte("test"))
|
||||||
|
if err != nil {
|
||||||
|
t.Error(fs.Name()+":", "Write failed: "+err.Error())
|
||||||
|
}
|
||||||
|
f.Close()
|
||||||
|
fi, err = fs.Stat(path)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(fs.Name()+":", "fs.Stat failed: "+err.Error())
|
||||||
|
}
|
||||||
|
if fi.ModTime().Equal(timeBefore) {
|
||||||
|
t.Error(fs.Name()+":", "ModTime was not set on Close()")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This test should be run with the race detector on:
|
||||||
|
// go test -race -v -timeout 10s -run TestRacingDeleteAndClose
|
||||||
|
func TestRacingDeleteAndClose(t *testing.T) {
|
||||||
|
fs := NewMemMapFs()
|
||||||
|
pathname := "testfile"
|
||||||
|
f, err := fs.Create(pathname)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
in := make(chan bool)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
<-in
|
||||||
|
f.Close()
|
||||||
|
}()
|
||||||
|
go func() {
|
||||||
|
<-in
|
||||||
|
fs.Remove(pathname)
|
||||||
|
}()
|
||||||
|
close(in)
|
||||||
|
}
|
||||||
|
|
||||||
|
// This test should be run with the race detector on:
|
||||||
|
// go test -run TestMemFsDataRace -race
|
||||||
|
func TestMemFsDataRace(t *testing.T) {
|
||||||
|
const dir = "test_dir"
|
||||||
|
fs := NewMemMapFs()
|
||||||
|
|
||||||
|
if err := fs.MkdirAll(dir, 0777); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
const n = 1000
|
||||||
|
done := make(chan struct{})
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer close(done)
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
fname := filepath.Join(dir, fmt.Sprintf("%d.txt", i))
|
||||||
|
if err := WriteFile(fs, fname, []byte(""), 0777); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if err := fs.Remove(fname); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
loop:
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-done:
|
||||||
|
break loop
|
||||||
|
default:
|
||||||
|
_, err := ReadDir(fs, dir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMemFsDirMode(t *testing.T) {
|
||||||
|
fs := NewMemMapFs()
|
||||||
|
err := fs.Mkdir("/testDir1", 0644)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
err = fs.MkdirAll("/sub/testDir2", 0644)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
info, err := fs.Stat("/testDir1")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if !info.IsDir() {
|
||||||
|
t.Error("should be a directory")
|
||||||
|
}
|
||||||
|
if !info.Mode().IsDir() {
|
||||||
|
t.Error("FileMode is not directory")
|
||||||
|
}
|
||||||
|
info, err = fs.Stat("/sub/testDir2")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if !info.IsDir() {
|
||||||
|
t.Error("should be a directory")
|
||||||
|
}
|
||||||
|
if !info.Mode().IsDir() {
|
||||||
|
t.Error("FileMode is not directory")
|
||||||
|
}
|
||||||
|
}
|
94
vendor/github.com/spf13/afero/os.go
generated
vendored
Normal file
94
vendor/github.com/spf13/afero/os.go
generated
vendored
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
// Copyright © 2014 Steve Francia <spf@spf13.com>.
|
||||||
|
// Copyright 2013 tsuru authors. All rights reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package afero
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// OsFs is a Fs implementation that uses functions provided by the os package.
|
||||||
|
//
|
||||||
|
// For details in any method, check the documentation of the os package
|
||||||
|
// (http://golang.org/pkg/os/).
|
||||||
|
type OsFs struct{}
|
||||||
|
|
||||||
|
func NewOsFs() Fs {
|
||||||
|
return &OsFs{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (OsFs) Name() string { return "OsFs" }
|
||||||
|
|
||||||
|
func (OsFs) Create(name string) (File, error) {
|
||||||
|
f, e := os.Create(name)
|
||||||
|
if f == nil {
|
||||||
|
// while this looks strange, we need to return a bare nil (of type nil) not
|
||||||
|
// a nil value of type *os.File or nil won't be nil
|
||||||
|
return nil, e
|
||||||
|
}
|
||||||
|
return f, e
|
||||||
|
}
|
||||||
|
|
||||||
|
func (OsFs) Mkdir(name string, perm os.FileMode) error {
|
||||||
|
return os.Mkdir(name, perm)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (OsFs) MkdirAll(path string, perm os.FileMode) error {
|
||||||
|
return os.MkdirAll(path, perm)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (OsFs) Open(name string) (File, error) {
|
||||||
|
f, e := os.Open(name)
|
||||||
|
if f == nil {
|
||||||
|
// while this looks strange, we need to return a bare nil (of type nil) not
|
||||||
|
// a nil value of type *os.File or nil won't be nil
|
||||||
|
return nil, e
|
||||||
|
}
|
||||||
|
return f, e
|
||||||
|
}
|
||||||
|
|
||||||
|
func (OsFs) OpenFile(name string, flag int, perm os.FileMode) (File, error) {
|
||||||
|
f, e := os.OpenFile(name, flag, perm)
|
||||||
|
if f == nil {
|
||||||
|
// while this looks strange, we need to return a bare nil (of type nil) not
|
||||||
|
// a nil value of type *os.File or nil won't be nil
|
||||||
|
return nil, e
|
||||||
|
}
|
||||||
|
return f, e
|
||||||
|
}
|
||||||
|
|
||||||
|
func (OsFs) Remove(name string) error {
|
||||||
|
return os.Remove(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (OsFs) RemoveAll(path string) error {
|
||||||
|
return os.RemoveAll(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (OsFs) Rename(oldname, newname string) error {
|
||||||
|
return os.Rename(oldname, newname)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (OsFs) Stat(name string) (os.FileInfo, error) {
|
||||||
|
return os.Stat(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (OsFs) Chmod(name string, mode os.FileMode) error {
|
||||||
|
return os.Chmod(name, mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (OsFs) Chtimes(name string, atime time.Time, mtime time.Time) error {
|
||||||
|
return os.Chtimes(name, atime, mtime)
|
||||||
|
}
|
108
vendor/github.com/spf13/afero/path.go
generated
vendored
Normal file
108
vendor/github.com/spf13/afero/path.go
generated
vendored
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
// Copyright ©2015 The Go Authors
|
||||||
|
// Copyright ©2015 Steve Francia <spf@spf13.com>
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package afero
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
// readDirNames reads the directory named by dirname and returns
|
||||||
|
// a sorted list of directory entries.
|
||||||
|
// adapted from https://golang.org/src/path/filepath/path.go
|
||||||
|
func readDirNames(fs Fs, dirname string) ([]string, error) {
|
||||||
|
f, err := fs.Open(dirname)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
names, err := f.Readdirnames(-1)
|
||||||
|
f.Close()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sort.Strings(names)
|
||||||
|
return names, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// walk recursively descends path, calling walkFn
|
||||||
|
// adapted from https://golang.org/src/path/filepath/path.go
|
||||||
|
func walk(fs Fs, path string, info os.FileInfo, walkFn filepath.WalkFunc) error {
|
||||||
|
err := walkFn(path, info, nil)
|
||||||
|
if err != nil {
|
||||||
|
if info.IsDir() && err == filepath.SkipDir {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !info.IsDir() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
names, err := readDirNames(fs, path)
|
||||||
|
if err != nil {
|
||||||
|
return walkFn(path, info, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, name := range names {
|
||||||
|
filename := filepath.Join(path, name)
|
||||||
|
fileInfo, err := lstatIfOs(fs, filename)
|
||||||
|
if err != nil {
|
||||||
|
if err := walkFn(filename, fileInfo, err); err != nil && err != filepath.SkipDir {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = walk(fs, filename, fileInfo, walkFn)
|
||||||
|
if err != nil {
|
||||||
|
if !fileInfo.IsDir() || err != filepath.SkipDir {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the filesystem is OsFs use Lstat, else use fs.Stat
|
||||||
|
func lstatIfOs(fs Fs, path string) (info os.FileInfo, err error) {
|
||||||
|
_, ok := fs.(*OsFs)
|
||||||
|
if ok {
|
||||||
|
info, err = os.Lstat(path)
|
||||||
|
} else {
|
||||||
|
info, err = fs.Stat(path)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Walk walks the file tree rooted at root, calling walkFn for each file or
|
||||||
|
// directory in the tree, including root. All errors that arise visiting files
|
||||||
|
// and directories are filtered by walkFn. The files are walked in lexical
|
||||||
|
// order, which makes the output deterministic but means that for very
|
||||||
|
// large directories Walk can be inefficient.
|
||||||
|
// Walk does not follow symbolic links.
|
||||||
|
|
||||||
|
func (a Afero) Walk(root string, walkFn filepath.WalkFunc) error {
|
||||||
|
return Walk(a.Fs, root, walkFn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Walk(fs Fs, root string, walkFn filepath.WalkFunc) error {
|
||||||
|
info, err := lstatIfOs(fs, root)
|
||||||
|
if err != nil {
|
||||||
|
return walkFn(root, nil, err)
|
||||||
|
}
|
||||||
|
return walk(fs, root, info, walkFn)
|
||||||
|
}
|
69
vendor/github.com/spf13/afero/path_test.go
generated
vendored
Normal file
69
vendor/github.com/spf13/afero/path_test.go
generated
vendored
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
// Copyright © 2014 Steve Francia <spf@spf13.com>.
|
||||||
|
// Copyright 2009 The Go Authors. All rights reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package afero
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestWalk(t *testing.T) {
|
||||||
|
defer removeAllTestFiles(t)
|
||||||
|
var testDir string
|
||||||
|
for i, fs := range Fss {
|
||||||
|
if i == 0 {
|
||||||
|
testDir = setupTestDirRoot(t, fs)
|
||||||
|
} else {
|
||||||
|
setupTestDirReusePath(t, fs, testDir)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
outputs := make([]string, len(Fss))
|
||||||
|
for i, fs := range Fss {
|
||||||
|
walkFn := func(path string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
t.Error("walkFn err:", err)
|
||||||
|
}
|
||||||
|
var size int64
|
||||||
|
if !info.IsDir() {
|
||||||
|
size = info.Size()
|
||||||
|
}
|
||||||
|
outputs[i] += fmt.Sprintln(path, info.Name(), size, info.IsDir(), err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
err := Walk(fs, testDir, walkFn)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fail := false
|
||||||
|
for i, o := range outputs {
|
||||||
|
if i == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if o != outputs[i-1] {
|
||||||
|
fail = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if fail {
|
||||||
|
t.Log("Walk outputs not equal!")
|
||||||
|
for i, o := range outputs {
|
||||||
|
t.Log(Fss[i].Name() + "\n" + o)
|
||||||
|
}
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
70
vendor/github.com/spf13/afero/readonlyfs.go
generated
vendored
Normal file
70
vendor/github.com/spf13/afero/readonlyfs.go
generated
vendored
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
package afero
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ReadOnlyFs struct {
|
||||||
|
source Fs
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewReadOnlyFs(source Fs) Fs {
|
||||||
|
return &ReadOnlyFs{source: source}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ReadOnlyFs) ReadDir(name string) ([]os.FileInfo, error) {
|
||||||
|
return ReadDir(r.source, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ReadOnlyFs) Chtimes(n string, a, m time.Time) error {
|
||||||
|
return syscall.EPERM
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ReadOnlyFs) Chmod(n string, m os.FileMode) error {
|
||||||
|
return syscall.EPERM
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ReadOnlyFs) Name() string {
|
||||||
|
return "ReadOnlyFilter"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ReadOnlyFs) Stat(name string) (os.FileInfo, error) {
|
||||||
|
return r.source.Stat(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ReadOnlyFs) Rename(o, n string) error {
|
||||||
|
return syscall.EPERM
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ReadOnlyFs) RemoveAll(p string) error {
|
||||||
|
return syscall.EPERM
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ReadOnlyFs) Remove(n string) error {
|
||||||
|
return syscall.EPERM
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ReadOnlyFs) OpenFile(name string, flag int, perm os.FileMode) (File, error) {
|
||||||
|
if flag&(os.O_WRONLY|syscall.O_RDWR|os.O_APPEND|os.O_CREATE|os.O_TRUNC) != 0 {
|
||||||
|
return nil, syscall.EPERM
|
||||||
|
}
|
||||||
|
return r.source.OpenFile(name, flag, perm)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ReadOnlyFs) Open(n string) (File, error) {
|
||||||
|
return r.source.Open(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ReadOnlyFs) Mkdir(n string, p os.FileMode) error {
|
||||||
|
return syscall.EPERM
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ReadOnlyFs) MkdirAll(n string, p os.FileMode) error {
|
||||||
|
return syscall.EPERM
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ReadOnlyFs) Create(n string) (File, error) {
|
||||||
|
return nil, syscall.EPERM
|
||||||
|
}
|
214
vendor/github.com/spf13/afero/regexpfs.go
generated
vendored
Normal file
214
vendor/github.com/spf13/afero/regexpfs.go
generated
vendored
Normal file
|
@ -0,0 +1,214 @@
|
||||||
|
package afero
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The RegexpFs filters files (not directories) by regular expression. Only
|
||||||
|
// files matching the given regexp will be allowed, all others get a ENOENT error (
|
||||||
|
// "No such file or directory").
|
||||||
|
//
|
||||||
|
type RegexpFs struct {
|
||||||
|
re *regexp.Regexp
|
||||||
|
source Fs
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRegexpFs(source Fs, re *regexp.Regexp) Fs {
|
||||||
|
return &RegexpFs{source: source, re: re}
|
||||||
|
}
|
||||||
|
|
||||||
|
type RegexpFile struct {
|
||||||
|
f File
|
||||||
|
re *regexp.Regexp
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RegexpFs) matchesName(name string) error {
|
||||||
|
if r.re == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if r.re.MatchString(name) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return syscall.ENOENT
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RegexpFs) dirOrMatches(name string) error {
|
||||||
|
dir, err := IsDir(r.source, name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if dir {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return r.matchesName(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RegexpFs) Chtimes(name string, a, m time.Time) error {
|
||||||
|
if err := r.dirOrMatches(name); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return r.source.Chtimes(name, a, m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RegexpFs) Chmod(name string, mode os.FileMode) error {
|
||||||
|
if err := r.dirOrMatches(name); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return r.source.Chmod(name, mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RegexpFs) Name() string {
|
||||||
|
return "RegexpFs"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RegexpFs) Stat(name string) (os.FileInfo, error) {
|
||||||
|
if err := r.dirOrMatches(name); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return r.source.Stat(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RegexpFs) Rename(oldname, newname string) error {
|
||||||
|
dir, err := IsDir(r.source, oldname)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if dir {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err := r.matchesName(oldname); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := r.matchesName(newname); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return r.source.Rename(oldname, newname)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RegexpFs) RemoveAll(p string) error {
|
||||||
|
dir, err := IsDir(r.source, p)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !dir {
|
||||||
|
if err := r.matchesName(p); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return r.source.RemoveAll(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RegexpFs) Remove(name string) error {
|
||||||
|
if err := r.dirOrMatches(name); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return r.source.Remove(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RegexpFs) OpenFile(name string, flag int, perm os.FileMode) (File, error) {
|
||||||
|
if err := r.dirOrMatches(name); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return r.source.OpenFile(name, flag, perm)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RegexpFs) Open(name string) (File, error) {
|
||||||
|
dir, err := IsDir(r.source, name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !dir {
|
||||||
|
if err := r.matchesName(name); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f, err := r.source.Open(name)
|
||||||
|
return &RegexpFile{f: f, re: r.re}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RegexpFs) Mkdir(n string, p os.FileMode) error {
|
||||||
|
return r.source.Mkdir(n, p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RegexpFs) MkdirAll(n string, p os.FileMode) error {
|
||||||
|
return r.source.MkdirAll(n, p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RegexpFs) Create(name string) (File, error) {
|
||||||
|
if err := r.matchesName(name); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return r.source.Create(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *RegexpFile) Close() error {
|
||||||
|
return f.f.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *RegexpFile) Read(s []byte) (int, error) {
|
||||||
|
return f.f.Read(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *RegexpFile) ReadAt(s []byte, o int64) (int, error) {
|
||||||
|
return f.f.ReadAt(s, o)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *RegexpFile) Seek(o int64, w int) (int64, error) {
|
||||||
|
return f.f.Seek(o, w)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *RegexpFile) Write(s []byte) (int, error) {
|
||||||
|
return f.f.Write(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *RegexpFile) WriteAt(s []byte, o int64) (int, error) {
|
||||||
|
return f.f.WriteAt(s, o)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *RegexpFile) Name() string {
|
||||||
|
return f.f.Name()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *RegexpFile) Readdir(c int) (fi []os.FileInfo, err error) {
|
||||||
|
var rfi []os.FileInfo
|
||||||
|
rfi, err = f.f.Readdir(c)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, i := range rfi {
|
||||||
|
if i.IsDir() || f.re.MatchString(i.Name()) {
|
||||||
|
fi = append(fi, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fi, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *RegexpFile) Readdirnames(c int) (n []string, err error) {
|
||||||
|
fi, err := f.Readdir(c)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, s := range fi {
|
||||||
|
n = append(n, s.Name())
|
||||||
|
}
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *RegexpFile) Stat() (os.FileInfo, error) {
|
||||||
|
return f.f.Stat()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *RegexpFile) Sync() error {
|
||||||
|
return f.f.Sync()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *RegexpFile) Truncate(s int64) error {
|
||||||
|
return f.f.Truncate(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *RegexpFile) WriteString(s string) (int, error) {
|
||||||
|
return f.f.WriteString(s)
|
||||||
|
}
|
96
vendor/github.com/spf13/afero/ro_regexp_test.go
generated
vendored
Normal file
96
vendor/github.com/spf13/afero/ro_regexp_test.go
generated
vendored
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
package afero
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFilterReadOnly(t *testing.T) {
|
||||||
|
fs := &ReadOnlyFs{source: &MemMapFs{}}
|
||||||
|
_, err := fs.Create("/file.txt")
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("Did not fail to create file")
|
||||||
|
}
|
||||||
|
// t.Logf("ERR=%s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFilterReadonlyRemoveAndRead(t *testing.T) {
|
||||||
|
mfs := &MemMapFs{}
|
||||||
|
fh, err := mfs.Create("/file.txt")
|
||||||
|
fh.Write([]byte("content here"))
|
||||||
|
fh.Close()
|
||||||
|
|
||||||
|
fs := NewReadOnlyFs(mfs)
|
||||||
|
err = fs.Remove("/file.txt")
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("Did not fail to remove file")
|
||||||
|
}
|
||||||
|
|
||||||
|
fh, err = fs.Open("/file.txt")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Failed to open file: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := make([]byte, len("content here"))
|
||||||
|
_, err = fh.Read(buf)
|
||||||
|
fh.Close()
|
||||||
|
if string(buf) != "content here" {
|
||||||
|
t.Errorf("Failed to read file: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = mfs.Remove("/file.txt")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Failed to remove file")
|
||||||
|
}
|
||||||
|
|
||||||
|
fh, err = fs.Open("/file.txt")
|
||||||
|
if err == nil {
|
||||||
|
fh.Close()
|
||||||
|
t.Errorf("File still present")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFilterRegexp(t *testing.T) {
|
||||||
|
fs := NewRegexpFs(&MemMapFs{}, regexp.MustCompile(`\.txt$`))
|
||||||
|
_, err := fs.Create("/file.html")
|
||||||
|
if err == nil {
|
||||||
|
|
||||||
|
t.Errorf("Did not fail to create file")
|
||||||
|
}
|
||||||
|
// t.Logf("ERR=%s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFilterRORegexpChain(t *testing.T) {
|
||||||
|
rofs := &ReadOnlyFs{source: &MemMapFs{}}
|
||||||
|
fs := &RegexpFs{re: regexp.MustCompile(`\.txt$`), source: rofs}
|
||||||
|
_, err := fs.Create("/file.txt")
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("Did not fail to create file")
|
||||||
|
}
|
||||||
|
// t.Logf("ERR=%s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFilterRegexReadDir(t *testing.T) {
|
||||||
|
mfs := &MemMapFs{}
|
||||||
|
fs1 := &RegexpFs{re: regexp.MustCompile(`\.txt$`), source: mfs}
|
||||||
|
fs := &RegexpFs{re: regexp.MustCompile(`^a`), source: fs1}
|
||||||
|
|
||||||
|
mfs.MkdirAll("/dir/sub", 0777)
|
||||||
|
for _, name := range []string{"afile.txt", "afile.html", "bfile.txt"} {
|
||||||
|
for _, dir := range []string{"/dir/", "/dir/sub/"} {
|
||||||
|
fh, _ := mfs.Create(dir + name)
|
||||||
|
fh.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
files, _ := ReadDir(fs, "/dir")
|
||||||
|
if len(files) != 2 { // afile.txt, sub
|
||||||
|
t.Errorf("Got wrong number of files: %#v", files)
|
||||||
|
}
|
||||||
|
|
||||||
|
f, _ := fs.Open("/dir/sub")
|
||||||
|
names, _ := f.Readdirnames(-1)
|
||||||
|
if len(names) != 1 {
|
||||||
|
t.Errorf("Got wrong number of names: %v", names)
|
||||||
|
}
|
||||||
|
}
|
274
vendor/github.com/spf13/afero/unionFile.go
generated
vendored
Normal file
274
vendor/github.com/spf13/afero/unionFile.go
generated
vendored
Normal file
|
@ -0,0 +1,274 @@
|
||||||
|
package afero
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The UnionFile implements the afero.File interface and will be returned
|
||||||
|
// when reading a directory present at least in the overlay or opening a file
|
||||||
|
// for writing.
|
||||||
|
//
|
||||||
|
// The calls to
|
||||||
|
// Readdir() and Readdirnames() merge the file os.FileInfo / names from the
|
||||||
|
// base and the overlay - for files present in both layers, only those
|
||||||
|
// from the overlay will be used.
|
||||||
|
//
|
||||||
|
// When opening files for writing (Create() / OpenFile() with the right flags)
|
||||||
|
// the operations will be done in both layers, starting with the overlay. A
|
||||||
|
// successful read in the overlay will move the cursor position in the base layer
|
||||||
|
// by the number of bytes read.
|
||||||
|
type UnionFile struct {
|
||||||
|
base File
|
||||||
|
layer File
|
||||||
|
off int
|
||||||
|
files []os.FileInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *UnionFile) Close() error {
|
||||||
|
// first close base, so we have a newer timestamp in the overlay. If we'd close
|
||||||
|
// the overlay first, we'd get a cacheStale the next time we access this file
|
||||||
|
// -> cache would be useless ;-)
|
||||||
|
if f.base != nil {
|
||||||
|
f.base.Close()
|
||||||
|
}
|
||||||
|
if f.layer != nil {
|
||||||
|
return f.layer.Close()
|
||||||
|
}
|
||||||
|
return BADFD
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *UnionFile) Read(s []byte) (int, error) {
|
||||||
|
if f.layer != nil {
|
||||||
|
n, err := f.layer.Read(s)
|
||||||
|
if (err == nil || err == io.EOF) && f.base != nil {
|
||||||
|
// advance the file position also in the base file, the next
|
||||||
|
// call may be a write at this position (or a seek with SEEK_CUR)
|
||||||
|
if _, seekErr := f.base.Seek(int64(n), os.SEEK_CUR); seekErr != nil {
|
||||||
|
// only overwrite err in case the seek fails: we need to
|
||||||
|
// report an eventual io.EOF to the caller
|
||||||
|
err = seekErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
if f.base != nil {
|
||||||
|
return f.base.Read(s)
|
||||||
|
}
|
||||||
|
return 0, BADFD
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *UnionFile) ReadAt(s []byte, o int64) (int, error) {
|
||||||
|
if f.layer != nil {
|
||||||
|
n, err := f.layer.ReadAt(s, o)
|
||||||
|
if (err == nil || err == io.EOF) && f.base != nil {
|
||||||
|
_, err = f.base.Seek(o+int64(n), os.SEEK_SET)
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
if f.base != nil {
|
||||||
|
return f.base.ReadAt(s, o)
|
||||||
|
}
|
||||||
|
return 0, BADFD
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *UnionFile) Seek(o int64, w int) (pos int64, err error) {
|
||||||
|
if f.layer != nil {
|
||||||
|
pos, err = f.layer.Seek(o, w)
|
||||||
|
if (err == nil || err == io.EOF) && f.base != nil {
|
||||||
|
_, err = f.base.Seek(o, w)
|
||||||
|
}
|
||||||
|
return pos, err
|
||||||
|
}
|
||||||
|
if f.base != nil {
|
||||||
|
return f.base.Seek(o, w)
|
||||||
|
}
|
||||||
|
return 0, BADFD
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *UnionFile) Write(s []byte) (n int, err error) {
|
||||||
|
if f.layer != nil {
|
||||||
|
n, err = f.layer.Write(s)
|
||||||
|
if err == nil && f.base != nil { // hmm, do we have fixed size files where a write may hit the EOF mark?
|
||||||
|
_, err = f.base.Write(s)
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
if f.base != nil {
|
||||||
|
return f.base.Write(s)
|
||||||
|
}
|
||||||
|
return 0, BADFD
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *UnionFile) WriteAt(s []byte, o int64) (n int, err error) {
|
||||||
|
if f.layer != nil {
|
||||||
|
n, err = f.layer.WriteAt(s, o)
|
||||||
|
if err == nil && f.base != nil {
|
||||||
|
_, err = f.base.WriteAt(s, o)
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
if f.base != nil {
|
||||||
|
return f.base.WriteAt(s, o)
|
||||||
|
}
|
||||||
|
return 0, BADFD
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *UnionFile) Name() string {
|
||||||
|
if f.layer != nil {
|
||||||
|
return f.layer.Name()
|
||||||
|
}
|
||||||
|
return f.base.Name()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Readdir will weave the two directories together and
|
||||||
|
// return a single view of the overlayed directories
|
||||||
|
func (f *UnionFile) Readdir(c int) (ofi []os.FileInfo, err error) {
|
||||||
|
if f.off == 0 {
|
||||||
|
var files = make(map[string]os.FileInfo)
|
||||||
|
var rfi []os.FileInfo
|
||||||
|
if f.layer != nil {
|
||||||
|
rfi, err = f.layer.Readdir(-1)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, fi := range rfi {
|
||||||
|
files[fi.Name()] = fi
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.base != nil {
|
||||||
|
rfi, err = f.base.Readdir(-1)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, fi := range rfi {
|
||||||
|
if _, exists := files[fi.Name()]; !exists {
|
||||||
|
files[fi.Name()] = fi
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, fi := range files {
|
||||||
|
f.files = append(f.files, fi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if c == -1 {
|
||||||
|
return f.files[f.off:], nil
|
||||||
|
}
|
||||||
|
defer func() { f.off += c }()
|
||||||
|
return f.files[f.off:c], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *UnionFile) Readdirnames(c int) ([]string, error) {
|
||||||
|
rfi, err := f.Readdir(c)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var names []string
|
||||||
|
for _, fi := range rfi {
|
||||||
|
names = append(names, fi.Name())
|
||||||
|
}
|
||||||
|
return names, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *UnionFile) Stat() (os.FileInfo, error) {
|
||||||
|
if f.layer != nil {
|
||||||
|
return f.layer.Stat()
|
||||||
|
}
|
||||||
|
if f.base != nil {
|
||||||
|
return f.base.Stat()
|
||||||
|
}
|
||||||
|
return nil, BADFD
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *UnionFile) Sync() (err error) {
|
||||||
|
if f.layer != nil {
|
||||||
|
err = f.layer.Sync()
|
||||||
|
if err == nil && f.base != nil {
|
||||||
|
err = f.base.Sync()
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if f.base != nil {
|
||||||
|
return f.base.Sync()
|
||||||
|
}
|
||||||
|
return BADFD
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *UnionFile) Truncate(s int64) (err error) {
|
||||||
|
if f.layer != nil {
|
||||||
|
err = f.layer.Truncate(s)
|
||||||
|
if err == nil && f.base != nil {
|
||||||
|
err = f.base.Truncate(s)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if f.base != nil {
|
||||||
|
return f.base.Truncate(s)
|
||||||
|
}
|
||||||
|
return BADFD
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *UnionFile) WriteString(s string) (n int, err error) {
|
||||||
|
if f.layer != nil {
|
||||||
|
n, err = f.layer.WriteString(s)
|
||||||
|
if err == nil && f.base != nil {
|
||||||
|
_, err = f.base.WriteString(s)
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
if f.base != nil {
|
||||||
|
return f.base.WriteString(s)
|
||||||
|
}
|
||||||
|
return 0, BADFD
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyToLayer(base Fs, layer Fs, name string) error {
|
||||||
|
bfh, err := base.Open(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer bfh.Close()
|
||||||
|
|
||||||
|
// First make sure the directory exists
|
||||||
|
exists, err := Exists(layer, filepath.Dir(name))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !exists {
|
||||||
|
err = layer.MkdirAll(filepath.Dir(name), 0777) // FIXME?
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the file on the overlay
|
||||||
|
lfh, err := layer.Create(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
n, err := io.Copy(lfh, bfh)
|
||||||
|
if err != nil {
|
||||||
|
// If anything fails, clean up the file
|
||||||
|
layer.Remove(name)
|
||||||
|
lfh.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
bfi, err := bfh.Stat()
|
||||||
|
if err != nil || bfi.Size() != n {
|
||||||
|
layer.Remove(name)
|
||||||
|
lfh.Close()
|
||||||
|
return syscall.EIO
|
||||||
|
}
|
||||||
|
|
||||||
|
err = lfh.Close()
|
||||||
|
if err != nil {
|
||||||
|
layer.Remove(name)
|
||||||
|
lfh.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return layer.Chtimes(name, bfi.ModTime(), bfi.ModTime())
|
||||||
|
}
|
331
vendor/github.com/spf13/afero/util.go
generated
vendored
Normal file
331
vendor/github.com/spf13/afero/util.go
generated
vendored
Normal file
|
@ -0,0 +1,331 @@
|
||||||
|
// Copyright ©2015 Steve Francia <spf@spf13.com>
|
||||||
|
// Portions Copyright ©2015 The Hugo Authors
|
||||||
|
// Portions Copyright 2016-present Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package afero
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
|
"golang.org/x/text/transform"
|
||||||
|
"golang.org/x/text/unicode/norm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Filepath separator defined by os.Separator.
|
||||||
|
const FilePathSeparator = string(filepath.Separator)
|
||||||
|
|
||||||
|
// Takes a reader and a path and writes the content
|
||||||
|
func (a Afero) WriteReader(path string, r io.Reader) (err error) {
|
||||||
|
return WriteReader(a.Fs, path, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func WriteReader(fs Fs, path string, r io.Reader) (err error) {
|
||||||
|
dir, _ := filepath.Split(path)
|
||||||
|
ospath := filepath.FromSlash(dir)
|
||||||
|
|
||||||
|
if ospath != "" {
|
||||||
|
err = fs.MkdirAll(ospath, 0777) // rwx, rw, r
|
||||||
|
if err != nil {
|
||||||
|
if err != os.ErrExist {
|
||||||
|
log.Panicln(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := fs.Create(path)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
_, err = io.Copy(file, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Same as WriteReader but checks to see if file/directory already exists.
|
||||||
|
func (a Afero) SafeWriteReader(path string, r io.Reader) (err error) {
|
||||||
|
return SafeWriteReader(a.Fs, path, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func SafeWriteReader(fs Fs, path string, r io.Reader) (err error) {
|
||||||
|
dir, _ := filepath.Split(path)
|
||||||
|
ospath := filepath.FromSlash(dir)
|
||||||
|
|
||||||
|
if ospath != "" {
|
||||||
|
err = fs.MkdirAll(ospath, 0777) // rwx, rw, r
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exists, err := Exists(fs, path)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if exists {
|
||||||
|
return fmt.Errorf("%v already exists", path)
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := fs.Create(path)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
_, err = io.Copy(file, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Afero) GetTempDir(subPath string) string {
|
||||||
|
return GetTempDir(a.Fs, subPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTempDir returns the default temp directory with trailing slash
|
||||||
|
// if subPath is not empty then it will be created recursively with mode 777 rwx rwx rwx
|
||||||
|
func GetTempDir(fs Fs, subPath string) string {
|
||||||
|
addSlash := func(p string) string {
|
||||||
|
if FilePathSeparator != p[len(p)-1:] {
|
||||||
|
p = p + FilePathSeparator
|
||||||
|
}
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
dir := addSlash(os.TempDir())
|
||||||
|
|
||||||
|
if subPath != "" {
|
||||||
|
// preserve windows backslash :-(
|
||||||
|
if FilePathSeparator == "\\" {
|
||||||
|
subPath = strings.Replace(subPath, "\\", "____", -1)
|
||||||
|
}
|
||||||
|
dir = dir + UnicodeSanitize((subPath))
|
||||||
|
if FilePathSeparator == "\\" {
|
||||||
|
dir = strings.Replace(dir, "____", "\\", -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if exists, _ := Exists(fs, dir); exists {
|
||||||
|
return addSlash(dir)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := fs.MkdirAll(dir, 0777)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
dir = addSlash(dir)
|
||||||
|
}
|
||||||
|
return dir
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rewrite string to remove non-standard path characters
|
||||||
|
func UnicodeSanitize(s string) string {
|
||||||
|
source := []rune(s)
|
||||||
|
target := make([]rune, 0, len(source))
|
||||||
|
|
||||||
|
for _, r := range source {
|
||||||
|
if unicode.IsLetter(r) ||
|
||||||
|
unicode.IsDigit(r) ||
|
||||||
|
unicode.IsMark(r) ||
|
||||||
|
r == '.' ||
|
||||||
|
r == '/' ||
|
||||||
|
r == '\\' ||
|
||||||
|
r == '_' ||
|
||||||
|
r == '-' ||
|
||||||
|
r == '%' ||
|
||||||
|
r == ' ' ||
|
||||||
|
r == '#' {
|
||||||
|
target = append(target, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(target)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transform characters with accents into plain forms.
|
||||||
|
func NeuterAccents(s string) string {
|
||||||
|
t := transform.Chain(norm.NFD, transform.RemoveFunc(isMn), norm.NFC)
|
||||||
|
result, _, _ := transform.String(t, string(s))
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func isMn(r rune) bool {
|
||||||
|
return unicode.Is(unicode.Mn, r) // Mn: nonspacing marks
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Afero) FileContainsBytes(filename string, subslice []byte) (bool, error) {
|
||||||
|
return FileContainsBytes(a.Fs, filename, subslice)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if a file contains a specified byte slice.
|
||||||
|
func FileContainsBytes(fs Fs, filename string, subslice []byte) (bool, error) {
|
||||||
|
f, err := fs.Open(filename)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
return readerContainsAny(f, subslice), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Afero) FileContainsAnyBytes(filename string, subslices [][]byte) (bool, error) {
|
||||||
|
return FileContainsAnyBytes(a.Fs, filename, subslices)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if a file contains any of the specified byte slices.
|
||||||
|
func FileContainsAnyBytes(fs Fs, filename string, subslices [][]byte) (bool, error) {
|
||||||
|
f, err := fs.Open(filename)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
return readerContainsAny(f, subslices...), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// readerContains reports whether any of the subslices is within r.
|
||||||
|
func readerContainsAny(r io.Reader, subslices ...[]byte) bool {
|
||||||
|
|
||||||
|
if r == nil || len(subslices) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
largestSlice := 0
|
||||||
|
|
||||||
|
for _, sl := range subslices {
|
||||||
|
if len(sl) > largestSlice {
|
||||||
|
largestSlice = len(sl)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if largestSlice == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
bufflen := largestSlice * 4
|
||||||
|
halflen := bufflen / 2
|
||||||
|
buff := make([]byte, bufflen)
|
||||||
|
var err error
|
||||||
|
var n, i int
|
||||||
|
|
||||||
|
for {
|
||||||
|
i++
|
||||||
|
if i == 1 {
|
||||||
|
n, err = io.ReadAtLeast(r, buff[:halflen], halflen)
|
||||||
|
} else {
|
||||||
|
if i != 2 {
|
||||||
|
// shift left to catch overlapping matches
|
||||||
|
copy(buff[:], buff[halflen:])
|
||||||
|
}
|
||||||
|
n, err = io.ReadAtLeast(r, buff[halflen:], halflen)
|
||||||
|
}
|
||||||
|
|
||||||
|
if n > 0 {
|
||||||
|
for _, sl := range subslices {
|
||||||
|
if bytes.Contains(buff, sl) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Afero) DirExists(path string) (bool, error) {
|
||||||
|
return DirExists(a.Fs, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DirExists checks if a path exists and is a directory.
|
||||||
|
func DirExists(fs Fs, path string) (bool, error) {
|
||||||
|
fi, err := fs.Stat(path)
|
||||||
|
if err == nil && fi.IsDir() {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Afero) IsDir(path string) (bool, error) {
|
||||||
|
return IsDir(a.Fs, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsDir checks if a given path is a directory.
|
||||||
|
func IsDir(fs Fs, path string) (bool, error) {
|
||||||
|
fi, err := fs.Stat(path)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return fi.IsDir(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Afero) IsEmpty(path string) (bool, error) {
|
||||||
|
return IsEmpty(a.Fs, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsEmpty checks if a given file or directory is empty.
|
||||||
|
func IsEmpty(fs Fs, path string) (bool, error) {
|
||||||
|
if b, _ := Exists(fs, path); !b {
|
||||||
|
return false, fmt.Errorf("%q path does not exist", path)
|
||||||
|
}
|
||||||
|
fi, err := fs.Stat(path)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if fi.IsDir() {
|
||||||
|
f, err := fs.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
list, err := f.Readdir(-1)
|
||||||
|
return len(list) == 0, nil
|
||||||
|
}
|
||||||
|
return fi.Size() == 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Afero) Exists(path string) (bool, error) {
|
||||||
|
return Exists(a.Fs, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if a file or directory exists.
|
||||||
|
func Exists(fs Fs, path string) (bool, error) {
|
||||||
|
_, err := fs.Stat(path)
|
||||||
|
if err == nil {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func FullBaseFsPath(basePathFs *BasePathFs, relativePath string) string {
|
||||||
|
combinedPath := filepath.Join(basePathFs.path, relativePath)
|
||||||
|
if parent, ok := basePathFs.source.(*BasePathFs); ok {
|
||||||
|
return FullBaseFsPath(parent, combinedPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
return combinedPath
|
||||||
|
}
|
450
vendor/github.com/spf13/afero/util_test.go
generated
vendored
Normal file
450
vendor/github.com/spf13/afero/util_test.go
generated
vendored
Normal file
|
@ -0,0 +1,450 @@
|
||||||
|
// Copyright ©2015 Steve Francia <spf@spf13.com>
|
||||||
|
// Portions Copyright ©2015 The Hugo Authors
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
//
|
||||||
|
|
||||||
|
package afero
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var testFS = new(MemMapFs)
|
||||||
|
|
||||||
|
func TestDirExists(t *testing.T) {
|
||||||
|
type test struct {
|
||||||
|
input string
|
||||||
|
expected bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// First create a couple directories so there is something in the filesystem
|
||||||
|
//testFS := new(MemMapFs)
|
||||||
|
testFS.MkdirAll("/foo/bar", 0777)
|
||||||
|
|
||||||
|
data := []test{
|
||||||
|
{".", true},
|
||||||
|
{"./", true},
|
||||||
|
{"..", true},
|
||||||
|
{"../", true},
|
||||||
|
{"./..", true},
|
||||||
|
{"./../", true},
|
||||||
|
{"/foo/", true},
|
||||||
|
{"/foo", true},
|
||||||
|
{"/foo/bar", true},
|
||||||
|
{"/foo/bar/", true},
|
||||||
|
{"/", true},
|
||||||
|
{"/some-really-random-directory-name", false},
|
||||||
|
{"/some/really/random/directory/name", false},
|
||||||
|
{"./some-really-random-local-directory-name", false},
|
||||||
|
{"./some/really/random/local/directory/name", false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, d := range data {
|
||||||
|
exists, _ := DirExists(testFS, filepath.FromSlash(d.input))
|
||||||
|
if d.expected != exists {
|
||||||
|
t.Errorf("Test %d %q failed. Expected %t got %t", i, d.input, d.expected, exists)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsDir(t *testing.T) {
|
||||||
|
testFS = new(MemMapFs)
|
||||||
|
|
||||||
|
type test struct {
|
||||||
|
input string
|
||||||
|
expected bool
|
||||||
|
}
|
||||||
|
data := []test{
|
||||||
|
{"./", true},
|
||||||
|
{"/", true},
|
||||||
|
{"./this-directory-does-not-existi", false},
|
||||||
|
{"/this-absolute-directory/does-not-exist", false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, d := range data {
|
||||||
|
|
||||||
|
exists, _ := IsDir(testFS, d.input)
|
||||||
|
if d.expected != exists {
|
||||||
|
t.Errorf("Test %d failed. Expected %t got %t", i, d.expected, exists)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsEmpty(t *testing.T) {
|
||||||
|
testFS = new(MemMapFs)
|
||||||
|
|
||||||
|
zeroSizedFile, _ := createZeroSizedFileInTempDir()
|
||||||
|
defer deleteFileInTempDir(zeroSizedFile)
|
||||||
|
nonZeroSizedFile, _ := createNonZeroSizedFileInTempDir()
|
||||||
|
defer deleteFileInTempDir(nonZeroSizedFile)
|
||||||
|
emptyDirectory, _ := createEmptyTempDir()
|
||||||
|
defer deleteTempDir(emptyDirectory)
|
||||||
|
nonEmptyZeroLengthFilesDirectory, _ := createTempDirWithZeroLengthFiles()
|
||||||
|
defer deleteTempDir(nonEmptyZeroLengthFilesDirectory)
|
||||||
|
nonEmptyNonZeroLengthFilesDirectory, _ := createTempDirWithNonZeroLengthFiles()
|
||||||
|
defer deleteTempDir(nonEmptyNonZeroLengthFilesDirectory)
|
||||||
|
nonExistentFile := os.TempDir() + "/this-file-does-not-exist.txt"
|
||||||
|
nonExistentDir := os.TempDir() + "/this/direcotry/does/not/exist/"
|
||||||
|
|
||||||
|
fileDoesNotExist := fmt.Errorf("%q path does not exist", nonExistentFile)
|
||||||
|
dirDoesNotExist := fmt.Errorf("%q path does not exist", nonExistentDir)
|
||||||
|
|
||||||
|
type test struct {
|
||||||
|
input string
|
||||||
|
expectedResult bool
|
||||||
|
expectedErr error
|
||||||
|
}
|
||||||
|
|
||||||
|
data := []test{
|
||||||
|
{zeroSizedFile.Name(), true, nil},
|
||||||
|
{nonZeroSizedFile.Name(), false, nil},
|
||||||
|
{emptyDirectory, true, nil},
|
||||||
|
{nonEmptyZeroLengthFilesDirectory, false, nil},
|
||||||
|
{nonEmptyNonZeroLengthFilesDirectory, false, nil},
|
||||||
|
{nonExistentFile, false, fileDoesNotExist},
|
||||||
|
{nonExistentDir, false, dirDoesNotExist},
|
||||||
|
}
|
||||||
|
for i, d := range data {
|
||||||
|
exists, err := IsEmpty(testFS, d.input)
|
||||||
|
if d.expectedResult != exists {
|
||||||
|
t.Errorf("Test %d %q failed exists. Expected result %t got %t", i, d.input, d.expectedResult, exists)
|
||||||
|
}
|
||||||
|
if d.expectedErr != nil {
|
||||||
|
if d.expectedErr.Error() != err.Error() {
|
||||||
|
t.Errorf("Test %d failed with err. Expected %q(%#v) got %q(%#v)", i, d.expectedErr, d.expectedErr, err, err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if d.expectedErr != err {
|
||||||
|
t.Errorf("Test %d failed. Expected error %q(%#v) got %q(%#v)", i, d.expectedErr, d.expectedErr, err, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReaderContains(t *testing.T) {
|
||||||
|
for i, this := range []struct {
|
||||||
|
v1 string
|
||||||
|
v2 [][]byte
|
||||||
|
expect bool
|
||||||
|
}{
|
||||||
|
{"abc", [][]byte{[]byte("a")}, true},
|
||||||
|
{"abc", [][]byte{[]byte("b")}, true},
|
||||||
|
{"abcdefg", [][]byte{[]byte("efg")}, true},
|
||||||
|
{"abc", [][]byte{[]byte("d")}, false},
|
||||||
|
{"abc", [][]byte{[]byte("d"), []byte("e")}, false},
|
||||||
|
{"abc", [][]byte{[]byte("d"), []byte("a")}, true},
|
||||||
|
{"abc", [][]byte{[]byte("b"), []byte("e")}, true},
|
||||||
|
{"", nil, false},
|
||||||
|
{"", [][]byte{[]byte("a")}, false},
|
||||||
|
{"a", [][]byte{[]byte("")}, false},
|
||||||
|
{"", [][]byte{[]byte("")}, false}} {
|
||||||
|
result := readerContainsAny(strings.NewReader(this.v1), this.v2...)
|
||||||
|
if result != this.expect {
|
||||||
|
t.Errorf("[%d] readerContains: got %t but expected %t", i, result, this.expect)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if readerContainsAny(nil, []byte("a")) {
|
||||||
|
t.Error("readerContains with nil reader")
|
||||||
|
}
|
||||||
|
|
||||||
|
if readerContainsAny(nil, nil) {
|
||||||
|
t.Error("readerContains with nil arguments")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func createZeroSizedFileInTempDir() (File, error) {
|
||||||
|
filePrefix := "_path_test_"
|
||||||
|
f, e := TempFile(testFS, "", filePrefix) // dir is os.TempDir()
|
||||||
|
if e != nil {
|
||||||
|
// if there was an error no file was created.
|
||||||
|
// => no requirement to delete the file
|
||||||
|
return nil, e
|
||||||
|
}
|
||||||
|
return f, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createNonZeroSizedFileInTempDir() (File, error) {
|
||||||
|
f, err := createZeroSizedFileInTempDir()
|
||||||
|
if err != nil {
|
||||||
|
// no file ??
|
||||||
|
}
|
||||||
|
byteString := []byte("byteString")
|
||||||
|
err = WriteFile(testFS, f.Name(), byteString, 0644)
|
||||||
|
if err != nil {
|
||||||
|
// delete the file
|
||||||
|
deleteFileInTempDir(f)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return f, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func deleteFileInTempDir(f File) {
|
||||||
|
err := testFS.Remove(f.Name())
|
||||||
|
if err != nil {
|
||||||
|
// now what?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func createEmptyTempDir() (string, error) {
|
||||||
|
dirPrefix := "_dir_prefix_"
|
||||||
|
d, e := TempDir(testFS, "", dirPrefix) // will be in os.TempDir()
|
||||||
|
if e != nil {
|
||||||
|
// no directory to delete - it was never created
|
||||||
|
return "", e
|
||||||
|
}
|
||||||
|
return d, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createTempDirWithZeroLengthFiles() (string, error) {
|
||||||
|
d, dirErr := createEmptyTempDir()
|
||||||
|
if dirErr != nil {
|
||||||
|
//now what?
|
||||||
|
}
|
||||||
|
filePrefix := "_path_test_"
|
||||||
|
_, fileErr := TempFile(testFS, d, filePrefix) // dir is os.TempDir()
|
||||||
|
if fileErr != nil {
|
||||||
|
// if there was an error no file was created.
|
||||||
|
// but we need to remove the directory to clean-up
|
||||||
|
deleteTempDir(d)
|
||||||
|
return "", fileErr
|
||||||
|
}
|
||||||
|
// the dir now has one, zero length file in it
|
||||||
|
return d, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func createTempDirWithNonZeroLengthFiles() (string, error) {
|
||||||
|
d, dirErr := createEmptyTempDir()
|
||||||
|
if dirErr != nil {
|
||||||
|
//now what?
|
||||||
|
}
|
||||||
|
filePrefix := "_path_test_"
|
||||||
|
f, fileErr := TempFile(testFS, d, filePrefix) // dir is os.TempDir()
|
||||||
|
if fileErr != nil {
|
||||||
|
// if there was an error no file was created.
|
||||||
|
// but we need to remove the directory to clean-up
|
||||||
|
deleteTempDir(d)
|
||||||
|
return "", fileErr
|
||||||
|
}
|
||||||
|
byteString := []byte("byteString")
|
||||||
|
fileErr = WriteFile(testFS, f.Name(), byteString, 0644)
|
||||||
|
if fileErr != nil {
|
||||||
|
// delete the file
|
||||||
|
deleteFileInTempDir(f)
|
||||||
|
// also delete the directory
|
||||||
|
deleteTempDir(d)
|
||||||
|
return "", fileErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// the dir now has one, zero length file in it
|
||||||
|
return d, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExists(t *testing.T) {
|
||||||
|
zeroSizedFile, _ := createZeroSizedFileInTempDir()
|
||||||
|
defer deleteFileInTempDir(zeroSizedFile)
|
||||||
|
nonZeroSizedFile, _ := createNonZeroSizedFileInTempDir()
|
||||||
|
defer deleteFileInTempDir(nonZeroSizedFile)
|
||||||
|
emptyDirectory, _ := createEmptyTempDir()
|
||||||
|
defer deleteTempDir(emptyDirectory)
|
||||||
|
nonExistentFile := os.TempDir() + "/this-file-does-not-exist.txt"
|
||||||
|
nonExistentDir := os.TempDir() + "/this/direcotry/does/not/exist/"
|
||||||
|
|
||||||
|
type test struct {
|
||||||
|
input string
|
||||||
|
expectedResult bool
|
||||||
|
expectedErr error
|
||||||
|
}
|
||||||
|
|
||||||
|
data := []test{
|
||||||
|
{zeroSizedFile.Name(), true, nil},
|
||||||
|
{nonZeroSizedFile.Name(), true, nil},
|
||||||
|
{emptyDirectory, true, nil},
|
||||||
|
{nonExistentFile, false, nil},
|
||||||
|
{nonExistentDir, false, nil},
|
||||||
|
}
|
||||||
|
for i, d := range data {
|
||||||
|
exists, err := Exists(testFS, d.input)
|
||||||
|
if d.expectedResult != exists {
|
||||||
|
t.Errorf("Test %d failed. Expected result %t got %t", i, d.expectedResult, exists)
|
||||||
|
}
|
||||||
|
if d.expectedErr != err {
|
||||||
|
t.Errorf("Test %d failed. Expected %q got %q", i, d.expectedErr, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSafeWriteToDisk(t *testing.T) {
|
||||||
|
emptyFile, _ := createZeroSizedFileInTempDir()
|
||||||
|
defer deleteFileInTempDir(emptyFile)
|
||||||
|
tmpDir, _ := createEmptyTempDir()
|
||||||
|
defer deleteTempDir(tmpDir)
|
||||||
|
|
||||||
|
randomString := "This is a random string!"
|
||||||
|
reader := strings.NewReader(randomString)
|
||||||
|
|
||||||
|
fileExists := fmt.Errorf("%v already exists", emptyFile.Name())
|
||||||
|
|
||||||
|
type test struct {
|
||||||
|
filename string
|
||||||
|
expectedErr error
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now().Unix()
|
||||||
|
nowStr := strconv.FormatInt(now, 10)
|
||||||
|
data := []test{
|
||||||
|
{emptyFile.Name(), fileExists},
|
||||||
|
{tmpDir + "/" + nowStr, nil},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, d := range data {
|
||||||
|
e := SafeWriteReader(testFS, d.filename, reader)
|
||||||
|
if d.expectedErr != nil {
|
||||||
|
if d.expectedErr.Error() != e.Error() {
|
||||||
|
t.Errorf("Test %d failed. Expected error %q but got %q", i, d.expectedErr.Error(), e.Error())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if d.expectedErr != e {
|
||||||
|
t.Errorf("Test %d failed. Expected %q but got %q", i, d.expectedErr, e)
|
||||||
|
}
|
||||||
|
contents, _ := ReadFile(testFS, d.filename)
|
||||||
|
if randomString != string(contents) {
|
||||||
|
t.Errorf("Test %d failed. Expected contents %q but got %q", i, randomString, string(contents))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
reader.Seek(0, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWriteToDisk(t *testing.T) {
|
||||||
|
emptyFile, _ := createZeroSizedFileInTempDir()
|
||||||
|
defer deleteFileInTempDir(emptyFile)
|
||||||
|
tmpDir, _ := createEmptyTempDir()
|
||||||
|
defer deleteTempDir(tmpDir)
|
||||||
|
|
||||||
|
randomString := "This is a random string!"
|
||||||
|
reader := strings.NewReader(randomString)
|
||||||
|
|
||||||
|
type test struct {
|
||||||
|
filename string
|
||||||
|
expectedErr error
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now().Unix()
|
||||||
|
nowStr := strconv.FormatInt(now, 10)
|
||||||
|
data := []test{
|
||||||
|
{emptyFile.Name(), nil},
|
||||||
|
{tmpDir + "/" + nowStr, nil},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, d := range data {
|
||||||
|
e := WriteReader(testFS, d.filename, reader)
|
||||||
|
if d.expectedErr != e {
|
||||||
|
t.Errorf("Test %d failed. WriteToDisk Error Expected %q but got %q", i, d.expectedErr, e)
|
||||||
|
}
|
||||||
|
contents, e := ReadFile(testFS, d.filename)
|
||||||
|
if e != nil {
|
||||||
|
t.Errorf("Test %d failed. Could not read file %s. Reason: %s\n", i, d.filename, e)
|
||||||
|
}
|
||||||
|
if randomString != string(contents) {
|
||||||
|
t.Errorf("Test %d failed. Expected contents %q but got %q", i, randomString, string(contents))
|
||||||
|
}
|
||||||
|
reader.Seek(0, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetTempDir(t *testing.T) {
|
||||||
|
dir := os.TempDir()
|
||||||
|
if FilePathSeparator != dir[len(dir)-1:] {
|
||||||
|
dir = dir + FilePathSeparator
|
||||||
|
}
|
||||||
|
testDir := "hugoTestFolder" + FilePathSeparator
|
||||||
|
tests := []struct {
|
||||||
|
input string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{"", dir},
|
||||||
|
{testDir + " Foo bar ", dir + testDir + " Foo bar " + FilePathSeparator},
|
||||||
|
{testDir + "Foo.Bar/foo_Bar-Foo", dir + testDir + "Foo.Bar/foo_Bar-Foo" + FilePathSeparator},
|
||||||
|
{testDir + "fOO,bar:foo%bAR", dir + testDir + "fOObarfoo%bAR" + FilePathSeparator},
|
||||||
|
{testDir + "FOo/BaR.html", dir + testDir + "FOo/BaR.html" + FilePathSeparator},
|
||||||
|
{testDir + "трям/трям", dir + testDir + "трям/трям" + FilePathSeparator},
|
||||||
|
{testDir + "은행", dir + testDir + "은행" + FilePathSeparator},
|
||||||
|
{testDir + "Банковский кассир", dir + testDir + "Банковский кассир" + FilePathSeparator},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
output := GetTempDir(new(MemMapFs), test.input)
|
||||||
|
if output != test.expected {
|
||||||
|
t.Errorf("Expected %#v, got %#v\n", test.expected, output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function is very dangerous. Don't use it.
|
||||||
|
func deleteTempDir(d string) {
|
||||||
|
err := os.RemoveAll(d)
|
||||||
|
if err != nil {
|
||||||
|
// now what?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFullBaseFsPath(t *testing.T) {
|
||||||
|
type dirSpec struct {
|
||||||
|
Dir1, Dir2, Dir3 string
|
||||||
|
}
|
||||||
|
dirSpecs := []dirSpec{
|
||||||
|
dirSpec{Dir1: "/", Dir2: "/", Dir3: "/"},
|
||||||
|
dirSpec{Dir1: "/", Dir2: "/path2", Dir3: "/"},
|
||||||
|
dirSpec{Dir1: "/path1/dir", Dir2: "/path2/dir/", Dir3: "/path3/dir"},
|
||||||
|
dirSpec{Dir1: "C:/path1", Dir2: "path2/dir", Dir3: "/path3/dir/"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ds := range dirSpecs {
|
||||||
|
memFs := NewMemMapFs()
|
||||||
|
level1Fs := NewBasePathFs(memFs, ds.Dir1)
|
||||||
|
level2Fs := NewBasePathFs(level1Fs, ds.Dir2)
|
||||||
|
level3Fs := NewBasePathFs(level2Fs, ds.Dir3)
|
||||||
|
|
||||||
|
type spec struct {
|
||||||
|
BaseFs Fs
|
||||||
|
FileName string
|
||||||
|
ExpectedPath string
|
||||||
|
}
|
||||||
|
specs := []spec{
|
||||||
|
spec{BaseFs: level3Fs, FileName: "f.txt", ExpectedPath: filepath.Join(ds.Dir1, ds.Dir2, ds.Dir3, "f.txt")},
|
||||||
|
spec{BaseFs: level3Fs, FileName: "", ExpectedPath: filepath.Join(ds.Dir1, ds.Dir2, ds.Dir3, "")},
|
||||||
|
spec{BaseFs: level2Fs, FileName: "f.txt", ExpectedPath: filepath.Join(ds.Dir1, ds.Dir2, "f.txt")},
|
||||||
|
spec{BaseFs: level2Fs, FileName: "", ExpectedPath: filepath.Join(ds.Dir1, ds.Dir2, "")},
|
||||||
|
spec{BaseFs: level1Fs, FileName: "f.txt", ExpectedPath: filepath.Join(ds.Dir1, "f.txt")},
|
||||||
|
spec{BaseFs: level1Fs, FileName: "", ExpectedPath: filepath.Join(ds.Dir1, "")},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, s := range specs {
|
||||||
|
if actualPath := FullBaseFsPath(s.BaseFs.(*BasePathFs), s.FileName); actualPath != s.ExpectedPath {
|
||||||
|
t.Errorf("Expected \n%s got \n%s", s.ExpectedPath, actualPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
46
vendor/k8s.io/client-go/tools/cache/testing/BUILD
generated
vendored
46
vendor/k8s.io/client-go/tools/cache/testing/BUILD
generated
vendored
|
@ -1,46 +0,0 @@
|
||||||
package(default_visibility = ["//visibility:public"])
|
|
||||||
|
|
||||||
load(
|
|
||||||
"@io_bazel_rules_go//go:def.bzl",
|
|
||||||
"go_library",
|
|
||||||
"go_test",
|
|
||||||
)
|
|
||||||
|
|
||||||
go_test(
|
|
||||||
name = "go_default_test",
|
|
||||||
srcs = ["fake_controller_source_test.go"],
|
|
||||||
importpath = "k8s.io/client-go/tools/cache/testing",
|
|
||||||
library = ":go_default_library",
|
|
||||||
deps = [
|
|
||||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
|
||||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
|
||||||
"//vendor/k8s.io/apimachinery/pkg/watch:go_default_library",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
go_library(
|
|
||||||
name = "go_default_library",
|
|
||||||
srcs = ["fake_controller_source.go"],
|
|
||||||
importpath = "k8s.io/client-go/tools/cache/testing",
|
|
||||||
deps = [
|
|
||||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
|
||||||
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
|
|
||||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
|
||||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
|
||||||
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
|
|
||||||
"//vendor/k8s.io/apimachinery/pkg/watch:go_default_library",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
filegroup(
|
|
||||||
name = "package-srcs",
|
|
||||||
srcs = glob(["**"]),
|
|
||||||
tags = ["automanaged"],
|
|
||||||
visibility = ["//visibility:private"],
|
|
||||||
)
|
|
||||||
|
|
||||||
filegroup(
|
|
||||||
name = "all-srcs",
|
|
||||||
srcs = [":package-srcs"],
|
|
||||||
tags = ["automanaged"],
|
|
||||||
)
|
|
255
vendor/k8s.io/client-go/tools/cache/testing/fake_controller_source.go
generated
vendored
255
vendor/k8s.io/client-go/tools/cache/testing/fake_controller_source.go
generated
vendored
|
@ -1,255 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2015 The Kubernetes Authors.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package framework
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"math/rand"
|
|
||||||
"strconv"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"k8s.io/api/core/v1"
|
|
||||||
"k8s.io/apimachinery/pkg/api/meta"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
|
||||||
"k8s.io/apimachinery/pkg/types"
|
|
||||||
"k8s.io/apimachinery/pkg/watch"
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewFakeControllerSource() *FakeControllerSource {
|
|
||||||
return &FakeControllerSource{
|
|
||||||
Items: map[nnu]runtime.Object{},
|
|
||||||
Broadcaster: watch.NewBroadcaster(100, watch.WaitIfChannelFull),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewFakePVControllerSource() *FakePVControllerSource {
|
|
||||||
return &FakePVControllerSource{
|
|
||||||
FakeControllerSource{
|
|
||||||
Items: map[nnu]runtime.Object{},
|
|
||||||
Broadcaster: watch.NewBroadcaster(100, watch.WaitIfChannelFull),
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewFakePVCControllerSource() *FakePVCControllerSource {
|
|
||||||
return &FakePVCControllerSource{
|
|
||||||
FakeControllerSource{
|
|
||||||
Items: map[nnu]runtime.Object{},
|
|
||||||
Broadcaster: watch.NewBroadcaster(100, watch.WaitIfChannelFull),
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
|
|
||||||
// FakeControllerSource implements listing/watching for testing.
|
|
||||||
type FakeControllerSource struct {
|
|
||||||
lock sync.RWMutex
|
|
||||||
Items map[nnu]runtime.Object
|
|
||||||
changes []watch.Event // one change per resourceVersion
|
|
||||||
Broadcaster *watch.Broadcaster
|
|
||||||
}
|
|
||||||
|
|
||||||
type FakePVControllerSource struct {
|
|
||||||
FakeControllerSource
|
|
||||||
}
|
|
||||||
|
|
||||||
type FakePVCControllerSource struct {
|
|
||||||
FakeControllerSource
|
|
||||||
}
|
|
||||||
|
|
||||||
// namespace, name, uid to be used as a key.
|
|
||||||
type nnu struct {
|
|
||||||
namespace, name string
|
|
||||||
uid types.UID
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add adds an object to the set and sends an add event to watchers.
|
|
||||||
// obj's ResourceVersion is set.
|
|
||||||
func (f *FakeControllerSource) Add(obj runtime.Object) {
|
|
||||||
f.Change(watch.Event{Type: watch.Added, Object: obj}, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Modify updates an object in the set and sends a modified event to watchers.
|
|
||||||
// obj's ResourceVersion is set.
|
|
||||||
func (f *FakeControllerSource) Modify(obj runtime.Object) {
|
|
||||||
f.Change(watch.Event{Type: watch.Modified, Object: obj}, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete deletes an object from the set and sends a delete event to watchers.
|
|
||||||
// obj's ResourceVersion is set.
|
|
||||||
func (f *FakeControllerSource) Delete(lastValue runtime.Object) {
|
|
||||||
f.Change(watch.Event{Type: watch.Deleted, Object: lastValue}, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddDropWatch adds an object to the set but forgets to send an add event to
|
|
||||||
// watchers.
|
|
||||||
// obj's ResourceVersion is set.
|
|
||||||
func (f *FakeControllerSource) AddDropWatch(obj runtime.Object) {
|
|
||||||
f.Change(watch.Event{Type: watch.Added, Object: obj}, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ModifyDropWatch updates an object in the set but forgets to send a modify
|
|
||||||
// event to watchers.
|
|
||||||
// obj's ResourceVersion is set.
|
|
||||||
func (f *FakeControllerSource) ModifyDropWatch(obj runtime.Object) {
|
|
||||||
f.Change(watch.Event{Type: watch.Modified, Object: obj}, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteDropWatch deletes an object from the set but forgets to send a delete
|
|
||||||
// event to watchers.
|
|
||||||
// obj's ResourceVersion is set.
|
|
||||||
func (f *FakeControllerSource) DeleteDropWatch(lastValue runtime.Object) {
|
|
||||||
f.Change(watch.Event{Type: watch.Deleted, Object: lastValue}, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FakeControllerSource) key(accessor metav1.Object) nnu {
|
|
||||||
return nnu{accessor.GetNamespace(), accessor.GetName(), accessor.GetUID()}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Change records the given event (setting the object's resource version) and
|
|
||||||
// sends a watch event with the specified probability.
|
|
||||||
func (f *FakeControllerSource) Change(e watch.Event, watchProbability float64) {
|
|
||||||
f.lock.Lock()
|
|
||||||
defer f.lock.Unlock()
|
|
||||||
|
|
||||||
accessor, err := meta.Accessor(e.Object)
|
|
||||||
if err != nil {
|
|
||||||
panic(err) // this is test code only
|
|
||||||
}
|
|
||||||
|
|
||||||
resourceVersion := len(f.changes) + 1
|
|
||||||
accessor.SetResourceVersion(strconv.Itoa(resourceVersion))
|
|
||||||
f.changes = append(f.changes, e)
|
|
||||||
key := f.key(accessor)
|
|
||||||
switch e.Type {
|
|
||||||
case watch.Added, watch.Modified:
|
|
||||||
f.Items[key] = e.Object
|
|
||||||
case watch.Deleted:
|
|
||||||
delete(f.Items, key)
|
|
||||||
}
|
|
||||||
|
|
||||||
if rand.Float64() < watchProbability {
|
|
||||||
f.Broadcaster.Action(e.Type, e.Object)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FakeControllerSource) getListItemsLocked() ([]runtime.Object, error) {
|
|
||||||
list := make([]runtime.Object, 0, len(f.Items))
|
|
||||||
for _, obj := range f.Items {
|
|
||||||
// Must make a copy to allow clients to modify the object.
|
|
||||||
// Otherwise, if they make a change and write it back, they
|
|
||||||
// will inadvertently change our canonical copy (in
|
|
||||||
// addition to racing with other clients).
|
|
||||||
list = append(list, obj.DeepCopyObject())
|
|
||||||
}
|
|
||||||
return list, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// List returns a list object, with its resource version set.
|
|
||||||
func (f *FakeControllerSource) List(options metav1.ListOptions) (runtime.Object, error) {
|
|
||||||
f.lock.RLock()
|
|
||||||
defer f.lock.RUnlock()
|
|
||||||
list, err := f.getListItemsLocked()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
listObj := &v1.List{}
|
|
||||||
if err := meta.SetList(listObj, list); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
listAccessor, err := meta.ListAccessor(listObj)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
resourceVersion := len(f.changes)
|
|
||||||
listAccessor.SetResourceVersion(strconv.Itoa(resourceVersion))
|
|
||||||
return listObj, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// List returns a list object, with its resource version set.
|
|
||||||
func (f *FakePVControllerSource) List(options metav1.ListOptions) (runtime.Object, error) {
|
|
||||||
f.lock.RLock()
|
|
||||||
defer f.lock.RUnlock()
|
|
||||||
list, err := f.FakeControllerSource.getListItemsLocked()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
listObj := &v1.PersistentVolumeList{}
|
|
||||||
if err := meta.SetList(listObj, list); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
listAccessor, err := meta.ListAccessor(listObj)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
resourceVersion := len(f.changes)
|
|
||||||
listAccessor.SetResourceVersion(strconv.Itoa(resourceVersion))
|
|
||||||
return listObj, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// List returns a list object, with its resource version set.
|
|
||||||
func (f *FakePVCControllerSource) List(options metav1.ListOptions) (runtime.Object, error) {
|
|
||||||
f.lock.RLock()
|
|
||||||
defer f.lock.RUnlock()
|
|
||||||
list, err := f.FakeControllerSource.getListItemsLocked()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
listObj := &v1.PersistentVolumeClaimList{}
|
|
||||||
if err := meta.SetList(listObj, list); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
listAccessor, err := meta.ListAccessor(listObj)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
resourceVersion := len(f.changes)
|
|
||||||
listAccessor.SetResourceVersion(strconv.Itoa(resourceVersion))
|
|
||||||
return listObj, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Watch returns a watch, which will be pre-populated with all changes
|
|
||||||
// after resourceVersion.
|
|
||||||
func (f *FakeControllerSource) Watch(options metav1.ListOptions) (watch.Interface, error) {
|
|
||||||
f.lock.RLock()
|
|
||||||
defer f.lock.RUnlock()
|
|
||||||
rc, err := strconv.Atoi(options.ResourceVersion)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if rc < len(f.changes) {
|
|
||||||
changes := []watch.Event{}
|
|
||||||
for _, c := range f.changes[rc:] {
|
|
||||||
// Must make a copy to allow clients to modify the
|
|
||||||
// object. Otherwise, if they make a change and write
|
|
||||||
// it back, they will inadvertently change the our
|
|
||||||
// canonical copy (in addition to racing with other
|
|
||||||
// clients).
|
|
||||||
changes = append(changes, watch.Event{Type: c.Type, Object: c.Object.DeepCopyObject()})
|
|
||||||
}
|
|
||||||
return f.Broadcaster.WatchWithPrefix(changes), nil
|
|
||||||
} else if rc > len(f.changes) {
|
|
||||||
return nil, errors.New("resource version in the future not supported by this fake")
|
|
||||||
}
|
|
||||||
return f.Broadcaster.Watch(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shutdown closes the underlying broadcaster, waiting for events to be
|
|
||||||
// delivered. It's an error to call any method after calling shutdown. This is
|
|
||||||
// enforced by Shutdown() leaving f locked.
|
|
||||||
func (f *FakeControllerSource) Shutdown() {
|
|
||||||
f.lock.Lock() // Purposely no unlock.
|
|
||||||
f.Broadcaster.Shutdown()
|
|
||||||
}
|
|
95
vendor/k8s.io/client-go/tools/cache/testing/fake_controller_source_test.go
generated
vendored
95
vendor/k8s.io/client-go/tools/cache/testing/fake_controller_source_test.go
generated
vendored
|
@ -1,95 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2015 The Kubernetes Authors.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package framework
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sync"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"k8s.io/api/core/v1"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
"k8s.io/apimachinery/pkg/watch"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ensure the watch delivers the requested and only the requested items.
|
|
||||||
func consume(t *testing.T, w watch.Interface, rvs []string, done *sync.WaitGroup) {
|
|
||||||
defer done.Done()
|
|
||||||
for _, rv := range rvs {
|
|
||||||
got, ok := <-w.ResultChan()
|
|
||||||
if !ok {
|
|
||||||
t.Errorf("%#v: unexpected channel close, wanted %v", rvs, rv)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
gotRV := got.Object.(*v1.Pod).ObjectMeta.ResourceVersion
|
|
||||||
if e, a := rv, gotRV; e != a {
|
|
||||||
t.Errorf("wanted %v, got %v", e, a)
|
|
||||||
} else {
|
|
||||||
t.Logf("Got %v as expected", gotRV)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// We should not get anything else.
|
|
||||||
got, open := <-w.ResultChan()
|
|
||||||
if open {
|
|
||||||
t.Errorf("%#v: unwanted object %#v", rvs, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRCNumber(t *testing.T) {
|
|
||||||
pod := func(name string) *v1.Pod {
|
|
||||||
return &v1.Pod{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: name,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
wg := &sync.WaitGroup{}
|
|
||||||
wg.Add(3)
|
|
||||||
|
|
||||||
source := NewFakeControllerSource()
|
|
||||||
source.Add(pod("foo"))
|
|
||||||
source.Modify(pod("foo"))
|
|
||||||
source.Modify(pod("foo"))
|
|
||||||
|
|
||||||
w, err := source.Watch(metav1.ListOptions{ResourceVersion: "1"})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
go consume(t, w, []string{"2", "3"}, wg)
|
|
||||||
|
|
||||||
list, err := source.List(metav1.ListOptions{})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
if e, a := "3", list.(*v1.List).ResourceVersion; e != a {
|
|
||||||
t.Errorf("wanted %v, got %v", e, a)
|
|
||||||
}
|
|
||||||
|
|
||||||
w2, err := source.Watch(metav1.ListOptions{ResourceVersion: "2"})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
go consume(t, w2, []string{"3"}, wg)
|
|
||||||
|
|
||||||
w3, err := source.Watch(metav1.ListOptions{ResourceVersion: "3"})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
go consume(t, w3, []string{}, wg)
|
|
||||||
source.Shutdown()
|
|
||||||
wg.Wait()
|
|
||||||
}
|
|
34
vendor/k8s.io/kubernetes/pkg/util/filesystem/BUILD
generated
vendored
Normal file
34
vendor/k8s.io/kubernetes/pkg/util/filesystem/BUILD
generated
vendored
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
package(default_visibility = ["//visibility:public"])
|
||||||
|
|
||||||
|
load(
|
||||||
|
"@io_bazel_rules_go//go:def.bzl",
|
||||||
|
"go_library",
|
||||||
|
)
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "go_default_library",
|
||||||
|
srcs = [
|
||||||
|
"defaultfs.go",
|
||||||
|
"fakefs.go",
|
||||||
|
"filesystem.go",
|
||||||
|
"watcher.go",
|
||||||
|
],
|
||||||
|
importpath = "k8s.io/kubernetes/pkg/util/filesystem",
|
||||||
|
deps = [
|
||||||
|
"//vendor/github.com/fsnotify/fsnotify:go_default_library",
|
||||||
|
"//vendor/github.com/spf13/afero:go_default_library",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "package-srcs",
|
||||||
|
srcs = glob(["**"]),
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "all-srcs",
|
||||||
|
srcs = [":package-srcs"],
|
||||||
|
tags = ["automanaged"],
|
||||||
|
)
|
117
vendor/k8s.io/kubernetes/pkg/util/filesystem/defaultfs.go
generated
vendored
Normal file
117
vendor/k8s.io/kubernetes/pkg/util/filesystem/defaultfs.go
generated
vendored
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package filesystem
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DefaultFs implements Filesystem using same-named functions from "os" and "io/ioutil"
|
||||||
|
type DefaultFs struct{}
|
||||||
|
|
||||||
|
var _ Filesystem = DefaultFs{}
|
||||||
|
|
||||||
|
// Stat via os.Stat
|
||||||
|
func (DefaultFs) Stat(name string) (os.FileInfo, error) {
|
||||||
|
return os.Stat(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create via os.Create
|
||||||
|
func (DefaultFs) Create(name string) (File, error) {
|
||||||
|
file, err := os.Create(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &defaultFile{file}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rename via os.Rename
|
||||||
|
func (DefaultFs) Rename(oldpath, newpath string) error {
|
||||||
|
return os.Rename(oldpath, newpath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MkdirAll via os.MkdirAll
|
||||||
|
func (DefaultFs) MkdirAll(path string, perm os.FileMode) error {
|
||||||
|
return os.MkdirAll(path, perm)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chtimes via os.Chtimes
|
||||||
|
func (DefaultFs) Chtimes(name string, atime time.Time, mtime time.Time) error {
|
||||||
|
return os.Chtimes(name, atime, mtime)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveAll via os.RemoveAll
|
||||||
|
func (DefaultFs) RemoveAll(path string) error {
|
||||||
|
return os.RemoveAll(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove via os.RemoveAll
|
||||||
|
func (DefaultFs) Remove(name string) error {
|
||||||
|
return os.Remove(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadFile via ioutil.ReadFile
|
||||||
|
func (DefaultFs) ReadFile(filename string) ([]byte, error) {
|
||||||
|
return ioutil.ReadFile(filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TempFile via ioutil.TempFile
|
||||||
|
func (DefaultFs) TempFile(dir, prefix string) (File, error) {
|
||||||
|
file, err := ioutil.TempFile(dir, prefix)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &defaultFile{file}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadDir via ioutil.ReadDir
|
||||||
|
func (DefaultFs) ReadDir(dirname string) ([]os.FileInfo, error) {
|
||||||
|
return ioutil.ReadDir(dirname)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Walk via filepath.Walk
|
||||||
|
func (DefaultFs) Walk(root string, walkFn filepath.WalkFunc) error {
|
||||||
|
return filepath.Walk(root, walkFn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// defaultFile implements File using same-named functions from "os"
|
||||||
|
type defaultFile struct {
|
||||||
|
file *os.File
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name via os.File.Name
|
||||||
|
func (file *defaultFile) Name() string {
|
||||||
|
return file.file.Name()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write via os.File.Write
|
||||||
|
func (file *defaultFile) Write(b []byte) (n int, err error) {
|
||||||
|
return file.file.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sync via os.File.Sync
|
||||||
|
func (file *defaultFile) Sync() error {
|
||||||
|
return file.file.Sync()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close via os.File.Close
|
||||||
|
func (file *defaultFile) Close() error {
|
||||||
|
return file.file.Close()
|
||||||
|
}
|
123
vendor/k8s.io/kubernetes/pkg/util/filesystem/fakefs.go
generated
vendored
Normal file
123
vendor/k8s.io/kubernetes/pkg/util/filesystem/fakefs.go
generated
vendored
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package filesystem
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/spf13/afero"
|
||||||
|
)
|
||||||
|
|
||||||
|
// fakeFs is implemented in terms of afero
|
||||||
|
type fakeFs struct {
|
||||||
|
a afero.Afero
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFakeFs returns a fake Filesystem that exists in-memory, useful for unit tests
|
||||||
|
func NewFakeFs() Filesystem {
|
||||||
|
return &fakeFs{a: afero.Afero{Fs: afero.NewMemMapFs()}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stat via afero.Fs.Stat
|
||||||
|
func (fs *fakeFs) Stat(name string) (os.FileInfo, error) {
|
||||||
|
return fs.a.Fs.Stat(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create via afero.Fs.Create
|
||||||
|
func (fs *fakeFs) Create(name string) (File, error) {
|
||||||
|
file, err := fs.a.Fs.Create(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &fakeFile{file}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rename via afero.Fs.Rename
|
||||||
|
func (fs *fakeFs) Rename(oldpath, newpath string) error {
|
||||||
|
return fs.a.Fs.Rename(oldpath, newpath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MkdirAll via afero.Fs.MkdirAll
|
||||||
|
func (fs *fakeFs) MkdirAll(path string, perm os.FileMode) error {
|
||||||
|
return fs.a.Fs.MkdirAll(path, perm)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chtimes via afero.Fs.Chtimes
|
||||||
|
func (fs *fakeFs) Chtimes(name string, atime time.Time, mtime time.Time) error {
|
||||||
|
return fs.a.Fs.Chtimes(name, atime, mtime)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadFile via afero.ReadFile
|
||||||
|
func (fs *fakeFs) ReadFile(filename string) ([]byte, error) {
|
||||||
|
return fs.a.ReadFile(filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TempFile via afero.TempFile
|
||||||
|
func (fs *fakeFs) TempFile(dir, prefix string) (File, error) {
|
||||||
|
file, err := fs.a.TempFile(dir, prefix)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &fakeFile{file}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadDir via afero.ReadDir
|
||||||
|
func (fs *fakeFs) ReadDir(dirname string) ([]os.FileInfo, error) {
|
||||||
|
return fs.a.ReadDir(dirname)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Walk via afero.Walk
|
||||||
|
func (fs *fakeFs) Walk(root string, walkFn filepath.WalkFunc) error {
|
||||||
|
return fs.a.Walk(root, walkFn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveAll via afero.RemoveAll
|
||||||
|
func (fs *fakeFs) RemoveAll(path string) error {
|
||||||
|
return fs.a.RemoveAll(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove via afero.RemoveAll
|
||||||
|
func (fs *fakeFs) Remove(name string) error {
|
||||||
|
return fs.a.Remove(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// fakeFile implements File; for use with fakeFs
|
||||||
|
type fakeFile struct {
|
||||||
|
file afero.File
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name via afero.File.Name
|
||||||
|
func (file *fakeFile) Name() string {
|
||||||
|
return file.file.Name()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write via afero.File.Write
|
||||||
|
func (file *fakeFile) Write(b []byte) (n int, err error) {
|
||||||
|
return file.file.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sync via afero.File.Sync
|
||||||
|
func (file *fakeFile) Sync() error {
|
||||||
|
return file.file.Sync()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close via afero.File.Close
|
||||||
|
func (file *fakeFile) Close() error {
|
||||||
|
return file.file.Close()
|
||||||
|
}
|
51
vendor/k8s.io/kubernetes/pkg/util/filesystem/filesystem.go
generated
vendored
Normal file
51
vendor/k8s.io/kubernetes/pkg/util/filesystem/filesystem.go
generated
vendored
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package filesystem
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Filesystem is an interface that we can use to mock various filesystem operations
|
||||||
|
type Filesystem interface {
|
||||||
|
// from "os"
|
||||||
|
Stat(name string) (os.FileInfo, error)
|
||||||
|
Create(name string) (File, error)
|
||||||
|
Rename(oldpath, newpath string) error
|
||||||
|
MkdirAll(path string, perm os.FileMode) error
|
||||||
|
Chtimes(name string, atime time.Time, mtime time.Time) error
|
||||||
|
RemoveAll(path string) error
|
||||||
|
Remove(name string) error
|
||||||
|
|
||||||
|
// from "io/ioutil"
|
||||||
|
ReadFile(filename string) ([]byte, error)
|
||||||
|
TempFile(dir, prefix string) (File, error)
|
||||||
|
ReadDir(dirname string) ([]os.FileInfo, error)
|
||||||
|
Walk(root string, walkFn filepath.WalkFunc) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// File is an interface that we can use to mock various filesystem operations typically
|
||||||
|
// accessed through the File object from the "os" package
|
||||||
|
type File interface {
|
||||||
|
// for now, the only os.File methods used are those below, add more as necessary
|
||||||
|
Name() string
|
||||||
|
Write(b []byte) (n int, err error)
|
||||||
|
Sync() error
|
||||||
|
Close() error
|
||||||
|
}
|
89
vendor/k8s.io/kubernetes/pkg/util/filesystem/watcher.go
generated
vendored
Normal file
89
vendor/k8s.io/kubernetes/pkg/util/filesystem/watcher.go
generated
vendored
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package filesystem
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/fsnotify/fsnotify"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FSWatcher is a callback-based filesystem watcher abstraction for fsnotify.
|
||||||
|
type FSWatcher interface {
|
||||||
|
// Initializes the watcher with the given watch handlers.
|
||||||
|
// Called before all other methods.
|
||||||
|
Init(FSEventHandler, FSErrorHandler) error
|
||||||
|
|
||||||
|
// Starts listening for events and errors.
|
||||||
|
// When an event or error occurs, the corresponding handler is called.
|
||||||
|
Run()
|
||||||
|
|
||||||
|
// Add a filesystem path to watch
|
||||||
|
AddWatch(path string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// FSEventHandler is called when a fsnotify event occurs.
|
||||||
|
type FSEventHandler func(event fsnotify.Event)
|
||||||
|
|
||||||
|
// FSErrorHandler is called when a fsnotify error occurs.
|
||||||
|
type FSErrorHandler func(err error)
|
||||||
|
|
||||||
|
type fsnotifyWatcher struct {
|
||||||
|
watcher *fsnotify.Watcher
|
||||||
|
eventHandler FSEventHandler
|
||||||
|
errorHandler FSErrorHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ FSWatcher = &fsnotifyWatcher{}
|
||||||
|
|
||||||
|
// NewFsnotifyWatcher returns an implementation of FSWatcher that continuously listens for
|
||||||
|
// fsnotify events and calls the event handler as soon as an event is received.
|
||||||
|
func NewFsnotifyWatcher() FSWatcher {
|
||||||
|
return &fsnotifyWatcher{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *fsnotifyWatcher) AddWatch(path string) error {
|
||||||
|
return w.watcher.Add(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *fsnotifyWatcher) Init(eventHandler FSEventHandler, errorHandler FSErrorHandler) error {
|
||||||
|
var err error
|
||||||
|
w.watcher, err = fsnotify.NewWatcher()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
w.eventHandler = eventHandler
|
||||||
|
w.errorHandler = errorHandler
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *fsnotifyWatcher) Run() {
|
||||||
|
go func() {
|
||||||
|
defer w.watcher.Close()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case event := <-w.watcher.Events:
|
||||||
|
if w.eventHandler != nil {
|
||||||
|
w.eventHandler(event)
|
||||||
|
}
|
||||||
|
case err := <-w.watcher.Errors:
|
||||||
|
if w.errorHandler != nil {
|
||||||
|
w.errorHandler(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
Loading…
Reference in a new issue