mirror of
https://github.com/k8snetworkplumbingwg/whereabouts.git
synced 2025-06-03 06:42:26 +00:00
![dependabot[bot]](/assets/img/avatar_default.png)
Bumps the other-go-modules group with 8 updates: | Package | From | To | | --- | --- | --- | | [github.com/containernetworking/cni](https://github.com/containernetworking/cni) | `1.2.3` | `1.3.0` | | [github.com/containernetworking/plugins](https://github.com/containernetworking/plugins) | `1.6.1` | `1.6.2` | | [github.com/k8snetworkplumbingwg/network-attachment-definition-client](https://github.com/k8snetworkplumbingwg/network-attachment-definition-client) | `1.7.0` | `1.7.6` | | [github.com/onsi/gomega](https://github.com/onsi/gomega) | `1.36.2` | `1.37.0` | | [gomodules.xyz/jsonpatch/v2](https://github.com/gomodules/jsonpatch) | `2.4.0` | `2.5.0` | | [github.com/go-co-op/gocron/v2](https://github.com/go-co-op/gocron) | `2.12.4` | `2.16.1` | | [github.com/fsnotify/fsnotify](https://github.com/fsnotify/fsnotify) | `1.8.0` | `1.9.0` | | [golang.org/x/time](https://github.com/golang/time) | `0.8.0` | `0.11.0` | Updates `github.com/containernetworking/cni` from 1.2.3 to 1.3.0 - [Release notes](https://github.com/containernetworking/cni/releases) - [Commits](https://github.com/containernetworking/cni/compare/v1.2.3...v1.3.0) Updates `github.com/containernetworking/plugins` from 1.6.1 to 1.6.2 - [Release notes](https://github.com/containernetworking/plugins/releases) - [Commits](https://github.com/containernetworking/plugins/compare/v1.6.1...v1.6.2) Updates `github.com/k8snetworkplumbingwg/network-attachment-definition-client` from 1.7.0 to 1.7.6 - [Release notes](https://github.com/k8snetworkplumbingwg/network-attachment-definition-client/releases) - [Commits](https://github.com/k8snetworkplumbingwg/network-attachment-definition-client/compare/v1.7.0...v1.7.6) Updates `github.com/onsi/gomega` from 1.36.2 to 1.37.0 - [Release notes](https://github.com/onsi/gomega/releases) - [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md) - [Commits](https://github.com/onsi/gomega/compare/v1.36.2...v1.37.0) Updates `gomodules.xyz/jsonpatch/v2` from 2.4.0 to 2.5.0 - [Release notes](https://github.com/gomodules/jsonpatch/releases) - [Changelog](https://github.com/gomodules/jsonpatch/blob/release-2.0/CHANGELOG.md) - [Commits](https://github.com/gomodules/jsonpatch/compare/v2.4.0...v2.5.0) Updates `github.com/go-co-op/gocron/v2` from 2.12.4 to 2.16.1 - [Release notes](https://github.com/go-co-op/gocron/releases) - [Commits](https://github.com/go-co-op/gocron/compare/v2.12.4...v2.16.1) Updates `github.com/fsnotify/fsnotify` from 1.8.0 to 1.9.0 - [Release notes](https://github.com/fsnotify/fsnotify/releases) - [Changelog](https://github.com/fsnotify/fsnotify/blob/main/CHANGELOG.md) - [Commits](https://github.com/fsnotify/fsnotify/compare/v1.8.0...v1.9.0) Updates `golang.org/x/time` from 0.8.0 to 0.11.0 - [Commits](https://github.com/golang/time/compare/v0.8.0...v0.11.0) --- updated-dependencies: - dependency-name: github.com/containernetworking/cni dependency-version: 1.3.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: other-go-modules - dependency-name: github.com/containernetworking/plugins dependency-version: 1.6.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: other-go-modules - dependency-name: github.com/k8snetworkplumbingwg/network-attachment-definition-client dependency-version: 1.7.6 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: other-go-modules - dependency-name: github.com/onsi/gomega dependency-version: 1.37.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: other-go-modules - dependency-name: gomodules.xyz/jsonpatch/v2 dependency-version: 2.5.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: other-go-modules - dependency-name: github.com/go-co-op/gocron/v2 dependency-version: 2.16.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: other-go-modules - dependency-name: github.com/fsnotify/fsnotify dependency-version: 1.9.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: other-go-modules - dependency-name: golang.org/x/time dependency-version: 0.11.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: other-go-modules ... Signed-off-by: dependabot[bot] <support@github.com>
497 lines
17 KiB
Go
497 lines
17 KiB
Go
// Package fsnotify provides a cross-platform interface for file system
|
||
// notifications.
|
||
//
|
||
// Currently supported systems:
|
||
//
|
||
// - Linux via inotify
|
||
// - BSD, macOS via kqueue
|
||
// - Windows via ReadDirectoryChangesW
|
||
// - illumos via FEN
|
||
//
|
||
// # FSNOTIFY_DEBUG
|
||
//
|
||
// Set the FSNOTIFY_DEBUG environment variable to "1" to print debug messages to
|
||
// stderr. This can be useful to track down some problems, especially in cases
|
||
// where fsnotify is used as an indirect dependency.
|
||
//
|
||
// Every event will be printed as soon as there's something useful to print,
|
||
// with as little processing from fsnotify.
|
||
//
|
||
// Example output:
|
||
//
|
||
// FSNOTIFY_DEBUG: 11:34:23.633087586 256:IN_CREATE → "/tmp/file-1"
|
||
// FSNOTIFY_DEBUG: 11:34:23.633202319 4:IN_ATTRIB → "/tmp/file-1"
|
||
// FSNOTIFY_DEBUG: 11:34:28.989728764 512:IN_DELETE → "/tmp/file-1"
|
||
package fsnotify
|
||
|
||
import (
|
||
"errors"
|
||
"fmt"
|
||
"os"
|
||
"path/filepath"
|
||
"strings"
|
||
)
|
||
|
||
// Watcher watches a set of paths, delivering events on a channel.
|
||
//
|
||
// A watcher should not be copied (e.g. pass it by pointer, rather than by
|
||
// value).
|
||
//
|
||
// # Linux notes
|
||
//
|
||
// When a file is removed a Remove event won't be emitted until all file
|
||
// descriptors are closed, and deletes will always emit a Chmod. For example:
|
||
//
|
||
// fp := os.Open("file")
|
||
// os.Remove("file") // Triggers Chmod
|
||
// fp.Close() // Triggers Remove
|
||
//
|
||
// This is the event that inotify sends, so not much can be changed about this.
|
||
//
|
||
// The fs.inotify.max_user_watches sysctl variable specifies the upper limit
|
||
// for the number of watches per user, and fs.inotify.max_user_instances
|
||
// specifies the maximum number of inotify instances per user. Every Watcher you
|
||
// create is an "instance", and every path you add is a "watch".
|
||
//
|
||
// These are also exposed in /proc as /proc/sys/fs/inotify/max_user_watches and
|
||
// /proc/sys/fs/inotify/max_user_instances
|
||
//
|
||
// To increase them you can use sysctl or write the value to the /proc file:
|
||
//
|
||
// # Default values on Linux 5.18
|
||
// sysctl fs.inotify.max_user_watches=124983
|
||
// sysctl fs.inotify.max_user_instances=128
|
||
//
|
||
// To make the changes persist on reboot edit /etc/sysctl.conf or
|
||
// /usr/lib/sysctl.d/50-default.conf (details differ per Linux distro; check
|
||
// your distro's documentation):
|
||
//
|
||
// fs.inotify.max_user_watches=124983
|
||
// fs.inotify.max_user_instances=128
|
||
//
|
||
// Reaching the limit will result in a "no space left on device" or "too many open
|
||
// files" error.
|
||
//
|
||
// # kqueue notes (macOS, BSD)
|
||
//
|
||
// kqueue requires opening a file descriptor for every file that's being watched;
|
||
// so if you're watching a directory with five files then that's six file
|
||
// descriptors. You will run in to your system's "max open files" limit faster on
|
||
// these platforms.
|
||
//
|
||
// The sysctl variables kern.maxfiles and kern.maxfilesperproc can be used to
|
||
// control the maximum number of open files, as well as /etc/login.conf on BSD
|
||
// systems.
|
||
//
|
||
// # Windows notes
|
||
//
|
||
// Paths can be added as "C:\\path\\to\\dir", but forward slashes
|
||
// ("C:/path/to/dir") will also work.
|
||
//
|
||
// When a watched directory is removed it will always send an event for the
|
||
// directory itself, but may not send events for all files in that directory.
|
||
// Sometimes it will send events for all files, sometimes it will send no
|
||
// events, and often only for some files.
|
||
//
|
||
// The default ReadDirectoryChangesW() buffer size is 64K, which is the largest
|
||
// value that is guaranteed to work with SMB filesystems. If you have many
|
||
// events in quick succession this may not be enough, and you will have to use
|
||
// [WithBufferSize] to increase the value.
|
||
type Watcher struct {
|
||
b backend
|
||
|
||
// Events sends the filesystem change events.
|
||
//
|
||
// fsnotify can send the following events; a "path" here can refer to a
|
||
// file, directory, symbolic link, or special file like a FIFO.
|
||
//
|
||
// fsnotify.Create A new path was created; this may be followed by one
|
||
// or more Write events if data also gets written to a
|
||
// file.
|
||
//
|
||
// fsnotify.Remove A path was removed.
|
||
//
|
||
// fsnotify.Rename A path was renamed. A rename is always sent with the
|
||
// old path as Event.Name, and a Create event will be
|
||
// sent with the new name. Renames are only sent for
|
||
// paths that are currently watched; e.g. moving an
|
||
// unmonitored file into a monitored directory will
|
||
// show up as just a Create. Similarly, renaming a file
|
||
// to outside a monitored directory will show up as
|
||
// only a Rename.
|
||
//
|
||
// fsnotify.Write A file or named pipe was written to. A Truncate will
|
||
// also trigger a Write. A single "write action"
|
||
// initiated by the user may show up as one or multiple
|
||
// writes, depending on when the system syncs things to
|
||
// disk. For example when compiling a large Go program
|
||
// you may get hundreds of Write events, and you may
|
||
// want to wait until you've stopped receiving them
|
||
// (see the dedup example in cmd/fsnotify).
|
||
//
|
||
// Some systems may send Write event for directories
|
||
// when the directory content changes.
|
||
//
|
||
// fsnotify.Chmod Attributes were changed. On Linux this is also sent
|
||
// when a file is removed (or more accurately, when a
|
||
// link to an inode is removed). On kqueue it's sent
|
||
// when a file is truncated. On Windows it's never
|
||
// sent.
|
||
Events chan Event
|
||
|
||
// Errors sends any errors.
|
||
Errors chan error
|
||
}
|
||
|
||
// Event represents a file system notification.
|
||
type Event struct {
|
||
// Path to the file or directory.
|
||
//
|
||
// Paths are relative to the input; for example with Add("dir") the Name
|
||
// will be set to "dir/file" if you create that file, but if you use
|
||
// Add("/path/to/dir") it will be "/path/to/dir/file".
|
||
Name string
|
||
|
||
// File operation that triggered the event.
|
||
//
|
||
// This is a bitmask and some systems may send multiple operations at once.
|
||
// Use the Event.Has() method instead of comparing with ==.
|
||
Op Op
|
||
|
||
// Create events will have this set to the old path if it's a rename. This
|
||
// only works when both the source and destination are watched. It's not
|
||
// reliable when watching individual files, only directories.
|
||
//
|
||
// For example "mv /tmp/file /tmp/rename" will emit:
|
||
//
|
||
// Event{Op: Rename, Name: "/tmp/file"}
|
||
// Event{Op: Create, Name: "/tmp/rename", RenamedFrom: "/tmp/file"}
|
||
renamedFrom string
|
||
}
|
||
|
||
// Op describes a set of file operations.
|
||
type Op uint32
|
||
|
||
// The operations fsnotify can trigger; see the documentation on [Watcher] for a
|
||
// full description, and check them with [Event.Has].
|
||
const (
|
||
// A new pathname was created.
|
||
Create Op = 1 << iota
|
||
|
||
// The pathname was written to; this does *not* mean the write has finished,
|
||
// and a write can be followed by more writes.
|
||
Write
|
||
|
||
// The path was removed; any watches on it will be removed. Some "remove"
|
||
// operations may trigger a Rename if the file is actually moved (for
|
||
// example "remove to trash" is often a rename).
|
||
Remove
|
||
|
||
// The path was renamed to something else; any watches on it will be
|
||
// removed.
|
||
Rename
|
||
|
||
// File attributes were changed.
|
||
//
|
||
// It's generally not recommended to take action on this event, as it may
|
||
// get triggered very frequently by some software. For example, Spotlight
|
||
// indexing on macOS, anti-virus software, backup software, etc.
|
||
Chmod
|
||
|
||
// File descriptor was opened.
|
||
//
|
||
// Only works on Linux and FreeBSD.
|
||
xUnportableOpen
|
||
|
||
// File was read from.
|
||
//
|
||
// Only works on Linux and FreeBSD.
|
||
xUnportableRead
|
||
|
||
// File opened for writing was closed.
|
||
//
|
||
// Only works on Linux and FreeBSD.
|
||
//
|
||
// The advantage of using this over Write is that it's more reliable than
|
||
// waiting for Write events to stop. It's also faster (if you're not
|
||
// listening to Write events): copying a file of a few GB can easily
|
||
// generate tens of thousands of Write events in a short span of time.
|
||
xUnportableCloseWrite
|
||
|
||
// File opened for reading was closed.
|
||
//
|
||
// Only works on Linux and FreeBSD.
|
||
xUnportableCloseRead
|
||
)
|
||
|
||
var (
|
||
// ErrNonExistentWatch is used when Remove() is called on a path that's not
|
||
// added.
|
||
ErrNonExistentWatch = errors.New("fsnotify: can't remove non-existent watch")
|
||
|
||
// ErrClosed is used when trying to operate on a closed Watcher.
|
||
ErrClosed = errors.New("fsnotify: watcher already closed")
|
||
|
||
// ErrEventOverflow is reported from the Errors channel when there are too
|
||
// many events:
|
||
//
|
||
// - inotify: inotify returns IN_Q_OVERFLOW – because there are too
|
||
// many queued events (the fs.inotify.max_queued_events
|
||
// sysctl can be used to increase this).
|
||
// - windows: The buffer size is too small; WithBufferSize() can be used to increase it.
|
||
// - kqueue, fen: Not used.
|
||
ErrEventOverflow = errors.New("fsnotify: queue or buffer overflow")
|
||
|
||
// 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, defaultBufferSize), make(chan error)
|
||
b, err := newBackend(ev, errs)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
return &Watcher{b: b, Events: ev, Errors: errs}, nil
|
||
}
|
||
|
||
// NewBufferedWatcher creates a new Watcher with a buffered Watcher.Events
|
||
// channel.
|
||
//
|
||
// The main use case for this is situations with a very large number of events
|
||
// where the kernel buffer size can't be increased (e.g. due to lack of
|
||
// permissions). An unbuffered Watcher will perform better for almost all use
|
||
// 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, sz), make(chan error)
|
||
b, err := newBackend(ev, errs)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
return &Watcher{b: b, Events: ev, Errors: errs}, nil
|
||
}
|
||
|
||
// Add starts monitoring the path for changes.
|
||
//
|
||
// A path can only be watched once; watching it more than once is a no-op and will
|
||
// not return an error. Paths that do not yet exist on the filesystem cannot be
|
||
// watched.
|
||
//
|
||
// A watch will be automatically removed if the watched path is deleted or
|
||
// renamed. The exception is the Windows backend, which doesn't remove the
|
||
// watcher on renames.
|
||
//
|
||
// Notifications on network filesystems (NFS, SMB, FUSE, etc.) or special
|
||
// filesystems (/proc, /sys, etc.) generally don't work.
|
||
//
|
||
// Returns [ErrClosed] if [Watcher.Close] was called.
|
||
//
|
||
// See [Watcher.AddWith] for a version that allows adding options.
|
||
//
|
||
// # Watching directories
|
||
//
|
||
// All files in a directory are monitored, including new files that are created
|
||
// after the watcher is started. Subdirectories are not watched (i.e. it's
|
||
// non-recursive).
|
||
//
|
||
// # Watching files
|
||
//
|
||
// Watching individual files (rather than directories) is generally not
|
||
// recommended as many programs (especially editors) update files atomically: it
|
||
// will write to a temporary file which is then moved to destination,
|
||
// overwriting the original (or some variant thereof). The watcher on the
|
||
// original file is now lost, as that no longer exists.
|
||
//
|
||
// The upshot of this is that a power failure or crash won't leave a
|
||
// half-written file.
|
||
//
|
||
// Watch the parent directory and use Event.Name to filter out files you're not
|
||
// interested in. There is an example of this in cmd/fsnotify/file.go.
|
||
func (w *Watcher) Add(path string) error { return w.b.Add(path) }
|
||
|
||
// AddWith is like [Watcher.Add], but allows adding options. When using Add()
|
||
// the defaults described below are used.
|
||
//
|
||
// Possible options are:
|
||
//
|
||
// - [WithBufferSize] sets the buffer size for the Windows backend; no-op on
|
||
// other platforms. The default is 64K (65536 bytes).
|
||
func (w *Watcher) AddWith(path string, opts ...addOpt) error { return w.b.AddWith(path, opts...) }
|
||
|
||
// Remove stops monitoring the path for changes.
|
||
//
|
||
// Directories are always removed non-recursively. For example, if you added
|
||
// /tmp/dir and /tmp/dir/subdir then you will need to remove both.
|
||
//
|
||
// Removing a path that has not yet been added returns [ErrNonExistentWatch].
|
||
//
|
||
// Returns nil if [Watcher.Close] was called.
|
||
func (w *Watcher) Remove(path string) error { return w.b.Remove(path) }
|
||
|
||
// Close removes all watches and closes the Events channel.
|
||
func (w *Watcher) Close() error { return w.b.Close() }
|
||
|
||
// WatchList returns all paths explicitly added with [Watcher.Add] (and are not
|
||
// yet removed).
|
||
//
|
||
// 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.
|
||
//
|
||
// Create, Write, Remove, Rename, and Chmod are always supported. It can only
|
||
// return false for an Op starting with Unportable.
|
||
func (w *Watcher) xSupports(op Op) bool { return w.b.xSupports(op) }
|
||
|
||
func (o Op) String() string {
|
||
var b strings.Builder
|
||
if o.Has(Create) {
|
||
b.WriteString("|CREATE")
|
||
}
|
||
if o.Has(Remove) {
|
||
b.WriteString("|REMOVE")
|
||
}
|
||
if o.Has(Write) {
|
||
b.WriteString("|WRITE")
|
||
}
|
||
if o.Has(xUnportableOpen) {
|
||
b.WriteString("|OPEN")
|
||
}
|
||
if o.Has(xUnportableRead) {
|
||
b.WriteString("|READ")
|
||
}
|
||
if o.Has(xUnportableCloseWrite) {
|
||
b.WriteString("|CLOSE_WRITE")
|
||
}
|
||
if o.Has(xUnportableCloseRead) {
|
||
b.WriteString("|CLOSE_READ")
|
||
}
|
||
if o.Has(Rename) {
|
||
b.WriteString("|RENAME")
|
||
}
|
||
if o.Has(Chmod) {
|
||
b.WriteString("|CHMOD")
|
||
}
|
||
if b.Len() == 0 {
|
||
return "[no events]"
|
||
}
|
||
return b.String()[1:]
|
||
}
|
||
|
||
// Has reports if this operation has the given operation.
|
||
func (o Op) Has(h Op) bool { return o&h != 0 }
|
||
|
||
// Has reports if this event has the given operation.
|
||
func (e Event) Has(op Op) bool { return e.Op.Has(op) }
|
||
|
||
// String returns a string representation of the event with their path.
|
||
func (e Event) String() string {
|
||
if e.renamedFrom != "" {
|
||
return fmt.Sprintf("%-13s %q ← %q", e.Op.String(), e.Name, e.renamedFrom)
|
||
}
|
||
return fmt.Sprintf("%-13s %q", e.Op.String(), e.Name)
|
||
}
|
||
|
||
type (
|
||
backend interface {
|
||
Add(string) error
|
||
AddWith(string, ...addOpt) error
|
||
Remove(string) error
|
||
WatchList() []string
|
||
Close() error
|
||
xSupports(Op) bool
|
||
}
|
||
addOpt func(opt *withOpts)
|
||
withOpts struct {
|
||
bufsize int
|
||
op Op
|
||
noFollow bool
|
||
sendCreate bool
|
||
}
|
||
)
|
||
|
||
var debug = func() bool {
|
||
// Check for exactly "1" (rather than mere existence) so we can add
|
||
// options/flags in the future. I don't know if we ever want that, but it's
|
||
// nice to leave the option open.
|
||
return os.Getenv("FSNOTIFY_DEBUG") == "1"
|
||
}()
|
||
|
||
var defaultOpts = withOpts{
|
||
bufsize: 65536, // 64K
|
||
op: Create | Write | Remove | Rename | Chmod,
|
||
}
|
||
|
||
func getOptions(opts ...addOpt) withOpts {
|
||
with := defaultOpts
|
||
for _, o := range opts {
|
||
if o != nil {
|
||
o(&with)
|
||
}
|
||
}
|
||
return with
|
||
}
|
||
|
||
// WithBufferSize sets the [ReadDirectoryChangesW] buffer size.
|
||
//
|
||
// This only has effect on Windows systems, and is a no-op for other backends.
|
||
//
|
||
// The default value is 64K (65536 bytes) which is the highest value that works
|
||
// on all filesystems and should be enough for most applications, but if you
|
||
// have a large burst of events it may not be enough. You can increase it if
|
||
// you're hitting "queue or buffer overflow" errors ([ErrEventOverflow]).
|
||
//
|
||
// [ReadDirectoryChangesW]: https://learn.microsoft.com/en-gb/windows/win32/api/winbase/nf-winbase-readdirectorychangesw
|
||
func WithBufferSize(bytes int) addOpt {
|
||
return func(opt *withOpts) { opt.bufsize = bytes }
|
||
}
|
||
|
||
// WithOps sets which operations to listen for. The default is [Create],
|
||
// [Write], [Remove], [Rename], and [Chmod].
|
||
//
|
||
// Excluding operations you're not interested in can save quite a bit of CPU
|
||
// time; in some use cases there may be hundreds of thousands of useless Write
|
||
// or Chmod operations per second.
|
||
//
|
||
// This can also be used to add unportable operations not supported by all
|
||
// platforms; unportable operations all start with "Unportable":
|
||
// [UnportableOpen], [UnportableRead], [UnportableCloseWrite], and
|
||
// [UnportableCloseRead].
|
||
//
|
||
// AddWith returns an error when using an unportable operation that's not
|
||
// supported. Use [Watcher.Support] to check for support.
|
||
func withOps(op Op) addOpt {
|
||
return func(opt *withOpts) { opt.op = op }
|
||
}
|
||
|
||
// WithNoFollow disables following symlinks, so the symlinks themselves are
|
||
// watched.
|
||
func withNoFollow() addOpt {
|
||
return func(opt *withOpts) { opt.noFollow = true }
|
||
}
|
||
|
||
// "Internal" option for recursive watches on inotify.
|
||
func withCreate() addOpt {
|
||
return func(opt *withOpts) { opt.sendCreate = true }
|
||
}
|
||
|
||
var enableRecurse = false
|
||
|
||
// Check if this path is recursive (ends with "/..." or "\..."), and return the
|
||
// path with the /... stripped.
|
||
func recursivePath(path string) (string, bool) {
|
||
path = filepath.Clean(path)
|
||
if !enableRecurse { // Only enabled in tests for now.
|
||
return path, false
|
||
}
|
||
if filepath.Base(path) == "..." {
|
||
return filepath.Dir(path), true
|
||
}
|
||
return path, false
|
||
}
|