diff --git a/.gitignore b/.gitignore
index 9633cdf..2c1e3d4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,2 @@
/cachefs
+debug.txt
diff --git a/Makefile b/Makefile
index 77def40..5c7cea3 100644
--- a/Makefile
+++ b/Makefile
@@ -31,7 +31,7 @@ clean:
gofmt:
$(CC) fmt ./...
-codeqa: govet misspell staticcheck test
+codeqa: govet misspell test
govet:
$(CC) vet ./...
diff --git a/TODO.txt b/TODO.txt
index c60034a..419aab5 100644
--- a/TODO.txt
+++ b/TODO.txt
@@ -1,4 +1,2 @@
-* add disk quota
- * preload: dont load file if quota exceeded
-* add LRU cache
-* clear cache if file sizes differ and > 0
+* fix deadlock
+ * maybe dirEmpty?
diff --git a/cmd/cachefs/main.go b/cmd/cachefs/main.go
index bc5cd00..c3b7893 100644
--- a/cmd/cachefs/main.go
+++ b/cmd/cachefs/main.go
@@ -27,10 +27,13 @@ var (
metadata string
listenHttp string
listenWebdav string
+ quota int64
log logr.Logger
)
+const gib = 1024 * 1024 * 1024
+
func main() {
klog.InitFlags(nil)
flag.StringVar(&src, "src", "", "root directory (NFS share)")
@@ -38,12 +41,13 @@ func main() {
flag.StringVar(&metadata, "data", "", "path to metadata file")
flag.StringVar(&listenHttp, "listen", "127.0.0.1:8080", "listen addr:port")
flag.StringVar(&listenWebdav, "webdav", "", "listen addr:port for webdav")
+ flag.Int64Var("a, "quota", 1, "max disk usage quota in GiB")
flag.Parse()
log = klogr.New().WithName("main")
log.Info("starting cachefs", "version", version)
- filesystem, err := fs.NewFS(src, dst, metadata, klogr.New().WithName("fs"))
+ filesystem, err := fs.NewFS(quota*gib, src, dst, metadata, klogr.New().WithName("fs"))
if err != nil {
klog.Fatalf("init failed: %s", err)
}
diff --git a/go.mod b/go.mod
index 437ed2a..da52458 100644
--- a/go.mod
+++ b/go.mod
@@ -1,9 +1,10 @@
module cachefs
-go 1.17
+go 1.18
require (
github.com/go-logr/logr v1.2.2
+ golang.org/x/exp v0.0.0-20220314205449-43aec2f8a4e7
golang.org/x/net v0.0.0-20220225172249-27dd8689420f
k8s.io/klog/v2 v2.40.1
)
diff --git a/go.sum b/go.sum
index 4ba81c6..b35a7e6 100644
--- a/go.sum
+++ b/go.sum
@@ -1,12 +1,9 @@
github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.2.2 h1:ahHml/yUpnlb96Rp8HCvtYVPY8ZYpxq3g7UYchIYwbs=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+golang.org/x/exp v0.0.0-20220314205449-43aec2f8a4e7 h1:jynE66seADJbyWMUdeOyVTvPtBZt7L6LJHupGwxPZRM=
+golang.org/x/exp v0.0.0-20220314205449-43aec2f8a4e7/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
-golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
-golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
-golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
k8s.io/klog/v2 v2.40.1 h1:P4RRucWk/lFOlDdkAr3mc7iWFkgKrZY9qZMAgek06S4=
k8s.io/klog/v2 v2.40.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
diff --git a/pkg/fs/chunk.go b/pkg/fs/chunk.go
index bd85cac..ab03ab0 100644
--- a/pkg/fs/chunk.go
+++ b/pkg/fs/chunk.go
@@ -2,15 +2,15 @@
package fs
-import "sort"
+import (
+ "golang.org/x/exp/slices"
+)
type Chunk [2]int64
type Chunks []Chunk
-func (cs Chunks) Len() int { return len(cs) }
-func (cs Chunks) Less(i, j int) bool { return cs[i][0] < cs[j][0] }
-func (cs Chunks) Swap(i, j int) { cs[i], cs[j] = cs[j], cs[i] }
+func (Chunks) Less(i, j Chunk) bool { return i[0] < j[0] }
func (cs Chunks) Exists(off int64, n int, size int64) bool {
end := off + int64(n)
@@ -42,7 +42,7 @@ func (cs *Chunks) merge() {
if len(c) < 2 {
return
}
- sort.Sort(*cs)
+ slices.SortFunc(c, c.Less)
for i := 0; i < len(c); i++ {
if i+1 == len(c) {
break
@@ -51,7 +51,7 @@ func (cs *Chunks) merge() {
if c[i+1][1] > c[i][1] {
c[i][1] = c[i+1][1]
}
- c = append(c[:i+1], c[i+2:]...)
+ c = slices.Delete(c, i+1, i+2)
i--
}
}
diff --git a/pkg/fs/fs.go b/pkg/fs/fs.go
index 0f72c3d..0925933 100644
--- a/pkg/fs/fs.go
+++ b/pkg/fs/fs.go
@@ -18,6 +18,7 @@ import (
"time"
"github.com/go-logr/logr"
+ "golang.org/x/exp/maps"
)
type FS struct {
@@ -29,12 +30,13 @@ type FS struct {
mdf *os.File
jenc *json.Encoder
dc *DirCache
- mm map[string]*Metadata
- prem map[string]func()
+ q *Quota
+ md MetadataMap
+ pm map[string]func()
cancel func()
}
-func NewFS(src, dst, metadata string, log logr.Logger) (fs *FS, err error) {
+func NewFS(max int64, src, dst, metadata string, log logr.Logger) (fs *FS, err error) {
if !filepath.IsAbs(src) {
return nil, errors.New("src path is not absolute")
}
@@ -58,15 +60,20 @@ func NewFS(src, dst, metadata string, log logr.Logger) (fs *FS, err error) {
dst: dst,
mdf: mdf,
dc: NewDirCache(),
- mm: make(map[string]*Metadata),
- prem: make(map[string]func()),
+ md: make(MetadataMap),
+ pm: make(map[string]func()),
jenc: json.NewEncoder(mdf),
}
- err = json.NewDecoder(mdf).Decode(&fs.mm)
+ fs.q, err = NewQuota(max, fs, fs.log.WithName("quota"))
+ if err != nil {
+ return
+ }
+ err = json.NewDecoder(mdf).Decode(&fs.md)
if err == io.EOF {
err = nil
}
fs.initMetadata()
+ fs.q.Init()
ctx, cancel := context.WithCancel(context.Background())
fs.cancel = cancel
go fs.flusher(ctx)
@@ -75,26 +82,25 @@ func NewFS(src, dst, metadata string, log logr.Logger) (fs *FS, err error) {
func (fs *FS) initMetadata() {
log := fs.log
- for k, v := range fs.mm {
+ maps.DeleteFunc(fs.md, func(k string, v *Metadata) bool {
_, err := fs.statDst(k)
if errors.Is(err, os.ErrNotExist) {
log.V(2).Info("removing not existing file from metadata", "file", k)
- delete(fs.mm, k)
- continue
+ return true
}
if len(v.Chunks) == 0 {
log.V(2).Info("removing empty file", "file", k)
err := fs.RemoveDst(k)
if err != nil {
log.Error(err, "error removing empty file", "file", k)
- continue
+ return false
}
- delete(fs.mm, k)
- continue
+ return true
}
v.fs = fs
v.name = k
- }
+ return false
+ })
}
func (fs *FS) Stat(name string) (fi stdfs.FileInfo, err error) {
@@ -126,7 +132,7 @@ func (fs *FS) statDst(name string) (fi stdfs.FileInfo, err error) {
}
func (fs *FS) CancelPreload(name string) {
- if cancel, ok := fs.prem[name]; ok {
+ if cancel, ok := fs.pm[name]; ok {
cancel()
}
}
@@ -146,9 +152,9 @@ func (fs *FS) Preload(name string) {
return
}
ctx, cancel := context.WithCancel(context.Background())
- fs.prem[name] = cancel
+ fs.pm[name] = cancel
unlock := func() {
- delete(fs.prem, name)
+ delete(fs.pm, name)
f.md.UnlockPreload()
}
go f.Preload(ctx, unlock)
@@ -199,15 +205,17 @@ func (fs *FS) Open(name string) (f http.File, err error) {
offline = true
log.V(2).Info("dir offline mode", "path", dp)
}
- return &Dir{f: sf, dc: fs.dc}, err
+ f = &Dir{f: sf, dc: fs.dc}
+ return
}
md := fs.metadata(name, sfi.Size())
- return &File{
+ f = &File{
log: log,
f: sf,
md: md,
offline: offline,
- }, nil
+ }
+ return
}
func (fs *FS) openCacheFile(name string, size int64) (df *os.File, err error) {
@@ -248,19 +256,29 @@ func (fs *FS) openCacheFile(name string, size int64) (df *os.File, err error) {
func (fs *FS) metadata(name string, size int64) (md *Metadata) {
fs.mu.Lock()
+ fs.log.Info("fs.mu.Lock()")
defer fs.mu.Unlock()
- if md, ok := fs.mm[name]; ok {
+ defer fs.log.Info("fs.mu.Unlock()")
+ if md, ok := fs.md[name]; ok {
+ if size > 0 && md.Size != size {
+ log := fs.log.WithValues("file", name)
+ log.V(2).Info("source file changed: deleting cache file", "src", size, "dst", md.Size)
+ err := md.Delete()
+ if err != nil {
+ log.Error(err, "error deleting cache file")
+ }
+ }
return md
}
md = &Metadata{Size: size, fs: fs, name: name}
- fs.mm[name] = md
+ fs.md[name] = md
return
}
func (fs *FS) CacheStatus(name string) int {
fs.mu.RLock()
defer fs.mu.RUnlock()
- if md, ok := fs.mm[name]; ok {
+ if md, ok := fs.md[name]; ok {
return int(math.Round(float64(md.Chunks.Size()) / float64(md.Size) * 100))
}
return -1
@@ -281,12 +299,11 @@ func (fs *FS) flushMetadata() {
log := fs.log
fs.mu.Lock()
defer fs.mu.Unlock()
- mm := make(map[string]*Metadata)
- for k, v := range fs.mm {
- if v.f != nil || len(v.Chunks) > 0 {
- mm[k] = v
- }
- }
+ log.V(2).Info("flushing metadata to disk")
+ md := maps.Clone(fs.md)
+ maps.DeleteFunc(md, func(_ string, v *Metadata) bool {
+ return len(v.Chunks) == 0
+ })
_, err := fs.mdf.Seek(0, io.SeekStart)
if err != nil {
log.Error(err, "failure seeking metadata file")
@@ -297,7 +314,7 @@ func (fs *FS) flushMetadata() {
log.Error(err, "failure truncating metadata file")
return
}
- err = fs.jenc.Encode(mm)
+ err = fs.jenc.Encode(md)
if err != nil {
log.Error(err, "failure flushing metadata file")
}
@@ -305,7 +322,10 @@ func (fs *FS) flushMetadata() {
func (fs *FS) Close() {
fs.cancel()
- for _, md := range fs.mm {
+ for _, cancel := range fs.pm {
+ cancel()
+ }
+ for _, md := range fs.md {
md.f.Sync()
md.f.Close()
}
diff --git a/pkg/fs/metadata.go b/pkg/fs/metadata.go
index b2d434d..b450e68 100644
--- a/pkg/fs/metadata.go
+++ b/pkg/fs/metadata.go
@@ -5,14 +5,23 @@ package fs
import (
"os"
"sync"
+ "sync/atomic"
+ "time"
)
+func now() int64 {
+ return time.Now().UTC().Unix()
+}
+
+type MetadataMap map[string]*Metadata
+
type Metadata struct {
mu sync.RWMutex `json:"-"`
fs *FS `json:"-"`
f *os.File `json:"-"`
name string `json:"-"`
Size int64 `json:"s"`
+ Atime int64 `json:"a"`
Chunks Chunks `json:"c"`
preload bool `json:"-"`
}
@@ -23,6 +32,7 @@ func (md *Metadata) Delete() error {
md.Chunks = Chunks{}
md.f.Close()
md.f = nil
+ atomic.StoreInt64(&md.Atime, now())
return md.fs.RemoveDst(md.name)
}
@@ -45,6 +55,7 @@ func (md *Metadata) ReadAt(p []byte, pos int64) (int, error) {
return 0, err
}
}
+ atomic.StoreInt64(&md.Atime, now())
return md.f.ReadAt(p, pos)
}
@@ -55,6 +66,8 @@ func (md *Metadata) WriteAt(data []byte, pos int64) (int, error) {
return 0, err
}
}
+ atomic.StoreInt64(&md.Atime, now())
+ md.fs.q.Add(len(data))
return md.f.WriteAt(data, pos)
}
diff --git a/pkg/fs/quota.go b/pkg/fs/quota.go
new file mode 100644
index 0000000..c8f7bfa
--- /dev/null
+++ b/pkg/fs/quota.go
@@ -0,0 +1,70 @@
+// Copyright (C) 2022 Marius Schellenberger
+
+package fs
+
+import (
+ "errors"
+ "math"
+ "sync/atomic"
+
+ "github.com/go-logr/logr"
+)
+
+var InvalidMaxQuota = errors.New("invalid max quota")
+
+type Quota struct {
+ log logr.Logger
+ fs *FS
+ max int64
+ cur int64
+}
+
+func NewQuota(max int64, fs *FS, log logr.Logger) (q *Quota, err error) {
+ if max <= 0 {
+ err = InvalidMaxQuota
+ return
+ }
+ q = &Quota{log: log, fs: fs, max: max}
+ return
+}
+
+func (q *Quota) Init() {
+ for _, v := range q.fs.md {
+ q.cur += v.Chunks.Size()
+ }
+ q.log.Info("quota usage", "current", q.cur, "max", q.max)
+}
+
+func (q *Quota) cleanup() {
+ if !q.fs.mu.TryLock() {
+ return
+ }
+ q.log.Info("!q.fs.mu.TryLock()")
+ q.log.Info("quota usage", "current", q.cur, "max", q.max)
+ defer q.fs.mu.Unlock()
+ defer q.log.Info("q.fs.mu.Unlock()")
+ atime := int64(math.MaxInt64)
+ var m *Metadata
+ for _, v := range q.fs.md {
+ if v.Atime != 0 && v.f != nil && v.Atime < atime {
+ atime = v.Atime
+ m = v
+ }
+ }
+ if m != nil {
+ log := q.log.WithValues("file", m.name)
+ log.Info("deleting oldest file")
+ err := m.Delete()
+ if err != nil {
+ q.log.Error(err, "error deleting oldest file")
+ return
+ }
+ atomic.AddInt64(&q.cur, -m.Size)
+ }
+}
+
+func (q *Quota) Add(n int) {
+ if q.max < atomic.AddInt64(&q.cur, int64(n)) {
+ q.cleanup()
+ }
+}
diff --git a/pkg/srv/interceptor.go b/pkg/srv/interceptor.go
index 104d366..ec50cdf 100644
--- a/pkg/srv/interceptor.go
+++ b/pkg/srv/interceptor.go
@@ -10,6 +10,8 @@ import (
"strings"
"cachefs/pkg/fs"
+
+ "golang.org/x/exp/slices"
)
type statusInterceptor struct {
@@ -51,9 +53,7 @@ type file struct {
type files []file
-func (f files) Len() int { return len(f) }
-func (f files) Less(i, j int) bool { return f[i].Name < f[j].Name }
-func (f files) Swap(i, j int) { f[i], f[j] = f[j], f[i] }
+func (files) Less(i, j file) bool { return i.Name < j.Name }
type responseInterceptor struct {
buf bytes.Buffer
@@ -98,7 +98,7 @@ func (r *responseInterceptor) GetPaths(path string, fs *fs.FS) (dir dirContents,
}
}
sort.Strings(dir.Dirs)
- sort.Sort(dir.Files)
+ slices.SortFunc(dir.Files, dir.Files.Less)
return
}
diff --git a/pkg/srv/srv.go b/pkg/srv/srv.go
index 235818d..2b0c180 100644
--- a/pkg/srv/srv.go
+++ b/pkg/srv/srv.go
@@ -7,7 +7,9 @@ import (
"io"
stdfs "io/fs"
"net/http"
+ "os"
"path"
+ "runtime"
"strings"
"cachefs/pkg/fs"
@@ -15,6 +17,21 @@ import (
"github.com/go-logr/logr"
)
+func PrintStack() {
+ os.Stderr.Write(Stack())
+}
+
+func Stack() []byte {
+ buf := make([]byte, 1024)
+ for {
+ n := runtime.Stack(buf, true)
+ if n < len(buf) {
+ return buf[:n]
+ }
+ buf = make([]byte, 2*len(buf))
+ }
+}
+
type FileServer struct {
log logr.Logger
fs *fs.FS
@@ -51,13 +68,17 @@ func (fs *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
r.URL.Path = upath
}
p := path.Clean(upath)
+ option := r.FormValue("o")
+ if option == "debug" {
+ PrintStack()
+ }
d, _, err := fs.fs.StatWithOffline(p)
if err != nil {
msg, code := toHTTPError(err)
http.Error(w, msg, code)
return
}
- option := r.FormValue("o")
+ //option := r.FormValue("o")
if option == "v" {
err = video.Execute(w, r.URL.Path)
if err != nil {
diff --git a/pkg/srv/templates.go b/pkg/srv/templates.go
index 382788e..f97a229 100644
--- a/pkg/srv/templates.go
+++ b/pkg/srv/templates.go
@@ -30,7 +30,7 @@ var (
{{end -}}
{{range $s := .Files -}}
| {{$s.Name}} |
- [v] [n] [p] [s] {{if ge $s.Status 0}}{{$s.Status}}%{{end}} |
+ [v] [n] [p] [s] {{if ne $s.Status -1}}{{$s.Status}}%{{end}} |
{{end -}}
diff --git a/pkg/srv/webdav.go b/pkg/srv/webdav.go
index 250e6cc..405ceb4 100644
--- a/pkg/srv/webdav.go
+++ b/pkg/srv/webdav.go
@@ -9,6 +9,7 @@ import (
"cachefs/pkg/fs"
"github.com/go-logr/logr"
+ "golang.org/x/exp/slices"
"golang.org/x/net/webdav"
)
@@ -65,10 +66,5 @@ func skipDavLog(path string) bool {
if i > 0 {
path = path[i+1:]
}
- for _, v := range skipDavFiles {
- if path == v {
- return true
- }
- }
- return false
+ return slices.Contains(skipDavFiles, path)
}
diff --git a/scripts/install_openbsd.sh b/scripts/install_openbsd.sh
index cc22dec..fa14751 100644
--- a/scripts/install_openbsd.sh
+++ b/scripts/install_openbsd.sh
@@ -11,7 +11,7 @@ cat <<"EOF">/etc/rc.d/cachefs
#!/bin/sh
daemon="/usr/local/sbin/cachefs"
-daemon_flags="-src /mnt/nfs -dst /mnt/cache -data /var/cachefs/md.json -listen :9090 -webdav :9091 -v 2"
+daemon_flags="-src /mnt/nfs -dst /mnt/cache -data /var/cachefs/md.json -listen :9090 -webdav :9091 -quota 10 -v 2"
daemon_logger="daemon.info"
daemon_user="_cachefs"
diff --git a/vendor/golang.org/x/exp/AUTHORS b/vendor/golang.org/x/exp/AUTHORS
new file mode 100644
index 0000000..15167cd
--- /dev/null
+++ b/vendor/golang.org/x/exp/AUTHORS
@@ -0,0 +1,3 @@
+# This source code refers to The Go Authors for copyright purposes.
+# The master list of authors is in the main Go distribution,
+# visible at http://tip.golang.org/AUTHORS.
diff --git a/vendor/golang.org/x/exp/CONTRIBUTORS b/vendor/golang.org/x/exp/CONTRIBUTORS
new file mode 100644
index 0000000..1c4577e
--- /dev/null
+++ b/vendor/golang.org/x/exp/CONTRIBUTORS
@@ -0,0 +1,3 @@
+# This source code was written by the Go contributors.
+# The master list of contributors is in the main Go distribution,
+# visible at http://tip.golang.org/CONTRIBUTORS.
diff --git a/vendor/golang.org/x/exp/LICENSE b/vendor/golang.org/x/exp/LICENSE
new file mode 100644
index 0000000..6a66aea
--- /dev/null
+++ b/vendor/golang.org/x/exp/LICENSE
@@ -0,0 +1,27 @@
+Copyright (c) 2009 The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+ * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/vendor/golang.org/x/exp/PATENTS b/vendor/golang.org/x/exp/PATENTS
new file mode 100644
index 0000000..7330990
--- /dev/null
+++ b/vendor/golang.org/x/exp/PATENTS
@@ -0,0 +1,22 @@
+Additional IP Rights Grant (Patents)
+
+"This implementation" means the copyrightable works distributed by
+Google as part of the Go project.
+
+Google hereby grants to You a perpetual, worldwide, non-exclusive,
+no-charge, royalty-free, irrevocable (except as stated in this section)
+patent license to make, have made, use, offer to sell, sell, import,
+transfer and otherwise run, modify and propagate the contents of this
+implementation of Go, where such license applies only to those patent
+claims, both currently owned or controlled by Google and acquired in
+the future, licensable by Google that are necessarily infringed by this
+implementation of Go. This grant does not include claims that would be
+infringed only as a consequence of further modification of this
+implementation. If you or your agent or exclusive licensee institute or
+order or agree to the institution of patent litigation against any
+entity (including a cross-claim or counterclaim in a lawsuit) alleging
+that this implementation of Go or any code incorporated within this
+implementation of Go constitutes direct or contributory patent
+infringement, or inducement of patent infringement, then any patent
+rights granted to you under this License for this implementation of Go
+shall terminate as of the date such litigation is filed.
diff --git a/vendor/golang.org/x/exp/constraints/constraints.go b/vendor/golang.org/x/exp/constraints/constraints.go
new file mode 100644
index 0000000..2c033df
--- /dev/null
+++ b/vendor/golang.org/x/exp/constraints/constraints.go
@@ -0,0 +1,50 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package constraints defines a set of useful constraints to be used
+// with type parameters.
+package constraints
+
+// Signed is a constraint that permits any signed integer type.
+// If future releases of Go add new predeclared signed integer types,
+// this constraint will be modified to include them.
+type Signed interface {
+ ~int | ~int8 | ~int16 | ~int32 | ~int64
+}
+
+// Unsigned is a constraint that permits any unsigned integer type.
+// If future releases of Go add new predeclared unsigned integer types,
+// this constraint will be modified to include them.
+type Unsigned interface {
+ ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr
+}
+
+// Integer is a constraint that permits any integer type.
+// If future releases of Go add new predeclared integer types,
+// this constraint will be modified to include them.
+type Integer interface {
+ Signed | Unsigned
+}
+
+// Float is a constraint that permits any floating-point type.
+// If future releases of Go add new predeclared floating-point types,
+// this constraint will be modified to include them.
+type Float interface {
+ ~float32 | ~float64
+}
+
+// Complex is a constraint that permits any complex numeric type.
+// If future releases of Go add new predeclared complex numeric types,
+// this constraint will be modified to include them.
+type Complex interface {
+ ~complex64 | ~complex128
+}
+
+// Ordered is a constraint that permits any ordered type: any type
+// that supports the operators < <= >= >.
+// If future releases of Go add new ordered types,
+// this constraint will be modified to include them.
+type Ordered interface {
+ Integer | Float | ~string
+}
diff --git a/vendor/golang.org/x/exp/maps/maps.go b/vendor/golang.org/x/exp/maps/maps.go
new file mode 100644
index 0000000..fc27cd1
--- /dev/null
+++ b/vendor/golang.org/x/exp/maps/maps.go
@@ -0,0 +1,90 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package maps defines various functions useful with maps of any type.
+package maps
+
+// Keys returns the keys of the map m.
+// The keys will be in an indeterminate order.
+func Keys[M ~map[K]V, K comparable, V any](m M) []K {
+ r := make([]K, 0, len(m))
+ for k := range m {
+ r = append(r, k)
+ }
+ return r
+}
+
+// Values returns the values of the map m.
+// The values will be in an indeterminate order.
+func Values[M ~map[K]V, K comparable, V any](m M) []V {
+ r := make([]V, 0, len(m))
+ for _, v := range m {
+ r = append(r, v)
+ }
+ return r
+}
+
+// Equal reports whether two maps contain the same key/value pairs.
+// Values are compared using ==.
+func Equal[M1, M2 ~map[K]V, K, V comparable](m1 M1, m2 M2) bool {
+ if len(m1) != len(m2) {
+ return false
+ }
+ for k, v1 := range m1 {
+ if v2, ok := m2[k]; !ok || v1 != v2 {
+ return false
+ }
+ }
+ return true
+}
+
+// EqualFunc is like Equal, but compares values using eq.
+// Keys are still compared with ==.
+func EqualFunc[M1 ~map[K]V1, M2 ~map[K]V2, K comparable, V1, V2 any](m1 M1, m2 M2, eq func(V1, V2) bool) bool {
+ if len(m1) != len(m2) {
+ return false
+ }
+ for k, v1 := range m1 {
+ if v2, ok := m2[k]; !ok || !eq(v1, v2) {
+ return false
+ }
+ }
+ return true
+}
+
+// Clear removes all entries from m, leaving it empty.
+func Clear[M ~map[K]V, K comparable, V any](m M) {
+ for k := range m {
+ delete(m, k)
+ }
+}
+
+// Clone returns a copy of m. This is a shallow clone:
+// the new keys and values are set using ordinary assignment.
+func Clone[M ~map[K]V, K comparable, V any](m M) M {
+ r := make(M, len(m))
+ for k, v := range m {
+ r[k] = v
+ }
+ return r
+}
+
+// Copy copies all key/value pairs in src adding them to dst.
+// When a key in src is already present in dst,
+// the value in dst will be overwritten by the value associated
+// with the key in src.
+func Copy[M ~map[K]V, K comparable, V any](dst, src M) {
+ for k, v := range src {
+ dst[k] = v
+ }
+}
+
+// DeleteFunc deletes any key/value pairs from m for which del returns true.
+func DeleteFunc[M ~map[K]V, K comparable, V any](m M, del func(K, V) bool) {
+ for k, v := range m {
+ if del(k, v) {
+ delete(m, k)
+ }
+ }
+}
diff --git a/vendor/golang.org/x/exp/slices/slices.go b/vendor/golang.org/x/exp/slices/slices.go
new file mode 100644
index 0000000..df78daf
--- /dev/null
+++ b/vendor/golang.org/x/exp/slices/slices.go
@@ -0,0 +1,213 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package slices defines various functions useful with slices of any type.
+// Unless otherwise specified, these functions all apply to the elements
+// of a slice at index 0 <= i < len(s).
+package slices
+
+import "golang.org/x/exp/constraints"
+
+// Equal reports whether two slices are equal: the same length and all
+// elements equal. If the lengths are different, Equal returns false.
+// Otherwise, the elements are compared in increasing index order, and the
+// comparison stops at the first unequal pair.
+// Floating point NaNs are not considered equal.
+func Equal[E comparable](s1, s2 []E) bool {
+ if len(s1) != len(s2) {
+ return false
+ }
+ for i := range s1 {
+ if s1[i] != s2[i] {
+ return false
+ }
+ }
+ return true
+}
+
+// EqualFunc reports whether two slices are equal using a comparison
+// function on each pair of elements. If the lengths are different,
+// EqualFunc returns false. Otherwise, the elements are compared in
+// increasing index order, and the comparison stops at the first index
+// for which eq returns false.
+func EqualFunc[E1, E2 any](s1 []E1, s2 []E2, eq func(E1, E2) bool) bool {
+ if len(s1) != len(s2) {
+ return false
+ }
+ for i, v1 := range s1 {
+ v2 := s2[i]
+ if !eq(v1, v2) {
+ return false
+ }
+ }
+ return true
+}
+
+// Compare compares the elements of s1 and s2.
+// The elements are compared sequentially, starting at index 0,
+// until one element is not equal to the other.
+// The result of comparing the first non-matching elements is returned.
+// If both slices are equal until one of them ends, the shorter slice is
+// considered less than the longer one.
+// The result is 0 if s1 == s2, -1 if s1 < s2, and +1 if s1 > s2.
+// Comparisons involving floating point NaNs are ignored.
+func Compare[E constraints.Ordered](s1, s2 []E) int {
+ s2len := len(s2)
+ for i, v1 := range s1 {
+ if i >= s2len {
+ return +1
+ }
+ v2 := s2[i]
+ switch {
+ case v1 < v2:
+ return -1
+ case v1 > v2:
+ return +1
+ }
+ }
+ if len(s1) < s2len {
+ return -1
+ }
+ return 0
+}
+
+// CompareFunc is like Compare but uses a comparison function
+// on each pair of elements. The elements are compared in increasing
+// index order, and the comparisons stop after the first time cmp
+// returns non-zero.
+// The result is the first non-zero result of cmp; if cmp always
+// returns 0 the result is 0 if len(s1) == len(s2), -1 if len(s1) < len(s2),
+// and +1 if len(s1) > len(s2).
+func CompareFunc[E1, E2 any](s1 []E1, s2 []E2, cmp func(E1, E2) int) int {
+ s2len := len(s2)
+ for i, v1 := range s1 {
+ if i >= s2len {
+ return +1
+ }
+ v2 := s2[i]
+ if c := cmp(v1, v2); c != 0 {
+ return c
+ }
+ }
+ if len(s1) < s2len {
+ return -1
+ }
+ return 0
+}
+
+// Index returns the index of the first occurrence of v in s,
+// or -1 if not present.
+func Index[E comparable](s []E, v E) int {
+ for i, vs := range s {
+ if v == vs {
+ return i
+ }
+ }
+ return -1
+}
+
+// IndexFunc returns the first index i satisfying f(s[i]),
+// or -1 if none do.
+func IndexFunc[E any](s []E, f func(E) bool) int {
+ for i, v := range s {
+ if f(v) {
+ return i
+ }
+ }
+ return -1
+}
+
+// Contains reports whether v is present in s.
+func Contains[E comparable](s []E, v E) bool {
+ return Index(s, v) >= 0
+}
+
+// Insert inserts the values v... into s at index i,
+// returning the modified slice.
+// In the returned slice r, r[i] == v[0].
+// Insert panics if i is out of range.
+// This function is O(len(s) + len(v)).
+func Insert[S ~[]E, E any](s S, i int, v ...E) S {
+ tot := len(s) + len(v)
+ if tot <= cap(s) {
+ s2 := s[:tot]
+ copy(s2[i+len(v):], s[i:])
+ copy(s2[i:], v)
+ return s2
+ }
+ s2 := make(S, tot)
+ copy(s2, s[:i])
+ copy(s2[i:], v)
+ copy(s2[i+len(v):], s[i:])
+ return s2
+}
+
+// Delete removes the elements s[i:j] from s, returning the modified slice.
+// Delete panics if s[i:j] is not a valid slice of s.
+// Delete modifies the contents of the slice s; it does not create a new slice.
+// Delete is O(len(s)-(j-i)), so if many items must be deleted, it is better to
+// make a single call deleting them all together than to delete one at a time.
+func Delete[S ~[]E, E any](s S, i, j int) S {
+ return append(s[:i], s[j:]...)
+}
+
+// Clone returns a copy of the slice.
+// The elements are copied using assignment, so this is a shallow clone.
+func Clone[S ~[]E, E any](s S) S {
+ // Preserve nil in case it matters.
+ if s == nil {
+ return nil
+ }
+ return append(S([]E{}), s...)
+}
+
+// Compact replaces consecutive runs of equal elements with a single copy.
+// This is like the uniq command found on Unix.
+// Compact modifies the contents of the slice s; it does not create a new slice.
+func Compact[S ~[]E, E comparable](s S) S {
+ if len(s) == 0 {
+ return s
+ }
+ i := 1
+ last := s[0]
+ for _, v := range s[1:] {
+ if v != last {
+ s[i] = v
+ i++
+ last = v
+ }
+ }
+ return s[:i]
+}
+
+// CompactFunc is like Compact but uses a comparison function.
+func CompactFunc[S ~[]E, E any](s S, eq func(E, E) bool) S {
+ if len(s) == 0 {
+ return s
+ }
+ i := 1
+ last := s[0]
+ for _, v := range s[1:] {
+ if !eq(v, last) {
+ s[i] = v
+ i++
+ last = v
+ }
+ }
+ return s[:i]
+}
+
+// Grow increases the slice's capacity, if necessary, to guarantee space for
+// another n elements. After Grow(n), at least n elements can be appended
+// to the slice without another allocation. Grow may modify elements of the
+// slice between the length and the capacity. If n is negative or too large to
+// allocate the memory, Grow panics.
+func Grow[S ~[]E, E any](s S, n int) S {
+ return append(s, make(S, n)...)[:len(s)]
+}
+
+// Clip removes unused capacity from the slice, returning s[:len(s):len(s)].
+func Clip[S ~[]E, E any](s S) S {
+ return s[:len(s):len(s)]
+}
diff --git a/vendor/golang.org/x/exp/slices/sort.go b/vendor/golang.org/x/exp/slices/sort.go
new file mode 100644
index 0000000..ed9f41a
--- /dev/null
+++ b/vendor/golang.org/x/exp/slices/sort.go
@@ -0,0 +1,95 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package slices
+
+import "golang.org/x/exp/constraints"
+
+// Sort sorts a slice of any ordered type in ascending order.
+func Sort[Elem constraints.Ordered](x []Elem) {
+ n := len(x)
+ quickSortOrdered(x, 0, n, maxDepth(n))
+}
+
+// Sort sorts the slice x in ascending order as determined by the less function.
+// This sort is not guaranteed to be stable.
+func SortFunc[Elem any](x []Elem, less func(a, b Elem) bool) {
+ n := len(x)
+ quickSortLessFunc(x, 0, n, maxDepth(n), less)
+}
+
+// SortStable sorts the slice x while keeping the original order of equal
+// elements, using less to compare elements.
+func SortStableFunc[Elem any](x []Elem, less func(a, b Elem) bool) {
+ stableLessFunc(x, len(x), less)
+}
+
+// IsSorted reports whether x is sorted in ascending order.
+func IsSorted[Elem constraints.Ordered](x []Elem) bool {
+ for i := len(x) - 1; i > 0; i-- {
+ if x[i] < x[i-1] {
+ return false
+ }
+ }
+ return true
+}
+
+// IsSortedFunc reports whether x is sorted in ascending order, with less as the
+// comparison function.
+func IsSortedFunc[Elem any](x []Elem, less func(a, b Elem) bool) bool {
+ for i := len(x) - 1; i > 0; i-- {
+ if less(x[i], x[i-1]) {
+ return false
+ }
+ }
+ return true
+}
+
+// BinarySearch searches for target in a sorted slice and returns the smallest
+// index at which target is found. If the target is not found, the index at
+// which it could be inserted into the slice is returned; therefore, if the
+// intention is to find target itself a separate check for equality with the
+// element at the returned index is required.
+func BinarySearch[Elem constraints.Ordered](x []Elem, target Elem) int {
+ return search(len(x), func(i int) bool { return x[i] >= target })
+}
+
+// BinarySearchFunc uses binary search to find and return the smallest index i
+// in [0, n) at which ok(i) is true, assuming that on the range [0, n),
+// ok(i) == true implies ok(i+1) == true. That is, BinarySearchFunc requires
+// that ok is false for some (possibly empty) prefix of the input range [0, n)
+// and then true for the (possibly empty) remainder; BinarySearchFunc returns
+// the first true index. If there is no such index, BinarySearchFunc returns n.
+// (Note that the "not found" return value is not -1 as in, for instance,
+// strings.Index.) Search calls ok(i) only for i in the range [0, n).
+func BinarySearchFunc[Elem any](x []Elem, ok func(Elem) bool) int {
+ return search(len(x), func(i int) bool { return ok(x[i]) })
+}
+
+// maxDepth returns a threshold at which quicksort should switch
+// to heapsort. It returns 2*ceil(lg(n+1)).
+func maxDepth(n int) int {
+ var depth int
+ for i := n; i > 0; i >>= 1 {
+ depth++
+ }
+ return depth * 2
+}
+
+func search(n int, f func(int) bool) int {
+ // Define f(-1) == false and f(n) == true.
+ // Invariant: f(i-1) == false, f(j) == true.
+ i, j := 0, n
+ for i < j {
+ h := int(uint(i+j) >> 1) // avoid overflow when computing h
+ // i ≤ h < j
+ if !f(h) {
+ i = h + 1 // preserves f(i-1) == false
+ } else {
+ j = h // preserves f(j) == true
+ }
+ }
+ // i == j, f(i-1) == false, and f(j) (= f(i)) == true => answer is i.
+ return i
+}
diff --git a/vendor/golang.org/x/exp/slices/zsortfunc.go b/vendor/golang.org/x/exp/slices/zsortfunc.go
new file mode 100644
index 0000000..82f156f
--- /dev/null
+++ b/vendor/golang.org/x/exp/slices/zsortfunc.go
@@ -0,0 +1,342 @@
+// Code generated by gen_sort_variants.go; DO NOT EDIT.
+
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package slices
+
+// insertionSortLessFunc sorts data[a:b] using insertion sort.
+func insertionSortLessFunc[Elem any](data []Elem, a, b int, less func(a, b Elem) bool) {
+ for i := a + 1; i < b; i++ {
+ for j := i; j > a && less(data[j], data[j-1]); j-- {
+ data[j], data[j-1] = data[j-1], data[j]
+ }
+ }
+}
+
+// siftDownLessFunc implements the heap property on data[lo:hi].
+// first is an offset into the array where the root of the heap lies.
+func siftDownLessFunc[Elem any](data []Elem, lo, hi, first int, less func(a, b Elem) bool) {
+ root := lo
+ for {
+ child := 2*root + 1
+ if child >= hi {
+ break
+ }
+ if child+1 < hi && less(data[first+child], data[first+child+1]) {
+ child++
+ }
+ if !less(data[first+root], data[first+child]) {
+ return
+ }
+ data[first+root], data[first+child] = data[first+child], data[first+root]
+ root = child
+ }
+}
+
+func heapSortLessFunc[Elem any](data []Elem, a, b int, less func(a, b Elem) bool) {
+ first := a
+ lo := 0
+ hi := b - a
+
+ // Build heap with greatest element at top.
+ for i := (hi - 1) / 2; i >= 0; i-- {
+ siftDownLessFunc(data, i, hi, first, less)
+ }
+
+ // Pop elements, largest first, into end of data.
+ for i := hi - 1; i >= 0; i-- {
+ data[first], data[first+i] = data[first+i], data[first]
+ siftDownLessFunc(data, lo, i, first, less)
+ }
+}
+
+// Quicksort, loosely following Bentley and McIlroy,
+// "Engineering a Sort Function" SP&E November 1993.
+
+// medianOfThreeLessFunc moves the median of the three values data[m0], data[m1], data[m2] into data[m1].
+func medianOfThreeLessFunc[Elem any](data []Elem, m1, m0, m2 int, less func(a, b Elem) bool) {
+ // sort 3 elements
+ if less(data[m1], data[m0]) {
+ data[m1], data[m0] = data[m0], data[m1]
+ }
+ // data[m0] <= data[m1]
+ if less(data[m2], data[m1]) {
+ data[m2], data[m1] = data[m1], data[m2]
+ // data[m0] <= data[m2] && data[m1] < data[m2]
+ if less(data[m1], data[m0]) {
+ data[m1], data[m0] = data[m0], data[m1]
+ }
+ }
+ // now data[m0] <= data[m1] <= data[m2]
+}
+
+func swapRangeLessFunc[Elem any](data []Elem, a, b, n int, less func(a, b Elem) bool) {
+ for i := 0; i < n; i++ {
+ data[a+i], data[b+i] = data[b+i], data[a+i]
+ }
+}
+
+func doPivotLessFunc[Elem any](data []Elem, lo, hi int, less func(a, b Elem) bool) (midlo, midhi int) {
+ m := int(uint(lo+hi) >> 1) // Written like this to avoid integer overflow.
+ if hi-lo > 40 {
+ // Tukey's "Ninther" median of three medians of three.
+ s := (hi - lo) / 8
+ medianOfThreeLessFunc(data, lo, lo+s, lo+2*s, less)
+ medianOfThreeLessFunc(data, m, m-s, m+s, less)
+ medianOfThreeLessFunc(data, hi-1, hi-1-s, hi-1-2*s, less)
+ }
+ medianOfThreeLessFunc(data, lo, m, hi-1, less)
+
+ // Invariants are:
+ // data[lo] = pivot (set up by ChoosePivot)
+ // data[lo < i < a] < pivot
+ // data[a <= i < b] <= pivot
+ // data[b <= i < c] unexamined
+ // data[c <= i < hi-1] > pivot
+ // data[hi-1] >= pivot
+ pivot := lo
+ a, c := lo+1, hi-1
+
+ for ; a < c && less(data[a], data[pivot]); a++ {
+ }
+ b := a
+ for {
+ for ; b < c && !less(data[pivot], data[b]); b++ { // data[b] <= pivot
+ }
+ for ; b < c && less(data[pivot], data[c-1]); c-- { // data[c-1] > pivot
+ }
+ if b >= c {
+ break
+ }
+ // data[b] > pivot; data[c-1] <= pivot
+ data[b], data[c-1] = data[c-1], data[b]
+ b++
+ c--
+ }
+ // If hi-c<3 then there are duplicates (by property of median of nine).
+ // Let's be a bit more conservative, and set border to 5.
+ protect := hi-c < 5
+ if !protect && hi-c < (hi-lo)/4 {
+ // Lets test some points for equality to pivot
+ dups := 0
+ if !less(data[pivot], data[hi-1]) { // data[hi-1] = pivot
+ data[c], data[hi-1] = data[hi-1], data[c]
+ c++
+ dups++
+ }
+ if !less(data[b-1], data[pivot]) { // data[b-1] = pivot
+ b--
+ dups++
+ }
+ // m-lo = (hi-lo)/2 > 6
+ // b-lo > (hi-lo)*3/4-1 > 8
+ // ==> m < b ==> data[m] <= pivot
+ if !less(data[m], data[pivot]) { // data[m] = pivot
+ data[m], data[b-1] = data[b-1], data[m]
+ b--
+ dups++
+ }
+ // if at least 2 points are equal to pivot, assume skewed distribution
+ protect = dups > 1
+ }
+ if protect {
+ // Protect against a lot of duplicates
+ // Add invariant:
+ // data[a <= i < b] unexamined
+ // data[b <= i < c] = pivot
+ for {
+ for ; a < b && !less(data[b-1], data[pivot]); b-- { // data[b] == pivot
+ }
+ for ; a < b && less(data[a], data[pivot]); a++ { // data[a] < pivot
+ }
+ if a >= b {
+ break
+ }
+ // data[a] == pivot; data[b-1] < pivot
+ data[a], data[b-1] = data[b-1], data[a]
+ a++
+ b--
+ }
+ }
+ // Swap pivot into middle
+ data[pivot], data[b-1] = data[b-1], data[pivot]
+ return b - 1, c
+}
+
+func quickSortLessFunc[Elem any](data []Elem, a, b, maxDepth int, less func(a, b Elem) bool) {
+ for b-a > 12 { // Use ShellSort for slices <= 12 elements
+ if maxDepth == 0 {
+ heapSortLessFunc(data, a, b, less)
+ return
+ }
+ maxDepth--
+ mlo, mhi := doPivotLessFunc(data, a, b, less)
+ // Avoiding recursion on the larger subproblem guarantees
+ // a stack depth of at most lg(b-a).
+ if mlo-a < b-mhi {
+ quickSortLessFunc(data, a, mlo, maxDepth, less)
+ a = mhi // i.e., quickSortLessFunc(data, mhi, b)
+ } else {
+ quickSortLessFunc(data, mhi, b, maxDepth, less)
+ b = mlo // i.e., quickSortLessFunc(data, a, mlo)
+ }
+ }
+ if b-a > 1 {
+ // Do ShellSort pass with gap 6
+ // It could be written in this simplified form cause b-a <= 12
+ for i := a + 6; i < b; i++ {
+ if less(data[i], data[i-6]) {
+ data[i], data[i-6] = data[i-6], data[i]
+ }
+ }
+ insertionSortLessFunc(data, a, b, less)
+ }
+}
+
+func stableLessFunc[Elem any](data []Elem, n int, less func(a, b Elem) bool) {
+ blockSize := 20 // must be > 0
+ a, b := 0, blockSize
+ for b <= n {
+ insertionSortLessFunc(data, a, b, less)
+ a = b
+ b += blockSize
+ }
+ insertionSortLessFunc(data, a, n, less)
+
+ for blockSize < n {
+ a, b = 0, 2*blockSize
+ for b <= n {
+ symMergeLessFunc(data, a, a+blockSize, b, less)
+ a = b
+ b += 2 * blockSize
+ }
+ if m := a + blockSize; m < n {
+ symMergeLessFunc(data, a, m, n, less)
+ }
+ blockSize *= 2
+ }
+}
+
+// symMergeLessFunc merges the two sorted subsequences data[a:m] and data[m:b] using
+// the SymMerge algorithm from Pok-Son Kim and Arne Kutzner, "Stable Minimum
+// Storage Merging by Symmetric Comparisons", in Susanne Albers and Tomasz
+// Radzik, editors, Algorithms - ESA 2004, volume 3221 of Lecture Notes in
+// Computer Science, pages 714-723. Springer, 2004.
+//
+// Let M = m-a and N = b-n. Wolog M < N.
+// The recursion depth is bound by ceil(log(N+M)).
+// The algorithm needs O(M*log(N/M + 1)) calls to data.Less.
+// The algorithm needs O((M+N)*log(M)) calls to data.Swap.
+//
+// The paper gives O((M+N)*log(M)) as the number of assignments assuming a
+// rotation algorithm which uses O(M+N+gcd(M+N)) assignments. The argumentation
+// in the paper carries through for Swap operations, especially as the block
+// swapping rotate uses only O(M+N) Swaps.
+//
+// symMerge assumes non-degenerate arguments: a < m && m < b.
+// Having the caller check this condition eliminates many leaf recursion calls,
+// which improves performance.
+func symMergeLessFunc[Elem any](data []Elem, a, m, b int, less func(a, b Elem) bool) {
+ // Avoid unnecessary recursions of symMerge
+ // by direct insertion of data[a] into data[m:b]
+ // if data[a:m] only contains one element.
+ if m-a == 1 {
+ // Use binary search to find the lowest index i
+ // such that data[i] >= data[a] for m <= i < b.
+ // Exit the search loop with i == b in case no such index exists.
+ i := m
+ j := b
+ for i < j {
+ h := int(uint(i+j) >> 1)
+ if less(data[h], data[a]) {
+ i = h + 1
+ } else {
+ j = h
+ }
+ }
+ // Swap values until data[a] reaches the position before i.
+ for k := a; k < i-1; k++ {
+ data[k], data[k+1] = data[k+1], data[k]
+ }
+ return
+ }
+
+ // Avoid unnecessary recursions of symMerge
+ // by direct insertion of data[m] into data[a:m]
+ // if data[m:b] only contains one element.
+ if b-m == 1 {
+ // Use binary search to find the lowest index i
+ // such that data[i] > data[m] for a <= i < m.
+ // Exit the search loop with i == m in case no such index exists.
+ i := a
+ j := m
+ for i < j {
+ h := int(uint(i+j) >> 1)
+ if !less(data[m], data[h]) {
+ i = h + 1
+ } else {
+ j = h
+ }
+ }
+ // Swap values until data[m] reaches the position i.
+ for k := m; k > i; k-- {
+ data[k], data[k-1] = data[k-1], data[k]
+ }
+ return
+ }
+
+ mid := int(uint(a+b) >> 1)
+ n := mid + m
+ var start, r int
+ if m > mid {
+ start = n - b
+ r = mid
+ } else {
+ start = a
+ r = m
+ }
+ p := n - 1
+
+ for start < r {
+ c := int(uint(start+r) >> 1)
+ if !less(data[p-c], data[c]) {
+ start = c + 1
+ } else {
+ r = c
+ }
+ }
+
+ end := n - start
+ if start < m && m < end {
+ rotateLessFunc(data, start, m, end, less)
+ }
+ if a < start && start < mid {
+ symMergeLessFunc(data, a, start, mid, less)
+ }
+ if mid < end && end < b {
+ symMergeLessFunc(data, mid, end, b, less)
+ }
+}
+
+// rotateLessFunc rotates two consecutive blocks u = data[a:m] and v = data[m:b] in data:
+// Data of the form 'x u v y' is changed to 'x v u y'.
+// rotate performs at most b-a many calls to data.Swap,
+// and it assumes non-degenerate arguments: a < m && m < b.
+func rotateLessFunc[Elem any](data []Elem, a, m, b int, less func(a, b Elem) bool) {
+ i := m - a
+ j := b - m
+
+ for i != j {
+ if i > j {
+ swapRangeLessFunc(data, m-i, m, j, less)
+ i -= j
+ } else {
+ swapRangeLessFunc(data, m-i, m+j-i, i, less)
+ j -= i
+ }
+ }
+ // i == j
+ swapRangeLessFunc(data, m-i, m, i, less)
+}
diff --git a/vendor/golang.org/x/exp/slices/zsortordered.go b/vendor/golang.org/x/exp/slices/zsortordered.go
new file mode 100644
index 0000000..6fa64a2
--- /dev/null
+++ b/vendor/golang.org/x/exp/slices/zsortordered.go
@@ -0,0 +1,344 @@
+// Code generated by gen_sort_variants.go; DO NOT EDIT.
+
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package slices
+
+import "golang.org/x/exp/constraints"
+
+// insertionSortOrdered sorts data[a:b] using insertion sort.
+func insertionSortOrdered[Elem constraints.Ordered](data []Elem, a, b int) {
+ for i := a + 1; i < b; i++ {
+ for j := i; j > a && (data[j] < data[j-1]); j-- {
+ data[j], data[j-1] = data[j-1], data[j]
+ }
+ }
+}
+
+// siftDownOrdered implements the heap property on data[lo:hi].
+// first is an offset into the array where the root of the heap lies.
+func siftDownOrdered[Elem constraints.Ordered](data []Elem, lo, hi, first int) {
+ root := lo
+ for {
+ child := 2*root + 1
+ if child >= hi {
+ break
+ }
+ if child+1 < hi && (data[first+child] < data[first+child+1]) {
+ child++
+ }
+ if !(data[first+root] < data[first+child]) {
+ return
+ }
+ data[first+root], data[first+child] = data[first+child], data[first+root]
+ root = child
+ }
+}
+
+func heapSortOrdered[Elem constraints.Ordered](data []Elem, a, b int) {
+ first := a
+ lo := 0
+ hi := b - a
+
+ // Build heap with greatest element at top.
+ for i := (hi - 1) / 2; i >= 0; i-- {
+ siftDownOrdered(data, i, hi, first)
+ }
+
+ // Pop elements, largest first, into end of data.
+ for i := hi - 1; i >= 0; i-- {
+ data[first], data[first+i] = data[first+i], data[first]
+ siftDownOrdered(data, lo, i, first)
+ }
+}
+
+// Quicksort, loosely following Bentley and McIlroy,
+// "Engineering a Sort Function" SP&E November 1993.
+
+// medianOfThreeOrdered moves the median of the three values data[m0], data[m1], data[m2] into data[m1].
+func medianOfThreeOrdered[Elem constraints.Ordered](data []Elem, m1, m0, m2 int) {
+ // sort 3 elements
+ if data[m1] < data[m0] {
+ data[m1], data[m0] = data[m0], data[m1]
+ }
+ // data[m0] <= data[m1]
+ if data[m2] < data[m1] {
+ data[m2], data[m1] = data[m1], data[m2]
+ // data[m0] <= data[m2] && data[m1] < data[m2]
+ if data[m1] < data[m0] {
+ data[m1], data[m0] = data[m0], data[m1]
+ }
+ }
+ // now data[m0] <= data[m1] <= data[m2]
+}
+
+func swapRangeOrdered[Elem constraints.Ordered](data []Elem, a, b, n int) {
+ for i := 0; i < n; i++ {
+ data[a+i], data[b+i] = data[b+i], data[a+i]
+ }
+}
+
+func doPivotOrdered[Elem constraints.Ordered](data []Elem, lo, hi int) (midlo, midhi int) {
+ m := int(uint(lo+hi) >> 1) // Written like this to avoid integer overflow.
+ if hi-lo > 40 {
+ // Tukey's "Ninther" median of three medians of three.
+ s := (hi - lo) / 8
+ medianOfThreeOrdered(data, lo, lo+s, lo+2*s)
+ medianOfThreeOrdered(data, m, m-s, m+s)
+ medianOfThreeOrdered(data, hi-1, hi-1-s, hi-1-2*s)
+ }
+ medianOfThreeOrdered(data, lo, m, hi-1)
+
+ // Invariants are:
+ // data[lo] = pivot (set up by ChoosePivot)
+ // data[lo < i < a] < pivot
+ // data[a <= i < b] <= pivot
+ // data[b <= i < c] unexamined
+ // data[c <= i < hi-1] > pivot
+ // data[hi-1] >= pivot
+ pivot := lo
+ a, c := lo+1, hi-1
+
+ for ; a < c && (data[a] < data[pivot]); a++ {
+ }
+ b := a
+ for {
+ for ; b < c && !(data[pivot] < data[b]); b++ { // data[b] <= pivot
+ }
+ for ; b < c && (data[pivot] < data[c-1]); c-- { // data[c-1] > pivot
+ }
+ if b >= c {
+ break
+ }
+ // data[b] > pivot; data[c-1] <= pivot
+ data[b], data[c-1] = data[c-1], data[b]
+ b++
+ c--
+ }
+ // If hi-c<3 then there are duplicates (by property of median of nine).
+ // Let's be a bit more conservative, and set border to 5.
+ protect := hi-c < 5
+ if !protect && hi-c < (hi-lo)/4 {
+ // Lets test some points for equality to pivot
+ dups := 0
+ if !(data[pivot] < data[hi-1]) { // data[hi-1] = pivot
+ data[c], data[hi-1] = data[hi-1], data[c]
+ c++
+ dups++
+ }
+ if !(data[b-1] < data[pivot]) { // data[b-1] = pivot
+ b--
+ dups++
+ }
+ // m-lo = (hi-lo)/2 > 6
+ // b-lo > (hi-lo)*3/4-1 > 8
+ // ==> m < b ==> data[m] <= pivot
+ if !(data[m] < data[pivot]) { // data[m] = pivot
+ data[m], data[b-1] = data[b-1], data[m]
+ b--
+ dups++
+ }
+ // if at least 2 points are equal to pivot, assume skewed distribution
+ protect = dups > 1
+ }
+ if protect {
+ // Protect against a lot of duplicates
+ // Add invariant:
+ // data[a <= i < b] unexamined
+ // data[b <= i < c] = pivot
+ for {
+ for ; a < b && !(data[b-1] < data[pivot]); b-- { // data[b] == pivot
+ }
+ for ; a < b && (data[a] < data[pivot]); a++ { // data[a] < pivot
+ }
+ if a >= b {
+ break
+ }
+ // data[a] == pivot; data[b-1] < pivot
+ data[a], data[b-1] = data[b-1], data[a]
+ a++
+ b--
+ }
+ }
+ // Swap pivot into middle
+ data[pivot], data[b-1] = data[b-1], data[pivot]
+ return b - 1, c
+}
+
+func quickSortOrdered[Elem constraints.Ordered](data []Elem, a, b, maxDepth int) {
+ for b-a > 12 { // Use ShellSort for slices <= 12 elements
+ if maxDepth == 0 {
+ heapSortOrdered(data, a, b)
+ return
+ }
+ maxDepth--
+ mlo, mhi := doPivotOrdered(data, a, b)
+ // Avoiding recursion on the larger subproblem guarantees
+ // a stack depth of at most lg(b-a).
+ if mlo-a < b-mhi {
+ quickSortOrdered(data, a, mlo, maxDepth)
+ a = mhi // i.e., quickSortOrdered(data, mhi, b)
+ } else {
+ quickSortOrdered(data, mhi, b, maxDepth)
+ b = mlo // i.e., quickSortOrdered(data, a, mlo)
+ }
+ }
+ if b-a > 1 {
+ // Do ShellSort pass with gap 6
+ // It could be written in this simplified form cause b-a <= 12
+ for i := a + 6; i < b; i++ {
+ if data[i] < data[i-6] {
+ data[i], data[i-6] = data[i-6], data[i]
+ }
+ }
+ insertionSortOrdered(data, a, b)
+ }
+}
+
+func stableOrdered[Elem constraints.Ordered](data []Elem, n int) {
+ blockSize := 20 // must be > 0
+ a, b := 0, blockSize
+ for b <= n {
+ insertionSortOrdered(data, a, b)
+ a = b
+ b += blockSize
+ }
+ insertionSortOrdered(data, a, n)
+
+ for blockSize < n {
+ a, b = 0, 2*blockSize
+ for b <= n {
+ symMergeOrdered(data, a, a+blockSize, b)
+ a = b
+ b += 2 * blockSize
+ }
+ if m := a + blockSize; m < n {
+ symMergeOrdered(data, a, m, n)
+ }
+ blockSize *= 2
+ }
+}
+
+// symMergeOrdered merges the two sorted subsequences data[a:m] and data[m:b] using
+// the SymMerge algorithm from Pok-Son Kim and Arne Kutzner, "Stable Minimum
+// Storage Merging by Symmetric Comparisons", in Susanne Albers and Tomasz
+// Radzik, editors, Algorithms - ESA 2004, volume 3221 of Lecture Notes in
+// Computer Science, pages 714-723. Springer, 2004.
+//
+// Let M = m-a and N = b-n. Wolog M < N.
+// The recursion depth is bound by ceil(log(N+M)).
+// The algorithm needs O(M*log(N/M + 1)) calls to data.Less.
+// The algorithm needs O((M+N)*log(M)) calls to data.Swap.
+//
+// The paper gives O((M+N)*log(M)) as the number of assignments assuming a
+// rotation algorithm which uses O(M+N+gcd(M+N)) assignments. The argumentation
+// in the paper carries through for Swap operations, especially as the block
+// swapping rotate uses only O(M+N) Swaps.
+//
+// symMerge assumes non-degenerate arguments: a < m && m < b.
+// Having the caller check this condition eliminates many leaf recursion calls,
+// which improves performance.
+func symMergeOrdered[Elem constraints.Ordered](data []Elem, a, m, b int) {
+ // Avoid unnecessary recursions of symMerge
+ // by direct insertion of data[a] into data[m:b]
+ // if data[a:m] only contains one element.
+ if m-a == 1 {
+ // Use binary search to find the lowest index i
+ // such that data[i] >= data[a] for m <= i < b.
+ // Exit the search loop with i == b in case no such index exists.
+ i := m
+ j := b
+ for i < j {
+ h := int(uint(i+j) >> 1)
+ if data[h] < data[a] {
+ i = h + 1
+ } else {
+ j = h
+ }
+ }
+ // Swap values until data[a] reaches the position before i.
+ for k := a; k < i-1; k++ {
+ data[k], data[k+1] = data[k+1], data[k]
+ }
+ return
+ }
+
+ // Avoid unnecessary recursions of symMerge
+ // by direct insertion of data[m] into data[a:m]
+ // if data[m:b] only contains one element.
+ if b-m == 1 {
+ // Use binary search to find the lowest index i
+ // such that data[i] > data[m] for a <= i < m.
+ // Exit the search loop with i == m in case no such index exists.
+ i := a
+ j := m
+ for i < j {
+ h := int(uint(i+j) >> 1)
+ if !(data[m] < data[h]) {
+ i = h + 1
+ } else {
+ j = h
+ }
+ }
+ // Swap values until data[m] reaches the position i.
+ for k := m; k > i; k-- {
+ data[k], data[k-1] = data[k-1], data[k]
+ }
+ return
+ }
+
+ mid := int(uint(a+b) >> 1)
+ n := mid + m
+ var start, r int
+ if m > mid {
+ start = n - b
+ r = mid
+ } else {
+ start = a
+ r = m
+ }
+ p := n - 1
+
+ for start < r {
+ c := int(uint(start+r) >> 1)
+ if !(data[p-c] < data[c]) {
+ start = c + 1
+ } else {
+ r = c
+ }
+ }
+
+ end := n - start
+ if start < m && m < end {
+ rotateOrdered(data, start, m, end)
+ }
+ if a < start && start < mid {
+ symMergeOrdered(data, a, start, mid)
+ }
+ if mid < end && end < b {
+ symMergeOrdered(data, mid, end, b)
+ }
+}
+
+// rotateOrdered rotates two consecutive blocks u = data[a:m] and v = data[m:b] in data:
+// Data of the form 'x u v y' is changed to 'x v u y'.
+// rotate performs at most b-a many calls to data.Swap,
+// and it assumes non-degenerate arguments: a < m && m < b.
+func rotateOrdered[Elem constraints.Ordered](data []Elem, a, m, b int) {
+ i := m - a
+ j := b - m
+
+ for i != j {
+ if i > j {
+ swapRangeOrdered(data, m-i, m, j)
+ i -= j
+ } else {
+ swapRangeOrdered(data, m-i, m+j-i, i)
+ j -= i
+ }
+ }
+ // i == j
+ swapRangeOrdered(data, m-i, m, i)
+}
diff --git a/vendor/modules.txt b/vendor/modules.txt
index d322cfd..c79f186 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -1,6 +1,11 @@
# github.com/go-logr/logr v1.2.2
## explicit; go 1.16
github.com/go-logr/logr
+# golang.org/x/exp v0.0.0-20220314205449-43aec2f8a4e7
+## explicit; go 1.18
+golang.org/x/exp/constraints
+golang.org/x/exp/maps
+golang.org/x/exp/slices
# golang.org/x/net v0.0.0-20220225172249-27dd8689420f
## explicit; go 1.17
golang.org/x/net/webdav