mirror of https://github.com/containers/podman.git
vendor: update to latest c/common
Fixes a flake in the system tests during image listing. Signed-off-by: Paul Holzinger <pholzing@redhat.com>
This commit is contained in:
parent
48423a615d
commit
86d7da8b08
6
go.mod
6
go.mod
|
@ -13,7 +13,7 @@ require (
|
|||
github.com/checkpoint-restore/go-criu/v7 v7.2.0
|
||||
github.com/containernetworking/plugins v1.6.2
|
||||
github.com/containers/buildah v1.39.1-0.20250401180830-32d78c69be17
|
||||
github.com/containers/common v0.62.4-0.20250401165412-9b0d134f392f
|
||||
github.com/containers/common v0.62.4-0.20250411145332-dd8ab77b8e26
|
||||
github.com/containers/conmon v2.0.20+incompatible
|
||||
github.com/containers/gvisor-tap-vsock v0.8.5
|
||||
github.com/containers/image/v5 v5.34.4-0.20250327193514-d3b241257363
|
||||
|
@ -97,7 +97,7 @@ require (
|
|||
github.com/containerd/platforms v1.0.0-rc.1 // indirect
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.16.3 // indirect
|
||||
github.com/containerd/typeurl/v2 v2.2.3 // indirect
|
||||
github.com/containernetworking/cni v1.2.3 // indirect
|
||||
github.com/containernetworking/cni v1.3.0 // indirect
|
||||
github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01 // indirect
|
||||
github.com/containers/luksy v0.0.0-20250217190002-40bd943d93b8 // indirect
|
||||
github.com/coreos/go-oidc/v3 v3.12.0 // indirect
|
||||
|
@ -110,7 +110,7 @@ require (
|
|||
github.com/docker/docker-credential-helpers v0.9.3 // indirect
|
||||
github.com/ebitengine/purego v0.8.2 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/fsnotify/fsnotify v1.8.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||
github.com/fsouza/go-dockerclient v1.12.0 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.0.5 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
|
|
12
go.sum
12
go.sum
|
@ -62,14 +62,14 @@ github.com/containerd/stargz-snapshotter/estargz v0.16.3 h1:7evrXtoh1mSbGj/pfRcc
|
|||
github.com/containerd/stargz-snapshotter/estargz v0.16.3/go.mod h1:uyr4BfYfOj3G9WBVE8cOlQmXAbPN9VEQpBBeJIuOipU=
|
||||
github.com/containerd/typeurl/v2 v2.2.3 h1:yNA/94zxWdvYACdYO8zofhrTVuQY73fFU1y++dYSw40=
|
||||
github.com/containerd/typeurl/v2 v2.2.3/go.mod h1:95ljDnPfD3bAbDJRugOiShd/DlAAsxGtUBhJxIn7SCk=
|
||||
github.com/containernetworking/cni v1.2.3 h1:hhOcjNVUQTnzdRJ6alC5XF+wd9mfGIUaj8FuJbEslXM=
|
||||
github.com/containernetworking/cni v1.2.3/go.mod h1:DuLgF+aPd3DzcTQTtp/Nvl1Kim23oFKdm2okJzBQA5M=
|
||||
github.com/containernetworking/cni v1.3.0 h1:v6EpN8RznAZj9765HhXQrtXgX+ECGebEYEmnuFjskwo=
|
||||
github.com/containernetworking/cni v1.3.0/go.mod h1:Bs8glZjjFfGPHMw6hQu82RUgEPNGEaBb9KS5KtNMnJ4=
|
||||
github.com/containernetworking/plugins v1.6.2 h1:pqP8Mq923TLyef5g97XfJ/xpDeVek4yF8A4mzy9Tc4U=
|
||||
github.com/containernetworking/plugins v1.6.2/go.mod h1:SP5UG3jDO9LtmfbBJdP+nl3A1atOtbj2MBOYsnaxy64=
|
||||
github.com/containers/buildah v1.39.1-0.20250401180830-32d78c69be17 h1:mfnd0BqHdV8p6+pol7SelIEgG7NbJQYW4IPywGakmCY=
|
||||
github.com/containers/buildah v1.39.1-0.20250401180830-32d78c69be17/go.mod h1:AvIRsFvWfSuMNGoMm8hXFaOGvzhsnujWz1C38rk+mk4=
|
||||
github.com/containers/common v0.62.4-0.20250401165412-9b0d134f392f h1:gpfCIBHhMAzJGEPjCrCAS18Pop+RgQSK38Grre/+gqQ=
|
||||
github.com/containers/common v0.62.4-0.20250401165412-9b0d134f392f/go.mod h1:B6J/JCUcc9vgeoBFFzL5QGszpa7SoVK+mAyUNXSWI6s=
|
||||
github.com/containers/common v0.62.4-0.20250411145332-dd8ab77b8e26 h1:IWIKClnY/k079271gn1gusiWJ2jxTVS8wu3O9ILRXYs=
|
||||
github.com/containers/common v0.62.4-0.20250411145332-dd8ab77b8e26/go.mod h1:nwl0g83V6WB7Sg7U++EcosW4JVV+OgqUaSXgI7NfjAA=
|
||||
github.com/containers/conmon v2.0.20+incompatible h1:YbCVSFSCqFjjVwHTPINGdMX1F6JXHGTUje2ZYobNrkg=
|
||||
github.com/containers/conmon v2.0.20+incompatible/go.mod h1:hgwZ2mtuDrppv78a/cOBNiCm6O0UMWGx1mu7P00nu5I=
|
||||
github.com/containers/gvisor-tap-vsock v0.8.5 h1:s7PA8znsZ4mamev5nNLsQqduYSlz1Ze5TWjfXnAfpEs=
|
||||
|
@ -146,8 +146,8 @@ github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4Nij
|
|||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
||||
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
|
||||
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/fsouza/go-dockerclient v1.12.0 h1:S2f2crEUbBNCFiF06kR/GvioEB8EMsb3Td/bpawD+aU=
|
||||
github.com/fsouza/go-dockerclient v1.12.0/go.mod h1:YWUtjg8japrqD/80L98nTtCoxQFp5B5wrSsnyeB5lFo=
|
||||
github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE=
|
||||
|
|
|
@ -67,18 +67,23 @@ type RuntimeConf struct {
|
|||
CacheDir string
|
||||
}
|
||||
|
||||
type NetworkConfig struct {
|
||||
Network *types.NetConf
|
||||
// Use PluginConfig instead of NetworkConfig, the NetworkConfig
|
||||
// backwards-compat alias will be removed in a future release.
|
||||
type NetworkConfig = PluginConfig
|
||||
|
||||
type PluginConfig struct {
|
||||
Network *types.PluginConf
|
||||
Bytes []byte
|
||||
}
|
||||
|
||||
type NetworkConfigList struct {
|
||||
Name string
|
||||
CNIVersion string
|
||||
DisableCheck bool
|
||||
DisableGC bool
|
||||
Plugins []*NetworkConfig
|
||||
Bytes []byte
|
||||
Name string
|
||||
CNIVersion string
|
||||
DisableCheck bool
|
||||
DisableGC bool
|
||||
LoadOnlyInlinedPlugins bool
|
||||
Plugins []*PluginConfig
|
||||
Bytes []byte
|
||||
}
|
||||
|
||||
type NetworkAttachment struct {
|
||||
|
@ -102,14 +107,14 @@ type CNI interface {
|
|||
GetNetworkListCachedResult(net *NetworkConfigList, rt *RuntimeConf) (types.Result, error)
|
||||
GetNetworkListCachedConfig(net *NetworkConfigList, rt *RuntimeConf) ([]byte, *RuntimeConf, error)
|
||||
|
||||
AddNetwork(ctx context.Context, net *NetworkConfig, rt *RuntimeConf) (types.Result, error)
|
||||
CheckNetwork(ctx context.Context, net *NetworkConfig, rt *RuntimeConf) error
|
||||
DelNetwork(ctx context.Context, net *NetworkConfig, rt *RuntimeConf) error
|
||||
GetNetworkCachedResult(net *NetworkConfig, rt *RuntimeConf) (types.Result, error)
|
||||
GetNetworkCachedConfig(net *NetworkConfig, rt *RuntimeConf) ([]byte, *RuntimeConf, error)
|
||||
AddNetwork(ctx context.Context, net *PluginConfig, rt *RuntimeConf) (types.Result, error)
|
||||
CheckNetwork(ctx context.Context, net *PluginConfig, rt *RuntimeConf) error
|
||||
DelNetwork(ctx context.Context, net *PluginConfig, rt *RuntimeConf) error
|
||||
GetNetworkCachedResult(net *PluginConfig, rt *RuntimeConf) (types.Result, error)
|
||||
GetNetworkCachedConfig(net *PluginConfig, rt *RuntimeConf) ([]byte, *RuntimeConf, error)
|
||||
|
||||
ValidateNetworkList(ctx context.Context, net *NetworkConfigList) ([]string, error)
|
||||
ValidateNetwork(ctx context.Context, net *NetworkConfig) ([]string, error)
|
||||
ValidateNetwork(ctx context.Context, net *PluginConfig) ([]string, error)
|
||||
|
||||
GCNetworkList(ctx context.Context, net *NetworkConfigList, args *GCArgs) error
|
||||
GetStatusNetworkList(ctx context.Context, net *NetworkConfigList) error
|
||||
|
@ -147,7 +152,7 @@ func NewCNIConfigWithCacheDir(path []string, cacheDir string, exec invoke.Exec)
|
|||
}
|
||||
}
|
||||
|
||||
func buildOneConfig(name, cniVersion string, orig *NetworkConfig, prevResult types.Result, rt *RuntimeConf) (*NetworkConfig, error) {
|
||||
func buildOneConfig(name, cniVersion string, orig *PluginConfig, prevResult types.Result, rt *RuntimeConf) (*PluginConfig, error) {
|
||||
var err error
|
||||
|
||||
inject := map[string]interface{}{
|
||||
|
@ -183,7 +188,7 @@ func buildOneConfig(name, cniVersion string, orig *NetworkConfig, prevResult typ
|
|||
// capabilities include "portMappings", and the CapabilityArgs map includes a
|
||||
// "portMappings" key, that key and its value are added to the "runtimeConfig"
|
||||
// dictionary to be passed to the plugin's stdin.
|
||||
func injectRuntimeConfig(orig *NetworkConfig, rt *RuntimeConf) (*NetworkConfig, error) {
|
||||
func injectRuntimeConfig(orig *PluginConfig, rt *RuntimeConf) (*PluginConfig, error) {
|
||||
var err error
|
||||
|
||||
rc := make(map[string]interface{})
|
||||
|
@ -404,7 +409,7 @@ func (c *CNIConfig) GetNetworkListCachedResult(list *NetworkConfigList, rt *Runt
|
|||
|
||||
// GetNetworkCachedResult returns the cached Result of the previous
|
||||
// AddNetwork() operation for a network, or an error.
|
||||
func (c *CNIConfig) GetNetworkCachedResult(net *NetworkConfig, rt *RuntimeConf) (types.Result, error) {
|
||||
func (c *CNIConfig) GetNetworkCachedResult(net *PluginConfig, rt *RuntimeConf) (types.Result, error) {
|
||||
return c.getCachedResult(net.Network.Name, net.Network.CNIVersion, rt)
|
||||
}
|
||||
|
||||
|
@ -416,7 +421,7 @@ func (c *CNIConfig) GetNetworkListCachedConfig(list *NetworkConfigList, rt *Runt
|
|||
|
||||
// GetNetworkCachedConfig copies the input RuntimeConf to output
|
||||
// RuntimeConf with fields updated with info from the cached Config.
|
||||
func (c *CNIConfig) GetNetworkCachedConfig(net *NetworkConfig, rt *RuntimeConf) ([]byte, *RuntimeConf, error) {
|
||||
func (c *CNIConfig) GetNetworkCachedConfig(net *PluginConfig, rt *RuntimeConf) ([]byte, *RuntimeConf, error) {
|
||||
return c.getCachedConfig(net.Network.Name, rt)
|
||||
}
|
||||
|
||||
|
@ -482,7 +487,7 @@ func (c *CNIConfig) GetCachedAttachments(containerID string) ([]*NetworkAttachme
|
|||
return attachments, nil
|
||||
}
|
||||
|
||||
func (c *CNIConfig) addNetwork(ctx context.Context, name, cniVersion string, net *NetworkConfig, prevResult types.Result, rt *RuntimeConf) (types.Result, error) {
|
||||
func (c *CNIConfig) addNetwork(ctx context.Context, name, cniVersion string, net *PluginConfig, prevResult types.Result, rt *RuntimeConf) (types.Result, error) {
|
||||
c.ensureExec()
|
||||
pluginPath, err := c.exec.FindInPath(net.Network.Type, c.Path)
|
||||
if err != nil {
|
||||
|
@ -524,7 +529,7 @@ func (c *CNIConfig) AddNetworkList(ctx context.Context, list *NetworkConfigList,
|
|||
return result, nil
|
||||
}
|
||||
|
||||
func (c *CNIConfig) checkNetwork(ctx context.Context, name, cniVersion string, net *NetworkConfig, prevResult types.Result, rt *RuntimeConf) error {
|
||||
func (c *CNIConfig) checkNetwork(ctx context.Context, name, cniVersion string, net *PluginConfig, prevResult types.Result, rt *RuntimeConf) error {
|
||||
c.ensureExec()
|
||||
pluginPath, err := c.exec.FindInPath(net.Network.Type, c.Path)
|
||||
if err != nil {
|
||||
|
@ -566,7 +571,7 @@ func (c *CNIConfig) CheckNetworkList(ctx context.Context, list *NetworkConfigLis
|
|||
return nil
|
||||
}
|
||||
|
||||
func (c *CNIConfig) delNetwork(ctx context.Context, name, cniVersion string, net *NetworkConfig, prevResult types.Result, rt *RuntimeConf) error {
|
||||
func (c *CNIConfig) delNetwork(ctx context.Context, name, cniVersion string, net *PluginConfig, prevResult types.Result, rt *RuntimeConf) error {
|
||||
c.ensureExec()
|
||||
pluginPath, err := c.exec.FindInPath(net.Network.Type, c.Path)
|
||||
if err != nil {
|
||||
|
@ -607,7 +612,7 @@ func (c *CNIConfig) DelNetworkList(ctx context.Context, list *NetworkConfigList,
|
|||
return nil
|
||||
}
|
||||
|
||||
func pluginDescription(net *types.NetConf) string {
|
||||
func pluginDescription(net *types.PluginConf) string {
|
||||
if net == nil {
|
||||
return "<missing>"
|
||||
}
|
||||
|
@ -621,7 +626,7 @@ func pluginDescription(net *types.NetConf) string {
|
|||
}
|
||||
|
||||
// AddNetwork executes the plugin with the ADD command
|
||||
func (c *CNIConfig) AddNetwork(ctx context.Context, net *NetworkConfig, rt *RuntimeConf) (types.Result, error) {
|
||||
func (c *CNIConfig) AddNetwork(ctx context.Context, net *PluginConfig, rt *RuntimeConf) (types.Result, error) {
|
||||
result, err := c.addNetwork(ctx, net.Network.Name, net.Network.CNIVersion, net, nil, rt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -635,7 +640,7 @@ func (c *CNIConfig) AddNetwork(ctx context.Context, net *NetworkConfig, rt *Runt
|
|||
}
|
||||
|
||||
// CheckNetwork executes the plugin with the CHECK command
|
||||
func (c *CNIConfig) CheckNetwork(ctx context.Context, net *NetworkConfig, rt *RuntimeConf) error {
|
||||
func (c *CNIConfig) CheckNetwork(ctx context.Context, net *PluginConfig, rt *RuntimeConf) error {
|
||||
// CHECK was added in CNI spec version 0.4.0 and higher
|
||||
if gtet, err := version.GreaterThanOrEqualTo(net.Network.CNIVersion, "0.4.0"); err != nil {
|
||||
return err
|
||||
|
@ -651,7 +656,7 @@ func (c *CNIConfig) CheckNetwork(ctx context.Context, net *NetworkConfig, rt *Ru
|
|||
}
|
||||
|
||||
// DelNetwork executes the plugin with the DEL command
|
||||
func (c *CNIConfig) DelNetwork(ctx context.Context, net *NetworkConfig, rt *RuntimeConf) error {
|
||||
func (c *CNIConfig) DelNetwork(ctx context.Context, net *PluginConfig, rt *RuntimeConf) error {
|
||||
var cachedResult types.Result
|
||||
|
||||
// Cached result on DEL was added in CNI spec version 0.4.0 and higher
|
||||
|
@ -711,7 +716,7 @@ func (c *CNIConfig) ValidateNetworkList(ctx context.Context, list *NetworkConfig
|
|||
// ValidateNetwork checks that a configuration is reasonably valid.
|
||||
// It uses the same logic as ValidateNetworkList)
|
||||
// Returns a list of capabilities
|
||||
func (c *CNIConfig) ValidateNetwork(ctx context.Context, net *NetworkConfig) ([]string, error) {
|
||||
func (c *CNIConfig) ValidateNetwork(ctx context.Context, net *PluginConfig) ([]string, error) {
|
||||
caps := []string{}
|
||||
for c, ok := range net.Network.Capabilities {
|
||||
if ok {
|
||||
|
@ -836,7 +841,7 @@ func (c *CNIConfig) GCNetworkList(ctx context.Context, list *NetworkConfigList,
|
|||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
func (c *CNIConfig) gcNetwork(ctx context.Context, net *NetworkConfig) error {
|
||||
func (c *CNIConfig) gcNetwork(ctx context.Context, net *PluginConfig) error {
|
||||
c.ensureExec()
|
||||
pluginPath, err := c.exec.FindInPath(net.Network.Type, c.Path)
|
||||
if err != nil {
|
||||
|
@ -871,7 +876,7 @@ func (c *CNIConfig) GetStatusNetworkList(ctx context.Context, list *NetworkConfi
|
|||
return nil
|
||||
}
|
||||
|
||||
func (c *CNIConfig) getStatusNetwork(ctx context.Context, net *NetworkConfig) error {
|
||||
func (c *CNIConfig) getStatusNetwork(ctx context.Context, net *PluginConfig) error {
|
||||
c.ensureExec()
|
||||
pluginPath, err := c.exec.FindInPath(net.Network.Type, c.Path)
|
||||
if err != nil {
|
||||
|
|
|
@ -45,9 +45,16 @@ func (e NoConfigsFoundError) Error() string {
|
|||
return fmt.Sprintf(`no net configurations found in %s`, e.Dir)
|
||||
}
|
||||
|
||||
func ConfFromBytes(bytes []byte) (*NetworkConfig, error) {
|
||||
conf := &NetworkConfig{Bytes: bytes, Network: &types.NetConf{}}
|
||||
if err := json.Unmarshal(bytes, conf.Network); err != nil {
|
||||
// This will not validate that the plugins actually belong to the netconfig by ensuring
|
||||
// that they are loaded from a directory named after the networkName, relative to the network config.
|
||||
//
|
||||
// Since here we are just accepting raw bytes, the caller is responsible for ensuring that the plugin
|
||||
// config provided here actually "belongs" to the networkconfig in question.
|
||||
func NetworkPluginConfFromBytes(pluginConfBytes []byte) (*PluginConfig, error) {
|
||||
// TODO why are we creating a struct that holds both the byte representation and the deserialized
|
||||
// representation, and returning that, instead of just returning the deserialized representation?
|
||||
conf := &PluginConfig{Bytes: pluginConfBytes, Network: &types.PluginConf{}}
|
||||
if err := json.Unmarshal(pluginConfBytes, conf.Network); err != nil {
|
||||
return nil, fmt.Errorf("error parsing configuration: %w", err)
|
||||
}
|
||||
if conf.Network.Type == "" {
|
||||
|
@ -56,17 +63,35 @@ func ConfFromBytes(bytes []byte) (*NetworkConfig, error) {
|
|||
return conf, nil
|
||||
}
|
||||
|
||||
func ConfFromFile(filename string) (*NetworkConfig, error) {
|
||||
bytes, err := os.ReadFile(filename)
|
||||
// Given a path to a directory containing a network configuration, and the name of a network,
|
||||
// loads all plugin definitions found at path `networkConfPath/networkName/*.conf`
|
||||
func NetworkPluginConfsFromFiles(networkConfPath, networkName string) ([]*PluginConfig, error) {
|
||||
var pConfs []*PluginConfig
|
||||
|
||||
pluginConfPath := filepath.Join(networkConfPath, networkName)
|
||||
|
||||
pluginConfFiles, err := ConfFiles(pluginConfPath, []string{".conf"})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading %s: %w", filename, err)
|
||||
return nil, fmt.Errorf("failed to read plugin config files in %s: %w", pluginConfPath, err)
|
||||
}
|
||||
return ConfFromBytes(bytes)
|
||||
|
||||
for _, pluginConfFile := range pluginConfFiles {
|
||||
pluginConfBytes, err := os.ReadFile(pluginConfFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading %s: %w", pluginConfFile, err)
|
||||
}
|
||||
pluginConf, err := NetworkPluginConfFromBytes(pluginConfBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pConfs = append(pConfs, pluginConf)
|
||||
}
|
||||
return pConfs, nil
|
||||
}
|
||||
|
||||
func ConfListFromBytes(bytes []byte) (*NetworkConfigList, error) {
|
||||
func NetworkConfFromBytes(confBytes []byte) (*NetworkConfigList, error) {
|
||||
rawList := make(map[string]interface{})
|
||||
if err := json.Unmarshal(bytes, &rawList); err != nil {
|
||||
if err := json.Unmarshal(confBytes, &rawList); err != nil {
|
||||
return nil, fmt.Errorf("error parsing configuration list: %w", err)
|
||||
}
|
||||
|
||||
|
@ -167,19 +192,36 @@ func ConfListFromBytes(bytes []byte) (*NetworkConfigList, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
loadOnlyInlinedPlugins, err := readBool("loadOnlyInlinedPlugins")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
list := &NetworkConfigList{
|
||||
Name: name,
|
||||
DisableCheck: disableCheck,
|
||||
DisableGC: disableGC,
|
||||
CNIVersion: cniVersion,
|
||||
Bytes: bytes,
|
||||
Name: name,
|
||||
DisableCheck: disableCheck,
|
||||
DisableGC: disableGC,
|
||||
LoadOnlyInlinedPlugins: loadOnlyInlinedPlugins,
|
||||
CNIVersion: cniVersion,
|
||||
Bytes: confBytes,
|
||||
}
|
||||
|
||||
var plugins []interface{}
|
||||
plug, ok := rawList["plugins"]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("error parsing configuration list: no 'plugins' key")
|
||||
// We can have a `plugins` list key in the main conf,
|
||||
// We can also have `loadOnlyInlinedPlugins == true`
|
||||
//
|
||||
// If `plugins` is there, then `loadOnlyInlinedPlugins` can be true
|
||||
//
|
||||
// If plugins is NOT there, then `loadOnlyInlinedPlugins` cannot be true
|
||||
//
|
||||
// We have to have at least some plugins.
|
||||
if !ok && loadOnlyInlinedPlugins {
|
||||
return nil, fmt.Errorf("error parsing configuration list: `loadOnlyInlinedPlugins` is true, and no 'plugins' key")
|
||||
} else if !ok && !loadOnlyInlinedPlugins {
|
||||
return list, nil
|
||||
}
|
||||
|
||||
plugins, ok = plug.([]interface{})
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("error parsing configuration list: invalid 'plugins' type %T", plug)
|
||||
|
@ -199,24 +241,68 @@ func ConfListFromBytes(bytes []byte) (*NetworkConfigList, error) {
|
|||
}
|
||||
list.Plugins = append(list.Plugins, netConf)
|
||||
}
|
||||
|
||||
return list, nil
|
||||
}
|
||||
|
||||
func ConfListFromFile(filename string) (*NetworkConfigList, error) {
|
||||
func NetworkConfFromFile(filename string) (*NetworkConfigList, error) {
|
||||
bytes, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading %s: %w", filename, err)
|
||||
}
|
||||
return ConfListFromBytes(bytes)
|
||||
|
||||
conf, err := NetworkConfFromBytes(bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !conf.LoadOnlyInlinedPlugins {
|
||||
plugins, err := NetworkPluginConfsFromFiles(filepath.Dir(filename), conf.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conf.Plugins = append(conf.Plugins, plugins...)
|
||||
}
|
||||
|
||||
if len(conf.Plugins) == 0 {
|
||||
// Having 0 plugins for a given network is not necessarily a problem,
|
||||
// but return as error for caller to decide, since they tried to load
|
||||
return nil, fmt.Errorf("no plugin configs found")
|
||||
}
|
||||
return conf, nil
|
||||
}
|
||||
|
||||
// Deprecated: This file format is no longer supported, use NetworkConfXXX and NetworkPluginXXX functions
|
||||
func ConfFromBytes(bytes []byte) (*NetworkConfig, error) {
|
||||
return NetworkPluginConfFromBytes(bytes)
|
||||
}
|
||||
|
||||
// Deprecated: This file format is no longer supported, use NetworkConfXXX and NetworkPluginXXX functions
|
||||
func ConfFromFile(filename string) (*NetworkConfig, error) {
|
||||
bytes, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading %s: %w", filename, err)
|
||||
}
|
||||
return ConfFromBytes(bytes)
|
||||
}
|
||||
|
||||
func ConfListFromBytes(bytes []byte) (*NetworkConfigList, error) {
|
||||
return NetworkConfFromBytes(bytes)
|
||||
}
|
||||
|
||||
func ConfListFromFile(filename string) (*NetworkConfigList, error) {
|
||||
return NetworkConfFromFile(filename)
|
||||
}
|
||||
|
||||
// ConfFiles simply returns a slice of all files in the provided directory
|
||||
// with extensions matching the provided set.
|
||||
func ConfFiles(dir string, extensions []string) ([]string, error) {
|
||||
// In part, adapted from rkt/networking/podenv.go#listFiles
|
||||
files, err := os.ReadDir(dir)
|
||||
switch {
|
||||
case err == nil: // break
|
||||
case os.IsNotExist(err):
|
||||
// If folder not there, return no error - only return an
|
||||
// error if we cannot read contents or there are no contents.
|
||||
return nil, nil
|
||||
default:
|
||||
return nil, err
|
||||
|
@ -237,6 +323,7 @@ func ConfFiles(dir string, extensions []string) ([]string, error) {
|
|||
return confFiles, nil
|
||||
}
|
||||
|
||||
// Deprecated: This file format is no longer supported, use NetworkConfXXX and NetworkPluginXXX functions
|
||||
func LoadConf(dir, name string) (*NetworkConfig, error) {
|
||||
files, err := ConfFiles(dir, []string{".conf", ".json"})
|
||||
switch {
|
||||
|
@ -260,6 +347,15 @@ func LoadConf(dir, name string) (*NetworkConfig, error) {
|
|||
}
|
||||
|
||||
func LoadConfList(dir, name string) (*NetworkConfigList, error) {
|
||||
return LoadNetworkConf(dir, name)
|
||||
}
|
||||
|
||||
// LoadNetworkConf looks at all the network configs in a given dir,
|
||||
// loads and parses them all, and returns the first one with an extension of `.conf`
|
||||
// that matches the provided network name predicate.
|
||||
func LoadNetworkConf(dir, name string) (*NetworkConfigList, error) {
|
||||
// TODO this .conflist/.conf extension thing is confusing and inexact
|
||||
// for implementors. We should pick one extension for everything and stick with it.
|
||||
files, err := ConfFiles(dir, []string{".conflist"})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -267,7 +363,7 @@ func LoadConfList(dir, name string) (*NetworkConfigList, error) {
|
|||
sort.Strings(files)
|
||||
|
||||
for _, confFile := range files {
|
||||
conf, err := ConfListFromFile(confFile)
|
||||
conf, err := NetworkConfFromFile(confFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -276,7 +372,7 @@ func LoadConfList(dir, name string) (*NetworkConfigList, error) {
|
|||
}
|
||||
}
|
||||
|
||||
// Try and load a network configuration file (instead of list)
|
||||
// Deprecated: Try and load a network configuration file (instead of list)
|
||||
// from the same name, then upconvert.
|
||||
singleConf, err := LoadConf(dir, name)
|
||||
if err != nil {
|
||||
|
@ -292,7 +388,8 @@ func LoadConfList(dir, name string) (*NetworkConfigList, error) {
|
|||
return ConfListFromConf(singleConf)
|
||||
}
|
||||
|
||||
func InjectConf(original *NetworkConfig, newValues map[string]interface{}) (*NetworkConfig, error) {
|
||||
// InjectConf takes a PluginConfig and inserts additional values into it, ensuring the result is serializable.
|
||||
func InjectConf(original *PluginConfig, newValues map[string]interface{}) (*PluginConfig, error) {
|
||||
config := make(map[string]interface{})
|
||||
err := json.Unmarshal(original.Bytes, &config)
|
||||
if err != nil {
|
||||
|
@ -316,12 +413,14 @@ func InjectConf(original *NetworkConfig, newValues map[string]interface{}) (*Net
|
|||
return nil, err
|
||||
}
|
||||
|
||||
return ConfFromBytes(newBytes)
|
||||
return NetworkPluginConfFromBytes(newBytes)
|
||||
}
|
||||
|
||||
// ConfListFromConf "upconverts" a network config in to a NetworkConfigList,
|
||||
// with the single network as the only entry in the list.
|
||||
func ConfListFromConf(original *NetworkConfig) (*NetworkConfigList, error) {
|
||||
//
|
||||
// Deprecated: Non-conflist file formats are unsupported, use NetworkConfXXX and NetworkPluginXXX functions
|
||||
func ConfListFromConf(original *PluginConfig) (*NetworkConfigList, error) {
|
||||
// Re-deserialize the config's json, then make a raw map configlist.
|
||||
// This may seem a bit strange, but it's to make the Bytes fields
|
||||
// actually make sense. Otherwise, the generated json is littered with
|
||||
|
|
|
@ -56,8 +56,12 @@ func (n *IPNet) UnmarshalJSON(data []byte) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// NetConfType describes a network.
|
||||
type NetConfType struct {
|
||||
// Use PluginConf instead of NetConf, the NetConf
|
||||
// backwards-compat alias will be removed in a future release.
|
||||
type NetConf = PluginConf
|
||||
|
||||
// PluginConf describes a plugin configuration for a specific network.
|
||||
type PluginConf struct {
|
||||
CNIVersion string `json:"cniVersion,omitempty"`
|
||||
|
||||
Name string `json:"name,omitempty"`
|
||||
|
@ -73,9 +77,6 @@ type NetConfType struct {
|
|||
ValidAttachments []GCAttachment `json:"cni.dev/valid-attachments,omitempty"`
|
||||
}
|
||||
|
||||
// NetConf is defined as different type as custom MarshalJSON() and issue #1096
|
||||
type NetConf NetConfType
|
||||
|
||||
// GCAttachment is the parameters to a GC call -- namely,
|
||||
// the container ID and ifname pair that represents a
|
||||
// still-valid attachment.
|
||||
|
@ -86,11 +87,8 @@ type GCAttachment struct {
|
|||
|
||||
// Note: DNS should be omit if DNS is empty but default Marshal function
|
||||
// will output empty structure hence need to write a Marshal function
|
||||
func (n *NetConfType) MarshalJSON() ([]byte, error) {
|
||||
// use type alias to escape recursion for json.Marshal() to MarshalJSON()
|
||||
type fixObjType = NetConf
|
||||
|
||||
bytes, err := json.Marshal(fixObjType(*n))
|
||||
func (n *PluginConf) MarshalJSON() ([]byte, error) {
|
||||
bytes, err := json.Marshal(*n)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -120,10 +118,10 @@ func (i *IPAM) IsEmpty() bool {
|
|||
type NetConfList struct {
|
||||
CNIVersion string `json:"cniVersion,omitempty"`
|
||||
|
||||
Name string `json:"name,omitempty"`
|
||||
DisableCheck bool `json:"disableCheck,omitempty"`
|
||||
DisableGC bool `json:"disableGC,omitempty"`
|
||||
Plugins []*NetConf `json:"plugins,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
DisableCheck bool `json:"disableCheck,omitempty"`
|
||||
DisableGC bool `json:"disableGC,omitempty"`
|
||||
Plugins []*PluginConf `json:"plugins,omitempty"`
|
||||
}
|
||||
|
||||
// Result is an interface that provides the result of plugin execution
|
||||
|
|
|
@ -63,7 +63,7 @@ func NewResult(version string, resultBytes []byte) (types.Result, error) {
|
|||
|
||||
// ParsePrevResult parses a prevResult in a NetConf structure and sets
|
||||
// the NetConf's PrevResult member to the parsed Result object.
|
||||
func ParsePrevResult(conf *types.NetConf) error {
|
||||
func ParsePrevResult(conf *types.PluginConf) error {
|
||||
if conf.RawPrevResult == nil {
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ func (i *Image) History(ctx context.Context) ([]ImageHistory, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
layerTree, err := i.runtime.newFreshLayerTree(ctx)
|
||||
layerTree, err := i.runtime.newFreshLayerTree()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -197,7 +197,7 @@ func (i *Image) IsDangling(ctx context.Context) (bool, error) {
|
|||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
tree, err := i.runtime.newLayerTreeFromData(ctx, images, layers, true)
|
||||
tree, err := i.runtime.newLayerTreeFromData(images, layers, true)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
@ -267,7 +267,7 @@ func (i *Image) TopLayer() string {
|
|||
|
||||
// Parent returns the parent image or nil if there is none
|
||||
func (i *Image) Parent(ctx context.Context) (*Image, error) {
|
||||
tree, err := i.runtime.newFreshLayerTree(ctx)
|
||||
tree, err := i.runtime.newFreshLayerTree()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -301,7 +301,7 @@ func (i *Image) Children(ctx context.Context) ([]*Image, error) {
|
|||
// created for this invocation only.
|
||||
func (i *Image) getChildren(ctx context.Context, all bool, tree *layerTree) ([]*Image, error) {
|
||||
if tree == nil {
|
||||
t, err := i.runtime.newFreshLayerTree(ctx)
|
||||
t, err := i.runtime.newFreshLayerTree()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@ func (i *Image) Tree(ctx context.Context, traverseChildren bool) (string, error)
|
|||
fmt.Fprintf(sb, "No Image Layers")
|
||||
}
|
||||
|
||||
layerTree, err := i.runtime.newFreshLayerTree(ctx)
|
||||
layerTree, err := i.runtime.newFreshLayerTree()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
|
|
@ -95,17 +95,17 @@ func (l *layerNode) repoTags() ([]string, error) {
|
|||
}
|
||||
|
||||
// newFreshLayerTree extracts a layerTree from consistent layers and images in the local storage.
|
||||
func (r *Runtime) newFreshLayerTree(ctx context.Context) (*layerTree, error) {
|
||||
func (r *Runtime) newFreshLayerTree() (*layerTree, error) {
|
||||
images, layers, err := r.getImagesAndLayers()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return r.newLayerTreeFromData(ctx, images, layers, false)
|
||||
return r.newLayerTreeFromData(images, layers, false)
|
||||
}
|
||||
|
||||
// newLayerTreeFromData extracts a layerTree from the given the layers and images.
|
||||
// The caller is responsible for (layers, images) being consistent.
|
||||
func (r *Runtime) newLayerTreeFromData(ctx context.Context, images []*Image, layers []storage.Layer, generateManifestDigestList bool) (*layerTree, error) {
|
||||
func (r *Runtime) newLayerTreeFromData(images []*Image, layers []storage.Layer, generateManifestDigestList bool) (*layerTree, error) {
|
||||
tree := layerTree{
|
||||
nodes: make(map[string]*layerNode),
|
||||
ociCache: make(map[string]*ociv1.Image),
|
||||
|
@ -136,14 +136,23 @@ func (r *Runtime) newLayerTreeFromData(ctx context.Context, images []*Image, lay
|
|||
if !generateManifestDigestList {
|
||||
continue
|
||||
}
|
||||
if manifestList, _ := img.IsManifestList(ctx); manifestList {
|
||||
mlist, err := img.ToManifestList()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, digest := range mlist.list.Instances() {
|
||||
tree.manifestListDigests[digest] = struct{}{}
|
||||
// ignore errors, common errors are
|
||||
// - image is not manifest
|
||||
// - image has been removed from the store in the meantime
|
||||
// In all cases we should ensure image listing still works and not error out.
|
||||
mlist, err := img.ToManifestList()
|
||||
if err != nil {
|
||||
// If it is not a manifest it likely is a regular image so just ignore it.
|
||||
// If the image is unknown that likely means there was a race where the image/manifest
|
||||
// was removed after out MultiList() call so we ignore that as well.
|
||||
if errors.Is(err, ErrNotAManifestList) || errors.Is(err, storageTypes.ErrImageUnknown) {
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, digest := range mlist.list.Instances() {
|
||||
tree.manifestListDigests[digest] = struct{}{}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
|
|
@ -149,7 +149,7 @@ func LoadFromImage(store storage.Store, image string) (string, List, error) {
|
|||
}
|
||||
manifestList, err := manifests.FromBlob(manifestBytes)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
return "", nil, fmt.Errorf("decoding manifest blob for image %q: %w", image, err)
|
||||
}
|
||||
list := &list{
|
||||
List: manifestList,
|
||||
|
|
|
@ -634,7 +634,7 @@ func (r *Runtime) ListImages(ctx context.Context, options *ListImagesOptions) ([
|
|||
|
||||
var tree *layerTree
|
||||
if needsLayerTree {
|
||||
tree, err = r.newLayerTreeFromData(ctx, images, snapshot.Layers, true)
|
||||
tree, err = r.newLayerTreeFromData(images, snapshot.Layers, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -533,7 +533,6 @@ func (c *CgroupControl) Stat() (*cgroups.Stats, error) {
|
|||
if !errors.Is(err, os.ErrNotExist) {
|
||||
return nil, err
|
||||
}
|
||||
logrus.Warningf("Failed to retrieve cgroup stats: %v", err)
|
||||
continue
|
||||
}
|
||||
found = true
|
||||
|
|
|
@ -145,6 +145,7 @@ func DefaultProfile() *Seccomp {
|
|||
"fadvise64",
|
||||
"fadvise64_64",
|
||||
"fallocate",
|
||||
"fanotify_init",
|
||||
"fanotify_mark",
|
||||
"fchdir",
|
||||
"fchmod",
|
||||
|
@ -614,7 +615,6 @@ func DefaultProfile() *Seccomp {
|
|||
{
|
||||
Names: []string{
|
||||
"bpf",
|
||||
"fanotify_init",
|
||||
"lookup_dcookie",
|
||||
"quotactl",
|
||||
"quotactl_fd",
|
||||
|
@ -630,7 +630,6 @@ func DefaultProfile() *Seccomp {
|
|||
},
|
||||
{
|
||||
Names: []string{
|
||||
"fanotify_init",
|
||||
"lookup_dcookie",
|
||||
"perf_event_open",
|
||||
"quotactl",
|
||||
|
|
|
@ -152,6 +152,7 @@
|
|||
"fadvise64",
|
||||
"fadvise64_64",
|
||||
"fallocate",
|
||||
"fanotify_init",
|
||||
"fanotify_mark",
|
||||
"fchdir",
|
||||
"fchmod",
|
||||
|
@ -691,7 +692,6 @@
|
|||
{
|
||||
"names": [
|
||||
"bpf",
|
||||
"fanotify_init",
|
||||
"lookup_dcookie",
|
||||
"quotactl",
|
||||
"quotactl_fd",
|
||||
|
@ -711,7 +711,6 @@
|
|||
},
|
||||
{
|
||||
"names": [
|
||||
"fanotify_init",
|
||||
"lookup_dcookie",
|
||||
"perf_event_open",
|
||||
"quotactl",
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
freebsd_task:
|
||||
name: 'FreeBSD'
|
||||
freebsd_instance:
|
||||
image_family: freebsd-14-1
|
||||
image_family: freebsd-14-2
|
||||
install_script:
|
||||
- pkg update -f
|
||||
- pkg install -y go
|
||||
|
|
|
@ -1,6 +1,39 @@
|
|||
# Changelog
|
||||
|
||||
1.8.0 2023-10-31
|
||||
1.9.0 2024-04-04
|
||||
----------------
|
||||
|
||||
### Changes and fixes
|
||||
|
||||
- all: make BufferedWatcher buffered again ([#657])
|
||||
|
||||
- inotify: fix race when adding/removing watches while a watched path is being
|
||||
deleted ([#678], [#686])
|
||||
|
||||
- inotify: don't send empty event if a watched path is unmounted ([#655])
|
||||
|
||||
- inotify: don't register duplicate watches when watching both a symlink and its
|
||||
target; previously that would get "half-added" and removing the second would
|
||||
panic ([#679])
|
||||
|
||||
- kqueue: fix watching relative symlinks ([#681])
|
||||
|
||||
- kqueue: correctly mark pre-existing entries when watching a link to a dir on
|
||||
kqueue ([#682])
|
||||
|
||||
- illumos: don't send error if changed file is deleted while processing the
|
||||
event ([#678])
|
||||
|
||||
|
||||
[#657]: https://github.com/fsnotify/fsnotify/pull/657
|
||||
[#678]: https://github.com/fsnotify/fsnotify/pull/678
|
||||
[#686]: https://github.com/fsnotify/fsnotify/pull/686
|
||||
[#655]: https://github.com/fsnotify/fsnotify/pull/655
|
||||
[#681]: https://github.com/fsnotify/fsnotify/pull/681
|
||||
[#679]: https://github.com/fsnotify/fsnotify/pull/679
|
||||
[#682]: https://github.com/fsnotify/fsnotify/pull/682
|
||||
|
||||
1.8.0 2024-10-31
|
||||
----------------
|
||||
|
||||
### Additions
|
||||
|
|
|
@ -77,6 +77,7 @@ End-of-line escapes with `\` are not supported.
|
|||
debug [yes/no] # Enable/disable FSNOTIFY_DEBUG (tests are run in
|
||||
parallel by default, so -parallel=1 is probably a good
|
||||
idea).
|
||||
print [any strings] # Print text to stdout; for debugging.
|
||||
|
||||
touch path
|
||||
mkdir [-p] dir
|
||||
|
|
|
@ -15,7 +15,6 @@ Platform support:
|
|||
| ReadDirectoryChangesW | Windows | Supported |
|
||||
| FEN | illumos | Supported |
|
||||
| fanotify | Linux 5.9+ | [Not yet](https://github.com/fsnotify/fsnotify/issues/114) |
|
||||
| AHAFS | AIX | [aix branch]; experimental due to lack of maintainer and test environment |
|
||||
| FSEvents | macOS | [Needs support in x/sys/unix][fsevents] |
|
||||
| USN Journals | Windows | [Needs support in x/sys/windows][usn] |
|
||||
| Polling | *All* | [Not yet](https://github.com/fsnotify/fsnotify/issues/9) |
|
||||
|
@ -25,7 +24,6 @@ untested.
|
|||
|
||||
[fsevents]: https://github.com/fsnotify/fsnotify/issues/11#issuecomment-1279133120
|
||||
[usn]: https://github.com/fsnotify/fsnotify/issues/53#issuecomment-1279829847
|
||||
[aix branch]: https://github.com/fsnotify/fsnotify/issues/353#issuecomment-1284590129
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
|
|
@ -9,6 +9,7 @@ package fsnotify
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
@ -19,27 +20,25 @@ import (
|
|||
)
|
||||
|
||||
type fen struct {
|
||||
*shared
|
||||
Events chan Event
|
||||
Errors chan error
|
||||
|
||||
mu sync.Mutex
|
||||
port *unix.EventPort
|
||||
done chan struct{} // Channel for sending a "quit message" to the reader goroutine
|
||||
dirs map[string]Op // Explicitly watched directories
|
||||
watches map[string]Op // Explicitly watched non-directories
|
||||
}
|
||||
|
||||
func newBackend(ev chan Event, errs chan error) (backend, error) {
|
||||
return newBufferedBackend(0, ev, errs)
|
||||
}
|
||||
var defaultBufferSize = 0
|
||||
|
||||
func newBufferedBackend(sz uint, ev chan Event, errs chan error) (backend, error) {
|
||||
func newBackend(ev chan Event, errs chan error) (backend, error) {
|
||||
w := &fen{
|
||||
shared: newShared(ev, errs),
|
||||
Events: ev,
|
||||
Errors: errs,
|
||||
dirs: make(map[string]Op),
|
||||
watches: make(map[string]Op),
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
|
||||
var err error
|
||||
|
@ -52,49 +51,10 @@ func newBufferedBackend(sz uint, ev chan Event, errs chan error) (backend, error
|
|||
return w, nil
|
||||
}
|
||||
|
||||
// sendEvent attempts to send an event to the user, returning true if the event
|
||||
// was put in the channel successfully and false if the watcher has been closed.
|
||||
func (w *fen) sendEvent(name string, op Op) (sent bool) {
|
||||
select {
|
||||
case <-w.done:
|
||||
return false
|
||||
case w.Events <- Event{Name: name, Op: op}:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// sendError attempts to send an error to the user, returning true if the error
|
||||
// was put in the channel successfully and false if the watcher has been closed.
|
||||
func (w *fen) sendError(err error) (sent bool) {
|
||||
if err == nil {
|
||||
return true
|
||||
}
|
||||
select {
|
||||
case <-w.done:
|
||||
return false
|
||||
case w.Errors <- err:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func (w *fen) isClosed() bool {
|
||||
select {
|
||||
case <-w.done:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (w *fen) Close() error {
|
||||
// Take the lock used by associateFile to prevent lingering events from
|
||||
// being processed after the close
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
if w.isClosed() {
|
||||
if w.shared.close() {
|
||||
return nil
|
||||
}
|
||||
close(w.done)
|
||||
return w.port.Close()
|
||||
}
|
||||
|
||||
|
@ -209,7 +169,7 @@ func (w *fen) readEvents() {
|
|||
return
|
||||
}
|
||||
// There was an error not caused by calling w.Close()
|
||||
if !w.sendError(err) {
|
||||
if !w.sendError(fmt.Errorf("port.Get: %w", err)) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
@ -277,13 +237,13 @@ func (w *fen) handleEvent(event *unix.PortEvent) error {
|
|||
isWatched := watchedDir || watchedPath
|
||||
|
||||
if events&unix.FILE_DELETE != 0 {
|
||||
if !w.sendEvent(path, Remove) {
|
||||
if !w.sendEvent(Event{Name: path, Op: Remove}) {
|
||||
return nil
|
||||
}
|
||||
reRegister = false
|
||||
}
|
||||
if events&unix.FILE_RENAME_FROM != 0 {
|
||||
if !w.sendEvent(path, Rename) {
|
||||
if !w.sendEvent(Event{Name: path, Op: Rename}) {
|
||||
return nil
|
||||
}
|
||||
// Don't keep watching the new file name
|
||||
|
@ -297,7 +257,7 @@ func (w *fen) handleEvent(event *unix.PortEvent) error {
|
|||
|
||||
// inotify reports a Remove event in this case, so we simulate this
|
||||
// here.
|
||||
if !w.sendEvent(path, Remove) {
|
||||
if !w.sendEvent(Event{Name: path, Op: Remove}) {
|
||||
return nil
|
||||
}
|
||||
// Don't keep watching the file that was removed
|
||||
|
@ -331,7 +291,7 @@ func (w *fen) handleEvent(event *unix.PortEvent) error {
|
|||
// get here, the sudirectory is already gone. Clearly we were watching
|
||||
// this path but now it is gone. Let's tell the user that it was
|
||||
// removed.
|
||||
if !w.sendEvent(path, Remove) {
|
||||
if !w.sendEvent(Event{Name: path, Op: Remove}) {
|
||||
return nil
|
||||
}
|
||||
// Suppress extra write events on removed directories; they are not
|
||||
|
@ -346,7 +306,7 @@ func (w *fen) handleEvent(event *unix.PortEvent) error {
|
|||
if err != nil {
|
||||
// The symlink still exists, but the target is gone. Report the
|
||||
// Remove similar to above.
|
||||
if !w.sendEvent(path, Remove) {
|
||||
if !w.sendEvent(Event{Name: path, Op: Remove}) {
|
||||
return nil
|
||||
}
|
||||
// Don't return the error
|
||||
|
@ -359,7 +319,7 @@ func (w *fen) handleEvent(event *unix.PortEvent) error {
|
|||
return err
|
||||
}
|
||||
} else {
|
||||
if !w.sendEvent(path, Write) {
|
||||
if !w.sendEvent(Event{Name: path, Op: Write}) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
@ -367,7 +327,7 @@ func (w *fen) handleEvent(event *unix.PortEvent) error {
|
|||
if events&unix.FILE_ATTRIB != 0 && stat != nil {
|
||||
// Only send Chmod if perms changed
|
||||
if stat.Mode().Perm() != fmode.Perm() {
|
||||
if !w.sendEvent(path, Chmod) {
|
||||
if !w.sendEvent(Event{Name: path, Op: Chmod}) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
@ -376,17 +336,27 @@ func (w *fen) handleEvent(event *unix.PortEvent) error {
|
|||
if stat != nil {
|
||||
// If we get here, it means we've hit an event above that requires us to
|
||||
// continue watching the file or directory
|
||||
return w.associateFile(path, stat, isWatched)
|
||||
err := w.associateFile(path, stat, isWatched)
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
// Path may have been removed since the stat.
|
||||
err = nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// The directory was modified, so we must find unwatched entities and watch
|
||||
// them. If something was removed from the directory, nothing will happen, as
|
||||
// everything else should still be watched.
|
||||
func (w *fen) updateDirectory(path string) error {
|
||||
// The directory was modified, so we must find unwatched entities and watch
|
||||
// them. If something was removed from the directory, nothing will happen,
|
||||
// as everything else should still be watched.
|
||||
files, err := os.ReadDir(path)
|
||||
if err != nil {
|
||||
// Directory no longer exists: probably just deleted since we got the
|
||||
// event.
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -401,10 +371,15 @@ func (w *fen) updateDirectory(path string) error {
|
|||
return err
|
||||
}
|
||||
err = w.associateFile(path, finfo, false)
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
// File may have disappeared between getting the dir listing and
|
||||
// adding the port: that's okay to ignore.
|
||||
continue
|
||||
}
|
||||
if !w.sendError(err) {
|
||||
return nil
|
||||
}
|
||||
if !w.sendEvent(path, Create) {
|
||||
if !w.sendEvent(Event{Name: path, Op: Create}) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
@ -430,7 +405,7 @@ func (w *fen) associateFile(path string, stat os.FileInfo, follow bool) error {
|
|||
// has fired but we haven't processed it yet.
|
||||
err := w.port.DissociatePath(path)
|
||||
if err != nil && !errors.Is(err, unix.ENOENT) {
|
||||
return err
|
||||
return fmt.Errorf("port.DissociatePath(%q): %w", path, err)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -446,14 +421,22 @@ func (w *fen) associateFile(path string, stat os.FileInfo, follow bool) error {
|
|||
if true {
|
||||
events |= unix.FILE_ATTRIB
|
||||
}
|
||||
return w.port.AssociatePath(path, stat, events, stat.Mode())
|
||||
err := w.port.AssociatePath(path, stat, events, stat.Mode())
|
||||
if err != nil {
|
||||
return fmt.Errorf("port.AssociatePath(%q): %w", path, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *fen) dissociateFile(path string, stat os.FileInfo, unused bool) error {
|
||||
if !w.port.PathIsWatched(path) {
|
||||
return nil
|
||||
}
|
||||
return w.port.DissociatePath(path)
|
||||
err := w.port.DissociatePath(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("port.DissociatePath(%q): %w", path, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *fen) WatchList() []string {
|
||||
|
|
|
@ -19,6 +19,7 @@ import (
|
|||
)
|
||||
|
||||
type inotify struct {
|
||||
*shared
|
||||
Events chan Event
|
||||
Errors chan error
|
||||
|
||||
|
@ -27,8 +28,6 @@ type inotify struct {
|
|||
fd int
|
||||
inotifyFile *os.File
|
||||
watches *watches
|
||||
done chan struct{} // Channel for sending a "quit message" to the reader goroutine
|
||||
doneMu sync.Mutex
|
||||
doneResp chan struct{} // Channel to respond to Close
|
||||
|
||||
// Store rename cookies in an array, with the index wrapping to 0. Almost
|
||||
|
@ -52,7 +51,6 @@ type inotify struct {
|
|||
|
||||
type (
|
||||
watches struct {
|
||||
mu sync.RWMutex
|
||||
wd map[uint32]*watch // wd → watch
|
||||
path map[string]uint32 // pathname → wd
|
||||
}
|
||||
|
@ -75,34 +73,13 @@ func newWatches() *watches {
|
|||
}
|
||||
}
|
||||
|
||||
func (w *watches) len() int {
|
||||
w.mu.RLock()
|
||||
defer w.mu.RUnlock()
|
||||
return len(w.wd)
|
||||
}
|
||||
|
||||
func (w *watches) add(ww *watch) {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
w.wd[ww.wd] = ww
|
||||
w.path[ww.path] = ww.wd
|
||||
}
|
||||
|
||||
func (w *watches) remove(wd uint32) {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
watch := w.wd[wd] // Could have had Remove() called. See #616.
|
||||
if watch == nil {
|
||||
return
|
||||
}
|
||||
delete(w.path, watch.path)
|
||||
delete(w.wd, wd)
|
||||
}
|
||||
func (w *watches) byPath(path string) *watch { return w.wd[w.path[path]] }
|
||||
func (w *watches) byWd(wd uint32) *watch { return w.wd[wd] }
|
||||
func (w *watches) len() int { return len(w.wd) }
|
||||
func (w *watches) add(ww *watch) { w.wd[ww.wd] = ww; w.path[ww.path] = ww.wd }
|
||||
func (w *watches) remove(watch *watch) { delete(w.path, watch.path); delete(w.wd, watch.wd) }
|
||||
|
||||
func (w *watches) removePath(path string) ([]uint32, error) {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
|
||||
path, recurse := recursivePath(path)
|
||||
wd, ok := w.path[path]
|
||||
if !ok {
|
||||
|
@ -123,7 +100,7 @@ func (w *watches) removePath(path string) ([]uint32, error) {
|
|||
wds := make([]uint32, 0, 8)
|
||||
wds = append(wds, wd)
|
||||
for p, rwd := range w.path {
|
||||
if filepath.HasPrefix(p, path) {
|
||||
if strings.HasPrefix(p, path) {
|
||||
delete(w.path, p)
|
||||
delete(w.wd, rwd)
|
||||
wds = append(wds, rwd)
|
||||
|
@ -132,22 +109,7 @@ func (w *watches) removePath(path string) ([]uint32, error) {
|
|||
return wds, nil
|
||||
}
|
||||
|
||||
func (w *watches) byPath(path string) *watch {
|
||||
w.mu.RLock()
|
||||
defer w.mu.RUnlock()
|
||||
return w.wd[w.path[path]]
|
||||
}
|
||||
|
||||
func (w *watches) byWd(wd uint32) *watch {
|
||||
w.mu.RLock()
|
||||
defer w.mu.RUnlock()
|
||||
return w.wd[wd]
|
||||
}
|
||||
|
||||
func (w *watches) updatePath(path string, f func(*watch) (*watch, error)) error {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
|
||||
var existing *watch
|
||||
wd, ok := w.path[path]
|
||||
if ok {
|
||||
|
@ -170,11 +132,9 @@ func (w *watches) updatePath(path string, f func(*watch) (*watch, error)) error
|
|||
return nil
|
||||
}
|
||||
|
||||
func newBackend(ev chan Event, errs chan error) (backend, error) {
|
||||
return newBufferedBackend(0, ev, errs)
|
||||
}
|
||||
var defaultBufferSize = 0
|
||||
|
||||
func newBufferedBackend(sz uint, ev chan Event, errs chan error) (backend, error) {
|
||||
func newBackend(ev chan Event, errs chan error) (backend, error) {
|
||||
// Need to set nonblocking mode for SetDeadline to work, otherwise blocking
|
||||
// I/O operations won't terminate on close.
|
||||
fd, errno := unix.InotifyInit1(unix.IN_CLOEXEC | unix.IN_NONBLOCK)
|
||||
|
@ -183,12 +143,12 @@ func newBufferedBackend(sz uint, ev chan Event, errs chan error) (backend, error
|
|||
}
|
||||
|
||||
w := &inotify{
|
||||
shared: newShared(ev, errs),
|
||||
Events: ev,
|
||||
Errors: errs,
|
||||
fd: fd,
|
||||
inotifyFile: os.NewFile(uintptr(fd), ""),
|
||||
watches: newWatches(),
|
||||
done: make(chan struct{}),
|
||||
doneResp: make(chan struct{}),
|
||||
}
|
||||
|
||||
|
@ -196,46 +156,10 @@ func newBufferedBackend(sz uint, ev chan Event, errs chan error) (backend, error
|
|||
return w, nil
|
||||
}
|
||||
|
||||
// Returns true if the event was sent, or false if watcher is closed.
|
||||
func (w *inotify) sendEvent(e Event) bool {
|
||||
select {
|
||||
case <-w.done:
|
||||
return false
|
||||
case w.Events <- e:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Returns true if the error was sent, or false if watcher is closed.
|
||||
func (w *inotify) sendError(err error) bool {
|
||||
if err == nil {
|
||||
return true
|
||||
}
|
||||
select {
|
||||
case <-w.done:
|
||||
return false
|
||||
case w.Errors <- err:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func (w *inotify) isClosed() bool {
|
||||
select {
|
||||
case <-w.done:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (w *inotify) Close() error {
|
||||
w.doneMu.Lock()
|
||||
if w.isClosed() {
|
||||
w.doneMu.Unlock()
|
||||
if w.shared.close() {
|
||||
return nil
|
||||
}
|
||||
close(w.done)
|
||||
w.doneMu.Unlock()
|
||||
|
||||
// Causes any blocking reads to return with an error, provided the file
|
||||
// still supports deadline operations.
|
||||
|
@ -244,9 +168,7 @@ func (w *inotify) Close() error {
|
|||
return err
|
||||
}
|
||||
|
||||
// Wait for goroutine to close
|
||||
<-w.doneResp
|
||||
|
||||
<-w.doneResp // Wait for readEvents() to finish.
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -266,6 +188,43 @@ func (w *inotify) AddWith(path string, opts ...addOpt) error {
|
|||
return fmt.Errorf("%w: %s", xErrUnsupported, with.op)
|
||||
}
|
||||
|
||||
add := func(path string, with withOpts, recurse bool) error {
|
||||
var flags uint32
|
||||
if with.noFollow {
|
||||
flags |= unix.IN_DONT_FOLLOW
|
||||
}
|
||||
if with.op.Has(Create) {
|
||||
flags |= unix.IN_CREATE
|
||||
}
|
||||
if with.op.Has(Write) {
|
||||
flags |= unix.IN_MODIFY
|
||||
}
|
||||
if with.op.Has(Remove) {
|
||||
flags |= unix.IN_DELETE | unix.IN_DELETE_SELF
|
||||
}
|
||||
if with.op.Has(Rename) {
|
||||
flags |= unix.IN_MOVED_TO | unix.IN_MOVED_FROM | unix.IN_MOVE_SELF
|
||||
}
|
||||
if with.op.Has(Chmod) {
|
||||
flags |= unix.IN_ATTRIB
|
||||
}
|
||||
if with.op.Has(xUnportableOpen) {
|
||||
flags |= unix.IN_OPEN
|
||||
}
|
||||
if with.op.Has(xUnportableRead) {
|
||||
flags |= unix.IN_ACCESS
|
||||
}
|
||||
if with.op.Has(xUnportableCloseWrite) {
|
||||
flags |= unix.IN_CLOSE_WRITE
|
||||
}
|
||||
if with.op.Has(xUnportableCloseRead) {
|
||||
flags |= unix.IN_CLOSE_NOWRITE
|
||||
}
|
||||
return w.register(path, flags, recurse)
|
||||
}
|
||||
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
path, recurse := recursivePath(path)
|
||||
if recurse {
|
||||
return filepath.WalkDir(path, func(root string, d fs.DirEntry, err error) error {
|
||||
|
@ -289,46 +248,11 @@ func (w *inotify) AddWith(path string, opts ...addOpt) error {
|
|||
w.sendEvent(Event{Name: root, Op: Create})
|
||||
}
|
||||
|
||||
return w.add(root, with, true)
|
||||
return add(root, with, true)
|
||||
})
|
||||
}
|
||||
|
||||
return w.add(path, with, false)
|
||||
}
|
||||
|
||||
func (w *inotify) add(path string, with withOpts, recurse bool) error {
|
||||
var flags uint32
|
||||
if with.noFollow {
|
||||
flags |= unix.IN_DONT_FOLLOW
|
||||
}
|
||||
if with.op.Has(Create) {
|
||||
flags |= unix.IN_CREATE
|
||||
}
|
||||
if with.op.Has(Write) {
|
||||
flags |= unix.IN_MODIFY
|
||||
}
|
||||
if with.op.Has(Remove) {
|
||||
flags |= unix.IN_DELETE | unix.IN_DELETE_SELF
|
||||
}
|
||||
if with.op.Has(Rename) {
|
||||
flags |= unix.IN_MOVED_TO | unix.IN_MOVED_FROM | unix.IN_MOVE_SELF
|
||||
}
|
||||
if with.op.Has(Chmod) {
|
||||
flags |= unix.IN_ATTRIB
|
||||
}
|
||||
if with.op.Has(xUnportableOpen) {
|
||||
flags |= unix.IN_OPEN
|
||||
}
|
||||
if with.op.Has(xUnportableRead) {
|
||||
flags |= unix.IN_ACCESS
|
||||
}
|
||||
if with.op.Has(xUnportableCloseWrite) {
|
||||
flags |= unix.IN_CLOSE_WRITE
|
||||
}
|
||||
if with.op.Has(xUnportableCloseRead) {
|
||||
flags |= unix.IN_CLOSE_NOWRITE
|
||||
}
|
||||
return w.register(path, flags, recurse)
|
||||
return add(path, with, false)
|
||||
}
|
||||
|
||||
func (w *inotify) register(path string, flags uint32, recurse bool) error {
|
||||
|
@ -342,6 +266,10 @@ func (w *inotify) register(path string, flags uint32, recurse bool) error {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
if e, ok := w.watches.wd[uint32(wd)]; ok {
|
||||
return e, nil
|
||||
}
|
||||
|
||||
if existing == nil {
|
||||
return &watch{
|
||||
wd: uint32(wd),
|
||||
|
@ -365,6 +293,9 @@ func (w *inotify) Remove(name string) error {
|
|||
fmt.Fprintf(os.Stderr, "FSNOTIFY_DEBUG: %s Remove(%q)\n",
|
||||
time.Now().Format("15:04:05.000000000"), name)
|
||||
}
|
||||
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
return w.remove(filepath.Clean(name))
|
||||
}
|
||||
|
||||
|
@ -399,13 +330,12 @@ func (w *inotify) WatchList() []string {
|
|||
return nil
|
||||
}
|
||||
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
entries := make([]string, 0, w.watches.len())
|
||||
w.watches.mu.RLock()
|
||||
for pathname := range w.watches.path {
|
||||
entries = append(entries, pathname)
|
||||
}
|
||||
w.watches.mu.RUnlock()
|
||||
|
||||
return entries
|
||||
}
|
||||
|
||||
|
@ -418,21 +348,17 @@ func (w *inotify) readEvents() {
|
|||
close(w.Events)
|
||||
}()
|
||||
|
||||
var (
|
||||
buf [unix.SizeofInotifyEvent * 4096]byte // Buffer for a maximum of 4096 raw events
|
||||
errno error // Syscall errno
|
||||
)
|
||||
var buf [unix.SizeofInotifyEvent * 4096]byte // Buffer for a maximum of 4096 raw events
|
||||
for {
|
||||
// See if we have been closed.
|
||||
if w.isClosed() {
|
||||
return
|
||||
}
|
||||
|
||||
n, err := w.inotifyFile.Read(buf[:])
|
||||
switch {
|
||||
case errors.Unwrap(err) == os.ErrClosed:
|
||||
return
|
||||
case err != nil:
|
||||
if err != nil {
|
||||
if errors.Is(err, os.ErrClosed) {
|
||||
return
|
||||
}
|
||||
if !w.sendError(err) {
|
||||
return
|
||||
}
|
||||
|
@ -440,13 +366,9 @@ func (w *inotify) readEvents() {
|
|||
}
|
||||
|
||||
if n < unix.SizeofInotifyEvent {
|
||||
var err error
|
||||
err := errors.New("notify: short read in readEvents()") // Read was too short.
|
||||
if n == 0 {
|
||||
err = io.EOF // If EOF is received. This should really never happen.
|
||||
} else if n < 0 {
|
||||
err = errno // If an error occurred while reading.
|
||||
} else {
|
||||
err = errors.New("notify: short read in readEvents()") // Read was too short.
|
||||
}
|
||||
if !w.sendError(err) {
|
||||
return
|
||||
|
@ -454,134 +376,137 @@ func (w *inotify) readEvents() {
|
|||
continue
|
||||
}
|
||||
|
||||
// We don't know how many events we just read into the buffer
|
||||
// While the offset points to at least one whole event...
|
||||
// We don't know how many events we just read into the buffer While the
|
||||
// offset points to at least one whole event.
|
||||
var offset uint32
|
||||
for offset <= uint32(n-unix.SizeofInotifyEvent) {
|
||||
var (
|
||||
// Point "raw" to the event in the buffer
|
||||
raw = (*unix.InotifyEvent)(unsafe.Pointer(&buf[offset]))
|
||||
mask = uint32(raw.Mask)
|
||||
nameLen = uint32(raw.Len)
|
||||
// Move to the next event in the buffer
|
||||
next = func() { offset += unix.SizeofInotifyEvent + nameLen }
|
||||
)
|
||||
// Point to the event in the buffer.
|
||||
inEvent := (*unix.InotifyEvent)(unsafe.Pointer(&buf[offset]))
|
||||
|
||||
if mask&unix.IN_Q_OVERFLOW != 0 {
|
||||
if inEvent.Mask&unix.IN_Q_OVERFLOW != 0 {
|
||||
if !w.sendError(ErrEventOverflow) {
|
||||
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.
|
||||
watch := w.watches.byWd(uint32(raw.Wd))
|
||||
/// Can be nil if Remove() was called in another goroutine for this
|
||||
/// path inbetween reading the events from the kernel and reading
|
||||
/// the internal state. Not much we can do about it, so just skip.
|
||||
/// See #616.
|
||||
if watch == nil {
|
||||
next()
|
||||
continue
|
||||
ev, ok := w.handleEvent(inEvent, &buf, offset)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
name := watch.path
|
||||
if nameLen > 0 {
|
||||
/// Point "bytes" at the first byte of the filename
|
||||
bytes := (*[unix.PathMax]byte)(unsafe.Pointer(&buf[offset+unix.SizeofInotifyEvent]))[:nameLen:nameLen]
|
||||
/// The filename is padded with NULL bytes. TrimRight() gets rid of those.
|
||||
name += "/" + strings.TrimRight(string(bytes[0:nameLen]), "\000")
|
||||
}
|
||||
|
||||
if debug {
|
||||
internal.Debug(name, raw.Mask, raw.Cookie)
|
||||
}
|
||||
|
||||
if mask&unix.IN_IGNORED != 0 { //&& event.Op != 0
|
||||
next()
|
||||
continue
|
||||
}
|
||||
|
||||
// inotify will automatically remove the watch on deletes; just need
|
||||
// to clean our state here.
|
||||
if mask&unix.IN_DELETE_SELF == unix.IN_DELETE_SELF {
|
||||
w.watches.remove(watch.wd)
|
||||
}
|
||||
|
||||
// We can't really update the state when a watched path is moved;
|
||||
// only IN_MOVE_SELF is sent and not IN_MOVED_{FROM,TO}. So remove
|
||||
// the watch.
|
||||
if mask&unix.IN_MOVE_SELF == unix.IN_MOVE_SELF {
|
||||
if watch.recurse {
|
||||
next() // Do nothing
|
||||
continue
|
||||
}
|
||||
|
||||
err := w.remove(watch.path)
|
||||
if err != nil && !errors.Is(err, ErrNonExistentWatch) {
|
||||
if !w.sendError(err) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Skip if we're watching both this path and the parent; the parent
|
||||
/// will already send a delete so no need to do it twice.
|
||||
if mask&unix.IN_DELETE_SELF != 0 {
|
||||
if _, ok := w.watches.path[filepath.Dir(watch.path)]; ok {
|
||||
next()
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
ev := w.newEvent(name, mask, raw.Cookie)
|
||||
// Need to update watch path for recurse.
|
||||
if watch.recurse {
|
||||
isDir := mask&unix.IN_ISDIR == unix.IN_ISDIR
|
||||
/// New directory created: set up watch on it.
|
||||
if isDir && ev.Has(Create) {
|
||||
err := w.register(ev.Name, watch.flags, true)
|
||||
if !w.sendError(err) {
|
||||
return
|
||||
}
|
||||
|
||||
// This was a directory rename, so we need to update all
|
||||
// the children.
|
||||
//
|
||||
// TODO: this is of course pretty slow; we should use a
|
||||
// better data structure for storing all of this, e.g. store
|
||||
// children in the watch. I have some code for this in my
|
||||
// kqueue refactor we can use in the future. For now I'm
|
||||
// okay with this as it's not publicly available.
|
||||
// Correctness first, performance second.
|
||||
if ev.renamedFrom != "" {
|
||||
w.watches.mu.Lock()
|
||||
for k, ww := range w.watches.wd {
|
||||
if k == watch.wd || ww.path == ev.Name {
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(ww.path, ev.renamedFrom) {
|
||||
ww.path = strings.Replace(ww.path, ev.renamedFrom, ev.Name, 1)
|
||||
w.watches.wd[k] = ww
|
||||
}
|
||||
}
|
||||
w.watches.mu.Unlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Send the events that are not ignored on the events channel
|
||||
if !w.sendEvent(ev) {
|
||||
return
|
||||
}
|
||||
next()
|
||||
|
||||
// Move to the next event in the buffer
|
||||
offset += unix.SizeofInotifyEvent + inEvent.Len
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *inotify) handleEvent(inEvent *unix.InotifyEvent, buf *[65536]byte, offset uint32) (Event, bool) {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
|
||||
/// 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.
|
||||
///
|
||||
/// Can be nil if Remove() was called in another goroutine for this path
|
||||
/// inbetween reading the events from the kernel and reading the internal
|
||||
/// state. Not much we can do about it, so just skip. See #616.
|
||||
watch := w.watches.byWd(uint32(inEvent.Wd))
|
||||
if watch == nil {
|
||||
return Event{}, true
|
||||
}
|
||||
|
||||
var (
|
||||
name = watch.path
|
||||
nameLen = uint32(inEvent.Len)
|
||||
)
|
||||
if nameLen > 0 {
|
||||
/// Point "bytes" at the first byte of the filename
|
||||
bb := *buf
|
||||
bytes := (*[unix.PathMax]byte)(unsafe.Pointer(&bb[offset+unix.SizeofInotifyEvent]))[:nameLen:nameLen]
|
||||
/// The filename is padded with NULL bytes. TrimRight() gets rid of those.
|
||||
name += "/" + strings.TrimRight(string(bytes[0:nameLen]), "\x00")
|
||||
}
|
||||
|
||||
if debug {
|
||||
internal.Debug(name, inEvent.Mask, inEvent.Cookie)
|
||||
}
|
||||
|
||||
if inEvent.Mask&unix.IN_IGNORED != 0 || inEvent.Mask&unix.IN_UNMOUNT != 0 {
|
||||
w.watches.remove(watch)
|
||||
return Event{}, true
|
||||
}
|
||||
|
||||
// inotify will automatically remove the watch on deletes; just need
|
||||
// to clean our state here.
|
||||
if inEvent.Mask&unix.IN_DELETE_SELF == unix.IN_DELETE_SELF {
|
||||
w.watches.remove(watch)
|
||||
}
|
||||
|
||||
// We can't really update the state when a watched path is moved; only
|
||||
// IN_MOVE_SELF is sent and not IN_MOVED_{FROM,TO}. So remove the watch.
|
||||
if inEvent.Mask&unix.IN_MOVE_SELF == unix.IN_MOVE_SELF {
|
||||
if watch.recurse { // Do nothing
|
||||
return Event{}, true
|
||||
}
|
||||
|
||||
err := w.remove(watch.path)
|
||||
if err != nil && !errors.Is(err, ErrNonExistentWatch) {
|
||||
if !w.sendError(err) {
|
||||
return Event{}, false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Skip if we're watching both this path and the parent; the parent will
|
||||
/// already send a delete so no need to do it twice.
|
||||
if inEvent.Mask&unix.IN_DELETE_SELF != 0 {
|
||||
_, ok := w.watches.path[filepath.Dir(watch.path)]
|
||||
if ok {
|
||||
return Event{}, true
|
||||
}
|
||||
}
|
||||
|
||||
ev := w.newEvent(name, inEvent.Mask, inEvent.Cookie)
|
||||
// Need to update watch path for recurse.
|
||||
if watch.recurse {
|
||||
isDir := inEvent.Mask&unix.IN_ISDIR == unix.IN_ISDIR
|
||||
/// New directory created: set up watch on it.
|
||||
if isDir && ev.Has(Create) {
|
||||
err := w.register(ev.Name, watch.flags, true)
|
||||
if !w.sendError(err) {
|
||||
return Event{}, false
|
||||
}
|
||||
|
||||
// This was a directory rename, so we need to update all the
|
||||
// children.
|
||||
//
|
||||
// TODO: this is of course pretty slow; we should use a better data
|
||||
// structure for storing all of this, e.g. store children in the
|
||||
// watch. I have some code for this in my kqueue refactor we can use
|
||||
// in the future. For now I'm okay with this as it's not publicly
|
||||
// available. Correctness first, performance second.
|
||||
if ev.renamedFrom != "" {
|
||||
for k, ww := range w.watches.wd {
|
||||
if k == watch.wd || ww.path == ev.Name {
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(ww.path, ev.renamedFrom) {
|
||||
ww.path = strings.Replace(ww.path, ev.renamedFrom, ev.Name, 1)
|
||||
w.watches.wd[k] = ww
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ev, true
|
||||
}
|
||||
|
||||
func (w *inotify) isRecursive(path string) bool {
|
||||
ww := w.watches.byPath(path)
|
||||
if ww == nil { // path could be a file, so also check the Dir.
|
||||
|
@ -650,8 +575,8 @@ func (w *inotify) xSupports(op Op) bool {
|
|||
}
|
||||
|
||||
func (w *inotify) state() {
|
||||
w.watches.mu.Lock()
|
||||
defer w.watches.mu.Unlock()
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
for wd, ww := range w.watches.wd {
|
||||
fmt.Fprintf(os.Stderr, "%4d: recurse=%t %q\n", wd, ww.recurse, ww.path)
|
||||
}
|
||||
|
|
|
@ -16,14 +16,13 @@ import (
|
|||
)
|
||||
|
||||
type kqueue struct {
|
||||
*shared
|
||||
Events chan Event
|
||||
Errors chan error
|
||||
|
||||
kq int // File descriptor (as returned by the kqueue() syscall).
|
||||
closepipe [2]int // Pipe used for closing kq.
|
||||
watches *watches
|
||||
done chan struct{}
|
||||
doneMu sync.Mutex
|
||||
}
|
||||
|
||||
type (
|
||||
|
@ -132,14 +131,18 @@ func (w *watches) byPath(path string) (watch, bool) {
|
|||
return info, ok
|
||||
}
|
||||
|
||||
func (w *watches) updateDirFlags(path string, flags uint32) {
|
||||
func (w *watches) updateDirFlags(path string, flags uint32) bool {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
|
||||
fd := w.path[path]
|
||||
fd, ok := w.path[path]
|
||||
if !ok { // Already deleted: don't re-set it here.
|
||||
return false
|
||||
}
|
||||
info := w.wd[fd]
|
||||
info.dirFlags = flags
|
||||
w.wd[fd] = info
|
||||
return true
|
||||
}
|
||||
|
||||
func (w *watches) remove(fd int, path string) bool {
|
||||
|
@ -179,22 +182,20 @@ func (w *watches) seenBefore(path string) bool {
|
|||
return ok
|
||||
}
|
||||
|
||||
func newBackend(ev chan Event, errs chan error) (backend, error) {
|
||||
return newBufferedBackend(0, ev, errs)
|
||||
}
|
||||
var defaultBufferSize = 0
|
||||
|
||||
func newBufferedBackend(sz uint, ev chan Event, errs chan error) (backend, error) {
|
||||
func newBackend(ev chan Event, errs chan error) (backend, error) {
|
||||
kq, closepipe, err := newKqueue()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
w := &kqueue{
|
||||
shared: newShared(ev, errs),
|
||||
Events: ev,
|
||||
Errors: errs,
|
||||
kq: kq,
|
||||
closepipe: closepipe,
|
||||
done: make(chan struct{}),
|
||||
watches: newWatches(),
|
||||
}
|
||||
|
||||
|
@ -210,7 +211,7 @@ func newBufferedBackend(sz uint, ev chan Event, errs chan error) (backend, error
|
|||
// all.
|
||||
func newKqueue() (kq int, closepipe [2]int, err error) {
|
||||
kq, err = unix.Kqueue()
|
||||
if kq == -1 {
|
||||
if err != nil {
|
||||
return kq, closepipe, err
|
||||
}
|
||||
|
||||
|
@ -239,54 +240,17 @@ func newKqueue() (kq int, closepipe [2]int, err error) {
|
|||
return kq, closepipe, nil
|
||||
}
|
||||
|
||||
// Returns true if the event was sent, or false if watcher is closed.
|
||||
func (w *kqueue) sendEvent(e Event) bool {
|
||||
select {
|
||||
case <-w.done:
|
||||
return false
|
||||
case w.Events <- e:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Returns true if the error was sent, or false if watcher is closed.
|
||||
func (w *kqueue) sendError(err error) bool {
|
||||
if err == nil {
|
||||
return true
|
||||
}
|
||||
select {
|
||||
case <-w.done:
|
||||
return false
|
||||
case w.Errors <- err:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func (w *kqueue) isClosed() bool {
|
||||
select {
|
||||
case <-w.done:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (w *kqueue) Close() error {
|
||||
w.doneMu.Lock()
|
||||
if w.isClosed() {
|
||||
w.doneMu.Unlock()
|
||||
if w.shared.close() {
|
||||
return nil
|
||||
}
|
||||
close(w.done)
|
||||
w.doneMu.Unlock()
|
||||
|
||||
pathsToRemove := w.watches.listPaths(false)
|
||||
for _, name := range pathsToRemove {
|
||||
w.Remove(name)
|
||||
}
|
||||
|
||||
// Send "quit" message to the reader goroutine.
|
||||
unix.Close(w.closepipe[1])
|
||||
unix.Close(w.closepipe[1]) // Send "quit" message to readEvents
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -303,7 +267,7 @@ func (w *kqueue) AddWith(name string, opts ...addOpt) error {
|
|||
return fmt.Errorf("%w: %s", xErrUnsupported, with.op)
|
||||
}
|
||||
|
||||
_, err := w.addWatch(name, noteAllEvents)
|
||||
_, err := w.addWatch(name, noteAllEvents, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -366,7 +330,7 @@ const noteAllEvents = unix.NOTE_DELETE | unix.NOTE_WRITE | unix.NOTE_ATTRIB | un
|
|||
// described in kevent(2).
|
||||
//
|
||||
// Returns the real path to the file which was added, with symlinks resolved.
|
||||
func (w *kqueue) addWatch(name string, flags uint32) (string, error) {
|
||||
func (w *kqueue) addWatch(name string, flags uint32, listDir bool) (string, error) {
|
||||
if w.isClosed() {
|
||||
return "", ErrClosed
|
||||
}
|
||||
|
@ -385,15 +349,15 @@ func (w *kqueue) addWatch(name string, flags uint32) (string, error) {
|
|||
return "", nil
|
||||
}
|
||||
|
||||
// Follow symlinks.
|
||||
if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
|
||||
// Follow symlinks, but only for paths added with Add(), and not paths
|
||||
// we're adding from internalWatch from a listdir.
|
||||
if !listDir && fi.Mode()&os.ModeSymlink == os.ModeSymlink {
|
||||
link, err := os.Readlink(name)
|
||||
if err != nil {
|
||||
// Return nil because Linux can add unresolvable symlinks to the
|
||||
// watch list without problems, so maintain consistency with
|
||||
// that. There will be no file events for broken symlinks.
|
||||
// TODO: more specific check; returns os.PathError; ENOENT?
|
||||
return "", nil
|
||||
return "", err
|
||||
}
|
||||
if !filepath.IsAbs(link) {
|
||||
link = filepath.Join(filepath.Dir(name), link)
|
||||
}
|
||||
|
||||
_, alreadyWatching = w.watches.byPath(link)
|
||||
|
@ -408,7 +372,7 @@ func (w *kqueue) addWatch(name string, flags uint32) (string, error) {
|
|||
name = link
|
||||
fi, err = os.Lstat(name)
|
||||
if err != nil {
|
||||
return "", nil
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -422,7 +386,6 @@ func (w *kqueue) addWatch(name string, flags uint32) (string, error) {
|
|||
if errors.Is(err, unix.EINTR) {
|
||||
continue
|
||||
}
|
||||
|
||||
return "", err
|
||||
}
|
||||
|
||||
|
@ -444,10 +407,16 @@ func (w *kqueue) addWatch(name string, flags uint32) (string, error) {
|
|||
if info.isDir {
|
||||
watchDir := (flags&unix.NOTE_WRITE) == unix.NOTE_WRITE &&
|
||||
(!alreadyWatching || (info.dirFlags&unix.NOTE_WRITE) != unix.NOTE_WRITE)
|
||||
w.watches.updateDirFlags(name, flags)
|
||||
if !w.watches.updateDirFlags(name, flags) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
if watchDir {
|
||||
if err := w.watchDirectoryFiles(name); err != nil {
|
||||
d := name
|
||||
if info.linkName != "" {
|
||||
d = info.linkName
|
||||
}
|
||||
if err := w.watchDirectoryFiles(d); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
@ -644,19 +613,22 @@ func (w *kqueue) dirChange(dir string) error {
|
|||
if errors.Is(err, os.ErrNotExist) {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("fsnotify.dirChange: %w", err)
|
||||
return fmt.Errorf("fsnotify.dirChange %q: %w", dir, err)
|
||||
}
|
||||
|
||||
for _, f := range files {
|
||||
fi, err := f.Info()
|
||||
if err != nil {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("fsnotify.dirChange: %w", err)
|
||||
}
|
||||
|
||||
err = w.sendCreateIfNew(filepath.Join(dir, fi.Name()), fi)
|
||||
if err != nil {
|
||||
// Don't need to send an error if this file isn't readable.
|
||||
if errors.Is(err, unix.EACCES) || errors.Is(err, unix.EPERM) {
|
||||
if errors.Is(err, unix.EACCES) || errors.Is(err, unix.EPERM) || errors.Is(err, os.ErrNotExist) {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("fsnotify.dirChange: %w", err)
|
||||
|
@ -688,11 +660,11 @@ func (w *kqueue) internalWatch(name string, fi os.FileInfo) (string, error) {
|
|||
// mimic Linux providing delete events for subdirectories, but preserve
|
||||
// the flags used if currently watching subdirectory
|
||||
info, _ := w.watches.byPath(name)
|
||||
return w.addWatch(name, info.dirFlags|unix.NOTE_DELETE|unix.NOTE_RENAME)
|
||||
return w.addWatch(name, info.dirFlags|unix.NOTE_DELETE|unix.NOTE_RENAME, true)
|
||||
}
|
||||
|
||||
// watch file to mimic Linux inotify
|
||||
return w.addWatch(name, noteAllEvents)
|
||||
// Watch file to mimic Linux inotify.
|
||||
return w.addWatch(name, noteAllEvents, true)
|
||||
}
|
||||
|
||||
// Register events with the queue.
|
||||
|
@ -722,9 +694,9 @@ func (w *kqueue) read(events []unix.Kevent_t) ([]unix.Kevent_t, error) {
|
|||
}
|
||||
|
||||
func (w *kqueue) xSupports(op Op) bool {
|
||||
if runtime.GOOS == "freebsd" {
|
||||
//return true // Supports everything.
|
||||
}
|
||||
//if runtime.GOOS == "freebsd" {
|
||||
// return true // Supports everything.
|
||||
//}
|
||||
if op.Has(xUnportableOpen) || op.Has(xUnportableRead) ||
|
||||
op.Has(xUnportableCloseWrite) || op.Has(xUnportableCloseRead) {
|
||||
return false
|
||||
|
|
|
@ -9,12 +9,11 @@ type other struct {
|
|||
Errors chan error
|
||||
}
|
||||
|
||||
var defaultBufferSize = 0
|
||||
|
||||
func newBackend(ev chan Event, errs chan error) (backend, error) {
|
||||
return nil, errors.New("fsnotify not supported on the current platform")
|
||||
}
|
||||
func newBufferedBackend(sz uint, ev chan Event, errs chan error) (backend, error) {
|
||||
return newBackend(ev, errs)
|
||||
}
|
||||
func (w *other) Close() error { return nil }
|
||||
func (w *other) WatchList() []string { return nil }
|
||||
func (w *other) Add(name string) error { return nil }
|
||||
|
|
|
@ -28,18 +28,16 @@ type readDirChangesW struct {
|
|||
|
||||
port windows.Handle // Handle to completion port
|
||||
input chan *input // Inputs to the reader are sent on this channel
|
||||
quit chan chan<- error
|
||||
done chan chan<- error
|
||||
|
||||
mu sync.Mutex // Protects access to watches, closed
|
||||
watches watchMap // Map of watches (key: i-number)
|
||||
closed bool // Set to true when Close() is first called
|
||||
}
|
||||
|
||||
func newBackend(ev chan Event, errs chan error) (backend, error) {
|
||||
return newBufferedBackend(50, ev, errs)
|
||||
}
|
||||
var defaultBufferSize = 50
|
||||
|
||||
func newBufferedBackend(sz uint, ev chan Event, errs chan error) (backend, error) {
|
||||
func newBackend(ev chan Event, errs chan error) (backend, error) {
|
||||
port, err := windows.CreateIoCompletionPort(windows.InvalidHandle, 0, 0, 0)
|
||||
if err != nil {
|
||||
return nil, os.NewSyscallError("CreateIoCompletionPort", err)
|
||||
|
@ -50,7 +48,7 @@ func newBufferedBackend(sz uint, ev chan Event, errs chan error) (backend, error
|
|||
port: port,
|
||||
watches: make(watchMap),
|
||||
input: make(chan *input, 1),
|
||||
quit: make(chan chan<- error, 1),
|
||||
done: make(chan chan<- error, 1),
|
||||
}
|
||||
go w.readEvents()
|
||||
return w, nil
|
||||
|
@ -70,8 +68,8 @@ func (w *readDirChangesW) sendEvent(name, renamedFrom string, mask uint64) bool
|
|||
event := w.newEvent(name, uint32(mask))
|
||||
event.renamedFrom = renamedFrom
|
||||
select {
|
||||
case ch := <-w.quit:
|
||||
w.quit <- ch
|
||||
case ch := <-w.done:
|
||||
w.done <- ch
|
||||
case w.Events <- event:
|
||||
}
|
||||
return true
|
||||
|
@ -83,10 +81,10 @@ func (w *readDirChangesW) sendError(err error) bool {
|
|||
return true
|
||||
}
|
||||
select {
|
||||
case <-w.done:
|
||||
return false
|
||||
case w.Errors <- err:
|
||||
return true
|
||||
case <-w.quit:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -99,9 +97,9 @@ func (w *readDirChangesW) Close() error {
|
|||
w.closed = true
|
||||
w.mu.Unlock()
|
||||
|
||||
// Send "quit" message to the reader goroutine
|
||||
// Send "done" message to the reader goroutine
|
||||
ch := make(chan error)
|
||||
w.quit <- ch
|
||||
w.done <- ch
|
||||
if err := w.wakeupReader(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -495,7 +493,7 @@ func (w *readDirChangesW) readEvents() {
|
|||
watch := (*watch)(unsafe.Pointer(ov))
|
||||
if watch == nil {
|
||||
select {
|
||||
case ch := <-w.quit:
|
||||
case ch := <-w.done:
|
||||
w.mu.Lock()
|
||||
var indexes []indexMap
|
||||
for _, index := range w.watches {
|
||||
|
|
|
@ -244,12 +244,13 @@ var (
|
|||
|
||||
// ErrUnsupported is returned by AddWith() when WithOps() specified an
|
||||
// Unportable event that's not supported on this platform.
|
||||
//lint:ignore ST1012 not relevant
|
||||
xErrUnsupported = errors.New("fsnotify: not supported with this backend")
|
||||
)
|
||||
|
||||
// NewWatcher creates a new Watcher.
|
||||
func NewWatcher() (*Watcher, error) {
|
||||
ev, errs := make(chan Event), make(chan error)
|
||||
ev, errs := make(chan Event, defaultBufferSize), make(chan error)
|
||||
b, err := newBackend(ev, errs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -266,8 +267,8 @@ func NewWatcher() (*Watcher, error) {
|
|||
// cases, and whenever possible you will be better off increasing the kernel
|
||||
// buffers instead of adding a large userspace buffer.
|
||||
func NewBufferedWatcher(sz uint) (*Watcher, error) {
|
||||
ev, errs := make(chan Event), make(chan error)
|
||||
b, err := newBufferedBackend(sz, ev, errs)
|
||||
ev, errs := make(chan Event, sz), make(chan error)
|
||||
b, err := newBackend(ev, errs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -337,7 +338,8 @@ func (w *Watcher) Close() error { return w.b.Close() }
|
|||
// WatchList returns all paths explicitly added with [Watcher.Add] (and are not
|
||||
// yet removed).
|
||||
//
|
||||
// Returns nil if [Watcher.Close] was called.
|
||||
// The order is undefined, and may differ per call. Returns nil if
|
||||
// [Watcher.Close] was called.
|
||||
func (w *Watcher) WatchList() []string { return w.b.WatchList() }
|
||||
|
||||
// Supports reports if all the listed operations are supported by this platform.
|
||||
|
|
|
@ -9,14 +9,14 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
SyscallEACCES = syscall.EACCES
|
||||
UnixEACCES = unix.EACCES
|
||||
ErrSyscallEACCES = syscall.EACCES
|
||||
ErrUnixEACCES = unix.EACCES
|
||||
)
|
||||
|
||||
var maxfiles uint64
|
||||
|
||||
// Go 1.19 will do this automatically: https://go-review.googlesource.com/c/go/+/393354/
|
||||
func SetRlimit() {
|
||||
// Go 1.19 will do this automatically: https://go-review.googlesource.com/c/go/+/393354/
|
||||
var l syscall.Rlimit
|
||||
err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &l)
|
||||
if err == nil && l.Cur != l.Max {
|
||||
|
|
|
@ -9,8 +9,8 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
SyscallEACCES = syscall.EACCES
|
||||
UnixEACCES = unix.EACCES
|
||||
ErrSyscallEACCES = syscall.EACCES
|
||||
ErrUnixEACCES = unix.EACCES
|
||||
)
|
||||
|
||||
var maxfiles uint64
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
//go:build !windows && !darwin && !freebsd
|
||||
//go:build !windows && !darwin && !freebsd && !plan9
|
||||
|
||||
package internal
|
||||
|
||||
|
@ -9,8 +9,8 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
SyscallEACCES = syscall.EACCES
|
||||
UnixEACCES = unix.EACCES
|
||||
ErrSyscallEACCES = syscall.EACCES
|
||||
ErrUnixEACCES = unix.EACCES
|
||||
)
|
||||
|
||||
var maxfiles uint64
|
||||
|
|
|
@ -10,8 +10,8 @@ import (
|
|||
|
||||
// Just a dummy.
|
||||
var (
|
||||
SyscallEACCES = errors.New("dummy")
|
||||
UnixEACCES = errors.New("dummy")
|
||||
ErrSyscallEACCES = errors.New("dummy")
|
||||
ErrUnixEACCES = errors.New("dummy")
|
||||
)
|
||||
|
||||
func SetRlimit() {}
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
package fsnotify
|
||||
|
||||
import "sync"
|
||||
|
||||
type shared struct {
|
||||
Events chan Event
|
||||
Errors chan error
|
||||
done chan struct{}
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func newShared(ev chan Event, errs chan error) *shared {
|
||||
return &shared{
|
||||
Events: ev,
|
||||
Errors: errs,
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
// Returns true if the event was sent, or false if watcher is closed.
|
||||
func (w *shared) sendEvent(e Event) bool {
|
||||
if e.Op == 0 {
|
||||
return true
|
||||
}
|
||||
select {
|
||||
case <-w.done:
|
||||
return false
|
||||
case w.Events <- e:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Returns true if the error was sent, or false if watcher is closed.
|
||||
func (w *shared) sendError(err error) bool {
|
||||
if err == nil {
|
||||
return true
|
||||
}
|
||||
select {
|
||||
case <-w.done:
|
||||
return false
|
||||
case w.Errors <- err:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func (w *shared) isClosed() bool {
|
||||
select {
|
||||
case <-w.done:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Mark as closed; returns true if it was already closed.
|
||||
func (w *shared) close() bool {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
if w.isClosed() {
|
||||
return true
|
||||
}
|
||||
close(w.done)
|
||||
return false
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
checks = ['all',
|
||||
'-U1000', # Don't complain about unused functions.
|
||||
]
|
|
@ -95,7 +95,7 @@ github.com/containerd/stargz-snapshotter/estargz/errorutil
|
|||
# github.com/containerd/typeurl/v2 v2.2.3
|
||||
## explicit; go 1.21
|
||||
github.com/containerd/typeurl/v2
|
||||
# github.com/containernetworking/cni v1.2.3
|
||||
# github.com/containernetworking/cni v1.3.0
|
||||
## explicit; go 1.21
|
||||
github.com/containernetworking/cni/libcni
|
||||
github.com/containernetworking/cni/pkg/invoke
|
||||
|
@ -142,7 +142,7 @@ github.com/containers/buildah/pkg/sshagent
|
|||
github.com/containers/buildah/pkg/util
|
||||
github.com/containers/buildah/pkg/volumes
|
||||
github.com/containers/buildah/util
|
||||
# github.com/containers/common v0.62.4-0.20250401165412-9b0d134f392f
|
||||
# github.com/containers/common v0.62.4-0.20250411145332-dd8ab77b8e26
|
||||
## explicit; go 1.23.3
|
||||
github.com/containers/common/internal
|
||||
github.com/containers/common/internal/attributedstring
|
||||
|
@ -489,7 +489,7 @@ github.com/ebitengine/purego/internal/strings
|
|||
# github.com/felixge/httpsnoop v1.0.4
|
||||
## explicit; go 1.13
|
||||
github.com/felixge/httpsnoop
|
||||
# github.com/fsnotify/fsnotify v1.8.0
|
||||
# github.com/fsnotify/fsnotify v1.9.0
|
||||
## explicit; go 1.17
|
||||
github.com/fsnotify/fsnotify
|
||||
github.com/fsnotify/fsnotify/internal
|
||||
|
|
Loading…
Reference in New Issue