diff --git a/go.mod b/go.mod index 0f8eb6ed6e1..81385f8dc0a 100644 --- a/go.mod +++ b/go.mod @@ -42,7 +42,7 @@ require ( github.com/coreos/stream-metadata-go v0.4.10-0.20250806142651-4a7d280a6c7b github.com/daixiang0/gci v0.13.5 github.com/digitalocean/go-libvirt v0.0.0-20240220204746-fcabe97a6eed - github.com/diskfs/go-diskfs v1.4.1 + github.com/diskfs/go-diskfs v1.7.1-0.20251217162235-58541aa8f559 github.com/form3tech-oss/jwt-go v3.2.3+incompatible github.com/go-logr/logr v1.4.3 github.com/go-openapi/errors v0.22.1 @@ -60,7 +60,7 @@ require ( github.com/gophercloud/utils/v2 v2.0.0-20250212084022-725b94822eeb github.com/h2non/filetype v1.0.12 github.com/hashicorp/go-multierror v1.1.1 - github.com/hashicorp/go-version v1.7.0 + github.com/hashicorp/go-version v1.8.0 github.com/jarcoal/httpmock v1.3.1 github.com/jongio/azidext/go/azidext v0.5.0 github.com/kdomanski/iso9660 v0.2.1 @@ -70,9 +70,9 @@ require ( github.com/microsoftgraph/msgraph-sdk-go v0.59.0 github.com/nutanix-cloud-native/cluster-api-provider-nutanix v1.7.0 github.com/nutanix-cloud-native/prism-go-client v0.5.0 - github.com/onsi/gomega v1.38.2 + github.com/onsi/gomega v1.39.1 github.com/openshift/api v0.0.0-20260429122012-1180c0f5c3e9 - github.com/openshift/assisted-image-service v0.0.0-20250917153356-4ca9ff81f712 + github.com/openshift/assisted-image-service v0.0.0-20260428115106-2b81dd8e7120 github.com/openshift/assisted-service/api v0.0.0 github.com/openshift/assisted-service/client v0.0.0 github.com/openshift/assisted-service/models v0.0.0 @@ -94,7 +94,7 @@ require ( github.com/prometheus/common v0.67.4 github.com/rogpeppe/go-internal v1.14.1 github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd - github.com/sirupsen/logrus v1.9.4-0.20230606125235-dd1b4c2e81af + github.com/sirupsen/logrus v1.9.4 github.com/spf13/cobra v1.10.1 github.com/stretchr/testify v1.11.1 github.com/thedevsaddam/retry v0.0.0-20200324223450-9769a859cc6d @@ -161,13 +161,16 @@ require ( github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 // indirect + github.com/anchore/go-lzo v0.1.0 // indirect github.com/aws/aws-sdk-go-v2/service/cloudfront v1.40.4 // indirect github.com/aws/aws-sdk-go-v2/service/signin v1.0.6 // indirect github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 // indirect + github.com/djherbis/times v1.6.0 // indirect github.com/envoyproxy/go-control-plane/envoy v1.36.0 // indirect github.com/envoyproxy/protoc-gen-validate v1.3.0 // indirect github.com/go-jose/go-jose/v4 v4.1.3 // indirect github.com/golangci/plugin-module-register v0.1.2 // indirect + github.com/klauspost/compress v1.18.0 // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/spiffe/go-spiffe/v2 v2.6.0 // indirect github.com/std-uritemplate/std-uritemplate/go/v2 v2.0.5 // indirect @@ -226,7 +229,6 @@ require ( github.com/coreos/go-systemd/v22 v22.6.0 // indirect github.com/coreos/vcontext v0.0.0-20230201181013-d72178a18687 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/djherbis/times v1.6.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/elliotwutingfeng/asciiset v0.0.0-20230602022725-51bbb787efab // indirect github.com/emicklei/go-restful/v3 v3.13.0 // indirect @@ -269,7 +271,6 @@ require ( github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect - github.com/klauspost/compress v1.18.0 // indirect github.com/kr/fs v0.1.0 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect diff --git a/go.sum b/go.sum index 9fc701f02a3..afbde909749 100644 --- a/go.sum +++ b/go.sum @@ -182,6 +182,8 @@ github.com/alingse/asasalint v0.0.11 h1:SFwnQXJ49Kx/1GghOFz1XGqHYKp21Kq1nHad/0WQ github.com/alingse/asasalint v0.0.11/go.mod h1:nCaoMhw7a9kSJObvQyVzNTPBDbNpdocqrSP7t/cW5+I= github.com/alingse/nilnesserr v0.1.2 h1:Yf8Iwm3z2hUUrP4muWfW83DF4nE3r1xZ26fGWUKCZlo= github.com/alingse/nilnesserr v0.1.2/go.mod h1:1xJPrXonEtX7wyTq8Dytns5P2hNzoWymVUIaKm4HNFg= +github.com/anchore/go-lzo v0.1.0 h1:NgAacnzqPeGH49Ky19QKLBZEuFRqtTG9cdaucc3Vncs= +github.com/anchore/go-lzo v0.1.0/go.mod h1:3kLx0bve2oN1iDwgM1U5zGku1Tfbdb0No5qp1eL1fIk= github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= github.com/apparentlymart/go-cidr v1.1.0 h1:2mAhrMoF+nhXqxTzSZMUzDHkLjmIHC+Zzn4tdgBZjnU= @@ -345,8 +347,8 @@ github.com/denis-tingaikin/go-header v0.5.0 h1:SRdnP5ZKvcO9KKRP1KJrhFR3RrlGuD+42 github.com/denis-tingaikin/go-header v0.5.0/go.mod h1:mMenU5bWrok6Wl2UsZjy+1okegmwQ3UgWl4V1D8gjlY= github.com/digitalocean/go-libvirt v0.0.0-20240220204746-fcabe97a6eed h1:pDXysiX24X+SE6MwVcfd5lGE21a4jNH9ZgaF9AyshHY= github.com/digitalocean/go-libvirt v0.0.0-20240220204746-fcabe97a6eed/go.mod h1:isF7ghADfbC01gQx4vZnIOrxXT5RXLG81y+UCb5XSwc= -github.com/diskfs/go-diskfs v1.4.1 h1:iODgkzHLmvXS+1VDztpW53T+dQm8GQzi20y9yUd5UCA= -github.com/diskfs/go-diskfs v1.4.1/go.mod h1:+tOkQs8CMMog6Nvljg8DGIxEXrgL48iyT3OM3IlSz74= +github.com/diskfs/go-diskfs v1.7.1-0.20251217162235-58541aa8f559 h1:rJpHpZwao2igQ9blNiq9xnxwRxExD8tbwhIDaLxydvQ= +github.com/diskfs/go-diskfs v1.7.1-0.20251217162235-58541aa8f559/go.mod h1:02SN/PNyJRmOlwuJ8FcE1OkPFcMPvAoFUZCAvsE3Ty4= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c= @@ -587,8 +589,8 @@ github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/ github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc= github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5 h1:xhMrHhTJ6zxu3gA4enFM9MLn9AY7613teCdFnlUVbSQ= -github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5/go.mod h1:5hDyRhoBCxViHszMt12TnOpEI4VVi+U8Gm9iphldiMA= +github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 h1:z2ogiKUYzX5Is6zr/vP9vJGqPwcdqsWjOt+V8J7+bTc= +github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI= github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -642,8 +644,8 @@ github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9 github.com/hashicorp/go-retryablehttp v0.7.0/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= github.com/hashicorp/go-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVUrx/c8Unxc48= github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw= -github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= -github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4= +github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= @@ -872,16 +874,16 @@ github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vv github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= -github.com/onsi/ginkgo/v2 v2.27.2 h1:LzwLj0b89qtIy6SSASkzlNvX6WktqurSHwkk2ipF/Ns= -github.com/onsi/ginkgo/v2 v2.27.2/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo= +github.com/onsi/ginkgo/v2 v2.28.0 h1:Rrf+lVLmtlBIKv6KrIGJCjyY8N36vDVcutbGJkyqjJc= +github.com/onsi/ginkgo/v2 v2.28.0/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= -github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A= -github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k= +github.com/onsi/gomega v1.39.1 h1:1IJLAad4zjPn2PsnhH70V4DKRFlrCzGBNrNaru+Vf28= +github.com/onsi/gomega v1.39.1/go.mod h1:hL6yVALoTOxeWudERyfppUcZXjMwIMLnuSfruD2lcfg= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= @@ -890,8 +892,8 @@ github.com/opencontainers/runtime-spec v1.2.0 h1:z97+pHb3uELt/yiAWD691HNHQIF07bE github.com/opencontainers/runtime-spec v1.2.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/openshift/api v0.0.0-20260429122012-1180c0f5c3e9 h1:lZw6pYY7El1giNk1lYvkp6hLungiqwIOqLlH+Hm7w9g= github.com/openshift/api v0.0.0-20260429122012-1180c0f5c3e9/go.mod h1:pyVjK0nZ4sRs4fuQVQ4rubsJdahI1PB94LnQ8sGdvxo= -github.com/openshift/assisted-image-service v0.0.0-20250917153356-4ca9ff81f712 h1:UJVh+I/AWZcOJASGdiLcTXkWB1OYNhS/383DHMcRvCQ= -github.com/openshift/assisted-image-service v0.0.0-20250917153356-4ca9ff81f712/go.mod h1:WGdSeSnK0voEWWwA4ar5eApNjGBLmGTpFurEKw/FXJc= +github.com/openshift/assisted-image-service v0.0.0-20260428115106-2b81dd8e7120 h1:fxnvVDuqfnAz9jCuritFrsb6IK33dxc/MxC9Gtv+nlk= +github.com/openshift/assisted-image-service v0.0.0-20260428115106-2b81dd8e7120/go.mod h1:eLxB7EHTDEvuJHobsVZsyMKOFkVATz4aGAwNtSGMdyM= github.com/openshift/assisted-service/api v0.0.0-20250922204150-a52b83145bea h1:YhJ9iHKKT5ooAdVr8qq3BdudhTxP/WF0XYDT5gzi1ak= github.com/openshift/assisted-service/api v0.0.0-20250922204150-a52b83145bea/go.mod h1:wA7MaLcf/KoUl7fhB1bHBdhRBLjWPih90sHpxOV6ZLE= github.com/openshift/assisted-service/client v0.0.0-20250922204150-a52b83145bea h1:nYepkoJZSEjQEadaZ7oZraaeTug0zSV43HISLaHTCF0= @@ -1021,8 +1023,8 @@ github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd/go.mod h1:TrYk7fJV github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.9.4-0.20230606125235-dd1b4c2e81af h1:Sp5TG9f7K39yfB+If0vjp97vuT74F72r8hfRpP8jLU0= -github.com/sirupsen/logrus v1.9.4-0.20230606125235-dd1b4c2e81af/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= +github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= github.com/sivchari/containedctx v1.0.3 h1:x+etemjbsh2fB5ewm5FeLNi5bUjK0V8n0RB+Wwfd0XE= github.com/sivchari/containedctx v1.0.3/go.mod h1:c1RDvCbnJLtH4lLcYD/GqwiBSSf4F5Qk0xld2rBqzJ4= github.com/sivchari/tenv v1.12.1 h1:+E0QzjktdnExv/wwsnnyk4oqZBUfuh89YMQT1cyuvSY= @@ -1286,7 +1288,6 @@ golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/vendor/github.com/anchore/go-lzo/.binny.yaml b/vendor/github.com/anchore/go-lzo/.binny.yaml new file mode 100644 index 00000000000..858327626f0 --- /dev/null +++ b/vendor/github.com/anchore/go-lzo/.binny.yaml @@ -0,0 +1,80 @@ +tools: + # we want to use a pinned version of binny to manage the toolchain (so binny manages itself!) + - name: binny + version: + want: v0.9.0 + method: github-release + with: + repo: anchore/binny + + # used for linting + - name: golangci-lint + version: + want: v2.1.6 + method: github-release + with: + repo: golangci/golangci-lint + + # used for showing the changelog at release + - name: glow + version: + want: v2.1.0 + method: github-release + with: + repo: charmbracelet/glow + + # used to release + - name: goreleaser + version: + want: v2.9.0 + method: github-release + with: + repo: goreleaser/goreleaser + + # used for organizing imports during static analysis + - name: gosimports + version: + want: v0.3.8 + method: github-release + with: + repo: rinchsan/gosimports + + # used at release to generate the changelog + - name: chronicle + version: + want: v0.8.0 + method: github-release + with: + repo: anchore/chronicle + + # used during static analysis for license compliance + - name: bouncer + version: + want: v0.4.0 + method: github-release + with: + repo: wagoodman/go-bouncer + + # used for running all local and CI tasks + - name: task + version: + want: v3.43.3 + method: github-release + with: + repo: go-task/task + + # used for triggering a release + - name: gh + version: + want: v2.73.0 + method: github-release + with: + repo: cli/cli + + # used for signing the checksums file at release + - name: cosign + version: + want: v2.5.0 + method: github-release + with: + repo: sigstore/cosign diff --git a/vendor/github.com/anchore/go-lzo/.bouncer.yaml b/vendor/github.com/anchore/go-lzo/.bouncer.yaml new file mode 100644 index 00000000000..f3380c4a5f6 --- /dev/null +++ b/vendor/github.com/anchore/go-lzo/.bouncer.yaml @@ -0,0 +1,13 @@ +permit: + - BSD.* + - CC0.* + - MIT.* + - Apache.* + - MPL.* + - ISC + - WTFPL + - Unlicense + +ignore-packages: + # crypto/internal/boring is released under the openSSL license as a part of the Golang Standard Libary + - crypto/internal/boring diff --git a/vendor/github.com/anchore/go-lzo/.gitignore b/vendor/github.com/anchore/go-lzo/.gitignore new file mode 100644 index 00000000000..0f22ad51919 --- /dev/null +++ b/vendor/github.com/anchore/go-lzo/.gitignore @@ -0,0 +1,23 @@ +# Tools and test cache +.tool/ +*.lzo +*.lzop +*.test + +# IDE +.idea/ +*.out + +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Go workspace file +go.work +go.work.sum + +# env file +.env diff --git a/vendor/github.com/anchore/go-lzo/.goreleaser.yaml b/vendor/github.com/anchore/go-lzo/.goreleaser.yaml new file mode 100644 index 00000000000..de4c3b1057e --- /dev/null +++ b/vendor/github.com/anchore/go-lzo/.goreleaser.yaml @@ -0,0 +1,19 @@ +release: + prerelease: auto + draft: false + +builds: + - skip: true + +signs: + - cmd: .tool/cosign + signature: "${artifact}.sig" + certificate: "${artifact}.pem" + args: + - "sign-blob" + - "--oidc-issuer=https://token.actions.githubusercontent.com" + - "--output-certificate=${certificate}" + - "--output-signature=${signature}" + - "${artifact}" + - "--yes" + artifacts: checksum diff --git a/vendor/github.com/anchore/go-lzo/CHANGELOG.md b/vendor/github.com/anchore/go-lzo/CHANGELOG.md new file mode 100644 index 00000000000..1e9e6dc4a74 --- /dev/null +++ b/vendor/github.com/anchore/go-lzo/CHANGELOG.md @@ -0,0 +1,2 @@ +1 error occurred: + * problem while attempting to find head tag: unable fetch head: reference not found diff --git a/vendor/github.com/anchore/go-lzo/CONTRIBUTING.md b/vendor/github.com/anchore/go-lzo/CONTRIBUTING.md new file mode 100644 index 00000000000..5d36255f244 --- /dev/null +++ b/vendor/github.com/anchore/go-lzo/CONTRIBUTING.md @@ -0,0 +1,154 @@ +[#](#) Contributing to go-lzo + +If you are looking to contribute to this project and want to open a GitHub pull request ("PR"), there are a few guidelines of what we are looking for in patches. Make sure you go through this document and ensure that your code proposal is aligned. + +## Setting up your environment + +Before you can contribute to go-lzo, you need to configure your development environment. + +### Debian setup + +You will need to install Go. The version on https://go.dev works best, using the system golang doesn't always work the way you might expect. + +Refer to the go.mod file in the root of this repo for the recommended version of Go to install. + +You will also need Docker. There's no reason the system packages shouldn't work, but we used the official Docker package. You can find instructions for installing Docker in Debian [here](https://docs.docker.com/engine/install/debian/). + +You also need to install some Debian packages + +```sh +sudo apt-get install build-essential zip bc libxml2-utils git +``` + +## Configuring Git + +You will need to configure your git client with your name and email address. This is easily done from the command line. + +```text +$ git config --global user.name "John Doe" +$ git config --global user.email "john.doe@example.com" +``` + +This username and email address will matter later in this guide. + +## Fork the repo + +You should fork the go-lzo repo using the "Fork" button at the top right of the go-lzo GitHub [site](https://github.com/anchore/go-lzo/). You will be doing your development in your fork, then submit a pull request to go-lzo. There are many resources how to use GitHub effectively, we will not cover those here. + +## Adding a feature or fix + +If you look at the go-lzo [Issue](https://github.com/anchore/go-lzo/issues) there are plenty of bugs and feature requests. Maybe look at the [good first issue](https://github.com/anchore/go-lzo/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22) list if you're not sure where to start. + +## Commit guidelines + +In the go-lzo project we like commits and pull requests (PR) to be easy to understand and review. Open source thrives best when everything happening is over documented and small enough to be understood. + +### Granular commits + +Please try to make every commit as simple as possible, but no simpler. The idea is that each commit should be a logical unit of code. Try not to commit too many tiny changes, for example every line changed in a file as a separate commit. And also try not to make a commit enormous, for example committing all your work at the end of the day. + +Rather than try to follow a strict guide on what is or is not best, we try to be flexible and simple in this space. Do what makes the most sense for the changes you are trying to include. + +### Commit title and description + +Remember that the message you leave for a commit is for the reviewer in the present, and for someone (maybe you) changing something in the future. Please make sure the title and description used is easy to understand and explains what was done. Jokes and clever comments generally don't age well in commit messages. Just the facts please. + +## Sign off your work + +The `sign-off` is an added line at the end of the explanation for the commit, certifying that you wrote it or otherwise have the right to submit it as an open-source patch. By submitting a contribution, you agree to be bound by the terms of the DCO Version 1.1 and Apache License Version 2.0. + +Signing off a commit certifies the below Developer's Certificate of Origin (DCO): + +```text +Developer's Certificate of Origin 1.1 + +By making a contribution to this project, I certify that: + + (a) The contribution was created in whole or in part by me and I + have the right to submit it under the open source license + indicated in the file; or + + (b) The contribution is based upon previous work that, to the best + of my knowledge, is covered under an appropriate open source + license and I have the right under that license to submit that + work with modifications, whether created in whole or in part + by me, under the same open source license (unless I am + permitted to submit under a different license), as indicated + in the file; or + + (c) The contribution was provided directly to me by some other + person who certified (a), (b) or (c) and I have not modified + it. + + (d) I understand and agree that this project and the contribution + are public and that a record of the contribution (including all + personal information I submit with it, including my sign-off) is + maintained indefinitely and may be redistributed consistent with + this project or the open source license(s) involved. +``` + +All contributions to this project are licensed under the [Apache License Version 2.0, January 2004](http://www.apache.org/licenses/). + +When committing your change, you can add the required line manually so that it looks like this: + +```text +Signed-off-by: John Doe +``` + +Creating a signed-off commit is then possible with `-s` or `--signoff`: + +```text +$ git commit -s -m "this is a commit message" +``` + +To double-check that the commit was signed-off, look at the log output: + +```text +$ git log -1 +commit 37ceh170e4hb283bb73d958f2036ee5k07e7fde7 (HEAD -> issue-35, origin/main, main) +Author: John Doe +Date: Mon Aug 1 11:27:13 2020 -0400 + + this is a commit message + + Signed-off-by: John Doe +``` + +## Test your changes + +This project has a `Makefile` which includes many helpers running both unit and integration tests. You can run `make help` to see all the options. Although PRs will have automatic checks for these, it is useful to run them locally, ensuring they pass before submitting changes. Ensure you've bootstrapped once before running tests: + +```text +$ make bootstrap +``` + +You only need to bootstrap once. After the bootstrap process, you can run the tests as many times as needed: + +```text +$ make unit +$ make integration +``` + +You can also run `make all` to run a more extensive test suite, but there is additional configuration that will be needed for those tests to run correctly. We will not cover the extra steps here. + +## Pull Request + +If you made it this far and all the tests are passing, it's time to submit a Pull Request (PR) for go-lzo. Submitting a PR is always a scary moment as what happens next can be an unknown. The go-lzo project strives to be easy to work with, we appreciate all contributions. Nobody is going to yell at you or try to make you feel bad. We love contributions and know how scary that first PR can be. + +### PR Title and Description + +Just like the commit title and description mentioned above, the PR title and description is very important for letting others know what's happening. Please include any details you think a reviewer will need to more properly review your PR. + +A PR that is very large or poorly described has a higher likelihood of being pushed to the end of the list. Reviewers like PRs they can understand and quickly review. + +### What to expect next + +Please be patient with the project. We try to review PRs in a timely manner, but this is highly dependent on all the other tasks we have going on. It's OK to ask for a status update every week or two, it's not OK to ask for a status update every day. + +It's very likely the reviewer will have questions and suggestions for changes to your PR. If your changes don't match the current style and flow of the other code, expect a request to change what you've done. + +## Document your changes + +And lastly, when proposed changes are modifying user-facing functionality or output, it is expected the PR will include updates to the documentation as well. go-lzo is not a project that is heavy on documentation. This will mostly be updating the README and help for the tool. + +If nobody knows new features exist, they can't use them! diff --git a/vendor/github.com/anchore/go-lzo/DEVELOPING.md b/vendor/github.com/anchore/go-lzo/DEVELOPING.md new file mode 100644 index 00000000000..8fe3fa408fe --- /dev/null +++ b/vendor/github.com/anchore/go-lzo/DEVELOPING.md @@ -0,0 +1,22 @@ +# Developing + +## Getting started + +In order to test and develop in this repo you will need the following dependencies installed: +- golang 1.24+ +- either docker or the lzo C lib & g++ + +To run tests and linters: +- `make unit` +- `make lint-fix` + +Tests depend on either docker (slower but more portable) or having the LZO c lib + headers as well as the g++ toolchain installed (less portable). +The test utilities will attempt to build the native LZO wrapper tool to `testdata/bin`, and if not successful, will fallback to creating a docker image with the same tooling -- there shouldn't be a need to select one or the other specifically. +These utilities are used to create compression golden files in `testdata/cache` used in unit tests. +Dynamic data is also generated within tests, which is saved to `testdata/crash` when a test fails. + +## Attribution considerations + +Note that the [suite of LZO algorithms](https://github.com/nemequ/lzo/blob/master/doc/LZO.TXT) from the [original project](http://www.oberhumer.com/opensource/lzo/) are licensed under GPL v2+. The current implementation was derived from the [Linux kernel documentation for the LZO stream format](https://docs.kernel.org/staging/lzo.html) and the [implementation from `lzokay` project](https://github.com/AxioDL/lzokay) (MIT licensed). +It is vital that any additional features and fixes are done without referencing the original implementation, any pseudocode describing the original implementation, +or any code that is otherwise licensed without a permissive license. diff --git a/vendor/github.com/anchore/go-lzo/LICENSE b/vendor/github.com/anchore/go-lzo/LICENSE new file mode 100644 index 00000000000..56e580e89c7 --- /dev/null +++ b/vendor/github.com/anchore/go-lzo/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Anchore, 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. diff --git a/vendor/github.com/anchore/go-lzo/Makefile b/vendor/github.com/anchore/go-lzo/Makefile new file mode 100644 index 00000000000..a048366a600 --- /dev/null +++ b/vendor/github.com/anchore/go-lzo/Makefile @@ -0,0 +1,43 @@ +TOOL_DIR = .tool +BINNY = $(TOOL_DIR)/binny +TASK = $(TOOL_DIR)/task + +.DEFAULT_GOAL := make-default + +## Bootstrapping targets ################################# + +# note: we need to assume that binny and task have not already been installed +$(BINNY): + @mkdir -p $(TOOL_DIR) + @curl -sSfL https://raw.githubusercontent.com/anchore/binny/main/install.sh | sh -s -- -b $(TOOL_DIR) + +# note: we need to assume that binny and task have not already been installed +.PHONY: task +$(TASK) task: $(BINNY) + @$(BINNY) install task -q + +.PHONY: ci-bootstrap-go +ci-bootstrap-go: + go mod download + +# this is a bootstrapping catch-all, where if the target doesn't exist, we'll ensure the tools are installed and then try again +%: + make $(TASK) + $(TASK) $@ + +## Shim targets ################################# + +.PHONY: make-default +make-default: $(TASK) + @# run the default task in the taskfile + @$(TASK) + +# for those of us that can't seem to kick the habit of typing `make ...` lets wrap the superior `task` tool +TASKS := $(shell bash -c "test -f $(TASK) && $(TASK) -l | grep '^\* ' | cut -d' ' -f2 | tr -d ':' | tr '\n' ' '" ) $(shell bash -c "test -f $(TASK) && $(TASK) -l | grep 'aliases:' | cut -d ':' -f 3 | tr '\n' ' ' | tr -d ','") + +.PHONY: $(TASKS) +$(TASKS): $(TASK) + @$(TASK) $@ + +help: $(TASK) + @$(TASK) -l diff --git a/vendor/github.com/anchore/go-lzo/README.md b/vendor/github.com/anchore/go-lzo/README.md new file mode 100644 index 00000000000..7db3e58101c --- /dev/null +++ b/vendor/github.com/anchore/go-lzo/README.md @@ -0,0 +1,10 @@ +# go-lzo + +This repository provides an implementation of the LZO1X decompression algorithm in Go. +The implementation is derived from the [Linux kernel documentation for the LZO stream format](https://docs.kernel.org/staging/lzo.html) and the [implementation from `lzokay` project](https://github.com/AxioDL/lzokay) (MIT licensed). It includes a `Reader` for streaming decompressed data and a standalone `Decompress` function for use with byte slices. + +To use this library: + +```bash +go get github.com/anchore/go-lzo +``` diff --git a/vendor/github.com/anchore/go-lzo/Taskfile.yaml b/vendor/github.com/anchore/go-lzo/Taskfile.yaml new file mode 100644 index 00000000000..d371ce7a88c --- /dev/null +++ b/vendor/github.com/anchore/go-lzo/Taskfile.yaml @@ -0,0 +1,211 @@ + +version: "3" +vars: + OWNER: anchore + PROJECT: go-lzo + + # static file dirs + TOOL_DIR: .tool + + # used for changelog generation + CHANGELOG: CHANGELOG.md + NEXT_VERSION: VERSION + + # note: the snapshot dir must be a relative path starting with ./ + RELEASE_CMD: "{{ .TOOL_DIR }}/goreleaser release --clean --release-notes {{ .CHANGELOG }}" + +env: + GNUMAKEFLAGS: '--no-print-directory' + +tasks: + + ## High-level tasks ################################# + + default: + desc: Run all validation tasks + aliases: + - pr-validations + - validations + cmds: + - task: static-analysis + - task: test + + static-analysis: + desc: Run all static analysis tasks + cmds: + - task: check-go-mod-tidy + - task: check-licenses + - task: lint + + test: + desc: Run all levels of test + cmds: + - task: unit + + + ## Bootstrap tasks ################################# + + binny: + internal: true + # desc: Get the binny tool + generates: + - "{{ .TOOL_DIR }}/binny" + status: + - "test -f {{ .TOOL_DIR }}/binny" + cmd: "curl -sSfL https://raw.githubusercontent.com/anchore/binny/main/install.sh | sh -s -- -b .tool" + silent: true + + tools: + desc: Install all tools needed for CI and local development + deps: [binny] + aliases: + - bootstrap + generates: + - ".binny.yaml" + - "{{ .TOOL_DIR }}/*" + status: + - "{{ .TOOL_DIR }}/binny check -v" + cmd: "{{ .TOOL_DIR }}/binny install -v" + silent: true + + update-tools: + desc: Update pinned versions of all tools to their latest available versions + deps: [binny] + generates: + - ".binny.yaml" + - "{{ .TOOL_DIR }}/*" + cmd: "{{ .TOOL_DIR }}/binny update -v" + silent: true + + list-tools: + desc: List all tools needed for CI and local development + deps: [binny] + cmd: "{{ .TOOL_DIR }}/binny list" + silent: true + + list-tool-updates: + desc: List all tools that are not up to date relative to the binny config + deps: [binny] + cmd: "{{ .TOOL_DIR }}/binny list --updates" + silent: true + + + ## Static analysis tasks ################################# + + format: + desc: Auto-format all source code + deps: [tools] + cmds: + - gofmt -w -s . + - "{{ .TOOL_DIR }}/gosimports -local github.com/anchore -w ." + - go mod tidy + + lint-fix: + desc: Auto-format all source code + run golangci lint fixers + deps: [tools] + cmds: + - task: format + - "{{ .TOOL_DIR }}/golangci-lint run --tests=false --fix" + + lint: + desc: Run gofmt + golangci lint checks + vars: + BAD_FMT_FILES: + sh: gofmt -l -s . + BAD_FILE_NAMES: + sh: "git ls-files . | grep -e ':' || true" + deps: [tools] + cmds: + # ensure there are no go fmt differences + - cmd: 'test -z "{{ .BAD_FMT_FILES }}" || (echo "files with gofmt issues: [{{ .BAD_FMT_FILES }}]"; exit 1)' + silent: true + # ensure there are no files with ":" in it (a known back case in the go ecosystem) + - cmd: 'test -z "{{ .BAD_FILE_NAMES }}" || (echo "files with bad names: [{{ .BAD_FILE_NAMES }}]"; exit 1)' + silent: true + # run linting + - "{{ .TOOL_DIR }}/golangci-lint run --tests=false" + + check-licenses: + # desc: Ensure transitive dependencies are compliant with the current license policy + deps: [tools] + cmd: "{{ .TOOL_DIR }}/bouncer check ./..." + + check-go-mod-tidy: + # desc: Ensure go.mod and go.sum are up to date + cmds: + - cmd: go mod tidy -diff + + + ## Testing tasks ################################# + + unit: + desc: Run unit tests + cmds: + - "go test ./..." + + ## Test-fixture-related targets ################################# + + fingerprints: + desc: Generate test fixture fingerprints + generates: + - testdata/cache.fingerprint + cmds: + - find *_test.go testdata/Dockerfile testdata/lzo-tool* -type f -exec md5sum {} + | awk '{print $1}' | sort | md5sum | awk '{print $1}' | tee testdata/cache.fingerprint + + show-test-cache: + silent: true + cmds: + - "echo '\nDocker daemon cache:'" + - "docker images --format '{{`{{.ID}}`}} {{`{{.Repository}}`}}:{{`{{.Tag}}`}}' | grep go-lzo-fixture- | sort" + - "echo '\nData cache:'" + - 'find . -type f -wholename "**/testdata/cache/*" | sort' + - "echo '\nTool cache:'" + - 'find . -type f -wholename "**/testdata/bin/*" | sort' + + ## Release targets ################################# + + changelog: + desc: Generate a changelog + deps: [tools] + generates: + - "{{ .CHANGELOG }}" + - "{{ .NEXT_VERSION }}" + cmds: + - "{{ .TOOL_DIR }}/chronicle -vv -n --version-file {{ .NEXT_VERSION }} > {{ .CHANGELOG }}" + - "{{ .TOOL_DIR }}/glow {{ .CHANGELOG }}" + + release: + desc: Create a release + interactive: true + deps: [tools] + cmds: + - cmd: .github/scripts/trigger-release.sh + silent: true + + + ## CI-only targets ################################# + + ci-check: + # desc: "[CI only] Are you in CI?" + requires: + vars: [CI] + + ci-release: + # desc: "[CI only] Create a release" + deps: [tools] + cmds: + - task: ci-check + - "{{ .TOOL_DIR }}/chronicle -vvv > {{ .CHANGELOG }}" + - cmd: "cat {{ .CHANGELOG }}" + silent: true + - "{{ .RELEASE_CMD }}" + + + ## Cleanup targets ################################# + + clean-cache: + desc: Remove all docker cache and local image tar cache + cmds: + - 'find . -type f -wholename "**/testdata/cache/*" -delete' + - 'find . -type f -wholename "**/testdata/bin/*" -delete' + - "docker images --format '{{`{{.ID}}`}} {{`{{.Repository}}`}}' | grep go-lzo-fixture- | awk '{print $$1}' | uniq | xargs -r docker rmi --force" diff --git a/vendor/github.com/anchore/go-lzo/decompress.go b/vendor/github.com/anchore/go-lzo/decompress.go new file mode 100644 index 00000000000..56870abd4d0 --- /dev/null +++ b/vendor/github.com/anchore/go-lzo/decompress.go @@ -0,0 +1,417 @@ +/* +Package lzo is a direct implementation of the LZO decompression algorithm in Go using the following sources as references: + - https://docs.kernel.org/staging/lzo.html (linux kernel documentation) + - https://github.com/AxioDL/lzokay/blob/db2df1fcbebc2ed06c10f727f72567d40f06a2be/lzokay.cpp (lzokay C++ implementation, MIT licensed) +*/ +package lzo + +import ( + "encoding/binary" + "errors" + "runtime" +) + +const ( + hostBigEndian = runtime.GOARCH == "ppc64" || runtime.GOARCH == "s390x" || runtime.GOARCH == "mips" || runtime.GOARCH == "mips64" + max255Count = (^uint(0))/255 - 2 + m3Marker = 0x20 + m4Marker = 0x10 +) + +var ( + ErrLookbehindOverrun = errors.New("lzo: lookbehind overrun") + ErrOutputOverrun = errors.New("lzo: output overrun") + ErrInputOverrun = errors.New("lzo: input overrun") + ErrDecompressionFailed = errors.New("lzo: error during decompression") + ErrInputNotConsumed = errors.New("lzo: input not fully consumed") +) + +// decoder holds all state needed during decompression +type decoder struct { + src, dst buffer // source and destination buffers + curState, nextState int // current state and next state + lbIdx, lbLen int // lookbehind current index and length +} + +// buffer holds a slice and its current index and end boundary +type buffer struct { + data []byte // underlying data slice + idx, end int // current index and end of the buffer +} + +func newDecoder(srcBytes, dstBytes []byte) *decoder { + return &decoder{ + src: newBuffer(srcBytes), + dst: newBuffer(dstBytes), + } +} + +func newBuffer(data []byte) buffer { + return buffer{ + data: data, + idx: 0, + end: len(data), + } +} + +// Decompress the given LZO1X compressed data to the destination buffer. +// +// Here's a summary of the state machine: +// +// Instruction Bits Description +// -------------- ------------ ------------------------------------------------- +// M1 (short) 0x00..0x0F copy 2-3 bytes based on previous literal state +// M1 (long) 0x00..0x0F when state==0, long literal run (>=4 bytes) +// M2 0x40..0xFF copy 3-8 bytes within 2kB distance +// M3 0x20..0x3F copy small block within 16kB distance +// M4 0x10..0x1F copy block within 16..48kB, end-of-stream if distance==16384 +func Decompress(srcBytes, dstBytes []byte) (outSize int, err error) { + d := newDecoder(srcBytes, dstBytes) + + if d.src.end < 3 { + return 0, ErrInputOverrun + } + + if err = d.handleFirstByteEncoding(); err != nil { + return d.dst.idx, err + } + +instructionLoop: + for { + if d.src.idx+1 > d.src.end { + return d.dst.idx, ErrInputOverrun + } + inst := d.src.data[d.src.idx] + d.src.idx++ + switch { + case inst&0xC0 != 0: + if err = d.handleM2(inst); err != nil { + return d.dst.idx, err + } + case inst&m3Marker != 0: + if err = d.handleM3(inst); err != nil { + return d.dst.idx, err + } + case inst&m4Marker != 0: + var finished bool + if finished, err = d.handleM4(inst); err != nil { + return d.dst.idx, err + } + if finished { + break instructionLoop /* stream finished */ + } + default: + /* [M1] Depends on the number of literals copied by the last instruction. */ + switch d.curState { + case 0: + if err = d.handleM1LongLiteral(inst); err != nil { + return d.dst.idx, err + } + d.curState = 4 + continue + default: + if err = d.handleM1ShortCopy(inst); err != nil { + return d.dst.idx, err + } + } + } + + if d.lbIdx < 0 { + return d.dst.idx, ErrLookbehindOverrun + } + if d.src.idx+d.nextState > d.src.end { + return d.dst.idx, ErrInputOverrun + } + if d.dst.idx+d.lbLen+d.nextState > d.dst.end { + return d.dst.idx, ErrOutputOverrun + } + + d.copyLookbehind() + d.copyLiterals(d.nextState) + } + + switch { + case d.lbLen != 3: + /* ensure terminating M4 was encountered */ + return d.dst.idx, ErrDecompressionFailed + case d.src.idx == d.src.end: + return d.dst.idx, nil + case d.src.idx < d.src.end: + return d.dst.idx, ErrInputNotConsumed + default: + return d.dst.idx, ErrInputOverrun + } +} + +//go:inline +func (d *decoder) handleFirstByteEncoding() error { + switch { + case d.src.data[d.src.idx] >= 22: + /* 22..255 : copy literal string + * length = (byte - 17) = 4..238 + * state = 4 [ don't copy extra literals ] + * skip byte + */ + length := int(d.src.data[d.src.idx]) - 17 + d.src.idx++ + if d.src.idx+length > d.src.end { + return ErrInputOverrun + } + if d.dst.idx+length > d.dst.end { + return ErrOutputOverrun + } + d.copyLiterals(length) + d.curState = 4 + + case d.src.data[d.src.idx] >= 18: + /* 18..21 : copy 0..3 literals + * state = (byte - 17) = 0..3 [ copy literals ] + * skip byte + */ + length := int(d.src.data[d.src.idx]) - 17 + d.src.idx++ + if d.src.idx+length > d.src.end { + return ErrInputOverrun + } + if d.dst.idx+length > d.dst.end { + return ErrOutputOverrun + } + d.copyLiterals(length) + d.curState = length + } + /* 0..17 : follow regular instruction encoding, see below. It is worth + * noting that codes 16 and 17 will represent a block copy from + * the dictionary which is empty, and that they will always be + * invalid at this place. + */ + return nil +} + +//go:inline +func (d *decoder) handleM2(inst byte) error { + /* [M2] + * 1 L L D D D S S (128..255) + * Copy 5-8 bytes from block within 2kB distance + * state = S (copy S literals after this block) + * length = 5 + L + * Always followed by exactly one byte : H H H H H H H H + * distance = (H << 3) + D + 1 + * + * 0 1 L D D D S S (64..127) + * Copy 3-4 bytes from block within 2kB distance + * state = S (copy S literals after this block) + * length = 3 + L + * Always followed by exactly one byte : H H H H H H H H + * distance = (H << 3) + D + 1 + */ + if d.src.idx+1 > d.src.end { + return ErrInputOverrun + } + d.lbIdx = d.dst.idx - ((int(d.src.data[d.src.idx]) << 3) + int((inst>>2)&0x7) + 1) + d.src.idx++ + d.lbLen = int(inst>>5) + 1 + d.nextState = int(inst & 0x3) + return nil +} + +//go:inline +func (d *decoder) handleM3(inst byte) error { + /* [M3] + * 0 0 1 L L L L L (32..63) + * Copy of small block within 16kB distance (preferably less than 34B) + * length = 2 + (L ?: 31 + (zero_bytes * 255) + non_zero_byte) + * Always followed by exactly one LE16 : D D D D D D D D : D D D D D D S S + * distance = D + 1 + * state = S (copy S literals after this block) + */ + d.lbLen = int(inst&0x1f) + 2 + if d.lbLen == 2 { + offset, err := d.countZeroBytes(31) + if err != nil { + return err + } + d.lbLen += offset + } + if d.src.idx+2 > d.src.end { + return ErrInputOverrun + } + val := int(d.getLe16()) + d.src.idx += 2 + d.lbIdx = d.dst.idx - (int(val>>2) + 1) + d.nextState = val & 0x3 + return nil +} + +//go:inline +func (d *decoder) handleM4(inst byte) (finished bool, err error) { + /* [M4] + * 0 0 0 1 H L L L (16..31) + * Copy of a block within 16..48kB distance (preferably less than 10B) + * length = 2 + (L ?: 7 + (zero_bytes * 255) + non_zero_byte) + * Always followed by exactly one LE16 : D D D D D D D D : D D D D D D S S + * distance = 16384 + (H << 14) + D + * state = S (copy S literals after this block) + * End of stream is reached if distance == 16384 + */ + d.lbLen = int(inst&0x7) + 2 + if d.lbLen == 2 { + offset, err := d.countZeroBytes(7) + if err != nil { + return false, err + } + d.lbLen += offset + } + + if d.src.idx+2 > d.src.end { + return false, ErrInputOverrun + } + + val := int(d.getLe16()) + d.src.idx += 2 + d.lbIdx = d.dst.idx - (int(inst&0x8)<<11 + int(val>>2)) + d.nextState = val & 0x3 + + if d.lbIdx == d.dst.idx { + finished = true /* stream finished */ + return finished, nil + } + + d.lbIdx -= 16384 + + return false, nil +} + +//go:inline +func (d *decoder) handleM1LongLiteral(inst byte) error { + /* If last instruction did not copy any literal (state == 0), this + * encoding will be a copy of 4 or more literal, and must be interpreted + * like this : + * + * 0 0 0 0 L L L L (0..15) : copy long literal string + * length = 3 + (L ?: 15 + (zero_bytes * 255) + non_zero_byte) + * state = 4 (no extra literals are copied) + */ + length := int(inst) + 3 + if length == 3 { + offset, err := d.countZeroBytes(15) + if err != nil { + return err + } + + length += offset + } + + if d.src.idx+length > d.src.end { + return ErrInputOverrun + } + + if d.dst.idx+length > d.dst.end { + return ErrOutputOverrun + } + + d.copyLiterals(length) + + return nil +} + +//go:inline +func (d *decoder) handleM1ShortCopy(inst byte) error { + switch { + case d.curState != 4: + /* If last instruction used to copy between 1 to 3 literals (encoded in + * the instruction's opcode or distance), the instruction is a copy of a + * 2-byte block from the dictionary within a 1kB distance. It is worth + * noting that this instruction provides little savings since it uses 2 + * bytes to encode a copy of 2 other bytes but it encodes the number of + * following literals for free. It must be interpreted like this : + * + * 0 0 0 0 D D S S (0..15) : copy 2 bytes from <= 1kB distance + * length = 2 + * state = S (copy S literals after this block) + * Always followed by exactly one byte : H H H H H H H H + * distance = (H << 2) + D + 1 + */ + + if d.src.idx+1 > d.src.end { + return ErrInputOverrun + } + + d.nextState = int(inst & 0x3) + d.lbIdx = d.dst.idx - (int(inst>>2) + (int(d.src.data[d.src.idx]) << 2) + 1) + d.src.idx++ + d.lbLen = 2 + + default: + /* If last instruction used to copy 4 or more literals (as detected by + * state == 4), the instruction becomes a copy of a 3-byte block from the + * dictionary from a 2..3kB distance, and must be interpreted like this : + * + * 0 0 0 0 D D S S (0..15) : copy 3 bytes from 2..3 kB distance + * length = 3 + * state = S (copy S literals after this block) + * Always followed by exactly one byte : H H H H H H H H + * distance = (H << 2) + D + 2049 + */ + + if d.src.idx+1 > d.src.end { + return ErrInputOverrun + } + + d.nextState = int(inst & 0x3) + d.lbIdx = d.dst.idx - (int(inst>>2) + (int(d.src.data[d.src.idx]) << 2) + 2049) + d.src.idx++ + d.lbLen = 3 + } + return nil +} + +//go:inline +func (d *decoder) copyLiterals(length int) { + // note: benchmarking shows that this is faster than using copy() + for i := 0; i < length; i++ { + d.dst.data[d.dst.idx+i] = d.src.data[d.src.idx+i] + } + d.dst.idx += length + d.src.idx += length +} + +//go:inline +func (d *decoder) copyLookbehind() { + // note: benchmarking shows that this is faster than using copy() + for i := 0; i < d.lbLen; i++ { + d.dst.data[d.dst.idx+i] = d.dst.data[d.lbIdx+i] + } + d.dst.idx += d.lbLen + d.curState = d.nextState +} + +//go:inline +func (d *decoder) countZeroBytes(factor int) (int, error) { + old := d.src.idx + // count how many zero bytes we have until we hit a non-zero byte + for d.src.idx < d.src.end && d.src.data[d.src.idx] == 0 { + d.src.idx++ + } + + count := d.src.idx - old + + if count > int(max255Count) { + return -1, ErrDecompressionFailed + } + if d.src.idx+1 > d.src.end { + return -1, ErrInputOverrun + } + offset := count*255 + factor + int(d.src.data[d.src.idx]) + d.src.idx++ + + return offset, nil +} + +//go:inline +func (d *decoder) getLe16() uint16 { + if hostBigEndian { + // swap bytes for big endian + return uint16(d.src.data[d.src.idx]) | (uint16(d.src.data[d.src.idx+1]) << 8) + } + return binary.LittleEndian.Uint16(d.src.data[d.src.idx : d.src.idx+2]) +} diff --git a/vendor/github.com/anchore/go-lzo/reader.go b/vendor/github.com/anchore/go-lzo/reader.go new file mode 100644 index 00000000000..751e841aae2 --- /dev/null +++ b/vendor/github.com/anchore/go-lzo/reader.go @@ -0,0 +1,131 @@ +package lzo + +import ( + "io" +) + +// Reader implements io.Reader for LZO-compressed data +type Reader struct { + src io.Reader + decompBuf []byte // buffer to hold decompressed data + readPos int // current read position in decompBuf + srcBuf []byte // buffer to accumulate compressed input + eof bool // whether we've reached end of compressed stream + blockSize int // size of decompression buffer +} + +// NewReader creates a new LZO reader with default 64KB buffer +func NewReader(src io.Reader) *Reader { + return NewReaderSize(src, 64*1024) +} + +// NewReaderSize creates a new LZO reader with specified buffer size +func NewReaderSize(src io.Reader, blockSize int) *Reader { + return &Reader{ + src: src, + blockSize: blockSize, + srcBuf: make([]byte, 0, blockSize), + decompBuf: make([]byte, 0, blockSize), + } +} + +func (r *Reader) Read(p []byte) (n int, err error) { + if len(p) == 0 { + return 0, nil + } + + // if we have data in our decompression buffer, serve from there first + if r.readPos < len(r.decompBuf) { + n = copy(p, r.decompBuf[r.readPos:]) + r.readPos += n + if r.readPos == len(r.decompBuf) { + // reset buffer when fully consumed + r.decompBuf = r.decompBuf[:0] + r.readPos = 0 + } + return n, nil + } + + // if we've reached EOF and have no more buffered data + if r.eof { + return 0, io.EOF + } + + // try to decompress the next block + err = r.decompressNextBlock() + if err != nil { + if err == io.EOF { + r.eof = true + } + return 0, err + } + + // now serve from the newly decompressed data + if len(r.decompBuf) > 0 { + n = copy(p, r.decompBuf[r.readPos:]) + r.readPos += n + if r.readPos == len(r.decompBuf) { + // reset buffer when fully consumed + r.decompBuf = r.decompBuf[:0] + r.readPos = 0 + } + return n, nil + } + + return 0, io.EOF +} + +// decompressNextBlock reads and decompresses the next LZO block +func (r *Reader) decompressNextBlock() error { + // read more compressed data if needed + + // TODO: should we handle LZO block headers/framing format here? + + // try to fill source buffer + if len(r.srcBuf) < 3 { // need at least 3 bytes for valid LZO stream + tempBuf := make([]byte, r.blockSize) + n, err := r.src.Read(tempBuf) + if n > 0 { + r.srcBuf = append(r.srcBuf, tempBuf[:n]...) + } + if err != nil { + if err == io.EOF && len(r.srcBuf) == 0 { + return io.EOF + } + if err != io.EOF { + return err + } + } + } + + // if we still don't have enough data, return EOF + if len(r.srcBuf) < 3 { + return io.EOF + } + + // create output buffer for decompression + outputBuf := make([]byte, r.blockSize) + + // decompress the entire remaining source buffer + // TODO: this assumes the entire srcBuf contains one LZO stream... for framed formats, you'd need to parse block boundaries + decompSize, err := Decompress(r.srcBuf, outputBuf) + if err != nil { + return err + } + + // store decompressed data + r.decompBuf = outputBuf[:decompSize] + r.readPos = 0 + + // clear source buffer since we consumed it all + r.srcBuf = r.srcBuf[:0] + + return nil +} + +func (r *Reader) Close() error { + if closer, ok := r.src.(io.Closer); ok { + return closer.Close() + } + return nil +} diff --git a/vendor/github.com/diskfs/go-diskfs/.gitignore b/vendor/github.com/diskfs/go-diskfs/.gitignore index 48b8bf9072d..203fca1fe9e 100644 --- a/vendor/github.com/diskfs/go-diskfs/.gitignore +++ b/vendor/github.com/diskfs/go-diskfs/.gitignore @@ -1 +1,88 @@ +# File created using '.gitignore Generator' for Visual Studio Code: https://bit.ly/vscode-gig +# Created by https://www.toptal.com/developers/gitignore/api/visualstudiocode,macos,go +# Edit at https://www.toptal.com/developers/gitignore?templates=visualstudiocode,macos,go + +### Go ### +# If you prefer the allow list template instead of the deny list, see community template: +# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore +# +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# IDE specific files +.idea/ + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ + +# Go workspace file +go.work + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### macOS Patch ### +# iCloud generated files +*.icloud + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix + +### VisualStudioCode Patch ### +# Ignore all local history of files +.history +.ionide + +# End of https://www.toptal.com/developers/gitignore/api/visualstudiocode,macos,go + +# Custom rules (everything added below won't be overriden by 'Generate .gitignore File' if you use 'Update' option) + vendor/ +tmp/ diff --git a/vendor/github.com/diskfs/go-diskfs/.golangci.yml b/vendor/github.com/diskfs/go-diskfs/.golangci.yml index 5434216b268..457548b118a 100644 --- a/vendor/github.com/diskfs/go-diskfs/.golangci.yml +++ b/vendor/github.com/diskfs/go-diskfs/.golangci.yml @@ -1,53 +1,72 @@ -linters-settings: - errcheck: - check-type-assertions: true - goconst: - min-len: 2 - min-occurrences: 3 - gocritic: - enabled-tags: - - diagnostic - - experimental - - opinionated - - performance - - style - govet: - check-shadowing: false - nolintlint: - require-explanation: true - require-specific: true - +version: "2" +run: + issues-exit-code: 1 linters: - disable-all: true + default: none enable: - bodyclose + - copyloopvar - dogsled - dupl - errcheck - - exportloopref - exhaustive - gocritic - - gofmt - - goimports - gocyclo - gosec - - gosimple - govet - ineffassign - misspell - - nolintlint - nakedret + - nolintlint - prealloc - predeclared - revive - staticcheck - - stylecheck - thelper - tparallel - - typecheck - unconvert - unparam - whitespace - -run: - issues-exit-code: 1 + settings: + errcheck: + check-type-assertions: true + goconst: + min-len: 2 + min-occurrences: 3 + gocritic: + enabled-tags: + - diagnostic + - experimental + - opinionated + - performance + - style + gosec: + excludes: + - G115 + govet: + disable: + - shadow + nolintlint: + require-explanation: true + require-specific: true + exclusions: + generated: lax + presets: + - comments + - common-false-positives + - legacy + - std-error-handling + paths: + - third_party$ + - builtin$ + - examples$ +formatters: + enable: + - gofmt + - goimports + exclusions: + generated: lax + paths: + - third_party$ + - builtin$ + - examples$ diff --git a/vendor/github.com/diskfs/go-diskfs/Makefile b/vendor/github.com/diskfs/go-diskfs/Makefile index 24bbc2f446a..8f7932d4011 100644 --- a/vendor/github.com/diskfs/go-diskfs/Makefile +++ b/vendor/github.com/diskfs/go-diskfs/Makefile @@ -6,7 +6,7 @@ GOENV ?= GO111MODULE=on CGO_ENABLED=0 GO_FILES ?= $(shell $(GOENV) go list ./...) GOBIN ?= $(shell go env GOPATH)/bin LINTER ?= $(GOBIN)/golangci-lint -LINTER_VERSION ?= v1.55.2 +LINTER_VERSION ?= v2.5.0 # BUILDARCH is the host architecture # ARCH is the target architecture diff --git a/vendor/github.com/diskfs/go-diskfs/README.md b/vendor/github.com/diskfs/go-diskfs/README.md index e230077b823..51b5ee9da5e 100644 --- a/vendor/github.com/diskfs/go-diskfs/README.md +++ b/vendor/github.com/diskfs/go-diskfs/README.md @@ -16,10 +16,18 @@ Note: detailed go documentation is available at [godoc.org](https://godoc.org/gi ### Concepts `go-diskfs` has a few basic concepts: +* Backend * Disk * Partition * Filesystem +#### Backend +Backend is a (relatively) thin layer which abstracts low-level read/write operations. Through a backend you can seamlessly operate different disk formats. + +Currently there is only one implementation - file. + +Use `file` backend to access block devices and raw image files. + #### Disk A disk represents either a file or block device that you access and manipulate. With access to the disk, you can: diff --git a/vendor/github.com/diskfs/go-diskfs/backend/file/file.go b/vendor/github.com/diskfs/go-diskfs/backend/file/file.go new file mode 100644 index 00000000000..ec7912e00ed --- /dev/null +++ b/vendor/github.com/diskfs/go-diskfs/backend/file/file.go @@ -0,0 +1,127 @@ +package file + +import ( + "errors" + "fmt" + "io" + "io/fs" + "os" + + "github.com/diskfs/go-diskfs/backend" +) + +type rawBackend struct { + storage fs.File + readOnly bool +} + +// Create a backend.Storage from provided fs.File +func New(f fs.File, readOnly bool) backend.Storage { + return rawBackend{ + storage: f, + readOnly: readOnly, + } +} + +// Create a backend.Storage from a path to a device +// Should pass a path to a block device e.g. /dev/sda or a path to a file /tmp/foo.img +// The provided device/file must exist at the time you call OpenFromPath() +func OpenFromPath(pathName string, readOnly bool) (backend.Storage, error) { + if pathName == "" { + return nil, errors.New("must pass device of file name") + } + + if _, err := os.Stat(pathName); os.IsNotExist(err) { + return nil, fmt.Errorf("provided device/file %s does not exist", pathName) + } + + openMode := os.O_RDONLY + + if !readOnly { + openMode |= os.O_RDWR | os.O_EXCL + } + + f, err := os.OpenFile(pathName, openMode, 0o600) + if err != nil { + return nil, fmt.Errorf("could not open device %s with mode %v: %w", pathName, openMode, err) + } + + return rawBackend{ + storage: f, + readOnly: readOnly, + }, nil +} + +// Create a backend.Storage from a path to an image file. +// Should pass a path to a file /tmp/foo.img +// The provided file must not exist at the time you call CreateFromPath() +func CreateFromPath(pathName string, size int64) (backend.Storage, error) { + if pathName == "" { + return nil, errors.New("must pass device name") + } + if size <= 0 { + return nil, errors.New("must pass valid device size to create") + } + f, err := os.OpenFile(pathName, os.O_RDWR|os.O_EXCL|os.O_CREATE, 0o666) + if err != nil { + return nil, fmt.Errorf("could not create device %s: %w", pathName, err) + } + err = os.Truncate(pathName, size) + if err != nil { + return nil, fmt.Errorf("could not expand device %s to size %d: %w", pathName, size, err) + } + + return rawBackend{ + storage: f, + readOnly: false, + }, nil +} + +// backend.Storage interface guard +var _ backend.Storage = (*rawBackend)(nil) + +// OS-specific file for ioctl calls via fd +func (f rawBackend) Sys() (*os.File, error) { + if osFile, ok := f.storage.(*os.File); ok { + return osFile, nil + } + return nil, backend.ErrNotSuitable +} + +// file for read-write operations +func (f rawBackend) Writable() (backend.WritableFile, error) { + if rwFile, ok := f.storage.(backend.WritableFile); ok { + if !f.readOnly { + return rwFile, nil + } + + return nil, backend.ErrIncorrectOpenMode + } + return nil, backend.ErrNotSuitable +} + +func (f rawBackend) Stat() (fs.FileInfo, error) { + return f.storage.Stat() +} + +func (f rawBackend) Read(b []byte) (int, error) { + return f.storage.Read(b) +} + +func (f rawBackend) Close() error { + return f.storage.Close() +} + +func (f rawBackend) ReadAt(p []byte, off int64) (n int, err error) { + if readerAt, ok := f.storage.(io.ReaderAt); ok { + return readerAt.ReadAt(p, off) + } + return -1, backend.ErrNotSuitable +} + +func (f rawBackend) Seek(offset int64, whence int) (int64, error) { + if seeker, ok := f.storage.(io.Seeker); ok { + return seeker.Seek(offset, whence) + } + return -1, backend.ErrNotSuitable +} diff --git a/vendor/github.com/diskfs/go-diskfs/backend/interface.go b/vendor/github.com/diskfs/go-diskfs/backend/interface.go new file mode 100644 index 00000000000..21e7b341bb0 --- /dev/null +++ b/vendor/github.com/diskfs/go-diskfs/backend/interface.go @@ -0,0 +1,33 @@ +package backend + +import ( + "errors" + "io" + "io/fs" + "os" +) + +var ( + ErrIncorrectOpenMode = errors.New("disk file or device not open for write") + ErrNotSuitable = errors.New("backing file is not suitable") +) + +type File interface { + fs.File + io.ReaderAt + io.Seeker + io.Closer +} + +type WritableFile interface { + File + io.WriterAt +} + +type Storage interface { + File + // OS-specific file for ioctl calls via fd + Sys() (*os.File, error) + // file for read-write operations + Writable() (WritableFile, error) +} diff --git a/vendor/github.com/diskfs/go-diskfs/backend/substorage.go b/vendor/github.com/diskfs/go-diskfs/backend/substorage.go new file mode 100644 index 00000000000..a0aa5f6c1dc --- /dev/null +++ b/vendor/github.com/diskfs/go-diskfs/backend/substorage.go @@ -0,0 +1,127 @@ +package backend + +import ( + "io" + "io/fs" + "os" +) + +type SubStorage struct { + underlying Storage + offset int64 + size int64 +} + +func Sub(u Storage, offset, size int64) Storage { + return SubStorage{ + underlying: u, + offset: offset, + size: size, + } +} + +func (s SubStorage) Stat() (fs.FileInfo, error) { + return s.underlying.Stat() +} + +func (s SubStorage) Read(bytes []byte) (int, error) { + return s.underlying.Read(bytes) +} + +func (s SubStorage) Close() error { + return s.underlying.Close() +} + +func (s SubStorage) ReadAt(p []byte, off int64) (n int, err error) { + return s.underlying.ReadAt(p, s.offset+off) +} + +func (s SubStorage) Seek(offset int64, whence int) (int64, error) { + var ( + pos int64 + err error + ) + + switch whence { + case io.SeekStart: + pos, err = s.underlying.Seek(offset+s.offset, io.SeekStart) + case io.SeekCurrent: + pos, err = s.underlying.Seek(offset, io.SeekCurrent) + case io.SeekEnd: + pos, err = s.underlying.Seek(s.offset+s.size+offset, io.SeekStart) + default: + return -1, ErrNotSuitable + } + + if err != nil { + return -1, err + } + + return pos - s.offset, nil +} + +func (s SubStorage) Sys() (*os.File, error) { + return s.underlying.Sys() +} + +func (s SubStorage) Writable() (WritableFile, error) { + uw, err := s.underlying.Writable() + if err != nil { + return nil, err + } + return subWritable{ + underlying: uw, + offset: s.offset, + size: s.size, + }, nil +} + +type subWritable struct { + underlying WritableFile + offset int64 + size int64 +} + +func (sw subWritable) Stat() (fs.FileInfo, error) { + return sw.underlying.Stat() +} + +func (sw subWritable) Read(b []byte) (int, error) { + return sw.underlying.Read(b) +} + +func (sw subWritable) Close() error { + return sw.underlying.Close() +} + +func (sw subWritable) ReadAt(p []byte, off int64) (n int, err error) { + return sw.underlying.ReadAt(p, sw.offset+off) +} + +func (sw subWritable) Seek(offset int64, whence int) (int64, error) { + var ( + pos int64 + err error + ) + + switch whence { + case io.SeekStart: + pos, err = sw.underlying.Seek(offset+sw.offset, io.SeekStart) + case io.SeekCurrent: + pos, err = sw.underlying.Seek(offset, io.SeekCurrent) + case io.SeekEnd: + pos, err = sw.underlying.Seek(sw.offset+sw.size+offset, io.SeekStart) + default: + return -1, ErrNotSuitable + } + + if err != nil { + return -1, err + } + + return pos - sw.offset, nil +} + +func (sw subWritable) WriteAt(p []byte, off int64) (n int, err error) { + return sw.underlying.WriteAt(p, sw.offset+off) +} diff --git a/vendor/github.com/diskfs/go-diskfs/disk/disk.go b/vendor/github.com/diskfs/go-diskfs/disk/disk.go index 796565b864b..8daf52f84a3 100644 --- a/vendor/github.com/diskfs/go-diskfs/disk/disk.go +++ b/vendor/github.com/diskfs/go-diskfs/disk/disk.go @@ -8,28 +8,24 @@ import ( "errors" "fmt" "io" - "os" - - log "github.com/sirupsen/logrus" + "github.com/diskfs/go-diskfs/backend" "github.com/diskfs/go-diskfs/filesystem" "github.com/diskfs/go-diskfs/filesystem/ext4" "github.com/diskfs/go-diskfs/filesystem/fat32" "github.com/diskfs/go-diskfs/filesystem/iso9660" "github.com/diskfs/go-diskfs/filesystem/squashfs" "github.com/diskfs/go-diskfs/partition" + log "github.com/sirupsen/logrus" ) // Disk is a reference to a single disk block device or image that has been Create() or Open() type Disk struct { - File *os.File - Info os.FileInfo - Type Type + Backend backend.Storage Size int64 LogicalBlocksize int64 PhysicalBlocksize int64 Table partition.Table - Writable bool DefaultBlocks bool } @@ -43,17 +39,13 @@ const ( Device ) -var ( - errIncorrectOpenMode = errors.New("disk file or device not open for write") -) - // GetPartitionTable retrieves a PartitionTable for a Disk // // If the table is able to be retrieved from the disk, it is saved in the instance. // // returns an error if the Disk is invalid or does not exist, or the partition table is unknown func (d *Disk) GetPartitionTable() (partition.Table, error) { - t, err := partition.Read(d.File, int(d.LogicalBlocksize), int(d.PhysicalBlocksize)) + t, err := partition.Read(d.Backend, int(d.LogicalBlocksize), int(d.PhysicalBlocksize)) if err != nil { return nil, err } @@ -68,24 +60,19 @@ func (d *Disk) GetPartitionTable() (partition.Table, error) { // // Actual writing of the table is delegated to the individual implementation func (d *Disk) Partition(table partition.Table) error { - if !d.Writable { - return errIncorrectOpenMode + rwBackingFile, err := d.Backend.Writable() + if err != nil { + return err } + // fill in the uuid - err := table.Write(d.File, d.Size) + err = table.Write(rwBackingFile, d.Size) if err != nil { return fmt.Errorf("failed to write partition table: %v", err) } d.Table = table - // the partition table needs to be re-read only if - // the disk file is an actual block device - if d.Type == Device { - err = d.ReReadPartitionTable() - if err != nil { - return fmt.Errorf("unable to re-read the partition table. Kernel still uses old partition table: %v", err) - } - } - return nil + + return d.ReReadPartitionTable() } // WritePartitionContents writes the contents of an io.Reader to a given partition @@ -95,8 +82,10 @@ func (d *Disk) Partition(table partition.Table) error { // returns an error if there was an error writing to the disk, reading from the reader, the table // is invalid, or the partition is invalid func (d *Disk) WritePartitionContents(part int, reader io.Reader) (int64, error) { - if !d.Writable { - return -1, errIncorrectOpenMode + backingRwFile, err := d.Backend.Writable() + + if err != nil { + return -1, err } if d.Table == nil { return -1, fmt.Errorf("cannot write contents of a partition on a disk without a partition table") @@ -109,7 +98,7 @@ func (d *Disk) WritePartitionContents(part int, reader io.Reader) (int64, error) if part > len(partitions) { return -1, fmt.Errorf("cannot write contents of partition %d which is greater than max partition %d", part, len(partitions)) } - written, err := partitions[part-1].WriteContents(d.File, reader) + written, err := partitions[part-1].WriteContents(backingRwFile, reader) return int64(written), err } @@ -131,7 +120,7 @@ func (d *Disk) ReadPartitionContents(part int, writer io.Writer) (int64, error) if part > len(partitions) { return -1, fmt.Errorf("cannot read contents of partition %d which is greater than max partition %d", part, len(partitions)) } - return partitions[part-1].ReadContents(d.File, writer) + return partitions[part-1].ReadContents(d.Backend, writer) } // FilesystemSpec represents the specification of a filesystem to be created @@ -162,9 +151,8 @@ func (d *Disk) CreateFilesystem(spec FilesystemSpec) (filesystem.FileSystem, err var ( size, start int64 ) + switch { - case !d.Writable: - return nil, errIncorrectOpenMode case spec.Partition == 0: size = d.Size start = 0 @@ -183,13 +171,13 @@ func (d *Disk) CreateFilesystem(spec FilesystemSpec) (filesystem.FileSystem, err switch spec.FSType { case filesystem.TypeFat32: - return fat32.Create(d.File, size, start, d.LogicalBlocksize, spec.VolumeLabel) + return fat32.Create(d.Backend, size, start, d.LogicalBlocksize, spec.VolumeLabel) case filesystem.TypeISO9660: - return iso9660.Create(d.File, size, start, d.LogicalBlocksize, spec.WorkDir) + return iso9660.Create(d.Backend, size, start, d.LogicalBlocksize, spec.WorkDir) case filesystem.TypeExt4: - return ext4.Create(d.File, size, start, d.LogicalBlocksize, nil) + return ext4.Create(d.Backend, size, start, d.LogicalBlocksize, &ext4.Params{VolumeName: spec.VolumeLabel}) case filesystem.TypeSquashfs: - return nil, errors.New("squashfs is a read-only filesystem") + return squashfs.Create(d.Backend, size, start, d.LogicalBlocksize) default: return nil, errors.New("unknown filesystem type requested") } @@ -228,7 +216,7 @@ func (d *Disk) GetFilesystem(part int) (filesystem.FileSystem, error) { // just try each type log.Debug("trying fat32") - fat32FS, err := fat32.Read(d.File, size, start, d.LogicalBlocksize) + fat32FS, err := fat32.Read(d.Backend, size, start, d.LogicalBlocksize) if err == nil { return fat32FS, nil } @@ -238,17 +226,17 @@ func (d *Disk) GetFilesystem(part int) (filesystem.FileSystem, error) { pbs = 0 } log.Debugf("trying iso9660 with physical block size %d", pbs) - iso9660FS, err := iso9660.Read(d.File, size, start, pbs) + iso9660FS, err := iso9660.Read(d.Backend, size, start, pbs) if err == nil { return iso9660FS, nil } log.Debugf("iso9660 failed: %v", err) - squashFS, err := squashfs.Read(d.File, size, start, d.LogicalBlocksize) + squashFS, err := squashfs.Read(d.Backend, size, start, d.LogicalBlocksize) if err == nil { return squashFS, nil } log.Debug("trying ext4") - ext4FS, err := ext4.Read(d.File, size, start, d.LogicalBlocksize) + ext4FS, err := ext4.Read(d.Backend, size, start, d.LogicalBlocksize) if err == nil { return ext4FS, nil } @@ -258,7 +246,7 @@ func (d *Disk) GetFilesystem(part int) (filesystem.FileSystem, error) { // Close the disk. Once successfully closed, it can no longer be used. func (d *Disk) Close() error { - if err := d.File.Close(); err != nil { + if err := d.Backend.Close(); err != nil { return err } *d = Disk{} diff --git a/vendor/github.com/diskfs/go-diskfs/disk/disk_unix.go b/vendor/github.com/diskfs/go-diskfs/disk/disk_unix.go index aba635c432f..1900f880b6b 100644 --- a/vendor/github.com/diskfs/go-diskfs/disk/disk_unix.go +++ b/vendor/github.com/diskfs/go-diskfs/disk/disk_unix.go @@ -5,6 +5,7 @@ package disk import ( "fmt" + "os" "golang.org/x/sys/unix" ) @@ -18,10 +19,24 @@ const ( // // It is done via an ioctl call with request as BLKRRPART. func (d *Disk) ReReadPartitionTable() error { - fd := d.File.Fd() - _, err := unix.IoctlGetInt(int(fd), blkrrpart) + // the partition table needs to be re-read only if + // the disk file is an actual block device + devInfo, err := d.Backend.Stat() if err != nil { - return fmt.Errorf("unable to re-read partition table: %v", err) + return err } + + if devInfo.Mode()&os.ModeDevice != 0 { + osFile, err := d.Backend.Sys() + if err != nil { + return err + } + fd := osFile.Fd() + _, err = unix.IoctlGetInt(int(fd), blkrrpart) + if err != nil { + return fmt.Errorf("unable to re-read the partition table. Kernel still uses old partition table: %v", err) + } + } + return nil } diff --git a/vendor/github.com/diskfs/go-diskfs/diskfs.go b/vendor/github.com/diskfs/go-diskfs/diskfs.go index f0952a09650..3b342210fe4 100644 --- a/vendor/github.com/diskfs/go-diskfs/diskfs.go +++ b/vendor/github.com/diskfs/go-diskfs/diskfs.go @@ -8,98 +8,6 @@ // This is not intended as a replacement for operating system filesystem and disk drivers. Instead, // it is intended to make it easy to work with partitions, partition tables and filesystems directly // without requiring operating system mounts. -// -// Some examples: -// -// 1. Create a disk image of size 10MB with a FAT32 filesystem spanning the entire disk. -// -// import diskfs "github.com/diskfs/go-diskfs" -// size := 10*1024*1024 // 10 MB -// -// diskImg := "/tmp/disk.img" -// disk := diskfs.Create(diskImg, size, diskfs.Raw, diskfs.SectorSizeDefault) -// -// fs, err := disk.CreateFilesystem(0, diskfs.TypeFat32) -// -// 2. Create a disk of size 20MB with an MBR partition table, a single partition beginning at block 2048 (1MB), -// of size 10MB filled with a FAT32 filesystem. -// -// import diskfs "github.com/diskfs/go-diskfs" -// -// diskSize := 10*1024*1024 // 10 MB -// -// diskImg := "/tmp/disk.img" -// disk := diskfs.Create(diskImg, size, diskfs.Raw, diskfs.SectorSizeDefault) -// -// table := &mbr.Table{ -// LogicalSectorSize: 512, -// PhysicalSectorSize: 512, -// Partitions: []*mbr.Partition{ -// { -// Bootable: false, -// Type: Linux, -// Start: 2048, -// Size: 20480, -// }, -// }, -// } -// -// fs, err := disk.CreateFilesystem(1, diskfs.TypeFat32) -// -// 3. Create a disk of size 20MB with a GPT partition table, a single partition beginning at block 2048 (1MB), -// of size 10MB, and fill with the contents from the 10MB file "/root/contents.dat" -// -// import diskfs "github.com/diskfs/go-diskfs" -// -// diskSize := 10*1024*1024 // 10 MB -// -// diskImg := "/tmp/disk.img" -// disk := diskfs.Create(diskImg, size, diskfs.Raw, diskfs.SectorSizeDefault) -// -// table := &gpt.Table{ -// LogicalSectorSize: 512, -// PhysicalSectorSize: 512, -// Partitions: []*gpt.Partition{ -// { -// LogicalSectorSize: 512, -// PhysicalSectorSize: 512, -// ProtectiveMBR: true, -// }, -// }, -// } -// -// f, err := os.Open("/root/contents.dat") -// written, err := disk.WritePartitionContents(1, f) -// -// 4. Create a disk of size 20MB with an MBR partition table, a single partition beginning at block 2048 (1MB), -// of size 10MB filled with a FAT32 filesystem, and create some directories and files in that filesystem. -// -// import diskfs "github.com/diskfs/go-diskfs" -// -// diskSize := 10*1024*1024 // 10 MB -// -// diskImg := "/tmp/disk.img" -// disk := diskfs.Create(diskImg, size, diskfs.Raw, diskfs.SectorSizeDefault) -// -// table := &mbr.Table{ -// LogicalSectorSize: 512, -// PhysicalSectorSize: 512, -// Partitions: []*mbr.Partition{ -// { -// Bootable: false, -// Type: Linux, -// Start: 2048, -// Size: 20480, -// }, -// }, -// } -// -// fs, err := disk.CreateFilesystem(1, diskfs.TypeFat32) -// err := fs.Mkdir("/FOO/BAR") -// rw, err := fs.OpenFile("/FOO/BAR/AFILE.EXE", os.O_CREATE|os.O_RDRWR) -// b := make([]byte, 1024, 1024) -// rand.Read(b) -// err := rw.Write(b) package diskfs import ( @@ -109,6 +17,8 @@ import ( log "github.com/sirupsen/logrus" + "github.com/diskfs/go-diskfs/backend" + "github.com/diskfs/go-diskfs/backend/file" "github.com/diskfs/go-diskfs/disk" ) @@ -122,14 +32,6 @@ const ( // blkpbszGet = 0x127b ) -// Format represents the format of the disk -type Format int - -const ( - // Raw disk format for basic raw disk - Raw Format = iota -) - // OpenModeOption represents file open modes type OpenModeOption int @@ -185,15 +87,13 @@ func writableMode(mode OpenModeOption) bool { return false } -func initDisk(f *os.File, openMode OpenModeOption, sectorSize SectorSize) (*disk.Disk, error) { +func initDisk(b backend.Storage, sectorSize SectorSize) (*disk.Disk, error) { + log.Debug("initDisk(): start") + var ( - diskType disk.Type - size int64 - lblksize = int64(defaultBlocksize) - pblksize = int64(defaultBlocksize) - defaultBlocks = true + lblksize = int64(defaultBlocksize) + pblksize = int64(defaultBlocksize) ) - log.Debug("initDisk(): start") if sectorSize != SectorSizeDefault { lblksize = int64(sectorSize) @@ -201,61 +101,65 @@ func initDisk(f *os.File, openMode OpenModeOption, sectorSize SectorSize) (*disk } // get device information - devInfo, err := f.Stat() + devInfo, err := b.Stat() if err != nil { - return nil, fmt.Errorf("could not get info for device %s: %v", f.Name(), err) + return nil, fmt.Errorf("could not get info for device %s: %v", devInfo.Name(), err) + } + + newDisk := &disk.Disk{ + Backend: b, + Size: devInfo.Size(), + LogicalBlocksize: lblksize, + PhysicalBlocksize: pblksize, + DefaultBlocks: true, } + mode := devInfo.Mode() switch { case mode.IsRegular(): log.Debug("initDisk(): regular file") - diskType = disk.File - size = devInfo.Size() - if size <= 0 { - return nil, fmt.Errorf("could not get file size for device %s", f.Name()) + if newDisk.Size <= 0 { + return nil, fmt.Errorf("could not get file size for device %s", devInfo.Name()) } case mode&os.ModeDevice != 0: log.Debug("initDisk(): block device") - diskType = disk.Device - size, err = getBlockDeviceSize(f) + osFile, err := newDisk.Backend.Sys() if err != nil { - return nil, fmt.Errorf("error getting block device %s size: %s", f.Name(), err) + return nil, backend.ErrNotSuitable } - lblksize, pblksize, err = getSectorSizes(f) - log.Debugf("initDisk(): logical block size %d, physical block size %d", lblksize, pblksize) - defaultBlocks = false - if err != nil { - return nil, fmt.Errorf("unable to get block sizes for device %s: %v", f.Name(), err) + + //nolint:revive // revive thinks we can drop the 'else' statement, but we need it to capture the size + if size, err := getBlockDeviceSize(osFile); err != nil { + return nil, fmt.Errorf("error getting block device %s size: %s", devInfo.Name(), err) + } else { + newDisk.Size = size } + + //nolint:revive // revive thinks we can drop the 'else' statement, but we need it to capture the size + if lblksize, pblksize, err = getSectorSizes(osFile); err != nil { + return nil, fmt.Errorf("unable to get block sizes for device %s: %v", devInfo.Name(), err) + } else { + log.Debugf("initDisk(): logical block size %d, physical block size %d", lblksize, pblksize) + + newDisk.LogicalBlocksize = lblksize + newDisk.PhysicalBlocksize = pblksize + newDisk.DefaultBlocks = false + } + default: - return nil, fmt.Errorf("device %s is neither a block device nor a regular file", f.Name()) + return nil, fmt.Errorf("device %s is neither a block device nor a regular file", devInfo.Name()) } // how many good blocks do we have? // var goodBlocks, orphanedBlocks int // goodBlocks = size / lblksize - writable := writableMode(openMode) - - ret := &disk.Disk{ - File: f, - Info: devInfo, - Type: diskType, - Size: size, - LogicalBlocksize: lblksize, - PhysicalBlocksize: pblksize, - Writable: writable, - DefaultBlocks: defaultBlocks, - } - // try to initialize the partition table. - // we ignore errors, because it is perfectly fine to open a disk - // and use it before it has a partition table. This is solely - // a convenience. - if table, err := ret.GetPartitionTable(); err == nil && table != nil { - ret.Table = table - } - return ret, nil + //nolint:errcheck // we ignore errors, because it is perfectly fine to open a disk and use it before it has a + // partition table. This is solely a convenience. + newDisk.GetPartitionTable() + + return newDisk, nil } func checkDevice(device string) error { @@ -302,6 +206,7 @@ func WithSectorSize(sectorSize SectorSize) OpenOpt { } } +// Might be deprecated in future: use .New + diskfs.OpenBackend // Open a Disk from a path to a device in read-write exclusive mode // Should pass a path to a block device e.g. /dev/sda or a path to a file /tmp/foo.img // The provided device must exist at the time you call Open(). @@ -328,28 +233,37 @@ func Open(device string, opts ...OpenOpt) (*disk.Disk, error) { if err != nil { return nil, fmt.Errorf("could not open device %s with mode %v: %w", device, m, err) } + // return our disk - return initDisk(f, ReadWriteExclusive, opt.sectorSize) + return initDisk(file.New(f, !writableMode(opt.mode)), opt.sectorSize) } +// Open a Disk using provided fs.File to a device in read-only mode +// Use OpenOpt to control options, such as sector size or open mode. +func OpenBackend(b backend.Storage, opts ...OpenOpt) (*disk.Disk, error) { + opt := &openOpts{ + mode: ReadOnly, + sectorSize: SectorSizeDefault, + } + + for _, o := range opts { + if err := o(opt); err != nil { + return nil, err + } + } + + return initDisk(b, opt.sectorSize) +} + +// Might be deprecated in future: use .CreateFromPath + diskfs.OpenBackend // Create a Disk from a path to a device // Should pass a path to a block device e.g. /dev/sda or a path to a file /tmp/foo.img // The provided device must not exist at the time you call Create() -func Create(device string, size int64, _ Format, sectorSize SectorSize) (*disk.Disk, error) { - if device == "" { - return nil, errors.New("must pass device name") - } - if size <= 0 { - return nil, errors.New("must pass valid device size to create") - } - f, err := os.OpenFile(device, os.O_RDWR|os.O_EXCL|os.O_CREATE, 0o666) - if err != nil { - return nil, fmt.Errorf("could not create device %s: %w", device, err) - } - err = os.Truncate(device, size) +func Create(device string, size int64, sectorSize SectorSize) (*disk.Disk, error) { + rawBackend, err := file.CreateFromPath(device, size) if err != nil { - return nil, fmt.Errorf("could not expand device %s to size %d: %w", device, size, err) + return nil, err } // return our disk - return initDisk(f, ReadWriteExclusive, sectorSize) + return initDisk(rawBackend, sectorSize) } diff --git a/vendor/github.com/diskfs/go-diskfs/filesystem/ext4/blockgroup.go b/vendor/github.com/diskfs/go-diskfs/filesystem/ext4/blockgroup.go index bf3b426d9af..fa21fa194aa 100644 --- a/vendor/github.com/diskfs/go-diskfs/filesystem/ext4/blockgroup.go +++ b/vendor/github.com/diskfs/go-diskfs/filesystem/ext4/blockgroup.go @@ -3,15 +3,15 @@ package ext4 import ( "fmt" - "github.com/diskfs/go-diskfs/util" + "github.com/diskfs/go-diskfs/util/bitmap" ) // blockGroup is a structure holding the data about a single block group // //nolint:unused // will be used in the future, not yet type blockGroup struct { - inodeBitmap *util.Bitmap - blockBitmap *util.Bitmap + inodeBitmap *bitmap.Bitmap + blockBitmap *bitmap.Bitmap blockSize int number int inodeTableSize int @@ -28,8 +28,8 @@ func blockGroupFromBytes(b []byte, blockSize, groupNumber int) (*blockGroup, err if actualSize != expectedSize { return nil, fmt.Errorf("expected to be passed %d bytes for 2 blocks of size %d, instead received %d", expectedSize, blockSize, actualSize) } - inodeBitmap := util.BitmapFromBytes(b[0:blockSize]) - blockBitmap := util.BitmapFromBytes(b[blockSize : 2*blockSize]) + inodeBitmap := bitmap.FromBytes(b[0:blockSize]) + blockBitmap := bitmap.FromBytes(b[blockSize : 2*blockSize]) bg := blockGroup{ inodeBitmap: inodeBitmap, diff --git a/vendor/github.com/diskfs/go-diskfs/filesystem/ext4/checksum.go b/vendor/github.com/diskfs/go-diskfs/filesystem/ext4/checksum.go index d7ffea43749..b84d28420f0 100644 --- a/vendor/github.com/diskfs/go-diskfs/filesystem/ext4/checksum.go +++ b/vendor/github.com/diskfs/go-diskfs/filesystem/ext4/checksum.go @@ -48,3 +48,8 @@ func directoryChecksumAppender(seed, inodeNumber, inodeGeneration uint32) checks func nullDirectoryChecksummer(b []byte) []byte { return b } + +// bitmapChecksum calculate the checksum for a bitmap +func bitmapChecksum(b []byte, hashSeed uint32) uint32 { + return crc.CRC32c(hashSeed, b) +} diff --git a/vendor/github.com/diskfs/go-diskfs/filesystem/ext4/ext4.go b/vendor/github.com/diskfs/go-diskfs/filesystem/ext4/ext4.go index b322dd7356d..ef37ab90305 100644 --- a/vendor/github.com/diskfs/go-diskfs/filesystem/ext4/ext4.go +++ b/vendor/github.com/diskfs/go-diskfs/filesystem/ext4/ext4.go @@ -13,9 +13,10 @@ import ( "strings" "time" + "github.com/diskfs/go-diskfs/backend" "github.com/diskfs/go-diskfs/filesystem" "github.com/diskfs/go-diskfs/filesystem/ext4/crc" - "github.com/diskfs/go-diskfs/util" + "github.com/diskfs/go-diskfs/util/bitmap" "github.com/google/uuid" ) @@ -55,8 +56,8 @@ const ( max32Num uint64 = math.MaxUint32 max64Num uint64 = math.MaxUint64 - maxFilesystemSize32Bit uint64 = 16*2 ^ 40 - maxFilesystemSize64Bit uint64 = 1*2 ^ 60 + maxFilesystemSize32Bit uint64 = 16 << 40 + maxFilesystemSize64Bit uint64 = 1 << 60 checksumType uint8 = 1 @@ -97,12 +98,12 @@ type FileSystem struct { blockGroups int64 size int64 start int64 - file util.File + backend backend.Storage } // Equal compare if two filesystems are equal func (fs *FileSystem) Equal(a *FileSystem) bool { - localMatch := fs.file == a.file + localMatch := fs.backend == a.backend sbMatch := fs.superblock.equal(a.superblock) gdMatch := fs.groupDescriptors.equal(a.groupDescriptors) return localMatch && sbMatch && gdMatch @@ -110,9 +111,14 @@ func (fs *FileSystem) Equal(a *FileSystem) bool { // Create creates an ext4 filesystem in a given file or device // -// requires the util.File where to create the filesystem, size is the size of the filesystem in bytes, -// start is how far in bytes from the beginning of the util.File to create the filesystem, -// and blocksize is is the logical blocksize to use for creating the filesystem +// requires the backend.Storage where to create the filesystem, size is the size of the filesystem in bytes, +// start is how far in bytes from the beginning of the backend.Storage to create the filesystem, +// and sectorsize is is the logical sector size to use for creating the filesystem +// +// blocksize is the size of the ext4 blocks, and is calculated as sectorsPerBlock * sectorsize. +// By ext4 specification, it must be between 512 and 4096 bytes, +// where sectorsize is the provided parameter, and sectorsPerBlock is part of `p *Params`. +// If either sectorsize or p.SectorsPerBlock is 0, it will calculate the optimal size for both. // // note that you are *not* required to create the filesystem on the entire disk. You could have a disk of size // 20GB, and create a small filesystem of size 50MB that begins 2GB into the disk. @@ -126,7 +132,7 @@ func (fs *FileSystem) Equal(a *FileSystem) bool { // or 512, it will return an error. // //nolint:gocyclo // yes, this has high cyclomatic complexity, but we can accept it -func Create(f util.File, size, start, sectorsize int64, p *Params) (*FileSystem, error) { +func Create(b backend.Storage, size, start, sectorsize int64, p *Params) (*FileSystem, error) { // be safe about the params pointer if p == nil { p = &Params{} @@ -137,6 +143,9 @@ func Create(f util.File, size, start, sectorsize int64, p *Params) (*FileSystem, if sectorsize != int64(SectorSize512) && sectorsize > 0 { return nil, fmt.Errorf("sectorsize for ext4 must be either 512 bytes or 0, not %d", sectorsize) } + if sectorsize == 0 { + sectorsize = int64(SectorSize512) + } var sectorsize32 = uint32(sectorsize) // there almost are no limits on an ext4 fs - theoretically up to 1 YB // but we do have to check the max and min size per the requested parameters @@ -156,12 +165,16 @@ func Create(f util.File, size, start, sectorsize int64, p *Params) (*FileSystem, // blocksize sectorsPerBlock := p.SectorsPerBlock - userProvidedBlocksize := false + // whether or not the user provided a blocksize + // if they did, we will stick with it, as long as it is valid. + // if they did not, then we are free to calculate it + var userProvidedBlocksize bool switch { + case sectorsPerBlock == 0: + sectorsPerBlock = 2 + userProvidedBlocksize = false case sectorsPerBlock > 128 || sectorsPerBlock < 2: return nil, fmt.Errorf("invalid sectors per block %d, must be between %d and %d sectors", sectorsPerBlock, 2, 128) - case sectorsPerBlock < 1: - sectorsPerBlock = 2 default: userProvidedBlocksize = true } @@ -525,21 +538,31 @@ func Create(f util.File, size, start, sectorsize int64, p *Params) (*FileSystem, projectQuotaInode: projectQuotaInode, logGroupsPerFlex: uint64(logGroupsPerFlex), } - gdt := groupDescriptors{} + gdt := buildGroupDescriptorsFromSuperblock(&sb) + // allocate root in the first group descriptor + bg0 := &gdt.descriptors[0] + bg0.usedDirectories++ + bg0.freeInodes-- + g := gdt.toBytes(gdtChecksumType, sb.checksumSeed) - b, err := sb.toBytes() + superblockBytes, err := sb.toBytes() if err != nil { return nil, fmt.Errorf("error converting Superblock to bytes: %v", err) } - g := gdt.toBytes(gdtChecksumType, sb.checksumSeed) // how big should the GDT be? - gdSize = groupDescriptorSize + gdSize = groupDescriptorSize // size of a single group descriptor if sb.features.fs64Bit { gdSize = groupDescriptorSize64Bit } - gdtSize := int64(gdSize) * numblocks + // now calculate how many there should be in total + groupCount := sb.blockGroupCount() + gdtSize := uint64(gdSize) * groupCount // write the superblock and GDT to the various locations on disk + + // Make SubStorage Backend + fsBackend := backend.Sub(b, start, size) + for _, bg := range backupSuperblocks { block := bg * int64(blocksPerGroup) blockStart := block * int64(blocksize) @@ -549,8 +572,13 @@ func Create(f util.File, size, start, sectorsize int64, p *Params) (*FileSystem, incr = int64(SectorSize512) * 2 } + writable, err := fsBackend.Writable() + if err != nil { + return nil, err + } + // write the superblock - count, err := f.WriteAt(b, incr+blockStart+start) + count, err := writable.WriteAt(superblockBytes, incr+blockStart) if err != nil { return nil, fmt.Errorf("error writing Superblock for block %d to disk: %v", block, err) } @@ -559,7 +587,7 @@ func Create(f util.File, size, start, sectorsize int64, p *Params) (*FileSystem, } // write the GDT - count, err = f.WriteAt(g, incr+blockStart+int64(SuperblockSize)+start) + count, err = writable.WriteAt(g, incr+blockStart+int64(SuperblockSize)) if err != nil { return nil, fmt.Errorf("error writing GDT for block %d to disk: %v", block, err) } @@ -577,14 +605,14 @@ func Create(f util.File, size, start, sectorsize int64, p *Params) (*FileSystem, blockGroups: blockGroups, size: size, start: start, - file: f, + backend: fsBackend, }, nil } // Read reads a filesystem from a given disk. // -// requires the util.File where to read the filesystem, size is the size of the filesystem in bytes, -// start is how far in bytes from the beginning of the util.File the filesystem is expected to begin, +// requires the backend.File where to read the filesystem, size is the size of the filesystem in bytes, +// start is how far in bytes from the beginning of the backend.File the filesystem is expected to begin, // and blocksize is is the logical blocksize to use for creating the filesystem // // note that you are *not* required to read a filesystem on the entire disk. You could have a disk of size @@ -597,7 +625,7 @@ func Create(f util.File, size, start, sectorsize int64, p *Params) (*FileSystem, // // If the provided blocksize is 0, it will use the default of 512 bytes. If it is any number other than 0 // or 512, it will return an error. -func Read(file util.File, size, start, sectorsize int64) (*FileSystem, error) { +func Read(b backend.Storage, size, start, sectorsize int64) (*FileSystem, error) { // blocksize must be <=0 or exactly SectorSize512 or error if sectorsize != int64(SectorSize512) && sectorsize > 0 { return nil, fmt.Errorf("sectorsize for ext4 must be either 512 bytes or 0, not %d", sectorsize) @@ -607,10 +635,13 @@ func Read(file util.File, size, start, sectorsize int64) (*FileSystem, error) { return nil, fmt.Errorf("requested size is smaller than minimum allowed ext4 size %d", Ext4MinSize) } + // Make SubStorage Backend + fsBackend := backend.Sub(b, start, size) + // load the information from the disk // read boot sector code bs := make([]byte, BootSectorSize) - n, err := file.ReadAt(bs, start) + n, err := fsBackend.ReadAt(bs, 0) if err != nil { return nil, fmt.Errorf("could not read boot sector bytes from file: %v", err) } @@ -621,7 +652,7 @@ func Read(file util.File, size, start, sectorsize int64) (*FileSystem, error) { // read the superblock // the superblock is one minimal block, i.e. 2 sectors superblockBytes := make([]byte, SuperblockSize) - n, err = file.ReadAt(superblockBytes, start+int64(BootSectorSize)) + n, err = fsBackend.ReadAt(superblockBytes, int64(BootSectorSize)) if err != nil { return nil, fmt.Errorf("could not read superblock bytes from file: %v", err) } @@ -639,6 +670,10 @@ func Read(file util.File, size, start, sectorsize int64) (*FileSystem, error) { // how big should the GDT be? gdtSize := uint64(sb.groupDescriptorSize) * sb.blockGroupCount() + if gdtSize == 0 { + return nil, errors.New("calculated Group Descriptor Table size is zero") + } + gdtBytes := make([]byte, gdtSize) // where do we find the GDT? // - if blocksize is 1024, then 1024 padding for BootSector is block 0, 1024 for superblock is block 1 @@ -649,7 +684,7 @@ func Read(file util.File, size, start, sectorsize int64) (*FileSystem, error) { if sb.blockSize == 1024 { gdtBlock = 2 } - n, err = file.ReadAt(gdtBytes, start+int64(gdtBlock)*int64(sb.blockSize)) + n, err = fsBackend.ReadAt(gdtBytes, int64(gdtBlock)*int64(sb.blockSize)) if err != nil { return nil, fmt.Errorf("could not read Group Descriptor Table bytes from file: %v", err) } @@ -668,10 +703,18 @@ func Read(file util.File, size, start, sectorsize int64) (*FileSystem, error) { blockGroups: int64(sb.blockGroupCount()), size: size, start: start, - file: file, + backend: fsBackend, }, nil } +// interface guard +var _ filesystem.FileSystem = (*FileSystem)(nil) + +// Do cleaning job for ext4. Note that ext4 does not have side-effects so we do not do anything. +func (fs *FileSystem) Close() error { + return nil +} + // Type returns the type code for the filesystem. Always returns filesystem.TypeExt4 func (fs *FileSystem) Type() filesystem.Type { return filesystem.TypeExt4 @@ -687,6 +730,44 @@ func (fs *FileSystem) Mkdir(p string) error { return err } +// creates a filesystem node (file, device special file, or named pipe) named pathname, +// with attributes specified by mode and dev +// +//nolint:revive // parameters will be used eventually +func (fs *FileSystem) Mknod(pathname string, mode uint32, dev int) error { + return filesystem.ErrNotImplemented +} + +// creates a new link (also known as a hard link) to an existing file. +// +//nolint:revive // parameters will be used eventually +func (fs *FileSystem) Link(oldpath, newpath string) error { + return filesystem.ErrNotImplemented +} + +// creates a symbolic link named linkpath which contains the string target. +// +//nolint:revive // parameters will be used eventually +func (fs *FileSystem) Symlink(oldpath, newpath string) error { + return filesystem.ErrNotImplemented +} + +// Chmod changes the mode of the named file to mode. If the file is a symbolic link, +// it changes the mode of the link's target. +// +//nolint:revive // parameters will be used eventually +func (fs *FileSystem) Chmod(name string, mode os.FileMode) error { + return filesystem.ErrNotImplemented +} + +// Chown changes the numeric uid and gid of the named file. If the file is a symbolic link, +// it changes the uid and gid of the link's target. A uid or gid of -1 means to not change that value +// +//nolint:revive // parameters will be used eventually +func (fs *FileSystem) Chown(name string, uid, gid int) error { + return filesystem.ErrNotImplemented +} + // ReadDir return the contents of a given directory in a given filesystem. // // Returns a slice of os.FileInfo with all of the entries in the directory. @@ -796,12 +877,30 @@ func (fs *FileSystem) Label() string { return fs.superblock.volumeLabel } -// Rm remove file or directory at path. +// Rename renames (moves) oldpath to newpath. If newpath already exists and is not a directory, Rename replaces it. +// +//nolint:revive // parameters will be used eventually +func (fs *FileSystem) Rename(oldpath, newpath string) error { + return filesystem.ErrNotImplemented +} + +// Deprecated: use filesystem.Remove(p string) instead +func (fs *FileSystem) Rm(p string) error { + return fs.Remove(p) +} + +// Removes file or directory at path. // If path is directory, it only will remove if it is empty. // If path is a file, it will remove the file. // Will not remove any parents. // Error if the file does not exist or is not an empty directory -func (fs *FileSystem) Rm(p string) error { +// +//nolint:gocyclo // yes, this has high cyclomatic complexity, but we can accept it +func (fs *FileSystem) Remove(p string) error { + gdtBlock := 1 + if fs.superblock.blockSize == 1024 { + gdtBlock = 2 + } parentDir, entry, err := fs.getEntryAndParent(p) if err != nil { return err @@ -812,6 +911,12 @@ func (fs *FileSystem) Rm(p string) error { if entry == nil { return fmt.Errorf("file does not exist: %s", p) } + + writableFile, err := fs.backend.Writable() + + if err != nil { + return err + } // if it is a directory, it must be empty if entry.fileType == dirFileTypeDirectory { // read the directory @@ -835,15 +940,12 @@ func (fs *FileSystem) Rm(p string) error { if err != nil { return fmt.Errorf("could not read extents for inode %d for %s: %v", entry.inode, p, err) } - // clear the inode from the inode bitmap - inodeBG := blockGroupForInode(int(entry.inode), fs.superblock.inodesPerGroup) - inodeBitmap, err := fs.readInodeBitmap(inodeBG) - if err != nil { - return fmt.Errorf("could not read inode bitmap: %v", err) - } // clear up the blocks from the block bitmap. We are not clearing the block content, just the bitmap. // keep a cache of bitmaps, so we do not have to read them again and again - blockBitmaps := make(map[int]*util.Bitmap) + blockBitmaps := make(map[int]*bitmap.Bitmap) + freedByBG := make(map[int]uint32) + var totalFreed uint64 + for _, e := range extents { for i := e.startingBlock; i < e.startingBlock+uint64(e.count); i++ { // determine what block group this block is in, and read the bitmap for that blockgroup @@ -861,12 +963,22 @@ func (fs *FileSystem) Rm(p string) error { if err := dataBlockBitmap.Clear(blockInBG); err != nil { return fmt.Errorf("could not clear block bitmap for block %d: %v", i, err) } + freedByBG[bg]++ + totalFreed++ } } for bg, dataBlockBitmap := range blockBitmaps { if err := fs.writeBlockBitmap(dataBlockBitmap, bg); err != nil { return fmt.Errorf("could not write block bitmap back to disk: %v", err) } + gd := fs.groupDescriptors.descriptors[bg] + // Increment free blocks by actual filesystem blocks we just cleared in THIS group + gd.freeBlocks += freedByBG[bg] + gd.blockBitmapChecksum = bitmapChecksum(dataBlockBitmap.ToBytes(), fs.superblock.checksumSeed) + gdBytes := gd.toBytes(fs.superblock.gdtChecksumType(), fs.superblock.checksumSeed) + if _, err := writableFile.WriteAt(gdBytes, int64(gdtBlock)*int64(fs.superblock.blockSize)+int64(gd.number)*int64(fs.superblock.groupDescriptorSize)); err != nil { + return fmt.Errorf("could not write Group Descriptor bytes to file: %v", err) + } } // remove the directory entry from the parent @@ -879,7 +991,10 @@ func (fs *FileSystem) Rm(p string) error { } parentDir.entries = newEntries // write the parent directory back - dirBytes := parentDir.toBytes(fs.superblock.blockSize, directoryChecksumAppender(fs.superblock.checksumSeed, parentDir.inode, 0)) + dirBytes := parentDir.toBytes( + fs.superblock.blockSize, + directoryChecksumAppender(fs.superblock.checksumSeed, parentDir.inode, 0), + ) parentInode, err := fs.readInode(parentDir.inode) if err != nil { return fmt.Errorf("could not read inode %d for %s: %v", entry.inode, path.Base(p), err) @@ -888,39 +1003,69 @@ func (fs *FileSystem) Rm(p string) error { if err != nil { return fmt.Errorf("could not read extents for inode %d for %s: %v", entry.inode, path.Base(p), err) } + // write the directory bytes back to the blocks, ensure block-aligned + bs := int(fs.superblock.blockSize) + written := 0 for _, e := range extents { for i := 0; i < int(e.count); i++ { - b := dirBytes[i:fs.superblock.blockSize] - if _, err := fs.file.WriteAt(b, (int64(i)+int64(e.startingBlock))*int64(fs.superblock.blockSize)); err != nil { + if written >= len(dirBytes) { + break + } + start := written + end := start + bs + if end > len(dirBytes) { + end = len(dirBytes) + } + b := dirBytes[start:end] + + fileOff := (int64(e.startingBlock) + int64(i)) * int64(bs) + + if _, err := writableFile.WriteAt(b, fileOff); err != nil { return fmt.Errorf("could not write inode bitmap back to disk: %v", err) } + // If the last block is short, zero-pad the remainder up to block size + if len(b) < bs { + zeros := make([]byte, bs-len(b)) + if _, err := writableFile.WriteAt(zeros, fileOff+int64(len(b))); err != nil { + return fmt.Errorf("could not pad directory block: %w", err) + } + } + written += bs } } + // clear the inode from the inode bitmap + inodeBG := blockGroupForInode(int(entry.inode), fs.superblock.inodesPerGroup) + inodeBitmap, err := fs.readInodeBitmap(inodeBG) + if err != nil { + return fmt.Errorf("could not read inode bitmap: %v", err) + } + // remove the inode from the bitmap and write the inode bitmap back // inode is absolute, but bitmap is relative to block group inodeInBG := int(entry.inode) - int(fs.superblock.inodesPerGroup)*inodeBG if err := inodeBitmap.Clear(inodeInBG); err != nil { return fmt.Errorf("could not clear inode bitmap for inode %d: %v", entry.inode, err) } - // write the inode bitmap back if err := fs.writeInodeBitmap(inodeBitmap, inodeBG); err != nil { return fmt.Errorf("could not write inode bitmap back to disk: %v", err) } - // update the group descriptor + + // Update the group descriptor: free inode count, free block count, used directory count; recompute checksums, and write GD gd := fs.groupDescriptors.descriptors[inodeBG] // update the group descriptor inodes and blocks gd.freeInodes++ gd.freeBlocks += uint32(removedInode.blocks) - // write the group descriptor back - gdBytes := gd.toBytes(fs.superblock.gdtChecksumType(), fs.superblock.uuid.ID()) - gdtBlock := 1 - if fs.superblock.blockSize == 1024 { - gdtBlock = 2 + if entry.fileType == dirFileTypeDirectory { + gd.usedDirectories-- } - if _, err := fs.file.WriteAt(gdBytes, fs.start+int64(gdtBlock)*int64(fs.superblock.blockSize)+int64(gd.number)*int64(fs.superblock.groupDescriptorSize)); err != nil { + gd.inodeBitmapChecksum = bitmapChecksum(inodeBitmap.ToBytes(), fs.superblock.checksumSeed) + + // write the group descriptor back + gdBytes := gd.toBytes(fs.superblock.gdtChecksumType(), fs.superblock.checksumSeed) + if _, err := writableFile.WriteAt(gdBytes, int64(gdtBlock)*int64(fs.superblock.blockSize)+int64(gd.number)*int64(fs.superblock.groupDescriptorSize)); err != nil { return fmt.Errorf("could not write Group Descriptor bytes to file: %v", err) } @@ -1035,7 +1180,7 @@ func (fs *FileSystem) readInode(inodeNumber uint32) (*inode, error) { offsetInode := (inodeNumber - 1) % inodesPerGroup // offset is how many bytes in our inode is offset := offsetInode * uint32(inodeSize) - read, err := fs.file.ReadAt(inodeBytes, int64(byteStart)+int64(offset)) + read, err := fs.backend.ReadAt(inodeBytes, int64(byteStart)+int64(offset)) if err != nil { return nil, fmt.Errorf("failed to read inode %d from offset %d of block %d from block group %d: %v", inodeNumber, offset, inodeTableBlock, bg, err) } @@ -1064,6 +1209,12 @@ func (fs *FileSystem) readInode(inodeNumber uint32) (*inode, error) { // writeInode write a single inode to disk func (fs *FileSystem) writeInode(i *inode) error { + writableFile, err := fs.backend.Writable() + + if err != nil { + return err + } + sb := fs.superblock inodeSize := sb.inodeSize inodesPerGroup := sb.inodesPerGroup @@ -1082,7 +1233,7 @@ func (fs *FileSystem) writeInode(i *inode) error { // offset is how many bytes in our inode is offset := int64(offsetInode) * int64(inodeSize) inodeBytes := i.toBytes(sb) - wrote, err := fs.file.WriteAt(inodeBytes, int64(byteStart)+offset) + wrote, err := writableFile.WriteAt(inodeBytes, int64(byteStart)+offset) if err != nil { return fmt.Errorf("failed to write inode %d at offset %d of block %d from block group %d: %v", i.number, offset, inodeTableBlock, bg, err) } @@ -1144,7 +1295,7 @@ func (fs *FileSystem) readFileBytes(extents extents, filesize uint64) ([]byte, e count = filesize - uint64(len(b)) } b2 := make([]byte, count) - read, err := fs.file.ReadAt(b2, int64(start)) + read, err := fs.backend.ReadAt(b2, int64(start)) if err != nil { return nil, fmt.Errorf("failed to read bytes for extent %d: %v", i, err) } @@ -1240,7 +1391,7 @@ func (fs *FileSystem) readBlock(blockNumber uint64) ([]byte, error) { // bytesStart is beginning byte for the inodeTableBlock byteStart := blockNumber * uint64(sb.blockSize) blockBytes := make([]byte, sb.blockSize) - read, err := fs.file.ReadAt(blockBytes, int64(byteStart)) + read, err := fs.backend.ReadAt(blockBytes, int64(byteStart)) if err != nil { return nil, fmt.Errorf("failed to read block %d: %v", blockNumber, err) } @@ -1463,6 +1614,11 @@ func (fs *FileSystem) allocateInode(parent uint32) (uint32, error) { gd groupDescriptor ) + writableFile, err := fs.backend.Writable() + if err != nil { + return 0, err + } + for _, gd = range fs.groupDescriptors.descriptors { if inodeNumber != -1 { break @@ -1495,14 +1651,14 @@ func (fs *FileSystem) allocateInode(parent uint32) (uint32, error) { gd.freeInodes-- // get the group descriptor as bytes - gdBytes := gd.toBytes(fs.superblock.gdtChecksumType(), fs.superblock.uuid.ID()) + gdBytes := gd.toBytes(fs.superblock.gdtChecksumType(), fs.superblock.checksumSeed) // write the group descriptor bytes // gdt starts in block 1 of any redundant copies, specifically in BG 0 gdtBlock := 1 blockByteLocation := gdtBlock * int(fs.superblock.blockSize) - gdOffset := fs.start + int64(blockByteLocation) + int64(bg)*int64(fs.superblock.groupDescriptorSize) - wrote, err := fs.file.WriteAt(gdBytes, gdOffset) + gdOffset := int64(blockByteLocation) + int64(bg)*int64(fs.superblock.groupDescriptorSize) + wrote, err := writableFile.WriteAt(gdBytes, gdOffset) if err != nil { return 0, fmt.Errorf("unable to write group descriptor bytes for blockgroup %d: %v", bg, err) } @@ -1552,7 +1708,7 @@ func (fs *FileSystem) allocateExtents(size uint64, previous *extents) (*extents, // TODO: instead of starting with BG 0, should start with BG where the inode for this file/dir is located var ( newExtents []extent - datablockBitmaps = map[int]*util.Bitmap{} + datablockBitmaps = map[int]*bitmap.Bitmap{} blocksPerGroup = fs.superblock.blocksPerGroup ) @@ -1645,7 +1801,7 @@ func (fs *FileSystem) allocateExtents(size uint64, previous *extents) (*extents, // readInodeBitmap read the inode bitmap off the disk. // This would be more efficient if we just read one group descriptor's bitmap // but for now we are about functionality, not efficiency, so it will read the whole thing. -func (fs *FileSystem) readInodeBitmap(group int) (*util.Bitmap, error) { +func (fs *FileSystem) readInodeBitmap(group int) (*bitmap.Bitmap, error) { if group >= len(fs.groupDescriptors.descriptors) { return nil, fmt.Errorf("block group %d does not exist", group) } @@ -1653,8 +1809,8 @@ func (fs *FileSystem) readInodeBitmap(group int) (*util.Bitmap, error) { bitmapLocation := gd.inodeBitmapLocation bitmapByteCount := fs.superblock.inodesPerGroup / 8 b := make([]byte, bitmapByteCount) - offset := int64(bitmapLocation*uint64(fs.superblock.blockSize) + uint64(fs.start)) - read, err := fs.file.ReadAt(b, offset) + offset := int64(bitmapLocation * uint64(fs.superblock.blockSize)) + read, err := fs.backend.ReadAt(b, offset) if err != nil { return nil, fmt.Errorf("unable to read inode bitmap for blockgroup %d: %w", gd.number, err) } @@ -1664,22 +1820,26 @@ func (fs *FileSystem) readInodeBitmap(group int) (*util.Bitmap, error) { // only take bytes corresponding to the number of inodes per group // create a bitmap - bs := util.NewBitmap(int(fs.superblock.blockSize) * len(fs.groupDescriptors.descriptors)) + bs := bitmap.New(int(fs.superblock.blockSize) * len(fs.groupDescriptors.descriptors)) bs.FromBytes(b) return bs, nil } // writeInodeBitmap write the inode bitmap to the disk. -func (fs *FileSystem) writeInodeBitmap(bm *util.Bitmap, group int) error { +func (fs *FileSystem) writeInodeBitmap(bm *bitmap.Bitmap, group int) error { if group >= len(fs.groupDescriptors.descriptors) { return fmt.Errorf("block group %d does not exist", group) } + writableFile, err := fs.backend.Writable() + if err != nil { + return err + } b := bm.ToBytes() gd := fs.groupDescriptors.descriptors[group] bitmapByteCount := fs.superblock.inodesPerGroup / 8 bitmapLocation := gd.inodeBitmapLocation - offset := int64(bitmapLocation*uint64(fs.superblock.blockSize) + uint64(fs.start)) - wrote, err := fs.file.WriteAt(b, offset) + offset := int64(bitmapLocation * uint64(fs.superblock.blockSize)) + wrote, err := writableFile.WriteAt(b, offset) if err != nil { return fmt.Errorf("unable to write inode bitmap for blockgroup %d: %w", gd.number, err) } @@ -1690,15 +1850,15 @@ func (fs *FileSystem) writeInodeBitmap(bm *util.Bitmap, group int) error { return nil } -func (fs *FileSystem) readBlockBitmap(group int) (*util.Bitmap, error) { +func (fs *FileSystem) readBlockBitmap(group int) (*bitmap.Bitmap, error) { if group >= len(fs.groupDescriptors.descriptors) { return nil, fmt.Errorf("block group %d does not exist", group) } gd := fs.groupDescriptors.descriptors[group] bitmapLocation := gd.blockBitmapLocation b := make([]byte, fs.superblock.blockSize) - offset := int64(bitmapLocation*uint64(fs.superblock.blockSize) + uint64(fs.start)) - read, err := fs.file.ReadAt(b, offset) + offset := int64(bitmapLocation * uint64(fs.superblock.blockSize)) + read, err := fs.backend.ReadAt(b, offset) if err != nil { return nil, fmt.Errorf("unable to read block bitmap for blockgroup %d: %w", gd.number, err) } @@ -1706,21 +1866,25 @@ func (fs *FileSystem) readBlockBitmap(group int) (*util.Bitmap, error) { return nil, fmt.Errorf("Read %d bytes instead of expected %d for block bitmap of block group %d", read, fs.superblock.blockSize, gd.number) } // create a bitmap - bs := util.NewBitmap(int(fs.superblock.blockSize) * len(fs.groupDescriptors.descriptors)) + bs := bitmap.New(int(fs.superblock.blockSize) * len(fs.groupDescriptors.descriptors)) bs.FromBytes(b) return bs, nil } // writeBlockBitmap write the inode bitmap to the disk. -func (fs *FileSystem) writeBlockBitmap(bm *util.Bitmap, group int) error { +func (fs *FileSystem) writeBlockBitmap(bm *bitmap.Bitmap, group int) error { if group >= len(fs.groupDescriptors.descriptors) { return fmt.Errorf("block group %d does not exist", group) } + writableFile, err := fs.backend.Writable() + if err != nil { + return err + } b := bm.ToBytes() gd := fs.groupDescriptors.descriptors[group] bitmapLocation := gd.blockBitmapLocation - offset := int64(bitmapLocation*uint64(fs.superblock.blockSize) + uint64(fs.start)) - wrote, err := fs.file.WriteAt(b, offset) + offset := int64(bitmapLocation * uint64(fs.superblock.blockSize)) + wrote, err := writableFile.WriteAt(b, offset) if err != nil { return fmt.Errorf("unable to write block bitmap for blockgroup %d: %w", gd.number, err) } @@ -1732,11 +1896,15 @@ func (fs *FileSystem) writeBlockBitmap(bm *util.Bitmap, group int) error { } func (fs *FileSystem) writeSuperblock() error { + writableFile, err := fs.backend.Writable() + if err != nil { + return err + } superblockBytes, err := fs.superblock.toBytes() if err != nil { return fmt.Errorf("could not convert superblock to bytes: %v", err) } - _, err = fs.file.WriteAt(superblockBytes, fs.start+int64(BootSectorSize)) + _, err = writableFile.WriteAt(superblockBytes, int64(BootSectorSize)) return err } @@ -1746,3 +1914,100 @@ func blockGroupForInode(inodeNumber int, inodesPerGroup uint32) int { func blockGroupForBlock(blockNumber int, blocksPerGroup uint32) int { return (blockNumber - 1) / int(blocksPerGroup) } + +// given the superblock, build the group descriptors +func buildGroupDescriptorsFromSuperblock(sb *superblock) groupDescriptors { + blocksPerGroup := uint64(sb.blocksPerGroup) + inodesPerGroup := sb.inodesPerGroup + inodeTableBlocks := (uint64(inodesPerGroup)*uint64(sb.inodeSize) + uint64(sb.blockSize) - 1) / uint64(sb.blockSize) + groups := int((sb.blockCount + blocksPerGroup - 1) / blocksPerGroup) + descSize := sb.groupDescriptorSize + + useMetaBg := sb.features.metaBlockGroups + firstMetaBg := uint64(sb.firstMetablockGroup) + + useFlexBg := sb.features.flexBlockGroups + flexSize := uint64(1) + if useFlexBg { + flexSize = 1 << sb.logGroupsPerFlex + } + + descs := make([]groupDescriptor, groups) + + for g := 0; g < groups; g++ { + var d groupDescriptor + d.number = uint16(g) + d.size = descSize + + firstBlockOfGroup := uint64(g) * blocksPerGroup + // Determine if this group holds a SB+GDT backup. + hasSuperBackup := false + if useMetaBg { + hasSuperBackup = uint64(g) >= firstMetaBg && (uint64(g)%firstMetaBg) == 0 + } else { + hasSuperBackup = checkSuperBackup(uint64(g)) + } + + // Metadata overhead in this group. + metaBlocks := uint64(0) + if hasSuperBackup { + gdtBlocks := + (uint64(groups)*uint64(descSize) + uint64(sb.blockSize) - 1) / + uint64(sb.blockSize) + metaBlocks = 1 + gdtBlocks + } + + // flex_bg owner group + flexOwner := (uint64(g) / flexSize) * flexSize + + // Base block numbers + bitmapBase := flexOwner*blocksPerGroup + metaBlocks + + if useFlexBg { + // all groups in a flex share the same bitmap/table set + d.blockBitmapLocation = bitmapBase + d.inodeBitmapLocation = bitmapBase + 1 + d.inodeTableLocation = bitmapBase + 2 + } else { + d.blockBitmapLocation = firstBlockOfGroup + metaBlocks + d.inodeBitmapLocation = d.blockBitmapLocation + 1 + d.inodeTableLocation = d.inodeBitmapLocation + 1 + } + + // Free blocks accounting + overhead := metaBlocks + if !useFlexBg || uint64(g) == flexOwner { + overhead += 1 + 1 + inodeTableBlocks + } + if overhead > blocksPerGroup { + overhead = blocksPerGroup + } + + d.freeBlocks = uint32(blocksPerGroup - overhead) + d.freeInodes = inodesPerGroup + d.usedDirectories = 0 + d.flags = blockGroupFlags{} + d.unusedInodes = 0 + d.blockBitmapChecksum = 0 + d.inodeBitmapChecksum = 0 + d.snapshotExclusionBitmapLocation = 0 + + descs[g] = d + } + + return groupDescriptors{descriptors: descs} +} + +func checkSuperBackup(g uint64) bool { + if g == 0 { + return true + } + for _, n := range []uint64{3, 5, 7} { + for x := n; x <= g; x *= n { + if x == g { + return true + } + } + } + return false +} diff --git a/vendor/github.com/diskfs/go-diskfs/filesystem/ext4/extent.go b/vendor/github.com/diskfs/go-diskfs/filesystem/ext4/extent.go index e5d456e0068..c107c4c50aa 100644 --- a/vendor/github.com/diskfs/go-diskfs/filesystem/ext4/extent.go +++ b/vendor/github.com/diskfs/go-diskfs/filesystem/ext4/extent.go @@ -680,8 +680,13 @@ func writeNodeToDisk(node extentBlockFinder, fs *FileSystem, parent *extentInter return fmt.Errorf("block number not found for node") } + writableFile, err := fs.backend.Writable() + if err != nil { + return err + } + data := node.toBytes() - _, err := fs.file.WriteAt(data, int64(blockNumber)*int64(fs.superblock.blockSize)) + _, err = writableFile.WriteAt(data, int64(blockNumber)*int64(fs.superblock.blockSize)) return err } @@ -720,7 +725,7 @@ func findChildNode(node *extentInternalNode, added *extents) int { //nolint:unparam // this parameter will be used eventually func loadChildNode(childPtr *extentChildPtr, fs *FileSystem) (extentBlockFinder, error) { data := make([]byte, fs.superblock.blockSize) - _, err := fs.file.ReadAt(data, int64(childPtr.diskBlock)*int64(fs.superblock.blockSize)) + _, err := fs.backend.ReadAt(data, int64(childPtr.diskBlock)*int64(fs.superblock.blockSize)) if err != nil { return nil, err } diff --git a/vendor/github.com/diskfs/go-diskfs/filesystem/ext4/file.go b/vendor/github.com/diskfs/go-diskfs/filesystem/ext4/file.go index 4dc653956c6..733a6acf739 100644 --- a/vendor/github.com/diskfs/go-diskfs/filesystem/ext4/file.go +++ b/vendor/github.com/diskfs/go-diskfs/filesystem/ext4/file.go @@ -61,7 +61,7 @@ func (fl *File) Read(b []byte) (int, error) { // read those bytes startPosOnDisk := e.startingBlock*blocksize + uint64(startPositionInExtent) b2 := make([]byte, toReadInOffset) - read, err := fl.filesystem.file.ReadAt(b2, int64(startPosOnDisk)) + read, err := fl.filesystem.backend.ReadAt(b2, int64(startPosOnDisk)) if err != nil { return int(readBytes), fmt.Errorf("failed to read bytes: %v", err) } @@ -145,6 +145,12 @@ func (fl *File) Write(b []byte) (int, error) { // the offset given for reading is relative to the file, so we need to calculate // where these are in the extents relative to the file writeStartBlock := uint64(fl.offset) / blocksize + + writableFile, err := fl.filesystem.backend.Writable() + if err != nil { + return -1, err + } + for _, e := range fl.extents { // if the last block of the extent is before the first block we want to write, skip it if uint64(e.fileBlock)+uint64(e.count) < writeStartBlock { @@ -164,7 +170,7 @@ func (fl *File) Write(b []byte) (int, error) { startPosOnDisk := e.startingBlock*blocksize + uint64(startPositionInExtent) b2 := make([]byte, toWriteInOffset) copy(b2, b[writtenBytes:]) - written, err := fl.filesystem.file.WriteAt(b2, int64(startPosOnDisk)) + written, err := writableFile.WriteAt(b2, int64(startPosOnDisk)) if err != nil { return int(writtenBytes), fmt.Errorf("failed to read bytes: %v", err) } @@ -175,7 +181,7 @@ func (fl *File) Write(b []byte) (int, error) { break } } - var err error + if fl.offset >= fileSize { err = io.EOF } diff --git a/vendor/github.com/diskfs/go-diskfs/filesystem/ext4/groupdescriptors.go b/vendor/github.com/diskfs/go-diskfs/filesystem/ext4/groupdescriptors.go index 995cda05e86..ced08f5aa8f 100644 --- a/vendor/github.com/diskfs/go-diskfs/filesystem/ext4/groupdescriptors.go +++ b/vendor/github.com/diskfs/go-diskfs/filesystem/ext4/groupdescriptors.go @@ -87,6 +87,9 @@ func (gds *groupDescriptors) equal(a *groupDescriptors) bool { // groupDescriptorsFromBytes create a groupDescriptors struct from bytes func groupDescriptorsFromBytes(b []byte, gdSize uint16, hashSeed uint32, checksumType gdtChecksumType) (*groupDescriptors, error) { + if gdSize == 0 { + return nil, fmt.Errorf("group descriptor size cannot be zero") + } gds := groupDescriptors{} gdSlice := make([]groupDescriptor, 0, 10) @@ -157,6 +160,7 @@ func groupDescriptorFromBytes(b []byte, gdSize uint16, number int, checksumType copy(blockBitmapChecksum[0:2], b[0x18:0x1a]) copy(inodeBitmapChecksum[0:2], b[0x1a:0x1c]) copy(unusedInodes[0:2], b[0x1c:0x1e]) + checksumInput := b[0x0:0x20] if gdSize == 64 { copy(blockBitmapLocation[4:8], b[0x20:0x24]) @@ -169,17 +173,10 @@ func groupDescriptorFromBytes(b []byte, gdSize uint16, number int, checksumType copy(snapshotExclusionBitmapLocation[4:8], b[0x34:0x38]) copy(blockBitmapChecksum[2:4], b[0x38:0x3a]) copy(inodeBitmapChecksum[2:4], b[0x3a:0x3c]) + checksumInput = b[0x0:0x40] } gdNumber := uint16(number) - // only bother with checking the checksum if it was not type none (pre-checksums) - if checksumType != gdtChecksumNone { - checksum := binary.LittleEndian.Uint16(b[0x1e:0x20]) - actualChecksum := groupDescriptorChecksum(b[0x0:0x40], hashSeed, gdNumber, checksumType) - if checksum != actualChecksum { - return nil, fmt.Errorf("checksum mismatch, passed %x, actual %x", checksum, actualChecksum) - } - } gd := groupDescriptor{ size: gdSize, @@ -197,6 +194,15 @@ func groupDescriptorFromBytes(b []byte, gdSize uint16, number int, checksumType flags: parseBlockGroupFlags(binary.LittleEndian.Uint16(b[0x12:0x14])), } + // only bother with checking the checksum if it was not type none (pre-checksums) + if checksumType != gdtChecksumNone { + checksum := binary.LittleEndian.Uint16(b[0x1e:0x20]) + actualChecksum := groupDescriptorChecksum(checksumInput, hashSeed, gdNumber, checksumType) + if checksum != actualChecksum { + return nil, fmt.Errorf("checksum mismatch, passed %x, actual %x", checksum, actualChecksum) + } + } + return &gd, nil } diff --git a/vendor/github.com/diskfs/go-diskfs/filesystem/ext4/superblock.go b/vendor/github.com/diskfs/go-diskfs/filesystem/ext4/superblock.go index fcafda9390b..0b68c860355 100644 --- a/vendor/github.com/diskfs/go-diskfs/filesystem/ext4/superblock.go +++ b/vendor/github.com/diskfs/go-diskfs/filesystem/ext4/superblock.go @@ -9,7 +9,7 @@ import ( "time" "github.com/diskfs/go-diskfs/filesystem/ext4/crc" - "github.com/diskfs/go-diskfs/util" + "github.com/diskfs/go-diskfs/util/slices" "github.com/google/uuid" ) @@ -347,7 +347,10 @@ func superblockFromBytes(b []byte) (*superblock, error) { sb.hashVersion = hashAlgorithm(b[0xfc]) - sb.groupDescriptorSize = binary.LittleEndian.Uint16(b[0xfe:0x100]) + sb.groupDescriptorSize = 32 + if sb.features.fs64Bit { + sb.groupDescriptorSize = binary.LittleEndian.Uint16(b[0xfe:0x100]) + } sb.defaultMountOptions = parseMountOptions(binary.LittleEndian.Uint32(b[0x100:0x104])) sb.firstMetablockGroup = binary.LittleEndian.Uint32(b[0x104:0x108]) @@ -735,7 +738,7 @@ func calculateBackupSuperblockGroups(bgs int64) []int64 { backupGroups = append(backupGroups, bg) } // sort the backup groups - uniqBackupGroups := util.Uniqify[int64](backupGroups) + uniqBackupGroups := slices.Uniqify[int64](backupGroups) sort.Slice(uniqBackupGroups, func(i, j int) bool { return uniqBackupGroups[i] < uniqBackupGroups[j] }) diff --git a/vendor/github.com/diskfs/go-diskfs/filesystem/fat32/directory.go b/vendor/github.com/diskfs/go-diskfs/filesystem/fat32/directory.go index bd917b756ff..2996ae28bcc 100644 --- a/vendor/github.com/diskfs/go-diskfs/filesystem/fat32/directory.go +++ b/vendor/github.com/diskfs/go-diskfs/filesystem/fat32/directory.go @@ -1,6 +1,7 @@ package fat32 import ( + "fmt" "time" ) @@ -41,7 +42,7 @@ func (d *Directory) entriesToBytes(bytesPerCluster int) ([]byte, error) { func (d *Directory) createEntry(name string, cluster uint32, dir bool) (*directoryEntry, error) { // is it a long filename or a short filename? var isLFN bool - // TODO: convertLfnSfn does not calculate if the short name conflicts and thus shoukld increment the last character + // TODO: convertLfnSfn does not calculate if the short name conflicts and thus should increment the last character // that should happen here, once we can look in the directory entry shortName, extension, isLFN, _ := convertLfnSfn(name) lfn := "" @@ -70,6 +71,60 @@ func (d *Directory) createEntry(name string, cluster uint32, dir bool) (*directo return &entry, nil } +// removeEntry removes an entry in the given directory +func (d *Directory) removeEntry(name string) error { + // TODO implement check for long/short filename after increment of sfn is correctly implemented + + removeEntryIndex := -1 + for i, entry := range d.entries { + if entry.filenameLong == name { // || entry.filenameShort == shortName do not compare SFN, since it is not incremented correctly + removeEntryIndex = i + } + } + + if removeEntryIndex == -1 { + return fmt.Errorf("cannot find entry for name %s", name) + } + + // remove the entry from the list + d.entries = append(d.entries[:removeEntryIndex], d.entries[removeEntryIndex+1:]...) + + return nil +} + +// renameEntry renames an entry in the given directory, and returns the handle to it +func (d *Directory) renameEntry(oldFileName, newFileName string) error { + // TODO implement check for long/short filename after increment of sfn is correctly implemented + + newEntries := make([]*directoryEntry, 0, len(d.entries)) + var isReplaced = false + for _, entry := range d.entries { + if entry.filenameLong == newFileName { + continue // skip adding already existing file, will be overwritten + } + if entry.filenameLong == oldFileName { // || entry.filenameShort == shortName do not compare SFN, since it is not incremented correctly + var lfn string + shortName, extension, isLFN, _ := convertLfnSfn(newFileName) + if isLFN { + lfn = newFileName + } + entry.filenameLong = lfn + entry.filenameShort = shortName + entry.fileExtension = extension + entry.modifyTime = time.Now() + isReplaced = true + } + newEntries = append(newEntries, entry) + } + if !isReplaced { + return fmt.Errorf("cannot find file entry for %s", oldFileName) + } + + d.entries = newEntries + + return nil +} + // createVolumeLabel create a volume label entry in the given directory, and return the handle to it func (d *Directory) createVolumeLabel(name string) (*directoryEntry, error) { // allocate a slot for the new filename in the existing directory diff --git a/vendor/github.com/diskfs/go-diskfs/filesystem/fat32/directoryentry.go b/vendor/github.com/diskfs/go-diskfs/filesystem/fat32/directoryentry.go index 08cb2ec441b..d0341626179 100644 --- a/vendor/github.com/diskfs/go-diskfs/filesystem/fat32/directoryentry.go +++ b/vendor/github.com/diskfs/go-diskfs/filesystem/fat32/directoryentry.go @@ -23,8 +23,6 @@ const ( var validShortNameCharacters, _ = asciiset.MakeASCIISet("!#$%&'()-0123456789@ABCDEFGHIJKLMNOPQRSTUVWXYZ^_`{}~") // directoryEntry is a single directory entry -// -//nolint:structcheck // we are willing to leave unused elements here so that we can know their reference type directoryEntry struct { filenameShort string fileExtension string @@ -66,6 +64,21 @@ func (de *directoryEntry) toBytes() ([]byte, error) { createDate, createTime := timeToDateTime(de.createTime) modifyDate, modifyTime := timeToDateTime(de.modifyTime) accessDate, _ := timeToDateTime(de.accessTime) + if de.isSystem { + dosBytes[11] |= 0x04 + } else { + dosBytes[11] &= ^byte(0x04) + } + if de.isHidden { + dosBytes[11] |= 0x02 + } else { + dosBytes[11] &= ^byte(0x02) + } + if de.isReadOnly { + dosBytes[11] |= 0x01 + } else { + dosBytes[11] &= ^byte(0x01) + } binary.LittleEndian.PutUint16(dosBytes[14:16], createTime) binary.LittleEndian.PutUint16(dosBytes[16:18], createDate) binary.LittleEndian.PutUint16(dosBytes[18:20], accessDate) @@ -103,7 +116,7 @@ func (de *directoryEntry) toBytes() ([]byte, error) { } if de.lowercaseExtension { - dosBytes[12] |= 0x04 + dosBytes[12] |= 0x10 } if de.lowercaseShortname { dosBytes[12] |= 0x08 @@ -148,6 +161,9 @@ byteLoop: lfn = tmpLfn + lfn continue } + isSystem := b[i+11]&0x04 == 0x04 + isHidden := b[i+11]&0x02 == 0x02 + isReadOnly := b[i+11]&0x01 == 0x01 // not LFN, so parse regularly createTime := binary.LittleEndian.Uint16(b[i+14 : i+16]) createDate := binary.LittleEndian.Uint16(b[i+16 : i+18]) @@ -161,7 +177,7 @@ byteLoop: isArchiveDirty := b[i+11]&0x20 == 0x20 isVolumeLabel := b[i+11]&0x08 == 0x08 lowercaseShortname := b[i+12]&0x08 == 0x08 - lowercaseExtension := b[i+12]&0x04 == 0x04 + lowercaseExtension := b[i+12]&0x10 == 0x10 entry := directoryEntry{ filenameLong: lfn, @@ -178,6 +194,9 @@ byteLoop: isVolumeLabel: isVolumeLabel, lowercaseShortname: lowercaseShortname, lowercaseExtension: lowercaseExtension, + isReadOnly: isReadOnly, + isHidden: isHidden, + isSystem: isSystem, } lfn = "" dirEntries = append(dirEntries, &entry) diff --git a/vendor/github.com/diskfs/go-diskfs/filesystem/fat32/fat32.go b/vendor/github.com/diskfs/go-diskfs/filesystem/fat32/fat32.go index ae6531734f8..06a59636b94 100644 --- a/vendor/github.com/diskfs/go-diskfs/filesystem/fat32/fat32.go +++ b/vendor/github.com/diskfs/go-diskfs/filesystem/fat32/fat32.go @@ -5,12 +5,11 @@ import ( "fmt" "os" "path" - "sort" "strings" "time" + "github.com/diskfs/go-diskfs/backend" "github.com/diskfs/go-diskfs/filesystem" - "github.com/diskfs/go-diskfs/util" ) // MsdosMediaType is the (mostly unused) media type. However, we provide and export the known constants for it. @@ -45,7 +44,7 @@ const ( maxCharsLongFilename int = 13 ) -//nolint:deadcode,varcheck,unused // we need these references in the future +//nolint:unused // we need these references in the future const ( minClusterSize int = 128 maxClusterSize int = 65529 @@ -60,12 +59,18 @@ type FileSystem struct { bytesPerCluster int size int64 start int64 - file util.File + backend backend.Storage } // Equal compare if two filesystems are equal func (fs *FileSystem) Equal(a *FileSystem) bool { - localMatch := fs.file == a.file && fs.dataStart == a.dataStart && fs.bytesPerCluster == a.bytesPerCluster + if fs == nil && a == nil { + return true + } + if fs == nil || a == nil { + return false + } + localMatch := fs.backend == a.backend && fs.dataStart == a.dataStart && fs.bytesPerCluster == a.bytesPerCluster tableMatch := fs.table.equal(&a.table) bsMatch := fs.bootSector.equal(&a.bootSector) fsisMatch := fs.fsis == a.fsis @@ -74,8 +79,8 @@ func (fs *FileSystem) Equal(a *FileSystem) bool { // Create creates a FAT32 filesystem in a given file or device // -// requires the util.File where to create the filesystem, size is the size of the filesystem in bytes, -// start is how far in bytes from the beginning of the util.File to create the filesystem, +// requires the backend.Storage where to create the filesystem, size is the size of the filesystem in bytes, +// start is how far in bytes from the beginning of the backend.Storage to create the filesystem, // and blocksize is is the logical blocksize to use for creating the filesystem // // note that you are *not* required to create the filesystem on the entire disk. You could have a disk of size @@ -88,7 +93,7 @@ func (fs *FileSystem) Equal(a *FileSystem) bool { // // If the provided blocksize is 0, it will use the default of 512 bytes. If it is any number other than 0 // or 512, it will return an error. -func Create(f util.File, size, start, blocksize int64, volumeLabel string) (*FileSystem, error) { +func Create(b backend.Storage, size, start, blocksize int64, volumeLabel string) (*FileSystem, error) { // blocksize must be <=0 or exactly SectorSize512 or error if blocksize != int64(SectorSize512) && blocksize > 0 { return nil, fmt.Errorf("blocksize for FAT32 must be either 512 bytes or 0, not %d", blocksize) @@ -107,6 +112,11 @@ func Create(f util.File, size, start, blocksize int64, volumeLabel string) (*Fil fsisPrimarySector := uint16(1) backupBootSector := uint16(6) + writableFile, err := b.Writable() + if err != nil { + return nil, err + } + /* size calculations we have the total size of the disk from `size uint64` @@ -223,17 +233,16 @@ func Create(f util.File, size, start, blocksize int64, volumeLabel string) (*Fil fatSecondaryStart := uint64(fatPrimaryStart) + uint64(fatSize) maxCluster := fatSize / 4 rootDirCluster := uint32(2) + clusters := make([]uint32, maxCluster+1) + clusters[rootDirCluster] = eocMarker fat := table{ fatID: fatID, eocMarker: eocMarker, unusedMarker: unusedMarker, size: fatSize, rootDirCluster: rootDirCluster, - clusters: map[uint32]uint32{ - // when we start, there is just one directory with a single cluster - rootDirCluster: eocMarker, - }, - maxCluster: maxCluster, + clusters: clusters, + maxCluster: maxCluster, } // where does our data start? @@ -248,22 +257,22 @@ func Create(f util.File, size, start, blocksize int64, volumeLabel string) (*Fil bytesPerCluster: int(sectorsPerCluster) * int(SectorSize512), start: start, size: size, - file: f, + backend: b, } // write the boot sector if err := fs.writeBootSector(); err != nil { - return nil, fmt.Errorf("failed to write the boot sector: %v", err) + return nil, fmt.Errorf("failed to write the boot sector: %w", err) } // write the fsis if err := fs.writeFsis(); err != nil { - return nil, fmt.Errorf("failed to write the file system information sector: %v", err) + return nil, fmt.Errorf("failed to write the file system information sector: %w", err) } // write the FAT tables if err := fs.writeFat(); err != nil { - return nil, fmt.Errorf("failed to write the file allocation table: %v", err) + return nil, fmt.Errorf("failed to write the file allocation table: %w", err) } // create root directory @@ -273,9 +282,9 @@ func Create(f util.File, size, start, blocksize int64, volumeLabel string) (*Fil // length of cluster in bytes tmpb := make([]byte, fs.bytesPerCluster) // zero out the root directory cluster - written, err := f.WriteAt(tmpb, clusterStart) + written, err := writableFile.WriteAt(tmpb, clusterStart) if err != nil { - return nil, fmt.Errorf("failed to zero out root directory: %v", err) + return nil, fmt.Errorf("failed to zero out root directory: %w", err) } if written != len(tmpb) || written != fs.bytesPerCluster { return nil, fmt.Errorf("incomplete zero out of root directory, wrote %d bytes instead of expected %d for cluster size %d", written, len(tmpb), fs.bytesPerCluster) @@ -292,13 +301,13 @@ func Create(f util.File, size, start, blocksize int64, volumeLabel string) (*Fil // write the root directory entries to disk err = fs.writeDirectoryEntries(rootDir) if err != nil { - return nil, fmt.Errorf("error writing root directory to disk: %v", err) + return nil, fmt.Errorf("error writing root directory to disk: %w", err) } // set the volume label err = fs.SetLabel(volumeLabel) if err != nil { - return nil, fmt.Errorf("failed to set volume label to '%s': %v", volumeLabel, err) + return nil, fmt.Errorf("failed to set volume label to '%s': %w", volumeLabel, err) } return fs, nil @@ -306,8 +315,8 @@ func Create(f util.File, size, start, blocksize int64, volumeLabel string) (*Fil // Read reads a filesystem from a given disk. // -// requires the util.File where to read the filesystem, size is the size of the filesystem in bytes, -// start is how far in bytes from the beginning of the util.File the filesystem is expected to begin, +// requires the backend.Storage where to read the filesystem, size is the size of the filesystem in bytes, +// start is how far in bytes from the beginning of the backend.Storage the filesystem is expected to begin, // and blocksize is is the logical blocksize to use for creating the filesystem // // note that you are *not* required to read a filesystem on the entire disk. You could have a disk of size @@ -320,7 +329,7 @@ func Create(f util.File, size, start, blocksize int64, volumeLabel string) (*Fil // // If the provided blocksize is 0, it will use the default of 512 bytes. If it is any number other than 0 // or 512, it will return an error. -func Read(file util.File, size, start, blocksize int64) (*FileSystem, error) { +func Read(b backend.Storage, size, start, blocksize int64) (*FileSystem, error) { // blocksize must be <=0 or exactly SectorSize512 or error if blocksize != int64(SectorSize512) && blocksize > 0 { return nil, fmt.Errorf("blocksize for FAT32 must be either 512 bytes or 0, not %d", blocksize) @@ -331,13 +340,12 @@ func Read(file util.File, size, start, blocksize int64) (*FileSystem, error) { if size < blocksize*4 { return nil, fmt.Errorf("requested size is smaller than minimum allowed FAT32 size %d", blocksize*4) } - // load the information from the disk // read first 512 bytes from the file bsb := make([]byte, SectorSize512) - n, err := file.ReadAt(bsb, start) + n, err := b.ReadAt(bsb, start) if err != nil { - return nil, fmt.Errorf("could not read bytes from file: %v", err) + return nil, fmt.Errorf("could not read bytes from file: %w", err) } if uint16(n) < uint16(SectorSize512) { return nil, fmt.Errorf("only could read %d bytes from file", n) @@ -345,7 +353,7 @@ func Read(file util.File, size, start, blocksize int64) (*FileSystem, error) { bs, err := msDosBootSectorFromBytes(bsb) if err != nil { - return nil, fmt.Errorf("error reading MS-DOS Boot Sector: %v", err) + return nil, fmt.Errorf("error reading MS-DOS Boot Sector: %w", err) } sectorsPerFat := bs.biosParameterBlock.sectorsPerFat @@ -356,24 +364,24 @@ func Read(file util.File, size, start, blocksize int64) (*FileSystem, error) { fatSecondaryStart := fatPrimaryStart + uint64(fatSize) fsisBytes := make([]byte, 512) - read, err := file.ReadAt(fsisBytes, int64(bs.biosParameterBlock.fsInformationSector)*blocksize+start) + read, err := b.ReadAt(fsisBytes, int64(bs.biosParameterBlock.fsInformationSector)*blocksize+start) if err != nil { - return nil, fmt.Errorf("unable to read bytes for FSInformationSector: %v", err) + return nil, fmt.Errorf("unable to read bytes for FSInformationSector: %w", err) } if read != 512 { return nil, fmt.Errorf("read %d bytes instead of expected %d for FS Information Sector", read, 512) } fsis, err := fsInformationSectorFromBytes(fsisBytes) if err != nil { - return nil, fmt.Errorf("error reading FileSystem Information Sector: %v", err) + return nil, fmt.Errorf("error reading FileSystem Information Sector: %w", err) } - b := make([]byte, fatSize) - _, _ = file.ReadAt(b, int64(fatPrimaryStart)+start) - fat := tableFromBytes(b) + partitionTableBytes := make([]byte, fatSize) + _, _ = b.ReadAt(partitionTableBytes, int64(fatPrimaryStart)+start) + fat := tableFromBytes(partitionTableBytes) - _, _ = file.ReadAt(b, int64(fatSecondaryStart)+start) - fat2 := tableFromBytes(b) + _, _ = b.ReadAt(partitionTableBytes, int64(fatSecondaryStart)+start) + fat2 := tableFromBytes(partitionTableBytes) if !fat.equal(fat2) { return nil, errors.New("fat tables did not match") } @@ -387,7 +395,7 @@ func Read(file util.File, size, start, blocksize int64) (*FileSystem, error) { bytesPerCluster: int(sectorsPerCluster) * int(SectorSize512), start: start, size: size, - file: file, + backend: b, }, nil } @@ -399,16 +407,20 @@ func (fs *FileSystem) writeBootSector() error { return nil, fmt.Errorf("error writing MS-DOS Boot Sector: %v", err) } */ + writableFile, err := fs.backend.Writable() + if err != nil { + return err + } b, err := fs.bootSector.toBytes() if err != nil { - return fmt.Errorf("error converting MS-DOS Boot Sector to bytes: %v", err) + return fmt.Errorf("error converting MS-DOS Boot Sector to bytes: %w", err) } // write main boot sector - count, err := fs.file.WriteAt(b, 0+fs.start) + count, err := writableFile.WriteAt(b, 0+fs.start) if err != nil { - return fmt.Errorf("error writing MS-DOS Boot Sector to disk: %v", err) + return fmt.Errorf("error writing MS-DOS Boot Sector to disk: %w", err) } if count != int(SectorSize512) { return fmt.Errorf("wrote %d bytes of MS-DOS Boot Sector to disk instead of expected %d", count, SectorSize512) @@ -416,9 +428,9 @@ func (fs *FileSystem) writeBootSector() error { // write backup boot sector to the file if fs.bootSector.biosParameterBlock.backupBootSector > 0 { - count, err = fs.file.WriteAt(b, int64(fs.bootSector.biosParameterBlock.backupBootSector)*int64(SectorSize512)+fs.start) + count, err = writableFile.WriteAt(b, int64(fs.bootSector.biosParameterBlock.backupBootSector)*int64(SectorSize512)+fs.start) if err != nil { - return fmt.Errorf("error writing MS-DOS Boot Sector to disk: %v", err) + return fmt.Errorf("error writing MS-DOS Boot Sector to disk: %w", err) } if count != int(SectorSize512) { return fmt.Errorf("wrote %d bytes of MS-DOS Boot Sector to disk instead of expected %d", count, SectorSize512) @@ -434,14 +446,18 @@ func (fs *FileSystem) writeFsis() error { fsisPrimary := int64(fsInformationSector * uint16(SectorSize512)) fsisBytes := fs.fsis.toBytes() + writableFile, err := fs.backend.Writable() + if err != nil { + return err + } - if _, err := fs.file.WriteAt(fsisBytes, fsisPrimary+fs.start); err != nil { - return fmt.Errorf("unable to write primary Fsis: %v", err) + if _, err := writableFile.WriteAt(fsisBytes, fsisPrimary+fs.start); err != nil { + return fmt.Errorf("unable to write primary Fsis: %w", err) } if backupBootSector > 0 { - if _, err := fs.file.WriteAt(fsisBytes, int64(backupBootSector+1)*int64(SectorSize512)+fs.start); err != nil { - return fmt.Errorf("unable to write backup Fsis: %v", err) + if _, err := writableFile.WriteAt(fsisBytes, int64(backupBootSector+1)*int64(SectorSize512)+fs.start); err != nil { + return fmt.Errorf("unable to write backup Fsis: %w", err) } } @@ -454,18 +470,30 @@ func (fs *FileSystem) writeFat() error { fatSecondaryStart := fatPrimaryStart + uint64(fs.table.size) fatBytes := fs.table.bytes() + writableFile, err := fs.backend.Writable() + if err != nil { + return err + } - if _, err := fs.file.WriteAt(fatBytes, int64(fatPrimaryStart)+fs.start); err != nil { - return fmt.Errorf("unable to write primary FAT table: %v", err) + if _, err := writableFile.WriteAt(fatBytes, int64(fatPrimaryStart)+fs.start); err != nil { + return fmt.Errorf("unable to write primary FAT table: %w", err) } - if _, err := fs.file.WriteAt(fatBytes, int64(fatSecondaryStart)+fs.start); err != nil { - return fmt.Errorf("unable to write backup FAT table: %v", err) + if _, err := writableFile.WriteAt(fatBytes, int64(fatSecondaryStart)+fs.start); err != nil { + return fmt.Errorf("unable to write backup FAT table: %w", err) } return nil } +// interface guard +var _ filesystem.FileSystem = (*FileSystem)(nil) + +// Do cleaning job for fat32. Note that fat32 does not have side-effects so we do not do anything. +func (fs *FileSystem) Close() error { + return nil +} + // Type returns the type code for the filesystem. Always returns filesystem.TypeFat32 func (fs *FileSystem) Type() filesystem.Type { return filesystem.TypeFat32 @@ -481,6 +509,34 @@ func (fs *FileSystem) Mkdir(p string) error { return err } +// creates a filesystem node (file, device special file, or named pipe) named pathname, +// with attributes specified by mode and dev +func (fs *FileSystem) Mknod(_ string, _ uint32, _ int) error { + return filesystem.ErrNotSupported +} + +// creates a new link (also known as a hard link) to an existing file. +func (fs *FileSystem) Link(_, _ string) error { + return filesystem.ErrNotSupported +} + +// creates a symbolic link named linkpath which contains the string target. +func (fs *FileSystem) Symlink(_, _ string) error { + return filesystem.ErrNotSupported +} + +// Chmod changes the mode of the named file to mode. If the file is a symbolic link, +// it changes the mode of the link's target. +func (fs *FileSystem) Chmod(_ string, _ os.FileMode) error { + return filesystem.ErrNotSupported +} + +// Chown changes the numeric uid and gid of the named file. If the file is a symbolic link, +// it changes the uid and gid of the link's target. A uid or gid of -1 means to not change that value +func (fs *FileSystem) Chown(_ string, _, _ int) error { + return filesystem.ErrNotSupported +} + // ReadDir return the contents of a given directory in a given filesystem. // // Returns a slice of os.FileInfo with all of the entries in the directory. @@ -489,7 +545,7 @@ func (fs *FileSystem) Mkdir(p string) error { func (fs *FileSystem) ReadDir(p string) ([]os.FileInfo, error) { _, entries, err := fs.readDirWithMkdir(p, false) if err != nil { - return nil, fmt.Errorf("error reading directory %s: %v", p, err) + return nil, fmt.Errorf("error reading directory %s: %w", p, err) } // once we have made it here, looping is done. We have found the final entry // we need to return all of the file info @@ -505,7 +561,7 @@ func (fs *FileSystem) ReadDir(p string) ([]os.FileInfo, error) { } fileExtension := e.fileExtension if e.lowercaseExtension { - shortName = strings.ToLower(fileExtension) + fileExtension = strings.ToLower(fileExtension) } if fileExtension != "" { shortName = fmt.Sprintf("%s.%s", shortName, fileExtension) @@ -538,7 +594,7 @@ func (fs *FileSystem) OpenFile(p string, flag int) (filesystem.File, error) { // get the directory entries parentDir, entries, err := fs.readDirWithMkdir(dir, false) if err != nil { - return nil, fmt.Errorf("could not read directory entries for %s", dir) + return nil, fmt.Errorf("could not read directory entries for %s: %w", dir, err) } // we now know that the directory exists, see if the file exists var targetEntry *directoryEntry @@ -547,7 +603,7 @@ func (fs *FileSystem) OpenFile(p string, flag int) (filesystem.File, error) { if e.fileExtension != "" { shortName += "." + e.fileExtension } - if e.filenameLong != filename && shortName != filename { + if !strings.EqualFold(e.filenameLong, filename) && !strings.EqualFold(shortName, filename) { continue } // cannot do anything with directories @@ -567,12 +623,12 @@ func (fs *FileSystem) OpenFile(p string, flag int) (filesystem.File, error) { // else create it targetEntry, err = fs.mkFile(parentDir, filename) if err != nil { - return nil, fmt.Errorf("failed to create file %s: %v", p, err) + return nil, fmt.Errorf("failed to create file %s: %w", p, err) } // write the directory entries to disk err = fs.writeDirectoryEntries(parentDir) if err != nil { - return nil, fmt.Errorf("error writing directory file %s to disk: %v", p, err) + return nil, fmt.Errorf("error writing directory file %s to disk: %w", p, err) } } offset := int64(0) @@ -583,10 +639,10 @@ func (fs *FileSystem) OpenFile(p string, flag int) (filesystem.File, error) { targetEntry.fileSize = 0 // we should not need to change the parent, because it is all pointers if err := fs.writeDirectoryEntries(parentDir); err != nil { - return nil, fmt.Errorf("error writing directory file %s to disk: %v", p, err) + return nil, fmt.Errorf("error writing directory file %s to disk: %w", p, err) } if _, err := fs.allocateSpace(1, targetEntry.clusterLocation); err != nil { - return nil, fmt.Errorf("unable to resize cluster list: %v", err) + return nil, fmt.Errorf("unable to resize cluster list: %w", err) } } if flag&os.O_APPEND == os.O_APPEND { @@ -602,6 +658,130 @@ func (fs *FileSystem) OpenFile(p string, flag int) (filesystem.File, error) { }, nil } +// removes the named file or (empty) directory. +func (fs *FileSystem) Remove(pathname string) error { + // get the path + dir := path.Dir(pathname) + filename := path.Base(pathname) + // if the dir == filename, then it is just / + if dir == filename { + return fmt.Errorf("cannot remove directory %s as file", pathname) + } + // get the directory entries + parentDir, entries, err := fs.readDirWithMkdir(dir, false) + if err != nil { + return fmt.Errorf("could not read directory entries for %s", dir) + } + // we now know that the directory exists, see if the file exists + var targetEntry *directoryEntry + for _, e := range entries { + shortName := e.filenameShort + if e.fileExtension != "" { + shortName += "." + e.fileExtension + } + if e.filenameLong != filename && shortName != filename { + continue + } + // cannot do anything with directories + if e.isSubdirectory { + content, err := fs.ReadDir(pathname) + if err != nil { + return fmt.Errorf("error while checking if file to delete is empty: %+v", err) + } + // '.' & '..' are always present in directory + if len(content) > 2 { + return fmt.Errorf("cannot remove non-empty directory %s", pathname) + } + } + // if we got this far, we have found the file + targetEntry = e + } + + // see if the file exists + // if the file does not exist, and is not opened for os.O_CREATE, return an error + if targetEntry == nil { + return fmt.Errorf("target file %s does not exist", pathname) + } + err = parentDir.removeEntry(filename) + if err != nil { + return fmt.Errorf("failed to remove file %s: %v", pathname, err) + } + + // we need to make sure that clusters are removed which may not be used anymore + _, err = fs.allocateSpace(uint64(parentDir.fileSize), parentDir.clusterLocation) + if err != nil { + return fmt.Errorf("failed to allocate clusters: %v", err) + } + + // write the directory entries to disk + err = fs.writeDirectoryEntries(parentDir) + if err != nil { + return fmt.Errorf("error writing directory file %s to disk: %v", pathname, err) + } + + return nil +} + +// Rename renames (moves) oldpath to newpath. If newpath already exists and is not a directory, Rename replaces it. +func (fs *FileSystem) Rename(oldpath, newpath string) error { + // get the path + dir := path.Dir(oldpath) + filename := path.Base(oldpath) + + newDir := path.Dir(newpath) + newname := path.Base(newpath) + if dir != newDir { + return errors.New("can only rename files within the same directory") + } + + // if the dir == filename, then it is just / + if dir == filename { + return fmt.Errorf("cannot rename directory %s as file", oldpath) + } + // get the directory entries + parentDir, entries, err := fs.readDirWithMkdir(dir, false) + if err != nil { + return fmt.Errorf("could not read directory entries for %s", dir) + } + // we now know that the directory exists, see if the file exists + var targetEntry *directoryEntry + for _, e := range entries { + shortName := e.filenameShort + if e.fileExtension != "" { + shortName += "." + e.fileExtension + } + if e.filenameLong != filename && shortName != filename { + continue + } + // if we got this far, we have found the file + targetEntry = e + } + + // see if the file exists + // if the file does not exist, and is not opened for os.O_CREATE, return an error + if targetEntry == nil { + return fmt.Errorf("target file %s does not exist", oldpath) + } + err = parentDir.renameEntry(filename, newname) + if err != nil { + return fmt.Errorf("failed to rename file %s: %v", oldpath, err) + } + + // we need to make sure that clusters are removed which may not be used anymore + _, err = fs.allocateSpace(uint64(parentDir.fileSize), parentDir.clusterLocation) + if err != nil { + return fmt.Errorf("failed to allocate clusters: %v", err) + } + + // write the directory entries to disk + err = fs.writeDirectoryEntries(parentDir) + if err != nil { + return fmt.Errorf("error writing directory file %s to disk: %v", oldpath, err) + } + + return nil +} + // Label get the label of the filesystem from the secial file in the root directory. // The label stored in the boot sector is ignored to mimic Windows behavior which // only stores and reads the label from the special file in the root directory. @@ -647,13 +827,13 @@ func (fs *FileSystem) SetLabel(volumeLabel string) error { // write the boot sector if err := fs.writeBootSector(); err != nil { - return fmt.Errorf("failed to write the boot sector") + return fmt.Errorf("failed to write the boot sector: %w", err) } // locate the filesystem root directory or create it rootDir, dirEntries, err := fs.readDirWithMkdir("/", false) if err != nil { - return fmt.Errorf("failed to locate root directory: %v", err) + return fmt.Errorf("failed to locate root directory: %w", err) } // locate the label entry, it may not exist @@ -671,14 +851,14 @@ func (fs *FileSystem) SetLabel(volumeLabel string) error { } else { _, err = fs.mkLabel(rootDir, volumeLabel) if err != nil { - return fmt.Errorf("failed to create volume label root directory entry '%s': %v", volumeLabel, err) + return fmt.Errorf("failed to create volume label root directory entry '%s': %w", volumeLabel, err) } } // write the root directory entries to disk err = fs.writeDirectoryEntries(rootDir) if err != nil { - return fmt.Errorf("failed to save the root directory to disk: %v", err) + return fmt.Errorf("failed to save the root directory to disk: %w", err) } return nil @@ -689,10 +869,9 @@ func (fs *FileSystem) getClusterList(firstCluster uint32) ([]uint32, error) { // first, get the chain of clusters complete := false cluster := firstCluster - clusters := fs.table.clusters // do we even have a valid cluster? - if _, ok := clusters[cluster]; !ok { + if cluster > fs.table.maxCluster || fs.table.clusters[cluster] == 0 { return nil, fmt.Errorf("invalid start cluster: %d", cluster) } @@ -701,11 +880,13 @@ func (fs *FileSystem) getClusterList(firstCluster uint32) ([]uint32, error) { // save the current cluster clusterList = append(clusterList, cluster) // get the next cluster - newCluster := clusters[cluster] + newCluster := fs.table.clusters[cluster] // if it is EOC, we are done switch { case fs.table.isEoc(newCluster): complete = true + case newCluster > fs.table.maxCluster: + return nil, fmt.Errorf("invalid cluster chain at %d", newCluster) case cluster < 2: return nil, fmt.Errorf("invalid cluster chain at %d", cluster) } @@ -718,7 +899,7 @@ func (fs *FileSystem) getClusterList(firstCluster uint32) ([]uint32, error) { func (fs *FileSystem) readDirectory(dir *Directory) ([]*directoryEntry, error) { clusterList, err := fs.getClusterList(dir.clusterLocation) if err != nil { - return nil, fmt.Errorf("could not read cluster list: %v", err) + return nil, fmt.Errorf("could not read cluster list: %w", err) } // read the data from all of the cluster entries in the list byteCount := len(clusterList) * fs.bytesPerCluster @@ -729,7 +910,7 @@ func (fs *FileSystem) readDirectory(dir *Directory) ([]*directoryEntry, error) { // length of cluster in bytes tmpb := make([]byte, fs.bytesPerCluster) // read the entire cluster - _, _ = fs.file.ReadAt(tmpb, clusterStart) + _, _ = fs.backend.ReadAt(tmpb, clusterStart) b = append(b, tmpb...) } // get the directory @@ -744,29 +925,34 @@ func (fs *FileSystem) mkSubdir(parent *Directory, name string) (*directoryEntry, // get a cluster chain for the file clusters, err := fs.allocateSpace(1, 0) if err != nil { - return nil, fmt.Errorf("could not allocate disk space for file %s: %v", name, err) + return nil, fmt.Errorf("could not allocate disk space for file %s: %w", name, err) } // create a directory entry for the file return parent.createEntry(name, clusters[0], true) } func (fs *FileSystem) writeDirectoryEntries(dir *Directory) error { - // we need to save the entries of theparent + // we need to save the entries of the parent b, err := dir.entriesToBytes(fs.bytesPerCluster) if err != nil { - return fmt.Errorf("could not create a valid byte stream for a FAT32 Entries: %v", err) + return fmt.Errorf("could not create a valid byte stream for a FAT32 Entries: %w", err) + } + + writableFile, err := fs.backend.Writable() + if err != nil { + return err } // now have to expand with zeros to the a multiple of cluster lengths // how many clusters do we need, how many do we have? clusterList, err := fs.getClusterList(dir.clusterLocation) if err != nil { - return fmt.Errorf("unable to get clusters for directory: %v", err) + return fmt.Errorf("unable to get clusters for directory: %w", err) } if len(b) > len(clusterList)*fs.bytesPerCluster { clusters, err := fs.allocateSpace(uint64(len(b)), clusterList[0]) if err != nil { - return fmt.Errorf("unable to allocate space for directory entries: %v", err) + return fmt.Errorf("unable to allocate space for directory entries: %w", err) } clusterList = clusters } @@ -776,9 +962,9 @@ func (fs *FileSystem) writeDirectoryEntries(dir *Directory) error { // bytes where the cluster starts clusterStart := fs.start + int64(fs.dataStart) + int64(cluster-2)*int64(fs.bytesPerCluster) bStart := i * fs.bytesPerCluster - written, err := fs.file.WriteAt(b[bStart:bStart+fs.bytesPerCluster], clusterStart) + written, err := writableFile.WriteAt(b[bStart:bStart+fs.bytesPerCluster], clusterStart) if err != nil { - return fmt.Errorf("error writing directory entries: %v", err) + return fmt.Errorf("error writing directory entries: %w", err) } if written != fs.bytesPerCluster { return fmt.Errorf("wrote %d bytes to cluster %d instead of expected %d", written, cluster, fs.bytesPerCluster) @@ -792,7 +978,7 @@ func (fs *FileSystem) mkFile(parent *Directory, name string) (*directoryEntry, e // get a cluster chain for the file clusters, err := fs.allocateSpace(1, 0) if err != nil { - return nil, fmt.Errorf("could not allocate disk space for directory %s: %v", name, err) + return nil, fmt.Errorf("could not allocate disk space for directory %s: %w", name, err) } // create a directory entry for the file return parent.createEntry(name, clusters[0], false) @@ -824,13 +1010,21 @@ func (fs *FileSystem) readDirWithMkdir(p string, doMake bool) (*Directory, []*di } entries, err = fs.readDirectory(currentDir) if err != nil { - return nil, nil, fmt.Errorf("failed to read directory %s", "/") + return nil, nil, fmt.Errorf("failed to read directory %s: %w", "/", err) } for i, subp := range paths { // do we have an entry whose name is the same as this name? found := false for _, e := range entries { - if e.filenameLong != subp && e.filenameShort != subp && (!e.lowercaseShortname || (e.lowercaseShortname && !strings.EqualFold(e.filenameShort, subp))) { + // don't match volume label + if e.isVolumeLabel { + continue + } + // if the filename does not match, continue + // match is determined by any one of: + // - long filename == provided name + // - uppercase(short filename) == uppercase(provided name) + if !strings.EqualFold(e.filenameLong, subp) && !strings.EqualFold(e.filenameShort, subp) { continue } if !e.isSubdirectory { @@ -851,7 +1045,7 @@ func (fs *FileSystem) readDirWithMkdir(p string, doMake bool) (*Directory, []*di var subdirEntry *directoryEntry subdirEntry, err = fs.mkSubdir(currentDir, subp) if err != nil { - return nil, nil, fmt.Errorf("failed to create subdirectory %s", "/"+strings.Join(paths[0:i+1], "/")) + return nil, nil, fmt.Errorf("failed to create subdirectory %s: %w", "/"+strings.Join(paths[0:i+1], "/"), err) } currentDir.modifyTime = subdirEntry.createTime // make a basic entry for the new subdir @@ -884,12 +1078,12 @@ func (fs *FileSystem) readDirWithMkdir(p string, doMake bool) (*Directory, []*di // write the new directory entries to disk err = fs.writeDirectoryEntries(dir) if err != nil { - return nil, nil, fmt.Errorf("error writing new directory entries to disk: %v", err) + return nil, nil, fmt.Errorf("error writing new directory entries to disk: %w", err) } // write the parent directory entries to disk err = fs.writeDirectoryEntries(currentDir) if err != nil { - return nil, nil, fmt.Errorf("error writing directory entries to disk: %v", err) + return nil, nil, fmt.Errorf("error writing directory entries to disk: %w", err) } // save where we are to search next currentDir = &Directory{ @@ -902,7 +1096,7 @@ func (fs *FileSystem) readDirWithMkdir(p string, doMake bool) (*Directory, []*di // get all of the entries in this directory entries, err = fs.readDirectory(currentDir) if err != nil { - return nil, nil, fmt.Errorf("failed to read directory %s", "/"+strings.Join(paths[0:i+1], "/")) + return nil, nil, fmt.Errorf("failed to read directory %s: %w", "/"+strings.Join(paths[0:i+1], "/"), err) } } // once we have made it here, looping is done; we have found the final entry @@ -915,6 +1109,10 @@ func (fs *FileSystem) readDirWithMkdir(p string, doMake bool) (*Directory, []*di // returns the indexes of clusters to be used in order. If the new size is smaller than // the original size, will shrink the chain. func (fs *FileSystem) allocateSpace(size uint64, previous uint32) ([]uint32, error) { + if previous > fs.table.maxCluster { + return nil, fmt.Errorf("invalid cluster chain at %d", previous) + } + var ( clusters []uint32 err error @@ -923,7 +1121,6 @@ func (fs *FileSystem) allocateSpace(size uint64, previous uint32) ([]uint32, err // 1- calculate how many clusters needed // 2- see how many clusters already are allocated // 3- if needed, allocate new clusters and extend the chain in the FAT table - keys := make([]uint32, 0, 20) allocated := make([]uint32, 0, 20) // what is the total count of clusters needed? @@ -939,7 +1136,7 @@ func (fs *FileSystem) allocateSpace(size uint64, previous uint32) ([]uint32, err if previous >= 2 { clusters, err = fs.getClusterList(previous) if err != nil { - return nil, fmt.Errorf("unable to get cluster list: %v", err) + return nil, fmt.Errorf("unable to get cluster list: %w", err) } originalClusterCount := len(clusters) extraClusterCount = count - originalClusterCount @@ -953,16 +1150,11 @@ func (fs *FileSystem) allocateSpace(size uint64, previous uint32) ([]uint32, err } // get a list of allocated clusters, so we can know which ones are unallocated and therefore allocatable - allClusters := fs.table.clusters maxCluster := fs.table.maxCluster - for k := range allClusters { - keys = append(keys, k) - } - sort.Slice(keys, func(i, j int) bool { return keys[i] < keys[j] }) if extraClusterCount > 0 { for i := uint32(2); i < maxCluster && len(allocated) < extraClusterCount; i++ { - if _, ok := allClusters[i]; !ok { + if fs.table.clusters[i] == 0 { // these become the same at this point allocated = append(allocated, i) } @@ -978,12 +1170,12 @@ func (fs *FileSystem) allocateSpace(size uint64, previous uint32) ([]uint32, err // extend the chain and fill them in if previous > 0 { - allClusters[previous] = allocated[0] + fs.table.clusters[previous] = allocated[0] } for i := 0; i < lastAlloc; i++ { - allClusters[allocated[i]] = allocated[i+1] + fs.table.clusters[allocated[i]] = allocated[i+1] } - allClusters[allocated[lastAlloc]] = fs.table.eocMarker + fs.table.clusters[allocated[lastAlloc]] = fs.table.eocMarker // update the FSIS lastAllocatedCluster = allocated[len(allocated)-1] @@ -999,13 +1191,21 @@ func (fs *FileSystem) allocateSpace(size uint64, previous uint32) ([]uint32, err } deallocated = clusters[lastAlloc+1:] + if uint32(lastAlloc) > fs.table.maxCluster || clusters[lastAlloc] > fs.table.maxCluster { + return nil, fmt.Errorf("invalid cluster chain at %d", lastAlloc) + } + // mark last allocated one as EOC - allClusters[clusters[lastAlloc]] = fs.table.eocMarker + fs.table.clusters[clusters[lastAlloc]] = fs.table.eocMarker // unmark all of the unused ones lastAllocatedCluster = fs.fsis.lastAllocatedCluster for _, cl := range deallocated { - allClusters[cl] = fs.table.unusedMarker + if cl > fs.table.maxCluster { + return nil, fmt.Errorf("invalid cluster chain at %d", cl) + } + + fs.table.clusters[cl] = fs.table.unusedMarker if cl == lastAllocatedCluster { lastAllocatedCluster-- } @@ -1015,12 +1215,12 @@ func (fs *FileSystem) allocateSpace(size uint64, previous uint32) ([]uint32, err // update the FSIS fs.fsis.lastAllocatedCluster = lastAllocatedCluster if err := fs.writeFsis(); err != nil { - return nil, fmt.Errorf("failed to write the file system information sector: %v", err) + return nil, fmt.Errorf("failed to write the file system information sector: %w", err) } // write the FAT tables if err := fs.writeFat(); err != nil { - return nil, fmt.Errorf("failed to write the file allocation table: %v", err) + return nil, fmt.Errorf("failed to write the file allocation table: %w", err) } // return all of the clusters diff --git a/vendor/github.com/diskfs/go-diskfs/filesystem/fat32/file.go b/vendor/github.com/diskfs/go-diskfs/filesystem/fat32/file.go index 16069ce5904..4d274ef7cc0 100644 --- a/vendor/github.com/diskfs/go-diskfs/filesystem/fat32/file.go +++ b/vendor/github.com/diskfs/go-diskfs/filesystem/fat32/file.go @@ -4,6 +4,8 @@ import ( "fmt" "io" "os" + + "github.com/diskfs/go-diskfs/filesystem" ) // File represents a single file in a FAT32 filesystem @@ -16,6 +18,65 @@ type File struct { filesystem *FileSystem } +// Get the full cluster chain of the File. +// Getting this file system internal info can be beneficial for some low-level operations, such as: +// - Performing secure erase. +// - Detecting file fragmentation. +// - Passing Disk locations to a different tool that can work with it. +func (fl *File) GetClusterChain() ([]uint32, error) { + if fl == nil || fl.filesystem == nil { + return nil, os.ErrClosed + } + + fs := fl.filesystem + clusters, err := fs.getClusterList(fl.clusterLocation) + if err != nil { + return nil, fmt.Errorf("unable to get list of clusters for file: %v", err) + } + + return clusters, nil +} + +type DiskRange struct { + Offset uint64 + Length uint64 +} + +// Get the disk ranges occupied by the File. +// Returns an array of disk ranges, where each entry is a contiguous area on disk. +// This information is similar to that returned by GetClusterChain, just in a different format, +// directly returning disk ranges instead of FAT clusters. +func (fl *File) GetDiskRanges() ([]DiskRange, error) { + clusters, err := fl.GetClusterChain() + if err != nil { + return nil, err + } + + fs := fl.filesystem + bytesPerCluster := uint64(fs.bytesPerCluster) + dataStart := uint64(fs.dataStart) + + var ranges []DiskRange + var lastCluster uint32 + + for _, cluster := range clusters { + if lastCluster != 0 && cluster == lastCluster+1 { + // Extend the current range + ranges[len(ranges)-1].Length += bytesPerCluster + } else { + // Add a new range + offset := dataStart + uint64(cluster-2)*bytesPerCluster + ranges = append(ranges, DiskRange{ + Offset: offset, + Length: bytesPerCluster, + }) + } + lastCluster = cluster + } + + return ranges, nil +} + // Read reads up to len(b) bytes from the File. // It returns the number of bytes read and any error encountered. // At end of file, Read returns 0, io.EOF @@ -35,7 +96,7 @@ func (fl *File) Read(b []byte) (int, error) { start := int(fs.dataStart) size := int(fl.fileSize) - int(fl.offset) maxRead := size - file := fs.file + file := fs.backend clusters, err := fs.getClusterList(fl.clusterLocation) if err != nil { return totalRead, fmt.Errorf("unable to get list of clusters for file: %v", err) @@ -78,8 +139,8 @@ func (fl *File) Read(b []byte) (int, error) { if toRead > left { toRead = left } - offset := uint32(start) + (clusters[i]-2)*uint32(bytesPerCluster) - _, _ = file.ReadAt(b[totalRead:totalRead+toRead], int64(offset)+fs.start) + offset := int64(start) + int64(clusters[i]-2)*int64(bytesPerCluster) + _, _ = file.ReadAt(b[totalRead:totalRead+toRead], offset+fs.start) totalRead += toRead if totalRead >= maxRead { break @@ -104,11 +165,17 @@ func (fl *File) Write(p []byte) (int, error) { if fl == nil || fl.filesystem == nil { return 0, os.ErrClosed } + totalWritten := 0 + writableFile, err := fl.filesystem.backend.Writable() + if err != nil { + return totalWritten, err + } + fs := fl.filesystem // if the file was not opened RDWR, nothing we can do if !fl.isReadWrite { - return totalWritten, fmt.Errorf("cannot write to file opened read-only") + return totalWritten, filesystem.ErrReadonlyFilesystem } // what is the new file size? writeSize := len(p) @@ -129,7 +196,6 @@ func (fl *File) Write(p []byte) (int, error) { } // write the content for the file bytesPerCluster := fl.filesystem.bytesPerCluster - file := fl.filesystem.file start := int(fl.filesystem.dataStart) clusterIndex := 0 @@ -146,7 +212,7 @@ func (fl *File) Write(p []byte) (int, error) { if toWrite > int64(len(p)) { toWrite = int64(len(p)) } - _, err := file.WriteAt(p[0:toWrite], offset+fs.start) + _, err := writableFile.WriteAt(p[0:toWrite], offset+fs.start) if err != nil { return totalWritten, fmt.Errorf("unable to write to file: %v", err) } @@ -161,8 +227,8 @@ func (fl *File) Write(p []byte) (int, error) { if toWrite > left { toWrite = left } - offset := uint32(start) + (clusters[i]-2)*uint32(bytesPerCluster) - _, err := file.WriteAt(p[totalWritten:totalWritten+toWrite], int64(offset)+fs.start) + offset := int64(start) + int64(clusters[i]-2)*int64(bytesPerCluster) + _, err := writableFile.WriteAt(p[totalWritten:totalWritten+toWrite], offset+fs.start) if err != nil { return totalWritten, fmt.Errorf("unable to write to file: %v", err) } @@ -206,3 +272,33 @@ func (fl *File) Close() error { fl.filesystem = nil return nil } + +func (fl *File) SetSystem(on bool) error { + fs := fl.filesystem + fl.isSystem = on + return fs.writeDirectoryEntries(fl.parent) +} + +func (fl *File) IsSystem() bool { + return fl.isSystem +} + +func (fl *File) SetHidden(on bool) error { + fs := fl.filesystem + fl.isHidden = on + return fs.writeDirectoryEntries(fl.parent) +} + +func (fl *File) IsHidden() bool { + return fl.isHidden +} + +func (fl *File) SetReadOnly(on bool) error { + fs := fl.filesystem + fl.isReadOnly = on + return fs.writeDirectoryEntries(fl.parent) +} + +func (fl *File) IsReadOnly() bool { + return fl.isReadOnly +} diff --git a/vendor/github.com/diskfs/go-diskfs/filesystem/fat32/fileinfo.go b/vendor/github.com/diskfs/go-diskfs/filesystem/fat32/fileinfo.go index ca0b1b26ac9..cf319ac1d7b 100644 --- a/vendor/github.com/diskfs/go-diskfs/filesystem/fat32/fileinfo.go +++ b/vendor/github.com/diskfs/go-diskfs/filesystem/fat32/fileinfo.go @@ -17,16 +17,22 @@ type FileInfo struct { } // IsDir abbreviation for Mode().IsDir() +// +//nolint:gocritic // we need this to comply with fs.FileInfo func (fi FileInfo) IsDir() bool { return fi.isDir } // ModTime modification time +// +//nolint:gocritic // we need this to comply with fs.FileInfo func (fi FileInfo) ModTime() time.Time { return fi.modTime } // Mode returns file mode +// +//nolint:gocritic // we need this to comply with fs.FileInfo func (fi FileInfo) Mode() os.FileMode { return fi.mode } @@ -34,6 +40,8 @@ func (fi FileInfo) Mode() os.FileMode { // Name base name of the file // // will return the long name of the file. If none exists, returns the shortname and extension +// +//nolint:gocritic // we need this to comply with fs.FileInfo func (fi FileInfo) Name() string { if fi.name != "" { return fi.name @@ -42,16 +50,22 @@ func (fi FileInfo) Name() string { } // ShortName just the 8.3 short name of the file +// +//nolint:gocritic // we need this to comply with fs.FileInfo func (fi FileInfo) ShortName() string { return fi.shortName } // Size length in bytes for regular files +// +//nolint:gocritic // we need this to comply with fs.FileInfo func (fi FileInfo) Size() int64 { return fi.size } // Sys underlying data source - not supported yet and so will return nil +// +//nolint:gocritic // we need this to comply with fs.FileInfo func (fi FileInfo) Sys() interface{} { return nil } diff --git a/vendor/github.com/diskfs/go-diskfs/filesystem/fat32/fsinfosector.go b/vendor/github.com/diskfs/go-diskfs/filesystem/fat32/fsinfosector.go index 1e336d8ab16..9dcf8e7d4cf 100644 --- a/vendor/github.com/diskfs/go-diskfs/filesystem/fat32/fsinfosector.go +++ b/vendor/github.com/diskfs/go-diskfs/filesystem/fat32/fsinfosector.go @@ -19,10 +19,8 @@ const ( const ( // unknownFreeDataClusterCount is the fixed flag for unknown number of free data clusters - //nolint:varcheck,deadcode // keep for future reference unknownFreeDataClusterCount uint32 = 0xffffffff // unknownlastAllocatedCluster is the fixed flag for unknown most recently allocated cluster - //nolint:varcheck,deadcode // keep for future reference unknownlastAllocatedCluster uint32 = 0xffffffff ) diff --git a/vendor/github.com/diskfs/go-diskfs/filesystem/fat32/table.go b/vendor/github.com/diskfs/go-diskfs/filesystem/fat32/table.go index 4210e9f01f1..600c471173f 100644 --- a/vendor/github.com/diskfs/go-diskfs/filesystem/fat32/table.go +++ b/vendor/github.com/diskfs/go-diskfs/filesystem/fat32/table.go @@ -2,7 +2,7 @@ package fat32 import ( "encoding/binary" - "reflect" + "slices" ) // table a FAT32 table @@ -10,7 +10,7 @@ type table struct { fatID uint32 eocMarker uint32 unusedMarker uint32 - clusters map[uint32]uint32 + clusters []uint32 rootDirCluster uint32 size uint32 maxCluster uint32 @@ -28,7 +28,7 @@ func (t *table) equal(a *table) bool { t.rootDirCluster == a.rootDirCluster && t.size == a.size && t.maxCluster == a.maxCluster && - reflect.DeepEqual(t.clusters, a.clusters) + slices.Equal(a.clusters, t.clusters) } /* @@ -37,12 +37,14 @@ func (t *table) equal(a *table) bool { */ func tableFromBytes(b []byte) *table { + maxCluster := uint32(len(b) / 4) + t := table{ fatID: binary.LittleEndian.Uint32(b[0:4]), eocMarker: binary.LittleEndian.Uint32(b[4:8]), size: uint32(len(b)), - clusters: map[uint32]uint32{}, - maxCluster: uint32(len(b) / 4), + clusters: make([]uint32, maxCluster+1), + maxCluster: maxCluster, rootDirCluster: 2, // always 2 for FAT32 } // just need to map the clusters in @@ -71,10 +73,7 @@ func (t *table) bytes() []byte { for i := uint32(2); i < numClusters; i++ { bStart := i * 4 bEnd := bStart + 4 - val := uint32(0) - if cluster, ok := t.clusters[i]; ok { - val = cluster - } + val := t.clusters[i] binary.LittleEndian.PutUint32(b[bStart:bEnd], val) } diff --git a/vendor/github.com/diskfs/go-diskfs/filesystem/filesystem.go b/vendor/github.com/diskfs/go-diskfs/filesystem/filesystem.go index 2c1acfa6c36..94c359b5cdd 100644 --- a/vendor/github.com/diskfs/go-diskfs/filesystem/filesystem.go +++ b/vendor/github.com/diskfs/go-diskfs/filesystem/filesystem.go @@ -3,25 +3,51 @@ package filesystem import ( + "errors" "os" ) +var ( + ErrNotSupported = errors.New("method not supported by this filesystem") + ErrNotImplemented = errors.New("method not implemented (patches are welcome)") + ErrReadonlyFilesystem = errors.New("read-only filesystem") +) + // FileSystem is a reference to a single filesystem on a disk type FileSystem interface { // Type return the type of filesystem Type() Type // Mkdir make a directory - Mkdir(string) error + Mkdir(pathname string) error + // creates a filesystem node (file, device special file, or named pipe) named pathname, + // with attributes specified by mode and dev + Mknod(pathname string, mode uint32, dev int) error + // creates a new link (also known as a hard link) to an existing file. + Link(oldpath, newpath string) error + // creates a symbolic link named linkpath which contains the string target. + Symlink(oldpath, newpath string) error + // Chmod changes the mode of the named file to mode. If the file is a symbolic link, + // it changes the mode of the link's target. + Chmod(name string, mode os.FileMode) error + // Chown changes the numeric uid and gid of the named file. If the file is a symbolic link, + // it changes the uid and gid of the link's target. A uid or gid of -1 means to not change that value + Chown(name string, uid, gid int) error // ReadDir read the contents of a directory - ReadDir(string) ([]os.FileInfo, error) + ReadDir(pathname string) ([]os.FileInfo, error) // OpenFile open a handle to read or write to a file - OpenFile(string, int) (File, error) + OpenFile(pathname string, flag int) (File, error) + // Rename renames (moves) oldpath to newpath. If newpath already exists and is not a directory, Rename replaces it. + Rename(oldpath, newpath string) error + // removes the named file or (empty) directory. + Remove(pathname string) error // Label get the label for the filesystem, or "" if none. Be careful to trim it, as it may contain // leading or following whitespace. The label is passed as-is and not cleaned up at all. Label() string // SetLabel changes the label on the writable filesystem. Different file system may hav different // length constraints. - SetLabel(string) error + SetLabel(label string) error + // Close will cleanup the temporary files created by the filesystem generation steps + Close() error } // Type represents the type of disk this is diff --git a/vendor/github.com/diskfs/go-diskfs/filesystem/iso9660/directoryentry.go b/vendor/github.com/diskfs/go-diskfs/filesystem/iso9660/directoryentry.go index d9877673041..bb1c2100d60 100644 --- a/vendor/github.com/diskfs/go-diskfs/filesystem/iso9660/directoryentry.go +++ b/vendor/github.com/diskfs/go-diskfs/filesystem/iso9660/directoryentry.go @@ -305,7 +305,7 @@ func parseDirEntry(b []byte, f *FileSystem) (*directoryEntry, error) { offset := int64(ce.Offset()) // read it from disk continuationBytes := make([]byte, size) - read, err := f.file.ReadAt(continuationBytes, location*f.blocksize+offset) + read, err := f.backend.ReadAt(continuationBytes, location*f.blocksize+offset) if err != nil { return nil, fmt.Errorf("error reading continuation entry data at %d: %v", location, err) } @@ -373,7 +373,7 @@ func (de *directoryEntry) getLocation(p string) (location, size uint32, err erro current := parts[0] // read the directory bytes dirb := make([]byte, de.size) - n, err := de.filesystem.file.ReadAt(dirb, int64(de.location)*de.filesystem.blocksize) + n, err := de.filesystem.backend.ReadAt(dirb, int64(de.location)*de.filesystem.blocksize) if err != nil { return 0, 0, fmt.Errorf("could not read directory: %v", err) } @@ -400,7 +400,7 @@ func (de *directoryEntry) getLocation(p string) (location, size uint32, err erro return 0, 0, fmt.Errorf("extension %s count not find a filename property: %v", e.ID(), err2) default: checkFilename = filename - //nolint:gosimple // redundant break, but we want this explicit + //nolint:staticcheck // redundant break, but we want this explicit break } } @@ -414,7 +414,7 @@ func (de *directoryEntry) getLocation(p string) (location, size uint32, err erro if location2 != 0 { // need to get the directory entry for the child dirb := make([]byte, de.filesystem.blocksize) - n, err2 := de.filesystem.file.ReadAt(dirb, int64(location2)*de.filesystem.blocksize) + n, err2 := de.filesystem.backend.ReadAt(dirb, int64(location2)*de.filesystem.blocksize) if err2 != nil { return 0, 0, fmt.Errorf("could not read bytes of relocated directory %s from block %d: %v", checkFilename, location2, err2) } @@ -459,7 +459,7 @@ func (de *directoryEntry) Name() string { continue default: name = filename - //nolint:gosimple // redundant break, but we want this explicit + //nolint:staticcheck // redundant break, but we want this explicit break } } diff --git a/vendor/github.com/diskfs/go-diskfs/filesystem/iso9660/eltorito.go b/vendor/github.com/diskfs/go-diskfs/filesystem/iso9660/eltorito.go index a598ef6695f..e3b1b3f9c42 100644 --- a/vendor/github.com/diskfs/go-diskfs/filesystem/iso9660/eltorito.go +++ b/vendor/github.com/diskfs/go-diskfs/filesystem/iso9660/eltorito.go @@ -7,10 +7,10 @@ import ( "os" "github.com/diskfs/go-diskfs/partition/mbr" - "github.com/diskfs/go-diskfs/util" + "github.com/diskfs/go-diskfs/version" ) -//nolint:deadcode,varcheck,unused // we need these references in the future +//nolint:unused // we need these references in the future const ( elToritoSector = 0x11 elToritoDefaultBlocks = 4 @@ -98,7 +98,7 @@ func (et *ElTorito) validationEntry() []byte { b := make([]byte, 0x20) b[0] = 1 b[1] = byte(et.Platform) - copy(b[4:0x1c], util.AppNameVersion) + copy(b[4:0x1c], version.AppName) b[0x1e] = 0x55 b[0x1f] = 0xaa // calculate checksum diff --git a/vendor/github.com/diskfs/go-diskfs/filesystem/iso9660/file.go b/vendor/github.com/diskfs/go-diskfs/filesystem/iso9660/file.go index 83dff8b7966..a00acf9b769 100644 --- a/vendor/github.com/diskfs/go-diskfs/filesystem/iso9660/file.go +++ b/vendor/github.com/diskfs/go-diskfs/filesystem/iso9660/file.go @@ -4,6 +4,8 @@ import ( "fmt" "io" "os" + + "github.com/diskfs/go-diskfs/filesystem" ) // File represents a single file in an iso9660 filesystem @@ -33,7 +35,7 @@ func (fl *File) Read(b []byte) (int, error) { size := int(fl.size) - int(fl.offset) location := int(fl.location) maxRead := size - file := fs.file + file := fs.backend // if there is nothing left to read, just return EOF if size <= 0 { @@ -65,7 +67,7 @@ func (fl *File) Read(b []byte) (int, error) { // // you cannot write to an iso, so this returns an error func (fl *File) Write(_ []byte) (int, error) { - return 0, fmt.Errorf("cannot write to a read-only iso filesystem") + return 0, filesystem.ErrReadonlyFilesystem } // Seek set the offset to a particular point in the file diff --git a/vendor/github.com/diskfs/go-diskfs/filesystem/iso9660/finalize.go b/vendor/github.com/diskfs/go-diskfs/filesystem/iso9660/finalize.go index aaa32629b9f..8a5b5d94b1e 100644 --- a/vendor/github.com/diskfs/go-diskfs/filesystem/iso9660/finalize.go +++ b/vendor/github.com/diskfs/go-diskfs/filesystem/iso9660/finalize.go @@ -4,6 +4,7 @@ import ( "fmt" "io" "io/fs" + "math" "os" "path" "path/filepath" @@ -12,7 +13,8 @@ import ( "strings" "time" - "github.com/diskfs/go-diskfs/util" + "github.com/diskfs/go-diskfs/backend" + "github.com/diskfs/go-diskfs/version" "github.com/djherbis/times" ) @@ -51,8 +53,13 @@ type FinalizeOptions struct { // Nlink() uint32 // number of hardlinks, if supported // Uid() uint32 // uid, if supported // Gid() uint32 // gid, if supported -// -//nolint:structcheck // keep unused members so that we can know their references +type collisionGroup struct { + files []*finalizeFileInfo + parent *finalizeFileInfo // directory containing these files + basename string // the truncated basename they all collide on + extension string // the truncated extension (if any) +} + type finalizeFileInfo struct { path string target string @@ -463,7 +470,11 @@ func (fsm *FileSystem) Finalize(options FinalizeOptions) error { 10- write volume descriptor set terminator */ - f := fsm.file + f, err := fsm.backend.Writable() + if err != nil { + return err + } + blocksize := int(fsm.blocksize) // 1- blank out sectors 0-15 @@ -672,8 +683,10 @@ func (fsm *FileSystem) Finalize(options FinalizeOptions) error { if err != nil { return fmt.Errorf("could not convert directory to bytes: %v", err) } - for i, e := range p { - _, _ = f.WriteAt(e, writeAt+int64(i*blocksize)) + var pos int64 + for _, e := range p { + _, _ = f.WriteAt(e, writeAt+pos) + pos += int64(len(e)) } } @@ -781,7 +794,7 @@ func (fsm *FileSystem) Finalize(options FinalizeOptions) error { pathTableMOptionalLocation: 0, volumeSetIdentifier: "", publisherIdentifier: "", - preparerIdentifier: util.AppNameVersion, + preparerIdentifier: version.AppName, applicationIdentifier: "", copyrightFile: "", // 37 bytes abstractFile: "", // 37 bytes @@ -816,7 +829,7 @@ func (fsm *FileSystem) Finalize(options FinalizeOptions) error { // copyFileData copy data from file `from` at offset `fromOffset` to file `to` at offset `toOffset`. // Copies `size` bytes. If `size` is 0, copies as many bytes as it can. -func copyFileData(from, to util.File, fromOffset, toOffset int64, size int) (int, error) { +func copyFileData(from backend.File, to backend.WritableFile, fromOffset, toOffset int64, size int) (int, error) { buf := make([]byte, 2048) copied := 0 for { @@ -909,10 +922,11 @@ func createPathTable(fi []*finalizeFileInfo) *pathTable { func walkTree(workspace string) ([]*finalizeFileInfo, map[string]*finalizeFileInfo, error) { var ( - dirList = make(map[string]*finalizeFileInfo) - fileList = make([]*finalizeFileInfo, 0) - entry *finalizeFileInfo - serial uint64 + dirList = make(map[string]*finalizeFileInfo) + fileList = make([]*finalizeFileInfo, 0) + collisionGroups = make(map[string]*collisionGroup) + entry *finalizeFileInfo + serial uint64 ) err := filepath.WalkDir(workspace, func(actualPath string, d fs.DirEntry, err error) error { if err != nil { @@ -956,14 +970,120 @@ func walkTree(workspace string) ([]*finalizeFileInfo, map[string]*finalizeFileIn dirList[parentDir] = parentDirInfo fileList = append(fileList, entry) } + + // Add to collision groups (for both files and directories) + // Build collision key: parent path + truncated 8.3 name + truncated := entry.shortname + if entry.extension != "" { + truncated += "." + entry.extension + } + collisionKey := parentDir + "/" + truncated + if collisionGroups[collisionKey] == nil { + collisionGroups[collisionKey] = &collisionGroup{ + files: make([]*finalizeFileInfo, 0), + parent: parentDirInfo, + basename: entry.shortname, + extension: entry.extension, + } + } + collisionGroups[collisionKey].files = append(collisionGroups[collisionKey].files, entry) return nil }) if err != nil { return nil, nil, err } + + // Resolve filename collisions + for _, group := range collisionGroups { + if len(group.files) > 1 { + if err := resolveCollisionGroup(group); err != nil { + return nil, nil, fmt.Errorf("error resolving filename collisions: %v", err) + } + } + } + return fileList, dirList, nil } +// resolveCollisionGroup resolves filename collisions for a group of files +// using xorriso's algorithm from libisofs/ecma119_tree.c +func resolveCollisionGroup(group *collisionGroup) error { + if len(group.files) <= 1 { + return nil + } + + // Build hash table of all names in the parent directory + nameTable := make(map[string]bool) + for _, sibling := range group.parent.children { + name := sibling.shortname + if sibling.extension != "" { + name += "." + sibling.extension + } + nameTable[name] = true + } + + // Calculate minimum digit width needed + digits := len(fmt.Sprintf("%d", len(group.files)-1)) + + // Try increasing digit widths until all files are resolved (max 7 digits) + for digits < 8 { + maxBasename := 8 - digits + + // Truncate basename to max length + basename := group.basename + if len(basename) > maxBasename { + basename = basename[:maxBasename] + } + extension := group.extension + + // Try to assign names to each file in the collision group + change := 0 + maxChange := int(math.Pow10(digits)) + allResolved := true + + for _, file := range group.files { + // Try incrementing change values until we find a unique name + found := false + for change < maxChange { + // Format the candidate name + indexStr := fmt.Sprintf("%0*d", digits, change) + candidate := basename + indexStr + if extension != "" { + candidate += "." + extension + } + change++ + + // Check if this name is already taken + if !nameTable[candidate] { + // Update file shortname in-place + file.shortname = basename + indexStr + nameTable[candidate] = true + found = true + break + } + } + + if !found { + allResolved = false + break + } + } + + if allResolved { + return nil + } + + // Need more digits, try again + digits++ + } + + name := group.basename + if group.extension != "" { + name += "." + group.extension + } + return fmt.Errorf("too many filename collisions for: %s", name) +} + func calculateBlocks(size, blocksize int64) uint32 { blocks := uint32(size / blocksize) // add one for partial @@ -988,5 +1108,17 @@ func calculateShortnameExtension(name string) (shortname, extension string) { shortname = re.ReplaceAllString(shortname, "_") extension = re.ReplaceAllString(extension, "_") + // Truncate to ISO 9660 Level 1 (8.3 format) for maximum compatibility + // This ensures compatibility with tools that expect traditional short names + // Extension: max 3 characters + if len(extension) > 3 { + extension = extension[:3] + } + + // Basename: max 8 characters + if len(shortname) > 8 { + shortname = shortname[:8] + } + return shortname, extension } diff --git a/vendor/github.com/diskfs/go-diskfs/filesystem/iso9660/iso9660.go b/vendor/github.com/diskfs/go-diskfs/filesystem/iso9660/iso9660.go index 64b28ed2ee1..2266058deb7 100644 --- a/vendor/github.com/diskfs/go-diskfs/filesystem/iso9660/iso9660.go +++ b/vendor/github.com/diskfs/go-diskfs/filesystem/iso9660/iso9660.go @@ -5,9 +5,10 @@ import ( "fmt" "os" "path" + "path/filepath" + "github.com/diskfs/go-diskfs/backend" "github.com/diskfs/go-diskfs/filesystem" - "github.com/diskfs/go-diskfs/util" ) const ( @@ -23,7 +24,7 @@ type FileSystem struct { workspace string size int64 start int64 - file util.File + backend backend.Storage blocksize int64 volumes volumeDescriptors pathTable *pathTable @@ -35,7 +36,7 @@ type FileSystem struct { // Equal compare if two filesystems are equal func (fsm *FileSystem) Equal(a *FileSystem) bool { - localMatch := fsm.file == a.file && fsm.size == a.size + localMatch := fsm.backend == a.backend && fsm.size == a.size vdMatch := fsm.volumes.equal(&a.volumes) return localMatch && vdMatch } @@ -47,8 +48,8 @@ func (fsm *FileSystem) Workspace() string { // Create creates an ISO9660 filesystem in a given directory // -// requires the util.File where to create the filesystem, size is the size of the filesystem in bytes, -// start is how far in bytes from the beginning of the util.File to create the filesystem, +// requires the backend.Storage where to create the filesystem, size is the size of the filesystem in bytes, +// start is how far in bytes from the beginning of the backend.Storage to create the filesystem, // and blocksize is is the logical blocksize to use for creating the filesystem // // note that you are *not* required to create the filesystem on the entire disk. You could have a disk of size @@ -60,7 +61,7 @@ func (fsm *FileSystem) Workspace() string { // where a partition starts and ends. // // If the provided blocksize is 0, it will use the default of 2 KB. -func Create(f util.File, size, start, blocksize int64, workspace string) (*FileSystem, error) { +func Create(b backend.Storage, size, start, blocksize int64, workspace string) (*FileSystem, error) { if blocksize == 0 { blocksize = defaultSectorSize } @@ -97,13 +98,16 @@ func Create(f util.File, size, start, blocksize int64, workspace string) (*FileS } } + // sometimes, at least on macos, extra separators in path can cause panic + workdir = filepath.Clean(workdir) + // create root directory // there is nothing in there return &FileSystem{ workspace: workdir, start: start, size: size, - file: f, + backend: b, volumes: volumeDescriptors{}, blocksize: blocksize, }, nil @@ -111,8 +115,8 @@ func Create(f util.File, size, start, blocksize int64, workspace string) (*FileS // Read reads a filesystem from a given disk. // -// requires the util.File where to read the filesystem, size is the size of the filesystem in bytes, -// start is how far in bytes from the beginning of the util.File the filesystem is expected to begin, +// requires the backend.File where to read the filesystem, size is the size of the filesystem in bytes, +// start is how far in bytes from the beginning of the backend.File the filesystem is expected to begin, // and blocksize is is the physical blocksize to use for reading the filesystem // // note that you are *not* required to read a filesystem on the entire disk. You could have a disk of size @@ -124,7 +128,7 @@ func Create(f util.File, size, start, blocksize int64, workspace string) (*FileS // where a partition starts and ends. // // If the provided blocksize is 0, it will use the default of 2K bytes -func Read(file util.File, size, start, blocksize int64) (*FileSystem, error) { +func Read(b backend.Storage, size, start, blocksize int64) (*FileSystem, error) { var read int if blocksize == 0 { @@ -146,7 +150,7 @@ func Read(file util.File, size, start, blocksize int64) (*FileSystem, error) { // load the information from the disk // read system area systemArea := make([]byte, systemAreaSize) - n, err := file.ReadAt(systemArea, start) + n, err := b.ReadAt(systemArea, start) if err != nil { return nil, fmt.Errorf("could not read bytes from file: %v", err) } @@ -156,7 +160,7 @@ func Read(file util.File, size, start, blocksize int64) (*FileSystem, error) { // we do not do anything with the system area for now // next read the volume descriptors, one at a time, until we hit the terminator - vds := make([]volumeDescriptor, 2) + vds := make([]volumeDescriptor, 0, 128) terminated := false var ( pvd *primaryVolumeDescriptor @@ -165,7 +169,7 @@ func Read(file util.File, size, start, blocksize int64) (*FileSystem, error) { for i := 0; !terminated; i++ { vdBytes := make([]byte, volumeDescriptorSize) // read vdBytes - read, err = file.ReadAt(vdBytes, start+systemAreaSize+int64(i)*volumeDescriptorSize) + read, err = b.ReadAt(vdBytes, start+systemAreaSize+int64(i)*volumeDescriptorSize) if err != nil { return nil, fmt.Errorf("unable to read bytes for volume descriptor %d: %v", i, err) } @@ -199,7 +203,7 @@ func Read(file util.File, size, start, blocksize int64) (*FileSystem, error) { rootDirEntry = pvd.rootDirectoryEntry pathTableBytes := make([]byte, pvd.pathTableSize) pathTableLocation := pvd.pathTableLLocation * uint32(pvd.blocksize) - read, err = file.ReadAt(pathTableBytes, int64(pathTableLocation)) + read, err = b.ReadAt(pathTableBytes, int64(pathTableLocation)) if err != nil { return nil, fmt.Errorf("unable to read path table of size %d at location %d: %v", pvd.pathTableSize, pathTableLocation, err) } @@ -212,30 +216,30 @@ func Read(file util.File, size, start, blocksize int64) (*FileSystem, error) { // is system use enabled? location := int64(rootDirEntry.location) * blocksize // get the size of the directory entry - b := make([]byte, 1) - read, err = file.ReadAt(b, location) + dirEntBytes := make([]byte, 1) + read, err = b.ReadAt(dirEntBytes, location) if err != nil { return nil, fmt.Errorf("unable to read root directory size at location %d: %v", location, err) } - if read != len(b) { - return nil, fmt.Errorf("root directory entry size, read %d bytes instead of expected %d", read, len(b)) + if read != len(dirEntBytes) { + return nil, fmt.Errorf("root directory entry size, read %d bytes instead of expected %d", read, len(dirEntBytes)) } - if b[0] == 0 { + if dirEntBytes[0] == 0 { return nil, fmt.Errorf("root directory entry size at location %d was zero, check header and blocksize, given as %d", location, blocksize) } // now read the whole entry - b = make([]byte, b[0]) - read, err = file.ReadAt(b, location) + dirEntBytes = make([]byte, dirEntBytes[0]) + read, err = b.ReadAt(dirEntBytes, location) if err != nil { return nil, fmt.Errorf("unable to read root directory entry at location %d: %v", location, err) } - if read != len(b) { - return nil, fmt.Errorf("root directory entry, read %d bytes instead of expected %d", read, len(b)) + if read != len(dirEntBytes) { + return nil, fmt.Errorf("root directory entry, read %d bytes instead of expected %d", read, len(dirEntBytes)) } // parse it - we do not have any handlers yet - de, err := parseDirEntry(b, &FileSystem{ + de, err := parseDirEntry(dirEntBytes, &FileSystem{ suspEnabled: true, - file: file, + backend: b, blocksize: blocksize, }) if err != nil { @@ -266,7 +270,7 @@ func Read(file util.File, size, start, blocksize int64) (*FileSystem, error) { workspace: "", // no workspace when we do nothing with it start: start, size: size, - file: file, + backend: b, volumes: volumeDescriptors{ descriptors: vds, primary: pvd, @@ -282,6 +286,17 @@ func Read(file util.File, size, start, blocksize int64) (*FileSystem, error) { return fs, nil } +// interface guard +var _ filesystem.FileSystem = (*FileSystem)(nil) + +// Delete the temporary directory created during the iso9660 image creation +func (fsm *FileSystem) Close() error { + if fsm.workspace != "" { + return os.RemoveAll(fsm.workspace) + } + return nil +} + // Type returns the type code for the filesystem. Always returns filesystem.TypeFat32 func (fsm *FileSystem) Type() filesystem.Type { return filesystem.TypeISO9660 @@ -295,7 +310,7 @@ func (fsm *FileSystem) Type() filesystem.Type { // if readonly and not in workspace, will return an error func (fsm *FileSystem) Mkdir(p string) error { if fsm.workspace == "" { - return fmt.Errorf("cannot write to read-only filesystem") + return filesystem.ErrReadonlyFilesystem } err := os.MkdirAll(path.Join(fsm.workspace, p), 0o755) if err != nil { @@ -305,6 +320,50 @@ func (fsm *FileSystem) Mkdir(p string) error { return err } +// creates a filesystem node (file, device special file, or named pipe) named pathname, +// with attributes specified by mode and dev +// +//nolint:revive // parameters will be used eventually +func (fsm *FileSystem) Mknod(pathname string, mode uint32, dev int) error { + // Rock Ridge has device files support + // https://en.wikipedia.org/wiki/ISO_9660#Rock_Ridge + return filesystem.ErrNotImplemented +} + +// creates a new link (also known as a hard link) to an existing file. +func (fsm *FileSystem) Link(_, _ string) error { + return filesystem.ErrNotSupported +} + +// creates a symbolic link named linkpath which contains the string target. +// +//nolint:revive // parameters will be used eventually +func (fsm *FileSystem) Symlink(oldpath, newpath string) error { + // Rock Ridge has symlink support + // https://en.wikipedia.org/wiki/ISO_9660#Rock_Ridge + return filesystem.ErrNotImplemented +} + +// Chmod changes the mode of the named file to mode. If the file is a symbolic link, +// it changes the mode of the link's target. +// +//nolint:revive // parameters will be used eventually +func (fsm *FileSystem) Chmod(name string, mode os.FileMode) error { + // Rock Ridge has UNIX-style file modes support + // https://en.wikipedia.org/wiki/ISO_9660#Rock_Ridge + return filesystem.ErrNotImplemented +} + +// Chown changes the numeric uid and gid of the named file. If the file is a symbolic link, +// it changes the uid and gid of the link's target. A uid or gid of -1 means to not change that value +// +//nolint:revive // parameters will be used eventually +func (fsm *FileSystem) Chown(name string, uid, gid int) error { + // Rock Ridge has user ids and group ids support + // https://en.wikipedia.org/wiki/ISO_9660#Rock_Ridge + return filesystem.ErrNotImplemented +} + // ReadDir return the contents of a given directory in a given filesystem. // // Returns a slice of os.FileInfo with all of the entries in the directory. @@ -368,7 +427,7 @@ func (fsm *FileSystem) OpenFile(p string, flag int) (filesystem.File, error) { writeMode := flag&os.O_WRONLY != 0 || flag&os.O_RDWR != 0 || flag&os.O_APPEND != 0 || flag&os.O_CREATE != 0 || flag&os.O_TRUNC != 0 || flag&os.O_EXCL != 0 if fsm.workspace == "" { if writeMode { - return nil, fmt.Errorf("cannot write to read-only filesystem") + return nil, filesystem.ErrReadonlyFilesystem } // get the directory entries @@ -414,6 +473,21 @@ func (fsm *FileSystem) OpenFile(p string, flag int) (filesystem.File, error) { return f, nil } +// Rename renames (moves) oldpath to newpath. If newpath already exists and is not a directory, Rename replaces it. +func (fsm *FileSystem) Rename(oldpath, newpath string) error { + if fsm.workspace == "" { + return filesystem.ErrReadonlyFilesystem + } + return os.Rename(path.Join(fsm.workspace, oldpath), path.Join(fsm.workspace, newpath)) +} + +func (fsm *FileSystem) Remove(p string) error { + if fsm.workspace == "" { + return filesystem.ErrReadonlyFilesystem + } + return os.Remove(path.Join(fsm.workspace, p)) +} + // readDirectory - read directory entry on iso only (not workspace) func (fsm *FileSystem) readDirectory(p string) ([]*directoryEntry, error) { var ( @@ -439,7 +513,7 @@ func (fsm *FileSystem) readDirectory(p string) ([]*directoryEntry, error) { if location != 0 { // we need 4 bytes to read the size of the directory; it is at offset 10 from beginning dirb := make([]byte, 4) - n, err = fsm.file.ReadAt(dirb, int64(location)*fsm.blocksize+10) + n, err = fsm.backend.ReadAt(dirb, int64(location)*fsm.blocksize+10) if err != nil { return nil, fmt.Errorf("could not read directory %s: %v", p, err) } @@ -464,7 +538,7 @@ func (fsm *FileSystem) readDirectory(p string) ([]*directoryEntry, error) { // we have a location, let's read the directories from it b := make([]byte, size) - n, err = fsm.file.ReadAt(b, int64(location)*fsm.blocksize) + n, err = fsm.backend.ReadAt(b, int64(location)*fsm.blocksize) if err != nil { return nil, fmt.Errorf("could not read directory entries for %s: %v", p, err) } diff --git a/vendor/github.com/diskfs/go-diskfs/filesystem/iso9660/rockridge.go b/vendor/github.com/diskfs/go-diskfs/filesystem/iso9660/rockridge.go index ed20a1d7971..65223196fd9 100644 --- a/vendor/github.com/diskfs/go-diskfs/filesystem/iso9660/rockridge.go +++ b/vendor/github.com/diskfs/go-diskfs/filesystem/iso9660/rockridge.go @@ -188,10 +188,7 @@ func (r *rockRidgeExtension) Relocate(dirs map[string]*finalizeFileInfo) ([]*fin } } // repeat until deepers has no children of depth > 8 - for { - if len(deepers) < 1 { - break - } + for len(deepers) > 0 { for _, e := range deepers { // we have a depth greater than 8, so move it e.trueParent = e.parent @@ -366,17 +363,17 @@ func (d rockRidgePosixAttributes) Merge([]directoryEntrySystemUseExtension) dire func (r *rockRidgeExtension) parsePosixAttributes(b []byte) (directoryEntrySystemUseExtension, error) { targetSize := r.pxLength if len(b) != targetSize { - //nolint:stylecheck // "Rock Ridge" is a proper noun + //nolint:staticcheck // "Rock Ridge" is a proper noun return nil, fmt.Errorf("Rock Ridge PX extension must be %d bytes, but received %d", targetSize, len(b)) } size := b[2] if size != uint8(targetSize) { - //nolint:stylecheck // "Rock Ridge" is a proper noun + //nolint:staticcheck // "Rock Ridge" is a proper noun return nil, fmt.Errorf("Rock Ridge PX extension must be %d bytes, but byte 2 indicated %d", targetSize, size) } version := b[3] if version != 1 { - //nolint:stylecheck // "Rock Ridge" is a proper noun + //nolint:staticcheck // "Rock Ridge" is a proper noun return nil, fmt.Errorf("Rock Ridge PX extension must be version 1, was %d", version) } // file mode @@ -469,17 +466,17 @@ func (d rockRidgePosixDeviceNumber) Merge([]directoryEntrySystemUseExtension) di func (r *rockRidgeExtension) parsePosixDeviceNumber(b []byte) (directoryEntrySystemUseExtension, error) { targetSize := 20 if len(b) != targetSize { - //nolint:stylecheck // "Rock Ridge" is a proper noun + //nolint:staticcheck // "Rock Ridge" is a proper noun return nil, fmt.Errorf("Rock Ridge PN extension must be %d bytes, but received %d", targetSize, len(b)) } size := b[2] if size != uint8(targetSize) { - //nolint:stylecheck // "Rock Ridge" is a proper noun + //nolint:staticcheck // "Rock Ridge" is a proper noun return nil, fmt.Errorf("Rock Ridge PN extension must be %d bytes, but byte 2 indicated %d", targetSize, size) } version := b[3] if version != 1 { - //nolint:stylecheck // "Rock Ridge" is a proper noun + //nolint:staticcheck // "Rock Ridge" is a proper noun return nil, fmt.Errorf("Rock Ridge PN extension must be version 1, was %d", version) } return rockRidgePosixDeviceNumber{ @@ -523,7 +520,7 @@ func (d rockRidgeSymlink) Bytes() []byte { maxComponentSize := directoryEntryMaxSize - headerSize // break the target of the link down into component parts, and then we can calculate the size components := splitPath(d.name) - root := false + var root bool if d.name[0] == "/"[0] { root = true } @@ -593,12 +590,12 @@ func (d rockRidgeSymlink) Merge(links []directoryEntrySystemUseExtension) direct func (r *rockRidgeExtension) parseSymlink(b []byte) (directoryEntrySystemUseExtension, error) { size := int(b[2]) if size != len(b) { - //nolint:stylecheck // "Rock Ridge" is a proper noun + //nolint:staticcheck // "Rock Ridge" is a proper noun return nil, fmt.Errorf("Rock Ridge SL extension received %d bytes, but byte 2 indicated %d", len(b), size) } version := b[3] if version != 1 { - //nolint:stylecheck // "Rock Ridge" is a proper noun + //nolint:staticcheck // "Rock Ridge" is a proper noun return nil, fmt.Errorf("Rock Ridge SL extension must be version 1, was %d", version) } continued := b[4] == 1 @@ -712,12 +709,12 @@ func (d rockRidgeName) Merge(names []directoryEntrySystemUseExtension) directory func (r *rockRidgeExtension) parseName(b []byte) (directoryEntrySystemUseExtension, error) { size := int(b[2]) if size != len(b) { - //nolint:stylecheck // "Rock Ridge" is a proper noun + //nolint:staticcheck // "Rock Ridge" is a proper noun return nil, fmt.Errorf("Rock Ridge NM extension received %d bytes, but byte 2 indicated %d", len(b), size) } version := b[3] if version != 1 { - //nolint:stylecheck // "Rock Ridge" is a proper noun + //nolint:staticcheck // "Rock Ridge" is a proper noun return nil, fmt.Errorf("Rock Ridge NM extension must be version 1, was %d", version) } continued := b[4]&1 != 0 @@ -863,12 +860,12 @@ func (d rockRidgeTimestamps) Merge([]directoryEntrySystemUseExtension) directory func (r *rockRidgeExtension) parseTimestamps(b []byte) (directoryEntrySystemUseExtension, error) { size := b[2] if int(size) != len(b) { - //nolint:stylecheck // "Rock Ridge" is a proper noun + //nolint:staticcheck // "Rock Ridge" is a proper noun return nil, fmt.Errorf("Rock Ridge TF extension has %d bytes, but byte 2 indicated %d", len(b), size) } version := b[3] if version != 1 { - //nolint:stylecheck // "Rock Ridge" is a proper noun + //nolint:staticcheck // "Rock Ridge" is a proper noun return nil, fmt.Errorf("Rock Ridge TF extension must be version 1, was %d", version) } // what timestamps are recorded? @@ -966,17 +963,17 @@ func (d rockRidgeSparseFile) Merge([]directoryEntrySystemUseExtension) directory func (r *rockRidgeExtension) parseSparseFile(b []byte) (directoryEntrySystemUseExtension, error) { targetSize := r.sfLength if len(b) != targetSize { - //nolint:stylecheck // "Rock Ridge" is a proper noun + //nolint:staticcheck // "Rock Ridge" is a proper noun return nil, fmt.Errorf("Rock Ridge SF extension must be %d bytes, but received %d", targetSize, len(b)) } size := b[2] if size != uint8(targetSize) { - //nolint:stylecheck // "Rock Ridge" is a proper noun + //nolint:staticcheck // "Rock Ridge" is a proper noun return nil, fmt.Errorf("Rock Ridge SF extension must be %d bytes, but byte 2 indicated %d", targetSize, size) } version := b[3] if version != 1 { - //nolint:stylecheck // "Rock Ridge" is a proper noun + //nolint:staticcheck // "Rock Ridge" is a proper noun return nil, fmt.Errorf("Rock Ridge SF extension must be version 1, was %d", version) } sf := &rockRidgeSparseFile{ @@ -1030,17 +1027,17 @@ func (d rockRidgeChildDirectory) Merge([]directoryEntrySystemUseExtension) direc func (r *rockRidgeExtension) parseChildDirectory(b []byte) (directoryEntrySystemUseExtension, error) { targetSize := 12 if len(b) != targetSize { - //nolint:stylecheck // "Rock Ridge" is a proper noun + //nolint:staticcheck // "Rock Ridge" is a proper noun return nil, fmt.Errorf("Rock Ridge CL extension must be %d bytes, but received %d", targetSize, len(b)) } size := b[2] if size != uint8(targetSize) { - //nolint:stylecheck // "Rock Ridge" is a proper noun + //nolint:staticcheck // "Rock Ridge" is a proper noun return nil, fmt.Errorf("Rock Ridge CL extension must be %d bytes, but byte 2 indicated %d", targetSize, size) } version := b[3] if version != 1 { - //nolint:stylecheck // "Rock Ridge" is a proper noun + //nolint:staticcheck // "Rock Ridge" is a proper noun return nil, fmt.Errorf("Rock Ridge CL extension must be version 1, was %d", version) } return rockRidgeChildDirectory{ @@ -1088,17 +1085,17 @@ func (d rockRidgeParentDirectory) Merge([]directoryEntrySystemUseExtension) dire func (r *rockRidgeExtension) parseParentDirectory(b []byte) (directoryEntrySystemUseExtension, error) { targetSize := 12 if len(b) != targetSize { - //nolint:stylecheck // "Rock Ridge" is a proper noun + //nolint:staticcheck // "Rock Ridge" is a proper noun return nil, fmt.Errorf("Rock Ridge PL extension must be %d bytes, but received %d", targetSize, len(b)) } size := b[2] if size != uint8(targetSize) { - //nolint:stylecheck // "Rock Ridge" is a proper noun + //nolint:staticcheck // "Rock Ridge" is a proper noun return nil, fmt.Errorf("Rock Ridge PL extension must be %d bytes, but byte 2 indicated %d", targetSize, size) } version := b[3] if version != 1 { - //nolint:stylecheck // "Rock Ridge" is a proper noun + //nolint:staticcheck // "Rock Ridge" is a proper noun return nil, fmt.Errorf("Rock Ridge PL extension must be version 1, was %d", version) } return rockRidgeParentDirectory{ @@ -1143,17 +1140,17 @@ func (d rockRidgeRelocatedDirectory) Merge([]directoryEntrySystemUseExtension) d func (r *rockRidgeExtension) parseRelocatedDirectory(b []byte) (directoryEntrySystemUseExtension, error) { targetSize := 4 if len(b) != targetSize { - //nolint:stylecheck // "Rock Ridge" is a proper noun + //nolint:staticcheck // "Rock Ridge" is a proper noun return nil, fmt.Errorf("Rock Ridge RE extension must be %d bytes, but received %d", targetSize, len(b)) } size := b[2] if size != uint8(targetSize) { - //nolint:stylecheck // "Rock Ridge" is a proper noun + //nolint:staticcheck // "Rock Ridge" is a proper noun return nil, fmt.Errorf("Rock Ridge RE extension must be %d bytes, but byte 2 indicated %d", targetSize, size) } version := b[3] if version != 1 { - //nolint:stylecheck // "Rock Ridge" is a proper noun + //nolint:staticcheck // "Rock Ridge" is a proper noun return nil, fmt.Errorf("Rock Ridge RE extension must be version 1, was %d", version) } return rockRidgeRelocatedDirectory{}, nil diff --git a/vendor/github.com/diskfs/go-diskfs/filesystem/iso9660/statt_others.go b/vendor/github.com/diskfs/go-diskfs/filesystem/iso9660/statt_others.go index 43bd53a68fe..819b8c71976 100644 --- a/vendor/github.com/diskfs/go-diskfs/filesystem/iso9660/statt_others.go +++ b/vendor/github.com/diskfs/go-diskfs/filesystem/iso9660/statt_others.go @@ -19,7 +19,7 @@ func statt(fi os.FileInfo) (links, uid, gid uint32) { return links, uid, gid } -//nolint:deadcode // this is here solely so that linter does not complain on darwin about unconvert +// this is here solely so that linter does not complain on darwin about unconvert func unused() uint32 { var f uint32 = 25 return uint32(f) diff --git a/vendor/github.com/diskfs/go-diskfs/filesystem/iso9660/volume_descriptor.go b/vendor/github.com/diskfs/go-diskfs/filesystem/iso9660/volume_descriptor.go index daf1713f751..3560e0fa4ea 100644 --- a/vendor/github.com/diskfs/go-diskfs/filesystem/iso9660/volume_descriptor.go +++ b/vendor/github.com/diskfs/go-diskfs/filesystem/iso9660/volume_descriptor.go @@ -63,7 +63,6 @@ type bootVolumeDescriptor struct { type terminatorVolumeDescriptor struct { } -//nolint:structcheck // we accept some unused fields as useful for reference type supplementaryVolumeDescriptor struct { volumeFlags uint8 systemIdentifier string // length 32 bytes @@ -184,8 +183,8 @@ func volumeDescriptorFromBytes(b []byte) (volumeDescriptor, error) { } // validate the version version := b[6] - if version != isoVersion { - return nil, fmt.Errorf("mismatched ISO version in Volume Descriptor. Found %x expected %x", version, isoVersion) + if volumeDescriptorType(b[0]) == volumeDescriptorPrimary && version != isoVersion { + return nil, fmt.Errorf("mismatched ISO version in Primary Volume Descriptor. Found %x expected %x", version, isoVersion) } // get the type and data - later we will be more intelligent about this and read actual primary volume info vdType := volumeDescriptorType(b[0]) diff --git a/vendor/github.com/diskfs/go-diskfs/filesystem/squashfs/compressor.go b/vendor/github.com/diskfs/go-diskfs/filesystem/squashfs/compressor.go index 173edabf80f..2b0778a06a8 100644 --- a/vendor/github.com/diskfs/go-diskfs/filesystem/squashfs/compressor.go +++ b/vendor/github.com/diskfs/go-diskfs/filesystem/squashfs/compressor.go @@ -7,8 +7,9 @@ import ( "fmt" "io" + "github.com/anchore/go-lzo" "github.com/klauspost/compress/zstd" - "github.com/pierrec/lz4/v4" + lz4 "github.com/pierrec/lz4/v4" "github.com/ulikunitz/xz" "github.com/ulikunitz/xz/lzma" ) @@ -359,6 +360,34 @@ func (c *CompressorZstd) decompress(in []byte) ([]byte, error) { return p, nil } +type CompressorLzo struct { +} + +func (c *CompressorLzo) compress(_ []byte) ([]byte, error) { + return nil, fmt.Errorf("LZO compression not yet supported") +} +func (c *CompressorLzo) decompress(in []byte) ([]byte, error) { + b := bytes.NewReader(in) + lz := lzo.NewReader(b) + p, err := io.ReadAll(lz) + if err != nil { + return nil, fmt.Errorf("error decompressing: %v", err) + } + return p, nil +} + +//nolint:unused,revive // it is important to implement the interface +func (c *CompressorLzo) loadOptions(b []byte) error { + // lzo has no supported options + return nil +} +func (c *CompressorLzo) optionsBytes() []byte { + return []byte{} +} +func (c *CompressorLzo) flavour() compression { + return compressionLzo +} + func newCompressor(flavour compression) (Compressor, error) { var c Compressor switch flavour { @@ -369,7 +398,7 @@ func newCompressor(flavour compression) (Compressor, error) { case compressionLzma: c = &CompressorLzma{} case compressionLzo: - return nil, fmt.Errorf("LZO compression not yet supported") + c = &CompressorLzo{} case compressionXz: c = &CompressorXz{} case compressionLz4: diff --git a/vendor/github.com/diskfs/go-diskfs/filesystem/squashfs/directoryentry.go b/vendor/github.com/diskfs/go-diskfs/filesystem/squashfs/directoryentry.go index 040212a5825..977ce4394bf 100644 --- a/vendor/github.com/diskfs/go-diskfs/filesystem/squashfs/directoryentry.go +++ b/vendor/github.com/diskfs/go-diskfs/filesystem/squashfs/directoryentry.go @@ -48,7 +48,7 @@ func (d *directoryEntry) equal(o *directoryEntry) bool { if !d.inode.equal(o.inode) { return false } - return d.isSubdirectory == o.isSubdirectory && d.name == o.name && d.size == o.size && d.modTime == o.modTime && d.mode == o.mode + return d.isSubdirectory == o.isSubdirectory && d.name == o.name && d.size == o.size && d.modTime.Equal(o.modTime) && d.mode == o.mode } // Name string // base name of the file @@ -181,7 +181,8 @@ func (d *directoryEntry) Open() (filesystem.File, error) { //nolint:exhaustive // all other cases fall under default switch iType { case inodeBasicFile: - extFile := body.(*basicFile).toExtended() + bFile, _ := body.(*basicFile) + extFile := bFile.toExtended() eFile = &extFile case inodeExtendedFile: eFile, _ = body.(*extendedFile) diff --git a/vendor/github.com/diskfs/go-diskfs/filesystem/squashfs/file.go b/vendor/github.com/diskfs/go-diskfs/filesystem/squashfs/file.go index 1b77d9698b7..d6a3e80ba2e 100644 --- a/vendor/github.com/diskfs/go-diskfs/filesystem/squashfs/file.go +++ b/vendor/github.com/diskfs/go-diskfs/filesystem/squashfs/file.go @@ -4,6 +4,8 @@ import ( "fmt" "io" "os" + + "github.com/diskfs/go-diskfs/filesystem" ) // File represents a single file in a squashfs filesystem @@ -18,6 +20,7 @@ type File struct { offset int64 filesystem *FileSystem blockLocation int64 // the position of the last block decompressed + blockSize uint32 // the size of the last block decompressed, needed for sparse files block []byte // the actual last block decompressed } @@ -45,7 +48,7 @@ func (fl *File) Read(b []byte) (int, error) { // 5- read in and uncompress the necessary blocks fs := fl.filesystem size := fl.size() - fl.offset - location := int64(fl.startBlock) + location := int64(fl.blocksStart) maxRead := len(b) // if there is nothing left to read, just return EOF @@ -102,7 +105,8 @@ func (fl *File) Read(b []byte) (int, error) { return read, fmt.Errorf("unexpected block.size=%d > fs.blocksize=%d", block.size, fs.blocksize) } var input []byte - if fl.blockLocation == location && fl.block != nil { + // check location and size, location can be the same for sparse files but with a size of 0 + if fl.blockLocation == location && fl.blockSize == block.size && fl.block != nil { // Read last block from cache input = fl.block } else { @@ -114,6 +118,7 @@ func (fl *File) Read(b []byte) (int, error) { // Cache the last block fl.blockLocation = location fl.block = input + fl.blockSize = block.size } outputBlock(input) } @@ -148,7 +153,7 @@ func (fl *File) Read(b []byte) (int, error) { // //nolint:unused,revive // but it is important to implement the interface func (fl *File) Write(p []byte) (int, error) { - return 0, fmt.Errorf("cannot write to a read-only squashfs filesystem") + return 0, filesystem.ErrReadonlyFilesystem } // Seek set the offset to a particular point in the file diff --git a/vendor/github.com/diskfs/go-diskfs/filesystem/squashfs/finalize.go b/vendor/github.com/diskfs/go-diskfs/filesystem/squashfs/finalize.go index 7c99e4f2af9..b23cd994c41 100644 --- a/vendor/github.com/diskfs/go-diskfs/filesystem/squashfs/finalize.go +++ b/vendor/github.com/diskfs/go-diskfs/filesystem/squashfs/finalize.go @@ -11,7 +11,7 @@ import ( "strings" "time" - "github.com/diskfs/go-diskfs/util" + "github.com/diskfs/go-diskfs/backend" "github.com/pkg/xattr" ) @@ -86,7 +86,11 @@ func (fs *FileSystem) Finalize(options FinalizeOptions) error { */ - f := fs.file + f, err := fs.backend.Writable() + if err != nil { + return err + } + blocksize := int(fs.blocksize) comp := compressionNone if options.Compression != nil { @@ -133,11 +137,11 @@ func (fs *FileSystem) Finalize(options FinalizeOptions) error { // write file fragments // fragmentBlockStart := location - fragmentBlocks, fragsWritten, err := writeFragmentBlocks(fileList, f, fs.workspace, blocksize, options, fragmentBlockStart) + fragmentBlocks, _, err := writeFragmentBlocks(fileList, f, fs.workspace, blocksize, options, fragmentBlockStart) if err != nil { return fmt.Errorf("error writing file fragment blocks: %v", err) } - location += fragsWritten + location += int64(len(fragmentBlocks) * blocksize) // extract extended attributes, and save them for later; these are written at the very end // this must be done *before* creating inodes, as inodes reference these @@ -356,7 +360,7 @@ func (fs *FileSystem) Finalize(options FinalizeOptions) error { return nil } -func copyFileData(from, to util.File, fromOffset, toOffset, blocksize int64, c Compressor) (raw, compressed int, blocks []*blockData, err error) { +func copyFileData(from backend.File, to backend.WritableFile, fromOffset, toOffset, blocksize int64, c Compressor) (raw, compressed int, blocks []*blockData, err error) { buf := make([]byte, blocksize) blocks = make([]*blockData, 0) for { @@ -392,7 +396,7 @@ func copyFileData(from, to util.File, fromOffset, toOffset, blocksize int64, c C // finalizeFragment write fragment data out to the archive, compressing if relevant. // Returns the total amount written, whether compressed, and any error. -func finalizeFragment(buf []byte, to util.File, toOffset int64, c Compressor) (raw int, compressed bool, err error) { +func finalizeFragment(buf []byte, to backend.WritableFile, toOffset int64, c Compressor) (raw int, compressed bool, err error) { // compress the block if needed if c != nil { out, err := c.compress(buf) @@ -517,7 +521,7 @@ func getTableIdx(m map[uint32]uint16, index uint32) uint16 { return m[index] } -func writeFileDataBlocks(e *finalizeFileInfo, to util.File, ws string, startBlock uint64, blocksize int, compressor Compressor, location int64) (blockCount, compressed int, err error) { +func writeFileDataBlocks(e *finalizeFileInfo, to backend.WritableFile, ws string, startBlock uint64, blocksize int, compressor Compressor, location int64) (blockCount, compressed int, err error) { from, err := os.Open(path.Join(ws, e.path)) if err != nil { return 0, 0, fmt.Errorf("failed to open file for reading %s: %v", e.path, err) @@ -541,7 +545,7 @@ func writeFileDataBlocks(e *finalizeFileInfo, to util.File, ws string, startBloc return blockCount, compressed, nil } -func writeMetadataBlock(buf []byte, to util.File, c Compressor, location int64) (int, error) { +func writeMetadataBlock(buf []byte, to backend.WritableFile, c Compressor, location int64) (int, error) { // compress the block if needed isCompressed := false if c != nil { @@ -569,7 +573,7 @@ func writeMetadataBlock(buf []byte, to util.File, c Compressor, location int64) return len(buf), nil } -func writeDataBlocks(fileList []*finalizeFileInfo, f util.File, ws string, blocksize int, compressor Compressor, location int64) (int, error) { +func writeDataBlocks(fileList []*finalizeFileInfo, f backend.WritableFile, ws string, blocksize int, compressor Compressor, location int64) (int, error) { allBlocks := 0 allWritten := 0 for _, e := range fileList { @@ -584,12 +588,13 @@ func writeDataBlocks(fileList []*finalizeFileInfo, f util.File, ws string, block } allBlocks += blocks allWritten += written + location += int64(written) } return allWritten, nil } // writeFragmentBlocks writes all of the fragment blocks to the archive. Returns slice of blocks written, the total bytes written, any error -func writeFragmentBlocks(fileList []*finalizeFileInfo, f util.File, ws string, blocksize int, options FinalizeOptions, location int64) ([]fragmentBlock, int64, error) { +func writeFragmentBlocks(fileList []*finalizeFileInfo, f backend.WritableFile, ws string, blocksize int, options FinalizeOptions, location int64) ([]fragmentBlock, int64, error) { compressor := options.Compression if options.NoCompressFragments { compressor = nil @@ -633,9 +638,10 @@ func writeFragmentBlocks(fileList []*finalizeFileInfo, f util.File, ws string, b compressed: compressed, location: location, }) + location += int64(blocksize) // increment as all writes will be to next block block fragmentBlockIndex++ - fragmentData = fragmentData[:blocksize] + fragmentData = make([]byte, 0) } e.fragment = &fragmentRef{ @@ -683,7 +689,7 @@ func writeFragmentBlocks(fileList []*finalizeFileInfo, f util.File, ws string, b return fragmentBlocks, allWritten, nil } -func writeInodes(files []*finalizeFileInfo, f util.File, compressor Compressor, location int64) (inodesWritten int, finalLocation uint64, err error) { +func writeInodes(files []*finalizeFileInfo, f backend.WritableFile, compressor Compressor, location int64) (inodesWritten int, finalLocation uint64, err error) { var ( buf []byte maxSize = int(metadataBlockSize) @@ -717,7 +723,7 @@ func writeInodes(files []*finalizeFileInfo, f util.File, compressor Compressor, } // writeDirectories write all directories out to disk. Assumes it already has been optimized. -func writeDirectories(dirs []*finalizeFileInfo, f util.File, compressor Compressor, location int64) (directoriesWritten int, finalLocation uint64, err error) { +func writeDirectories(dirs []*finalizeFileInfo, f backend.WritableFile, compressor Compressor, location int64) (directoriesWritten int, finalLocation uint64, err error) { var ( buf []byte maxSize = int(metadataBlockSize) @@ -756,7 +762,7 @@ func writeDirectories(dirs []*finalizeFileInfo, f util.File, compressor Compress // writeFragmentTable write the fragment table // //nolint:unparam,unused,revive // this does not use fragmentBlocksStart yet, but only because we have not yet added support -func writeFragmentTable(fragmentBlocks []fragmentBlock, fragmentBlocksStart int64, f util.File, compressor Compressor, location int64) (fragmentsWritten int, finalLocation uint64, err error) { +func writeFragmentTable(fragmentBlocks []fragmentBlock, fragmentBlocksStart int64, f backend.WritableFile, compressor Compressor, location int64) (fragmentsWritten int, finalLocation uint64, err error) { // now write the actual fragment table entries var ( indexEntries []uint64 @@ -818,7 +824,7 @@ func writeFragmentTable(fragmentBlocks []fragmentBlock, fragmentBlocksStart int6 } // writeExportTable write the export table at the given location. -func writeExportTable(files []*finalizeFileInfo, f util.File, compressor Compressor, location int64) (entriesWritten int, finalLocation uint64, err error) { +func writeExportTable(files []*finalizeFileInfo, f backend.WritableFile, compressor Compressor, location int64) (entriesWritten int, finalLocation uint64, err error) { var ( maxSize = int(metadataBlockSize) ) @@ -878,7 +884,7 @@ func writeExportTable(files []*finalizeFileInfo, f util.File, compressor Compres } // writeIDTable write the uidgid table at the given location. -func writeIDTable(idtable map[uint32]uint16, f util.File, compressor Compressor, location int64) (entriesWritten int, finalLocation uint64, err error) { +func writeIDTable(idtable map[uint32]uint16, f backend.WritableFile, compressor Compressor, location int64) (entriesWritten int, finalLocation uint64, err error) { var ( maxSize = int(metadataBlockSize) ) @@ -938,7 +944,7 @@ func writeIDTable(idtable map[uint32]uint16, f util.File, compressor Compressor, } // writeXattrs write the xattrs and its lookup table at the given location. -func writeXattrs(xattrs []map[string]string, f util.File, compressor Compressor, location int64) (xattrsWritten int, finalLocation uint64, err error) { +func writeXattrs(xattrs []map[string]string, f backend.WritableFile, compressor Compressor, location int64) (xattrsWritten int, finalLocation uint64, err error) { var ( maxSize = int(metadataBlockSize) offset int @@ -1100,11 +1106,11 @@ func createInodes(fileList []*finalizeFileInfo, idtable map[uint32]uint16, optio if e.startBlock|uint32max != uint32max || e.Size()|int64(uint32max) != int64(uint32max) || len(e.xattrs) > 0 || e.links > 0 { // use extendedFile inode ef := &extendedFile{ - startBlock: e.startBlock, - fileSize: uint64(e.Size()), - blockSizes: e.blocks, - links: e.links, - xAttrIndex: e.xAttrIndex, + blocksStart: uint64(e.dataLocation), + fileSize: uint64(e.Size()), + blockSizes: e.blocks, + links: e.links, + xAttrIndex: e.xAttrIndex, } if e.fragment != nil { ef.fragmentBlockIndex = e.fragment.block @@ -1115,13 +1121,15 @@ func createInodes(fileList []*finalizeFileInfo, idtable map[uint32]uint16, optio } else { // use basicFile bf := &basicFile{ - startBlock: uint32(e.startBlock), - fileSize: uint32(e.Size()), - blockSizes: e.blocks, + blocksStart: uint32(e.dataLocation), + fileSize: uint32(e.Size()), + blockSizes: e.blocks, } if e.fragment != nil { bf.fragmentBlockIndex = e.fragment.block bf.fragmentOffset = e.fragment.offset + } else { + bf.fragmentBlockIndex = 0xffffffff } in = bf inodeT = inodeBasicFile @@ -1444,7 +1452,7 @@ func populateDirectoryLocations(directories []*finalizeFileInfo) { d.directoryLocation = blockPosition{ block: uint32(pos / int(metadataBlockSize)), offset: uint16(pos % int(metadataBlockSize)), - size: len(b), + size: len(b) + 3, } pos += len(b) } diff --git a/vendor/github.com/diskfs/go-diskfs/filesystem/squashfs/finalize_unix.go b/vendor/github.com/diskfs/go-diskfs/filesystem/squashfs/finalize_unix.go index 6051168824a..4c881cb4250 100644 --- a/vendor/github.com/diskfs/go-diskfs/filesystem/squashfs/finalize_unix.go +++ b/vendor/github.com/diskfs/go-diskfs/filesystem/squashfs/finalize_unix.go @@ -30,7 +30,7 @@ func getFileProperties(fi os.FileInfo) (links, uid, gid uint32) { return links, uid, gid } -//nolint:deadcode // this is here solely so that linter does not complain on darwin about unconvert +// this is here solely so that linter does not complain on darwin about unconvert func unused() uint32 { var f uint32 = 25 return uint32(f) diff --git a/vendor/github.com/diskfs/go-diskfs/filesystem/squashfs/finalizefileinfo.go b/vendor/github.com/diskfs/go-diskfs/filesystem/squashfs/finalizefileinfo.go index 10e893bc2a5..af13f3fef53 100644 --- a/vendor/github.com/diskfs/go-diskfs/filesystem/squashfs/finalizefileinfo.go +++ b/vendor/github.com/diskfs/go-diskfs/filesystem/squashfs/finalizefileinfo.go @@ -14,8 +14,6 @@ import ( // ModTime() time.Time // modification time // IsDir() bool // abbreviation for Mode().IsDir() // Sys() interface{} // underlying data source (can return nil) -// -//nolint:structcheck // we are willing to leave unused elements here so that we can know their reference type finalizeFileInfo struct { path string target string diff --git a/vendor/github.com/diskfs/go-diskfs/filesystem/squashfs/fragment.go b/vendor/github.com/diskfs/go-diskfs/filesystem/squashfs/fragment.go index 258b3637e9c..4af9adc0dc6 100644 --- a/vendor/github.com/diskfs/go-diskfs/filesystem/squashfs/fragment.go +++ b/vendor/github.com/diskfs/go-diskfs/filesystem/squashfs/fragment.go @@ -5,7 +5,7 @@ import ( "fmt" ) -//nolint:deadcode,varcheck,unused // we need these references in the future +//nolint:unused // we need these references in the future const ( fragmentEntriesPerBlock = 512 fragmentEntrySize = 16 @@ -17,7 +17,6 @@ type fragmentEntry struct { compressed bool } -//nolint:structcheck,deadcode,unused // we need these references in the future type fragmentTable struct { entries []*fragmentEntry } @@ -40,7 +39,7 @@ func parseFragmentEntry(b []byte) (*fragmentEntry, error) { start := binary.LittleEndian.Uint64(b[0:8]) size := binary.LittleEndian.Uint32(b[8:12]) unCompFlag := uint32(1 << 24) - compressed := true + var compressed = true if size&unCompFlag == unCompFlag { compressed = false } diff --git a/vendor/github.com/diskfs/go-diskfs/filesystem/squashfs/inode.go b/vendor/github.com/diskfs/go-diskfs/filesystem/squashfs/inode.go index a08852b8ba2..b69058048dc 100644 --- a/vendor/github.com/diskfs/go-diskfs/filesystem/squashfs/inode.go +++ b/vendor/github.com/diskfs/go-diskfs/filesystem/squashfs/inode.go @@ -368,7 +368,7 @@ func parseDirectoryIndexes(b []byte, count int) ([]*directoryIndex, error) { // basicFile type basicFile struct { - startBlock uint32 // block count from the start of the data section where data for this file is stored + blocksStart uint32 // The offset from the start of the archive to the first data block. fragmentBlockIndex uint32 fragmentOffset uint32 fileSize uint32 @@ -391,12 +391,12 @@ func (i basicFile) equal(o inodeBody) bool { return false } } - return i.startBlock == oi.startBlock && i.fragmentOffset == oi.fragmentOffset && i.fragmentBlockIndex == oi.fragmentBlockIndex && i.fileSize == oi.fileSize + return i.blocksStart == oi.blocksStart && i.fragmentOffset == oi.fragmentOffset && i.fragmentBlockIndex == oi.fragmentBlockIndex && i.fileSize == oi.fileSize } func (i basicFile) toBytes() []byte { b := make([]byte, 16+4*len(i.blockSizes)) - binary.LittleEndian.PutUint32(b[0:4], i.startBlock) + binary.LittleEndian.PutUint32(b[0:4], i.blocksStart) binary.LittleEndian.PutUint32(b[4:8], i.fragmentBlockIndex) binary.LittleEndian.PutUint32(b[8:12], i.fragmentOffset) binary.LittleEndian.PutUint32(b[12:16], i.fileSize) @@ -413,7 +413,7 @@ func (i basicFile) xattrIndex() (uint32, bool) { } func (i basicFile) toExtended() extendedFile { return extendedFile{ - startBlock: uint64(i.startBlock), + blocksStart: uint64(i.blocksStart), fileSize: uint64(i.fileSize), sparse: 0, links: 0, @@ -433,7 +433,7 @@ func parseBasicFile(b []byte, blocksize int) (*basicFile, int, error) { } fileSize := binary.LittleEndian.Uint32(b[12:16]) d := &basicFile{ - startBlock: binary.LittleEndian.Uint32(b[0:4]), + blocksStart: binary.LittleEndian.Uint32(b[0:4]), fragmentBlockIndex: binary.LittleEndian.Uint32(b[4:8]), fragmentOffset: binary.LittleEndian.Uint32(b[8:12]), fileSize: fileSize, @@ -455,7 +455,7 @@ func parseBasicFile(b []byte, blocksize int) (*basicFile, int, error) { // extendedFile type extendedFile struct { - startBlock uint64 + blocksStart uint64 fileSize uint64 sparse uint64 links uint32 @@ -481,7 +481,7 @@ func (i extendedFile) equal(o inodeBody) bool { return false } } - return i.startBlock == oi.startBlock && + return i.blocksStart == oi.blocksStart && i.fragmentOffset == oi.fragmentOffset && i.fragmentBlockIndex == oi.fragmentBlockIndex && i.fileSize == oi.fileSize && @@ -492,7 +492,7 @@ func (i extendedFile) equal(o inodeBody) bool { func (i extendedFile) toBytes() []byte { b := make([]byte, 40+4*len(i.blockSizes)) - binary.LittleEndian.PutUint64(b[0:8], i.startBlock) + binary.LittleEndian.PutUint64(b[0:8], i.blocksStart) binary.LittleEndian.PutUint64(b[8:16], i.fileSize) binary.LittleEndian.PutUint64(b[16:24], i.sparse) binary.LittleEndian.PutUint32(b[24:28], i.links) @@ -521,7 +521,7 @@ func parseExtendedFile(b []byte, blocksize int) (*extendedFile, int, error) { } fileSize := binary.LittleEndian.Uint64(b[8:16]) d := &extendedFile{ - startBlock: binary.LittleEndian.Uint64(b[0:8]), + blocksStart: binary.LittleEndian.Uint64(b[0:8]), fileSize: fileSize, sparse: binary.LittleEndian.Uint64(b[16:24]), links: binary.LittleEndian.Uint32(b[24:28]), @@ -639,11 +639,12 @@ func parseExtendedSymlink(b []byte) (*extendedSymlink, int, error) { s := &extendedSymlink{ links: binary.LittleEndian.Uint32(b[0:4]), } + targetSize := int(binary.LittleEndian.Uint32(b[4:8])) // account for the synlink target, plus 4 bytes for the xattr index after it - extra = int(binary.LittleEndian.Uint32(b[4:8])) + 4 - if len(b[target:]) > extra { - s.target = string(b[8 : 8+extra]) - s.xAttrIndex = binary.LittleEndian.Uint32(b[8+extra : 8+extra+4]) + extra = targetSize + 4 + if len(b) >= extra+target { + s.target = string(b[target : target+targetSize]) + s.xAttrIndex = binary.LittleEndian.Uint32(b[target+targetSize : target+targetSize+4]) extra = 0 } return s, extra, nil @@ -862,7 +863,7 @@ type extendedSocket struct { // idTable is an indexed table of IDs // -//nolint:deadcode // we need these references in the future +// we need these references in the future type idTable []uint32 // parseInodeBody parse the body of an inode. This only parses the non-variable size part, diff --git a/vendor/github.com/diskfs/go-diskfs/filesystem/squashfs/squashfs.go b/vendor/github.com/diskfs/go-diskfs/filesystem/squashfs/squashfs.go index bdd8d5a7eef..dace30e0901 100644 --- a/vendor/github.com/diskfs/go-diskfs/filesystem/squashfs/squashfs.go +++ b/vendor/github.com/diskfs/go-diskfs/filesystem/squashfs/squashfs.go @@ -8,8 +8,8 @@ import ( "os" "path" + "github.com/diskfs/go-diskfs/backend" "github.com/diskfs/go-diskfs/filesystem" - "github.com/diskfs/go-diskfs/util" ) const ( @@ -26,7 +26,7 @@ type FileSystem struct { superblock *superblock size int64 start int64 - file util.File + backend backend.Storage blocksize int64 compressor Compressor fragments []*fragmentEntry @@ -38,7 +38,7 @@ type FileSystem struct { // Equal compare if two filesystems are equal func (fs *FileSystem) Equal(a *FileSystem) bool { - localMatch := fs.file == a.file && fs.size == a.size + localMatch := fs.backend == a.backend && fs.size == a.size superblockMatch := fs.superblock.equal(a.superblock) return localMatch && superblockMatch } @@ -49,7 +49,7 @@ func (fs *FileSystem) Label() string { } func (fs *FileSystem) SetLabel(string) error { - return fmt.Errorf("SquashFS filesystem is read-only") + return filesystem.ErrReadonlyFilesystem } // Workspace get the workspace path @@ -59,8 +59,8 @@ func (fs *FileSystem) Workspace() string { // Create creates a squashfs filesystem in a given directory // -// requires the util.File where to create the filesystem, size is the size of the filesystem in bytes, -// start is how far in bytes from the beginning of the util.File to create the filesystem, +// requires the backend.Storage where to create the filesystem, size is the size of the filesystem in bytes, +// start is how far in bytes from the beginning of the backend.Storage to create the filesystem, // and blocksize is is the logical blocksize to use for creating the filesystem // // note that you are *not* required to create the filesystem on the entire disk. You could have a disk of size @@ -72,7 +72,7 @@ func (fs *FileSystem) Workspace() string { // where a partition starts and ends. // // If the provided blocksize is 0, it will use the default of 128 KB. -func Create(f util.File, size, start, blocksize int64) (*FileSystem, error) { +func Create(b backend.Storage, size, start, blocksize int64) (*FileSystem, error) { if blocksize == 0 { blocksize = defaultBlockSize } @@ -94,15 +94,15 @@ func Create(f util.File, size, start, blocksize int64) (*FileSystem, error) { workspace: tmpdir, start: start, size: size, - file: f, + backend: b, blocksize: blocksize, }, nil } // Read reads a filesystem from a given disk. // -// requires the util.File where to read the filesystem, size is the size of the filesystem in bytes, -// start is how far in bytes from the beginning of the util.File the filesystem is expected to begin, +// requires the backend.Storage where to read the filesystem, size is the size of the filesystem in bytes, +// start is how far in bytes from the beginning of the backend.Storage the filesystem is expected to begin, // and blocksize is is the logical blocksize to use for creating the filesystem // // note that you are *not* required to read a filesystem on the entire disk. You could have a disk of size @@ -145,7 +145,7 @@ func Create(f util.File, size, start, blocksize int64) (*FileSystem, error) { // uses this library like this: // // rclone -P --transfers 16 --checkers 16 copy :archive:/path/to/tensorflow.sqfs /tmp/tensorflow -func Read(file util.File, size, start, blocksize int64) (*FileSystem, error) { +func Read(b backend.Storage, size, start, blocksize int64) (*FileSystem, error) { var ( read int err error @@ -162,8 +162,8 @@ func Read(file util.File, size, start, blocksize int64) (*FileSystem, error) { // load the information from the disk // read the superblock - b := make([]byte, superblockSize) - read, err = file.ReadAt(b, start) + superblockBytes := make([]byte, superblockSize) + read, err = b.ReadAt(superblockBytes, start) if err != nil { return nil, fmt.Errorf("unable to read bytes for superblock: %v", err) } @@ -172,7 +172,7 @@ func Read(file util.File, size, start, blocksize int64) (*FileSystem, error) { } // parse superblock - s, err := parseSuperblock(b) + s, err := parseSuperblock(superblockBytes) if err != nil { return nil, fmt.Errorf("error parsing superblock: %v", err) } @@ -184,7 +184,7 @@ func Read(file util.File, size, start, blocksize int64) (*FileSystem, error) { } // load fragments - fragments, err := readFragmentTable(s, file, compress) + fragments, err := readFragmentTable(s, b, compress) if err != nil { return nil, fmt.Errorf("error reading fragments: %v", err) } @@ -195,14 +195,14 @@ func Read(file util.File, size, start, blocksize int64) (*FileSystem, error) { ) if !s.noXattrs && s.xattrTableStart != 0xffff_ffff_ffff_ffff { // xattr is right to the end of the disk - xattrs, err = readXattrsTable(s, file, compress) + xattrs, err = readXattrsTable(s, b, compress) if err != nil { return nil, fmt.Errorf("error reading xattr table: %v", err) } } // read uidsgids - uidsgids, err := readUidsGids(s, file, compress) + uidsgids, err := readUidsGids(s, b, compress) if err != nil { return nil, fmt.Errorf("error reading uids/gids: %v", err) } @@ -211,7 +211,7 @@ func Read(file util.File, size, start, blocksize int64) (*FileSystem, error) { workspace: "", // no workspace when we do nothing with it start: start, size: size, - file: file, + backend: b, superblock: s, blocksize: int64(s.blocksize), // use the blocksize in the superblock xattrs: xattrs, @@ -229,6 +229,17 @@ func Read(file util.File, size, start, blocksize int64) (*FileSystem, error) { return fs, nil } +// interface guard +var _ filesystem.FileSystem = (*FileSystem)(nil) + +// Delete the temporary directory created during the SquashFS image creation +func (fs *FileSystem) Close() error { + if fs.workspace != "" { + return os.RemoveAll(fs.workspace) + } + return nil +} + // Type returns the type code for the filesystem. Always returns filesystem.TypeFat32 func (fs *FileSystem) Type() filesystem.Type { return filesystem.TypeSquashfs @@ -266,7 +277,7 @@ func (fs *FileSystem) GetCacheSize() int { // if readonly and not in workspace, will return an error func (fs *FileSystem) Mkdir(p string) error { if fs.workspace == "" { - return fmt.Errorf("cannot write to read-only filesystem") + return filesystem.ErrReadonlyFilesystem } err := os.MkdirAll(path.Join(fs.workspace, p), 0o755) if err != nil { @@ -276,6 +287,50 @@ func (fs *FileSystem) Mkdir(p string) error { return err } +// creates a filesystem node (file, device special file, or named pipe) named pathname, +// with attributes specified by mode and dev +// +//nolint:revive // parameters will be used eventually +func (fs *FileSystem) Mknod(pathname string, mode uint32, dev int) error { + // https://dr-emann.github.io/squashfs/squashfs.html#_device_special_files + // https://dr-emann.github.io/squashfs/squashfs.html#_ipc_inodes_fifo_or_socket + return filesystem.ErrNotImplemented +} + +// creates a new link (also known as a hard link) to an existing file. +// +//nolint:revive // parameters will be used eventually +func (fs *FileSystem) Link(oldpath, newpath string) error { + // https://dr-emann.github.io/squashfs/squashfs.html#_symbolic_links + return filesystem.ErrNotImplemented +} + +// creates a symbolic link named linkpath which contains the string target. +// +//nolint:revive // parameters will be used eventually +func (fs *FileSystem) Symlink(oldpath, newpath string) error { + // https://dr-emann.github.io/squashfs/squashfs.html#_symbolic_links + return filesystem.ErrNotImplemented +} + +// Chmod changes the mode of the named file to mode. If the file is a symbolic link, +// it changes the mode of the link's target. +// +//nolint:revive // parameters will be used eventually +func (fs *FileSystem) Chmod(name string, mode os.FileMode) error { + // https://dr-emann.github.io/squashfs/squashfs.html#_common_inode_header + return filesystem.ErrNotImplemented +} + +// Chown changes the numeric uid and gid of the named file. If the file is a symbolic link, +// it changes the uid and gid of the link's target. A uid or gid of -1 means to not change that value +// +//nolint:revive // parameters will be used eventually +func (fs *FileSystem) Chown(name string, uid, gid int) error { + // https://dr-emann.github.io/squashfs/squashfs.html#_id_table + return filesystem.ErrNotImplemented +} + // ReadDir return the contents of a given directory in a given filesystem. // // Returns a slice of os.FileInfo with all of the entries in the directory. @@ -336,7 +391,7 @@ func (fs *FileSystem) OpenFile(p string, flag int) (filesystem.File, error) { writeMode := flag&os.O_WRONLY != 0 || flag&os.O_RDWR != 0 || flag&os.O_APPEND != 0 || flag&os.O_CREATE != 0 || flag&os.O_TRUNC != 0 || flag&os.O_EXCL != 0 if fs.workspace == "" { if writeMode { - return nil, fmt.Errorf("cannot write to read-only filesystem") + return nil, filesystem.ErrReadonlyFilesystem } // get the directory entries @@ -379,6 +434,21 @@ func (fs *FileSystem) OpenFile(p string, flag int) (filesystem.File, error) { return f, nil } +// Rename renames (moves) oldpath to newpath. If newpath already exists and is not a directory, Rename replaces it. +func (fs *FileSystem) Rename(oldpath, newpath string) error { + if fs.workspace == "" { + return filesystem.ErrReadonlyFilesystem + } + return os.Rename(path.Join(fs.workspace, oldpath), path.Join(fs.workspace, newpath)) +} + +func (fs *FileSystem) Remove(p string) error { + if fs.workspace == "" { + return filesystem.ErrReadonlyFilesystem + } + return os.Remove(path.Join(fs.workspace, p)) +} + // readDirectory - read directory entry on squashfs only (not workspace) func (fs *FileSystem) readDirectory(p string) ([]*directoryEntry, error) { // use the root inode to find the location of the root direectory in the table @@ -470,7 +540,7 @@ func (fs *FileSystem) hydrateDirectoryEntries(entries []*directoryEntryRaw) ([]* body, header := in.getBody(), in.getHeader() xattrIndex, has := body.xattrIndex() xattrs := map[string]string{} - if has && xattrIndex != noXattrInodeFlag { + if has { xattrs, err = fs.xattrs.find(int(xattrIndex)) if err != nil { return nil, fmt.Errorf("error reading xattrs for %s: %v", e.name, err) @@ -500,7 +570,7 @@ func (fs *FileSystem) getInode(blockOffset uint32, byteOffset uint16, iType inod // get the block // start by getting the minimum for the proposed type. It very well might be wrong. size := inodeTypeToSize(iType) - uncompressed, err := fs.readMetadata(fs.file, fs.compressor, int64(fs.superblock.inodeTableStart), blockOffset, byteOffset, size) + uncompressed, err := fs.readMetadata(fs.backend, fs.compressor, int64(fs.superblock.inodeTableStart), blockOffset, byteOffset, size) if err != nil { return nil, fmt.Errorf("error reading block at position %d: %v", blockOffset, err) } @@ -514,7 +584,7 @@ func (fs *FileSystem) getInode(blockOffset uint32, byteOffset uint16, iType inod size = inodeTypeToSize(iType) // Read more data if necessary (quite rare) if size > len(uncompressed) { - uncompressed, err = fs.readMetadata(fs.file, fs.compressor, int64(fs.superblock.inodeTableStart), blockOffset, byteOffset, size) + uncompressed, err = fs.readMetadata(fs.backend, fs.compressor, int64(fs.superblock.inodeTableStart), blockOffset, byteOffset, size) if err != nil { return nil, fmt.Errorf("error reading block at position %d: %v", blockOffset, err) } @@ -528,7 +598,7 @@ func (fs *FileSystem) getInode(blockOffset uint32, byteOffset uint16, iType inod // if it returns extra > 0, then it needs that many more bytes to be read, and to be reparsed if extra > 0 { size += extra - uncompressed, err = fs.readMetadata(fs.file, fs.compressor, int64(fs.superblock.inodeTableStart), blockOffset, byteOffset, size) + uncompressed, err = fs.readMetadata(fs.backend, fs.compressor, int64(fs.superblock.inodeTableStart), blockOffset, byteOffset, size) if err != nil { return nil, fmt.Errorf("error reading block at position %d: %v", blockOffset, err) } @@ -548,7 +618,7 @@ func (fs *FileSystem) getInode(blockOffset uint32, byteOffset uint16, iType inod // block when uncompressed. func (fs *FileSystem) getDirectory(blockOffset uint32, byteOffset uint16, size int) (*directory, error) { // get the block - uncompressed, err := fs.readMetadata(fs.file, fs.compressor, int64(fs.superblock.directoryTableStart), blockOffset, byteOffset, size) + uncompressed, err := fs.readMetadata(fs.backend, fs.compressor, int64(fs.superblock.directoryTableStart), blockOffset, byteOffset, size) if err != nil { return nil, fmt.Errorf("error reading block at position %d: %v", blockOffset, err) } @@ -566,7 +636,7 @@ func (fs *FileSystem) readBlock(location int64, compressed bool, size uint32) ([ return make([]byte, fs.superblock.blocksize), nil } b := make([]byte, size) - read, err := fs.file.ReadAt(b, location) + read, err := fs.backend.ReadAt(b, location) if err != nil && err != io.EOF { return nil, fmt.Errorf("error reading block %d: %v", location, err) } @@ -595,7 +665,7 @@ func (fs *FileSystem) readFragment(index, offset uint32, fragmentSize int64) ([] data, _, err := fs.cache.get(pos, func() (data []byte, size uint16, err error) { // figure out the size of the compressed block and if it is compressed b := make([]byte, fragmentInfo.size) - read, err := fs.file.ReadAt(b, pos) + read, err := fs.backend.ReadAt(b, pos) if err != nil && err != io.EOF { return nil, 0, fmt.Errorf("unable to read fragment block %d: %v", index, err) } @@ -636,7 +706,7 @@ func validateBlocksize(blocksize int64) error { return nil } -func readFragmentTable(s *superblock, file util.File, c Compressor) ([]*fragmentEntry, error) { +func readFragmentTable(s *superblock, file backend.File, c Compressor) ([]*fragmentEntry, error) { // get the first level index, which is just the pointers to the fragment table metadata blocks blockCount := s.fragmentCount / 512 if s.fragmentCount%512 > 0 { @@ -693,7 +763,7 @@ To read the xattr table: 6- Read the id metablocks based on the indexes and uncompress if needed 7- Read all of the xattr metadata. It starts at the location indicated by the header, and ends at the id table */ -func readXattrsTable(s *superblock, file util.File, c Compressor) (*xAttrTable, error) { +func readXattrsTable(s *superblock, file backend.File, c Compressor) (*xAttrTable, error) { // first read the header b := make([]byte, xAttrHeaderSize) read, err := file.ReadAt(b, int64(s.xattrTableStart)) @@ -747,6 +817,7 @@ func readXattrsTable(s *superblock, file util.File, c Compressor) (*xAttrTable, // now load the actual xAttrs data xAttrEnd := binary.LittleEndian.Uint64(b[:8]) xAttrData := make([]byte, 0) + offsetMap := map[uint32]uint32{0: 0} for i := xAttrStart; i < xAttrEnd; { uncompressed, size, err = fs.readMetaBlock(file, c, int64(i)) if err != nil { @@ -754,15 +825,16 @@ func readXattrsTable(s *superblock, file util.File, c Compressor) (*xAttrTable, } xAttrData = append(xAttrData, uncompressed...) i += uint64(size) + offsetMap[uint32(i-xAttrStart)] = uint32(len(xAttrData)) } // now have all of the indexes and metadata loaded // need to pass it the offset of the beginning of the id table from the beginning of the disk - return parseXattrsTable(xAttrData, bIndex, s.idTableStart, c) + return parseXattrsTable(xAttrData, bIndex, offsetMap, c) } -//nolint:unparam,unused,revive // this does not use offset or compressor yet, but only because we have not yet added support -func parseXattrsTable(bUIDXattr, bIndex []byte, offset uint64, c Compressor) (*xAttrTable, error) { +//nolint:unparam,unused,revive // this does not use compressor yet, but only because we have not yet added support +func parseXattrsTable(bUIDXattr, bIndex []byte, offsetMap map[uint32]uint32, c Compressor) (*xAttrTable, error) { // create the ID list var ( xAttrIDList []*xAttrIndex @@ -770,7 +842,7 @@ func parseXattrsTable(bUIDXattr, bIndex []byte, offset uint64, c Compressor) (*x entrySize := int(xAttrIDEntrySize) for i := 0; i+entrySize <= len(bIndex); i += entrySize { - entry, err := parseXAttrIndex(bIndex[i:]) + entry, err := parseXAttrIndex(bIndex[i:], offsetMap) if err != nil { return nil, fmt.Errorf("error parsing xAttr ID table entry in position %d: %v", i, err) } @@ -796,7 +868,7 @@ To read the uids/gids table: 4- Read the indexes. They are uncompressed, 8 bytes each (uint64); one index per id metablock 5- Read the id metablocks based on the indexes and uncompress if needed */ -func readUidsGids(s *superblock, file util.File, c Compressor) ([]uint32, error) { +func readUidsGids(s *superblock, file backend.File, c Compressor) ([]uint32, error) { // find out how many xattr IDs we have and where the metadata starts. The table always starts // with this information idStart := s.idTableStart diff --git a/vendor/github.com/diskfs/go-diskfs/filesystem/squashfs/superblock.go b/vendor/github.com/diskfs/go-diskfs/filesystem/squashfs/superblock.go index f891d3e6374..5fc72bf08b3 100644 --- a/vendor/github.com/diskfs/go-diskfs/filesystem/squashfs/superblock.go +++ b/vendor/github.com/diskfs/go-diskfs/filesystem/squashfs/superblock.go @@ -168,7 +168,7 @@ func (s *superblock) toBytes() []byte { binary.LittleEndian.PutUint32(b[16:20], s.fragmentCount) binary.LittleEndian.PutUint16(b[20:22], uint16(s.compression)) binary.LittleEndian.PutUint16(b[22:24], uint16(math.Log2(float64(s.blocksize)))) - copy(b[24:26], s.superblockFlags.bytes()) + copy(b[24:26], s.superblockFlags.bytes()) //nolint:staticcheck // this could be as s.bytes() but it would be more confusing binary.LittleEndian.PutUint16(b[26:28], s.idCount) binary.LittleEndian.PutUint16(b[28:30], superblockMajorVersion) binary.LittleEndian.PutUint16(b[30:32], superblockMinorVersion) diff --git a/vendor/github.com/diskfs/go-diskfs/filesystem/squashfs/xattr.go b/vendor/github.com/diskfs/go-diskfs/filesystem/squashfs/xattr.go index e1894217d3d..12e4cc114b3 100644 --- a/vendor/github.com/diskfs/go-diskfs/filesystem/squashfs/xattr.go +++ b/vendor/github.com/diskfs/go-diskfs/filesystem/squashfs/xattr.go @@ -18,15 +18,23 @@ type xAttrIndex struct { size uint32 } -func parseXAttrIndex(b []byte) (*xAttrIndex, error) { +func parseXAttrIndex(b []byte, offsetMap map[uint32]uint32) (*xAttrIndex, error) { if len(b) < int(xAttrIDEntrySize) { return nil, fmt.Errorf("cannot parse xAttr Index of size %d less than minimum %d", len(b), xAttrIDEntrySize) } - return &xAttrIndex{ - pos: binary.LittleEndian.Uint64(b[0:8]), + + offsetKey := binary.LittleEndian.Uint32(b[2:6]) + offset, ok := offsetMap[offsetKey] + if !ok { + return nil, fmt.Errorf("cannot parse xAttr Index invalid offset key %d", offsetKey) + } + toReturn := &xAttrIndex{ + pos: uint64(binary.LittleEndian.Uint16(b[0:2])) + uint64(offset), count: binary.LittleEndian.Uint32(b[8:12]), size: binary.LittleEndian.Uint32(b[12:16]), - }, nil + } + + return toReturn, nil } type xAttrTable struct { @@ -39,6 +47,9 @@ func (x *xAttrTable) find(pos int) (map[string]string, error) { return nil, fmt.Errorf("position %d is greater than list size %d", pos, len(x.list)) } entry := x.list[pos] + if int(entry.pos) >= len(x.data) { + return nil, fmt.Errorf("entry position %d is greater than list size %d", entry.pos, len(x.data)) + } b := x.data[entry.pos:] count := entry.count ptr := 0 diff --git a/vendor/github.com/diskfs/go-diskfs/partition/gpt/partition.go b/vendor/github.com/diskfs/go-diskfs/partition/gpt/partition.go index bfa0e66270a..82ee7ab3d9d 100644 --- a/vendor/github.com/diskfs/go-diskfs/partition/gpt/partition.go +++ b/vendor/github.com/diskfs/go-diskfs/partition/gpt/partition.go @@ -9,7 +9,7 @@ import ( "strings" "unicode/utf16" - "github.com/diskfs/go-diskfs/util" + "github.com/diskfs/go-diskfs/backend" uuid "github.com/google/uuid" ) @@ -147,7 +147,7 @@ func (p *Partition) GetStart() int64 { // WriteContents fills the partition with the contents provided // reads from beginning of reader to exactly size of partition in bytes -func (p *Partition) WriteContents(f util.File, contents io.Reader) (uint64, error) { +func (p *Partition) WriteContents(f backend.WritableFile, contents io.Reader) (uint64, error) { pss, lss := p.sectorSizes() total := uint64(0) // validate start/end/size @@ -202,7 +202,7 @@ func (p *Partition) WriteContents(f util.File, contents io.Reader) (uint64, erro // ReadContents reads the contents of the partition into a writer // streams the entire partition to the writer -func (p *Partition) ReadContents(f util.File, out io.Writer) (int64, error) { +func (p *Partition) ReadContents(f backend.File, out io.Writer) (int64, error) { pss, _ := p.sectorSizes() total := int64(0) // chunks of physical sector size for efficient writing diff --git a/vendor/github.com/diskfs/go-diskfs/partition/gpt/table.go b/vendor/github.com/diskfs/go-diskfs/partition/gpt/table.go index 132b3578e01..af01c3f365d 100644 --- a/vendor/github.com/diskfs/go-diskfs/partition/gpt/table.go +++ b/vendor/github.com/diskfs/go-diskfs/partition/gpt/table.go @@ -7,8 +7,8 @@ import ( "hash/crc32" "strings" + "github.com/diskfs/go-diskfs/backend" "github.com/diskfs/go-diskfs/partition/part" - "github.com/diskfs/go-diskfs/util" uuid "github.com/google/uuid" ) @@ -295,7 +295,7 @@ func (t *Table) toGPTBytes(primary bool) ([]byte, error) { copy(b[56:72], bytesToUUIDBytes(guid[0:16])) // starting LBA of array of partition entries - binary.LittleEndian.PutUint64(b[72:80], t.partitionArraySector(true)) + binary.LittleEndian.PutUint64(b[72:80], t.partitionArraySector(primary)) // how many entries? binary.LittleEndian.PutUint32(b[80:84], uint32(t.partitionArraySize)) @@ -462,8 +462,8 @@ func (t *Table) Type() string { } // Write writes a GPT to disk -// Must be passed the util.File to which to write and the size of the disk -func (t *Table) Write(f util.File, size int64) error { +// Must be passed the backend.WritableFile to which to write and the size of the disk +func (t *Table) Write(f backend.WritableFile, size int64) error { // it is possible that we are given a basic new table that we need to initialize if !t.initialized { t.initTable(size) @@ -536,11 +536,11 @@ func (t *Table) Write(f util.File, size int64) error { } // Read read a partition table from a disk -// must be passed the util.File from which to read, and the logical and physical block sizes +// must be passed the backend.File from which to read, and the logical and physical block sizes // // if successful, returns a gpt.Table struct // returns errors if fails at any stage reading the disk or processing the bytes on disk as a GPT -func Read(f util.File, logicalBlockSize, physicalBlockSize int) (*Table, error) { +func Read(f backend.File, logicalBlockSize, physicalBlockSize int) (*Table, error) { // read the data off of the disk - first block is the compatibility MBR, ssecond is the GPT table b := make([]byte, logicalBlockSize*2) read, err := f.ReadAt(b, 0) @@ -595,7 +595,7 @@ func (t *Table) UUID() string { } // Verify will attempt to evaluate the headers -func (t *Table) Verify(f util.File, diskSize uint64) error { +func (t *Table) Verify(f backend.File, diskSize uint64) error { if t.LogicalSectorSize == 0 { // Avoid divide by zero panic. return fmt.Errorf("table is not initialized") diff --git a/vendor/github.com/diskfs/go-diskfs/partition/mbr/partition.go b/vendor/github.com/diskfs/go-diskfs/partition/mbr/partition.go index a63428da9b0..8a5468f5269 100644 --- a/vendor/github.com/diskfs/go-diskfs/partition/mbr/partition.go +++ b/vendor/github.com/diskfs/go-diskfs/partition/mbr/partition.go @@ -7,7 +7,7 @@ import ( "fmt" "io" - "github.com/diskfs/go-diskfs/util" + "github.com/diskfs/go-diskfs/backend" ) // Partition represents the structure of a single partition on the disk @@ -115,7 +115,7 @@ func partitionFromBytes(b []byte, logicalSectorSize, physicalSectorSize int) (*P EndSector: b[6], EndCylinder: b[7], Start: binary.LittleEndian.Uint32(b[8:12]), - Size: binary.LittleEndian.Uint32(b[12:16]), + Size: binary.LittleEndian.Uint32(b[12:16]), //nolint:gosec // we already checked the length above logicalSectorSize: logicalSectorSize, physicalSectorSize: physicalSectorSize, }, nil @@ -123,7 +123,7 @@ func partitionFromBytes(b []byte, logicalSectorSize, physicalSectorSize int) (*P // WriteContents fills the partition with the contents provided // reads from beginning of reader to exactly size of partition in bytes -func (p *Partition) WriteContents(f util.File, contents io.Reader) (uint64, error) { +func (p *Partition) WriteContents(f backend.WritableFile, contents io.Reader) (uint64, error) { pss, lss := p.sectorSizes() total := uint64(0) @@ -166,7 +166,7 @@ func (p *Partition) WriteContents(f util.File, contents io.Reader) (uint64, erro // readContents reads the contents of the partition into a writer // streams the entire partition to the writer -func (p *Partition) ReadContents(f util.File, out io.Writer) (int64, error) { +func (p *Partition) ReadContents(f backend.File, out io.Writer) (int64, error) { pss, lss := p.sectorSizes() total := int64(0) // chunks of physical sector size for efficient writing diff --git a/vendor/github.com/diskfs/go-diskfs/partition/mbr/table.go b/vendor/github.com/diskfs/go-diskfs/partition/mbr/table.go index c64dab5f0f9..4a55f3d6f58 100644 --- a/vendor/github.com/diskfs/go-diskfs/partition/mbr/table.go +++ b/vendor/github.com/diskfs/go-diskfs/partition/mbr/table.go @@ -5,8 +5,8 @@ import ( "encoding/binary" "fmt" + "github.com/diskfs/go-diskfs/backend" "github.com/diskfs/go-diskfs/partition/part" - "github.com/diskfs/go-diskfs/util" ) // Table represents an MBR partition table to be applied to a disk or read from a disk @@ -134,7 +134,7 @@ func (t *Table) Type() string { // Read read a partition table from a disk, given the logical block size and physical block size // //nolint:unused,revive // not used in MBR, but it is important to implement the interface -func Read(f util.File, logicalBlockSize, physicalBlockSize int) (*Table, error) { +func Read(f backend.File, logicalBlockSize, physicalBlockSize int) (*Table, error) { // read the data off of the disk b := make([]byte, mbrSize) read, err := f.ReadAt(b, 0) @@ -168,10 +168,10 @@ func (t *Table) toBytes() []byte { } // Write writes a given MBR Table to disk. -// Must be passed the util.File to write to and the size of the disk +// Must be passed the backend.WritableFile to write to and the size of the disk // //nolint:unused,revive // not used in MBR, but it is important to implement the interface -func (t *Table) Write(f util.File, size int64) error { +func (t *Table) Write(f backend.WritableFile, size int64) error { b := t.toBytes() written, err := f.WriteAt(b, partitionEntriesStart) @@ -196,7 +196,7 @@ func (t *Table) GetPartitions() []part.Partition { // Verify will attempt to evaluate the headers // //nolint:unused,revive // not used in MBR, but it is important to implement the interface -func (t *Table) Verify(f util.File, diskSize uint64) error { +func (t *Table) Verify(f backend.File, diskSize uint64) error { return nil } diff --git a/vendor/github.com/diskfs/go-diskfs/partition/part/partition.go b/vendor/github.com/diskfs/go-diskfs/partition/part/partition.go index f87ba9faa02..1a34f6d3278 100644 --- a/vendor/github.com/diskfs/go-diskfs/partition/part/partition.go +++ b/vendor/github.com/diskfs/go-diskfs/partition/part/partition.go @@ -3,14 +3,14 @@ package part import ( "io" - "github.com/diskfs/go-diskfs/util" + "github.com/diskfs/go-diskfs/backend" ) // Partition reference to an individual partition on disk type Partition interface { GetSize() int64 GetStart() int64 - ReadContents(util.File, io.Writer) (int64, error) - WriteContents(util.File, io.Reader) (uint64, error) + ReadContents(backend.File, io.Writer) (int64, error) + WriteContents(backend.WritableFile, io.Reader) (uint64, error) UUID() string } diff --git a/vendor/github.com/diskfs/go-diskfs/partition/partition.go b/vendor/github.com/diskfs/go-diskfs/partition/partition.go index d47ffba9297..627f3a8f1ed 100644 --- a/vendor/github.com/diskfs/go-diskfs/partition/partition.go +++ b/vendor/github.com/diskfs/go-diskfs/partition/partition.go @@ -5,13 +5,13 @@ package partition import ( "fmt" + "github.com/diskfs/go-diskfs/backend" "github.com/diskfs/go-diskfs/partition/gpt" "github.com/diskfs/go-diskfs/partition/mbr" - "github.com/diskfs/go-diskfs/util" ) // Read read a partition table from a disk -func Read(f util.File, logicalBlocksize, physicalBlocksize int) (Table, error) { +func Read(f backend.File, logicalBlocksize, physicalBlocksize int) (Table, error) { // just try each type gptTable, err := gpt.Read(f, logicalBlocksize, physicalBlocksize) if err == nil { diff --git a/vendor/github.com/diskfs/go-diskfs/partition/table.go b/vendor/github.com/diskfs/go-diskfs/partition/table.go index 62f56503a4d..91d89592030 100644 --- a/vendor/github.com/diskfs/go-diskfs/partition/table.go +++ b/vendor/github.com/diskfs/go-diskfs/partition/table.go @@ -1,16 +1,16 @@ package partition import ( + "github.com/diskfs/go-diskfs/backend" "github.com/diskfs/go-diskfs/partition/part" - "github.com/diskfs/go-diskfs/util" ) // Table reference to a partitioning table on disk type Table interface { Type() string - Write(util.File, int64) error + Write(backend.WritableFile, int64) error GetPartitions() []part.Partition Repair(diskSize uint64) error - Verify(f util.File, diskSize uint64) error + Verify(f backend.File, diskSize uint64) error UUID() string } diff --git a/vendor/github.com/diskfs/go-diskfs/util/bitmap.go b/vendor/github.com/diskfs/go-diskfs/util/bitmap/bitmap.go similarity index 94% rename from vendor/github.com/diskfs/go-diskfs/util/bitmap.go rename to vendor/github.com/diskfs/go-diskfs/util/bitmap/bitmap.go index 6fb85a1ba77..84c553104f7 100644 --- a/vendor/github.com/diskfs/go-diskfs/util/bitmap.go +++ b/vendor/github.com/diskfs/go-diskfs/util/bitmap/bitmap.go @@ -1,4 +1,4 @@ -package util +package bitmap import "fmt" @@ -13,8 +13,8 @@ type Contiguous struct { Count int } -// BitmapFromBytes create a bitmap struct from bytes -func BitmapFromBytes(b []byte) *Bitmap { +// FromBytes create a bitmap struct from bytes +func FromBytes(b []byte) *Bitmap { // just copy them over bits := make([]byte, len(b)) copy(bits, b) @@ -25,9 +25,9 @@ func BitmapFromBytes(b []byte) *Bitmap { return &bm } -// NewBitmap creates a new bitmap of size bytes; it is not in bits to force the caller to have +// New creates a new bitmap of size bytes; it is not in bits to force the caller to have // a complete set -func NewBitmap(bytes int) *Bitmap { +func New(bytes int) *Bitmap { bm := Bitmap{ bits: make([]byte, bytes), } diff --git a/vendor/github.com/diskfs/go-diskfs/util/file.go b/vendor/github.com/diskfs/go-diskfs/util/file.go deleted file mode 100644 index 78d0a152c71..00000000000 --- a/vendor/github.com/diskfs/go-diskfs/util/file.go +++ /dev/null @@ -1,13 +0,0 @@ -// Package util common utilities or other elements shared across github.com/diskfs/go-diskfs packages -package util - -import "io" - -// File interface that can be read from and written to. -// Normally implemented as actual os.File, but useful as a separate interface so can easily -// use alternate implementations. -type File interface { - io.ReaderAt - io.WriterAt - io.Seeker -} diff --git a/vendor/github.com/diskfs/go-diskfs/util/uniqify.go b/vendor/github.com/diskfs/go-diskfs/util/slices/uniqify.go similarity index 93% rename from vendor/github.com/diskfs/go-diskfs/util/uniqify.go rename to vendor/github.com/diskfs/go-diskfs/util/slices/uniqify.go index c091a6ecb8e..ec86d1a8838 100644 --- a/vendor/github.com/diskfs/go-diskfs/util/uniqify.go +++ b/vendor/github.com/diskfs/go-diskfs/util/slices/uniqify.go @@ -1,4 +1,4 @@ -package util +package slices func Uniqify[T comparable](s []T) []T { m := make(map[T]bool) diff --git a/vendor/github.com/diskfs/go-diskfs/util/version.go b/vendor/github.com/diskfs/go-diskfs/util/version.go deleted file mode 100644 index f6c3e7fc962..00000000000 --- a/vendor/github.com/diskfs/go-diskfs/util/version.go +++ /dev/null @@ -1,6 +0,0 @@ -package util - -const ( - // AppNameVersion name and URL to app - AppNameVersion = "https://github.com/diskfs/go-diskfs" -) diff --git a/vendor/github.com/diskfs/go-diskfs/version/version.go b/vendor/github.com/diskfs/go-diskfs/version/version.go new file mode 100644 index 00000000000..bb0d60c7018 --- /dev/null +++ b/vendor/github.com/diskfs/go-diskfs/version/version.go @@ -0,0 +1,6 @@ +package version + +const ( + // AppName name and URL to app + AppName = "https://github.com/diskfs/go-diskfs" +) diff --git a/vendor/github.com/golang/mock/AUTHORS b/vendor/github.com/golang/mock/AUTHORS deleted file mode 100644 index 660b8ccc8ae..00000000000 --- a/vendor/github.com/golang/mock/AUTHORS +++ /dev/null @@ -1,12 +0,0 @@ -# This is the official list of GoMock authors for copyright purposes. -# This file is distinct from the CONTRIBUTORS files. -# See the latter for an explanation. - -# Names should be added to this file as -# Name or Organization -# The email address is not required for organizations. - -# Please keep the list sorted. - -Alex Reece -Google Inc. diff --git a/vendor/github.com/golang/mock/CONTRIBUTORS b/vendor/github.com/golang/mock/CONTRIBUTORS deleted file mode 100644 index def849cab1b..00000000000 --- a/vendor/github.com/golang/mock/CONTRIBUTORS +++ /dev/null @@ -1,37 +0,0 @@ -# This is the official list of people who can contribute (and typically -# have contributed) code to the gomock repository. -# The AUTHORS file lists the copyright holders; this file -# lists people. For example, Google employees are listed here -# but not in AUTHORS, because Google holds the copyright. -# -# The submission process automatically checks to make sure -# that people submitting code are listed in this file (by email address). -# -# Names should be added to this file only after verifying that -# the individual or the individual's organization has agreed to -# the appropriate Contributor License Agreement, found here: -# -# http://code.google.com/legal/individual-cla-v1.0.html -# http://code.google.com/legal/corporate-cla-v1.0.html -# -# The agreement for individuals can be filled out on the web. -# -# When adding J Random Contributor's name to this file, -# either J's name or J's organization's name should be -# added to the AUTHORS file, depending on whether the -# individual or corporate CLA was used. - -# Names should be added to this file like so: -# Name -# -# An entry with two email addresses specifies that the -# first address should be used in the submit logs and -# that the second address should be recognized as the -# same person when interacting with Rietveld. - -# Please keep the list sorted. - -Aaron Jacobs -Alex Reece -David Symonds -Ryan Barrett diff --git a/vendor/github.com/golang/mock/LICENSE b/vendor/github.com/golang/mock/LICENSE deleted file mode 100644 index d6456956733..00000000000 --- a/vendor/github.com/golang/mock/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - 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. diff --git a/vendor/github.com/golang/mock/gomock/call.go b/vendor/github.com/golang/mock/gomock/call.go deleted file mode 100644 index 98881596d13..00000000000 --- a/vendor/github.com/golang/mock/gomock/call.go +++ /dev/null @@ -1,471 +0,0 @@ -// Copyright 2010 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package gomock - -import ( - "fmt" - "reflect" - "strconv" - "strings" -) - -// Call represents an expected call to a mock. -type Call struct { - t TestHelper // for triggering test failures on invalid call setup - - receiver interface{} // the receiver of the method call - method string // the name of the method - methodType reflect.Type // the type of the method - args []Matcher // the args - origin string // file and line number of call setup - - preReqs []*Call // prerequisite calls - - // Expectations - minCalls, maxCalls int - - numCalls int // actual number made - - // actions are called when this Call is called. Each action gets the args and - // can set the return values by returning a non-nil slice. Actions run in the - // order they are created. - actions []func([]interface{}) []interface{} -} - -// newCall creates a *Call. It requires the method type in order to support -// unexported methods. -func newCall(t TestHelper, receiver interface{}, method string, methodType reflect.Type, args ...interface{}) *Call { - t.Helper() - - // TODO: check arity, types. - mArgs := make([]Matcher, len(args)) - for i, arg := range args { - if m, ok := arg.(Matcher); ok { - mArgs[i] = m - } else if arg == nil { - // Handle nil specially so that passing a nil interface value - // will match the typed nils of concrete args. - mArgs[i] = Nil() - } else { - mArgs[i] = Eq(arg) - } - } - - // callerInfo's skip should be updated if the number of calls between the user's test - // and this line changes, i.e. this code is wrapped in another anonymous function. - // 0 is us, 1 is RecordCallWithMethodType(), 2 is the generated recorder, and 3 is the user's test. - origin := callerInfo(3) - actions := []func([]interface{}) []interface{}{func([]interface{}) []interface{} { - // Synthesize the zero value for each of the return args' types. - rets := make([]interface{}, methodType.NumOut()) - for i := 0; i < methodType.NumOut(); i++ { - rets[i] = reflect.Zero(methodType.Out(i)).Interface() - } - return rets - }} - return &Call{t: t, receiver: receiver, method: method, methodType: methodType, - args: mArgs, origin: origin, minCalls: 1, maxCalls: 1, actions: actions} -} - -// AnyTimes allows the expectation to be called 0 or more times -func (c *Call) AnyTimes() *Call { - c.minCalls, c.maxCalls = 0, 1e8 // close enough to infinity - return c -} - -// MinTimes requires the call to occur at least n times. If AnyTimes or MaxTimes have not been called or if MaxTimes -// was previously called with 1, MinTimes also sets the maximum number of calls to infinity. -func (c *Call) MinTimes(n int) *Call { - c.minCalls = n - if c.maxCalls == 1 { - c.maxCalls = 1e8 - } - return c -} - -// MaxTimes limits the number of calls to n times. If AnyTimes or MinTimes have not been called or if MinTimes was -// previously called with 1, MaxTimes also sets the minimum number of calls to 0. -func (c *Call) MaxTimes(n int) *Call { - c.maxCalls = n - if c.minCalls == 1 { - c.minCalls = 0 - } - return c -} - -// DoAndReturn declares the action to run when the call is matched. -// The return values from this function are returned by the mocked function. -// It takes an interface{} argument to support n-arity functions. -// The anonymous function must match the function signature mocked method. -func (c *Call) DoAndReturn(f interface{}) *Call { - // TODO: Check arity and types here, rather than dying badly elsewhere. - v := reflect.ValueOf(f) - - c.addAction(func(args []interface{}) []interface{} { - c.t.Helper() - ft := v.Type() - if c.methodType.NumIn() != ft.NumIn() { - if ft.IsVariadic() { - c.t.Fatalf("wrong number of arguments in DoAndReturn func for %T.%v The function signature must match the mocked method, a variadic function cannot be used.", - c.receiver, c.method) - } else { - c.t.Fatalf("wrong number of arguments in DoAndReturn func for %T.%v: got %d, want %d [%s]", - c.receiver, c.method, ft.NumIn(), c.methodType.NumIn(), c.origin) - } - return nil - } - vArgs := make([]reflect.Value, len(args)) - for i := 0; i < len(args); i++ { - if args[i] != nil { - vArgs[i] = reflect.ValueOf(args[i]) - } else { - // Use the zero value for the arg. - vArgs[i] = reflect.Zero(ft.In(i)) - } - } - vRets := v.Call(vArgs) - rets := make([]interface{}, len(vRets)) - for i, ret := range vRets { - rets[i] = ret.Interface() - } - return rets - }) - return c -} - -// Do declares the action to run when the call is matched. The function's -// return values are ignored to retain backward compatibility. To use the -// return values call DoAndReturn. -// It takes an interface{} argument to support n-arity functions. -// The anonymous function must match the function signature mocked method. -func (c *Call) Do(f interface{}) *Call { - // TODO: Check arity and types here, rather than dying badly elsewhere. - v := reflect.ValueOf(f) - - c.addAction(func(args []interface{}) []interface{} { - c.t.Helper() - ft := v.Type() - if c.methodType.NumIn() != ft.NumIn() { - if ft.IsVariadic() { - c.t.Fatalf("wrong number of arguments in Do func for %T.%v The function signature must match the mocked method, a variadic function cannot be used.", - c.receiver, c.method) - } else { - c.t.Fatalf("wrong number of arguments in Do func for %T.%v: got %d, want %d [%s]", - c.receiver, c.method, ft.NumIn(), c.methodType.NumIn(), c.origin) - } - return nil - } - vArgs := make([]reflect.Value, len(args)) - for i := 0; i < len(args); i++ { - if args[i] != nil { - vArgs[i] = reflect.ValueOf(args[i]) - } else { - // Use the zero value for the arg. - vArgs[i] = reflect.Zero(ft.In(i)) - } - } - v.Call(vArgs) - return nil - }) - return c -} - -// Return declares the values to be returned by the mocked function call. -func (c *Call) Return(rets ...interface{}) *Call { - c.t.Helper() - - mt := c.methodType - if len(rets) != mt.NumOut() { - c.t.Fatalf("wrong number of arguments to Return for %T.%v: got %d, want %d [%s]", - c.receiver, c.method, len(rets), mt.NumOut(), c.origin) - } - for i, ret := range rets { - if got, want := reflect.TypeOf(ret), mt.Out(i); got == want { - // Identical types; nothing to do. - } else if got == nil { - // Nil needs special handling. - switch want.Kind() { - case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice: - // ok - default: - c.t.Fatalf("argument %d to Return for %T.%v is nil, but %v is not nillable [%s]", - i, c.receiver, c.method, want, c.origin) - } - } else if got.AssignableTo(want) { - // Assignable type relation. Make the assignment now so that the generated code - // can return the values with a type assertion. - v := reflect.New(want).Elem() - v.Set(reflect.ValueOf(ret)) - rets[i] = v.Interface() - } else { - c.t.Fatalf("wrong type of argument %d to Return for %T.%v: %v is not assignable to %v [%s]", - i, c.receiver, c.method, got, want, c.origin) - } - } - - c.addAction(func([]interface{}) []interface{} { - return rets - }) - - return c -} - -// Times declares the exact number of times a function call is expected to be executed. -func (c *Call) Times(n int) *Call { - c.minCalls, c.maxCalls = n, n - return c -} - -// SetArg declares an action that will set the nth argument's value, -// indirected through a pointer. Or, in the case of a slice and map, SetArg -// will copy value's elements/key-value pairs into the nth argument. -func (c *Call) SetArg(n int, value interface{}) *Call { - c.t.Helper() - - mt := c.methodType - // TODO: This will break on variadic methods. - // We will need to check those at invocation time. - if n < 0 || n >= mt.NumIn() { - c.t.Fatalf("SetArg(%d, ...) called for a method with %d args [%s]", - n, mt.NumIn(), c.origin) - } - // Permit setting argument through an interface. - // In the interface case, we don't (nay, can't) check the type here. - at := mt.In(n) - switch at.Kind() { - case reflect.Ptr: - dt := at.Elem() - if vt := reflect.TypeOf(value); !vt.AssignableTo(dt) { - c.t.Fatalf("SetArg(%d, ...) argument is a %v, not assignable to %v [%s]", - n, vt, dt, c.origin) - } - case reflect.Interface: - // nothing to do - case reflect.Slice: - // nothing to do - case reflect.Map: - // nothing to do - default: - c.t.Fatalf("SetArg(%d, ...) referring to argument of non-pointer non-interface non-slice non-map type %v [%s]", - n, at, c.origin) - } - - c.addAction(func(args []interface{}) []interface{} { - v := reflect.ValueOf(value) - switch reflect.TypeOf(args[n]).Kind() { - case reflect.Slice: - setSlice(args[n], v) - case reflect.Map: - setMap(args[n], v) - default: - reflect.ValueOf(args[n]).Elem().Set(v) - } - return nil - }) - return c -} - -// isPreReq returns true if other is a direct or indirect prerequisite to c. -func (c *Call) isPreReq(other *Call) bool { - for _, preReq := range c.preReqs { - if other == preReq || preReq.isPreReq(other) { - return true - } - } - return false -} - -// After declares that the call may only match after preReq has been exhausted. -func (c *Call) After(preReq *Call) *Call { - c.t.Helper() - - if c == preReq { - c.t.Fatalf("A call isn't allowed to be its own prerequisite") - } - if preReq.isPreReq(c) { - c.t.Fatalf("Loop in call order: %v is a prerequisite to %v (possibly indirectly).", c, preReq) - } - - c.preReqs = append(c.preReqs, preReq) - return c -} - -// Returns true if the minimum number of calls have been made. -func (c *Call) satisfied() bool { - return c.numCalls >= c.minCalls -} - -// Returns true if the maximum number of calls have been made. -func (c *Call) exhausted() bool { - return c.numCalls >= c.maxCalls -} - -func (c *Call) String() string { - args := make([]string, len(c.args)) - for i, arg := range c.args { - args[i] = arg.String() - } - arguments := strings.Join(args, ", ") - return fmt.Sprintf("%T.%v(%s) %s", c.receiver, c.method, arguments, c.origin) -} - -// Tests if the given call matches the expected call. -// If yes, returns nil. If no, returns error with message explaining why it does not match. -func (c *Call) matches(args []interface{}) error { - if !c.methodType.IsVariadic() { - if len(args) != len(c.args) { - return fmt.Errorf("expected call at %s has the wrong number of arguments. Got: %d, want: %d", - c.origin, len(args), len(c.args)) - } - - for i, m := range c.args { - if !m.Matches(args[i]) { - return fmt.Errorf( - "expected call at %s doesn't match the argument at index %d.\nGot: %v\nWant: %v", - c.origin, i, formatGottenArg(m, args[i]), m, - ) - } - } - } else { - if len(c.args) < c.methodType.NumIn()-1 { - return fmt.Errorf("expected call at %s has the wrong number of matchers. Got: %d, want: %d", - c.origin, len(c.args), c.methodType.NumIn()-1) - } - if len(c.args) != c.methodType.NumIn() && len(args) != len(c.args) { - return fmt.Errorf("expected call at %s has the wrong number of arguments. Got: %d, want: %d", - c.origin, len(args), len(c.args)) - } - if len(args) < len(c.args)-1 { - return fmt.Errorf("expected call at %s has the wrong number of arguments. Got: %d, want: greater than or equal to %d", - c.origin, len(args), len(c.args)-1) - } - - for i, m := range c.args { - if i < c.methodType.NumIn()-1 { - // Non-variadic args - if !m.Matches(args[i]) { - return fmt.Errorf("expected call at %s doesn't match the argument at index %s.\nGot: %v\nWant: %v", - c.origin, strconv.Itoa(i), formatGottenArg(m, args[i]), m) - } - continue - } - // The last arg has a possibility of a variadic argument, so let it branch - - // sample: Foo(a int, b int, c ...int) - if i < len(c.args) && i < len(args) { - if m.Matches(args[i]) { - // Got Foo(a, b, c) want Foo(matcherA, matcherB, gomock.Any()) - // Got Foo(a, b, c) want Foo(matcherA, matcherB, someSliceMatcher) - // Got Foo(a, b, c) want Foo(matcherA, matcherB, matcherC) - // Got Foo(a, b) want Foo(matcherA, matcherB) - // Got Foo(a, b, c, d) want Foo(matcherA, matcherB, matcherC, matcherD) - continue - } - } - - // The number of actual args don't match the number of matchers, - // or the last matcher is a slice and the last arg is not. - // If this function still matches it is because the last matcher - // matches all the remaining arguments or the lack of any. - // Convert the remaining arguments, if any, into a slice of the - // expected type. - vArgsType := c.methodType.In(c.methodType.NumIn() - 1) - vArgs := reflect.MakeSlice(vArgsType, 0, len(args)-i) - for _, arg := range args[i:] { - vArgs = reflect.Append(vArgs, reflect.ValueOf(arg)) - } - if m.Matches(vArgs.Interface()) { - // Got Foo(a, b, c, d, e) want Foo(matcherA, matcherB, gomock.Any()) - // Got Foo(a, b, c, d, e) want Foo(matcherA, matcherB, someSliceMatcher) - // Got Foo(a, b) want Foo(matcherA, matcherB, gomock.Any()) - // Got Foo(a, b) want Foo(matcherA, matcherB, someEmptySliceMatcher) - break - } - // Wrong number of matchers or not match. Fail. - // Got Foo(a, b) want Foo(matcherA, matcherB, matcherC, matcherD) - // Got Foo(a, b, c) want Foo(matcherA, matcherB, matcherC, matcherD) - // Got Foo(a, b, c, d) want Foo(matcherA, matcherB, matcherC, matcherD, matcherE) - // Got Foo(a, b, c, d, e) want Foo(matcherA, matcherB, matcherC, matcherD) - // Got Foo(a, b, c) want Foo(matcherA, matcherB) - - return fmt.Errorf("expected call at %s doesn't match the argument at index %s.\nGot: %v\nWant: %v", - c.origin, strconv.Itoa(i), formatGottenArg(m, args[i:]), c.args[i]) - } - } - - // Check that all prerequisite calls have been satisfied. - for _, preReqCall := range c.preReqs { - if !preReqCall.satisfied() { - return fmt.Errorf("expected call at %s doesn't have a prerequisite call satisfied:\n%v\nshould be called before:\n%v", - c.origin, preReqCall, c) - } - } - - // Check that the call is not exhausted. - if c.exhausted() { - return fmt.Errorf("expected call at %s has already been called the max number of times", c.origin) - } - - return nil -} - -// dropPrereqs tells the expected Call to not re-check prerequisite calls any -// longer, and to return its current set. -func (c *Call) dropPrereqs() (preReqs []*Call) { - preReqs = c.preReqs - c.preReqs = nil - return -} - -func (c *Call) call() []func([]interface{}) []interface{} { - c.numCalls++ - return c.actions -} - -// InOrder declares that the given calls should occur in order. -func InOrder(calls ...*Call) { - for i := 1; i < len(calls); i++ { - calls[i].After(calls[i-1]) - } -} - -func setSlice(arg interface{}, v reflect.Value) { - va := reflect.ValueOf(arg) - for i := 0; i < v.Len(); i++ { - va.Index(i).Set(v.Index(i)) - } -} - -func setMap(arg interface{}, v reflect.Value) { - va := reflect.ValueOf(arg) - for _, e := range va.MapKeys() { - va.SetMapIndex(e, reflect.Value{}) - } - for _, e := range v.MapKeys() { - va.SetMapIndex(e, v.MapIndex(e)) - } -} - -func (c *Call) addAction(action func([]interface{}) []interface{}) { - c.actions = append(c.actions, action) -} - -func formatGottenArg(m Matcher, arg interface{}) string { - got := fmt.Sprintf("%v (%T)", arg, arg) - if gs, ok := m.(GotFormatter); ok { - got = gs.Got(arg) - } - return got -} diff --git a/vendor/github.com/golang/mock/gomock/callset.go b/vendor/github.com/golang/mock/gomock/callset.go deleted file mode 100644 index 49dba787a40..00000000000 --- a/vendor/github.com/golang/mock/gomock/callset.go +++ /dev/null @@ -1,113 +0,0 @@ -// Copyright 2011 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package gomock - -import ( - "bytes" - "errors" - "fmt" -) - -// callSet represents a set of expected calls, indexed by receiver and method -// name. -type callSet struct { - // Calls that are still expected. - expected map[callSetKey][]*Call - // Calls that have been exhausted. - exhausted map[callSetKey][]*Call -} - -// callSetKey is the key in the maps in callSet -type callSetKey struct { - receiver interface{} - fname string -} - -func newCallSet() *callSet { - return &callSet{make(map[callSetKey][]*Call), make(map[callSetKey][]*Call)} -} - -// Add adds a new expected call. -func (cs callSet) Add(call *Call) { - key := callSetKey{call.receiver, call.method} - m := cs.expected - if call.exhausted() { - m = cs.exhausted - } - m[key] = append(m[key], call) -} - -// Remove removes an expected call. -func (cs callSet) Remove(call *Call) { - key := callSetKey{call.receiver, call.method} - calls := cs.expected[key] - for i, c := range calls { - if c == call { - // maintain order for remaining calls - cs.expected[key] = append(calls[:i], calls[i+1:]...) - cs.exhausted[key] = append(cs.exhausted[key], call) - break - } - } -} - -// FindMatch searches for a matching call. Returns error with explanation message if no call matched. -func (cs callSet) FindMatch(receiver interface{}, method string, args []interface{}) (*Call, error) { - key := callSetKey{receiver, method} - - // Search through the expected calls. - expected := cs.expected[key] - var callsErrors bytes.Buffer - for _, call := range expected { - err := call.matches(args) - if err != nil { - _, _ = fmt.Fprintf(&callsErrors, "\n%v", err) - } else { - return call, nil - } - } - - // If we haven't found a match then search through the exhausted calls so we - // get useful error messages. - exhausted := cs.exhausted[key] - for _, call := range exhausted { - if err := call.matches(args); err != nil { - _, _ = fmt.Fprintf(&callsErrors, "\n%v", err) - continue - } - _, _ = fmt.Fprintf( - &callsErrors, "all expected calls for method %q have been exhausted", method, - ) - } - - if len(expected)+len(exhausted) == 0 { - _, _ = fmt.Fprintf(&callsErrors, "there are no expected calls of the method %q for that receiver", method) - } - - return nil, errors.New(callsErrors.String()) -} - -// Failures returns the calls that are not satisfied. -func (cs callSet) Failures() []*Call { - failures := make([]*Call, 0, len(cs.expected)) - for _, calls := range cs.expected { - for _, call := range calls { - if !call.satisfied() { - failures = append(failures, call) - } - } - } - return failures -} diff --git a/vendor/github.com/golang/mock/gomock/controller.go b/vendor/github.com/golang/mock/gomock/controller.go deleted file mode 100644 index 5e2def135bf..00000000000 --- a/vendor/github.com/golang/mock/gomock/controller.go +++ /dev/null @@ -1,298 +0,0 @@ -// Copyright 2010 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package gomock - -import ( - "context" - "fmt" - "reflect" - "runtime" - "sync" -) - -// A TestReporter is something that can be used to report test failures. It -// is satisfied by the standard library's *testing.T. -type TestReporter interface { - Errorf(format string, args ...interface{}) - Fatalf(format string, args ...interface{}) -} - -// TestHelper is a TestReporter that has the Helper method. It is satisfied -// by the standard library's *testing.T. -type TestHelper interface { - TestReporter - Helper() -} - -// cleanuper is used to check if TestHelper also has the `Cleanup` method. A -// common pattern is to pass in a `*testing.T` to -// `NewController(t TestReporter)`. In Go 1.14+, `*testing.T` has a cleanup -// method. This can be utilized to call `Finish()` so the caller of this library -// does not have to. -type cleanuper interface { - Cleanup(func()) -} - -// A Controller represents the top-level control of a mock ecosystem. It -// defines the scope and lifetime of mock objects, as well as their -// expectations. It is safe to call Controller's methods from multiple -// goroutines. Each test should create a new Controller and invoke Finish via -// defer. -// -// func TestFoo(t *testing.T) { -// ctrl := gomock.NewController(t) -// defer ctrl.Finish() -// // .. -// } -// -// func TestBar(t *testing.T) { -// t.Run("Sub-Test-1", st) { -// ctrl := gomock.NewController(st) -// defer ctrl.Finish() -// // .. -// }) -// t.Run("Sub-Test-2", st) { -// ctrl := gomock.NewController(st) -// defer ctrl.Finish() -// // .. -// }) -// }) -type Controller struct { - // T should only be called within a generated mock. It is not intended to - // be used in user code and may be changed in future versions. T is the - // TestReporter passed in when creating the Controller via NewController. - // If the TestReporter does not implement a TestHelper it will be wrapped - // with a nopTestHelper. - T TestHelper - mu sync.Mutex - expectedCalls *callSet - finished bool -} - -// NewController returns a new Controller. It is the preferred way to create a -// Controller. -// -// New in go1.14+, if you are passing a *testing.T into this function you no -// longer need to call ctrl.Finish() in your test methods. -func NewController(t TestReporter) *Controller { - h, ok := t.(TestHelper) - if !ok { - h = &nopTestHelper{t} - } - ctrl := &Controller{ - T: h, - expectedCalls: newCallSet(), - } - if c, ok := isCleanuper(ctrl.T); ok { - c.Cleanup(func() { - ctrl.T.Helper() - ctrl.finish(true, nil) - }) - } - - return ctrl -} - -type cancelReporter struct { - t TestHelper - cancel func() -} - -func (r *cancelReporter) Errorf(format string, args ...interface{}) { - r.t.Errorf(format, args...) -} -func (r *cancelReporter) Fatalf(format string, args ...interface{}) { - defer r.cancel() - r.t.Fatalf(format, args...) -} - -func (r *cancelReporter) Helper() { - r.t.Helper() -} - -// WithContext returns a new Controller and a Context, which is cancelled on any -// fatal failure. -func WithContext(ctx context.Context, t TestReporter) (*Controller, context.Context) { - h, ok := t.(TestHelper) - if !ok { - h = &nopTestHelper{t: t} - } - - ctx, cancel := context.WithCancel(ctx) - return NewController(&cancelReporter{t: h, cancel: cancel}), ctx -} - -type nopTestHelper struct { - t TestReporter -} - -func (h *nopTestHelper) Errorf(format string, args ...interface{}) { - h.t.Errorf(format, args...) -} -func (h *nopTestHelper) Fatalf(format string, args ...interface{}) { - h.t.Fatalf(format, args...) -} - -func (h nopTestHelper) Helper() {} - -// RecordCall is called by a mock. It should not be called by user code. -func (ctrl *Controller) RecordCall(receiver interface{}, method string, args ...interface{}) *Call { - ctrl.T.Helper() - - recv := reflect.ValueOf(receiver) - for i := 0; i < recv.Type().NumMethod(); i++ { - if recv.Type().Method(i).Name == method { - return ctrl.RecordCallWithMethodType(receiver, method, recv.Method(i).Type(), args...) - } - } - ctrl.T.Fatalf("gomock: failed finding method %s on %T", method, receiver) - panic("unreachable") -} - -// RecordCallWithMethodType is called by a mock. It should not be called by user code. -func (ctrl *Controller) RecordCallWithMethodType(receiver interface{}, method string, methodType reflect.Type, args ...interface{}) *Call { - ctrl.T.Helper() - - call := newCall(ctrl.T, receiver, method, methodType, args...) - - ctrl.mu.Lock() - defer ctrl.mu.Unlock() - ctrl.expectedCalls.Add(call) - - return call -} - -// Call is called by a mock. It should not be called by user code. -func (ctrl *Controller) Call(receiver interface{}, method string, args ...interface{}) []interface{} { - ctrl.T.Helper() - - // Nest this code so we can use defer to make sure the lock is released. - actions := func() []func([]interface{}) []interface{} { - ctrl.T.Helper() - ctrl.mu.Lock() - defer ctrl.mu.Unlock() - - expected, err := ctrl.expectedCalls.FindMatch(receiver, method, args) - if err != nil { - // callerInfo's skip should be updated if the number of calls between the user's test - // and this line changes, i.e. this code is wrapped in another anonymous function. - // 0 is us, 1 is controller.Call(), 2 is the generated mock, and 3 is the user's test. - origin := callerInfo(3) - ctrl.T.Fatalf("Unexpected call to %T.%v(%v) at %s because: %s", receiver, method, args, origin, err) - } - - // Two things happen here: - // * the matching call no longer needs to check prerequite calls, - // * and the prerequite calls are no longer expected, so remove them. - preReqCalls := expected.dropPrereqs() - for _, preReqCall := range preReqCalls { - ctrl.expectedCalls.Remove(preReqCall) - } - - actions := expected.call() - if expected.exhausted() { - ctrl.expectedCalls.Remove(expected) - } - return actions - }() - - var rets []interface{} - for _, action := range actions { - if r := action(args); r != nil { - rets = r - } - } - - return rets -} - -// Finish checks to see if all the methods that were expected to be called -// were called. It should be invoked for each Controller. It is not idempotent -// and therefore can only be invoked once. -// -// New in go1.14+, if you are passing a *testing.T into NewController function you no -// longer need to call ctrl.Finish() in your test methods. -func (ctrl *Controller) Finish() { - // If we're currently panicking, probably because this is a deferred call. - // This must be recovered in the deferred function. - err := recover() - ctrl.finish(false, err) -} - -func (ctrl *Controller) finish(cleanup bool, panicErr interface{}) { - ctrl.T.Helper() - - ctrl.mu.Lock() - defer ctrl.mu.Unlock() - - if ctrl.finished { - if _, ok := isCleanuper(ctrl.T); !ok { - ctrl.T.Fatalf("Controller.Finish was called more than once. It has to be called exactly once.") - } - return - } - ctrl.finished = true - - // Short-circuit, pass through the panic. - if panicErr != nil { - panic(panicErr) - } - - // Check that all remaining expected calls are satisfied. - failures := ctrl.expectedCalls.Failures() - for _, call := range failures { - ctrl.T.Errorf("missing call(s) to %v", call) - } - if len(failures) != 0 { - if !cleanup { - ctrl.T.Fatalf("aborting test due to missing call(s)") - return - } - ctrl.T.Errorf("aborting test due to missing call(s)") - } -} - -// callerInfo returns the file:line of the call site. skip is the number -// of stack frames to skip when reporting. 0 is callerInfo's call site. -func callerInfo(skip int) string { - if _, file, line, ok := runtime.Caller(skip + 1); ok { - return fmt.Sprintf("%s:%d", file, line) - } - return "unknown file" -} - -// isCleanuper checks it if t's base TestReporter has a Cleanup method. -func isCleanuper(t TestReporter) (cleanuper, bool) { - tr := unwrapTestReporter(t) - c, ok := tr.(cleanuper) - return c, ok -} - -// unwrapTestReporter unwraps TestReporter to the base implementation. -func unwrapTestReporter(t TestReporter) TestReporter { - tr := t - switch nt := t.(type) { - case *cancelReporter: - tr = nt.t - if h, check := tr.(*nopTestHelper); check { - tr = h.t - } - case *nopTestHelper: - tr = nt.t - default: - // not wrapped - } - return tr -} diff --git a/vendor/github.com/golang/mock/gomock/doc.go b/vendor/github.com/golang/mock/gomock/doc.go deleted file mode 100644 index 1706b504858..00000000000 --- a/vendor/github.com/golang/mock/gomock/doc.go +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright 2022 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package gomock is a mock framework for Go. -// -// Standard usage: -// (1) Define an interface that you wish to mock. -// type MyInterface interface { -// SomeMethod(x int64, y string) -// } -// (2) Use mockgen to generate a mock from the interface. -// (3) Use the mock in a test: -// func TestMyThing(t *testing.T) { -// mockCtrl := gomock.NewController(t)// -// mockObj := something.NewMockMyInterface(mockCtrl) -// mockObj.EXPECT().SomeMethod(4, "blah") -// // pass mockObj to a real object and play with it. -// } -// -// By default, expected calls are not enforced to run in any particular order. -// Call order dependency can be enforced by use of InOrder and/or Call.After. -// Call.After can create more varied call order dependencies, but InOrder is -// often more convenient. -// -// The following examples create equivalent call order dependencies. -// -// Example of using Call.After to chain expected call order: -// -// firstCall := mockObj.EXPECT().SomeMethod(1, "first") -// secondCall := mockObj.EXPECT().SomeMethod(2, "second").After(firstCall) -// mockObj.EXPECT().SomeMethod(3, "third").After(secondCall) -// -// Example of using InOrder to declare expected call order: -// -// gomock.InOrder( -// mockObj.EXPECT().SomeMethod(1, "first"), -// mockObj.EXPECT().SomeMethod(2, "second"), -// mockObj.EXPECT().SomeMethod(3, "third"), -// ) -// -// The standard TestReporter most users will pass to `NewController` is a -// `*testing.T` from the context of the test. Note that this will use the -// standard `t.Error` and `t.Fatal` methods to report what happened in the test. -// In some cases this can leave your testing package in a weird state if global -// state is used since `t.Fatal` is like calling panic in the middle of a -// function. In these cases it is recommended that you pass in your own -// `TestReporter`. -package gomock diff --git a/vendor/github.com/golang/mock/gomock/matchers.go b/vendor/github.com/golang/mock/gomock/matchers.go deleted file mode 100644 index 2822fb2c8c4..00000000000 --- a/vendor/github.com/golang/mock/gomock/matchers.go +++ /dev/null @@ -1,341 +0,0 @@ -// Copyright 2010 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package gomock - -import ( - "fmt" - "reflect" - "strings" -) - -// A Matcher is a representation of a class of values. -// It is used to represent the valid or expected arguments to a mocked method. -type Matcher interface { - // Matches returns whether x is a match. - Matches(x interface{}) bool - - // String describes what the matcher matches. - String() string -} - -// WantFormatter modifies the given Matcher's String() method to the given -// Stringer. This allows for control on how the "Want" is formatted when -// printing . -func WantFormatter(s fmt.Stringer, m Matcher) Matcher { - type matcher interface { - Matches(x interface{}) bool - } - - return struct { - matcher - fmt.Stringer - }{ - matcher: m, - Stringer: s, - } -} - -// StringerFunc type is an adapter to allow the use of ordinary functions as -// a Stringer. If f is a function with the appropriate signature, -// StringerFunc(f) is a Stringer that calls f. -type StringerFunc func() string - -// String implements fmt.Stringer. -func (f StringerFunc) String() string { - return f() -} - -// GotFormatter is used to better print failure messages. If a matcher -// implements GotFormatter, it will use the result from Got when printing -// the failure message. -type GotFormatter interface { - // Got is invoked with the received value. The result is used when - // printing the failure message. - Got(got interface{}) string -} - -// GotFormatterFunc type is an adapter to allow the use of ordinary -// functions as a GotFormatter. If f is a function with the appropriate -// signature, GotFormatterFunc(f) is a GotFormatter that calls f. -type GotFormatterFunc func(got interface{}) string - -// Got implements GotFormatter. -func (f GotFormatterFunc) Got(got interface{}) string { - return f(got) -} - -// GotFormatterAdapter attaches a GotFormatter to a Matcher. -func GotFormatterAdapter(s GotFormatter, m Matcher) Matcher { - return struct { - GotFormatter - Matcher - }{ - GotFormatter: s, - Matcher: m, - } -} - -type anyMatcher struct{} - -func (anyMatcher) Matches(interface{}) bool { - return true -} - -func (anyMatcher) String() string { - return "is anything" -} - -type eqMatcher struct { - x interface{} -} - -func (e eqMatcher) Matches(x interface{}) bool { - // In case, some value is nil - if e.x == nil || x == nil { - return reflect.DeepEqual(e.x, x) - } - - // Check if types assignable and convert them to common type - x1Val := reflect.ValueOf(e.x) - x2Val := reflect.ValueOf(x) - - if x1Val.Type().AssignableTo(x2Val.Type()) { - x1ValConverted := x1Val.Convert(x2Val.Type()) - return reflect.DeepEqual(x1ValConverted.Interface(), x2Val.Interface()) - } - - return false -} - -func (e eqMatcher) String() string { - return fmt.Sprintf("is equal to %v (%T)", e.x, e.x) -} - -type nilMatcher struct{} - -func (nilMatcher) Matches(x interface{}) bool { - if x == nil { - return true - } - - v := reflect.ValueOf(x) - switch v.Kind() { - case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, - reflect.Ptr, reflect.Slice: - return v.IsNil() - } - - return false -} - -func (nilMatcher) String() string { - return "is nil" -} - -type notMatcher struct { - m Matcher -} - -func (n notMatcher) Matches(x interface{}) bool { - return !n.m.Matches(x) -} - -func (n notMatcher) String() string { - return "not(" + n.m.String() + ")" -} - -type assignableToTypeOfMatcher struct { - targetType reflect.Type -} - -func (m assignableToTypeOfMatcher) Matches(x interface{}) bool { - return reflect.TypeOf(x).AssignableTo(m.targetType) -} - -func (m assignableToTypeOfMatcher) String() string { - return "is assignable to " + m.targetType.Name() -} - -type allMatcher struct { - matchers []Matcher -} - -func (am allMatcher) Matches(x interface{}) bool { - for _, m := range am.matchers { - if !m.Matches(x) { - return false - } - } - return true -} - -func (am allMatcher) String() string { - ss := make([]string, 0, len(am.matchers)) - for _, matcher := range am.matchers { - ss = append(ss, matcher.String()) - } - return strings.Join(ss, "; ") -} - -type lenMatcher struct { - i int -} - -func (m lenMatcher) Matches(x interface{}) bool { - v := reflect.ValueOf(x) - switch v.Kind() { - case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice, reflect.String: - return v.Len() == m.i - default: - return false - } -} - -func (m lenMatcher) String() string { - return fmt.Sprintf("has length %d", m.i) -} - -type inAnyOrderMatcher struct { - x interface{} -} - -func (m inAnyOrderMatcher) Matches(x interface{}) bool { - given, ok := m.prepareValue(x) - if !ok { - return false - } - wanted, ok := m.prepareValue(m.x) - if !ok { - return false - } - - if given.Len() != wanted.Len() { - return false - } - - usedFromGiven := make([]bool, given.Len()) - foundFromWanted := make([]bool, wanted.Len()) - for i := 0; i < wanted.Len(); i++ { - wantedMatcher := Eq(wanted.Index(i).Interface()) - for j := 0; j < given.Len(); j++ { - if usedFromGiven[j] { - continue - } - if wantedMatcher.Matches(given.Index(j).Interface()) { - foundFromWanted[i] = true - usedFromGiven[j] = true - break - } - } - } - - missingFromWanted := 0 - for _, found := range foundFromWanted { - if !found { - missingFromWanted++ - } - } - extraInGiven := 0 - for _, used := range usedFromGiven { - if !used { - extraInGiven++ - } - } - - return extraInGiven == 0 && missingFromWanted == 0 -} - -func (m inAnyOrderMatcher) prepareValue(x interface{}) (reflect.Value, bool) { - xValue := reflect.ValueOf(x) - switch xValue.Kind() { - case reflect.Slice, reflect.Array: - return xValue, true - default: - return reflect.Value{}, false - } -} - -func (m inAnyOrderMatcher) String() string { - return fmt.Sprintf("has the same elements as %v", m.x) -} - -// Constructors - -// All returns a composite Matcher that returns true if and only all of the -// matchers return true. -func All(ms ...Matcher) Matcher { return allMatcher{ms} } - -// Any returns a matcher that always matches. -func Any() Matcher { return anyMatcher{} } - -// Eq returns a matcher that matches on equality. -// -// Example usage: -// Eq(5).Matches(5) // returns true -// Eq(5).Matches(4) // returns false -func Eq(x interface{}) Matcher { return eqMatcher{x} } - -// Len returns a matcher that matches on length. This matcher returns false if -// is compared to a type that is not an array, chan, map, slice, or string. -func Len(i int) Matcher { - return lenMatcher{i} -} - -// Nil returns a matcher that matches if the received value is nil. -// -// Example usage: -// var x *bytes.Buffer -// Nil().Matches(x) // returns true -// x = &bytes.Buffer{} -// Nil().Matches(x) // returns false -func Nil() Matcher { return nilMatcher{} } - -// Not reverses the results of its given child matcher. -// -// Example usage: -// Not(Eq(5)).Matches(4) // returns true -// Not(Eq(5)).Matches(5) // returns false -func Not(x interface{}) Matcher { - if m, ok := x.(Matcher); ok { - return notMatcher{m} - } - return notMatcher{Eq(x)} -} - -// AssignableToTypeOf is a Matcher that matches if the parameter to the mock -// function is assignable to the type of the parameter to this function. -// -// Example usage: -// var s fmt.Stringer = &bytes.Buffer{} -// AssignableToTypeOf(s).Matches(time.Second) // returns true -// AssignableToTypeOf(s).Matches(99) // returns false -// -// var ctx = reflect.TypeOf((*context.Context)(nil)).Elem() -// AssignableToTypeOf(ctx).Matches(context.Background()) // returns true -func AssignableToTypeOf(x interface{}) Matcher { - if xt, ok := x.(reflect.Type); ok { - return assignableToTypeOfMatcher{xt} - } - return assignableToTypeOfMatcher{reflect.TypeOf(x)} -} - -// InAnyOrder is a Matcher that returns true for collections of the same elements ignoring the order. -// -// Example usage: -// InAnyOrder([]int{1, 2, 3}).Matches([]int{1, 3, 2}) // returns true -// InAnyOrder([]int{1, 2, 3}).Matches([]int{1, 2}) // returns false -func InAnyOrder(x interface{}) Matcher { - return inAnyOrderMatcher{x} -} diff --git a/vendor/github.com/hashicorp/go-version/LICENSE b/vendor/github.com/hashicorp/go-version/LICENSE index 1409d6ab92f..bb1e9a486af 100644 --- a/vendor/github.com/hashicorp/go-version/LICENSE +++ b/vendor/github.com/hashicorp/go-version/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2014 HashiCorp, Inc. +Copyright IBM Corp. 2014, 2025 Mozilla Public License, version 2.0 diff --git a/vendor/github.com/hashicorp/go-version/README.md b/vendor/github.com/hashicorp/go-version/README.md index 4b7806cd964..83a8249f72c 100644 --- a/vendor/github.com/hashicorp/go-version/README.md +++ b/vendor/github.com/hashicorp/go-version/README.md @@ -1,6 +1,7 @@ # Versioning Library for Go + ![Build Status](https://github.com/hashicorp/go-version/actions/workflows/go-tests.yml/badge.svg) -[![GoDoc](https://godoc.org/github.com/hashicorp/go-version?status.svg)](https://godoc.org/github.com/hashicorp/go-version) +[![Go Reference](https://pkg.go.dev/badge/github.com/hashicorp/go-version.svg)](https://pkg.go.dev/github.com/hashicorp/go-version) go-version is a library for parsing versions and version constraints, and verifying versions against a set of constraints. go-version @@ -12,7 +13,7 @@ Versions used with go-version must follow [SemVer](http://semver.org/). ## Installation and Usage Package documentation can be found on -[GoDoc](http://godoc.org/github.com/hashicorp/go-version). +[Go Reference](https://pkg.go.dev/github.com/hashicorp/go-version). Installation can be done with a normal `go get`: diff --git a/vendor/github.com/hashicorp/go-version/constraint.go b/vendor/github.com/hashicorp/go-version/constraint.go index 29bdc4d2b5d..3964da070d6 100644 --- a/vendor/github.com/hashicorp/go-version/constraint.go +++ b/vendor/github.com/hashicorp/go-version/constraint.go @@ -1,4 +1,4 @@ -// Copyright (c) HashiCorp, Inc. +// Copyright IBM Corp. 2014, 2025 // SPDX-License-Identifier: MPL-2.0 package version @@ -8,8 +8,26 @@ import ( "regexp" "sort" "strings" + "sync" ) +var ( + constraintRegexp *regexp.Regexp + constraintRegexpOnce sync.Once +) + +func getConstraintRegexp() *regexp.Regexp { + constraintRegexpOnce.Do(func() { + // This heavy lifting only happens the first time this function is called + constraintRegexp = regexp.MustCompile(fmt.Sprintf( + `^\s*(%s)\s*(%s)\s*$`, + `<=|>=|!=|~>|<|>|=|`, + VersionRegexpRaw, + )) + }) + return constraintRegexp +} + // Constraint represents a single constraint for a version, such as // ">= 1.0". type Constraint struct { @@ -29,38 +47,11 @@ type Constraints []*Constraint type constraintFunc func(v, c *Version) bool -var constraintOperators map[string]constraintOperation - type constraintOperation struct { op operator f constraintFunc } -var constraintRegexp *regexp.Regexp - -func init() { - constraintOperators = map[string]constraintOperation{ - "": {op: equal, f: constraintEqual}, - "=": {op: equal, f: constraintEqual}, - "!=": {op: notEqual, f: constraintNotEqual}, - ">": {op: greaterThan, f: constraintGreaterThan}, - "<": {op: lessThan, f: constraintLessThan}, - ">=": {op: greaterThanEqual, f: constraintGreaterThanEqual}, - "<=": {op: lessThanEqual, f: constraintLessThanEqual}, - "~>": {op: pessimistic, f: constraintPessimistic}, - } - - ops := make([]string, 0, len(constraintOperators)) - for k := range constraintOperators { - ops = append(ops, regexp.QuoteMeta(k)) - } - - constraintRegexp = regexp.MustCompile(fmt.Sprintf( - `^\s*(%s)\s*(%s)\s*$`, - strings.Join(ops, "|"), - VersionRegexpRaw)) -} - // NewConstraint will parse one or more constraints from the given // constraint string. The string must be a comma-separated list of // constraints. @@ -107,7 +98,7 @@ func (cs Constraints) Check(v *Version) bool { // to '>0.2' it is *NOT* treated as equal. // // Missing operator is treated as equal to '=', whitespaces -// are ignored and constraints are sorted before comaparison. +// are ignored and constraints are sorted before comparison. func (cs Constraints) Equals(c Constraints) bool { if len(cs) != len(c) { return false @@ -176,9 +167,9 @@ func (c *Constraint) String() string { } func parseSingle(v string) (*Constraint, error) { - matches := constraintRegexp.FindStringSubmatch(v) + matches := getConstraintRegexp().FindStringSubmatch(v) if matches == nil { - return nil, fmt.Errorf("Malformed constraint: %s", v) + return nil, fmt.Errorf("malformed constraint: %s", v) } check, err := NewVersion(matches[2]) @@ -186,7 +177,25 @@ func parseSingle(v string) (*Constraint, error) { return nil, err } - cop := constraintOperators[matches[1]] + var cop constraintOperation + switch matches[1] { + case "=": + cop = constraintOperation{op: equal, f: constraintEqual} + case "!=": + cop = constraintOperation{op: notEqual, f: constraintNotEqual} + case ">": + cop = constraintOperation{op: greaterThan, f: constraintGreaterThan} + case "<": + cop = constraintOperation{op: lessThan, f: constraintLessThan} + case ">=": + cop = constraintOperation{op: greaterThanEqual, f: constraintGreaterThanEqual} + case "<=": + cop = constraintOperation{op: lessThanEqual, f: constraintLessThanEqual} + case "~>": + cop = constraintOperation{op: pessimistic, f: constraintPessimistic} + default: + cop = constraintOperation{op: equal, f: constraintEqual} + } return &Constraint{ f: cop.f, diff --git a/vendor/github.com/hashicorp/go-version/version.go b/vendor/github.com/hashicorp/go-version/version.go index 7c683c2813a..17b29732ee1 100644 --- a/vendor/github.com/hashicorp/go-version/version.go +++ b/vendor/github.com/hashicorp/go-version/version.go @@ -1,23 +1,39 @@ -// Copyright (c) HashiCorp, Inc. +// Copyright IBM Corp. 2014, 2025 // SPDX-License-Identifier: MPL-2.0 package version import ( - "bytes" "database/sql/driver" "fmt" "regexp" "strconv" "strings" + "sync" ) // The compiled regular expression used to test the validity of a version. var ( - versionRegexp *regexp.Regexp - semverRegexp *regexp.Regexp + versionRegexp *regexp.Regexp + versionRegexpOnce sync.Once + semverRegexp *regexp.Regexp + semverRegexpOnce sync.Once ) +func getVersionRegexp() *regexp.Regexp { + versionRegexpOnce.Do(func() { + versionRegexp = regexp.MustCompile("^" + VersionRegexpRaw + "$") + }) + return versionRegexp +} + +func getSemverRegexp() *regexp.Regexp { + semverRegexpOnce.Do(func() { + semverRegexp = regexp.MustCompile("^" + SemverRegexpRaw + "$") + }) + return semverRegexp +} + // The raw regular expression string used for testing the validity // of a version. const ( @@ -42,28 +58,23 @@ type Version struct { original string } -func init() { - versionRegexp = regexp.MustCompile("^" + VersionRegexpRaw + "$") - semverRegexp = regexp.MustCompile("^" + SemverRegexpRaw + "$") -} - // NewVersion parses the given version and returns a new // Version. func NewVersion(v string) (*Version, error) { - return newVersion(v, versionRegexp) + return newVersion(v, getVersionRegexp()) } // NewSemver parses the given version and returns a new // Version that adheres strictly to SemVer specs // https://semver.org/ func NewSemver(v string) (*Version, error) { - return newVersion(v, semverRegexp) + return newVersion(v, getSemverRegexp()) } func newVersion(v string, pattern *regexp.Regexp) (*Version, error) { matches := pattern.FindStringSubmatch(v) if matches == nil { - return nil, fmt.Errorf("Malformed version: %s", v) + return nil, fmt.Errorf("malformed version: %s", v) } segmentsStr := strings.Split(matches[1], ".") segments := make([]int64, len(segmentsStr)) @@ -71,7 +82,7 @@ func newVersion(v string, pattern *regexp.Regexp) (*Version, error) { val, err := strconv.ParseInt(str, 10, 64) if err != nil { return nil, fmt.Errorf( - "Error parsing version: %s", err) + "error parsing version: %s", err) } segments[i] = val @@ -174,7 +185,7 @@ func (v *Version) Compare(other *Version) int { } else if lhs < rhs { return -1 } - // Otherwis, rhs was > lhs, they're not equal + // Otherwise, rhs was > lhs, they're not equal return 1 } @@ -382,22 +393,29 @@ func (v *Version) Segments64() []int64 { // missing parts (1.0 => 1.0.0) will be made into a canonicalized form // as shown in the parenthesized examples. func (v *Version) String() string { - var buf bytes.Buffer - fmtParts := make([]string, len(v.segments)) + return string(v.bytes()) +} + +func (v *Version) bytes() []byte { + var buf []byte for i, s := range v.segments { - // We can ignore err here since we've pre-parsed the values in segments - str := strconv.FormatInt(s, 10) - fmtParts[i] = str + if i > 0 { + buf = append(buf, '.') + } + buf = strconv.AppendInt(buf, s, 10) } - fmt.Fprintf(&buf, strings.Join(fmtParts, ".")) + if v.pre != "" { - fmt.Fprintf(&buf, "-%s", v.pre) + buf = append(buf, '-') + buf = append(buf, v.pre...) } + if v.metadata != "" { - fmt.Fprintf(&buf, "+%s", v.metadata) + buf = append(buf, '+') + buf = append(buf, v.metadata...) } - return buf.String() + return buf } // Original returns the original parsed version as-is, including any diff --git a/vendor/github.com/hashicorp/go-version/version_collection.go b/vendor/github.com/hashicorp/go-version/version_collection.go index 83547fe13d6..11bc8b1c56c 100644 --- a/vendor/github.com/hashicorp/go-version/version_collection.go +++ b/vendor/github.com/hashicorp/go-version/version_collection.go @@ -1,4 +1,4 @@ -// Copyright (c) HashiCorp, Inc. +// Copyright IBM Corp. 2014, 2025 // SPDX-License-Identifier: MPL-2.0 package version diff --git a/vendor/github.com/onsi/gomega/CHANGELOG.md b/vendor/github.com/onsi/gomega/CHANGELOG.md index b7d7309f3f2..91e65521b42 100644 --- a/vendor/github.com/onsi/gomega/CHANGELOG.md +++ b/vendor/github.com/onsi/gomega/CHANGELOG.md @@ -1,3 +1,18 @@ +## 1.39.1 + +Update all dependencies. This auto-updated the required version of Go to 1.24, consistent with the fact that Go 1.23 has been out of support for almost six months. + +## 1.39.0 + +### Features + +Add `MatchErrorStrictly` which only passes if `errors.Is(actual, expected)` returns true. `MatchError`, by contrast, will fallback to string comparison. + +## 1.38.3 + +### Fixes +make string formatitng more consistent for users who use format.Object directly + ## 1.38.2 - roll back to go 1.23.0 [c404969] diff --git a/vendor/github.com/onsi/gomega/format/format.go b/vendor/github.com/onsi/gomega/format/format.go index 96f04b21045..6c23ba338bc 100644 --- a/vendor/github.com/onsi/gomega/format/format.go +++ b/vendor/github.com/onsi/gomega/format/format.go @@ -262,7 +262,7 @@ func Object(object any, indentation uint) string { if err, ok := object.(error); ok && !isNilValue(value) { // isNilValue check needed here to avoid nil deref due to boxed nil commonRepresentation += "\n" + IndentString(err.Error(), indentation) + "\n" + indent } - return fmt.Sprintf("%s<%s>: %s%s", indent, formatType(value), commonRepresentation, formatValue(value, indentation)) + return fmt.Sprintf("%s<%s>: %s%s", indent, formatType(value), commonRepresentation, formatValue(value, indentation, true)) } /* @@ -306,7 +306,7 @@ func formatType(v reflect.Value) string { } } -func formatValue(value reflect.Value, indentation uint) string { +func formatValue(value reflect.Value, indentation uint, isTopLevel bool) string { if indentation > MaxDepth { return "..." } @@ -367,11 +367,11 @@ func formatValue(value reflect.Value, indentation uint) string { case reflect.Func: return fmt.Sprintf("0x%x", value.Pointer()) case reflect.Ptr: - return formatValue(value.Elem(), indentation) + return formatValue(value.Elem(), indentation, isTopLevel) case reflect.Slice: return truncateLongStrings(formatSlice(value, indentation)) case reflect.String: - return truncateLongStrings(formatString(value.String(), indentation)) + return truncateLongStrings(formatString(value.String(), indentation, isTopLevel)) case reflect.Array: return truncateLongStrings(formatSlice(value, indentation)) case reflect.Map: @@ -392,8 +392,8 @@ func formatValue(value reflect.Value, indentation uint) string { } } -func formatString(object any, indentation uint) string { - if indentation == 1 { +func formatString(object any, indentation uint, isTopLevel bool) string { + if isTopLevel { s := fmt.Sprintf("%s", object) components := strings.Split(s, "\n") result := "" @@ -416,14 +416,14 @@ func formatString(object any, indentation uint) string { func formatSlice(v reflect.Value, indentation uint) string { if v.Kind() == reflect.Slice && v.Type().Elem().Kind() == reflect.Uint8 && isPrintableString(string(v.Bytes())) { - return formatString(v.Bytes(), indentation) + return formatString(v.Bytes(), indentation, false) } l := v.Len() result := make([]string, l) longest := 0 - for i := 0; i < l; i++ { - result[i] = formatValue(v.Index(i), indentation+1) + for i := range l { + result[i] = formatValue(v.Index(i), indentation+1, false) if len(result[i]) > longest { longest = len(result[i]) } @@ -443,7 +443,7 @@ func formatMap(v reflect.Value, indentation uint) string { longest := 0 for i, key := range v.MapKeys() { value := v.MapIndex(key) - result[i] = fmt.Sprintf("%s: %s", formatValue(key, indentation+1), formatValue(value, indentation+1)) + result[i] = fmt.Sprintf("%s: %s", formatValue(key, indentation+1, false), formatValue(value, indentation+1, false)) if len(result[i]) > longest { longest = len(result[i]) } @@ -462,10 +462,10 @@ func formatStruct(v reflect.Value, indentation uint) string { l := v.NumField() result := []string{} longest := 0 - for i := 0; i < l; i++ { + for i := range l { structField := t.Field(i) fieldEntry := v.Field(i) - representation := fmt.Sprintf("%s: %s", structField.Name, formatValue(fieldEntry, indentation+1)) + representation := fmt.Sprintf("%s: %s", structField.Name, formatValue(fieldEntry, indentation+1, false)) result = append(result, representation) if len(representation) > longest { longest = len(representation) @@ -479,7 +479,7 @@ func formatStruct(v reflect.Value, indentation uint) string { } func formatInterface(v reflect.Value, indentation uint) string { - return fmt.Sprintf("<%s>%s", formatType(v.Elem()), formatValue(v.Elem(), indentation)) + return fmt.Sprintf("<%s>%s", formatType(v.Elem()), formatValue(v.Elem(), indentation, false)) } func isNilValue(a reflect.Value) bool { diff --git a/vendor/github.com/onsi/gomega/gomega_dsl.go b/vendor/github.com/onsi/gomega/gomega_dsl.go index fdba34ee9dd..87c70692bfa 100644 --- a/vendor/github.com/onsi/gomega/gomega_dsl.go +++ b/vendor/github.com/onsi/gomega/gomega_dsl.go @@ -22,7 +22,7 @@ import ( "github.com/onsi/gomega/types" ) -const GOMEGA_VERSION = "1.38.2" +const GOMEGA_VERSION = "1.39.1" const nilGomegaPanic = `You are trying to make an assertion, but haven't registered Gomega's fail handler. If you're using Ginkgo then you probably forgot to put your assertion in an It(). diff --git a/vendor/github.com/onsi/gomega/matchers.go b/vendor/github.com/onsi/gomega/matchers.go index 10b6693fd63..16ca8f46dcf 100644 --- a/vendor/github.com/onsi/gomega/matchers.go +++ b/vendor/github.com/onsi/gomega/matchers.go @@ -146,6 +146,24 @@ func MatchError(expected any, functionErrorDescription ...any) types.GomegaMatch } } +// MatchErrorStrictly succeeds iff actual is a non-nil error that matches the passed in +// expected error according to errors.Is(actual, expected). +// +// This behavior differs from MatchError where +// +// Expect(errors.New("some error")).To(MatchError(errors.New("some error"))) +// +// succeeds, but errors.Is would return false so: +// +// Expect(errors.New("some error")).To(MatchErrorStrictly(errors.New("some error"))) +// +// fails. +func MatchErrorStrictly(expected error) types.GomegaMatcher { + return &matchers.MatchErrorStrictlyMatcher{ + Expected: expected, + } +} + // BeClosed succeeds if actual is a closed channel. // It is an error to pass a non-channel to BeClosed, it is also an error to pass nil // @@ -515,8 +533,8 @@ func HaveExistingField(field string) types.GomegaMatcher { // and even interface values. // // actual := 42 -// Expect(actual).To(HaveValue(42)) -// Expect(&actual).To(HaveValue(42)) +// Expect(actual).To(HaveValue(Equal(42))) +// Expect(&actual).To(HaveValue(Equal(42))) func HaveValue(matcher types.GomegaMatcher) types.GomegaMatcher { return &matchers.HaveValueMatcher{ Matcher: matcher, diff --git a/vendor/github.com/onsi/gomega/matchers/have_key_matcher.go b/vendor/github.com/onsi/gomega/matchers/have_key_matcher.go index 9e16dcf5d6c..16630c18e34 100644 --- a/vendor/github.com/onsi/gomega/matchers/have_key_matcher.go +++ b/vendor/github.com/onsi/gomega/matchers/have_key_matcher.go @@ -39,7 +39,7 @@ func (matcher *HaveKeyMatcher) Match(actual any) (success bool, err error) { } keys := reflect.ValueOf(actual).MapKeys() - for i := 0; i < len(keys); i++ { + for i := range keys { success, err := keyMatcher.Match(keys[i].Interface()) if err != nil { return false, fmt.Errorf("HaveKey's key matcher failed with:\n%s%s", format.Indent, err.Error()) diff --git a/vendor/github.com/onsi/gomega/matchers/have_key_with_value_matcher.go b/vendor/github.com/onsi/gomega/matchers/have_key_with_value_matcher.go index 1c53f1e56af..0cd7081532e 100644 --- a/vendor/github.com/onsi/gomega/matchers/have_key_with_value_matcher.go +++ b/vendor/github.com/onsi/gomega/matchers/have_key_with_value_matcher.go @@ -52,7 +52,7 @@ func (matcher *HaveKeyWithValueMatcher) Match(actual any) (success bool, err err } keys := reflect.ValueOf(actual).MapKeys() - for i := 0; i < len(keys); i++ { + for i := range keys { success, err := keyMatcher.Match(keys[i].Interface()) if err != nil { return false, fmt.Errorf("HaveKeyWithValue's key matcher failed with:\n%s%s", format.Indent, err.Error()) diff --git a/vendor/github.com/onsi/gomega/matchers/match_error_strictly_matcher.go b/vendor/github.com/onsi/gomega/matchers/match_error_strictly_matcher.go new file mode 100644 index 00000000000..63969b26630 --- /dev/null +++ b/vendor/github.com/onsi/gomega/matchers/match_error_strictly_matcher.go @@ -0,0 +1,39 @@ +package matchers + +import ( + "errors" + "fmt" + + "github.com/onsi/gomega/format" +) + +type MatchErrorStrictlyMatcher struct { + Expected error +} + +func (matcher *MatchErrorStrictlyMatcher) Match(actual any) (success bool, err error) { + + if isNil(matcher.Expected) { + return false, fmt.Errorf("Expected error is nil, use \"ToNot(HaveOccurred())\" to explicitly check for nil errors") + } + + if isNil(actual) { + return false, fmt.Errorf("Expected an error, got nil") + } + + if !isError(actual) { + return false, fmt.Errorf("Expected an error. Got:\n%s", format.Object(actual, 1)) + } + + actualErr := actual.(error) + + return errors.Is(actualErr, matcher.Expected), nil +} + +func (matcher *MatchErrorStrictlyMatcher) FailureMessage(actual any) (message string) { + return format.Message(actual, "to match error", matcher.Expected) +} + +func (matcher *MatchErrorStrictlyMatcher) NegatedFailureMessage(actual any) (message string) { + return format.Message(actual, "not to match error", matcher.Expected) +} diff --git a/vendor/github.com/onsi/gomega/matchers/support/goraph/edge/edge.go b/vendor/github.com/onsi/gomega/matchers/support/goraph/edge/edge.go index 8c38411b283..72edba20f71 100644 --- a/vendor/github.com/onsi/gomega/matchers/support/goraph/edge/edge.go +++ b/vendor/github.com/onsi/gomega/matchers/support/goraph/edge/edge.go @@ -1,6 +1,9 @@ package edge -import . "github.com/onsi/gomega/matchers/support/goraph/node" +import ( + . "github.com/onsi/gomega/matchers/support/goraph/node" + "slices" +) type Edge struct { Node1 int @@ -20,13 +23,7 @@ func (ec EdgeSet) Free(node Node) bool { } func (ec EdgeSet) Contains(edge Edge) bool { - for _, e := range ec { - if e == edge { - return true - } - } - - return false + return slices.Contains(ec, edge) } func (ec EdgeSet) FindByNodes(node1, node2 Node) (Edge, bool) { diff --git a/vendor/github.com/openshift/assisted-image-service/pkg/isoeditor/isoutil.go b/vendor/github.com/openshift/assisted-image-service/pkg/isoeditor/isoutil.go index 078e4be2398..f869a447531 100644 --- a/vendor/github.com/openshift/assisted-image-service/pkg/isoeditor/isoutil.go +++ b/vendor/github.com/openshift/assisted-image-service/pkg/isoeditor/isoutil.go @@ -104,7 +104,7 @@ func Create(outPath string, workDir string, volumeLabel string) error { // we were writing to a particular partition on a device, but we are // not so the minimum iso size will work for us here minISOSize := 38 * 1024 - d, err := diskfs.Create(outPath, int64(minISOSize), diskfs.Raw, diskfs.SectorSizeDefault) + d, err := diskfs.Create(outPath, int64(minISOSize), diskfs.SectorSizeDefault) if err != nil { return err } @@ -353,7 +353,7 @@ func ReadFileFromISO(isoPath, filePath string) ([]byte, error) { // Gets directly the ISO 9660 filesystem (equivalent to GetFileSystem(0)). func GetISO9660FileSystem(d *disk.Disk) (filesystem.FileSystem, error) { - return iso9660.Read(d.File, d.Size, 0, 0) + return iso9660.Read(d.Backend, d.Size, 0, 0) } // fileEntry represents a single file to be added to a CPIO archive diff --git a/vendor/github.com/openshift/assisted-image-service/pkg/isoeditor/mock_editor.go b/vendor/github.com/openshift/assisted-image-service/pkg/isoeditor/mock_editor.go index 935b21f0ec2..7e073291b23 100644 --- a/vendor/github.com/openshift/assisted-image-service/pkg/isoeditor/mock_editor.go +++ b/vendor/github.com/openshift/assisted-image-service/pkg/isoeditor/mock_editor.go @@ -1,5 +1,10 @@ // Code generated by MockGen. DO NOT EDIT. // Source: github.com/openshift/assisted-image-service/pkg/isoeditor (interfaces: Editor) +// +// Generated by this command: +// +// mockgen -package=isoeditor -destination=mock_editor.go . Editor +// // Package isoeditor is a generated GoMock package. package isoeditor @@ -7,13 +12,14 @@ package isoeditor import ( reflect "reflect" - gomock "github.com/golang/mock/gomock" + gomock "go.uber.org/mock/gomock" ) // MockEditor is a mock of Editor interface. type MockEditor struct { ctrl *gomock.Controller recorder *MockEditorMockRecorder + isgomock struct{} } // MockEditorMockRecorder is the mock recorder for MockEditor. @@ -34,15 +40,15 @@ func (m *MockEditor) EXPECT() *MockEditorMockRecorder { } // CreateMinimalISOTemplate mocks base method. -func (m *MockEditor) CreateMinimalISOTemplate(arg0, arg1, arg2, arg3, arg4, arg5 string) error { +func (m *MockEditor) CreateMinimalISOTemplate(fullISOPath, rootFSURL, arch, minimalISOPath, openshiftVersion, nmstatectlPath string) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CreateMinimalISOTemplate", arg0, arg1, arg2, arg3, arg4, arg5) + ret := m.ctrl.Call(m, "CreateMinimalISOTemplate", fullISOPath, rootFSURL, arch, minimalISOPath, openshiftVersion, nmstatectlPath) ret0, _ := ret[0].(error) return ret0 } // CreateMinimalISOTemplate indicates an expected call of CreateMinimalISOTemplate. -func (mr *MockEditorMockRecorder) CreateMinimalISOTemplate(arg0, arg1, arg2, arg3, arg4, arg5 interface{}) *gomock.Call { +func (mr *MockEditorMockRecorder) CreateMinimalISOTemplate(fullISOPath, rootFSURL, arch, minimalISOPath, openshiftVersion, nmstatectlPath any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateMinimalISOTemplate", reflect.TypeOf((*MockEditor)(nil).CreateMinimalISOTemplate), arg0, arg1, arg2, arg3, arg4, arg5) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateMinimalISOTemplate", reflect.TypeOf((*MockEditor)(nil).CreateMinimalISOTemplate), fullISOPath, rootFSURL, arch, minimalISOPath, openshiftVersion, nmstatectlPath) } diff --git a/vendor/github.com/openshift/assisted-image-service/pkg/isoeditor/mock_executer.go b/vendor/github.com/openshift/assisted-image-service/pkg/isoeditor/mock_executer.go index 416b51f6d94..df2f595fd57 100644 --- a/vendor/github.com/openshift/assisted-image-service/pkg/isoeditor/mock_executer.go +++ b/vendor/github.com/openshift/assisted-image-service/pkg/isoeditor/mock_executer.go @@ -1,5 +1,10 @@ // Code generated by MockGen. DO NOT EDIT. // Source: github.com/openshift/assisted-image-service/pkg/isoeditor (interfaces: Executer) +// +// Generated by this command: +// +// mockgen -package=isoeditor -destination=mock_executer.go . Executer +// // Package isoeditor is a generated GoMock package. package isoeditor @@ -7,13 +12,14 @@ package isoeditor import ( reflect "reflect" - gomock "github.com/golang/mock/gomock" + gomock "go.uber.org/mock/gomock" ) // MockExecuter is a mock of Executer interface. type MockExecuter struct { ctrl *gomock.Controller recorder *MockExecuterMockRecorder + isgomock struct{} } // MockExecuterMockRecorder is the mock recorder for MockExecuter. @@ -34,16 +40,16 @@ func (m *MockExecuter) EXPECT() *MockExecuterMockRecorder { } // Execute mocks base method. -func (m *MockExecuter) Execute(arg0, arg1 string) (string, error) { +func (m *MockExecuter) Execute(command, workDir string) (string, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Execute", arg0, arg1) + ret := m.ctrl.Call(m, "Execute", command, workDir) ret0, _ := ret[0].(string) ret1, _ := ret[1].(error) return ret0, ret1 } // Execute indicates an expected call of Execute. -func (mr *MockExecuterMockRecorder) Execute(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockExecuterMockRecorder) Execute(command, workDir any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Execute", reflect.TypeOf((*MockExecuter)(nil).Execute), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Execute", reflect.TypeOf((*MockExecuter)(nil).Execute), command, workDir) } diff --git a/vendor/github.com/openshift/assisted-image-service/pkg/isoeditor/mock_nmstate_handler.go b/vendor/github.com/openshift/assisted-image-service/pkg/isoeditor/mock_nmstate_handler.go index 91ed852bc14..9ee856ad946 100644 --- a/vendor/github.com/openshift/assisted-image-service/pkg/isoeditor/mock_nmstate_handler.go +++ b/vendor/github.com/openshift/assisted-image-service/pkg/isoeditor/mock_nmstate_handler.go @@ -1,5 +1,10 @@ // Code generated by MockGen. DO NOT EDIT. // Source: github.com/openshift/assisted-image-service/pkg/isoeditor (interfaces: NmstateHandler) +// +// Generated by this command: +// +// mockgen -package=isoeditor -destination=mock_nmstate_handler.go . NmstateHandler +// // Package isoeditor is a generated GoMock package. package isoeditor @@ -7,13 +12,14 @@ package isoeditor import ( reflect "reflect" - gomock "github.com/golang/mock/gomock" + gomock "go.uber.org/mock/gomock" ) // MockNmstateHandler is a mock of NmstateHandler interface. type MockNmstateHandler struct { ctrl *gomock.Controller recorder *MockNmstateHandlerMockRecorder + isgomock struct{} } // MockNmstateHandlerMockRecorder is the mock recorder for MockNmstateHandler. @@ -34,16 +40,16 @@ func (m *MockNmstateHandler) EXPECT() *MockNmstateHandlerMockRecorder { } // BuildNmstateCpioArchive mocks base method. -func (m *MockNmstateHandler) BuildNmstateCpioArchive(arg0 string) ([]byte, error) { +func (m *MockNmstateHandler) BuildNmstateCpioArchive(rootfsPath string) ([]byte, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "BuildNmstateCpioArchive", arg0) + ret := m.ctrl.Call(m, "BuildNmstateCpioArchive", rootfsPath) ret0, _ := ret[0].([]byte) ret1, _ := ret[1].(error) return ret0, ret1 } // BuildNmstateCpioArchive indicates an expected call of BuildNmstateCpioArchive. -func (mr *MockNmstateHandlerMockRecorder) BuildNmstateCpioArchive(arg0 interface{}) *gomock.Call { +func (mr *MockNmstateHandlerMockRecorder) BuildNmstateCpioArchive(rootfsPath any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BuildNmstateCpioArchive", reflect.TypeOf((*MockNmstateHandler)(nil).BuildNmstateCpioArchive), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BuildNmstateCpioArchive", reflect.TypeOf((*MockNmstateHandler)(nil).BuildNmstateCpioArchive), rootfsPath) } diff --git a/vendor/github.com/openshift/assisted-image-service/pkg/isoeditor/mock_nmstatectl_extractor.go b/vendor/github.com/openshift/assisted-image-service/pkg/isoeditor/mock_nmstatectl_extractor.go new file mode 100644 index 00000000000..8e4b6724745 --- /dev/null +++ b/vendor/github.com/openshift/assisted-image-service/pkg/isoeditor/mock_nmstatectl_extractor.go @@ -0,0 +1,55 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/openshift/assisted-image-service/pkg/isoeditor (interfaces: NmstatectlExtractor) +// +// Generated by this command: +// +// mockgen -package=isoeditor -destination=mock_nmstatectl_extractor.go . NmstatectlExtractor +// + +// Package isoeditor is a generated GoMock package. +package isoeditor + +import ( + reflect "reflect" + + gomock "go.uber.org/mock/gomock" +) + +// MockNmstatectlExtractor is a mock of NmstatectlExtractor interface. +type MockNmstatectlExtractor struct { + ctrl *gomock.Controller + recorder *MockNmstatectlExtractorMockRecorder + isgomock struct{} +} + +// MockNmstatectlExtractorMockRecorder is the mock recorder for MockNmstatectlExtractor. +type MockNmstatectlExtractorMockRecorder struct { + mock *MockNmstatectlExtractor +} + +// NewMockNmstatectlExtractor creates a new mock instance. +func NewMockNmstatectlExtractor(ctrl *gomock.Controller) *MockNmstatectlExtractor { + mock := &MockNmstatectlExtractor{ctrl: ctrl} + mock.recorder = &MockNmstatectlExtractorMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockNmstatectlExtractor) EXPECT() *MockNmstatectlExtractorMockRecorder { + return m.recorder +} + +// ExtractNmstatectl mocks base method. +func (m *MockNmstatectlExtractor) ExtractNmstatectl(nmstateDir string) (string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ExtractNmstatectl", nmstateDir) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ExtractNmstatectl indicates an expected call of ExtractNmstatectl. +func (mr *MockNmstatectlExtractorMockRecorder) ExtractNmstatectl(nmstateDir any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExtractNmstatectl", reflect.TypeOf((*MockNmstatectlExtractor)(nil).ExtractNmstatectl), nmstateDir) +} diff --git a/vendor/github.com/openshift/assisted-image-service/pkg/isoeditor/mock_nmstatectl_extractor_factory.go b/vendor/github.com/openshift/assisted-image-service/pkg/isoeditor/mock_nmstatectl_extractor_factory.go new file mode 100644 index 00000000000..224153f3e0d --- /dev/null +++ b/vendor/github.com/openshift/assisted-image-service/pkg/isoeditor/mock_nmstatectl_extractor_factory.go @@ -0,0 +1,55 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/openshift/assisted-image-service/pkg/isoeditor (interfaces: NmstatectlExtractorFactory) +// +// Generated by this command: +// +// mockgen -package=isoeditor -destination=mock_nmstatectl_extractor_factory.go . NmstatectlExtractorFactory +// + +// Package isoeditor is a generated GoMock package. +package isoeditor + +import ( + reflect "reflect" + + gomock "go.uber.org/mock/gomock" +) + +// MockNmstatectlExtractorFactory is a mock of NmstatectlExtractorFactory interface. +type MockNmstatectlExtractorFactory struct { + ctrl *gomock.Controller + recorder *MockNmstatectlExtractorFactoryMockRecorder + isgomock struct{} +} + +// MockNmstatectlExtractorFactoryMockRecorder is the mock recorder for MockNmstatectlExtractorFactory. +type MockNmstatectlExtractorFactoryMockRecorder struct { + mock *MockNmstatectlExtractorFactory +} + +// NewMockNmstatectlExtractorFactory creates a new mock instance. +func NewMockNmstatectlExtractorFactory(ctrl *gomock.Controller) *MockNmstatectlExtractorFactory { + mock := &MockNmstatectlExtractorFactory{ctrl: ctrl} + mock.recorder = &MockNmstatectlExtractorFactoryMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockNmstatectlExtractorFactory) EXPECT() *MockNmstatectlExtractorFactoryMockRecorder { + return m.recorder +} + +// CreateNmstatectlExtractor mocks base method. +func (m *MockNmstatectlExtractorFactory) CreateNmstatectlExtractor(nmstateDir string) (NmstatectlExtractor, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateNmstatectlExtractor", nmstateDir) + ret0, _ := ret[0].(NmstatectlExtractor) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateNmstatectlExtractor indicates an expected call of CreateNmstatectlExtractor. +func (mr *MockNmstatectlExtractorFactoryMockRecorder) CreateNmstatectlExtractor(nmstateDir any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateNmstatectlExtractor", reflect.TypeOf((*MockNmstatectlExtractorFactory)(nil).CreateNmstatectlExtractor), nmstateDir) +} diff --git a/vendor/github.com/openshift/assisted-image-service/pkg/isoeditor/nmstate_handler.go b/vendor/github.com/openshift/assisted-image-service/pkg/isoeditor/nmstate_handler.go index 94d748ad17d..257e2de4c88 100644 --- a/vendor/github.com/openshift/assisted-image-service/pkg/isoeditor/nmstate_handler.go +++ b/vendor/github.com/openshift/assisted-image-service/pkg/isoeditor/nmstate_handler.go @@ -19,14 +19,16 @@ type NmstateHandler interface { } type nmstateHandler struct { - workDir string - executer Executer + workDir string + executer Executer + nmstatectlExtractorFactory NmstatectlExtractorFactory } -func NewNmstateHandler(workDir string, executer Executer) NmstateHandler { +func NewNmstateHandler(workDir string, executer Executer, nmstatectlExtractorFactory NmstatectlExtractorFactory) NmstateHandler { return &nmstateHandler{ - workDir: workDir, - executer: executer, + workDir: workDir, + executer: executer, + nmstatectlExtractorFactory: nmstatectlExtractorFactory, } } @@ -50,7 +52,7 @@ func (n *nmstateHandler) BuildNmstateCpioArchive(rootfsPath string) ([]byte, err if err != nil { return nil, err } - nmstatectlPath := filepath.Join(nmstateDir, "squashfs-root", binaryPath) + nmstatectlPath := filepath.Join(nmstateDir, binaryPath) // Check if nmstatectl binary file exists if _, err = os.Stat(nmstatectlPath); os.IsNotExist(err) { @@ -78,20 +80,110 @@ func (n *nmstateHandler) BuildNmstateCpioArchive(rootfsPath string) ([]byte, err return compressedCpio, err } -// TODO: Update the code to utilize go-diskfs's squashfs instead of unsquashfs once go-diskfs supports the zstd compression format used by CoreOS - MGMT-19227 func (n *nmstateHandler) extractNmstatectl(rootfsPath, nmstateDir string) (string, error) { _, err := n.executer.Execute(fmt.Sprintf("cat %s | cpio -i", rootfsPath), nmstateDir) if err != nil { log.Errorf("failed to extract rootfs.img using cpio command: %v", err.Error()) return "", err } + + nmstatectlExtractor, err := n.nmstatectlExtractorFactory.CreateNmstatectlExtractor(nmstateDir) + if err != nil { + log.Errorf("failed to create nmstate extractor: %v", err.Error()) + return "", err + } + + return nmstatectlExtractor.ExtractNmstatectl(nmstateDir) +} + +//go:generate mockgen -package=isoeditor -destination=mock_executer.go . Executer +type Executer interface { + Execute(command, workDir string) (string, error) +} + +type CommonExecuter struct{} + +func (e *CommonExecuter) Execute(command, workDir string) (string, error) { + var stdoutBytes, stderrBytes bytes.Buffer + cmd := exec.Command("bash", "-c", command) + cmd.Stdout = &stdoutBytes + cmd.Stderr = &stderrBytes + log.Infof("Running cmd: %s", command) + cmd.Dir = workDir + err := cmd.Run() + if err != nil { + return "", errors.Wrapf(err, "Failed to execute cmd (%s): %s", cmd, stderrBytes.String()) + } + + return strings.TrimSuffix(stdoutBytes.String(), "\n"), nil +} + +//go:generate mockgen -package=isoeditor -destination=mock_nmstatectl_extractor.go . NmstatectlExtractor +type NmstatectlExtractor interface { + ExtractNmstatectl(nmstateDir string) (string, error) +} + +//go:generate mockgen -package=isoeditor -destination=mock_nmstatectl_extractor_factory.go . NmstatectlExtractorFactory +type NmstatectlExtractorFactory interface { + CreateNmstatectlExtractor(nmstateDir string) (NmstatectlExtractor, error) +} + +type nmstatectlExtractorFactory struct { + executer Executer +} + +func NewNmstatectlExtractorFactory(executer Executer) NmstatectlExtractorFactory { + return &nmstatectlExtractorFactory{ + executer: executer, + } +} + +func (f *nmstatectlExtractorFactory) CreateNmstatectlExtractor(nmstateDir string) (NmstatectlExtractor, error) { + r, err := regexp.Compile(`root\.([^.]+)$`) + if err != nil { + log.Errorf("failed to compile regexp: %v", err.Error()) + return nil, err + } + + entries, err := os.ReadDir(nmstateDir) + if err != nil { + log.Errorf("failed to list files in nmstateDir: %v", err.Error()) + return nil, err + } + + extension := "" + for _, entry := range entries { + matches := r.FindStringSubmatch(entry.Name()) + if len(matches) > 1 { + extension = matches[1] + } + } + + switch extension { + case "squashfs": + return &squashfsExtractor{executer: f.executer}, nil + case "erofs": + return &erofsExtractor{executer: f.executer}, nil + case "": + return nil, errors.New("failed to find root file extension") + default: + return nil, fmt.Errorf("unknown extension for root file: %s", extension) + } +} + +type squashfsExtractor struct { + executer Executer +} + +// TODO: Update the code to utilize go-diskfs's squashfs instead of unsquashfs once go-diskfs supports the zstd compression format used by CoreOS - MGMT-19227 +func (e *squashfsExtractor) ExtractNmstatectl(nmstateDir string) (string, error) { // limiting files is needed on el<=9 due to https://github.com/plougher/squashfs-tools/issues/125 ulimit := "ulimit -n 1024" // Listing the filesystem concisely, displaying only files (using `-lc` option). // Each file in the output won't include any prefix before `/ostree` (by using `-dest ''` option), // which is useful when invoking `-extract-file` (after finding the `nmstatectl` binary path). - list, err := n.executer.Execute(fmt.Sprintf("%s ; unsquashfs -dest '' -lc %s", ulimit, "root.squashfs"), nmstateDir) + list, err := e.executer.Execute(fmt.Sprintf("%s ; unsquashfs -dest '' -lc %s", ulimit, "root.squashfs"), nmstateDir) if err != nil { log.Errorf("failed to unsquashfs root.squashfs: %v", err.Error()) return "", err @@ -104,32 +196,85 @@ func (n *nmstateHandler) extractNmstatectl(rootfsPath, nmstateDir string) (strin } binaryPath := r.FindString(list) - _, err = n.executer.Execute(fmt.Sprintf("%s ; unsquashfs -no-xattrs %s -extract-file %s", ulimit, "root.squashfs", binaryPath), nmstateDir) + _, err = e.executer.Execute(fmt.Sprintf("%s ; unsquashfs -no-xattrs %s -extract-file %s", ulimit, "root.squashfs", binaryPath), nmstateDir) if err != nil { log.Errorf("failed to unsquashfs root.squashfs: %v", err.Error()) return "", err } - return binaryPath, nil + return filepath.Join("squashfs-root", binaryPath), nil } -//go:generate mockgen -package=isoeditor -destination=mock_executer.go . Executer -type Executer interface { - Execute(command, workDir string) (string, error) +type erofsExtractor struct { + executer Executer } -type CommonExecuter struct{} +func (e *erofsExtractor) ExtractNmstatectl(nmstateDir string) (string, error) { + nmstatectlPath, err := e.findFileInErofs(nmstateDir, "/", "nmstatectl") + if err != nil { + log.Errorf("failed to search for nmstatectl in root.erofs: %v", err.Error()) + return "", err + } -func (e *CommonExecuter) Execute(command, workDir string) (string, error) { - var stdoutBytes, stderrBytes bytes.Buffer - cmd := exec.Command("bash", "-c", command) - cmd.Stdout = &stdoutBytes - cmd.Stderr = &stderrBytes - log.Infof(fmt.Sprintf("Running cmd: %s\n", command)) - cmd.Dir = workDir - err := cmd.Run() + if nmstatectlPath == "" { + return "", errors.New("nmstatectl not found in root.erofs") + } + + _, err = e.executer.Execute(fmt.Sprintf("dump.erofs --cat --path=%s root.erofs > nmstatectl", nmstatectlPath), nmstateDir) if err != nil { - return "", errors.Wrapf(err, "Failed to execute cmd (%s): %s", cmd, stderrBytes.String()) + log.Errorf("failed to copy nmstatectl from root.erofs: %v", err.Error()) + return "", err } - return strings.TrimSuffix(stdoutBytes.String(), "\n"), nil + return "nmstatectl", nil +} + +// findFileInErofs recursively searches for a file in an EROFS image using dump.erofs +func (e *erofsExtractor) findFileInErofs(nmstateDir, dirPath, filename string) (string, error) { + output, err := e.executer.Execute(fmt.Sprintf("dump.erofs --path=%s --ls root.erofs", dirPath), nmstateDir) + if err != nil { + return "", errors.Wrapf(err, "failed to list directory %s", dirPath) + } + + // Parse the output to find entries + // Format: " NID TYPE FILENAME" + // Example: " 44 1 .aleph-version.json" + // Type 1 = regular file, Type 2 = directory + entryRegex := regexp.MustCompile(`^\s*(\d+)\s+(\d+)\s+(.+)$`) + + lines := strings.Split(output, "\n") + for _, line := range lines { + matches := entryRegex.FindStringSubmatch(line) + if len(matches) != 4 { + continue + } + + entryType := matches[2] + entryName := strings.TrimSpace(matches[3]) + + if entryName == "." || entryName == ".." { + continue + } + + fullPath := filepath.Join(dirPath, entryName) + + // Return if a regular file (entry type 1) matches + if entryName == filename && entryType == "1" { + return fullPath, nil + } + + // Recurse into directories (type 2) + if entryType == "2" { + found, err := e.findFileInErofs(nmstateDir, fullPath, filename) + if err != nil { + // Log but continue searching other directories + log.Warnf("failed to search in %s: %v", fullPath, err) + continue + } + if found != "" { + return found, nil + } + } + } + + return "", nil } diff --git a/vendor/github.com/openshift/assisted-image-service/pkg/isoeditor/rhcos.go b/vendor/github.com/openshift/assisted-image-service/pkg/isoeditor/rhcos.go index adf330c3196..d645f409c3e 100644 --- a/vendor/github.com/openshift/assisted-image-service/pkg/isoeditor/rhcos.go +++ b/vendor/github.com/openshift/assisted-image-service/pkg/isoeditor/rhcos.go @@ -1,10 +1,12 @@ package isoeditor import ( + "encoding/json" "fmt" "os" "path/filepath" "regexp" + "strings" "github.com/openshift/assisted-image-service/internal/common" log "github.com/sirupsen/logrus" @@ -19,6 +21,36 @@ const ( RootfsImagePath = "images/pxeboot/rootfs.img" ) +// transformKernelArgs applies the standard kernel argument transformations: +// 1. Remove coreos.liveiso parameter +// 2. Add coreos.live.rootfs_url parameter at the specified insertion point +func transformKernelArgs(content string, insertionPattern string, rootFSURL string, fileEntry *kargsFileEntry) (string, error) { + // Validate rootfs URL + if strings.Contains(rootFSURL, "$") { + return "", fmt.Errorf("invalid rootfs URL: contains invalid character '$'") + } + if strings.Contains(rootFSURL, "\\") { + return "", fmt.Errorf("invalid rootfs URL: contains invalid character '\\'") + } + + var err error + + // Remove the coreos.liveiso parameter + content, err = editString(content, `\b(?Pcoreos\.liveiso=\S+ ?)`, "", fileEntry) + if err != nil { + return "", err + } + + // Add the rootfs_url parameter at the specified insertion point + replacement := " coreos.live.rootfs_url=\"" + rootFSURL + "\"" + content, err = editString(content, insertionPattern, replacement, fileEntry) + if err != nil { + return "", err + } + + return content, nil +} + //go:generate mockgen -package=isoeditor -destination=mock_editor.go . Editor type Editor interface { CreateMinimalISOTemplate(fullISOPath, rootFSURL, arch, minimalISOPath, openshiftVersion, nmstatectlPath string) error @@ -52,19 +84,11 @@ func CreateMinimalISO(extractDir, volumeID, rootFSURL, arch, minimalISOPath stri includeNmstateRamDisk = true } - if err := fixGrubConfig(rootFSURL, extractDir, includeNmstateRamDisk); err != nil { - log.WithError(err).Warnf("Failed to edit grub config") + if err := updateKargs(extractDir, rootFSURL, includeNmstateRamDisk, arch); err != nil { + log.WithError(err).Warnf("Failed to update kargs offsets and sizes") return err } - // ignore isolinux.cfg for ppc64le because it doesn't exist - if arch != "ppc64le" { - if err := fixIsolinuxConfig(rootFSURL, extractDir, includeNmstateRamDisk); err != nil { - log.WithError(err).Warnf("Failed to edit isolinux config") - return err - } - } - if err := Create(minimalISOPath, extractDir, volumeID); err != nil { return err } @@ -148,13 +172,16 @@ func embedInitrdPlaceholders(extractDir string) error { return nil } -func fixGrubConfig(rootFSURL, extractDir string, includeNmstateRamDisk bool) error { +// fixGrubConfig modifies grub.cfg and updates kargs config in place +func fixGrubConfig(rootFSURL, extractDir string, includeNmstateRamDisk bool, kargs *kargsConfig) error { availableGrubPaths := []string{"EFI/redhat/grub.cfg", "EFI/fedora/grub.cfg", "boot/grub/grub.cfg", "EFI/centos/grub.cfg"} var foundGrubPath string + var fileEntry *kargsFileEntry for _, pathSection := range availableGrubPaths { path := filepath.Join(extractDir, pathSection) if _, err := os.Stat(path); err == nil { foundGrubPath = path + fileEntry = kargs.FindFileByPath(pathSection) break } } @@ -162,65 +189,230 @@ func fixGrubConfig(rootFSURL, extractDir string, includeNmstateRamDisk bool) err return fmt.Errorf("no grub.cfg found, possible paths are %v", availableGrubPaths) } - // Add the rootfs url - replacement := fmt.Sprintf("$1 $2 'coreos.live.rootfs_url=%s'", rootFSURL) - if err := editFile(foundGrubPath, `(?m)^(\s+linux) (.+| )+$`, replacement); err != nil { + // Read the file content + content, err := os.ReadFile(foundGrubPath) + if err != nil { return err } + contentStr := string(content) - // Remove the coreos.liveiso parameter - if err := editFile(foundGrubPath, ` coreos.liveiso=\S+`, ""); err != nil { + // Typical grub.cfg lines we're modifying: + // + // linux /images/pxeboot/vmlinuz rw coreos.liveiso=rhcos-9.6.20250523-0 ignition.firstboot ignition.platform.id=metal + // initrd /images/pxeboot/initrd.img /images/ignition.img + + // Apply standard kernel argument transformations (remove coreos.liveiso, add rootfs_url) + contentStr, err = transformKernelArgs(contentStr, `(?m)^(\s+linux .+)(?P$)`, rootFSURL, fileEntry) + if err != nil { return err } - // Edit config to add custom ramdisk image to initrd + // Edit config to add custom ramdisk image to initrd - capture the end-of-line position to append our images + var initrdReplacement string if includeNmstateRamDisk { - if err := editFile(foundGrubPath, `(?m)^(\s+initrd) (.+| )+$`, fmt.Sprintf("$1 $2 %s %s", ramDiskImagePath, nmstateDiskImagePath)); err != nil { - return err - } + initrdReplacement = " " + ramDiskImagePath + " " + nmstateDiskImagePath } else { - if err := editFile(foundGrubPath, `(?m)^(\s+initrd) (.+| )+$`, fmt.Sprintf("$1 $2 %s", ramDiskImagePath)); err != nil { - return err - } + initrdReplacement = " " + ramDiskImagePath + } + contentStr, err = editString(contentStr, `(?m)^(\s+initrd .+)(?P$)`, initrdReplacement, fileEntry) + if err != nil { + return err } - return nil + // Write the modified content back to the file + return os.WriteFile(foundGrubPath, []byte(contentStr), 0600) } -func fixIsolinuxConfig(rootFSURL, extractDir string, includeNmstateRamDisk bool) error { - replacement := fmt.Sprintf("$1 $2 coreos.live.rootfs_url=%s", rootFSURL) - if err := editFile(filepath.Join(extractDir, "isolinux/isolinux.cfg"), `(?m)^(\s+append) (.+| )+$`, replacement); err != nil { +// fixIsolinuxConfig modifies isolinux.cfg and updates kargs config in place +func fixIsolinuxConfig(rootFSURL, extractDir string, includeNmstateRamDisk bool, kargs *kargsConfig) error { + relativeIsolinuxPath := strings.TrimPrefix(defaultIsolinuxFilePath, "/") + isolinuxPath := filepath.Join(extractDir, relativeIsolinuxPath) + + // Read the file content + content, err := os.ReadFile(isolinuxPath) + if err != nil { return err } + contentStr := string(content) - if err := editFile(filepath.Join(extractDir, "isolinux/isolinux.cfg"), ` coreos.liveiso=\S+`, ""); err != nil { + // Typical isolinux.cfg line we're modifying: + // + // append initrd=/images/pxeboot/initrd.img,/images/ignition.img rw coreos.liveiso=rhcos-9.6.20250523-0 ignition.firstboot ignition.platform.id=metal + + // Find the kargs file entry for this file + fileEntry := kargs.FindFileByPath(relativeIsolinuxPath) + + // Apply standard kernel argument transformations (remove coreos.liveiso, add rootfs_url) + contentStr, err = transformKernelArgs(contentStr, `(?m)^(\s+append .+)(?P$)`, rootFSURL, fileEntry) + if err != nil { return err } + // Add ramdisk images to initrd specification - capture the position right after the initrd argument to append our images + var initrdReplacement string if includeNmstateRamDisk { - if err := editFile(filepath.Join(extractDir, "isolinux/isolinux.cfg"), `(?m)^(\s+append.*initrd=\S+) (.*)$`, fmt.Sprintf("${1},%s,%s ${2}", ramDiskImagePath, nmstateDiskImagePath)); err != nil { - return err - } + initrdReplacement = "," + ramDiskImagePath + "," + nmstateDiskImagePath } else { - if err := editFile(filepath.Join(extractDir, "isolinux/isolinux.cfg"), `(?m)^(\s+append.*initrd=\S+) (.*)$`, fmt.Sprintf("${1},%s ${2}", ramDiskImagePath)); err != nil { - return err - } + initrdReplacement = "," + ramDiskImagePath + } + contentStr, err = editString(contentStr, `(?m)^(\s+append.*initrd=\S+)(?P)`, initrdReplacement, fileEntry) + if err != nil { + return err + } + + // Write the modified content back to the file + return os.WriteFile(isolinuxPath, []byte(contentStr), 0600) +} + +// editString applies a regex replacement to a string and returns the modified string +// It looks for a named capture group called "replace" and replaces only that content, using precise string manipulation +func editString(content string, reString string, replacement string, fileEntry *kargsFileEntry) (string, error) { + re := regexp.MustCompile(reString) + + // Get the index of the "replace" named capture group + replaceIndex := re.SubexpIndex("replace") + if replaceIndex == -1 { + return "", fmt.Errorf("regex must have a named capture group called 'replace'") + } + + // Find the first match with subgroups + submatchIndexes := re.FindStringSubmatchIndex(content) + if submatchIndexes == nil { + // No match found + return content, nil } + // submatchIndexes contains [fullMatchStart, fullMatchEnd, group1Start, group1End, group2Start, group2End, ...] + if len(submatchIndexes) < (replaceIndex+1)*2 { + return "", fmt.Errorf("regex match does not contain the 'replace' capture group") + } + + replaceStart := submatchIndexes[replaceIndex*2] + replaceEnd := submatchIndexes[replaceIndex*2+1] + + // Replace only the "replace" capturing group + newContent := content[:replaceStart] + replacement + content[replaceEnd:] + + if content == newContent { + return content, nil + } + + if fileEntry == nil || fileEntry.Offset == nil { + return newContent, nil + } + + embedStart := *fileEntry.Offset + fileSizeChange := int64(len(newContent)) - int64(len(content)) + + replaceStartPos := int64(replaceStart) + replaceEndPos := int64(replaceEnd) + + // Add boundary crossing check to ensure no replacements span across the embed area start boundary + if replaceStartPos < embedStart && replaceEndPos > embedStart { + return "", fmt.Errorf("replacement spans across embed area boundary (replace: %d-%d, embed starts: %d)", replaceStartPos, replaceEndPos, embedStart) + } + + if replaceEndPos <= embedStart { + // Change is before embed area - affects offset + *fileEntry.Offset += fileSizeChange + } + + return newContent, nil +} + +type kargsFileEntry struct { + End *string `json:"end,omitempty"` + Offset *int64 `json:"offset,omitempty"` + Pad *string `json:"pad,omitempty"` + Path *string `json:"path,omitempty"` +} + +type kargsConfig struct { + Default string `json:"default"` + Files []kargsFileEntry `json:"files"` + Size int64 `json:"size"` +} + +// FindFileByPath searches for a file entry by its path and returns a pointer to it. +// Returns nil if no file with the specified path is found. +func (k *kargsConfig) FindFileByPath(path string) *kargsFileEntry { + if k == nil { + return nil + } + for i := range k.Files { + if k.Files[i].Path != nil && *k.Files[i].Path == path { + return &k.Files[i] + } + } return nil } -func editFile(fileName string, reString string, replacement string) error { - content, err := os.ReadFile(fileName) +// updateDefaultKargs modifies the default kernel arguments to match bootloader modifications +// and applies the embed area size change directly to config.Size +func updateDefaultKargs(config *kargsConfig, rootFSURL string) error { + originalDefault := config.Default + + // Apply the same transformations we make to bootloader configs + // For default kargs, we append at the end (using $ insertion pattern) + // Pass nil for fileEntry since we don't track offsets for the default string + var err error + config.Default, err = transformKernelArgs(config.Default, `(?P$)`, rootFSURL, nil) if err != nil { return err } - re := regexp.MustCompile(reString) - newContent := re.ReplaceAllString(string(content), replacement) + // Calculate and apply the embed area size change from the default kargs transformation + embedSizeChange := int64(len(config.Default)) - int64(len(originalDefault)) + config.Size += embedSizeChange - if err := os.WriteFile(fileName, []byte(newContent), 0600); err != nil { - return err + return nil +} + +// updateKargs reads kargs.json, applies fixes with embed area awareness, and updates kargs.json +func updateKargs(extractDir, rootFSURL string, includeNmstateRamDisk bool, arch string) error { + kargsPath := filepath.Join(extractDir, "coreos/kargs.json") + + var config *kargsConfig + + if _, err := os.Stat(kargsPath); !os.IsNotExist(err) { + kargsData, err := os.ReadFile(kargsPath) + if err != nil { + return fmt.Errorf("failed to read kargs.json: %v", err) + } + + var kargsStruct kargsConfig + if err := json.Unmarshal(kargsData, &kargsStruct); err != nil { + return fmt.Errorf("failed to unmarshal kargs.json: %v", err) + } + config = &kargsStruct + } + + // Apply bootloader config changes (without tracking size changes) + if err := fixGrubConfig(rootFSURL, extractDir, includeNmstateRamDisk, config); err != nil { + return fmt.Errorf("failed to fix grub config: %v", err) + } + + // ignore isolinux.cfg for ppc64le because it doesn't exist + if arch != "ppc64le" { + if err := fixIsolinuxConfig(rootFSURL, extractDir, includeNmstateRamDisk, config); err != nil { + return fmt.Errorf("failed to fix isolinux config: %v", err) + } + } + + if config != nil { + // Update the default kernel arguments and apply embed area size change + if err := updateDefaultKargs(config, rootFSURL); err != nil { + return fmt.Errorf("failed to update default kargs: %v", err) + } + + updatedData, err := json.MarshalIndent(*config, "", " ") + if err != nil { + return fmt.Errorf("failed to marshal updated kargs.json: %v", err) + } + + if err := os.WriteFile(kargsPath, updatedData, 0600); err != nil { + return fmt.Errorf("failed to write updated kargs.json: %v", err) + } } return nil diff --git a/vendor/github.com/sirupsen/logrus/.golangci.yml b/vendor/github.com/sirupsen/logrus/.golangci.yml index 65dc2850377..792db361813 100644 --- a/vendor/github.com/sirupsen/logrus/.golangci.yml +++ b/vendor/github.com/sirupsen/logrus/.golangci.yml @@ -1,40 +1,67 @@ +version: "2" run: - # do not run on test files yet tests: false - -# all available settings of specific linters -linters-settings: - errcheck: - # report about not checking of errors in type assetions: `a := b.(MyStruct)`; - # default is false: such cases aren't reported by default. - check-type-assertions: false - - # report about assignment of errors to blank identifier: `num, _ := strconv.Atoi(numStr)`; - # default is false: such cases aren't reported by default. - check-blank: false - - lll: - line-length: 100 - tab-width: 4 - - prealloc: - simple: false - range-loops: false - for-loops: false - - whitespace: - multi-if: false # Enforces newlines (or comments) after every multi-line if statement - multi-func: false # Enforces newlines (or comments) after every multi-line function signature - linters: enable: - - megacheck - - govet + - asasalint + - asciicheck + - bidichk + - bodyclose + - contextcheck + - durationcheck + - errchkjson + - errorlint + - exhaustive + - gocheckcompilerdirectives + - gochecksumtype + - gosec + - gosmopolitan + - loggercheck + - makezero + - musttag + - nilerr + - nilnesserr + - noctx + - protogetter + - reassign + - recvcheck + - rowserrcheck + - spancheck + - sqlclosecheck + - testifylint + - unparam + - zerologlint disable: - - maligned - prealloc - disable-all: false - presets: - - bugs - - unused - fast: false + settings: + errcheck: + check-type-assertions: false + check-blank: false + lll: + line-length: 100 + tab-width: 4 + prealloc: + simple: false + range-loops: false + for-loops: false + whitespace: + multi-if: false + multi-func: false + exclusions: + generated: lax + presets: + - comments + - common-false-positives + - legacy + - std-error-handling + paths: + - third_party$ + - builtin$ + - examples$ +formatters: + exclusions: + generated: lax + paths: + - third_party$ + - builtin$ + - examples$ diff --git a/vendor/github.com/sirupsen/logrus/CHANGELOG.md b/vendor/github.com/sirupsen/logrus/CHANGELOG.md index 7567f612898..098608ff4b4 100644 --- a/vendor/github.com/sirupsen/logrus/CHANGELOG.md +++ b/vendor/github.com/sirupsen/logrus/CHANGELOG.md @@ -37,7 +37,7 @@ Features: # 1.6.0 Fixes: * end of line cleanup - * revert the entry concurrency bug fix whic leads to deadlock under some circumstances + * revert the entry concurrency bug fix which leads to deadlock under some circumstances * update dependency on go-windows-terminal-sequences to fix a crash with go 1.14 Features: @@ -129,7 +129,7 @@ This new release introduces: which is mostly useful for logger wrapper * a fix reverting the immutability of the entry given as parameter to the hooks a new configuration field of the json formatter in order to put all the fields - in a nested dictionnary + in a nested dictionary * a new SetOutput method in the Logger * a new configuration of the textformatter to configure the name of the default keys * a new configuration of the text formatter to disable the level truncation diff --git a/vendor/github.com/sirupsen/logrus/README.md b/vendor/github.com/sirupsen/logrus/README.md index d1d4a85fd75..cc5dab7eb78 100644 --- a/vendor/github.com/sirupsen/logrus/README.md +++ b/vendor/github.com/sirupsen/logrus/README.md @@ -1,4 +1,4 @@ -# Logrus :walrus: [![Build Status](https://github.com/sirupsen/logrus/workflows/CI/badge.svg)](https://github.com/sirupsen/logrus/actions?query=workflow%3ACI) [![Build Status](https://travis-ci.org/sirupsen/logrus.svg?branch=master)](https://travis-ci.org/sirupsen/logrus) [![Go Reference](https://pkg.go.dev/badge/github.com/sirupsen/logrus.svg)](https://pkg.go.dev/github.com/sirupsen/logrus) +# Logrus :walrus: [![Build Status](https://github.com/sirupsen/logrus/workflows/CI/badge.svg)](https://github.com/sirupsen/logrus/actions?query=workflow%3ACI) [![Go Reference](https://pkg.go.dev/badge/github.com/sirupsen/logrus.svg)](https://pkg.go.dev/github.com/sirupsen/logrus) Logrus is a structured logger for Go (golang), completely API compatible with the standard library logger. @@ -40,7 +40,7 @@ plain text): ![Colored](http://i.imgur.com/PY7qMwd.png) -With `log.SetFormatter(&log.JSONFormatter{})`, for easy parsing by logstash +With `logrus.SetFormatter(&logrus.JSONFormatter{})`, for easy parsing by logstash or Splunk: ```text @@ -60,9 +60,9 @@ ocean","size":10,"time":"2014-03-10 19:57:38.562264131 -0400 EDT"} "time":"2014-03-10 19:57:38.562543128 -0400 EDT"} ``` -With the default `log.SetFormatter(&log.TextFormatter{})` when a TTY is not +With the default `logrus.SetFormatter(&logrus.TextFormatter{})` when a TTY is not attached, the output is compatible with the -[logfmt](http://godoc.org/github.com/kr/logfmt) format: +[logfmt](https://pkg.go.dev/github.com/kr/logfmt) format: ```text time="2015-03-26T01:27:38-04:00" level=debug msg="Started observing beach" animal=walrus number=8 @@ -75,17 +75,18 @@ time="2015-03-26T01:27:38-04:00" level=fatal msg="The ice breaks!" err=&{0x20822 To ensure this behaviour even if a TTY is attached, set your formatter as follows: ```go - log.SetFormatter(&log.TextFormatter{ - DisableColors: true, - FullTimestamp: true, - }) +logrus.SetFormatter(&logrus.TextFormatter{ + DisableColors: true, + FullTimestamp: true, +}) ``` #### Logging Method Name If you wish to add the calling method as a field, instruct the logger via: + ```go -log.SetReportCaller(true) +logrus.SetReportCaller(true) ``` This adds the caller as 'method' like so: @@ -100,11 +101,11 @@ time="2015-03-26T01:27:38-04:00" level=fatal method=github.com/sirupsen/arcticcr Note that this does add measurable overhead - the cost will depend on the version of Go, but is between 20 and 40% in recent tests with 1.6 and 1.7. You can validate this in your environment via benchmarks: -``` + +```bash go test -bench=.*CallerTracing ``` - #### Case-sensitivity The organization's name was changed to lower-case--and this will not be changed @@ -118,12 +119,10 @@ The simplest way to use Logrus is simply the package-level exported logger: ```go package main -import ( - log "github.com/sirupsen/logrus" -) +import "github.com/sirupsen/logrus" func main() { - log.WithFields(log.Fields{ + logrus.WithFields(logrus.Fields{ "animal": "walrus", }).Info("A walrus appears") } @@ -139,6 +138,7 @@ package main import ( "os" + log "github.com/sirupsen/logrus" ) @@ -190,26 +190,27 @@ package main import ( "os" + "github.com/sirupsen/logrus" ) // Create a new instance of the logger. You can have any number of instances. -var log = logrus.New() +var logger = logrus.New() func main() { // The API for setting attributes is a little different than the package level - // exported logger. See Godoc. - log.Out = os.Stdout + // exported logger. See Godoc. + logger.Out = os.Stdout // You could set this to any `io.Writer` such as a file // file, err := os.OpenFile("logrus.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) // if err == nil { - // log.Out = file + // logger.Out = file // } else { - // log.Info("Failed to log to file, using default stderr") + // logger.Info("Failed to log to file, using default stderr") // } - log.WithFields(logrus.Fields{ + logger.WithFields(logrus.Fields{ "animal": "walrus", "size": 10, }).Info("A group of walrus emerges from the ocean") @@ -219,12 +220,12 @@ func main() { #### Fields Logrus encourages careful, structured logging through logging fields instead of -long, unparseable error messages. For example, instead of: `log.Fatalf("Failed +long, unparseable error messages. For example, instead of: `logrus.Fatalf("Failed to send event %s to topic %s with key %d")`, you should log the much more discoverable: ```go -log.WithFields(log.Fields{ +logrus.WithFields(logrus.Fields{ "event": event, "topic": topic, "key": key, @@ -245,12 +246,12 @@ seen as a hint you should add a field, however, you can still use the Often it's helpful to have fields _always_ attached to log statements in an application or parts of one. For example, you may want to always log the `request_id` and `user_ip` in the context of a request. Instead of writing -`log.WithFields(log.Fields{"request_id": request_id, "user_ip": user_ip})` on +`logger.WithFields(logrus.Fields{"request_id": request_id, "user_ip": user_ip})` on every line, you can create a `logrus.Entry` to pass around instead: ```go -requestLogger := log.WithFields(log.Fields{"request_id": request_id, "user_ip": user_ip}) -requestLogger.Info("something happened on that request") # will log request_id and user_ip +requestLogger := logger.WithFields(logrus.Fields{"request_id": request_id, "user_ip": user_ip}) +requestLogger.Info("something happened on that request") // will log request_id and user_ip requestLogger.Warn("something not great happened") ``` @@ -264,28 +265,31 @@ Logrus comes with [built-in hooks](hooks/). Add those, or your custom hook, in `init`: ```go +package main + import ( - log "github.com/sirupsen/logrus" - "gopkg.in/gemnasium/logrus-airbrake-hook.v2" // the package is named "airbrake" - logrus_syslog "github.com/sirupsen/logrus/hooks/syslog" "log/syslog" + + "github.com/sirupsen/logrus" + airbrake "gopkg.in/gemnasium/logrus-airbrake-hook.v2" + logrus_syslog "github.com/sirupsen/logrus/hooks/syslog" ) func init() { // Use the Airbrake hook to report errors that have Error severity or above to // an exception tracker. You can create custom hooks, see the Hooks section. - log.AddHook(airbrake.NewHook(123, "xyz", "production")) + logrus.AddHook(airbrake.NewHook(123, "xyz", "production")) hook, err := logrus_syslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "") if err != nil { - log.Error("Unable to connect to local syslog daemon") + logrus.Error("Unable to connect to local syslog daemon") } else { - log.AddHook(hook) + logrus.AddHook(hook) } } ``` -Note: Syslog hook also support connecting to local syslog (Ex. "/dev/log" or "/var/run/syslog" or "/var/run/log"). For the detail, please check the [syslog hook README](hooks/syslog/README.md). +Note: Syslog hooks also support connecting to local syslog (Ex. "/dev/log" or "/var/run/syslog" or "/var/run/log"). For the detail, please check the [syslog hook README](hooks/syslog/README.md). A list of currently known service hooks can be found in this wiki [page](https://github.com/sirupsen/logrus/wiki/Hooks) @@ -295,15 +299,15 @@ A list of currently known service hooks can be found in this wiki [page](https:/ Logrus has seven logging levels: Trace, Debug, Info, Warning, Error, Fatal and Panic. ```go -log.Trace("Something very low level.") -log.Debug("Useful debugging information.") -log.Info("Something noteworthy happened!") -log.Warn("You should probably take a look at this.") -log.Error("Something failed but I'm not quitting.") +logrus.Trace("Something very low level.") +logrus.Debug("Useful debugging information.") +logrus.Info("Something noteworthy happened!") +logrus.Warn("You should probably take a look at this.") +logrus.Error("Something failed but I'm not quitting.") // Calls os.Exit(1) after logging -log.Fatal("Bye.") +logrus.Fatal("Bye.") // Calls panic() after logging -log.Panic("I'm bailing.") +logrus.Panic("I'm bailing.") ``` You can set the logging level on a `Logger`, then it will only log entries with @@ -311,13 +315,13 @@ that severity or anything above it: ```go // Will log anything that is info or above (warn, error, fatal, panic). Default. -log.SetLevel(log.InfoLevel) +logrus.SetLevel(logrus.InfoLevel) ``` -It may be useful to set `log.Level = logrus.DebugLevel` in a debug or verbose +It may be useful to set `logrus.Level = logrus.DebugLevel` in a debug or verbose environment if your application has that. -Note: If you want different log levels for global (`log.SetLevel(...)`) and syslog logging, please check the [syslog hook README](hooks/syslog/README.md#different-log-levels-for-local-and-remote-logging). +Note: If you want different log levels for global (`logrus.SetLevel(...)`) and syslog logging, please check the [syslog hook README](hooks/syslog/README.md#different-log-levels-for-local-and-remote-logging). #### Entries @@ -340,17 +344,17 @@ could do: ```go import ( - log "github.com/sirupsen/logrus" + "github.com/sirupsen/logrus" ) func init() { // do something here to set environment depending on an environment variable // or command-line flag if Environment == "production" { - log.SetFormatter(&log.JSONFormatter{}) + logrus.SetFormatter(&logrus.JSONFormatter{}) } else { // The TextFormatter is default, you don't actually have to do this. - log.SetFormatter(&log.TextFormatter{}) + logrus.SetFormatter(&logrus.TextFormatter{}) } } ``` @@ -372,11 +376,11 @@ The built-in logging formatters are: * When colors are enabled, levels are truncated to 4 characters by default. To disable truncation set the `DisableLevelTruncation` field to `true`. * When outputting to a TTY, it's often helpful to visually scan down a column where all the levels are the same width. Setting the `PadLevelText` field to `true` enables this behavior, by adding padding to the level text. - * All options are listed in the [generated docs](https://godoc.org/github.com/sirupsen/logrus#TextFormatter). + * All options are listed in the [generated docs](https://pkg.go.dev/github.com/sirupsen/logrus#TextFormatter). * `logrus.JSONFormatter`. Logs fields as JSON. - * All options are listed in the [generated docs](https://godoc.org/github.com/sirupsen/logrus#JSONFormatter). + * All options are listed in the [generated docs](https://pkg.go.dev/github.com/sirupsen/logrus#JSONFormatter). -Third party logging formatters: +Third-party logging formatters: * [`FluentdFormatter`](https://github.com/joonix/log). Formats entries that can be parsed by Kubernetes and Google Container Engine. * [`GELF`](https://github.com/fabienm/go-logrus-formatters). Formats entries so they comply to Graylog's [GELF 1.1 specification](http://docs.graylog.org/en/2.4/pages/gelf.html). @@ -384,7 +388,7 @@ Third party logging formatters: * [`prefixed`](https://github.com/x-cray/logrus-prefixed-formatter). Displays log entry source along with alternative layout. * [`zalgo`](https://github.com/aybabtme/logzalgo). Invoking the Power of Zalgo. * [`nested-logrus-formatter`](https://github.com/antonfisher/nested-logrus-formatter). Converts logrus fields to a nested structure. -* [`powerful-logrus-formatter`](https://github.com/zput/zxcTool). get fileName, log's line number and the latest function's name when print log; Sava log to files. +* [`powerful-logrus-formatter`](https://github.com/zput/zxcTool). get fileName, log's line number and the latest function's name when print log; Save log to files. * [`caption-json-formatter`](https://github.com/nolleh/caption_json_formatter). logrus's message json formatter with human-readable caption added. You can define your formatter by implementing the `Formatter` interface, @@ -393,10 +397,9 @@ requiring a `Format` method. `Format` takes an `*Entry`. `entry.Data` is a default ones (see Entries section above): ```go -type MyJSONFormatter struct { -} +type MyJSONFormatter struct{} -log.SetFormatter(new(MyJSONFormatter)) +logrus.SetFormatter(new(MyJSONFormatter)) func (f *MyJSONFormatter) Format(entry *Entry) ([]byte, error) { // Note this doesn't include Time, Level and Message which are available on @@ -455,17 +458,18 @@ entries. It should not be a feature of the application-level logger. #### Testing -Logrus has a built in facility for asserting the presence of log messages. This is implemented through the `test` hook and provides: +Logrus has a built-in facility for asserting the presence of log messages. This is implemented through the `test` hook and provides: * decorators for existing logger (`test.NewLocal` and `test.NewGlobal`) which basically just adds the `test` hook * a test logger (`test.NewNullLogger`) that just records log messages (and does not output any): ```go import( + "testing" + "github.com/sirupsen/logrus" "github.com/sirupsen/logrus/hooks/test" "github.com/stretchr/testify/assert" - "testing" ) func TestSomething(t*testing.T){ @@ -486,15 +490,15 @@ func TestSomething(t*testing.T){ Logrus can register one or more functions that will be called when any `fatal` level message is logged. The registered handlers will be executed before logrus performs an `os.Exit(1)`. This behavior may be helpful if callers need -to gracefully shutdown. Unlike a `panic("Something went wrong...")` call which can be intercepted with a deferred `recover` a call to `os.Exit(1)` can not be intercepted. +to gracefully shut down. Unlike a `panic("Something went wrong...")` call which can be intercepted with a deferred `recover` a call to `os.Exit(1)` can not be intercepted. -``` -... +```go +// ... handler := func() { - // gracefully shutdown something... + // gracefully shut down something... } logrus.RegisterExitHandler(handler) -... +// ... ``` #### Thread safety @@ -502,7 +506,7 @@ logrus.RegisterExitHandler(handler) By default, Logger is protected by a mutex for concurrent writes. The mutex is held when calling hooks and writing logs. If you are sure such locking is not needed, you can call logger.SetNoLock() to disable the locking. -Situation when locking is not needed includes: +Situations when locking is not needed include: * You have no hooks registered, or hooks calling is already thread-safe. diff --git a/vendor/github.com/sirupsen/logrus/appveyor.yml b/vendor/github.com/sirupsen/logrus/appveyor.yml index df9d65c3a5b..e90f09ea68c 100644 --- a/vendor/github.com/sirupsen/logrus/appveyor.yml +++ b/vendor/github.com/sirupsen/logrus/appveyor.yml @@ -1,14 +1,12 @@ -version: "{build}" +# Minimal stub to satisfy AppVeyor CI +version: 1.0.{build} platform: x64 -clone_folder: c:\gopath\src\github.com\sirupsen\logrus -environment: - GOPATH: c:\gopath +shallow_clone: true + branches: only: - master -install: - - set PATH=%GOPATH%\bin;c:\go\bin;%PATH% - - go version + - main + build_script: - - go get -t - - go test + - echo "No-op build to satisfy AppVeyor CI" diff --git a/vendor/github.com/sirupsen/logrus/entry.go b/vendor/github.com/sirupsen/logrus/entry.go index 71cdbbc35d2..71d796d0b13 100644 --- a/vendor/github.com/sirupsen/logrus/entry.go +++ b/vendor/github.com/sirupsen/logrus/entry.go @@ -34,13 +34,15 @@ func init() { minimumCallerDepth = 1 } -// Defines the key when adding errors using WithError. +// ErrorKey defines the key when adding errors using [WithError], [Logger.WithError]. var ErrorKey = "error" -// An entry is the final or intermediate Logrus logging entry. It contains all +// Entry is the final or intermediate Logrus logging entry. It contains all // the fields passed with WithField{,s}. It's finally logged when Trace, Debug, // Info, Warn, Error, Fatal or Panic is called on it. These objects can be // reused and passed around as much as you wish to avoid field duplication. +// +//nolint:recvcheck // the methods of "Entry" use pointer receiver and non-pointer receiver. type Entry struct { Logger *Logger @@ -86,12 +88,12 @@ func (entry *Entry) Dup() *Entry { return &Entry{Logger: entry.Logger, Data: data, Time: entry.Time, Context: entry.Context, err: entry.err} } -// Returns the bytes representation of this entry from the formatter. +// Bytes returns the bytes representation of this entry from the formatter. func (entry *Entry) Bytes() ([]byte, error) { return entry.Logger.Formatter.Format(entry) } -// Returns the string representation from the reader and ultimately the +// String returns the string representation from the reader and ultimately the // formatter. func (entry *Entry) String() (string, error) { serialized, err := entry.Bytes() @@ -102,12 +104,13 @@ func (entry *Entry) String() (string, error) { return str, nil } -// Add an error as single field (using the key defined in ErrorKey) to the Entry. +// WithError adds an error as single field (using the key defined in [ErrorKey]) +// to the Entry. func (entry *Entry) WithError(err error) *Entry { return entry.WithField(ErrorKey, err) } -// Add a context to the Entry. +// WithContext adds a context to the Entry. func (entry *Entry) WithContext(ctx context.Context) *Entry { dataCopy := make(Fields, len(entry.Data)) for k, v := range entry.Data { @@ -116,12 +119,12 @@ func (entry *Entry) WithContext(ctx context.Context) *Entry { return &Entry{Logger: entry.Logger, Data: dataCopy, Time: entry.Time, err: entry.err, Context: ctx} } -// Add a single field to the Entry. +// WithField adds a single field to the Entry. func (entry *Entry) WithField(key string, value interface{}) *Entry { return entry.WithFields(Fields{key: value}) } -// Add a map of fields to the Entry. +// WithFields adds a map of fields to the Entry. func (entry *Entry) WithFields(fields Fields) *Entry { data := make(Fields, len(entry.Data)+len(fields)) for k, v := range entry.Data { @@ -150,7 +153,7 @@ func (entry *Entry) WithFields(fields Fields) *Entry { return &Entry{Logger: entry.Logger, Data: data, Time: entry.Time, err: fieldErr, Context: entry.Context} } -// Overrides the time of the Entry. +// WithTime overrides the time of the Entry. func (entry *Entry) WithTime(t time.Time) *Entry { dataCopy := make(Fields, len(entry.Data)) for k, v := range entry.Data { @@ -204,7 +207,7 @@ func getCaller() *runtime.Frame { // If the caller isn't part of this package, we're done if pkg != logrusPackage { - return &f //nolint:scopelint + return &f } } @@ -432,7 +435,7 @@ func (entry *Entry) Panicln(args ...interface{}) { entry.Logln(PanicLevel, args...) } -// Sprintlnn => Sprint no newline. This is to get the behavior of how +// sprintlnn => Sprint no newline. This is to get the behavior of how // fmt.Sprintln where spaces are always added between operands, regardless of // their type. Instead of vendoring the Sprintln implementation to spare a // string allocation, we do the simplest thing. diff --git a/vendor/github.com/sirupsen/logrus/hooks.go b/vendor/github.com/sirupsen/logrus/hooks.go index 3f151cdc392..9ab978a4578 100644 --- a/vendor/github.com/sirupsen/logrus/hooks.go +++ b/vendor/github.com/sirupsen/logrus/hooks.go @@ -1,16 +1,16 @@ package logrus -// A hook to be fired when logging on the logging levels returned from -// `Levels()` on your implementation of the interface. Note that this is not +// Hook describes hooks to be fired when logging on the logging levels returned from +// [Hook.Levels] on your implementation of the interface. Note that this is not // fired in a goroutine or a channel with workers, you should handle such -// functionality yourself if your call is non-blocking and you don't wish for +// functionality yourself if your call is non-blocking, and you don't wish for // the logging calls for levels returned from `Levels()` to block. type Hook interface { Levels() []Level Fire(*Entry) error } -// Internal type for storing the hooks on a logger instance. +// LevelHooks is an internal type for storing the hooks on a logger instance. type LevelHooks map[Level][]Hook // Add a hook to an instance of logger. This is called with diff --git a/vendor/github.com/sirupsen/logrus/hooks/test/test.go b/vendor/github.com/sirupsen/logrus/hooks/test/test.go index 046f0bf6cf2..4b4d1f1a558 100644 --- a/vendor/github.com/sirupsen/logrus/hooks/test/test.go +++ b/vendor/github.com/sirupsen/logrus/hooks/test/test.go @@ -3,7 +3,7 @@ package test import ( - "io/ioutil" + "io" "sync" "github.com/sirupsen/logrus" @@ -42,7 +42,7 @@ func NewLocal(logger *logrus.Logger) *Hook { func NewNullLogger() (*logrus.Logger, *Hook) { logger := logrus.New() - logger.Out = ioutil.Discard + logger.Out = io.Discard return logger, NewLocal(logger) diff --git a/vendor/github.com/sirupsen/logrus/logger.go b/vendor/github.com/sirupsen/logrus/logger.go index 5ff0aef6d3f..f5b8c439ee8 100644 --- a/vendor/github.com/sirupsen/logrus/logger.go +++ b/vendor/github.com/sirupsen/logrus/logger.go @@ -72,16 +72,16 @@ func (mw *MutexWrap) Disable() { mw.disabled = true } -// Creates a new logger. Configuration should be set by changing `Formatter`, -// `Out` and `Hooks` directly on the default logger instance. You can also just +// New Creates a new logger. Configuration should be set by changing [Formatter], +// Out and Hooks directly on the default Logger instance. You can also just // instantiate your own: // -// var log = &logrus.Logger{ -// Out: os.Stderr, -// Formatter: new(logrus.TextFormatter), -// Hooks: make(logrus.LevelHooks), -// Level: logrus.DebugLevel, -// } +// var log = &logrus.Logger{ +// Out: os.Stderr, +// Formatter: new(logrus.TextFormatter), +// Hooks: make(logrus.LevelHooks), +// Level: logrus.DebugLevel, +// } // // It's recommended to make this a global instance called `log`. func New() *Logger { @@ -118,30 +118,30 @@ func (logger *Logger) WithField(key string, value interface{}) *Entry { return entry.WithField(key, value) } -// Adds a struct of fields to the log entry. All it does is call `WithField` for -// each `Field`. +// WithFields adds a struct of fields to the log entry. It calls [Entry.WithField] +// for each Field. func (logger *Logger) WithFields(fields Fields) *Entry { entry := logger.newEntry() defer logger.releaseEntry(entry) return entry.WithFields(fields) } -// Add an error as single field to the log entry. All it does is call -// `WithError` for the given `error`. +// WithError adds an error as single field to the log entry. It calls +// [Entry.WithError] for the given error. func (logger *Logger) WithError(err error) *Entry { entry := logger.newEntry() defer logger.releaseEntry(entry) return entry.WithError(err) } -// Add a context to the log entry. +// WithContext add a context to the log entry. func (logger *Logger) WithContext(ctx context.Context) *Entry { entry := logger.newEntry() defer logger.releaseEntry(entry) return entry.WithContext(ctx) } -// Overrides the time of the log entry. +// WithTime overrides the time of the log entry. func (logger *Logger) WithTime(t time.Time) *Entry { entry := logger.newEntry() defer logger.releaseEntry(entry) @@ -347,9 +347,9 @@ func (logger *Logger) Exit(code int) { logger.ExitFunc(code) } -//When file is opened with appending mode, it's safe to -//write concurrently to a file (within 4k message on Linux). -//In these cases user can choose to disable the lock. +// SetNoLock disables the lock for situations where a file is opened with +// appending mode, and safe for concurrent writes to the file (within 4k +// message on Linux). In these cases user can choose to disable the lock. func (logger *Logger) SetNoLock() { logger.mu.Disable() } diff --git a/vendor/github.com/sirupsen/logrus/logrus.go b/vendor/github.com/sirupsen/logrus/logrus.go index 2f16224cb9f..37fc4fef85a 100644 --- a/vendor/github.com/sirupsen/logrus/logrus.go +++ b/vendor/github.com/sirupsen/logrus/logrus.go @@ -6,13 +6,15 @@ import ( "strings" ) -// Fields type, used to pass to `WithFields`. +// Fields type, used to pass to [WithFields]. type Fields map[string]interface{} // Level type +// +//nolint:recvcheck // the methods of "Entry" use pointer receiver and non-pointer receiver. type Level uint32 -// Convert the Level to a string. E.g. PanicLevel becomes "panic". +// Convert the Level to a string. E.g. [PanicLevel] becomes "panic". func (level Level) String() string { if b, err := level.MarshalText(); err == nil { return string(b) @@ -77,7 +79,7 @@ func (level Level) MarshalText() ([]byte, error) { return nil, fmt.Errorf("not a valid logrus level %d", level) } -// A constant exposing all logging levels +// AllLevels exposing all logging levels. var AllLevels = []Level{ PanicLevel, FatalLevel, @@ -119,8 +121,8 @@ var ( ) // StdLogger is what your logrus-enabled library should take, that way -// it'll accept a stdlib logger and a logrus logger. There's no standard -// interface, this is the closest we get, unfortunately. +// it'll accept a stdlib logger ([log.Logger]) and a logrus logger. +// There's no standard interface, so this is the closest we get, unfortunately. type StdLogger interface { Print(...interface{}) Printf(string, ...interface{}) @@ -135,7 +137,8 @@ type StdLogger interface { Panicln(...interface{}) } -// The FieldLogger interface generalizes the Entry and Logger types +// FieldLogger extends the [StdLogger] interface, generalizing +// the [Entry] and [Logger] types. type FieldLogger interface { WithField(key string, value interface{}) *Entry WithFields(fields Fields) *Entry @@ -176,8 +179,9 @@ type FieldLogger interface { // IsPanicEnabled() bool } -// Ext1FieldLogger (the first extension to FieldLogger) is superfluous, it is -// here for consistancy. Do not use. Use Logger or Entry instead. +// Ext1FieldLogger (the first extension to [FieldLogger]) is superfluous, it is +// here for consistency. Do not use. Use [FieldLogger], [Logger] or [Entry] +// instead. type Ext1FieldLogger interface { FieldLogger Tracef(format string, args ...interface{}) diff --git a/vendor/github.com/sirupsen/logrus/text_formatter.go b/vendor/github.com/sirupsen/logrus/text_formatter.go index be2c6efe5ed..6dfeb18b10e 100644 --- a/vendor/github.com/sirupsen/logrus/text_formatter.go +++ b/vendor/github.com/sirupsen/logrus/text_formatter.go @@ -306,6 +306,7 @@ func (f *TextFormatter) needsQuoting(text string) bool { return false } for _, ch := range text { + //nolint:staticcheck // QF1001: could apply De Morgan's law if !((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') || @@ -334,6 +335,6 @@ func (f *TextFormatter) appendValue(b *bytes.Buffer, value interface{}) { if !f.needsQuoting(stringVal) { b.WriteString(stringVal) } else { - b.WriteString(fmt.Sprintf("%q", stringVal)) + fmt.Fprintf(b, "%q", stringVal) } } diff --git a/vendor/modules.txt b/vendor/modules.txt index 0158c9755b7..7fb9476abb5 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -394,6 +394,9 @@ github.com/PaesslerAG/gval # github.com/PaesslerAG/jsonpath v0.1.1 ## explicit github.com/PaesslerAG/jsonpath +# github.com/anchore/go-lzo v0.1.0 +## explicit; go 1.24.0 +github.com/anchore/go-lzo # github.com/apparentlymart/go-cidr v1.1.0 ## explicit github.com/apparentlymart/go-cidr/cidr @@ -706,9 +709,11 @@ github.com/digitalocean/go-libvirt/internal/event github.com/digitalocean/go-libvirt/internal/go-xdr/xdr2 github.com/digitalocean/go-libvirt/socket github.com/digitalocean/go-libvirt/socket/dialers -# github.com/diskfs/go-diskfs v1.4.1 -## explicit; go 1.21 +# github.com/diskfs/go-diskfs v1.7.1-0.20251217162235-58541aa8f559 +## explicit; go 1.24.0 github.com/diskfs/go-diskfs +github.com/diskfs/go-diskfs/backend +github.com/diskfs/go-diskfs/backend/file github.com/diskfs/go-diskfs/disk github.com/diskfs/go-diskfs/filesystem github.com/diskfs/go-diskfs/filesystem/ext4 @@ -721,7 +726,9 @@ github.com/diskfs/go-diskfs/partition github.com/diskfs/go-diskfs/partition/gpt github.com/diskfs/go-diskfs/partition/mbr github.com/diskfs/go-diskfs/partition/part -github.com/diskfs/go-diskfs/util +github.com/diskfs/go-diskfs/util/bitmap +github.com/diskfs/go-diskfs/util/slices +github.com/diskfs/go-diskfs/version # github.com/djherbis/times v1.6.0 ## explicit; go 1.16 github.com/djherbis/times @@ -895,7 +902,6 @@ github.com/golang-jwt/jwt/v4 github.com/golang-jwt/jwt/v5 # github.com/golang/mock v1.7.0-rc.1 ## explicit; go 1.15 -github.com/golang/mock/gomock # github.com/golang/protobuf v1.5.4 ## explicit; go 1.17 github.com/golang/protobuf/ptypes/timestamp @@ -1036,7 +1042,7 @@ github.com/hashicorp/go-multierror # github.com/hashicorp/go-retryablehttp v0.7.8 ## explicit; go 1.23 github.com/hashicorp/go-retryablehttp -# github.com/hashicorp/go-version v1.7.0 +# github.com/hashicorp/go-version v1.8.0 ## explicit github.com/hashicorp/go-version # github.com/hexops/gotextdiff v1.0.3 @@ -1278,8 +1284,8 @@ github.com/nutanix-cloud-native/prism-go-client/v3/models # github.com/oklog/ulid v1.3.1 ## explicit github.com/oklog/ulid -# github.com/onsi/gomega v1.38.2 -## explicit; go 1.23.0 +# github.com/onsi/gomega v1.39.1 +## explicit; go 1.24.0 github.com/onsi/gomega github.com/onsi/gomega/format github.com/onsi/gomega/internal @@ -1319,8 +1325,8 @@ github.com/openshift/api/machineconfiguration/v1alpha1 github.com/openshift/api/operator/v1 github.com/openshift/api/operator/v1alpha1 github.com/openshift/api/route/v1 -# github.com/openshift/assisted-image-service v0.0.0-20250917153356-4ca9ff81f712 -## explicit; go 1.21 +# github.com/openshift/assisted-image-service v0.0.0-20260428115106-2b81dd8e7120 +## explicit; go 1.25.0 github.com/openshift/assisted-image-service/internal/common github.com/openshift/assisted-image-service/pkg/isoeditor github.com/openshift/assisted-image-service/pkg/overlay @@ -1562,8 +1568,8 @@ github.com/shurcooL/httpfs/vfsutil # github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd ## explicit github.com/shurcooL/vfsgen -# github.com/sirupsen/logrus v1.9.4-0.20230606125235-dd1b4c2e81af -## explicit; go 1.13 +# github.com/sirupsen/logrus v1.9.4 +## explicit; go 1.17 github.com/sirupsen/logrus github.com/sirupsen/logrus/hooks/test # github.com/spf13/cobra v1.10.1