diff --git a/vendor.conf b/vendor.conf index 73d63e523..0f3d650d2 100644 --- a/vendor.conf +++ b/vendor.conf @@ -8,11 +8,11 @@ github.com/containerd/containerd 3013762fc58941e33ba70e8f8d9256911f134124 github.com/containerd/continuity d8fb8589b0e8e85b8c8bbaa8840226d0dfeb7371 github.com/containerd/fifo fbfb6a11ec671efbe94ad1c12c2e98773f19e1e6 github.com/containerd/go-runc 4f6e87ae043f859a38255247b49c9abc262d002f +github.com/containerd/go-cni f2d7272f12d045b16ed924f50e91f9f9cecc55a7 github.com/containerd/typeurl f6943554a7e7e88b3c14aad190bf05932da84788 github.com/containernetworking/cni v0.6.0 github.com/containernetworking/plugins v0.6.0 github.com/coreos/go-systemd 48702e0da86bd25e76cfef347e2adeb434a0d0a6 -github.com/cri-o/ocicni 9b451e26eb7c694d564991fbf44f77d0afb9b03c github.com/davecgh/go-spew v1.1.0 github.com/docker/distribution b38e5838b7b2f2ad48e06ec4b500011976080621 github.com/docker/docker 86f080cff0914e9694068ed78d503701667c4c00 @@ -21,7 +21,6 @@ github.com/docker/go-metrics 4ea375f7759c82740c893fc030bc37088d2ec098 github.com/docker/go-units v0.3.1 github.com/docker/spdystream 449fdfce4d962303d702fec724ef0ad181c92528 github.com/emicklei/go-restful ff4f55a206334ef123e4f79bbf348980da81ca46 -github.com/fsnotify/fsnotify 7d7316ed6e1ed2de075aab8dfc76de5d158d66e1 github.com/ghodss/yaml 73d445a93680fa1a78ae23a5839bad48f32ba1ee github.com/godbus/dbus c7fdd8b5cd55e87b4e1f4e372cdb1db61dd6c66f github.com/gogo/protobuf v0.5 diff --git a/vendor/github.com/cri-o/ocicni/LICENSE b/vendor/github.com/containerd/go-cni/LICENSE similarity index 94% rename from vendor/github.com/cri-o/ocicni/LICENSE rename to vendor/github.com/containerd/go-cni/LICENSE index 3fd703072..261eeb9e9 100644 --- a/vendor/github.com/cri-o/ocicni/LICENSE +++ b/vendor/github.com/containerd/go-cni/LICENSE @@ -1,4 +1,3 @@ - Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ @@ -176,7 +175,18 @@ END OF TERMS AND CONDITIONS - Copyright 2016 Red Hat, Inc. + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/vendor/github.com/containerd/go-cni/README.md b/vendor/github.com/containerd/go-cni/README.md new file mode 100644 index 000000000..900b42440 --- /dev/null +++ b/vendor/github.com/containerd/go-cni/README.md @@ -0,0 +1,46 @@ +# go-cni + +A generic CNI library to provide APIs for CNI plugin interactions. The library provides APIs to: + +- Setup networks for container namespace +- Remove networks from container namespace +- Query status of CNI network plugin initialization + +go-cni aims to support plugins that implement [Container Network Interface](https://github.com/containernetworking/cni) + +## Usage +``` +func main() { + id := "123456" + netns := "/proc/9999/ns/net" + defaultIfName := "eth0" + // Initialize library + l = gocni.New(gocni.WithMinNetworkCount(2), + gocni.WithLoNetwork(), + gocni.WithPluginConfDir("/etc/mycni/net.d"), + gocni.WithPluginDir([]string{"/opt/mycni/bin", "/opt/cni/bin"}), + gocni.WithDefaultIfName(defaultIfName)) + + // Setup network for namespace. + labels := map[string]string{ + "K8S_POD_NAMESPACE": "namespace1", + "K8S_POD_NAME": "pod1", + "K8S_POD_INFRA_CONTAINER_ID": id, + } + result, err := l.Setup(id, netns, gocni.WithLabels(labels)) + if err != nil { + return nil, fmt.Errorf("failed to setup network for namespace %q: %v", id, err) + } + defer func() { + if retErr != nil { + // Teardown network if an error is returned. + if err := l.Remove(id, netns, gocni.WithLabels(labels)); err != nil { + fmt.Errorf("Failed to destroy network for namespace %q", id) + } + } + }() + // Get IP of the default interface + IP := result.Interfaces[defaultIfName].IPConfigs[0].IP.String() + fmt.Printf("IP of the default interface %s:%s", defaultIfName, IP) +} +``` diff --git a/vendor/github.com/containerd/go-cni/cni.go b/vendor/github.com/containerd/go-cni/cni.go new file mode 100644 index 000000000..89f0dbc04 --- /dev/null +++ b/vendor/github.com/containerd/go-cni/cni.go @@ -0,0 +1,141 @@ +/* + Copyright The containerd 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 cni + +import ( + "fmt" + "sync" + + cnilibrary "github.com/containernetworking/cni/libcni" + "github.com/containernetworking/cni/pkg/types/current" + "github.com/pkg/errors" +) + +type CNI interface { + // Setup setup the network for the namespace + Setup(id string, path string, opts ...NamespaceOpts) (*CNIResult, error) + // Remove tears down the network of the namespace. + Remove(id string, path string, opts ...NamespaceOpts) error + // Load loads the cni network config + Load(opts ...LoadOption) error + // Status checks the status of the cni initialization + Status() error +} + +type libcni struct { + config + + cniConfig cnilibrary.CNI + networkCount int // minimum network plugin configurations needed to initialize cni + networks []*Network + sync.RWMutex +} + +func defaultCNIConfig() *libcni { + return &libcni{ + config: config{ + pluginDirs: []string{DefaultCNIDir}, + pluginConfDir: DefaultNetDir, + prefix: DefaultPrefix, + }, + cniConfig: &cnilibrary.CNIConfig{ + Path: []string{DefaultCNIDir}, + }, + networkCount: 1, + } +} + +func New(config ...ConfigOption) (CNI, error) { + cni := defaultCNIConfig() + var err error + for _, c := range config { + if err = c(cni); err != nil { + return nil, err + } + } + return cni, nil +} + +func (c *libcni) Load(opts ...LoadOption) error { + var err error + // Reset the networks on a load operation to ensure + // config happens on a clean slate + c.reset() + + for _, o := range opts { + if err = o(c); err != nil { + return errors.Wrapf(ErrLoad, fmt.Sprintf("cni config load failed: %v", err)) + } + } + return c.Status() +} + +func (c *libcni) Status() error { + c.RLock() + defer c.RUnlock() + if len(c.networks) < c.networkCount { + return ErrCNINotInitialized + } + return nil +} + +// Setup setups the network in the namespace +func (c *libcni) Setup(id string, path string, opts ...NamespaceOpts) (*CNIResult, error) { + if err:=c.Status();err!=nil{ + return nil,err + } + ns, err := newNamespace(id, path, opts...) + if err != nil { + return nil, err + } + var results []*current.Result + c.RLock() + defer c.RUnlock() + for _, network := range c.networks { + r, err := network.Attach(ns) + if err != nil { + return nil, err + } + results = append(results, r) + } + return c.GetCNIResultFromResults(results) +} + +// Remove removes the network config from the namespace +func (c *libcni) Remove(id string, path string, opts ...NamespaceOpts) error { + if err:=c.Status();err!=nil{ + return err + } + ns, err := newNamespace(id, path, opts...) + if err != nil { + return err + } + c.RLock() + defer c.RUnlock() + for _, network := range c.networks { + if err := network.Remove(ns); err != nil { + return err + } + } + return nil +} + +func (c *libcni) reset() { + c.Lock() + defer c.Unlock() + c.networks = nil +} diff --git a/vendor/github.com/containerd/go-cni/errors.go b/vendor/github.com/containerd/go-cni/errors.go new file mode 100644 index 000000000..c6f468924 --- /dev/null +++ b/vendor/github.com/containerd/go-cni/errors.go @@ -0,0 +1,39 @@ +package cni + +import ( + "github.com/pkg/errors" +) + +var ( + ErrCNINotInitialized = errors.New("cni plugin not initialized") + ErrInvalidConfig = errors.New("invalid cni config") + ErrNotFound = errors.New("not found") + ErrRead = errors.New("failed to read config file") + ErrInvalidResult = errors.New("invalid result") + ErrLoad = errors.New("failed to load cni config") +) + +// IsCNINotInitialized returns true if the error is due cni config not being intialized +func IsCNINotInitialized(err error) bool { + return errors.Cause(err) == ErrCNINotInitialized +} + +// IsInvalidConfig returns true if the error is invalid cni config +func IsInvalidConfig(err error) bool { + return errors.Cause(err) == ErrInvalidConfig +} + +// IsNotFound returns true if the error is due to a missing config or result +func IsNotFound(err error) bool { + return errors.Cause(err) == ErrNotFound +} + +// IsReadFailure return true if the error is a config read failure +func IsReadFailure(err error) bool { + return errors.Cause(err) == ErrRead +} + +// IsInvalidResult return true if the error is due to invalid cni result +func IsInvalidResult(err error) bool { + return errors.Cause(err) == ErrInvalidResult +} diff --git a/vendor/github.com/containerd/go-cni/helper.go b/vendor/github.com/containerd/go-cni/helper.go new file mode 100644 index 000000000..6cde2b332 --- /dev/null +++ b/vendor/github.com/containerd/go-cni/helper.go @@ -0,0 +1,41 @@ +/* + Copyright The containerd 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 cni + +import ( + "fmt" + + "github.com/containernetworking/cni/pkg/types/current" +) + +func validateInterfaceConfig(ipConf *current.IPConfig, ifs int) error { + if ipConf == nil { + return fmt.Errorf("invalid IP configuration") + } + if ipConf.Interface != nil && *ipConf.Interface > ifs { + return fmt.Errorf("invalid IP configuration with invalid interface %d", *ipConf.Interface) + } + return nil +} + +func getIfName(prefix string, i int) string { + return fmt.Sprintf("%s%d", prefix, i) +} + +func defaultInterface(prefix string) string { + return getIfName(prefix, 0) +} diff --git a/vendor/github.com/containerd/go-cni/namespace.go b/vendor/github.com/containerd/go-cni/namespace.go new file mode 100644 index 000000000..746c995eb --- /dev/null +++ b/vendor/github.com/containerd/go-cni/namespace.go @@ -0,0 +1,75 @@ +/* + Copyright The containerd 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 cni + +import ( + cnilibrary "github.com/containernetworking/cni/libcni" + "github.com/containernetworking/cni/pkg/types/current" +) + +type Network struct { + cni cnilibrary.CNI + config *cnilibrary.NetworkConfigList + ifName string +} + +func (n *Network) Attach(ns *Namespace) (*current.Result, error) { + r, err := n.cni.AddNetworkList(n.config, ns.config(n.ifName)) + if err != nil { + return nil, err + } + return current.NewResultFromResult(r) +} + +func (n *Network) Remove(ns *Namespace) error { + return n.cni.DelNetworkList(n.config, ns.config(n.ifName)) +} + +type Namespace struct { + id string + path string + capabilityArgs map[string]interface{} + args map[string]string +} + +func newNamespace(id, path string, opts ...NamespaceOpts) (*Namespace, error) { + ns := &Namespace{ + id: id, + path: path, + capabilityArgs: make(map[string]interface{}), + args: make(map[string]string), + } + for _, o := range opts { + if err := o(ns); err != nil { + return nil, err + } + } + return ns, nil +} + +func (ns *Namespace) config(ifName string) *cnilibrary.RuntimeConf { + c := &cnilibrary.RuntimeConf{ + ContainerID: ns.id, + NetNS: ns.path, + IfName: ifName, + } + for k, v := range ns.args { + c.Args = append(c.Args, [2]string{k, v}) + } + c.CapabilityArgs = ns.capabilityArgs + return c +} diff --git a/vendor/github.com/containerd/go-cni/namespace_opts.go b/vendor/github.com/containerd/go-cni/namespace_opts.go new file mode 100644 index 000000000..fad50ebce --- /dev/null +++ b/vendor/github.com/containerd/go-cni/namespace_opts.go @@ -0,0 +1,58 @@ +/* + Copyright The containerd 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 cni + +type NamespaceOpts func(s *Namespace) error + +// Capabilities +func WithCapabilityPortMap(portMapping []PortMapping) NamespaceOpts { + return func(c *Namespace) error { + c.capabilityArgs["portMappings"] = portMapping + return nil + } +} + +func WithCapabilityIPRanges(ipRanges []IPRanges) NamespaceOpts { + return func(c *Namespace) error { + c.capabilityArgs["ipRanges"] = ipRanges + return nil + } +} + +func WithCapability(name string, capability interface{}) NamespaceOpts { + return func(c *Namespace) error { + c.capabilityArgs[name] = capability + return nil + } +} + +// Args +func WithLabels(labels map[string]string) NamespaceOpts { + return func(c *Namespace) error { + for k, v := range labels { + c.args[k] = v + } + return nil + } +} + +func WithArgs(k, v string) NamespaceOpts { + return func(c *Namespace) error { + c.args[k] = v + return nil + } +} diff --git a/vendor/github.com/containerd/go-cni/opts.go b/vendor/github.com/containerd/go-cni/opts.go new file mode 100644 index 000000000..ada1daa0c --- /dev/null +++ b/vendor/github.com/containerd/go-cni/opts.go @@ -0,0 +1,226 @@ +/* + Copyright The containerd 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 cni + +import ( + "sort" + "strings" + + cnilibrary "github.com/containernetworking/cni/libcni" + "github.com/pkg/errors" +) + +type ConfigOption func(c *libcni) error + +// WithInterfacePrefix sets the prefix for network interfaces +// e.g. eth or wlan +func WithInterfacePrefix(prefix string) ConfigOption { + return func(c *libcni) error { + c.prefix = prefix + return nil + } +} + +// WithPluginDir can be used to set the locations of +// the cni plugin binaries +func WithPluginDir(dirs []string) ConfigOption { + return func(c *libcni) error { + c.pluginDirs = dirs + c.cniConfig = &cnilibrary.CNIConfig{Path: dirs} + return nil + } +} + +// WithPluginConfDir can be used to configure the +// cni configuration directory. +func WithPluginConfDir(dir string) ConfigOption { + return func(c *libcni) error { + c.pluginConfDir = dir + return nil + } +} + +// WithMinNetworkCount can be used to configure the +// minimum networks to be configured and initalized +// for the status to report success. By default its 1. +func WithMinNetworkCount(count int) ConfigOption { + return func(c *libcni) error { + c.networkCount = count + return nil + } +} + +// LoadOption can be used with Load API +// to load network configuration from different +// sources. +type LoadOption func(c *libcni) error + +// WithLoNetwork can be used to load the loopback +// network config. +func WithLoNetwork() LoadOption { + return func(c *libcni) error { + loConfig, _ := cnilibrary.ConfListFromBytes([]byte(`{ +"cniVersion": "0.3.1", +"name": "cni-loopback", +"plugins": [{ + "type": "loopback" +}] +}`)) + + c.Lock() + defer c.Unlock() + c.networks = append(c.networks,&Network{ + cni: c.cniConfig, + config: loConfig, + ifName: "lo", + }) + return nil + } +} + +// WithConf can be used to load config directly +// from byte. +func WithConf(bytes []byte) LoadOption { + return func(c *libcni) error { + conf, err := cnilibrary.ConfFromBytes(bytes) + if err != nil { + return err + } + confList, err := cnilibrary.ConfListFromConf(conf) + if err != nil { + return err + } + c.Lock() + defer c.Unlock() + c.networks = append(c.networks, &Network{ + cni: c.cniConfig, + config: confList, + ifName: getIfName(c.prefix, 0), + }) + return nil + } +} + +// WithConfFile can be used to load network config +// from an .conf file. Supported with absolute fileName +// with path only. +func WithConfFile(fileName string) LoadOption { + return func(c *libcni) error { + conf, err := cnilibrary.ConfFromFile(fileName) + if err != nil { + return err + } + // upconvert to conf list + confList, err := cnilibrary.ConfListFromConf(conf) + if err != nil { + return err + } + c.Lock() + defer c.Unlock() + c.networks = append(c.networks, &Network{ + cni: c.cniConfig, + config: confList, + ifName: getIfName(c.prefix, 0), + }) + return nil + } +} + +// WithConfListFile can be used to load network config +// from an .conflist file. Supported with absolute fileName +// with path only. +func WithConfListFile(fileName string) LoadOption { + return func(c *libcni) error { + confList, err := cnilibrary.ConfListFromFile(fileName) + if err != nil { + return err + } + c.Lock() + defer c.Unlock() + c.networks = append(c.networks,&Network{ + cni: c.cniConfig, + config: confList, + ifName: getIfName(c.prefix, 0), + }) + return nil + } +} + +// WithDefaultConf can be used to detect network config +// files from the configured cni config directory and load +// them. +func WithDefaultConf() LoadOption { + return func(c *libcni) error { + files, err := cnilibrary.ConfFiles(c.pluginConfDir, []string{".conf", ".conflist", ".json"}) + switch { + case err != nil: + return errors.Wrapf(ErrRead, "failed to read config file: %v", err) + case len(files) == 0: + return errors.Wrapf(ErrCNINotInitialized, "no network config found in %s", c.pluginConfDir) + } + + // files contains the network config files associated with cni network. + // Use lexicographical way as a defined order for network config files. + sort.Strings(files) + // Since the CNI spec does not specify a way to detect default networks, + // the convention chosen is - the first network configuration in the sorted + // list of network conf files as the default network and choose the default + // interface provided during init as the network interface for this default + // network. For every other network use a generated interface id. + i := 0 + c.Lock() + defer c.Unlock() + for _, confFile := range files { + var confList *cnilibrary.NetworkConfigList + if strings.HasSuffix(confFile, ".conflist") { + confList, err = cnilibrary.ConfListFromFile(confFile) + if err != nil { + return errors.Wrapf(ErrInvalidConfig, "failed to load CNI config list file %s: %v", confFile, err) + } + } else { + conf, err := cnilibrary.ConfFromFile(confFile) + if err != nil { + return errors.Wrapf(ErrInvalidConfig, "failed to load CNI config file %s: %v", confFile, err) + } + // Ensure the config has a "type" so we know what plugin to run. + // Also catches the case where somebody put a conflist into a conf file. + if conf.Network.Type == "" { + return errors.Wrapf(ErrInvalidConfig, "network type not found in %s", confFile) + } + + confList, err = cnilibrary.ConfListFromConf(conf) + if err != nil { + return errors.Wrapf(ErrInvalidConfig, "failed to convert CNI config file %s to list: %v", confFile, err) + } + } + if len(confList.Plugins) == 0 { + return errors.Wrapf(ErrInvalidConfig, "CNI config list %s has no networks, skipping", confFile) + + } + c.networks = append(c.networks, &Network{ + cni: c.cniConfig, + config: confList, + ifName: getIfName(c.prefix, i), + }) + i++ + } + if len(c.networks) == 0 { + return errors.Wrapf(ErrCNINotInitialized, "no valid networks found in %s", c.pluginDirs) + } + return nil + } +} diff --git a/vendor/github.com/containerd/go-cni/result.go b/vendor/github.com/containerd/go-cni/result.go new file mode 100644 index 000000000..d79967682 --- /dev/null +++ b/vendor/github.com/containerd/go-cni/result.go @@ -0,0 +1,103 @@ +/* + Copyright The containerd 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 cni + +import ( + "net" + + "github.com/containernetworking/cni/pkg/types" + "github.com/containernetworking/cni/pkg/types/current" + "github.com/pkg/errors" +) + +type IPConfig struct { + IP net.IP + Gateway net.IP +} + +type CNIResult struct { + Interfaces map[string]*Config + DNS []types.DNS + Routes []*types.Route +} + +type Config struct { + IPConfigs []*IPConfig + Mac string + Sandbox string +} + +// GetCNIResultFromResults returns a structured data containing the +// interface configuration for each of the interfaces created in the namespace. +// Conforms with +// Result: +// a) Interfaces list. Depending on the plugin, this can include the sandbox +// (eg, container or hypervisor) interface name and/or the host interface +// name, the hardware addresses of each interface, and details about the +// sandbox (if any) the interface is in. +// b) IP configuration assigned to each interface. The IPv4 and/or IPv6 addresses, +// gateways, and routes assigned to sandbox and/or host interfaces. +// c) DNS information. Dictionary that includes DNS information for nameservers, +// domain, search domains and options. +func (c *libcni) GetCNIResultFromResults(results []*current.Result) (*CNIResult, error) { + r := &CNIResult{ + Interfaces: make(map[string]*Config), + } + + // Plugins may not need to return Interfaces in result if + // if there are no multiple interfaces created. In that case + // all configs should be applied against default interface + r.Interfaces[defaultInterface(c.prefix)] = &Config{} + + // Walk through all the results + for _, result := range results { + // Walk through all the interface in each result + for _, intf := range result.Interfaces { + r.Interfaces[intf.Name] = &Config{ + Mac: intf.Mac, + Sandbox: intf.Sandbox, + } + } + // Walk through all the IPs in the result and attach it to corresponding + // interfaces + for _, ipConf := range result.IPs { + if err := validateInterfaceConfig(ipConf, len(result.Interfaces)); err != nil { + return nil, errors.Wrapf(ErrInvalidResult, "failed to valid interface config: %v", err) + } + name := c.getInterfaceName(result.Interfaces, ipConf) + r.Interfaces[name].IPConfigs = append(r.Interfaces[name].IPConfigs, + &IPConfig{IP: ipConf.Address.IP, Gateway: ipConf.Gateway}) + } + r.DNS = append(r.DNS, result.DNS) + r.Routes = append(r.Routes, result.Routes...) + } + if _, ok := r.Interfaces[defaultInterface(c.prefix)]; !ok { + return nil, errors.Wrapf(ErrNotFound, "default network not found") + } + return r, nil +} + +// getInterfaceName returns the interface name if the plugins +// return the result with associated interfaces. If interface +// is not present then default interface name is used +func (c *libcni) getInterfaceName(interfaces []*current.Interface, + ipConf *current.IPConfig) string { + if ipConf.Interface != nil { + return interfaces[*ipConf.Interface].Name + } + return defaultInterface(c.prefix) +} diff --git a/vendor/github.com/containerd/go-cni/testutils.go b/vendor/github.com/containerd/go-cni/testutils.go new file mode 100644 index 000000000..d9453c8d9 --- /dev/null +++ b/vendor/github.com/containerd/go-cni/testutils.go @@ -0,0 +1,78 @@ +/* + Copyright The containerd 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 cni + +import ( + "fmt" + "io/ioutil" + "os" + "path" + "testing" +) + +func makeTmpDir(prefix string) (string, error) { + tmpDir, err := ioutil.TempDir(os.TempDir(), prefix) + if err != nil { + return "", err + } + return tmpDir, nil +} + +func makeFakeCNIConfig(t *testing.T) (string, string) { + cniDir, err := makeTmpDir("fakecni") + if err != nil { + t.Fatalf("Failed to create plugin config dir: %v", err) + } + + cniConfDir := path.Join(cniDir, "net.d") + err = os.MkdirAll(cniConfDir, 0777) + if err != nil { + t.Fatalf("Failed to create network config dir: %v", err) + } + + networkConfig1 := path.Join(cniConfDir, "mocknetwork1.conf") + f1, err := os.Create(networkConfig1) + if err != nil { + t.Fatalf("Failed to create network config %v: %v", f1, err) + } + networkConfig2 := path.Join(cniConfDir, "mocknetwork2.conf") + f2, err := os.Create(networkConfig2) + if err != nil { + t.Fatalf("Failed to create network config %v: %v", f2, err) + } + + cfg1 := fmt.Sprintf(`{ "name": "%s", "type": "%s", "capabilities": {"portMappings": true} }`, "plugin1", "fakecni") + _, err = f1.WriteString(cfg1) + if err != nil { + t.Fatalf("Failed to write network config file %v: %v", f1, err) + } + f1.Close() + cfg2 := fmt.Sprintf(`{ "name": "%s", "type": "%s", "capabilities": {"portMappings": true} }`, "plugin2", "fakecni") + _, err = f2.WriteString(cfg2) + if err != nil { + t.Fatalf("Failed to write network config file %v: %v", f2, err) + } + f2.Close() + return cniDir, cniConfDir +} + +func tearDownCNIConfig(t *testing.T, confDir string) { + err := os.RemoveAll(confDir) + if err != nil { + t.Fatalf("Failed to cleanup CNI configs: %v", err) + } +} diff --git a/vendor/github.com/containerd/go-cni/types.go b/vendor/github.com/containerd/go-cni/types.go new file mode 100644 index 000000000..1a77fa90a --- /dev/null +++ b/vendor/github.com/containerd/go-cni/types.go @@ -0,0 +1,45 @@ +/* + Copyright The containerd 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 cni + +const ( + CNIPluginName = "cni" + DefaultNetDir = "/etc/cni/net.d" + DefaultCNIDir = "/opt/cni/bin" + VendorCNIDirTemplate = "%s/opt/%s/bin" + DefaultPrefix = "eth" +) + +type config struct { + pluginDirs []string + pluginConfDir string + prefix string +} + +type PortMapping struct { + HostPort int32 + ContainerPort int32 + Protocol string + HostIP string +} + +type IPRanges struct { + Subnet string + RangeStart string + RangeEnd string + Gateway string +} diff --git a/vendor/github.com/containerd/go-cni/vendor.conf b/vendor/github.com/containerd/go-cni/vendor.conf new file mode 100644 index 000000000..aefe9a108 --- /dev/null +++ b/vendor/github.com/containerd/go-cni/vendor.conf @@ -0,0 +1,6 @@ +github.com/stretchr/testify b89eecf5ca5db6d3ba60b237ffe3df7bafb7662f +github.com/davecgh/go-spew 8991bc29aa16c548c550c7ff78260e27b9ab7c73 +github.com/pmezard/go-difflib 792786c7400a136282c1664665ae0a8db921c6c2 +github.com/stretchr/objx 8a3f7159479fbc75b30357fbc48f380b7320f08e +github.com/containernetworking/cni 142cde0c766cd6055cc7fdfdcb44579c0c9c35bf +github.com/pkg/errors v0.8.0 diff --git a/vendor/github.com/cri-o/ocicni/README.md b/vendor/github.com/cri-o/ocicni/README.md deleted file mode 100644 index 99c103c83..000000000 --- a/vendor/github.com/cri-o/ocicni/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# ocicni - -API layer to call the CNI plugins from an OCI lifecycle daemon diff --git a/vendor/github.com/cri-o/ocicni/pkg/ocicni/ocicni.go b/vendor/github.com/cri-o/ocicni/pkg/ocicni/ocicni.go deleted file mode 100644 index c2ba9e4f2..000000000 --- a/vendor/github.com/cri-o/ocicni/pkg/ocicni/ocicni.go +++ /dev/null @@ -1,425 +0,0 @@ -package ocicni - -import ( - "errors" - "fmt" - "os" - "os/exec" - "sort" - "strings" - "sync" - - "github.com/containernetworking/cni/libcni" - cnitypes "github.com/containernetworking/cni/pkg/types" - "github.com/fsnotify/fsnotify" - "github.com/sirupsen/logrus" -) - -type cniNetworkPlugin struct { - loNetwork *cniNetwork - - sync.RWMutex - defaultNetwork *cniNetwork - - nsenterPath string - pluginDir string - cniDirs []string - vendorCNIDirPrefix string - - monitorNetDirChan chan struct{} - - // The pod map provides synchronization for a given pod's network - // operations. Each pod's setup/teardown/status operations - // are synchronized against each other, but network operations of other - // pods can proceed in parallel. - podsLock sync.Mutex - pods map[string]*podLock -} - -type cniNetwork struct { - name string - NetworkConfig *libcni.NetworkConfigList - CNIConfig libcni.CNI -} - -var errMissingDefaultNetwork = errors.New("Missing CNI default network") - -type podLock struct { - // Count of in-flight operations for this pod; when this reaches zero - // the lock can be removed from the pod map - refcount uint - - // Lock to synchronize operations for this specific pod - mu sync.Mutex -} - -func buildFullPodName(podNetwork PodNetwork) string { - return podNetwork.Namespace + "_" + podNetwork.Name -} - -// Lock network operations for a specific pod. If that pod is not yet in -// the pod map, it will be added. The reference count for the pod will -// be increased. -func (plugin *cniNetworkPlugin) podLock(podNetwork PodNetwork) *sync.Mutex { - plugin.podsLock.Lock() - defer plugin.podsLock.Unlock() - - fullPodName := buildFullPodName(podNetwork) - lock, ok := plugin.pods[fullPodName] - if !ok { - lock = &podLock{} - plugin.pods[fullPodName] = lock - } - lock.refcount++ - return &lock.mu -} - -// Unlock network operations for a specific pod. The reference count for the -// pod will be decreased. If the reference count reaches zero, the pod will be -// removed from the pod map. -func (plugin *cniNetworkPlugin) podUnlock(podNetwork PodNetwork) { - plugin.podsLock.Lock() - defer plugin.podsLock.Unlock() - - fullPodName := buildFullPodName(podNetwork) - lock, ok := plugin.pods[fullPodName] - if !ok { - logrus.Warningf("Unbalanced pod lock unref for %s", fullPodName) - return - } else if lock.refcount == 0 { - // This should never ever happen, but handle it anyway - delete(plugin.pods, fullPodName) - logrus.Errorf("Pod lock for %s still in map with zero refcount", fullPodName) - return - } - lock.refcount-- - lock.mu.Unlock() - if lock.refcount == 0 { - delete(plugin.pods, fullPodName) - } -} - -func (plugin *cniNetworkPlugin) monitorNetDir() { - watcher, err := fsnotify.NewWatcher() - if err != nil { - logrus.Errorf("could not create new watcher %v", err) - return - } - defer watcher.Close() - - if err = watcher.Add(plugin.pluginDir); err != nil { - logrus.Errorf("Failed to add watch on %q: %v", plugin.pluginDir, err) - return - } - - // Now that `watcher` is running and watching the `pluginDir` - // gather the initial configuration, before starting the - // goroutine which will actually process events. It has to be - // done in this order to avoid missing any updates which might - // otherwise occur between gathering the initial configuration - // and starting the watcher. - if err := plugin.syncNetworkConfig(); err != nil { - logrus.Infof("Initial CNI setting failed, continue monitoring: %v", err) - } else { - logrus.Infof("Initial CNI setting succeeded") - } - - go func() { - for { - select { - case event := <-watcher.Events: - logrus.Debugf("CNI monitoring event %v", event) - if event.Op&fsnotify.Create != fsnotify.Create && - event.Op&fsnotify.Write != fsnotify.Write { - continue - } - - if err = plugin.syncNetworkConfig(); err == nil { - logrus.Infof("CNI asynchronous setting succeeded") - continue - } - - logrus.Errorf("CNI setting failed, continue monitoring: %v", err) - - case err := <-watcher.Errors: - logrus.Errorf("CNI monitoring error %v", err) - close(plugin.monitorNetDirChan) - return - } - } - }() - - <-plugin.monitorNetDirChan -} - -// InitCNI takes the plugin directory and CNI directories where the CNI config -// files should be searched for. If no valid CNI configs exist, network requests -// will fail until valid CNI config files are present in the config directory. -func InitCNI(pluginDir string, cniDirs ...string) (CNIPlugin, error) { - vendorCNIDirPrefix := "" - plugin := &cniNetworkPlugin{ - defaultNetwork: nil, - loNetwork: getLoNetwork(cniDirs, vendorCNIDirPrefix), - pluginDir: pluginDir, - cniDirs: cniDirs, - vendorCNIDirPrefix: vendorCNIDirPrefix, - monitorNetDirChan: make(chan struct{}), - pods: make(map[string]*podLock), - } - - var err error - plugin.nsenterPath, err = exec.LookPath("nsenter") - if err != nil { - return nil, err - } - - // Ensure plugin directory exists, because the following monitoring logic - // relies on that. - if err := os.MkdirAll(pluginDir, 0755); err != nil { - return nil, err - } - - go plugin.monitorNetDir() - - return plugin, nil -} - -func getDefaultCNINetwork(pluginDir string, cniDirs []string, vendorCNIDirPrefix string) (*cniNetwork, error) { - if pluginDir == "" { - pluginDir = DefaultNetDir - } - if len(cniDirs) == 0 { - cniDirs = []string{DefaultCNIDir} - } - - files, err := libcni.ConfFiles(pluginDir, []string{".conf", ".conflist", ".json"}) - switch { - case err != nil: - return nil, err - case len(files) == 0: - return nil, errMissingDefaultNetwork - } - - sort.Strings(files) - for _, confFile := range files { - var confList *libcni.NetworkConfigList - if strings.HasSuffix(confFile, ".conflist") { - confList, err = libcni.ConfListFromFile(confFile) - if err != nil { - logrus.Warningf("Error loading CNI config list file %s: %v", confFile, err) - continue - } - } else { - conf, err := libcni.ConfFromFile(confFile) - if err != nil { - logrus.Warningf("Error loading CNI config file %s: %v", confFile, err) - continue - } - if conf.Network.Type == "" { - logrus.Warningf("Error loading CNI config file %s: no 'type'; perhaps this is a .conflist?", confFile) - continue - } - confList, err = libcni.ConfListFromConf(conf) - if err != nil { - logrus.Warningf("Error converting CNI config file %s to list: %v", confFile, err) - continue - } - } - if len(confList.Plugins) == 0 { - logrus.Warningf("CNI config list %s has no networks, skipping", confFile) - continue - } - logrus.Infof("CNI network %s (type=%v) is used from %s", confList.Name, confList.Plugins[0].Network.Type, confFile) - // Search for vendor-specific plugins as well as default plugins in the CNI codebase. - vendorDir := vendorCNIDir(vendorCNIDirPrefix, confList.Plugins[0].Network.Type) - cninet := &libcni.CNIConfig{ - Path: append(cniDirs, vendorDir), - } - network := &cniNetwork{name: confList.Name, NetworkConfig: confList, CNIConfig: cninet} - return network, nil - } - return nil, fmt.Errorf("No valid networks found in %s", pluginDir) -} - -func vendorCNIDir(prefix, pluginType string) string { - return fmt.Sprintf(VendorCNIDirTemplate, prefix, pluginType) -} - -func getLoNetwork(cniDirs []string, vendorDirPrefix string) *cniNetwork { - if len(cniDirs) == 0 { - cniDirs = []string{DefaultCNIDir} - } - - loConfig, err := libcni.ConfListFromBytes([]byte(`{ - "cniVersion": "0.2.0", - "name": "cni-loopback", - "plugins": [{ - "type": "loopback" - }] -}`)) - if err != nil { - // The hardcoded config above should always be valid and unit tests will - // catch this - panic(err) - } - vendorDir := vendorCNIDir(vendorDirPrefix, loConfig.Plugins[0].Network.Type) - cninet := &libcni.CNIConfig{ - Path: append(cniDirs, vendorDir), - } - loNetwork := &cniNetwork{ - name: "lo", - NetworkConfig: loConfig, - CNIConfig: cninet, - } - - return loNetwork -} - -func (plugin *cniNetworkPlugin) syncNetworkConfig() error { - network, err := getDefaultCNINetwork(plugin.pluginDir, plugin.cniDirs, plugin.vendorCNIDirPrefix) - if err != nil { - logrus.Errorf("error updating cni config: %s", err) - return err - } - plugin.setDefaultNetwork(network) - - return nil -} - -func (plugin *cniNetworkPlugin) getDefaultNetwork() *cniNetwork { - plugin.RLock() - defer plugin.RUnlock() - return plugin.defaultNetwork -} - -func (plugin *cniNetworkPlugin) setDefaultNetwork(n *cniNetwork) { - plugin.Lock() - defer plugin.Unlock() - plugin.defaultNetwork = n -} - -func (plugin *cniNetworkPlugin) checkInitialized() error { - if plugin.getDefaultNetwork() == nil { - return errors.New("cni config uninitialized") - } - return nil -} - -func (plugin *cniNetworkPlugin) Name() string { - return CNIPluginName -} - -func (plugin *cniNetworkPlugin) SetUpPod(podNetwork PodNetwork) (cnitypes.Result, error) { - if err := plugin.checkInitialized(); err != nil { - return nil, err - } - - plugin.podLock(podNetwork).Lock() - defer plugin.podUnlock(podNetwork) - - _, err := plugin.loNetwork.addToNetwork(podNetwork) - if err != nil { - logrus.Errorf("Error while adding to cni lo network: %s", err) - return nil, err - } - - result, err := plugin.getDefaultNetwork().addToNetwork(podNetwork) - if err != nil { - logrus.Errorf("Error while adding to cni network: %s", err) - return nil, err - } - - return result, err -} - -func (plugin *cniNetworkPlugin) TearDownPod(podNetwork PodNetwork) error { - if err := plugin.checkInitialized(); err != nil { - return err - } - - plugin.podLock(podNetwork).Lock() - defer plugin.podUnlock(podNetwork) - - return plugin.getDefaultNetwork().deleteFromNetwork(podNetwork) -} - -// TODO: Use the addToNetwork function to obtain the IP of the Pod. That will assume idempotent ADD call to the plugin. -// Also fix the runtime's call to Status function to be done only in the case that the IP is lost, no need to do periodic calls -func (plugin *cniNetworkPlugin) GetPodNetworkStatus(podNetwork PodNetwork) (string, error) { - plugin.podLock(podNetwork).Lock() - defer plugin.podUnlock(podNetwork) - - ip, err := getContainerIP(plugin.nsenterPath, podNetwork.NetNS, DefaultInterfaceName, "-4") - if err != nil { - ip, err = getContainerIP(plugin.nsenterPath, podNetwork.NetNS, DefaultInterfaceName, "-6") - } - if err != nil { - return "", err - } - - return ip.String(), nil -} - -func (network *cniNetwork) addToNetwork(podNetwork PodNetwork) (cnitypes.Result, error) { - rt, err := buildCNIRuntimeConf(podNetwork) - if err != nil { - logrus.Errorf("Error adding network: %v", err) - return nil, err - } - - netconf, cninet := network.NetworkConfig, network.CNIConfig - logrus.Infof("About to add CNI network %s (type=%v)", netconf.Name, netconf.Plugins[0].Network.Type) - res, err := cninet.AddNetworkList(netconf, rt) - if err != nil { - logrus.Errorf("Error adding network: %v", err) - return nil, err - } - - return res, nil -} - -func (network *cniNetwork) deleteFromNetwork(podNetwork PodNetwork) error { - rt, err := buildCNIRuntimeConf(podNetwork) - if err != nil { - logrus.Errorf("Error deleting network: %v", err) - return err - } - - netconf, cninet := network.NetworkConfig, network.CNIConfig - logrus.Infof("About to del CNI network %s (type=%v)", netconf.Name, netconf.Plugins[0].Network.Type) - err = cninet.DelNetworkList(netconf, rt) - if err != nil { - logrus.Errorf("Error deleting network: %v", err) - return err - } - return nil -} - -func buildCNIRuntimeConf(podNetwork PodNetwork) (*libcni.RuntimeConf, error) { - logrus.Infof("Got pod network %+v", podNetwork) - - rt := &libcni.RuntimeConf{ - ContainerID: podNetwork.ID, - NetNS: podNetwork.NetNS, - IfName: DefaultInterfaceName, - Args: [][2]string{ - {"IgnoreUnknown", "1"}, - {"K8S_POD_NAMESPACE", podNetwork.Namespace}, - {"K8S_POD_NAME", podNetwork.Name}, - {"K8S_POD_INFRA_CONTAINER_ID", podNetwork.ID}, - }, - } - - if len(podNetwork.PortMappings) == 0 { - return rt, nil - } - - rt.CapabilityArgs = map[string]interface{}{ - "portMappings": podNetwork.PortMappings, - } - return rt, nil -} - -func (plugin *cniNetworkPlugin) Status() error { - return plugin.checkInitialized() -} diff --git a/vendor/github.com/cri-o/ocicni/pkg/ocicni/types.go b/vendor/github.com/cri-o/ocicni/pkg/ocicni/types.go deleted file mode 100644 index 60816d179..000000000 --- a/vendor/github.com/cri-o/ocicni/pkg/ocicni/types.go +++ /dev/null @@ -1,66 +0,0 @@ -package ocicni - -import ( - "github.com/containernetworking/cni/pkg/types" -) - -const ( - // DefaultInterfaceName is the string to be used for the interface name inside the net namespace - DefaultInterfaceName = "eth0" - // CNIPluginName is the default name of the plugin - CNIPluginName = "cni" - // DefaultNetDir is the place to look for CNI Network - DefaultNetDir = "/etc/cni/net.d" - // DefaultCNIDir is the place to look for cni config files - DefaultCNIDir = "/opt/cni/bin" - // VendorCNIDirTemplate is the template for looking up vendor specific cni config/executable files - VendorCNIDirTemplate = "%s/opt/%s/bin" -) - -// PortMapping maps to the standard CNI portmapping Capability -// see: https://github.com/containernetworking/cni/blob/master/CONVENTIONS.md -type PortMapping struct { - // HostPort is the port number on the host. - HostPort int32 `json:"hostPort"` - // ContainerPort is the port number inside the sandbox. - ContainerPort int32 `json:"containerPort"` - // Protocol is the protocol of the port mapping. - Protocol string `json:"protocol"` - // HostIP is the host ip to use. - HostIP string `json:"hostIP"` -} - -// PodNetwork configures the network of a pod sandbox. -type PodNetwork struct { - // Name is the name of the sandbox. - Name string - // Namespace is the namespace of the sandbox. - Namespace string - // ID is the id of the sandbox container. - ID string - // NetNS is the network namespace path of the sandbox. - NetNS string - // PortMappings is the port mapping of the sandbox. - PortMappings []PortMapping -} - -// CNIPlugin is the interface that needs to be implemented by a plugin -type CNIPlugin interface { - // Name returns the plugin's name. This will be used when searching - // for a plugin by name, e.g. - Name() string - - // SetUpPod is the method called after the sandbox container of - // the pod has been created but before the other containers of the - // pod are launched. - SetUpPod(network PodNetwork) (types.Result, error) - - // TearDownPod is the method called before a pod's sandbox container will be deleted - TearDownPod(network PodNetwork) error - - // Status is the method called to obtain the ipv4 or ipv6 addresses of the pod sandbox - GetPodNetworkStatus(network PodNetwork) (string, error) - - // NetworkStatus returns error if the network plugin is in error state - Status() error -} diff --git a/vendor/github.com/cri-o/ocicni/pkg/ocicni/util.go b/vendor/github.com/cri-o/ocicni/pkg/ocicni/util.go deleted file mode 100644 index 547e95972..000000000 --- a/vendor/github.com/cri-o/ocicni/pkg/ocicni/util.go +++ /dev/null @@ -1,32 +0,0 @@ -package ocicni - -import ( - "fmt" - "net" - "os/exec" - "strings" -) - -func getContainerIP(nsenterPath, netnsPath, interfaceName, addrType string) (net.IP, error) { - // Try to retrieve ip inside container network namespace - output, err := exec.Command(nsenterPath, fmt.Sprintf("--net=%s", netnsPath), "-F", "--", - "ip", "-o", addrType, "addr", "show", "dev", interfaceName, "scope", "global").CombinedOutput() - if err != nil { - return nil, fmt.Errorf("Unexpected command output %s with error: %v", output, err) - } - - lines := strings.Split(string(output), "\n") - if len(lines) < 1 { - return nil, fmt.Errorf("Unexpected command output %s", output) - } - fields := strings.Fields(lines[0]) - if len(fields) < 4 { - return nil, fmt.Errorf("Unexpected address output %s ", lines[0]) - } - ip, _, err := net.ParseCIDR(fields[3]) - if err != nil { - return nil, fmt.Errorf("CNI failed to parse ip from output %s due to %v", output, err) - } - - return ip, nil -} diff --git a/vendor/github.com/fsnotify/fsnotify/LICENSE b/vendor/github.com/fsnotify/fsnotify/LICENSE deleted file mode 100644 index f21e54080..000000000 --- a/vendor/github.com/fsnotify/fsnotify/LICENSE +++ /dev/null @@ -1,28 +0,0 @@ -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. diff --git a/vendor/github.com/fsnotify/fsnotify/README.md b/vendor/github.com/fsnotify/fsnotify/README.md deleted file mode 100644 index 399320741..000000000 --- a/vendor/github.com/fsnotify/fsnotify/README.md +++ /dev/null @@ -1,79 +0,0 @@ -# File system notifications for Go - -[![GoDoc](https://godoc.org/github.com/fsnotify/fsnotify?status.svg)](https://godoc.org/github.com/fsnotify/fsnotify) [![Go Report Card](https://goreportcard.com/badge/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 macOS. - -|Adapter |OS |Status | -|----------|----------|----------| -|inotify |Linux 2.6.27 or later, Android\*|Supported [![Build Status](https://travis-ci.org/fsnotify/fsnotify.svg?branch=master)](https://travis-ci.org/fsnotify/fsnotify)| -|kqueue |BSD, macOS, iOS\*|Supported [![Build Status](https://travis-ci.org/fsnotify/fsnotify.svg?branch=master)](https://travis-ci.org/fsnotify/fsnotify)| -|ReadDirectoryChangesW|Windows|Supported [![Build status](https://ci.appveyor.com/api/projects/status/ivwjubaih4r0udeh/branch/master?svg=true)](https://ci.appveyor.com/project/NathanYoungman/fsnotify/branch/master)| -|FSEvents |macOS |[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) and consult the [FAQ](#faq) for usage 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). - -## FAQ - -**When a file is moved to another directory is it still being watched?** - -No (it shouldn't be, unless you are watching where it was moved to). - -**When I watch a directory, are all subdirectories watched as well?** - -No, you must add watches for any directory you want to watch (a recursive watcher is on the roadmap [#18][]). - -**Do I have to watch the Error and Event channels in a separate goroutine?** - -As of now, yes. Looking into making this single-thread friendly (see [howeyc #7][#7]) - -**Why am I receiving multiple events for the same file on OS X?** - -Spotlight indexing on OS X can result in multiple events (see [howeyc #62][#62]). A temporary workaround is to add your folder(s) to the *Spotlight Privacy settings* until we have a native FSEvents implementation (see [#11][]). - -**How many files can be watched at once?** - -There are OS-specific limits as to how many watches can be created: -* Linux: /proc/sys/fs/inotify/max_user_watches contains the limit, reaching this limit results in a "no space left on device" error. -* BSD / OSX: sysctl variables "kern.maxfiles" and "kern.maxfilesperproc", reaching these limits results in a "too many open files" error. - -[#62]: https://github.com/howeyc/fsnotify/issues/62 -[#18]: https://github.com/fsnotify/fsnotify/issues/18 -[#11]: https://github.com/fsnotify/fsnotify/issues/11 -[#7]: https://github.com/howeyc/fsnotify/issues/7 - -[contributing]: https://github.com/fsnotify/fsnotify/blob/master/CONTRIBUTING.md - -## Related Projects - -* [notify](https://github.com/rjeczalik/notify) -* [fsevents](https://github.com/fsnotify/fsevents) - diff --git a/vendor/github.com/fsnotify/fsnotify/fen.go b/vendor/github.com/fsnotify/fsnotify/fen.go deleted file mode 100644 index ced39cb88..000000000 --- a/vendor/github.com/fsnotify/fsnotify/fen.go +++ /dev/null @@ -1,37 +0,0 @@ -// 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 -} diff --git a/vendor/github.com/fsnotify/fsnotify/fsnotify.go b/vendor/github.com/fsnotify/fsnotify/fsnotify.go deleted file mode 100644 index 190bf0de5..000000000 --- a/vendor/github.com/fsnotify/fsnotify/fsnotify.go +++ /dev/null @@ -1,66 +0,0 @@ -// 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" - "errors" - "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()) -} - -// Common errors that can be reported by a watcher -var ErrEventOverflow = errors.New("fsnotify queue overflow") diff --git a/vendor/github.com/fsnotify/fsnotify/inotify.go b/vendor/github.com/fsnotify/fsnotify/inotify.go deleted file mode 100644 index bfa9dbc3c..000000000 --- a/vendor/github.com/fsnotify/fsnotify/inotify.go +++ /dev/null @@ -1,334 +0,0 @@ -// 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 mask&unix.IN_Q_OVERFLOW != 0 { - select { - case w.Errors <- ErrEventOverflow: - case <-w.done: - return - } - } - - // 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 -} diff --git a/vendor/github.com/fsnotify/fsnotify/inotify_poller.go b/vendor/github.com/fsnotify/fsnotify/inotify_poller.go deleted file mode 100644 index cc7db4b22..000000000 --- a/vendor/github.com/fsnotify/fsnotify/inotify_poller.go +++ /dev/null @@ -1,187 +0,0 @@ -// 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) - } -} diff --git a/vendor/github.com/fsnotify/fsnotify/kqueue.go b/vendor/github.com/fsnotify/fsnotify/kqueue.go deleted file mode 100644 index c2b4acb18..000000000 --- a/vendor/github.com/fsnotify/fsnotify/kqueue.go +++ /dev/null @@ -1,503 +0,0 @@ -// 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()) -} diff --git a/vendor/github.com/fsnotify/fsnotify/open_mode_bsd.go b/vendor/github.com/fsnotify/fsnotify/open_mode_bsd.go deleted file mode 100644 index 7d8de1451..000000000 --- a/vendor/github.com/fsnotify/fsnotify/open_mode_bsd.go +++ /dev/null @@ -1,11 +0,0 @@ -// 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 diff --git a/vendor/github.com/fsnotify/fsnotify/open_mode_darwin.go b/vendor/github.com/fsnotify/fsnotify/open_mode_darwin.go deleted file mode 100644 index 9139e1716..000000000 --- a/vendor/github.com/fsnotify/fsnotify/open_mode_darwin.go +++ /dev/null @@ -1,12 +0,0 @@ -// 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 diff --git a/vendor/github.com/fsnotify/fsnotify/windows.go b/vendor/github.com/fsnotify/fsnotify/windows.go deleted file mode 100644 index 09436f31d..000000000 --- a/vendor/github.com/fsnotify/fsnotify/windows.go +++ /dev/null @@ -1,561 +0,0 @@ -// 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 -}