diff --git a/go.mod b/go.mod index ff6d57697b..e70ee8186a 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/opencloud-eu/opencloud -go 1.24.0 +go 1.24.1 require ( dario.cat/mergo v1.0.1 @@ -63,7 +63,7 @@ require ( github.com/onsi/ginkgo/v2 v2.23.0 github.com/onsi/gomega v1.36.2 github.com/open-policy-agent/opa v1.1.0 - github.com/opencloud-eu/reva/v2 v2.27.3-0.20250304172421-22b1ead80cdd + github.com/opencloud-eu/reva/v2 v2.27.3-0.20250312134906-766c69c5d1be github.com/orcaman/concurrent-map v1.0.0 github.com/owncloud/libre-graph-api-go v1.0.5-0.20240829135935-80dc00d6f5ea github.com/pkg/errors v0.9.1 @@ -82,7 +82,7 @@ require ( github.com/test-go/testify v1.1.4 github.com/thejerf/suture/v4 v4.0.6 github.com/tidwall/gjson v1.18.0 - github.com/tus/tusd/v2 v2.6.0 + github.com/tus/tusd/v2 v2.7.1 github.com/unrolled/secure v1.16.0 github.com/urfave/cli/v2 v2.27.5 github.com/xhit/go-simple-mail/v2 v2.16.0 @@ -96,14 +96,14 @@ require ( go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 go.opentelemetry.io/otel/sdk v1.35.0 go.opentelemetry.io/otel/trace v1.35.0 - golang.org/x/crypto v0.34.0 - golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c + golang.org/x/crypto v0.36.0 + golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac golang.org/x/image v0.24.0 - golang.org/x/net v0.35.0 + golang.org/x/net v0.37.0 golang.org/x/oauth2 v0.28.0 - golang.org/x/sync v0.11.0 - golang.org/x/term v0.29.0 - golang.org/x/text v0.22.0 + golang.org/x/sync v0.12.0 + golang.org/x/term v0.30.0 + golang.org/x/text v0.23.0 google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a google.golang.org/grpc v1.71.0 google.golang.org/protobuf v1.36.5 @@ -308,24 +308,24 @@ require ( github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect github.com/yashtewari/glob-intersection v0.2.0 // indirect - go.etcd.io/etcd/api/v3 v3.5.18 // indirect - go.etcd.io/etcd/client/pkg/v3 v3.5.18 // indirect - go.etcd.io/etcd/client/v3 v3.5.18 // indirect + go.etcd.io/etcd/api/v3 v3.5.19 // indirect + go.etcd.io/etcd/client/pkg/v3 v3.5.19 // indirect + go.etcd.io/etcd/client/v3 v3.5.19 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 // indirect go.opentelemetry.io/otel/metric v1.35.0 // indirect go.opentelemetry.io/proto/otlp v1.5.0 // indirect go.uber.org/atomic v1.11.0 // indirect - go.uber.org/multierr v1.9.0 // indirect + go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.23.0 // indirect - golang.org/x/mod v0.23.0 // indirect - golang.org/x/sys v0.30.0 // indirect + golang.org/x/mod v0.24.0 // indirect + golang.org/x/sys v0.31.0 // indirect golang.org/x/time v0.10.0 // indirect - golang.org/x/tools v0.30.0 // indirect + golang.org/x/tools v0.31.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250219182151-9fdb1cabc7b2 // indirect gopkg.in/cenkalti/backoff.v1 v1.1.0 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect diff --git a/go.sum b/go.sum index e5cd21b0da..f1b4dc435a 100644 --- a/go.sum +++ b/go.sum @@ -865,8 +865,8 @@ github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8= github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY= github.com/open-policy-agent/opa v1.1.0 h1:HMz2evdEMTyNqtdLjmu3Vyx06BmhNYAx67Yz3Ll9q2s= github.com/open-policy-agent/opa v1.1.0/go.mod h1:T1pASQ1/vwfTa+e2fYcfpLCvWgYtqtiUv+IuA/dLPQs= -github.com/opencloud-eu/reva/v2 v2.27.3-0.20250304172421-22b1ead80cdd h1:n4w8SHHuk74HOHy7ZEsL6zw2N/SEarqr482jAh5C/p4= -github.com/opencloud-eu/reva/v2 v2.27.3-0.20250304172421-22b1ead80cdd/go.mod h1:Zi6h/WupAKzY/umvteGJCY3q4GOvTyrlm4JZfiuHeds= +github.com/opencloud-eu/reva/v2 v2.27.3-0.20250312134906-766c69c5d1be h1:dxKsVUzdKIGf1hfGdr1GH6NFfo0xuIcPma/qjHNG8mU= +github.com/opencloud-eu/reva/v2 v2.27.3-0.20250312134906-766c69c5d1be/go.mod h1:sqlExPoEnEd0KdfoSKogV8PrwEBY3l06icoa4gJnGnU= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= @@ -1097,8 +1097,8 @@ github.com/toorop/go-dkim v0.0.0-20201103131630-e1cd1a0a5208/go.mod h1:BzWtXXrXz github.com/transip/gotransip/v6 v6.2.0/go.mod h1:pQZ36hWWRahCUXkFWlx9Hs711gLd8J4qdgLdRzmtY+g= github.com/trustelem/zxcvbn v1.0.1 h1:mp4JFtzdDYGj9WYSD3KQSkwwUumWNFzXaAjckaTYpsc= github.com/trustelem/zxcvbn v1.0.1/go.mod h1:zonUyKeh7sw6psPf/e3DtRqkRyZvAbOfjNz/aO7YQ5s= -github.com/tus/tusd/v2 v2.6.0 h1:Je243QDKnFTvm/WkLH2bd1oQ+7trolrflRWyuI0PdWI= -github.com/tus/tusd/v2 v2.6.0/go.mod h1:1Eb1lBoSRBfYJ/mQfFVjyw8ZdNMdBqW17vgQKl3Ah9g= +github.com/tus/tusd/v2 v2.7.1 h1:TGJjhv9RYXDmsTz8ug/qSd9vQpmD0Ik0G0IPo80Qmc0= +github.com/tus/tusd/v2 v2.7.1/go.mod h1:PLdIMQ/ge+5ADgGKcL3FgTaPs+7wB0JIiI5HQXAiJE8= github.com/uber-go/atomic v1.3.2/go.mod h1:/Ct5t2lcmbJ4OSe/waGBoaVvVqtO0bmtfVNex1PFV8g= github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= @@ -1144,12 +1144,12 @@ github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQ go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.4.0 h1:TU77id3TnN/zKr7CO/uk+fBCwF2jGcMuw2B/FMAzYIk= go.etcd.io/bbolt v1.4.0/go.mod h1:AsD+OCi/qPN1giOX1aiLAha3o1U8rAz65bvN4j0sRuk= -go.etcd.io/etcd/api/v3 v3.5.18 h1:Q4oDAKnmwqTo5lafvB+afbgCDF7E35E4EYV2g+FNGhs= -go.etcd.io/etcd/api/v3 v3.5.18/go.mod h1:uY03Ob2H50077J7Qq0DeehjM/A9S8PhVfbQ1mSaMopU= -go.etcd.io/etcd/client/pkg/v3 v3.5.18 h1:mZPOYw4h8rTk7TeJ5+3udUkfVGBqc+GCjOJYd68QgNM= -go.etcd.io/etcd/client/pkg/v3 v3.5.18/go.mod h1:BxVf2o5wXG9ZJV+/Cu7QNUiJYk4A29sAhoI5tIRsCu4= -go.etcd.io/etcd/client/v3 v3.5.18 h1:nvvYmNHGumkDjZhTHgVU36A9pykGa2K4lAJ0yY7hcXA= -go.etcd.io/etcd/client/v3 v3.5.18/go.mod h1:kmemwOsPU9broExyhYsBxX4spCTDX3yLgPMWtpBXG6E= +go.etcd.io/etcd/api/v3 v3.5.19 h1:w3L6sQZGsWPuBxRQ4m6pPP3bVUtV8rjW033EGwlr0jw= +go.etcd.io/etcd/api/v3 v3.5.19/go.mod h1:QqKGViq4KTgOG43dr/uH0vmGWIaoJY3ggFi6ZH0TH/U= +go.etcd.io/etcd/client/pkg/v3 v3.5.19 h1:9VsyGhg0WQGjDWWlDI4VuaS9PZJGNbPkaHEIuLwtixk= +go.etcd.io/etcd/client/pkg/v3 v3.5.19/go.mod h1:qaOi1k4ZA9lVLejXNvyPABrVEe7VymMF2433yyRQ7O0= +go.etcd.io/etcd/client/v3 v3.5.19 h1:+4byIz6ti3QC28W0zB0cEZWwhpVHXdrKovyycJh1KNo= +go.etcd.io/etcd/client/v3 v3.5.19/go.mod h1:FNzyinmMIl0oVsty1zA3hFeUrxXI/JpEnz4sG+POzjU= go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= @@ -1196,8 +1196,8 @@ go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= -go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= -go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/ratelimit v0.0.0-20180316092928-c15da0234277/go.mod h1:2X8KaoNd1J0lZV+PxJk/5+DGbO/tpwLR1m++a7FnB/Y= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= @@ -1226,8 +1226,8 @@ golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDf golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= -golang.org/x/crypto v0.34.0 h1:+/C6tk6rf/+t5DhUketUbD1aNGqiSX3j15Z6xuIDlBA= -golang.org/x/crypto v0.34.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= +golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= +golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -1238,8 +1238,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY= -golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8= +golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac h1:l5+whBCLH3iH2ZNHYLbAe58bo7yrN4mVcnkHDYz5vvs= +golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac/go.mod h1:hH+7mtFmImwwcMvScyxUhjuVHR3HGaDPMn9rMSUUbxo= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E= @@ -1269,8 +1269,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM= -golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= +golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1327,8 +1327,8 @@ golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= -golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= -golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= +golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= +golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1356,8 +1356,8 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= -golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= +golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180622082034-63fc586f45fe/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1439,8 +1439,8 @@ golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= -golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -1453,8 +1453,8 @@ golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= -golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= -golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= +golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= +golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1471,8 +1471,8 @@ golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= -golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1535,8 +1535,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= -golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY= -golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY= +golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU= +golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1600,8 +1600,8 @@ google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 h1:ToEetK57OidYuqD google.golang.org/genproto v0.0.0-20241118233622-e639e219e697/go.mod h1:JJrvXBWRZaFMxBufik1a4RpFw4HhgVtBBWQeQgUj2cc= google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a h1:nwKuGPlUAt+aR+pcrkfFRrTU1BVrSmYyYMxYbUIVHr0= google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a/go.mod h1:3kWAYMk1I75K4vykHtKt2ycnOgpA6974V7bREqbsenU= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a h1:51aaUVRocpvUOSQKM6Q7VuoaktNIaMCLuhZB6DKksq4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a/go.mod h1:uRxBH1mhmO8PGhU89cMcHaXKZqO+OfakD8QQO0oYwlQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250219182151-9fdb1cabc7b2 h1:DMTIbak9GhdaSxEjvVzAeNZvyc03I61duqNbnm3SU0M= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250219182151-9fdb1cabc7b2/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.19.1/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= diff --git a/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/fs/decomposed/blobstore/blobstore.go b/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/fs/decomposed/blobstore/blobstore.go index 82edfe2159..7c6edc0a8e 100644 --- a/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/fs/decomposed/blobstore/blobstore.go +++ b/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/fs/decomposed/blobstore/blobstore.go @@ -53,7 +53,7 @@ func New(root string) (*Blobstore, error) { } // Upload stores some data in the blobstore under the given key -func (bs *Blobstore) Upload(node *node.Node, source string) error { +func (bs *Blobstore) Upload(node *node.Node, source, _copyTarget string) error { if node.BlobID == "" { return ErrBlobIDEmpty } diff --git a/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/fs/decomposeds3/blobstore/blobstore.go b/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/fs/decomposeds3/blobstore/blobstore.go index d5ca5273fc..ceea7be48b 100644 --- a/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/fs/decomposeds3/blobstore/blobstore.go +++ b/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/fs/decomposeds3/blobstore/blobstore.go @@ -77,7 +77,7 @@ func New(endpoint, region, bucket, accessKey, secretKey string, defaultPutOption } // Upload stores some data in the blobstore under the given key -func (bs *Blobstore) Upload(node *node.Node, source string) error { +func (bs *Blobstore) Upload(node *node.Node, source, _copyTarget string) error { reader, err := os.Open(source) if err != nil { return errors.Wrap(err, "can not open source file to upload") diff --git a/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/blobstore/blobstore.go b/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/blobstore/blobstore.go index 1268385e4f..8501196b5c 100644 --- a/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/blobstore/blobstore.go +++ b/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/blobstore/blobstore.go @@ -81,7 +81,11 @@ func (bs *Blobstore) Upload(node *node.Node, source, copyTarget string) error { return err } - file.Seek(0, 0) + _, err = file.Seek(0, 0) + if err != nil { + return err + } + copyFile, err := os.OpenFile(copyTarget, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600) if err != nil { return errors.Wrapf(err, "could not open copy target '%s' for writing", copyTarget) diff --git a/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/options/options.go b/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/options/options.go index 1e653ec384..60341a4b44 100644 --- a/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/options/options.go +++ b/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/options/options.go @@ -59,6 +59,11 @@ func New(m map[string]interface{}) (*Options, error) { m["metadata_backend"] = "hybrid" } + // debounced scan delay + if o.ScanDebounceDelay == 0 { + o.ScanDebounceDelay = 10 * time.Millisecond + } + do, err := decomposedoptions.New(m) if err != nil { return nil, err diff --git a/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/posix.go b/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/posix.go index 752156b902..9b9a0df5f1 100644 --- a/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/posix.go +++ b/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/posix.go @@ -78,7 +78,7 @@ func New(m map[string]interface{}, stream events.Stream, log *zerolog.Logger) (s var lu *lookup.Lookup switch o.MetadataBackend { case "xattrs": - lu = lookup.New(metadata.NewXattrsBackend(o.Root, o.FileMetadataCache), um, o, &timemanager.Manager{}) + lu = lookup.New(metadata.NewXattrsBackend(o.FileMetadataCache), um, o, &timemanager.Manager{}) case "hybrid": lu = lookup.New(metadata.NewHybridBackend(1024, // start offloading grants after 1KB func(n metadata.MetadataNode) string { diff --git a/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/trashbin/trashbin.go b/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/trashbin/trashbin.go index 55d2486128..a8793645b5 100644 --- a/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/trashbin/trashbin.go +++ b/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/trashbin/trashbin.go @@ -259,56 +259,71 @@ func (tb *Trashbin) ListRecycle(ctx context.Context, spaceID string, key, relati } // RestoreRecycleItem restores the specified item -func (tb *Trashbin) RestoreRecycleItem(ctx context.Context, spaceID string, key, relativePath string, restoreRef *provider.Reference) error { +func (tb *Trashbin) RestoreRecycleItem(ctx context.Context, spaceID string, key, relativePath string, restoreRef *provider.Reference) (*node.Node, error) { _, span := tracer.Start(ctx, "RestoreRecycleItem") defer span.End() trashRoot := filepath.Join(tb.lu.InternalPath(spaceID, spaceID), ".Trash") trashPath := filepath.Clean(filepath.Join(trashRoot, "files", key+".trashitem", relativePath)) + restorePath := "" // TODO why can we not use NodeFromResource here? It will use walk path. Do trashed items have a problem with that? - restoreBaseNode, err := tb.lu.NodeFromID(ctx, restoreRef.GetResourceId()) - if err != nil { - return err + if restoreRef != nil { + restoreBaseNode, err := tb.lu.NodeFromID(ctx, restoreRef.GetResourceId()) + if err != nil { + return nil, err + } + restorePath = filepath.Join(restoreBaseNode.InternalPath(), restoreRef.GetPath()) + } else { + originalPath, _, err := tb.readInfoFile(trashRoot, key) + if err != nil { + return nil, err + } + restorePath = filepath.Join(tb.lu.InternalPath(spaceID, spaceID), originalPath, relativePath) } - restorePath := filepath.Join(restoreBaseNode.InternalPath(), restoreRef.GetPath()) // TODO the decomposed trash also checks the permissions on the restore node _, id, _, err := tb.lu.MetadataBackend().IdentifyPath(ctx, trashPath) if err != nil { - return err + return nil, err } // update parent id in case it was restored to a different location _, parentID, _, err := tb.lu.MetadataBackend().IdentifyPath(ctx, filepath.Dir(restorePath)) if err != nil { - return err + return nil, err } if len(parentID) == 0 { - return fmt.Errorf("trashbin: parent id not found for %s", restorePath) + return nil, fmt.Errorf("trashbin: parent id not found for %s", restorePath) } trashNode := &trashNode{spaceID: spaceID, id: id, path: trashPath} err = tb.lu.MetadataBackend().Set(ctx, trashNode, prefixes.ParentidAttr, []byte(parentID)) if err != nil { - return err + return nil, err } // restore the item err = os.Rename(trashPath, restorePath) if err != nil { - return err + return nil, err } - if err := tb.lu.CacheID(ctx, spaceID, string(id), restorePath); err != nil { - tb.log.Error().Err(err).Str("spaceID", spaceID).Str("id", string(id)).Str("path", restorePath).Msg("trashbin: error caching id") + if err := tb.lu.CacheID(ctx, spaceID, id, restorePath); err != nil { + tb.log.Error().Err(err).Str("spaceID", spaceID).Str("id", id).Str("path", restorePath).Msg("trashbin: error caching id") + } + + restoredNode, err := tb.lu.NodeFromID(ctx, &provider.ResourceId{SpaceId: spaceID, OpaqueId: id}) + if err != nil { + return nil, err } // cleanup trash info if relativePath == "." || relativePath == "/" { - return os.Remove(filepath.Join(trashRoot, "info", key+".trashinfo")) + return restoredNode, os.Remove(filepath.Join(trashRoot, "info", key+".trashinfo")) } else { - return nil + return restoredNode, nil } + } // PurgeRecycleItem purges the specified item, all its children and all their revisions diff --git a/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/tree/assimilation.go b/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/tree/assimilation.go index f1f5b1c9d4..73f683fff1 100644 --- a/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/tree/assimilation.go +++ b/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/tree/assimilation.go @@ -197,7 +197,7 @@ func (t *Tree) Scan(path string, action EventAction, isDir bool) error { }) } if err := t.setDirty(filepath.Dir(path), true); err != nil { - return err + t.log.Error().Err(err).Str("path", path).Bool("isDir", isDir).Msg("failed to mark directory as dirty") } t.scanDebouncer.Debounce(scanItem{ Path: filepath.Dir(path), @@ -208,7 +208,7 @@ func (t *Tree) Scan(path string, action EventAction, isDir bool) error { // 2. New directory // -> scan directory if err := t.setDirty(path, true); err != nil { - return err + t.log.Error().Err(err).Str("path", path).Bool("isDir", isDir).Msg("failed to mark directory as dirty") } t.scanDebouncer.Debounce(scanItem{ Path: path, @@ -244,8 +244,13 @@ func (t *Tree) Scan(path string, action EventAction, isDir bool) error { t.log.Debug().Str("path", path).Bool("isDir", isDir).Msg("scanning path (ActionMoveFrom)") // 6. file/directory moved out of the watched directory // -> update directory - if err := t.setDirty(filepath.Dir(path), true); err != nil { - return err + err := t.HandleFileDelete(path) + if err != nil { + t.log.Error().Err(err).Str("path", path).Bool("isDir", isDir).Msg("failed to handle deleted item") + } + err = t.setDirty(filepath.Dir(path), true) + if err != nil { + t.log.Error().Err(err).Str("path", path).Bool("isDir", isDir).Msg("failed to mark directory as dirty") } go func() { _ = t.WarmupIDCache(filepath.Dir(path), false, true) }() @@ -258,7 +263,7 @@ func (t *Tree) Scan(path string, action EventAction, isDir bool) error { err := t.HandleFileDelete(path) if err != nil { - return err + t.log.Error().Err(err).Str("path", path).Bool("isDir", isDir).Msg("failed to handle deleted item") } t.scanDebouncer.Debounce(scanItem{ @@ -342,7 +347,7 @@ func (t *Tree) findSpaceId(path string) (string, node.Attributes, error) { } } - return string(spaceID), spaceAttrs, nil + return spaceID, spaceAttrs, nil } spaceCandidate = filepath.Dir(spaceCandidate) } @@ -387,7 +392,7 @@ func (t *Tree) assimilate(item scanItem) error { // the file has an id set, we already know it from the past n := node.NewBaseNode(spaceID, id, t.lookup) - previousPath, ok := t.lookup.GetCachedID(context.Background(), spaceID, string(id)) + previousPath, ok := t.lookup.GetCachedID(context.Background(), spaceID, id) previousParentID, _ := t.lookup.MetadataBackend().Get(context.Background(), n, prefixes.ParentidAttr) // compare metadata mtime with actual mtime. if it matches AND the path hasn't changed (move operation) @@ -418,10 +423,10 @@ func (t *Tree) assimilate(item scanItem) error { // this is a move t.log.Debug().Str("path", item.Path).Msg("move detected") - if err := t.lookup.CacheID(context.Background(), spaceID, string(id), item.Path); err != nil { - t.log.Error().Err(err).Str("spaceID", spaceID).Str("id", string(id)).Str("path", item.Path).Msg("could not cache id") + if err := t.lookup.CacheID(context.Background(), spaceID, id, item.Path); err != nil { + t.log.Error().Err(err).Str("spaceID", spaceID).Str("id", id).Str("path", item.Path).Msg("could not cache id") } - _, attrs, err := t.updateFile(item.Path, string(id), spaceID) + _, attrs, err := t.updateFile(item.Path, id, spaceID) if err != nil { return err } @@ -471,11 +476,11 @@ func (t *Tree) assimilate(item scanItem) error { } else { // This item had already been assimilated in the past. Update the path t.log.Debug().Str("path", item.Path).Msg("updating cached path") - if err := t.lookup.CacheID(context.Background(), spaceID, string(id), item.Path); err != nil { - t.log.Error().Err(err).Str("spaceID", spaceID).Str("id", string(id)).Str("path", item.Path).Msg("could not cache id") + if err := t.lookup.CacheID(context.Background(), spaceID, id, item.Path); err != nil { + t.log.Error().Err(err).Str("spaceID", spaceID).Str("id", id).Str("path", item.Path).Msg("could not cache id") } - _, _, err := t.updateFile(item.Path, string(id), spaceID) + _, _, err := t.updateFile(item.Path, id, spaceID) if err != nil { return err } @@ -753,7 +758,7 @@ func (t *Tree) WarmupIDCache(root string, assimilate, onlyDirty bool) error { } spaceID, _, _, err = t.lookup.MetadataBackend().IdentifyPath(context.Background(), spaceCandidate) - if err == nil { + if err == nil && len(spaceID) > 0 { err = scopeSpace(path) if err != nil { return err diff --git a/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/tree/revisions.go b/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/tree/revisions.go index c8017d950a..e98a3b81dd 100644 --- a/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/tree/revisions.go +++ b/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/tree/revisions.go @@ -223,44 +223,6 @@ func (tp *Tree) DownloadRevision(ctx context.Context, ref *provider.Reference, r return ri, reader, nil } -func (tp *Tree) getRevisionNode(ctx context.Context, ref *provider.Reference, revisionKey string, hasPermission func(*provider.ResourcePermissions) bool) (*node.Node, error) { - _, span := tracer.Start(ctx, "getRevisionNode") - defer span.End() - log := appctx.GetLogger(ctx) - - // verify revision key format - kp := strings.SplitN(revisionKey, node.RevisionIDDelimiter, 2) - if len(kp) != 2 { - log.Error().Str("revisionKey", revisionKey).Msg("malformed revisionKey") - return nil, errtypes.NotFound(revisionKey) - } - log.Debug().Str("revisionKey", revisionKey).Msg("DownloadRevision") - - spaceID := ref.ResourceId.SpaceId - // check if the node is available and has not been deleted - n, err := node.ReadNode(ctx, tp.lookup, spaceID, kp[0], false, nil, false) - if err != nil { - return nil, err - } - if !n.Exists { - err = errtypes.NotFound(filepath.Join(n.ParentID, n.Name)) - return nil, err - } - - p, err := tp.permissions.AssemblePermissions(ctx, n) - switch { - case err != nil: - return nil, err - case !hasPermission(p): - return nil, errtypes.PermissionDenied(filepath.Join(n.ParentID, n.Name)) - } - - // Set space owner in context - storagespace.ContextSendSpaceOwnerID(ctx, n.SpaceOwnerOrManager(ctx)) - - return n, nil -} - func (tp *Tree) RestoreRevision(ctx context.Context, srcNode, targetNode metadata.MetadataNode) error { source := srcNode.InternalPath() target := targetNode.InternalPath() @@ -275,7 +237,10 @@ func (tp *Tree) RestoreRevision(ctx context.Context, srcNode, targetNode metadat return err } defer wf.Close() - wf.Truncate(0) + err = wf.Truncate(0) + if err != nil { + return err + } if _, err := io.Copy(wf, rf); err != nil { return err @@ -293,7 +258,11 @@ func (tp *Tree) RestoreRevision(ctx context.Context, srcNode, targetNode metadat // always set the node mtime to the current time mtime := time.Now() - os.Chtimes(target, mtime, mtime) + err = os.Chtimes(target, mtime, mtime) + if err != nil { + return errtypes.InternalError("failed to update times:" + err.Error()) + } + err = tp.lookup.MetadataBackend().SetMultiple(ctx, targetNode, map[string][]byte{ prefixes.MTimeAttr: []byte(mtime.UTC().Format(time.RFC3339Nano)), diff --git a/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/tree/tree.go b/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/tree/tree.go index 6308ff155d..91f3d2e507 100644 --- a/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/tree/tree.go +++ b/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/tree/tree.go @@ -26,7 +26,6 @@ import ( "io/fs" "os" "path/filepath" - "regexp" "strings" "time" @@ -62,13 +61,6 @@ func init() { tracer = otel.Tracer("github.com/cs3org/reva/pkg/storage/pkg/decomposedfs/tree") } -// Blobstore defines an interface for storing blobs in a blobstore -type Blobstore interface { - Upload(node *node.Node, source, copyTarget string) error - Download(node *node.Node) (io.ReadCloser, error) - Delete(node *node.Node) error -} - type Watcher interface { Watch(path string) } @@ -82,7 +74,7 @@ type scanItem struct { // Tree manages a hierarchical tree type Tree struct { lookup *lookup.Lookup - blobstore Blobstore + blobstore node.Blobstore trashbin *trashbin.Trashbin propagator propagator.Propagator permissions permissions.Permissions @@ -103,7 +95,7 @@ type Tree struct { type PermissionCheckFunc func(rp *provider.ResourcePermissions) bool // New returns a new instance of Tree -func New(lu node.PathLookup, bs Blobstore, um usermapper.Mapper, trashbin *trashbin.Trashbin, permissions permissions.Permissions, o *options.Options, es events.Stream, cache store.Store, log *zerolog.Logger) (*Tree, error) { +func New(lu node.PathLookup, bs node.Blobstore, um usermapper.Mapper, trashbin *trashbin.Trashbin, permissions permissions.Permissions, o *options.Options, es events.Stream, cache store.Store, log *zerolog.Logger) (*Tree, error) { scanQueue := make(chan scanItem) t := &Tree{ lookup: lu.(*lookup.Lookup), @@ -242,7 +234,10 @@ func (t *Tree) TouchFile(ctx context.Context, n *node.Node, markprocessing bool, if err != nil { return err } - t.lookup.TimeManager().OverrideMtime(ctx, n, &attributes, nodeMTime) + err = t.lookup.TimeManager().OverrideMtime(ctx, n, &attributes, nodeMTime) + if err != nil { + return err + } } else { fi, err := f.Stat() if err != nil { @@ -572,13 +567,13 @@ func (t *Tree) DeleteBlob(node *node.Node) error { } // BuildSpaceIDIndexEntry returns the entry for the space id index -func (t *Tree) BuildSpaceIDIndexEntry(spaceID, nodeID string) string { - return nodeID +func (t *Tree) BuildSpaceIDIndexEntry(spaceID string) string { + return spaceID } // ResolveSpaceIDIndexEntry returns the node id for the space id index entry -func (t *Tree) ResolveSpaceIDIndexEntry(spaceid, entry string) (string, string, error) { - return spaceid, entry, nil +func (t *Tree) ResolveSpaceIDIndexEntry(spaceID string) (string, error) { + return spaceID, nil } // InitNewNode initializes a new node @@ -652,8 +647,6 @@ func (t *Tree) createDirNode(ctx context.Context, n *node.Node) (err error) { return n.SetXattrsWithContext(ctx, attributes, false) } -var nodeIDRegep = regexp.MustCompile(`.*/nodes/([^.]*).*`) - func (t *Tree) isIgnored(path string) bool { return isLockFile(path) || isTrash(path) || t.isUpload(path) || t.isInternal(path) } diff --git a/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/decomposedfs.go b/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/decomposedfs.go index 45b2b452dc..11a5960555 100644 --- a/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/decomposedfs.go +++ b/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/decomposedfs.go @@ -130,7 +130,7 @@ type Decomposedfs struct { } // NewDefault returns an instance with default components -func NewDefault(m map[string]interface{}, bs tree.Blobstore, es events.Stream, log *zerolog.Logger) (storage.FS, error) { +func NewDefault(m map[string]interface{}, bs node.Blobstore, es events.Stream, log *zerolog.Logger) (storage.FS, error) { if log == nil { log = &zerolog.Logger{} } @@ -143,9 +143,9 @@ func NewDefault(m map[string]interface{}, bs tree.Blobstore, es events.Stream, l var lu *lookup.Lookup switch o.MetadataBackend { case "xattrs": - lu = lookup.New(metadata.NewXattrsBackend(o.Root, o.FileMetadataCache), o, &timemanager.Manager{}) + lu = lookup.New(metadata.NewXattrsBackend(o.FileMetadataCache), o, &timemanager.Manager{}) case "messagepack": - lu = lookup.New(metadata.NewMessagePackBackend(o.Root, o.FileMetadataCache), o, &timemanager.Manager{}) + lu = lookup.New(metadata.NewMessagePackBackend(o.FileMetadataCache), o, &timemanager.Manager{}) default: return nil, fmt.Errorf("unknown metadata backend %s, only 'messagepack' or 'xattrs' (default) supported", o.MetadataBackend) } @@ -1292,7 +1292,23 @@ func (fs *Decomposedfs) RestoreRecycleItem(ctx context.Context, space *provider. return errtypes.NotFound(key) } - return fs.trashbin.RestoreRecycleItem(ctx, spaceID, key, relativePath, restoreRef) + restoredNode, err := fs.trashbin.RestoreRecycleItem(ctx, spaceID, key, relativePath, restoreRef) + if err != nil { + return err + } + + var sizeDiff int64 + if restoredNode.IsDir(ctx) { + treeSize, err := restoredNode.GetTreeSize(ctx) + if err != nil { + return err + } + sizeDiff = int64(treeSize) + } else { + sizeDiff = restoredNode.Blobsize + } + + return fs.tp.Propagate(ctx, restoredNode, sizeDiff) } func (fs *Decomposedfs) PurgeRecycleItem(ctx context.Context, space *provider.Reference, key, relativePath string) error { diff --git a/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/metadata/messagepack_backend.go b/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/metadata/messagepack_backend.go index 177a262134..fc1c873bd9 100644 --- a/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/metadata/messagepack_backend.go +++ b/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/metadata/messagepack_backend.go @@ -40,7 +40,6 @@ import ( // MessagePackBackend persists the attributes in messagepack format inside the file type MessagePackBackend struct { - rootPath string metaCache cache.FileMetadataCache } @@ -51,9 +50,8 @@ type readWriteCloseSeekTruncater interface { } // NewMessagePackBackend returns a new MessagePackBackend instance -func NewMessagePackBackend(rootPath string, o cache.Config) MessagePackBackend { +func NewMessagePackBackend(o cache.Config) MessagePackBackend { return MessagePackBackend{ - rootPath: filepath.Clean(rootPath), metaCache: cache.GetFileMetadataCache(o), } } @@ -63,7 +61,7 @@ func (MessagePackBackend) Name() string { return "messagepack" } // IdentifyPath returns the id and mtime of a file func (b MessagePackBackend) IdentifyPath(_ context.Context, path string) (string, string, time.Time, error) { - metaPath := filepath.Join(path + ".mpk") + metaPath := filepath.Clean(path + ".mpk") source, err := os.Open(metaPath) // // No cached entry found. Read from storage and store in cache if err != nil { diff --git a/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/metadata/xattrs_backend.go b/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/metadata/xattrs_backend.go index 965027cc42..4460872098 100644 --- a/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/metadata/xattrs_backend.go +++ b/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/metadata/xattrs_backend.go @@ -37,12 +37,11 @@ import ( // XattrsBackend stores the file attributes in extended attributes type XattrsBackend struct { - rootPath string metaCache cache.FileMetadataCache } // NewMessageBackend returns a new XattrsBackend instance -func NewXattrsBackend(rootPath string, o cache.Config) XattrsBackend { +func NewXattrsBackend(o cache.Config) XattrsBackend { return XattrsBackend{ metaCache: cache.GetFileMetadataCache(o), } diff --git a/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/node/node.go b/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/node/node.go index fe567e5cb3..4854985b43 100644 --- a/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/node/node.go +++ b/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/node/node.go @@ -86,6 +86,13 @@ const ( ProcessingStatus = "processing:" ) +// Blobstore defines an interface for storing blobs in a blobstore +type Blobstore interface { + Upload(node *Node, source, copyTarget string) error + Download(node *Node) (io.ReadCloser, error) + Delete(node *Node) error +} + type TimeManager interface { // OverrideMTime overrides the mtime of the node, either on the node itself or in the given attributes, depending on the implementation OverrideMtime(ctx context.Context, n *Node, attrs *Attributes, mtime time.Time) error @@ -129,8 +136,8 @@ type Tree interface { ReadBlob(node *Node) (io.ReadCloser, error) DeleteBlob(node *Node) error - BuildSpaceIDIndexEntry(spaceID, nodeID string) string - ResolveSpaceIDIndexEntry(spaceID, entry string) (string, string, error) + BuildSpaceIDIndexEntry(spaceID string) string + ResolveSpaceIDIndexEntry(spaceID string) (string, error) CreateRevision(ctx context.Context, n *Node, version string, f *lockedfile.File) (string, error) ListRevisions(ctx context.Context, ref *provider.Reference) ([]*provider.FileVersion, error) diff --git a/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/recycle.go b/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/recycle.go index ad9a9c7744..dc7d888fb6 100644 --- a/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/recycle.go +++ b/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/recycle.go @@ -129,7 +129,7 @@ func (tb *DecomposedfsTrashbin) ListRecycle(ctx context.Context, spaceID string, } item := &provider.RecycleItem{ Type: provider.ResourceType(typeInt), - Size: uint64(size), + Size: size, Key: filepath.Join(key, relativePath), DeletionTime: deletionTime, Ref: &provider.Reference{ @@ -345,7 +345,7 @@ func (tb *DecomposedfsTrashbin) listTrashRoot(ctx context.Context, spaceID strin } // RestoreRecycleItem restores the specified item -func (tb *DecomposedfsTrashbin) RestoreRecycleItem(ctx context.Context, spaceID string, key, relativePath string, restoreRef *provider.Reference) error { +func (tb *DecomposedfsTrashbin) RestoreRecycleItem(ctx context.Context, spaceID string, key, relativePath string, restoreRef *provider.Reference) (*node.Node, error) { _, span := tracer.Start(ctx, "RestoreRecycleItem") defer span.End() @@ -353,7 +353,7 @@ func (tb *DecomposedfsTrashbin) RestoreRecycleItem(ctx context.Context, spaceID if restoreRef != nil { tn, err := tb.fs.lu.NodeFromResource(ctx, restoreRef) if err != nil { - return err + return nil, err } targetNode = tn @@ -361,19 +361,19 @@ func (tb *DecomposedfsTrashbin) RestoreRecycleItem(ctx context.Context, spaceID rn, parent, restoreFunc, err := tb.fs.tp.(*tree.Tree).RestoreRecycleItemFunc(ctx, spaceID, key, relativePath, targetNode) if err != nil { - return err + return nil, err } // check permissions of deleted node rp, err := tb.fs.p.AssembleTrashPermissions(ctx, rn) switch { case err != nil: - return err + return nil, err case !rp.RestoreRecycleItem: if rp.Stat { - return errtypes.PermissionDenied(key) + return nil, errtypes.PermissionDenied(key) } - return errtypes.NotFound(key) + return nil, errtypes.NotFound(key) } // Set space owner in context @@ -383,13 +383,13 @@ func (tb *DecomposedfsTrashbin) RestoreRecycleItem(ctx context.Context, spaceID pp, err := tb.fs.p.AssemblePermissions(ctx, parent) switch { case err != nil: - return err + return nil, err case !pp.InitiateFileUpload: // share receiver cannot restore to a shared resource to which she does not have write permissions. if rp.Stat { - return errtypes.PermissionDenied(key) + return nil, errtypes.PermissionDenied(key) } - return errtypes.NotFound(key) + return nil, errtypes.NotFound(key) } // Run the restore func diff --git a/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/spaces.go b/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/spaces.go index 95687c05f0..a156da5bbf 100644 --- a/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/spaces.go +++ b/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/spaces.go @@ -30,6 +30,8 @@ import ( "sync/atomic" "time" + "maps" + userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" v1beta11 "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" @@ -248,7 +250,7 @@ func (fs *Decomposedfs) ListStorageSpaces(ctx context.Context, filter []*provide var ( spaceID = spaceIDAny - nodeID = spaceIDAny + entry = spaceIDAny requestedUserID *userv1beta1.UserId ) @@ -266,8 +268,8 @@ func (fs *Decomposedfs) ListStorageSpaces(ctx context.Context, filter []*provide spaceTypes[filter[i].GetSpaceType()] = struct{}{} } case provider.ListStorageSpacesRequest_Filter_TYPE_ID: - _, spaceID, nodeID, _ = storagespace.SplitID(filter[i].GetId().OpaqueId) - if strings.Contains(nodeID, "/") { + _, spaceID, entry, _ = storagespace.SplitID(filter[i].GetId().OpaqueId) + if strings.Contains(entry, "/") { return []*provider.StorageSpace{}, nil } case provider.ListStorageSpacesRequest_Filter_TYPE_USER: @@ -296,11 +298,11 @@ func (fs *Decomposedfs) ListStorageSpaces(ctx context.Context, filter []*provide // /path/to/root/spaces/personal/nodeid // /path/to/root/spaces/shared/nodeid - if spaceID != spaceIDAny && nodeID != spaceIDAny { + if spaceID != spaceIDAny && entry != spaceIDAny { // try directly reading the node - n, err := node.ReadNode(ctx, fs.lu, spaceID, nodeID, true, nil, false) // permission to read disabled space is checked later + n, err := node.ReadNode(ctx, fs.lu, spaceID, entry, true, nil, false) // permission to read disabled space is checked later if err != nil { - appctx.GetLogger(ctx).Error().Err(err).Str("id", nodeID).Msg("could not read node") + appctx.GetLogger(ctx).Error().Err(err).Str("id", entry).Msg("could not read node") return nil, err } if !n.Exists { @@ -332,12 +334,10 @@ func (fs *Decomposedfs) ListStorageSpaces(ctx context.Context, filter []*provide return nil, errors.Wrap(err, "error reading user index") } - if nodeID == spaceIDAny { - for spaceID, nodeID := range allMatches { - matches[spaceID] = nodeID - } + if entry == spaceIDAny { + maps.Copy(matches, allMatches) } else { - matches[allMatches[nodeID]] = allMatches[nodeID] + matches[allMatches[entry]] = allMatches[entry] } // get Groups for userid @@ -359,12 +359,10 @@ func (fs *Decomposedfs) ListStorageSpaces(ctx context.Context, filter []*provide return nil, errors.Wrap(err, "error reading group index") } - if nodeID == spaceIDAny { - for spaceID, nodeID := range allMatches { - matches[spaceID] = nodeID - } + if entry == spaceIDAny { + maps.Copy(matches, allMatches) } else { - matches[allMatches[nodeID]] = allMatches[nodeID] + matches[allMatches[entry]] = allMatches[entry] } } @@ -389,12 +387,10 @@ func (fs *Decomposedfs) ListStorageSpaces(ctx context.Context, filter []*provide return nil, errors.Wrap(err, "error reading type index") } - if nodeID == spaceIDAny { - for spaceID, nodeID := range allMatches { - matches[spaceID] = nodeID - } + if entry == spaceIDAny { + maps.Copy(matches, allMatches) } else { - matches[allMatches[nodeID]] = allMatches[nodeID] + matches[allMatches[entry]] = allMatches[entry] } } } @@ -417,9 +413,9 @@ func (fs *Decomposedfs) ListStorageSpaces(ctx context.Context, filter []*provide // Distribute work errg.Go(func() error { defer close(work) - for spaceID, nodeID := range matches { + for spaceID, entry := range matches { select { - case work <- []string{spaceID, nodeID}: + case work <- []string{spaceID, entry}: case <-ctx.Done(): return ctx.Err() } @@ -435,15 +431,15 @@ func (fs *Decomposedfs) ListStorageSpaces(ctx context.Context, filter []*provide for i := 0; i < numWorkers; i++ { errg.Go(func() error { for match := range work { - spaceID, nodeID, err := fs.tp.ResolveSpaceIDIndexEntry(match[0], match[1]) + spaceID, err := fs.tp.ResolveSpaceIDIndexEntry(match[1]) if err != nil { - appctx.GetLogger(ctx).Error().Err(err).Str("id", nodeID).Msg("resolve space id index entry, skipping") + appctx.GetLogger(ctx).Error().Err(err).Str("id", spaceID).Msg("resolve space id index entry, skipping") continue } - n, err := node.ReadNode(ctx, fs.lu, spaceID, nodeID, true, nil, true) + n, err := node.ReadNode(ctx, fs.lu, spaceID, spaceID, true, nil, true) if err != nil { - appctx.GetLogger(ctx).Error().Err(err).Str("id", nodeID).Msg("could not read node, skipping") + appctx.GetLogger(ctx).Error().Err(err).Str("id", spaceID).Msg("could not read node, skipping") continue } @@ -459,7 +455,7 @@ func (fs *Decomposedfs) ListStorageSpaces(ctx context.Context, filter []*provide case errtypes.NotFound: // ok default: - appctx.GetLogger(ctx).Error().Err(err).Str("id", nodeID).Msg("could not convert to storage space") + appctx.GetLogger(ctx).Error().Err(err).Str("id", spaceID).Msg("could not convert to storage space") } continue } @@ -497,9 +493,9 @@ func (fs *Decomposedfs) ListStorageSpaces(ctx context.Context, filter []*provide } // if there are no matches (or they happened to be spaces for the owner) and the node is a child return a space - if int64(len(matches)) <= numShares.Load() && nodeID != spaceID { + if int64(len(matches)) <= numShares.Load() && entry != spaceID { // try node id - n, err := node.ReadNode(ctx, fs.lu, spaceID, nodeID, true, nil, false) // permission to read disabled space is checked in storageSpaceFromNode + n, err := node.ReadNode(ctx, fs.lu, spaceID, entry, true, nil, false) // permission to read disabled space is checked in storageSpaceFromNode if err != nil { return nil, err } @@ -817,7 +813,7 @@ func (fs *Decomposedfs) DeleteStorageSpace(ctx context.Context, req *provider.De // - for decomposedfs/decomposeds3 it is the relative link to the space root // - for the posixfs it is the node id func (fs *Decomposedfs) updateIndexes(ctx context.Context, grantee *provider.Grantee, spaceType, spaceID, nodeID string) error { - target := fs.tp.BuildSpaceIDIndexEntry(spaceID, nodeID) + target := fs.tp.BuildSpaceIDIndexEntry(spaceID) err := fs.linkStorageSpaceType(ctx, spaceType, spaceID, target) if err != nil { return err diff --git a/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/trashbin/trashbin.go b/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/trashbin/trashbin.go index 344dc07ebd..deb09d6d78 100644 --- a/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/trashbin/trashbin.go +++ b/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/trashbin/trashbin.go @@ -23,13 +23,14 @@ import ( provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" "github.com/opencloud-eu/reva/v2/pkg/storage" + "github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/node" ) type Trashbin interface { Setup(storage.FS) error ListRecycle(ctx context.Context, spaceID, key, relativePath string) ([]*provider.RecycleItem, error) - RestoreRecycleItem(ctx context.Context, spaceID, key, relativePath string, restoreRef *provider.Reference) error + RestoreRecycleItem(ctx context.Context, spaceID, key, relativePath string, restoreRef *provider.Reference) (*node.Node, error) PurgeRecycleItem(ctx context.Context, spaceID, key, relativePath string) error EmptyRecycle(ctx context.Context, spaceID string) error } diff --git a/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/tree/tree.go b/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/tree/tree.go index 47e408d944..23b3b994df 100644 --- a/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/tree/tree.go +++ b/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/tree/tree.go @@ -56,17 +56,10 @@ func init() { tracer = otel.Tracer("github.com/cs3org/reva/pkg/storage/utils/decomposedfs/tree") } -// Blobstore defines an interface for storing blobs in a blobstore -type Blobstore interface { - Upload(node *node.Node, source string) error - Download(node *node.Node) (io.ReadCloser, error) - Delete(node *node.Node) error -} - // Tree manages a hierarchical tree type Tree struct { lookup node.PathLookup - blobstore Blobstore + blobstore node.Blobstore propagator propagator.Propagator permissions permissions.Permissions @@ -79,7 +72,7 @@ type Tree struct { type PermissionCheckFunc func(rp *provider.ResourcePermissions) bool // New returns a new instance of Tree -func New(lu node.PathLookup, bs Blobstore, o *options.Options, p permissions.Permissions, cache store.Store, log *zerolog.Logger) *Tree { +func New(lu node.PathLookup, bs node.Blobstore, o *options.Options, p permissions.Permissions, cache store.Store, log *zerolog.Logger) *Tree { return &Tree{ lookup: lu, blobstore: bs, @@ -524,7 +517,7 @@ func (t *Tree) Delete(ctx context.Context, n *node.Node) (err error) { } // RestoreRecycleItemFunc returns a node and a function to restore it from the trash. -func (t *Tree) RestoreRecycleItemFunc(ctx context.Context, spaceid, key, trashPath string, targetNode *node.Node) (*node.Node, *node.Node, func() error, error) { +func (t *Tree) RestoreRecycleItemFunc(ctx context.Context, spaceid, key, trashPath string, targetNode *node.Node) (*node.Node, *node.Node, func() (*node.Node, error), error) { _, span := tracer.Start(ctx, "RestoreRecycleItemFunc") defer span.End() logger := appctx.GetLogger(ctx) @@ -555,9 +548,9 @@ func (t *Tree) RestoreRecycleItemFunc(ctx context.Context, spaceid, key, trashPa return nil, nil, nil, err } - fn := func() error { + fn := func() (*node.Node, error) { if targetNode.Exists { - return errtypes.AlreadyExists("origin already exists") + return nil, errtypes.AlreadyExists("origin already exists") } parts := strings.SplitN(recycleNode.ID, node.TrashIDDelimiter, 2) @@ -567,18 +560,18 @@ func (t *Tree) RestoreRecycleItemFunc(ctx context.Context, spaceid, key, trashPa // add the entry for the parent dir err = os.Symlink("../../../../../"+lookup.Pathify(originalId, 4, 2), filepath.Join(targetNode.ParentPath(), targetNode.Name)) if err != nil { - return err + return nil, err } // attempt to rename only if we're not in a subfolder if recycleNode.ID != restoreNode.ID { err = os.Rename(recycleNode.InternalPath(), restoreNode.InternalPath()) if err != nil { - return err + return nil, err } err = t.lookup.MetadataBackend().Rename(recycleNode, restoreNode) if err != nil { - return err + return nil, err } } @@ -590,7 +583,7 @@ func (t *Tree) RestoreRecycleItemFunc(ctx context.Context, spaceid, key, trashPa attrs.SetString(prefixes.ParentidAttr, targetNode.ParentID) if err = t.lookup.MetadataBackend().SetMultiple(ctx, restoreNode, map[string][]byte(attrs), true); err != nil { - return errors.Wrap(err, "Decomposedfs: could not update recycle node") + return nil, errors.Wrap(err, "Decomposedfs: could not update recycle node") } // delete item link in trash @@ -598,7 +591,7 @@ func (t *Tree) RestoreRecycleItemFunc(ctx context.Context, spaceid, key, trashPa if trashPath != "" && trashPath != "/" { resolvedTrashRoot, err := filepath.EvalSymlinks(trashItem) if err != nil { - return errors.Wrap(err, "Decomposedfs: could not resolve trash root") + return nil, errors.Wrap(err, "Decomposedfs: could not resolve trash root") } deletePath = filepath.Join(resolvedTrashRoot, trashPath) if err = os.Remove(deletePath); err != nil { @@ -609,18 +602,11 @@ func (t *Tree) RestoreRecycleItemFunc(ctx context.Context, spaceid, key, trashPa logger.Error().Err(err).Str("trashItem", trashItem).Str("deletePath", deletePath).Str("trashPath", trashPath).Msg("error recursively deleting trash item") } } - - var sizeDiff int64 - if recycleNode.IsDir(ctx) { - treeSize, err := recycleNode.GetTreeSize(ctx) - if err != nil { - return err - } - sizeDiff = int64(treeSize) - } else { - sizeDiff = recycleNode.Blobsize - } - return t.Propagate(ctx, targetNode, sizeDiff) + rn := node.New(restoreNode.SpaceID, restoreNode.ID, targetNode.ParentID, targetNode.Name, recycleNode.Blobsize, recycleNode.BlobID, recycleNode.Type(ctx), recycleNode.Owner(), t.lookup) + rn.SpaceRoot = targetNode.SpaceRoot + rn.Exists = true + // the recycle node has an id with the trish timestamp, but the propagation is only interested in the parent id + return rn, nil } return recycleNode, parent, fn, nil } @@ -801,7 +787,7 @@ func (t *Tree) Propagate(ctx context.Context, n *node.Node, sizeDiff int64) (err // WriteBlob writes a blob to the blobstore func (t *Tree) WriteBlob(node *node.Node, source string) error { - return t.blobstore.Upload(node, source) + return t.blobstore.Upload(node, source, "") } // ReadBlob reads a blob from the blobstore @@ -826,13 +812,14 @@ func (t *Tree) DeleteBlob(node *node.Node) error { } // BuildSpaceIDIndexEntry returns the entry for the space id index -func (t *Tree) BuildSpaceIDIndexEntry(spaceID, nodeID string) string { +func (t *Tree) BuildSpaceIDIndexEntry(spaceID string) string { return "../../../spaces/" + lookup.Pathify(spaceID, 1, 2) + "/nodes/" + lookup.Pathify(spaceID, 4, 2) } // ResolveSpaceIDIndexEntry returns the node id for the space id index entry -func (t *Tree) ResolveSpaceIDIndexEntry(_, entry string) (string, string, error) { - return ReadSpaceAndNodeFromIndexLink(entry) +func (t *Tree) ResolveSpaceIDIndexEntry(entry string) (string, error) { + spaceID, _, err := ReadSpaceAndNodeFromIndexLink(entry) + return spaceID, err } // ReadSpaceAndNodeFromIndexLink reads a symlink and parses space and node id if the link has the correct format, eg: diff --git a/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/upload/store.go b/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/upload/store.go index 2f43c0daa5..712e9f598d 100644 --- a/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/upload/store.go +++ b/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/upload/store.go @@ -125,7 +125,7 @@ func (store DecomposedFsStore) List(ctx context.Context) ([]*DecomposedFsSession func (store DecomposedFsStore) Get(ctx context.Context, id string) (*DecomposedFsSession, error) { sessionPath := sessionPath(store.root, id) match := _idRegexp.FindStringSubmatch(sessionPath) - if match == nil || len(match) < 2 { + if len(match) < 2 { return nil, fmt.Errorf("invalid upload path") } diff --git a/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/utils/decomposedfs/upload/store.go b/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/utils/decomposedfs/upload/store.go index 3284eb3d71..fa12f89028 100644 --- a/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/utils/decomposedfs/upload/store.go +++ b/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/utils/decomposedfs/upload/store.go @@ -125,7 +125,7 @@ func (store OcisStore) List(ctx context.Context) ([]*OcisSession, error) { func (store OcisStore) Get(ctx context.Context, id string) (*OcisSession, error) { sessionPath := sessionPath(store.root, id) match := _idRegexp.FindStringSubmatch(sessionPath) - if match == nil || len(match) < 2 { + if len(match) < 2 { return nil, fmt.Errorf("invalid upload path") } diff --git a/vendor/github.com/opencloud-eu/reva/v2/tests/cs3mocks/mocks/CollaborationAPIClient.go b/vendor/github.com/opencloud-eu/reva/v2/tests/cs3mocks/mocks/CollaborationAPIClient.go index 93cc14d377..a8048cd678 100644 --- a/vendor/github.com/opencloud-eu/reva/v2/tests/cs3mocks/mocks/CollaborationAPIClient.go +++ b/vendor/github.com/opencloud-eu/reva/v2/tests/cs3mocks/mocks/CollaborationAPIClient.go @@ -16,7 +16,7 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -// Code generated by mockery v2.40.2. DO NOT EDIT. +// Code generated by mockery v2.53.2. DO NOT EDIT. package mocks diff --git a/vendor/github.com/opencloud-eu/reva/v2/tests/cs3mocks/mocks/GatewayAPIClient.go b/vendor/github.com/opencloud-eu/reva/v2/tests/cs3mocks/mocks/GatewayAPIClient.go index 6037c33ad3..5302746744 100644 --- a/vendor/github.com/opencloud-eu/reva/v2/tests/cs3mocks/mocks/GatewayAPIClient.go +++ b/vendor/github.com/opencloud-eu/reva/v2/tests/cs3mocks/mocks/GatewayAPIClient.go @@ -1,4 +1,22 @@ -// Code generated by mockery v2.46.3. DO NOT EDIT. +// Copyright 2018-2022 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +// Code generated by mockery v2.53.2. DO NOT EDIT. package mocks diff --git a/vendor/github.com/tus/tusd/v2/pkg/handler/composer.go b/vendor/github.com/tus/tusd/v2/pkg/handler/composer.go index abea0a6900..437549fbe4 100644 --- a/vendor/github.com/tus/tusd/v2/pkg/handler/composer.go +++ b/vendor/github.com/tus/tusd/v2/pkg/handler/composer.go @@ -14,6 +14,8 @@ type StoreComposer struct { Concater ConcaterDataStore UsesLengthDeferrer bool LengthDeferrer LengthDeferrerDataStore + ContentServer ContentServerDataStore + UsesContentServer bool } // NewStoreComposer creates a new and empty store composer. @@ -85,3 +87,8 @@ func (store *StoreComposer) UseLengthDeferrer(ext LengthDeferrerDataStore) { store.UsesLengthDeferrer = ext != nil store.LengthDeferrer = ext } + +func (store *StoreComposer) UseContentServer(ext ContentServerDataStore) { + store.UsesContentServer = ext != nil + store.ContentServer = ext +} diff --git a/vendor/github.com/tus/tusd/v2/pkg/handler/config.go b/vendor/github.com/tus/tusd/v2/pkg/handler/config.go index e5c6702632..480fa243c2 100644 --- a/vendor/github.com/tus/tusd/v2/pkg/handler/config.go +++ b/vendor/github.com/tus/tusd/v2/pkg/handler/config.go @@ -75,6 +75,13 @@ type Config struct { // If the error is non-nil, the error will be forwarded to the client. Furthermore, // HTTPResponse will be ignored and the error value can contain values for the HTTP response. PreFinishResponseCallback func(hook HookEvent) (HTTPResponse, error) + // PreUploadTerminateCallback will be invoked on DELETE requests before an upload is terminated, + // giving the application the opportunity to reject the termination. For example, to ensure resources + // used by other services are not deleted. + // If the callback returns no error, optional values from HTTPResponse will be contained in the HTTP response. + // If the error is non-nil, the error will be forwarded to the client. Furthermore, + // HTTPResponse will be ignored and the error value can contain values for the HTTP response. + PreUploadTerminateCallback func(hook HookEvent) (HTTPResponse, error) // GracefulRequestCompletionTimeout is the timeout for operations to complete after an HTTP // request has ended (successfully or by error). For example, if an HTTP request is interrupted, // instead of stopping immediately, the handler and data store will be given some additional diff --git a/vendor/github.com/tus/tusd/v2/pkg/handler/datastore.go b/vendor/github.com/tus/tusd/v2/pkg/handler/datastore.go index 54c828c63f..5243834fd7 100644 --- a/vendor/github.com/tus/tusd/v2/pkg/handler/datastore.go +++ b/vendor/github.com/tus/tusd/v2/pkg/handler/datastore.go @@ -3,6 +3,7 @@ package handler import ( "context" "io" + "net/http" ) type MetaData map[string]string @@ -191,3 +192,21 @@ type Lock interface { // Unlock releases an existing lock for the given upload. Unlock() error } + +type ServableUpload interface { + // ServeContent serves the uploaded data as specified by the GET request. + // It allows data stores to delegate the handling of range requests and conditional + // requests to their underlying providers. + // The tusd handler will set the Content-Type and Content-Disposition headers + // before calling ServeContent, but the implementation can override them. + // After calling ServeContent, the handler will not take any further action + // other than handling a potential error. + ServeContent(ctx context.Context, w http.ResponseWriter, r *http.Request) error +} + +// ContentServerDataStore is the interface for DataStores that can serve content directly. +// When the handler serves a GET request, it will pass the request to ServeContent +// and delegate its handling to the DataStore, instead of using GetReader to obtain the content. +type ContentServerDataStore interface { + AsServableUpload(upload Upload) ServableUpload +} diff --git a/vendor/github.com/tus/tusd/v2/pkg/handler/unrouted_handler.go b/vendor/github.com/tus/tusd/v2/pkg/handler/unrouted_handler.go index be46735472..1e1998a79e 100644 --- a/vendor/github.com/tus/tusd/v2/pkg/handler/unrouted_handler.go +++ b/vendor/github.com/tus/tusd/v2/pkg/handler/unrouted_handler.go @@ -60,6 +60,7 @@ var ( ErrInvalidUploadDeferLength = NewError("ERR_INVALID_UPLOAD_LENGTH_DEFER", "invalid Upload-Defer-Length header", http.StatusBadRequest) ErrUploadStoppedByServer = NewError("ERR_UPLOAD_STOPPED", "upload has been stopped by server", http.StatusBadRequest) ErrUploadRejectedByServer = NewError("ERR_UPLOAD_REJECTED", "upload creation has been rejected by server", http.StatusBadRequest) + ErrUploadTerminationRejected = NewError("ERR_UPLOAD_TERMINATION_REJECTED", "upload termination has been rejected by server", http.StatusBadRequest) ErrUploadInterrupted = NewError("ERR_UPLOAD_INTERRUPTED", "upload has been interrupted by another request for this upload resource", http.StatusBadRequest) ErrServerShutdown = NewError("ERR_SERVER_SHUTDOWN", "request has been interrupted because the server is shutting down", http.StatusServiceUnavailable) ErrOriginNotAllowed = NewError("ERR_ORIGIN_NOT_ALLOWED", "request origin is not allowed", http.StatusForbidden) @@ -177,10 +178,10 @@ func (handler *UnroutedHandler) Middleware(h http.Handler) http.Handler { // We also update the write deadline, but makes sure that it is larger than the read deadline, so we // can still write a response in the case of a read timeout. if err := c.resC.SetReadDeadline(time.Now().Add(handler.config.NetworkTimeout)); err != nil { - c.log.Warn("NetworkControlError", "error", err) + c.log.WarnContext(c, "NetworkControlError", "error", err) } if err := c.resC.SetWriteDeadline(time.Now().Add(2 * handler.config.NetworkTimeout)); err != nil { - c.log.Warn("NetworkControlError", "error", err) + c.log.WarnContext(c, "NetworkControlError", "error", err) } // Allow overriding the HTTP method. The reason for this is @@ -190,7 +191,7 @@ func (handler *UnroutedHandler) Middleware(h http.Handler) http.Handler { r.Method = newMethod } - c.log.Info("RequestIncoming") + c.log.InfoContext(c, "RequestIncoming") handler.Metrics.incRequestsTotal(r.Method) @@ -405,7 +406,7 @@ func (handler *UnroutedHandler) PostFile(w http.ResponseWriter, r *http.Request) handler.Metrics.incUploadsCreated() c.log = c.log.With("id", id) - c.log.Info("UploadCreated", "size", size, "url", url) + c.log.InfoContext(c, "UploadCreated", "size", size, "url", url) if handler.config.NotifyCreatedUploads { handler.CreatedUploads <- newHookEvent(c, info) @@ -572,7 +573,7 @@ func (handler *UnroutedHandler) PostFileV2(w http.ResponseWriter, r *http.Reques handler.Metrics.incUploadsCreated() c.log = c.log.With("id", id) - c.log.Info("UploadCreated", "size", info.Size, "url", url) + c.log.InfoContext(c, "UploadCreated", "size", info.Size, "url", url) if handler.config.NotifyCreatedUploads { handler.CreatedUploads <- newHookEvent(c, info) @@ -891,7 +892,7 @@ func (handler *UnroutedHandler) writeChunk(c *httpContext, resp HTTPResponse, up maxSize = length } - c.log.Info("ChunkWriteStart", "maxSize", maxSize, "offset", offset) + c.log.InfoContext(c, "ChunkWriteStart", "maxSize", maxSize, "offset", offset) var bytesWritten int64 var err error @@ -907,12 +908,12 @@ func (handler *UnroutedHandler) writeChunk(c *httpContext, resp HTTPResponse, up // Update the read deadline for every successful read operation. This ensures that the request handler // keeps going while data is transmitted but that dead connections can also time out and be cleaned up. if err := c.resC.SetReadDeadline(time.Now().Add(handler.config.NetworkTimeout)); err != nil { - c.log.Warn("NetworkTimeoutError", "error", err) + c.log.WarnContext(c, "NetworkTimeoutError", "error", err) } // The write deadline is updated accordingly to ensure that we can also write responses. if err := c.resC.SetWriteDeadline(time.Now().Add(2 * handler.config.NetworkTimeout)); err != nil { - c.log.Warn("NetworkTimeoutError", "error", err) + c.log.WarnContext(c, "NetworkTimeoutError", "error", err) } } @@ -935,7 +936,7 @@ func (handler *UnroutedHandler) writeChunk(c *httpContext, resp HTTPResponse, up // it in the response, if the store did not also return an error. bodyErr := c.body.hasError() if bodyErr != nil { - c.log.Error("BodyReadError", "error", bodyErr.Error()) + c.log.ErrorContext(c, "BodyReadError", "error", bodyErr.Error()) if err == nil { err = bodyErr } @@ -947,12 +948,12 @@ func (handler *UnroutedHandler) writeChunk(c *httpContext, resp HTTPResponse, up if terminateErr := handler.terminateUpload(c, upload, info); terminateErr != nil { // We only log this error and not show it to the user since this // termination error is not relevant to the uploading client - c.log.Error("UploadStopTerminateError", "error", terminateErr.Error()) + c.log.ErrorContext(c, "UploadStopTerminateError", "error", terminateErr.Error()) } } } - c.log.Info("ChunkWriteComplete", "bytesWritten", bytesWritten) + c.log.InfoContext(c, "ChunkWriteComplete", "bytesWritten", bytesWritten) // Send new offset to client newOffset := offset + bytesWritten @@ -1003,7 +1004,7 @@ func (handler *UnroutedHandler) emitFinishEvents(c *httpContext, resp HTTPRespon resp = resp.MergeWith(resp2) } - c.log.Info("UploadFinished", "size", info.Size) + c.log.InfoContext(c, "UploadFinished", "size", info.Size) handler.Metrics.incUploadsFinished() if handler.config.NotifyCompleteUploads { @@ -1047,6 +1048,7 @@ func (handler *UnroutedHandler) GetFile(w http.ResponseWriter, r *http.Request) return } + // Fall back to the existing GetReader implementation if ContentServerDataStore is not implemented contentType, contentDisposition := filterContentType(info) resp := HTTPResponse{ StatusCode: http.StatusOK, @@ -1058,6 +1060,27 @@ func (handler *UnroutedHandler) GetFile(w http.ResponseWriter, r *http.Request) Body: "", // Body is intentionally left empty, and we copy it manually in later. } + // If the data store implements ContentServerDataStore, use delegate the handling + // of GET requests to the data store. + // Otherwise, we will use the existing GetReader implementation. + if handler.composer.UsesContentServer { + servableUpload := handler.composer.ContentServer.AsServableUpload(upload) + + // Pass file type and name to the implementation, but it may override them. + w.Header().Set("Content-Type", resp.Header["Content-Type"]) + w.Header().Set("Content-Disposition", resp.Header["Content-Disposition"]) + + // Use loggingResponseWriter to get the ResponseOutgoing log entry that + // normally handler.sendResp would produce. + loggingW := &loggingResponseWriter{ResponseWriter: w, logger: c.log} + + err = servableUpload.ServeContent(c, loggingW, r) + if err != nil { + handler.sendError(c, err) + } + return + } + // If no data has been uploaded yet, respond with an empty "204 No Content" status. if info.Offset == 0 { resp.StatusCode = http.StatusNoContent @@ -1065,6 +1088,15 @@ func (handler *UnroutedHandler) GetFile(w http.ResponseWriter, r *http.Request) return } + if handler.composer.UsesContentServer { + servableUpload := handler.composer.ContentServer.AsServableUpload(upload) + err = servableUpload.ServeContent(c, w, r) + if err != nil { + handler.sendError(c, err) + } + return + } + src, err := upload.GetReader(c) if err != nil { handler.sendError(c, err) @@ -1172,7 +1204,7 @@ func (handler *UnroutedHandler) DelFile(w http.ResponseWriter, r *http.Request) } var info FileInfo - if handler.config.NotifyTerminatedUploads { + if handler.config.NotifyTerminatedUploads || handler.config.PreUploadTerminateCallback != nil { info, err = upload.GetInfo(c) if err != nil { handler.sendError(c, err) @@ -1180,15 +1212,26 @@ func (handler *UnroutedHandler) DelFile(w http.ResponseWriter, r *http.Request) } } + resp := HTTPResponse{ + StatusCode: http.StatusNoContent, + } + + if handler.config.PreUploadTerminateCallback != nil { + resp2, err := handler.config.PreUploadTerminateCallback(newHookEvent(c, info)) + if err != nil { + handler.sendError(c, err) + return + } + resp = resp.MergeWith(resp2) + } + err = handler.terminateUpload(c, upload, info) if err != nil { handler.sendError(c, err) return } - handler.sendResp(c, HTTPResponse{ - StatusCode: http.StatusNoContent, - }) + handler.sendResp(c, resp) } // terminateUpload passes a given upload to the DataStore's Terminater, @@ -1208,7 +1251,7 @@ func (handler *UnroutedHandler) terminateUpload(c *httpContext, upload Upload, i handler.TerminatedUploads <- newHookEvent(c, info) } - c.log.Info("UploadTerminated") + c.log.InfoContext(c, "UploadTerminated") handler.Metrics.incUploadsTerminated() return nil @@ -1222,7 +1265,7 @@ func (handler *UnroutedHandler) sendError(c *httpContext, err error) { var detailedErr Error if !errors.As(err, &detailedErr) { - c.log.Error("InternalServerError", "message", err.Error()) + c.log.ErrorContext(c, "InternalServerError", "message", err.Error()) detailedErr = NewError("ERR_INTERNAL_SERVER_ERROR", err.Error(), http.StatusInternalServerError) } @@ -1240,7 +1283,7 @@ func (handler *UnroutedHandler) sendError(c *httpContext, err error) { func (handler *UnroutedHandler) sendResp(c *httpContext, resp HTTPResponse) { resp.writeTo(c.res) - c.log.Info("ResponseOutgoing", "status", resp.StatusCode, "body", resp.Body) + c.log.InfoContext(c, "ResponseOutgoing", "status", resp.StatusCode, "body", resp.Body) } // Make an absolute URLs to the given upload id. If the base path is absolute @@ -1323,6 +1366,14 @@ func getHostAndProtocol(r *http.Request, allowForwarded bool) (host, proto strin } } + // Remove default ports + if proto == "http" { + host = strings.TrimSuffix(host, ":80") + } + if proto == "https" { + host = strings.TrimSuffix(host, ":443") + } + return } @@ -1393,7 +1444,7 @@ func (handler *UnroutedHandler) lockUpload(c *httpContext, id string) (Lock, err // No need to wrap this in a sync.OnceFunc because c.cancel will be a noop after the first call. releaseLock := func() { - c.log.Info("UploadInterrupted") + c.log.InfoContext(c, "UploadInterrupted") c.cancel(ErrUploadInterrupted) } @@ -1671,3 +1722,20 @@ func validateUploadId(newId string) error { return nil } + +// loggingResponseWriter is a wrapper around http.ResponseWriter that logs the +// final status code similar to UnroutedHandler.sendResp. +type loggingResponseWriter struct { + http.ResponseWriter + logger *slog.Logger +} + +func (w *loggingResponseWriter) WriteHeader(statusCode int) { + if statusCode >= 200 { + w.logger.Info("ResponseOutgoing", "status", statusCode) + } + w.ResponseWriter.WriteHeader(statusCode) +} + +// Unwrap provides access to the underlying http.ResponseWriter. +func (w *loggingResponseWriter) Unwrap() http.ResponseWriter { return w.ResponseWriter } diff --git a/vendor/go.etcd.io/etcd/api/v3/version/version.go b/vendor/go.etcd.io/etcd/api/v3/version/version.go index 15c99a2c28..e614915822 100644 --- a/vendor/go.etcd.io/etcd/api/v3/version/version.go +++ b/vendor/go.etcd.io/etcd/api/v3/version/version.go @@ -26,7 +26,7 @@ import ( var ( // MinClusterVersion is the min cluster version this etcd binary is compatible with. MinClusterVersion = "3.0.0" - Version = "3.5.18" + Version = "3.5.19" APIVersion = "unknown" // Git SHA Value will be set during build diff --git a/vendor/go.uber.org/multierr/CHANGELOG.md b/vendor/go.uber.org/multierr/CHANGELOG.md index d2c8aadaf0..f8177b978c 100644 --- a/vendor/go.uber.org/multierr/CHANGELOG.md +++ b/vendor/go.uber.org/multierr/CHANGELOG.md @@ -1,6 +1,21 @@ Releases ======== +v1.11.0 (2023-03-28) +==================== +- `Errors` now supports any error that implements multiple-error + interface. +- Add `Every` function to allow checking if all errors in the chain + satisfies `errors.Is` against the target error. + +v1.10.0 (2023-03-08) +==================== + +- Comply with Go 1.20's multiple-error interface. +- Drop Go 1.18 support. + Per the support policy, only Go 1.19 and 1.20 are supported now. +- Drop all non-test external dependencies. + v1.9.0 (2022-12-12) =================== diff --git a/vendor/go.uber.org/multierr/README.md b/vendor/go.uber.org/multierr/README.md index 70aacecd71..5ab6ac40f4 100644 --- a/vendor/go.uber.org/multierr/README.md +++ b/vendor/go.uber.org/multierr/README.md @@ -2,9 +2,29 @@ `multierr` allows combining one or more Go `error`s together. +## Features + +- **Idiomatic**: + multierr follows best practices in Go, and keeps your code idiomatic. + - It keeps the underlying error type hidden, + allowing you to deal in `error` values exclusively. + - It provides APIs to safely append into an error from a `defer` statement. +- **Performant**: + multierr is optimized for performance: + - It avoids allocations where possible. + - It utilizes slice resizing semantics to optimize common cases + like appending into the same error object from a loop. +- **Interoperable**: + multierr interoperates with the Go standard library's error APIs seamlessly: + - The `errors.Is` and `errors.As` functions *just work*. +- **Lightweight**: + multierr comes with virtually no dependencies. + ## Installation - go get -u go.uber.org/multierr +```bash +go get -u go.uber.org/multierr@latest +``` ## Status diff --git a/vendor/go.uber.org/multierr/error.go b/vendor/go.uber.org/multierr/error.go index cdd91ae56d..3a828b2dff 100644 --- a/vendor/go.uber.org/multierr/error.go +++ b/vendor/go.uber.org/multierr/error.go @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2021 Uber Technologies, Inc. +// Copyright (c) 2017-2023 Uber Technologies, Inc. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -147,8 +147,7 @@ import ( "io" "strings" "sync" - - "go.uber.org/atomic" + "sync/atomic" ) var ( @@ -196,23 +195,7 @@ type errorGroup interface { // // Callers of this function are free to modify the returned slice. func Errors(err error) []error { - if err == nil { - return nil - } - - // Note that we're casting to multiError, not errorGroup. Our contract is - // that returned errors MAY implement errorGroup. Errors, however, only - // has special behavior for multierr-specific error objects. - // - // This behavior can be expanded in the future but I think it's prudent to - // start with as little as possible in terms of contract and possibility - // of misuse. - eg, ok := err.(*multiError) - if !ok { - return []error{err} - } - - return append(([]error)(nil), eg.Errors()...) + return extractErrors(err) } // multiError is an error that holds one or more errors. @@ -227,8 +210,6 @@ type multiError struct { errors []error } -var _ errorGroup = (*multiError)(nil) - // Errors returns the list of underlying errors. // // This slice MUST NOT be modified. @@ -239,33 +220,6 @@ func (merr *multiError) Errors() []error { return merr.errors } -// As attempts to find the first error in the error list that matches the type -// of the value that target points to. -// -// This function allows errors.As to traverse the values stored on the -// multierr error. -func (merr *multiError) As(target interface{}) bool { - for _, err := range merr.Errors() { - if errors.As(err, target) { - return true - } - } - return false -} - -// Is attempts to match the provided error against errors in the error list. -// -// This function allows errors.Is to traverse the values stored on the -// multierr error. -func (merr *multiError) Is(target error) bool { - for _, err := range merr.Errors() { - if errors.Is(err, target) { - return true - } - } - return false -} - func (merr *multiError) Error() string { if merr == nil { return "" @@ -281,6 +235,17 @@ func (merr *multiError) Error() string { return result } +// Every compares every error in the given err against the given target error +// using [errors.Is], and returns true only if every comparison returned true. +func Every(err error, target error) bool { + for _, e := range extractErrors(err) { + if !errors.Is(e, target) { + return false + } + } + return true +} + func (merr *multiError) Format(f fmt.State, c rune) { if c == 'v' && f.Flag('+') { merr.writeMultiline(f) diff --git a/vendor/go.uber.org/multierr/error_post_go120.go b/vendor/go.uber.org/multierr/error_post_go120.go new file mode 100644 index 0000000000..a173f9c251 --- /dev/null +++ b/vendor/go.uber.org/multierr/error_post_go120.go @@ -0,0 +1,48 @@ +// Copyright (c) 2017-2023 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +//go:build go1.20 +// +build go1.20 + +package multierr + +// Unwrap returns a list of errors wrapped by this multierr. +func (merr *multiError) Unwrap() []error { + return merr.Errors() +} + +type multipleErrors interface { + Unwrap() []error +} + +func extractErrors(err error) []error { + if err == nil { + return nil + } + + // check if the given err is an Unwrapable error that + // implements multipleErrors interface. + eg, ok := err.(multipleErrors) + if !ok { + return []error{err} + } + + return append(([]error)(nil), eg.Unwrap()...) +} diff --git a/vendor/go.uber.org/multierr/error_pre_go120.go b/vendor/go.uber.org/multierr/error_pre_go120.go new file mode 100644 index 0000000000..93872a3fcd --- /dev/null +++ b/vendor/go.uber.org/multierr/error_pre_go120.go @@ -0,0 +1,79 @@ +// Copyright (c) 2017-2023 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +//go:build !go1.20 +// +build !go1.20 + +package multierr + +import "errors" + +// Versions of Go before 1.20 did not support the Unwrap() []error method. +// This provides a similar behavior by implementing the Is(..) and As(..) +// methods. +// See the errors.Join proposal for details: +// https://github.com/golang/go/issues/53435 + +// As attempts to find the first error in the error list that matches the type +// of the value that target points to. +// +// This function allows errors.As to traverse the values stored on the +// multierr error. +func (merr *multiError) As(target interface{}) bool { + for _, err := range merr.Errors() { + if errors.As(err, target) { + return true + } + } + return false +} + +// Is attempts to match the provided error against errors in the error list. +// +// This function allows errors.Is to traverse the values stored on the +// multierr error. +func (merr *multiError) Is(target error) bool { + for _, err := range merr.Errors() { + if errors.Is(err, target) { + return true + } + } + return false +} + +func extractErrors(err error) []error { + if err == nil { + return nil + } + + // Note that we're casting to multiError, not errorGroup. Our contract is + // that returned errors MAY implement errorGroup. Errors, however, only + // has special behavior for multierr-specific error objects. + // + // This behavior can be expanded in the future but I think it's prudent to + // start with as little as possible in terms of contract and possibility + // of misuse. + eg, ok := err.(*multiError) + if !ok { + return []error{err} + } + + return append(([]error)(nil), eg.Errors()...) +} diff --git a/vendor/go.uber.org/multierr/glide.yaml b/vendor/go.uber.org/multierr/glide.yaml deleted file mode 100644 index 6ef084ec24..0000000000 --- a/vendor/go.uber.org/multierr/glide.yaml +++ /dev/null @@ -1,8 +0,0 @@ -package: go.uber.org/multierr -import: -- package: go.uber.org/atomic - version: ^1 -testImport: -- package: github.com/stretchr/testify - subpackages: - - assert diff --git a/vendor/golang.org/x/crypto/ssh/handshake.go b/vendor/golang.org/x/crypto/ssh/handshake.go index fef687db0e..c9202b05da 100644 --- a/vendor/golang.org/x/crypto/ssh/handshake.go +++ b/vendor/golang.org/x/crypto/ssh/handshake.go @@ -25,6 +25,11 @@ const debugHandshake = false // quickly. const chanSize = 16 +// maxPendingPackets sets the maximum number of packets to queue while waiting +// for KEX to complete. This limits the total pending data to maxPendingPackets +// * maxPacket bytes, which is ~16.8MB. +const maxPendingPackets = 64 + // keyingTransport is a packet based transport that supports key // changes. It need not be thread-safe. It should pass through // msgNewKeys in both directions. @@ -73,11 +78,19 @@ type handshakeTransport struct { incoming chan []byte readError error - mu sync.Mutex - writeError error - sentInitPacket []byte - sentInitMsg *kexInitMsg - pendingPackets [][]byte // Used when a key exchange is in progress. + mu sync.Mutex + // Condition for the above mutex. It is used to notify a completed key + // exchange or a write failure. Writes can wait for this condition while a + // key exchange is in progress. + writeCond *sync.Cond + writeError error + sentInitPacket []byte + sentInitMsg *kexInitMsg + // Used to queue writes when a key exchange is in progress. The length is + // limited by pendingPacketsSize. Once full, writes will block until the key + // exchange is completed or an error occurs. If not empty, it is emptied + // all at once when the key exchange is completed in kexLoop. + pendingPackets [][]byte writePacketsLeft uint32 writeBytesLeft int64 userAuthComplete bool // whether the user authentication phase is complete @@ -134,6 +147,7 @@ func newHandshakeTransport(conn keyingTransport, config *Config, clientVersion, config: config, } + t.writeCond = sync.NewCond(&t.mu) t.resetReadThresholds() t.resetWriteThresholds() @@ -260,6 +274,7 @@ func (t *handshakeTransport) recordWriteError(err error) { defer t.mu.Unlock() if t.writeError == nil && err != nil { t.writeError = err + t.writeCond.Broadcast() } } @@ -363,6 +378,8 @@ write: } } t.pendingPackets = t.pendingPackets[:0] + // Unblock writePacket if waiting for KEX. + t.writeCond.Broadcast() t.mu.Unlock() } @@ -577,11 +594,20 @@ func (t *handshakeTransport) writePacket(p []byte) error { } if t.sentInitMsg != nil { - // Copy the packet so the writer can reuse the buffer. - cp := make([]byte, len(p)) - copy(cp, p) - t.pendingPackets = append(t.pendingPackets, cp) - return nil + if len(t.pendingPackets) < maxPendingPackets { + // Copy the packet so the writer can reuse the buffer. + cp := make([]byte, len(p)) + copy(cp, p) + t.pendingPackets = append(t.pendingPackets, cp) + return nil + } + for t.sentInitMsg != nil { + // Block and wait for KEX to complete or an error. + t.writeCond.Wait() + if t.writeError != nil { + return t.writeError + } + } } if t.writeBytesLeft > 0 { @@ -598,6 +624,7 @@ func (t *handshakeTransport) writePacket(p []byte) error { if err := t.pushPacket(p); err != nil { t.writeError = err + t.writeCond.Broadcast() } return nil diff --git a/vendor/golang.org/x/crypto/ssh/messages.go b/vendor/golang.org/x/crypto/ssh/messages.go index b55f860564..118427bc05 100644 --- a/vendor/golang.org/x/crypto/ssh/messages.go +++ b/vendor/golang.org/x/crypto/ssh/messages.go @@ -818,6 +818,8 @@ func decode(packet []byte) (interface{}, error) { return new(userAuthSuccessMsg), nil case msgUserAuthFailure: msg = new(userAuthFailureMsg) + case msgUserAuthBanner: + msg = new(userAuthBannerMsg) case msgUserAuthPubKeyOk: msg = new(userAuthPubKeyOkMsg) case msgGlobalRequest: diff --git a/vendor/golang.org/x/crypto/ssh/tcpip.go b/vendor/golang.org/x/crypto/ssh/tcpip.go index ef5059a11d..93d844f035 100644 --- a/vendor/golang.org/x/crypto/ssh/tcpip.go +++ b/vendor/golang.org/x/crypto/ssh/tcpip.go @@ -459,7 +459,7 @@ func (c *Client) dial(laddr string, lport int, raddr string, rport int) (Channel return nil, err } go DiscardRequests(in) - return ch, err + return ch, nil } type tcpChan struct { diff --git a/vendor/golang.org/x/exp/constraints/constraints.go b/vendor/golang.org/x/exp/constraints/constraints.go deleted file mode 100644 index 2c033dff47..0000000000 --- a/vendor/golang.org/x/exp/constraints/constraints.go +++ /dev/null @@ -1,50 +0,0 @@ -// 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 index ecc0dabb74..c25939b92b 100644 --- a/vendor/golang.org/x/exp/maps/maps.go +++ b/vendor/golang.org/x/exp/maps/maps.go @@ -5,9 +5,20 @@ // Package maps defines various functions useful with maps of any type. package maps +import "maps" + +// TODO(adonovan): when https://go.dev/issue/32816 is accepted, all of +// these functions except Keys and Values should be annotated +// (provisionally with "//go:fix inline") so that tools can safely and +// automatically replace calls to exp/maps with calls to std maps by +// inlining them. + // 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 { + // The simplest true equivalent using std is: + // return slices.AppendSeq(make([]K, 0, len(m)), maps.Keys(m)). + r := make([]K, 0, len(m)) for k := range m { r = append(r, k) @@ -18,6 +29,9 @@ func Keys[M ~map[K]V, K comparable, V any](m M) []K { // 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 { + // The simplest true equivalent using std is: + // return slices.AppendSeq(make([]V, 0, len(m)), maps.Values(m)). + r := make([]V, 0, len(m)) for _, v := range m { r = append(r, v) @@ -28,50 +42,24 @@ func Values[M ~map[K]V, K comparable, V any](m M) []V { // 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 + return maps.Equal(m1, m2) } // 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 + return maps.EqualFunc(m1, m2, eq) } // 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) - } + clear(m) } // 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 { - // Preserve nil in case it matters. - if m == nil { - return nil - } - r := make(M, len(m)) - for k, v := range m { - r[k] = v - } - return r + return maps.Clone(m) } // Copy copies all key/value pairs in src adding them to dst. @@ -79,16 +67,10 @@ func Clone[M ~map[K]V, K comparable, V any](m M) M { // the value in dst will be overwritten by the value associated // with the key in src. func Copy[M1 ~map[K]V, M2 ~map[K]V, K comparable, V any](dst M1, src M2) { - for k, v := range src { - dst[k] = v - } + maps.Copy(dst, src) } // 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) - } - } + maps.DeleteFunc(m, del) } diff --git a/vendor/golang.org/x/exp/slices/cmp.go b/vendor/golang.org/x/exp/slices/cmp.go deleted file mode 100644 index fbf1934a06..0000000000 --- a/vendor/golang.org/x/exp/slices/cmp.go +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2023 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" - -// min is a version of the predeclared function from the Go 1.21 release. -func min[T constraints.Ordered](a, b T) T { - if a < b || isNaN(a) { - return a - } - return b -} - -// max is a version of the predeclared function from the Go 1.21 release. -func max[T constraints.Ordered](a, b T) T { - if a > b || isNaN(a) { - return a - } - return b -} - -// cmpLess is a copy of cmp.Less from the Go 1.21 release. -func cmpLess[T constraints.Ordered](x, y T) bool { - return (isNaN(x) && !isNaN(y)) || x < y -} - -// cmpCompare is a copy of cmp.Compare from the Go 1.21 release. -func cmpCompare[T constraints.Ordered](x, y T) int { - xNaN := isNaN(x) - yNaN := isNaN(y) - if xNaN && yNaN { - return 0 - } - if xNaN || x < y { - return -1 - } - if yNaN || x > y { - return +1 - } - return 0 -} diff --git a/vendor/golang.org/x/exp/slices/slices.go b/vendor/golang.org/x/exp/slices/slices.go index 46ceac3439..757383ea1c 100644 --- a/vendor/golang.org/x/exp/slices/slices.go +++ b/vendor/golang.org/x/exp/slices/slices.go @@ -6,26 +6,22 @@ package slices import ( - "unsafe" - - "golang.org/x/exp/constraints" + "cmp" + "slices" ) +// TODO(adonovan): when https://go.dev/issue/32816 is accepted, all of +// these functions should be annotated (provisionally with "//go:fix +// inline") so that tools can safely and automatically replace calls +// to exp/slices with calls to std slices by inlining them. + // 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[S ~[]E, E comparable](s1, s2 S) bool { - if len(s1) != len(s2) { - return false - } - for i := range s1 { - if s1[i] != s2[i] { - return false - } - } - return true + return slices.Equal(s1, s2) } // EqualFunc reports whether two slices are equal using an equality @@ -34,16 +30,7 @@ func Equal[S ~[]E, E comparable](s1, s2 S) bool { // increasing index order, and the comparison stops at the first index // for which eq returns false. func EqualFunc[S1 ~[]E1, S2 ~[]E2, E1, E2 any](s1 S1, s2 S2, 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 + return slices.EqualFunc(s1, s2, eq) } // Compare compares the elements of s1 and s2, using [cmp.Compare] on each pair @@ -53,20 +40,8 @@ func EqualFunc[S1 ~[]E1, S2 ~[]E2, E1, E2 any](s1 S1, s2 S2, eq func(E1, E2) boo // 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. -func Compare[S ~[]E, E constraints.Ordered](s1, s2 S) int { - for i, v1 := range s1 { - if i >= len(s2) { - return +1 - } - v2 := s2[i] - if c := cmpCompare(v1, v2); c != 0 { - return c - } - } - if len(s1) < len(s2) { - return -1 - } - return 0 +func Compare[S ~[]E, E cmp.Ordered](s1, s2 S) int { + return slices.Compare(s1, s2) } // CompareFunc is like [Compare] but uses a custom comparison function on each @@ -75,52 +50,30 @@ func Compare[S ~[]E, E constraints.Ordered](s1, s2 S) int { // 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[S1 ~[]E1, S2 ~[]E2, E1, E2 any](s1 S1, s2 S2, cmp func(E1, E2) int) int { - for i, v1 := range s1 { - if i >= len(s2) { - return +1 - } - v2 := s2[i] - if c := cmp(v1, v2); c != 0 { - return c - } - } - if len(s1) < len(s2) { - return -1 - } - return 0 + return slices.CompareFunc(s1, s2, cmp) } // Index returns the index of the first occurrence of v in s, // or -1 if not present. func Index[S ~[]E, E comparable](s S, v E) int { - for i := range s { - if v == s[i] { - return i - } - } - return -1 + return slices.Index(s, v) } // IndexFunc returns the first index i satisfying f(s[i]), // or -1 if none do. func IndexFunc[S ~[]E, E any](s S, f func(E) bool) int { - for i := range s { - if f(s[i]) { - return i - } - } - return -1 + return slices.IndexFunc(s, f) } // Contains reports whether v is present in s. func Contains[S ~[]E, E comparable](s S, v E) bool { - return Index(s, v) >= 0 + return slices.Contains(s, v) } // ContainsFunc reports whether at least one // element e of s satisfies f(e). func ContainsFunc[S ~[]E, E any](s S, f func(E) bool) bool { - return IndexFunc(s, f) >= 0 + return slices.ContainsFunc(s, f) } // Insert inserts the values v... into s at index i, @@ -131,92 +84,7 @@ func ContainsFunc[S ~[]E, E any](s S, f func(E) bool) bool { // 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 { - m := len(v) - if m == 0 { - return s - } - n := len(s) - if i == n { - return append(s, v...) - } - if n+m > cap(s) { - // Use append rather than make so that we bump the size of - // the slice up to the next storage class. - // This is what Grow does but we don't call Grow because - // that might copy the values twice. - s2 := append(s[:i], make(S, n+m-i)...) - copy(s2[i:], v) - copy(s2[i+m:], s[i:]) - return s2 - } - s = s[:n+m] - - // before: - // s: aaaaaaaabbbbccccccccdddd - // ^ ^ ^ ^ - // i i+m n n+m - // after: - // s: aaaaaaaavvvvbbbbcccccccc - // ^ ^ ^ ^ - // i i+m n n+m - // - // a are the values that don't move in s. - // v are the values copied in from v. - // b and c are the values from s that are shifted up in index. - // d are the values that get overwritten, never to be seen again. - - if !overlaps(v, s[i+m:]) { - // Easy case - v does not overlap either the c or d regions. - // (It might be in some of a or b, or elsewhere entirely.) - // The data we copy up doesn't write to v at all, so just do it. - - copy(s[i+m:], s[i:]) - - // Now we have - // s: aaaaaaaabbbbbbbbcccccccc - // ^ ^ ^ ^ - // i i+m n n+m - // Note the b values are duplicated. - - copy(s[i:], v) - - // Now we have - // s: aaaaaaaavvvvbbbbcccccccc - // ^ ^ ^ ^ - // i i+m n n+m - // That's the result we want. - return s - } - - // The hard case - v overlaps c or d. We can't just shift up - // the data because we'd move or clobber the values we're trying - // to insert. - // So instead, write v on top of d, then rotate. - copy(s[n:], v) - - // Now we have - // s: aaaaaaaabbbbccccccccvvvv - // ^ ^ ^ ^ - // i i+m n n+m - - rotateRight(s[i:], m) - - // Now we have - // s: aaaaaaaavvvvbbbbcccccccc - // ^ ^ ^ ^ - // i i+m n n+m - // That's the result we want. - return s -} - -// clearSlice sets all elements up to the length of s to the zero value of E. -// We may use the builtin clear func instead, and remove clearSlice, when upgrading -// to Go 1.21+. -func clearSlice[S ~[]E, E any](s S) { - var zero E - for i := range s { - s[i] = zero - } + return slices.Insert(s, i, v...) } // Delete removes the elements s[i:j] from s, returning the modified slice. @@ -225,135 +93,27 @@ func clearSlice[S ~[]E, E any](s S) { // make a single call deleting them all together than to delete one at a time. // Delete zeroes the elements s[len(s)-(j-i):len(s)]. func Delete[S ~[]E, E any](s S, i, j int) S { - _ = s[i:j:len(s)] // bounds check - - if i == j { - return s - } - - oldlen := len(s) - s = append(s[:i], s[j:]...) - clearSlice(s[len(s):oldlen]) // zero/nil out the obsolete elements, for GC - return s + return slices.Delete(s, i, j) } // DeleteFunc removes any elements from s for which del returns true, // returning the modified slice. // DeleteFunc zeroes the elements between the new length and the original length. func DeleteFunc[S ~[]E, E any](s S, del func(E) bool) S { - i := IndexFunc(s, del) - if i == -1 { - return s - } - // Don't start copying elements until we find one to delete. - for j := i + 1; j < len(s); j++ { - if v := s[j]; !del(v) { - s[i] = v - i++ - } - } - clearSlice(s[i:]) // zero/nil out the obsolete elements, for GC - return s[:i] + return slices.DeleteFunc(s, del) } // Replace replaces the elements s[i:j] by the given v, and returns the // modified slice. Replace panics if s[i:j] is not a valid slice of s. // When len(v) < (j-i), Replace zeroes the elements between the new length and the original length. func Replace[S ~[]E, E any](s S, i, j int, v ...E) S { - _ = s[i:j] // verify that i:j is a valid subslice - - if i == j { - return Insert(s, i, v...) - } - if j == len(s) { - return append(s[:i], v...) - } - - tot := len(s[:i]) + len(v) + len(s[j:]) - if tot > cap(s) { - // Too big to fit, allocate and copy over. - s2 := append(s[:i], make(S, tot-i)...) // See Insert - copy(s2[i:], v) - copy(s2[i+len(v):], s[j:]) - return s2 - } - - r := s[:tot] - - if i+len(v) <= j { - // Easy, as v fits in the deleted portion. - copy(r[i:], v) - if i+len(v) != j { - copy(r[i+len(v):], s[j:]) - } - clearSlice(s[tot:]) // zero/nil out the obsolete elements, for GC - return r - } - - // We are expanding (v is bigger than j-i). - // The situation is something like this: - // (example has i=4,j=8,len(s)=16,len(v)=6) - // s: aaaaxxxxbbbbbbbbyy - // ^ ^ ^ ^ - // i j len(s) tot - // a: prefix of s - // x: deleted range - // b: more of s - // y: area to expand into - - if !overlaps(r[i+len(v):], v) { - // Easy, as v is not clobbered by the first copy. - copy(r[i+len(v):], s[j:]) - copy(r[i:], v) - return r - } - - // This is a situation where we don't have a single place to which - // we can copy v. Parts of it need to go to two different places. - // We want to copy the prefix of v into y and the suffix into x, then - // rotate |y| spots to the right. - // - // v[2:] v[:2] - // | | - // s: aaaavvvvbbbbbbbbvv - // ^ ^ ^ ^ - // i j len(s) tot - // - // If either of those two destinations don't alias v, then we're good. - y := len(v) - (j - i) // length of y portion - - if !overlaps(r[i:j], v) { - copy(r[i:j], v[y:]) - copy(r[len(s):], v[:y]) - rotateRight(r[i:], y) - return r - } - if !overlaps(r[len(s):], v) { - copy(r[len(s):], v[:y]) - copy(r[i:j], v[y:]) - rotateRight(r[i:], y) - return r - } - - // Now we know that v overlaps both x and y. - // That means that the entirety of b is *inside* v. - // So we don't need to preserve b at all; instead we - // can copy v first, then copy the b part of v out of - // v to the right destination. - k := startIdx(v, s[j:]) - copy(r[i:], v) - copy(r[i+len(v):], r[i+k:]) - return r + return slices.Replace(s, i, j, v...) } // 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...) + return slices.Clone(s) } // Compact replaces consecutive runs of equal elements with a single copy. @@ -362,40 +122,14 @@ func Clone[S ~[]E, E any](s S) S { // which may have a smaller length. // Compact zeroes the elements between the new length and the original length. func Compact[S ~[]E, E comparable](s S) S { - if len(s) < 2 { - return s - } - i := 1 - for k := 1; k < len(s); k++ { - if s[k] != s[k-1] { - if i != k { - s[i] = s[k] - } - i++ - } - } - clearSlice(s[i:]) // zero/nil out the obsolete elements, for GC - return s[:i] + return slices.Compact(s) } // CompactFunc is like [Compact] but uses an equality function to compare elements. // For runs of elements that compare equal, CompactFunc keeps the first one. // CompactFunc zeroes the elements between the new length and the original length. func CompactFunc[S ~[]E, E any](s S, eq func(E, E) bool) S { - if len(s) < 2 { - return s - } - i := 1 - for k := 1; k < len(s); k++ { - if !eq(s[k], s[k-1]) { - if i != k { - s[i] = s[k] - } - i++ - } - } - clearSlice(s[i:]) // zero/nil out the obsolete elements, for GC - return s[:i] + return slices.CompactFunc(s, eq) } // Grow increases the slice's capacity, if necessary, to guarantee space for @@ -403,113 +137,15 @@ func CompactFunc[S ~[]E, E any](s S, eq func(E, E) bool) S { // to the slice without another allocation. If n is negative or too large to // allocate the memory, Grow panics. func Grow[S ~[]E, E any](s S, n int) S { - if n < 0 { - panic("cannot be negative") - } - if n -= cap(s) - len(s); n > 0 { - // TODO(https://go.dev/issue/53888): Make using []E instead of S - // to workaround a compiler bug where the runtime.growslice optimization - // does not take effect. Revert when the compiler is fixed. - s = append([]E(s)[:cap(s)], make([]E, n)...)[:len(s)] - } - return s + return slices.Grow(s, n) } // 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)] -} - -// Rotation algorithm explanation: -// -// rotate left by 2 -// start with -// 0123456789 -// split up like this -// 01 234567 89 -// swap first 2 and last 2 -// 89 234567 01 -// join first parts -// 89234567 01 -// recursively rotate first left part by 2 -// 23456789 01 -// join at the end -// 2345678901 -// -// rotate left by 8 -// start with -// 0123456789 -// split up like this -// 01 234567 89 -// swap first 2 and last 2 -// 89 234567 01 -// join last parts -// 89 23456701 -// recursively rotate second part left by 6 -// 89 01234567 -// join at the end -// 8901234567 - -// TODO: There are other rotate algorithms. -// This algorithm has the desirable property that it moves each element exactly twice. -// The triple-reverse algorithm is simpler and more cache friendly, but takes more writes. -// The follow-cycles algorithm can be 1-write but it is not very cache friendly. - -// rotateLeft rotates b left by n spaces. -// s_final[i] = s_orig[i+r], wrapping around. -func rotateLeft[E any](s []E, r int) { - for r != 0 && r != len(s) { - if r*2 <= len(s) { - swap(s[:r], s[len(s)-r:]) - s = s[:len(s)-r] - } else { - swap(s[:len(s)-r], s[r:]) - s, r = s[len(s)-r:], r*2-len(s) - } - } -} -func rotateRight[E any](s []E, r int) { - rotateLeft(s, len(s)-r) -} - -// swap swaps the contents of x and y. x and y must be equal length and disjoint. -func swap[E any](x, y []E) { - for i := 0; i < len(x); i++ { - x[i], y[i] = y[i], x[i] - } -} - -// overlaps reports whether the memory ranges a[0:len(a)] and b[0:len(b)] overlap. -func overlaps[E any](a, b []E) bool { - if len(a) == 0 || len(b) == 0 { - return false - } - elemSize := unsafe.Sizeof(a[0]) - if elemSize == 0 { - return false - } - // TODO: use a runtime/unsafe facility once one becomes available. See issue 12445. - // Also see crypto/internal/alias/alias.go:AnyOverlap - return uintptr(unsafe.Pointer(&a[0])) <= uintptr(unsafe.Pointer(&b[len(b)-1]))+(elemSize-1) && - uintptr(unsafe.Pointer(&b[0])) <= uintptr(unsafe.Pointer(&a[len(a)-1]))+(elemSize-1) -} - -// startIdx returns the index in haystack where the needle starts. -// prerequisite: the needle must be aliased entirely inside the haystack. -func startIdx[E any](haystack, needle []E) int { - p := &needle[0] - for i := range haystack { - if p == &haystack[i] { - return i - } - } - // TODO: what if the overlap is by a non-integral number of Es? - panic("needle not found") + return slices.Clip(s) } // Reverse reverses the elements of the slice in place. func Reverse[S ~[]E, E any](s S) { - for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 { - s[i], s[j] = s[j], s[i] - } + slices.Reverse(s) } diff --git a/vendor/golang.org/x/exp/slices/sort.go b/vendor/golang.org/x/exp/slices/sort.go index f58bbc7ba4..e270a74652 100644 --- a/vendor/golang.org/x/exp/slices/sort.go +++ b/vendor/golang.org/x/exp/slices/sort.go @@ -2,21 +2,20 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:generate go run $GOROOT/src/sort/gen_sort_variants.go -exp - package slices import ( - "math/bits" - - "golang.org/x/exp/constraints" + "cmp" + "slices" ) +// TODO(adonovan): add a "//go:fix inline" annotation to each function +// in this file; see https://go.dev/issue/32816. + // Sort sorts a slice of any ordered type in ascending order. // When sorting floating-point numbers, NaNs are ordered before other values. -func Sort[S ~[]E, E constraints.Ordered](x S) { - n := len(x) - pdqsortOrdered(x, 0, n, bits.Len(uint(n))) +func Sort[S ~[]E, E cmp.Ordered](x S) { + slices.Sort(x) } // SortFunc sorts the slice x in ascending order as determined by the cmp @@ -29,118 +28,60 @@ func Sort[S ~[]E, E constraints.Ordered](x S) { // See https://en.wikipedia.org/wiki/Weak_ordering#Strict_weak_orderings. // To indicate 'uncomparable', return 0 from the function. func SortFunc[S ~[]E, E any](x S, cmp func(a, b E) int) { - n := len(x) - pdqsortCmpFunc(x, 0, n, bits.Len(uint(n)), cmp) + slices.SortFunc(x, cmp) } // SortStableFunc sorts the slice x while keeping the original order of equal // elements, using cmp to compare elements in the same way as [SortFunc]. func SortStableFunc[S ~[]E, E any](x S, cmp func(a, b E) int) { - stableCmpFunc(x, len(x), cmp) + slices.SortStableFunc(x, cmp) } // IsSorted reports whether x is sorted in ascending order. -func IsSorted[S ~[]E, E constraints.Ordered](x S) bool { - for i := len(x) - 1; i > 0; i-- { - if cmpLess(x[i], x[i-1]) { - return false - } - } - return true +func IsSorted[S ~[]E, E cmp.Ordered](x S) bool { + return slices.IsSorted(x) } // IsSortedFunc reports whether x is sorted in ascending order, with cmp as the // comparison function as defined by [SortFunc]. func IsSortedFunc[S ~[]E, E any](x S, cmp func(a, b E) int) bool { - for i := len(x) - 1; i > 0; i-- { - if cmp(x[i], x[i-1]) < 0 { - return false - } - } - return true + return slices.IsSortedFunc(x, cmp) } // Min returns the minimal value in x. It panics if x is empty. // For floating-point numbers, Min propagates NaNs (any NaN value in x // forces the output to be NaN). -func Min[S ~[]E, E constraints.Ordered](x S) E { - if len(x) < 1 { - panic("slices.Min: empty list") - } - m := x[0] - for i := 1; i < len(x); i++ { - m = min(m, x[i]) - } - return m +func Min[S ~[]E, E cmp.Ordered](x S) E { + return slices.Min(x) } // MinFunc returns the minimal value in x, using cmp to compare elements. // It panics if x is empty. If there is more than one minimal element // according to the cmp function, MinFunc returns the first one. func MinFunc[S ~[]E, E any](x S, cmp func(a, b E) int) E { - if len(x) < 1 { - panic("slices.MinFunc: empty list") - } - m := x[0] - for i := 1; i < len(x); i++ { - if cmp(x[i], m) < 0 { - m = x[i] - } - } - return m + return slices.MinFunc(x, cmp) } // Max returns the maximal value in x. It panics if x is empty. // For floating-point E, Max propagates NaNs (any NaN value in x // forces the output to be NaN). -func Max[S ~[]E, E constraints.Ordered](x S) E { - if len(x) < 1 { - panic("slices.Max: empty list") - } - m := x[0] - for i := 1; i < len(x); i++ { - m = max(m, x[i]) - } - return m +func Max[S ~[]E, E cmp.Ordered](x S) E { + return slices.Max(x) } // MaxFunc returns the maximal value in x, using cmp to compare elements. // It panics if x is empty. If there is more than one maximal element // according to the cmp function, MaxFunc returns the first one. func MaxFunc[S ~[]E, E any](x S, cmp func(a, b E) int) E { - if len(x) < 1 { - panic("slices.MaxFunc: empty list") - } - m := x[0] - for i := 1; i < len(x); i++ { - if cmp(x[i], m) > 0 { - m = x[i] - } - } - return m + return slices.MaxFunc(x, cmp) } // BinarySearch searches for target in a sorted slice and returns the position // where target is found, or the position where target would appear in the // sort order; it also returns a bool saying whether the target is really found // in the slice. The slice must be sorted in increasing order. -func BinarySearch[S ~[]E, E constraints.Ordered](x S, target E) (int, bool) { - // Inlining is faster than calling BinarySearchFunc with a lambda. - n := len(x) - // Define x[-1] < target and x[n] >= target. - // Invariant: x[i-1] < target, x[j] >= target. - i, j := 0, n - for i < j { - h := int(uint(i+j) >> 1) // avoid overflow when computing h - // i ≤ h < j - if cmpLess(x[h], target) { - i = h + 1 // preserves x[i-1] < target - } else { - j = h // preserves x[j] >= target - } - } - // i == j, x[i-1] < target, and x[j] (= x[i]) >= target => answer is i. - return i, i < n && (x[i] == target || (isNaN(x[i]) && isNaN(target))) +func BinarySearch[S ~[]E, E cmp.Ordered](x S, target E) (int, bool) { + return slices.BinarySearch(x, target) } // BinarySearchFunc works like [BinarySearch], but uses a custom comparison @@ -151,47 +92,5 @@ func BinarySearch[S ~[]E, E constraints.Ordered](x S, target E) (int, bool) { // cmp must implement the same ordering as the slice, such that if // cmp(a, t) < 0 and cmp(b, t) >= 0, then a must precede b in the slice. func BinarySearchFunc[S ~[]E, E, T any](x S, target T, cmp func(E, T) int) (int, bool) { - n := len(x) - // Define cmp(x[-1], target) < 0 and cmp(x[n], target) >= 0 . - // Invariant: cmp(x[i - 1], target) < 0, cmp(x[j], target) >= 0. - i, j := 0, n - for i < j { - h := int(uint(i+j) >> 1) // avoid overflow when computing h - // i ≤ h < j - if cmp(x[h], target) < 0 { - i = h + 1 // preserves cmp(x[i - 1], target) < 0 - } else { - j = h // preserves cmp(x[j], target) >= 0 - } - } - // i == j, cmp(x[i-1], target) < 0, and cmp(x[j], target) (= cmp(x[i], target)) >= 0 => answer is i. - return i, i < n && cmp(x[i], target) == 0 -} - -type sortedHint int // hint for pdqsort when choosing the pivot - -const ( - unknownHint sortedHint = iota - increasingHint - decreasingHint -) - -// xorshift paper: https://www.jstatsoft.org/article/view/v008i14/xorshift.pdf -type xorshift uint64 - -func (r *xorshift) Next() uint64 { - *r ^= *r << 13 - *r ^= *r >> 17 - *r ^= *r << 5 - return uint64(*r) -} - -func nextPowerOfTwo(length int) uint { - return 1 << bits.Len(uint(length)) -} - -// isNaN reports whether x is a NaN without requiring the math package. -// This will always return false if T is not floating-point. -func isNaN[T constraints.Ordered](x T) bool { - return x != x + return slices.BinarySearchFunc(x, target, cmp) } diff --git a/vendor/golang.org/x/exp/slices/zsortanyfunc.go b/vendor/golang.org/x/exp/slices/zsortanyfunc.go deleted file mode 100644 index 06f2c7a248..0000000000 --- a/vendor/golang.org/x/exp/slices/zsortanyfunc.go +++ /dev/null @@ -1,479 +0,0 @@ -// 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 - -// insertionSortCmpFunc sorts data[a:b] using insertion sort. -func insertionSortCmpFunc[E any](data []E, a, b int, cmp func(a, b E) int) { - for i := a + 1; i < b; i++ { - for j := i; j > a && (cmp(data[j], data[j-1]) < 0); j-- { - data[j], data[j-1] = data[j-1], data[j] - } - } -} - -// siftDownCmpFunc implements the heap property on data[lo:hi]. -// first is an offset into the array where the root of the heap lies. -func siftDownCmpFunc[E any](data []E, lo, hi, first int, cmp func(a, b E) int) { - root := lo - for { - child := 2*root + 1 - if child >= hi { - break - } - if child+1 < hi && (cmp(data[first+child], data[first+child+1]) < 0) { - child++ - } - if !(cmp(data[first+root], data[first+child]) < 0) { - return - } - data[first+root], data[first+child] = data[first+child], data[first+root] - root = child - } -} - -func heapSortCmpFunc[E any](data []E, a, b int, cmp func(a, b E) int) { - first := a - lo := 0 - hi := b - a - - // Build heap with greatest element at top. - for i := (hi - 1) / 2; i >= 0; i-- { - siftDownCmpFunc(data, i, hi, first, cmp) - } - - // 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] - siftDownCmpFunc(data, lo, i, first, cmp) - } -} - -// pdqsortCmpFunc sorts data[a:b]. -// The algorithm based on pattern-defeating quicksort(pdqsort), but without the optimizations from BlockQuicksort. -// pdqsort paper: https://arxiv.org/pdf/2106.05123.pdf -// C++ implementation: https://github.com/orlp/pdqsort -// Rust implementation: https://docs.rs/pdqsort/latest/pdqsort/ -// limit is the number of allowed bad (very unbalanced) pivots before falling back to heapsort. -func pdqsortCmpFunc[E any](data []E, a, b, limit int, cmp func(a, b E) int) { - const maxInsertion = 12 - - var ( - wasBalanced = true // whether the last partitioning was reasonably balanced - wasPartitioned = true // whether the slice was already partitioned - ) - - for { - length := b - a - - if length <= maxInsertion { - insertionSortCmpFunc(data, a, b, cmp) - return - } - - // Fall back to heapsort if too many bad choices were made. - if limit == 0 { - heapSortCmpFunc(data, a, b, cmp) - return - } - - // If the last partitioning was imbalanced, we need to breaking patterns. - if !wasBalanced { - breakPatternsCmpFunc(data, a, b, cmp) - limit-- - } - - pivot, hint := choosePivotCmpFunc(data, a, b, cmp) - if hint == decreasingHint { - reverseRangeCmpFunc(data, a, b, cmp) - // The chosen pivot was pivot-a elements after the start of the array. - // After reversing it is pivot-a elements before the end of the array. - // The idea came from Rust's implementation. - pivot = (b - 1) - (pivot - a) - hint = increasingHint - } - - // The slice is likely already sorted. - if wasBalanced && wasPartitioned && hint == increasingHint { - if partialInsertionSortCmpFunc(data, a, b, cmp) { - return - } - } - - // Probably the slice contains many duplicate elements, partition the slice into - // elements equal to and elements greater than the pivot. - if a > 0 && !(cmp(data[a-1], data[pivot]) < 0) { - mid := partitionEqualCmpFunc(data, a, b, pivot, cmp) - a = mid - continue - } - - mid, alreadyPartitioned := partitionCmpFunc(data, a, b, pivot, cmp) - wasPartitioned = alreadyPartitioned - - leftLen, rightLen := mid-a, b-mid - balanceThreshold := length / 8 - if leftLen < rightLen { - wasBalanced = leftLen >= balanceThreshold - pdqsortCmpFunc(data, a, mid, limit, cmp) - a = mid + 1 - } else { - wasBalanced = rightLen >= balanceThreshold - pdqsortCmpFunc(data, mid+1, b, limit, cmp) - b = mid - } - } -} - -// partitionCmpFunc does one quicksort partition. -// Let p = data[pivot] -// Moves elements in data[a:b] around, so that data[i]
=p for i =p for i