From 1591cea790adebaf3d2166b13fbed5fb7aafced0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20Trma=C4=8D?= Date: Thu, 17 Oct 2024 23:40:44 +0200 Subject: [PATCH 01/14] DO NOT MERGE: Nerf make-and-check-size MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Miloslav Trmač --- hack/make-and-check-size | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/hack/make-and-check-size b/hack/make-and-check-size index 5b0021d126..c61fe79ac6 100755 --- a/hack/make-and-check-size +++ b/hack/make-and-check-size @@ -121,11 +121,7 @@ for bin in bin/*;do echo "*" echo "$separator" else - echo "* Please investigate, and fix if possible." - echo "*" - echo "* A repo admin can override by setting the $OVERRIDE_LABEL label" - echo "$separator" - exit 1 + echo "HACK HACK HACK continuing anyway" fi fi else From edb6d39619e355681a97f53455da9f4dfbcc061f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20Trma=C4=8D?= Date: Wed, 16 Oct 2024 00:21:42 +0200 Subject: [PATCH 02/14] DO NOT MERGE: Test with a zstd:chunked testimage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Miloslav Trmač --- test/apiv2/test-apiv2 | 2 +- test/e2e/config_amd64.go | 2 +- test/e2e/config_arm64.go | 2 +- test/e2e/config_ppc64le.go | 2 +- test/system/helpers.bash | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/test/apiv2/test-apiv2 b/test/apiv2/test-apiv2 index c8df99639c..cb36911909 100755 --- a/test/apiv2/test-apiv2 +++ b/test/apiv2/test-apiv2 @@ -12,7 +12,7 @@ ME=$(basename $0) PODMAN_TEST_IMAGE_REGISTRY=${PODMAN_TEST_IMAGE_REGISTRY:-"quay.io"} PODMAN_TEST_IMAGE_USER=${PODMAN_TEST_IMAGE_USER:-"libpod"} PODMAN_TEST_IMAGE_NAME=${PODMAN_TEST_IMAGE_NAME:-"testimage"} -PODMAN_TEST_IMAGE_TAG=${PODMAN_TEST_IMAGE_TAG:-"20241011"} +PODMAN_TEST_IMAGE_TAG=${PODMAN_TEST_IMAGE_TAG:-"20241010"} PODMAN_TEST_IMAGE_FQN="$PODMAN_TEST_IMAGE_REGISTRY/$PODMAN_TEST_IMAGE_USER/$PODMAN_TEST_IMAGE_NAME:$PODMAN_TEST_IMAGE_TAG" IMAGE=$PODMAN_TEST_IMAGE_FQN diff --git a/test/e2e/config_amd64.go b/test/e2e/config_amd64.go index 49da96b3d0..bd946e6689 100644 --- a/test/e2e/config_amd64.go +++ b/test/e2e/config_amd64.go @@ -10,7 +10,7 @@ var ( NGINX_IMAGE = "quay.io/libpod/alpine_nginx:latest" //nolint:revive,stylecheck BB_GLIBC = "docker.io/library/busybox:glibc" //nolint:revive,stylecheck REGISTRY_IMAGE = "quay.io/libpod/registry:2.8.2" //nolint:revive,stylecheck - CITEST_IMAGE = "quay.io/libpod/testimage:20241011" //nolint:revive,stylecheck + CITEST_IMAGE = "quay.io/libpod/testimage:20241010" //nolint:revive,stylecheck SYSTEMD_IMAGE = "quay.io/libpod/systemd-image:20240124" //nolint:revive,stylecheck CIRROS_IMAGE = "quay.io/libpod/cirros:latest" //nolint:revive,stylecheck ) diff --git a/test/e2e/config_arm64.go b/test/e2e/config_arm64.go index fcfb496b0f..d37f9cef65 100644 --- a/test/e2e/config_arm64.go +++ b/test/e2e/config_arm64.go @@ -10,7 +10,7 @@ var ( NGINX_IMAGE = "quay.io/lsm5/alpine_nginx-aarch64:latest" //nolint:revive,stylecheck BB_GLIBC = "docker.io/library/busybox:glibc" //nolint:revive,stylecheck REGISTRY_IMAGE = "quay.io/libpod/registry:2.8.2" //nolint:revive,stylecheck - CITEST_IMAGE = "quay.io/libpod/testimage:20241011" //nolint:revive,stylecheck + CITEST_IMAGE = "quay.io/libpod/testimage:20241010" //nolint:revive,stylecheck SYSTEMD_IMAGE = "quay.io/libpod/systemd-image:20240124" //nolint:revive,stylecheck CIRROS_IMAGE = "quay.io/libpod/cirros:latest" //nolint:revive,stylecheck ) diff --git a/test/e2e/config_ppc64le.go b/test/e2e/config_ppc64le.go index 0ed3e5de69..a9bdabea22 100644 --- a/test/e2e/config_ppc64le.go +++ b/test/e2e/config_ppc64le.go @@ -9,6 +9,6 @@ var ( CACHE_IMAGES = []string{ALPINE, BB, fedoraMinimal, NGINX_IMAGE, REDIS_IMAGE, INFRA_IMAGE, CITEST_IMAGE} NGINX_IMAGE = "quay.io/libpod/alpine_nginx-ppc64le:latest" BB_GLIBC = "docker.io/ppc64le/busybox:glibc" - CITEST_IMAGE = "quay.io/libpod/testimage:20241011" + CITEST_IMAGE = "quay.io/libpod/testimage:20241010" REGISTRY_IMAGE string ) diff --git a/test/system/helpers.bash b/test/system/helpers.bash index 65ae66b9d3..f4d57eda70 100644 --- a/test/system/helpers.bash +++ b/test/system/helpers.bash @@ -14,7 +14,7 @@ PODMAN_RUNTIME= PODMAN_TEST_IMAGE_REGISTRY=${PODMAN_TEST_IMAGE_REGISTRY:-"quay.io"} PODMAN_TEST_IMAGE_USER=${PODMAN_TEST_IMAGE_USER:-"libpod"} PODMAN_TEST_IMAGE_NAME=${PODMAN_TEST_IMAGE_NAME:-"testimage"} -PODMAN_TEST_IMAGE_TAG=${PODMAN_TEST_IMAGE_TAG:-"20241011"} +PODMAN_TEST_IMAGE_TAG=${PODMAN_TEST_IMAGE_TAG:-"20241010"} PODMAN_TEST_IMAGE_FQN="$PODMAN_TEST_IMAGE_REGISTRY/$PODMAN_TEST_IMAGE_USER/$PODMAN_TEST_IMAGE_NAME:$PODMAN_TEST_IMAGE_TAG" # Larger image containing systemd tools. From e649851dd7a9bde2d184597deccff7ee3d39b7b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20Trma=C4=8D?= Date: Thu, 17 Oct 2024 23:42:35 +0200 Subject: [PATCH 03/14] DO NOT MERGE: Test with a zstd:chunked container MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ... from https://github.com/containers/automation_images/pull/395 . Signed-off-by: Miloslav Trmač --- .cirrus.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.cirrus.yml b/.cirrus.yml index 04aeee601e..07b5162946 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -33,7 +33,7 @@ env: DEBIAN_NAME: "debian-13" # Image identifiers - IMAGE_SUFFIX: "c20241212t122344z-f41f40d13" + IMAGE_SUFFIX: "c20241122t210103z-f41f40d13" # EC2 images FEDORA_AMI: "fedora-aws-${IMAGE_SUFFIX}" From 352928b5b8b0737b3858831d18c268340469768d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20Trma=C4=8D?= Date: Thu, 28 Nov 2024 21:39:52 +0100 Subject: [PATCH 04/14] Test with current c/storage and c/image MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Miloslav Trmač --- go.mod | 18 +- go.sum | 36 +-- .../stargz-snapshotter/estargz/testutil.go | 15 +- .../containerd/typeurl/v2/README.md | 6 + .../github.com/containerd/typeurl/v2/types.go | 89 +++--- .../containerd/typeurl/v2/types_gogo.go | 68 +++++ .../containers/image/v5/copy/single.go | 24 +- .../image/v5/directory/directory_dest.go | 1 + .../image/v5/docker/daemon/client.go | 11 + .../image/v5/docker/distribution_error.go | 8 +- .../image/v5/docker/docker_image_dest.go | 1 + .../image/v5/docker/docker_image_src.go | 4 + .../image/v5/docker/internal/tarfile/dest.go | 1 + .../stubs/original_oci_config.go | 16 + .../v5/internal/imagedestination/wrapper.go | 1 + .../internal/manifest/docker_schema2_list.go | 32 +- .../image/v5/internal/private/private.go | 7 + .../image/v5/oci/archive/oci_dest.go | 9 + .../image/v5/oci/internal/oci_util.go | 2 +- .../image/v5/oci/layout/oci_dest.go | 1 + .../image/v5/openshift/openshift_dest.go | 9 + .../containers/image/v5/pkg/blobcache/dest.go | 9 + .../image/v5/signature/fulcio_cert_stub.go | 2 +- .../v5/signature/internal/rekor_set_stub.go | 2 +- .../image/v5/storage/storage_dest.go | 281 ++++++++++++------ .../image/v5/storage/storage_reference.go | 4 +- .../image/v5/storage/storage_src.go | 22 +- .../containers/image/v5/version/version.go | 4 +- .../github.com/containers/storage/.cirrus.yml | 4 +- vendor/github.com/containers/storage/Makefile | 2 +- vendor/github.com/containers/storage/VERSION | 2 +- vendor/github.com/containers/storage/check.go | 8 +- .../containers/storage/drivers/aufs/aufs.go | 5 + .../containers/storage/drivers/btrfs/btrfs.go | 5 + .../storage/drivers/chown_darwin.go | 2 +- .../containers/storage/drivers/chown_unix.go | 2 +- .../storage/drivers/copy/copy_linux.go | 4 +- .../containers/storage/drivers/driver.go | 20 ++ .../storage/drivers/overlay/check_116.go | 3 +- .../{overlay_nocgo.go => composefs_nocgo.go} | 0 .../storage/drivers/overlay/overlay.go | 21 ++ .../drivers/register/register_overlay.go | 2 +- .../containers/storage/drivers/vfs/driver.go | 17 ++ .../storage/drivers/windows/windows.go | 5 + .../containers/storage/drivers/zfs/zfs.go | 5 + .../storage/internal/dedup/dedup.go | 163 ++++++++++ .../storage/internal/dedup/dedup_linux.go | 139 +++++++++ .../internal/dedup/dedup_unsupported.go | 27 ++ .../github.com/containers/storage/layers.go | 37 ++- .../containers/storage/pkg/archive/archive.go | 54 ++-- .../storage/pkg/archive/changes_linux.go | 4 +- .../storage/pkg/chunked/cache_linux.go | 10 +- .../storage/pkg/chunked/compression.go | 18 +- .../storage/pkg/chunked/compression_linux.go | 167 ++++++----- .../pkg/chunked/compressor/compressor.go | 24 +- .../storage/pkg/chunked/dump/dump.go | 51 ++-- .../storage/pkg/chunked/filesystem_linux.go | 73 ++++- .../internal/{ => minimal}/compression.go | 2 +- .../storage/pkg/chunked/internal/path/path.go | 12 + .../storage/pkg/chunked/storage_linux.go | 224 ++++++++++---- .../containers/storage/pkg/chunked/toc/toc.go | 4 +- .../containers/storage/pkg/idtools/idtools.go | 169 +++++++++-- .../storage/pkg/system/extattr_freebsd.go | 93 ++++++ .../storage/pkg/system/extattr_unsupported.go | 24 ++ .../storage/pkg/system/stat_netbsd.go | 13 + .../storage/pkg/system/xattrs_darwin.go | 2 +- .../storage/pkg/system/xattrs_freebsd.go | 85 ++++++ .../storage/pkg/system/xattrs_linux.go | 2 +- .../storage/pkg/system/xattrs_unsupported.go | 4 +- vendor/github.com/containers/storage/store.go | 64 ++++ .../registry/client/auth/challenge/addr.go | 27 -- .../client/auth/challenge/authchallenge.go | 237 --------------- vendor/github.com/proglottis/gpgme/.gitignore | 2 + .../github.com/proglottis/gpgme/callbacks.go | 42 --- vendor/github.com/proglottis/gpgme/data.go | 119 +++++--- vendor/github.com/proglottis/gpgme/go_gpgme.c | 20 +- vendor/github.com/proglottis/gpgme/go_gpgme.h | 4 +- vendor/github.com/proglottis/gpgme/gpgme.go | 78 +++-- .../proglottis/gpgme/unset_agent_info.go | 1 + .../sigstore/pkg/oauthflow/interactive.go | 2 +- .../sylabs/sif/v2/pkg/sif/create.go | 12 +- vendor/golang.org/x/oauth2/README.md | 15 +- vendor/modules.txt | 31 +- 83 files changed, 1927 insertions(+), 918 deletions(-) create mode 100644 vendor/github.com/containerd/typeurl/v2/types_gogo.go create mode 100644 vendor/github.com/containers/image/v5/internal/imagedestination/stubs/original_oci_config.go rename vendor/github.com/containers/storage/drivers/overlay/{overlay_nocgo.go => composefs_nocgo.go} (100%) create mode 100644 vendor/github.com/containers/storage/internal/dedup/dedup.go create mode 100644 vendor/github.com/containers/storage/internal/dedup/dedup_linux.go create mode 100644 vendor/github.com/containers/storage/internal/dedup/dedup_unsupported.go rename vendor/github.com/containers/storage/pkg/chunked/internal/{ => minimal}/compression.go (99%) create mode 100644 vendor/github.com/containers/storage/pkg/chunked/internal/path/path.go create mode 100644 vendor/github.com/containers/storage/pkg/system/extattr_freebsd.go create mode 100644 vendor/github.com/containers/storage/pkg/system/extattr_unsupported.go create mode 100644 vendor/github.com/containers/storage/pkg/system/stat_netbsd.go create mode 100644 vendor/github.com/containers/storage/pkg/system/xattrs_freebsd.go delete mode 100644 vendor/github.com/docker/distribution/registry/client/auth/challenge/addr.go delete mode 100644 vendor/github.com/docker/distribution/registry/client/auth/challenge/authchallenge.go delete mode 100644 vendor/github.com/proglottis/gpgme/callbacks.go diff --git a/go.mod b/go.mod index 3cf52fb031..4956998bb4 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/containers/podman/v5 // Warning: if there is a "toolchain" directive anywhere in this file (and most of the // time there shouldn't be), its version must be an exact match to the "go" directive. -go 1.22.6 +go 1.22.8 require ( github.com/BurntSushi/toml v1.4.0 @@ -17,11 +17,11 @@ require ( github.com/containers/common v0.61.1-0.20241202111335-2d4a9a65dd81 github.com/containers/conmon v2.0.20+incompatible github.com/containers/gvisor-tap-vsock v0.8.1 - github.com/containers/image/v5 v5.33.0 + github.com/containers/image/v5 v5.33.1-0.20241214000558-01058107e817 github.com/containers/libhvee v0.9.0 github.com/containers/ocicrypt v1.2.0 github.com/containers/psgo v1.9.0 - github.com/containers/storage v1.56.0 + github.com/containers/storage v1.56.1-0.20241213190044-18afcfdf2c3d github.com/containers/winquit v1.1.0 github.com/coreos/go-systemd/v22 v22.5.1-0.20231103132048-7d375ecc2b09 github.com/crc-org/crc/v2 v2.45.0 @@ -103,8 +103,8 @@ require ( github.com/containerd/errdefs/pkg v0.3.0 // indirect github.com/containerd/log v0.1.0 // indirect github.com/containerd/platforms v0.2.1 // indirect - github.com/containerd/stargz-snapshotter/estargz v0.15.1 // indirect - github.com/containerd/typeurl/v2 v2.2.0 // indirect + github.com/containerd/stargz-snapshotter/estargz v0.16.3 // indirect + github.com/containerd/typeurl/v2 v2.2.3 // indirect github.com/containernetworking/cni v1.2.3 // indirect github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01 // indirect github.com/containers/luksy v0.0.0-20241007190014-e2530d691420 // indirect @@ -189,18 +189,18 @@ require ( github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect - github.com/proglottis/gpgme v0.1.3 // indirect + github.com/proglottis/gpgme v0.1.4 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/seccomp/libseccomp-golang v0.10.0 // indirect github.com/secure-systems-lab/go-securesystemslib v0.8.0 // indirect github.com/segmentio/ksuid v1.0.4 // indirect github.com/sigstore/fulcio v1.6.4 // indirect github.com/sigstore/rekor v1.3.6 // indirect - github.com/sigstore/sigstore v1.8.9 // indirect + github.com/sigstore/sigstore v1.8.11 // indirect github.com/skeema/knownhosts v1.3.0 // indirect github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect github.com/stefanberger/go-pkcs11uri v0.0.0-20230803200340-78284954bff6 // indirect - github.com/sylabs/sif/v2 v2.19.1 // indirect + github.com/sylabs/sif/v2 v2.20.1 // indirect github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 // indirect github.com/tchap/go-patricia/v2 v2.3.1 // indirect github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 // indirect @@ -222,7 +222,7 @@ require ( go.opentelemetry.io/otel/trace v1.28.0 // indirect golang.org/x/arch v0.8.0 // indirect golang.org/x/mod v0.22.0 // indirect - golang.org/x/oauth2 v0.23.0 // indirect + golang.org/x/oauth2 v0.24.0 // indirect golang.org/x/time v0.6.0 // indirect golang.org/x/tools v0.27.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect diff --git a/go.sum b/go.sum index 97c191c404..2bed508595 100644 --- a/go.sum +++ b/go.sum @@ -68,10 +68,10 @@ github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= -github.com/containerd/stargz-snapshotter/estargz v0.15.1 h1:eXJjw9RbkLFgioVaTG+G/ZW/0kEe2oEKCdS/ZxIyoCU= -github.com/containerd/stargz-snapshotter/estargz v0.15.1/go.mod h1:gr2RNwukQ/S9Nv33Lt6UC7xEx58C+LHRdoqbEKjz1Kk= -github.com/containerd/typeurl/v2 v2.2.0 h1:6NBDbQzr7I5LHgp34xAXYF5DOTQDn05X58lsPEmzLso= -github.com/containerd/typeurl/v2 v2.2.0/go.mod h1:8XOOxnyatxSWuG8OfsZXVnAF4iZfedjS/8UHSPJnX4g= +github.com/containerd/stargz-snapshotter/estargz v0.16.3 h1:7evrXtoh1mSbGj/pfRccTampEyKpjpOnS3CyiV1Ebr8= +github.com/containerd/stargz-snapshotter/estargz v0.16.3/go.mod h1:uyr4BfYfOj3G9WBVE8cOlQmXAbPN9VEQpBBeJIuOipU= +github.com/containerd/typeurl/v2 v2.2.3 h1:yNA/94zxWdvYACdYO8zofhrTVuQY73fFU1y++dYSw40= +github.com/containerd/typeurl/v2 v2.2.3/go.mod h1:95ljDnPfD3bAbDJRugOiShd/DlAAsxGtUBhJxIn7SCk= github.com/containernetworking/cni v1.2.3 h1:hhOcjNVUQTnzdRJ6alC5XF+wd9mfGIUaj8FuJbEslXM= github.com/containernetworking/cni v1.2.3/go.mod h1:DuLgF+aPd3DzcTQTtp/Nvl1Kim23oFKdm2okJzBQA5M= github.com/containernetworking/plugins v1.5.1 h1:T5ji+LPYjjgW0QM+KyrigZbLsZ8jaX+E5J/EcKOE4gQ= @@ -84,8 +84,8 @@ github.com/containers/conmon v2.0.20+incompatible h1:YbCVSFSCqFjjVwHTPINGdMX1F6J github.com/containers/conmon v2.0.20+incompatible/go.mod h1:hgwZ2mtuDrppv78a/cOBNiCm6O0UMWGx1mu7P00nu5I= github.com/containers/gvisor-tap-vsock v0.8.1 h1:88qkOjGMF9NmyoVG/orUw73mdwj3z4aOwEbRS01hF78= github.com/containers/gvisor-tap-vsock v0.8.1/go.mod h1:gjdY4JBWnynrXsxT8+OM7peEOd4FCZpoOWjSadHva0g= -github.com/containers/image/v5 v5.33.0 h1:6oPEFwTurf7pDTGw7TghqGs8K0+OvPtY/UyzU0B2DfE= -github.com/containers/image/v5 v5.33.0/go.mod h1:T7HpASmvnp2H1u4cyckMvCzLuYgpD18dSmabSw0AcHk= +github.com/containers/image/v5 v5.33.1-0.20241214000558-01058107e817 h1:7kJ9W88gfxAmhwM0xkhrKEcYizxkg9htF1fA9HiXLlg= +github.com/containers/image/v5 v5.33.1-0.20241214000558-01058107e817/go.mod h1:MwtBFIDcbUZV4Y7IvmkQcCxSgTdL0j8Ui+GqSKhj5p0= github.com/containers/libhvee v0.9.0 h1:5UxJMka1lDfxTeITA25Pd8QVVttJAG43eQS1Getw1tc= github.com/containers/libhvee v0.9.0/go.mod h1:p44VJd8jMIx3SRN1eM6PxfCEwXQE0lJ0dQppCAlzjPQ= github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01 h1:Qzk5C6cYglewc+UyGf6lc8Mj2UaPTHy/iF2De0/77CA= @@ -96,8 +96,8 @@ github.com/containers/ocicrypt v1.2.0 h1:X14EgRK3xNFvJEfI5O4Qn4T3E25ANudSOZz/sir github.com/containers/ocicrypt v1.2.0/go.mod h1:ZNviigQajtdlxIZGibvblVuIFBKIuUI2M0QM12SD31U= github.com/containers/psgo v1.9.0 h1:eJ74jzSaCHnWt26OlKZROSyUyRcGDf+gYBdXnxrMW4g= github.com/containers/psgo v1.9.0/go.mod h1:0YoluUm43Mz2UnBIh1P+6V6NWcbpTL5uRtXyOcH0B5A= -github.com/containers/storage v1.56.0 h1:DZ9KSkj6M2tvj/4bBoaJu3QDHRl35BwsZ4kmLJS97ZI= -github.com/containers/storage v1.56.0/go.mod h1:c6WKowcAlED/DkWGNuL9bvGYqIWCVy7isRMdCSKWNjk= +github.com/containers/storage v1.56.1-0.20241213190044-18afcfdf2c3d h1:exBfFBQ1aus+p6JdnLzYEyRCmI0r4LIT6xns5CXflVM= +github.com/containers/storage v1.56.1-0.20241213190044-18afcfdf2c3d/go.mod h1:S0a82hrdqPnFLguvlXYW7RlCH9TC5Ec+7xh5lWGk5dA= github.com/containers/winquit v1.1.0 h1:jArun04BNDQvt2W0Y78kh9TazN2EIEMG5Im6/JY7+pE= github.com/containers/winquit v1.1.0/go.mod h1:PsPeZlnbkmGGIToMPHF1zhWjBUkd8aHjMOr/vFcPxw8= github.com/coreos/go-oidc/v3 v3.11.0 h1:Ia3MxdwpSw702YW0xgfmP1GVCMA9aEFWu12XUZ3/OtI= @@ -129,8 +129,8 @@ github.com/disiqueira/gotree/v3 v3.0.2 h1:ik5iuLQQoufZBNPY518dXhiO5056hyNBIK9lWh github.com/disiqueira/gotree/v3 v3.0.2/go.mod h1:ZuyjE4+mUQZlbpkI24AmruZKhg3VHEgPLDY8Qk+uUu8= 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/docker/cli v27.3.1+incompatible h1:qEGdFBF3Xu6SCvCYhc7CzaQTlBmqDuzxPDpigSyeKQQ= -github.com/docker/cli v27.3.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v27.4.0+incompatible h1:/nJzWkcI1MDMN+U+px/YXnQWJqnu4J+QKGTfD6ptiTc= +github.com/docker/cli v27.4.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v27.4.0+incompatible h1:I9z7sQ5qyzO0BfAb9IMOawRkAGxhYsidKiTMcm0DU+A= @@ -424,8 +424,8 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= -github.com/proglottis/gpgme v0.1.3 h1:Crxx0oz4LKB3QXc5Ea0J19K/3ICfy3ftr5exgUK1AU0= -github.com/proglottis/gpgme v0.1.3/go.mod h1:fPbW/EZ0LvwQtH8Hy7eixhp1eF3G39dtx7GUN+0Gmy0= +github.com/proglottis/gpgme v0.1.4 h1:3nE7YNA70o2aLjcg63tXMOhPD7bplfE5CBdV+hLAm2M= +github.com/proglottis/gpgme v0.1.4/go.mod h1:5LoXMgpE4bttgwwdv9bLs/vwqv3qV7F4glEEZ7mRKrM= github.com/prometheus/client_golang v1.20.2 h1:5ctymQzZlyOON1666svgwn3s6IKWgfbjsejTMiXIyjg= github.com/prometheus/client_golang v1.20.2/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -459,8 +459,8 @@ github.com/sigstore/fulcio v1.6.4 h1:d86obfxUAG3Y6CYwOx1pdwCZwKmROB6w6927pKOVIRY github.com/sigstore/fulcio v1.6.4/go.mod h1:Y6bn3i3KGhXpaHsAtYP3Z4Np0+VzCo1fLv8Ci6mbPDs= github.com/sigstore/rekor v1.3.6 h1:QvpMMJVWAp69a3CHzdrLelqEqpTM3ByQRt5B5Kspbi8= github.com/sigstore/rekor v1.3.6/go.mod h1:JDTSNNMdQ/PxdsS49DJkJ+pRJCO/83nbR5p3aZQteXc= -github.com/sigstore/sigstore v1.8.9 h1:NiUZIVWywgYuVTxXmRoTT4O4QAGiTEKup4N1wdxFadk= -github.com/sigstore/sigstore v1.8.9/go.mod h1:d9ZAbNDs8JJfxJrYmulaTazU3Pwr8uLL9+mii4BNR3w= +github.com/sigstore/sigstore v1.8.11 h1:tEqeQqbT+awtM87ec9KEeSUxT/AFvJNawneYJyAkFrQ= +github.com/sigstore/sigstore v1.8.11/go.mod h1:fdrFQosxCQ4wTL5H1NrZcQkqQ72AQbPjtpcL2QOGKV0= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/skeema/knownhosts v1.3.0 h1:AM+y0rI04VksttfwjkSTNQorvGqmwATnvnAHpSgc0LY= @@ -487,8 +487,8 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/sylabs/sif/v2 v2.19.1 h1:1eeMmFc8elqJe60ZiWwXgL3gMheb0IP4GmNZ4q0IEA0= -github.com/sylabs/sif/v2 v2.19.1/go.mod h1:U1SUhvl8X1JIxAylC0DYz1fa/Xba6EMZD1dGPGBH83E= +github.com/sylabs/sif/v2 v2.20.1 h1:vzCj6ID8kwQV1OME+bTjEC8J2l/0NQsJVH1PehRg7p4= +github.com/sylabs/sif/v2 v2.20.1/go.mod h1:l3o3KPnvqvmXEPX4EC457HnxJXEGztdjWQVJKHt0u7w= github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 h1:kdXcSzyDtseVEc4yCz2qF8ZrQvIDBJLl4S1c3GCXmoI= github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/tchap/go-patricia/v2 v2.3.1 h1:6rQp39lgIYZ+MHmdEq4xzuk1t7OdC35z/xm0BGhTkes= @@ -608,8 +608,8 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI= golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= -golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE= +golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= diff --git a/vendor/github.com/containerd/stargz-snapshotter/estargz/testutil.go b/vendor/github.com/containerd/stargz-snapshotter/estargz/testutil.go index 0ca6fd75f2..ba650b4d1d 100644 --- a/vendor/github.com/containerd/stargz-snapshotter/estargz/testutil.go +++ b/vendor/github.com/containerd/stargz-snapshotter/estargz/testutil.go @@ -26,12 +26,13 @@ import ( "archive/tar" "bytes" "compress/gzip" + "crypto/rand" "crypto/sha256" "encoding/json" "errors" "fmt" "io" - "math/rand" + "math/big" "os" "path/filepath" "reflect" @@ -45,10 +46,6 @@ import ( digest "github.com/opencontainers/go-digest" ) -func init() { - rand.Seed(time.Now().UnixNano()) -} - // TestingController is Compression with some helper methods necessary for testing. type TestingController interface { Compression @@ -920,9 +917,11 @@ func checkVerifyInvalidTOCEntryFail(filename string) check { } if sampleEntry == nil { t.Fatalf("TOC must contain at least one regfile or chunk entry other than the rewrite target") + return } if targetEntry == nil { t.Fatalf("rewrite target not found") + return } targetEntry.Offset = sampleEntry.Offset }, @@ -2291,7 +2290,11 @@ var runes = []rune("1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWX func randomContents(n int) string { b := make([]rune, n) for i := range b { - b[i] = runes[rand.Intn(len(runes))] + bi, err := rand.Int(rand.Reader, big.NewInt(int64(len(runes)))) + if err != nil { + panic(err) + } + b[i] = runes[int(bi.Int64())] } return string(b) } diff --git a/vendor/github.com/containerd/typeurl/v2/README.md b/vendor/github.com/containerd/typeurl/v2/README.md index 8d86600a40..3098526ab1 100644 --- a/vendor/github.com/containerd/typeurl/v2/README.md +++ b/vendor/github.com/containerd/typeurl/v2/README.md @@ -18,3 +18,9 @@ As a containerd sub-project, you will find the: * and [Contributing guidelines](https://github.com/containerd/project/blob/main/CONTRIBUTING.md) information in our [`containerd/project`](https://github.com/containerd/project) repository. + +## Optional + +By default, support for gogoproto is available along side the standard Google +protobuf types. +You can choose to leave gogo support out by using the `!no_gogo` build tag. diff --git a/vendor/github.com/containerd/typeurl/v2/types.go b/vendor/github.com/containerd/typeurl/v2/types.go index 78817b701e..9bf7810416 100644 --- a/vendor/github.com/containerd/typeurl/v2/types.go +++ b/vendor/github.com/containerd/typeurl/v2/types.go @@ -24,7 +24,6 @@ import ( "reflect" "sync" - gogoproto "github.com/gogo/protobuf/proto" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/reflect/protoregistry" "google.golang.org/protobuf/types/known/anypb" @@ -33,8 +32,16 @@ import ( var ( mu sync.RWMutex registry = make(map[reflect.Type]string) + handlers []handler ) +type handler interface { + Marshaller(interface{}) func() ([]byte, error) + Unmarshaller(interface{}) func([]byte) error + TypeURL(interface{}) string + GetType(url string) (reflect.Type, bool) +} + // Definitions of common error types used throughout typeurl. // // These error types are used with errors.Wrap and errors.Wrapf to add context @@ -112,9 +119,12 @@ func TypeURL(v interface{}) (string, error) { switch t := v.(type) { case proto.Message: return string(t.ProtoReflect().Descriptor().FullName()), nil - case gogoproto.Message: - return gogoproto.MessageName(t), nil default: + for _, h := range handlers { + if u := h.TypeURL(v); u != "" { + return u, nil + } + } return "", fmt.Errorf("type %s: %w", reflect.TypeOf(v), ErrNotFound) } } @@ -149,12 +159,19 @@ func MarshalAny(v interface{}) (Any, error) { marshal = func(v interface{}) ([]byte, error) { return proto.Marshal(t) } - case gogoproto.Message: - marshal = func(v interface{}) ([]byte, error) { - return gogoproto.Marshal(t) - } default: - marshal = json.Marshal + for _, h := range handlers { + if m := h.Marshaller(v); m != nil { + marshal = func(v interface{}) ([]byte, error) { + return m() + } + break + } + } + + if marshal == nil { + marshal = json.Marshal + } } url, err := TypeURL(v) @@ -223,13 +240,13 @@ func MarshalAnyToProto(from interface{}) (*anypb.Any, error) { } func unmarshal(typeURL string, value []byte, v interface{}) (interface{}, error) { - t, err := getTypeByUrl(typeURL) + t, isProto, err := getTypeByUrl(typeURL) if err != nil { return nil, err } if v == nil { - v = reflect.New(t.t).Interface() + v = reflect.New(t).Interface() } else { // Validate interface type provided by client vURL, err := TypeURL(v) @@ -241,51 +258,45 @@ func unmarshal(typeURL string, value []byte, v interface{}) (interface{}, error) } } - if t.isProto { - switch t := v.(type) { - case proto.Message: - err = proto.Unmarshal(value, t) - case gogoproto.Message: - err = gogoproto.Unmarshal(value, t) + if isProto { + pm, ok := v.(proto.Message) + if ok { + return v, proto.Unmarshal(value, pm) } - } else { - err = json.Unmarshal(value, v) - } - return v, err -} + for _, h := range handlers { + if unmarshal := h.Unmarshaller(v); unmarshal != nil { + return v, unmarshal(value) + } + } + } -type urlType struct { - t reflect.Type - isProto bool + // fallback to json unmarshaller + return v, json.Unmarshal(value, v) } -func getTypeByUrl(url string) (urlType, error) { +func getTypeByUrl(url string) (_ reflect.Type, isProto bool, _ error) { mu.RLock() for t, u := range registry { if u == url { mu.RUnlock() - return urlType{ - t: t, - }, nil + return t, false, nil } } mu.RUnlock() - // fallback to proto registry - t := gogoproto.MessageType(url) - if t != nil { - return urlType{ - // get the underlying Elem because proto returns a pointer to the type - t: t.Elem(), - isProto: true, - }, nil - } mt, err := protoregistry.GlobalTypes.FindMessageByURL(url) if err != nil { - return urlType{}, fmt.Errorf("type with url %s: %w", url, ErrNotFound) + if errors.Is(err, protoregistry.NotFound) { + for _, h := range handlers { + if t, isProto := h.GetType(url); t != nil { + return t, isProto, nil + } + } + } + return nil, false, fmt.Errorf("type with url %s: %w", url, ErrNotFound) } empty := mt.New().Interface() - return urlType{t: reflect.TypeOf(empty).Elem(), isProto: true}, nil + return reflect.TypeOf(empty).Elem(), true, nil } func tryDereference(v interface{}) reflect.Type { diff --git a/vendor/github.com/containerd/typeurl/v2/types_gogo.go b/vendor/github.com/containerd/typeurl/v2/types_gogo.go new file mode 100644 index 0000000000..adb892ec60 --- /dev/null +++ b/vendor/github.com/containerd/typeurl/v2/types_gogo.go @@ -0,0 +1,68 @@ +//go:build !no_gogo + +/* + Copyright The containerd Authors. + + 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 typeurl + +import ( + "reflect" + + gogoproto "github.com/gogo/protobuf/proto" +) + +func init() { + handlers = append(handlers, gogoHandler{}) +} + +type gogoHandler struct{} + +func (gogoHandler) Marshaller(v interface{}) func() ([]byte, error) { + pm, ok := v.(gogoproto.Message) + if !ok { + return nil + } + return func() ([]byte, error) { + return gogoproto.Marshal(pm) + } +} + +func (gogoHandler) Unmarshaller(v interface{}) func([]byte) error { + pm, ok := v.(gogoproto.Message) + if !ok { + return nil + } + + return func(dt []byte) error { + return gogoproto.Unmarshal(dt, pm) + } +} + +func (gogoHandler) TypeURL(v interface{}) string { + pm, ok := v.(gogoproto.Message) + if !ok { + return "" + } + return gogoproto.MessageName(pm) +} + +func (gogoHandler) GetType(url string) (reflect.Type, bool) { + t := gogoproto.MessageType(url) + if t == nil { + return nil, false + } + return t.Elem(), true +} diff --git a/vendor/github.com/containers/image/v5/copy/single.go b/vendor/github.com/containers/image/v5/copy/single.go index e008c7e86f..7bc44d1f83 100644 --- a/vendor/github.com/containers/image/v5/copy/single.go +++ b/vendor/github.com/containers/image/v5/copy/single.go @@ -109,7 +109,7 @@ func (c *copier) copySingleImage(ctx context.Context, unparsedImage *image.Unpar } } - if err := checkImageDestinationForCurrentRuntime(ctx, c.options.DestinationCtx, src, c.dest); err != nil { + if err := prepareImageConfigForDest(ctx, c.options.DestinationCtx, src, c.dest); err != nil { return copySingleImageResult{}, err } @@ -316,12 +316,15 @@ func (c *copier) copySingleImage(ctx context.Context, unparsedImage *image.Unpar return res, nil } -// checkImageDestinationForCurrentRuntime enforces dest.MustMatchRuntimeOS, if necessary. -func checkImageDestinationForCurrentRuntime(ctx context.Context, sys *types.SystemContext, src types.Image, dest types.ImageDestination) error { +// prepareImageConfigForDest enforces dest.MustMatchRuntimeOS and handles dest.NoteOriginalOCIConfig, if necessary. +func prepareImageConfigForDest(ctx context.Context, sys *types.SystemContext, src types.Image, dest private.ImageDestination) error { + ociConfig, configErr := src.OCIConfig(ctx) + // Do not fail on configErr here, this might be an artifact + // and maybe nothing needs this to be a container image and to process the config. + if dest.MustMatchRuntimeOS() { - c, err := src.OCIConfig(ctx) - if err != nil { - return fmt.Errorf("parsing image configuration: %w", err) + if configErr != nil { + return fmt.Errorf("parsing image configuration: %w", configErr) } wantedPlatforms := platform.WantedPlatforms(sys) @@ -331,7 +334,7 @@ func checkImageDestinationForCurrentRuntime(ctx context.Context, sys *types.Syst // For a transitional period, this might trigger warnings because the Variant // field was added to OCI config only recently. If this turns out to be too noisy, // revert this check to only look for (OS, Architecture). - if platform.MatchesPlatform(c.Platform, wantedPlatform) { + if platform.MatchesPlatform(ociConfig.Platform, wantedPlatform) { match = true break } @@ -339,9 +342,14 @@ func checkImageDestinationForCurrentRuntime(ctx context.Context, sys *types.Syst } if !match { logrus.Infof("Image operating system mismatch: image uses OS %q+architecture %q+%q, expecting one of %q", - c.OS, c.Architecture, c.Variant, strings.Join(options.list, ", ")) + ociConfig.OS, ociConfig.Architecture, ociConfig.Variant, strings.Join(options.list, ", ")) } } + + if err := dest.NoteOriginalOCIConfig(ociConfig, configErr); err != nil { + return err + } + return nil } diff --git a/vendor/github.com/containers/image/v5/directory/directory_dest.go b/vendor/github.com/containers/image/v5/directory/directory_dest.go index 000c8987d9..6e88aa01d1 100644 --- a/vendor/github.com/containers/image/v5/directory/directory_dest.go +++ b/vendor/github.com/containers/image/v5/directory/directory_dest.go @@ -29,6 +29,7 @@ var ErrNotContainerImageDir = errors.New("not a containers image directory, don' type dirImageDestination struct { impl.Compat impl.PropertyMethodsInitialize + stubs.IgnoresOriginalOCIConfig stubs.NoPutBlobPartialInitialize stubs.AlwaysSupportsSignatures diff --git a/vendor/github.com/containers/image/v5/docker/daemon/client.go b/vendor/github.com/containers/image/v5/docker/daemon/client.go index 64ccf6ae55..8cef9698f8 100644 --- a/vendor/github.com/containers/image/v5/docker/daemon/client.go +++ b/vendor/github.com/containers/image/v5/docker/daemon/client.go @@ -3,6 +3,7 @@ package daemon import ( "net/http" "path/filepath" + "time" "github.com/containers/image/v5/types" dockerclient "github.com/docker/docker/client" @@ -82,6 +83,11 @@ func tlsConfig(sys *types.SystemContext) (*http.Client, error) { Transport: &http.Transport{ Proxy: http.ProxyFromEnvironment, TLSClientConfig: tlsc, + // In general we want to follow docker/daemon/client.defaultHTTPClient , as long as it doesn’t affect compatibility. + // These idle connection limits really only apply to long-running clients, which is not our case here; + // we include the same values purely for symmetry. + MaxIdleConns: 6, + IdleConnTimeout: 30 * time.Second, }, CheckRedirect: dockerclient.CheckRedirect, }, nil @@ -92,6 +98,11 @@ func httpConfig() *http.Client { Transport: &http.Transport{ Proxy: http.ProxyFromEnvironment, TLSClientConfig: nil, + // In general we want to follow docker/daemon/client.defaultHTTPClient , as long as it doesn’t affect compatibility. + // These idle connection limits really only apply to long-running clients, which is not our case here; + // we include the same values purely for symmetry. + MaxIdleConns: 6, + IdleConnTimeout: 30 * time.Second, }, CheckRedirect: dockerclient.CheckRedirect, } diff --git a/vendor/github.com/containers/image/v5/docker/distribution_error.go b/vendor/github.com/containers/image/v5/docker/distribution_error.go index 0a0064576a..622d21fb1c 100644 --- a/vendor/github.com/containers/image/v5/docker/distribution_error.go +++ b/vendor/github.com/containers/image/v5/docker/distribution_error.go @@ -24,7 +24,6 @@ import ( "slices" "github.com/docker/distribution/registry/api/errcode" - dockerChallenge "github.com/docker/distribution/registry/client/auth/challenge" ) // errNoErrorsInBody is returned when an HTTP response body parses to an empty @@ -114,10 +113,11 @@ func mergeErrors(err1, err2 error) error { // UnexpectedHTTPStatusError returned for response code outside of expected // range. func handleErrorResponse(resp *http.Response) error { - if resp.StatusCode >= 400 && resp.StatusCode < 500 { + switch { + case resp.StatusCode == http.StatusUnauthorized: // Check for OAuth errors within the `WWW-Authenticate` header first // See https://tools.ietf.org/html/rfc6750#section-3 - for _, c := range dockerChallenge.ResponseChallenges(resp) { + for _, c := range parseAuthHeader(resp.Header) { if c.Scheme == "bearer" { var err errcode.Error // codes defined at https://tools.ietf.org/html/rfc6750#section-3.1 @@ -138,6 +138,8 @@ func handleErrorResponse(resp *http.Response) error { return mergeErrors(err, parseHTTPErrorResponse(resp.StatusCode, resp.Body)) } } + fallthrough + case resp.StatusCode >= 400 && resp.StatusCode < 500: err := parseHTTPErrorResponse(resp.StatusCode, resp.Body) if uErr, ok := err.(*unexpectedHTTPResponseError); ok && resp.StatusCode == 401 { return errcode.ErrorCodeUnauthorized.WithDetail(uErr.Response) diff --git a/vendor/github.com/containers/image/v5/docker/docker_image_dest.go b/vendor/github.com/containers/image/v5/docker/docker_image_dest.go index f5e2bb61e8..3ac43cf9fc 100644 --- a/vendor/github.com/containers/image/v5/docker/docker_image_dest.go +++ b/vendor/github.com/containers/image/v5/docker/docker_image_dest.go @@ -41,6 +41,7 @@ import ( type dockerImageDestination struct { impl.Compat impl.PropertyMethodsInitialize + stubs.IgnoresOriginalOCIConfig stubs.NoPutBlobPartialInitialize ref dockerReference diff --git a/vendor/github.com/containers/image/v5/docker/docker_image_src.go b/vendor/github.com/containers/image/v5/docker/docker_image_src.go index 6e44ce0960..41ab9bfd16 100644 --- a/vendor/github.com/containers/image/v5/docker/docker_image_src.go +++ b/vendor/github.com/containers/image/v5/docker/docker_image_src.go @@ -340,6 +340,10 @@ func handle206Response(streams chan io.ReadCloser, errs chan error, body io.Read } return } + if parts >= len(chunks) { + errs <- errors.New("too many parts returned by the server") + break + } s := signalCloseReader{ closed: make(chan struct{}), stream: p, diff --git a/vendor/github.com/containers/image/v5/docker/internal/tarfile/dest.go b/vendor/github.com/containers/image/v5/docker/internal/tarfile/dest.go index f142346807..8f5ba7e36e 100644 --- a/vendor/github.com/containers/image/v5/docker/internal/tarfile/dest.go +++ b/vendor/github.com/containers/image/v5/docker/internal/tarfile/dest.go @@ -24,6 +24,7 @@ import ( type Destination struct { impl.Compat impl.PropertyMethodsInitialize + stubs.IgnoresOriginalOCIConfig stubs.NoPutBlobPartialInitialize stubs.NoSignaturesInitialize diff --git a/vendor/github.com/containers/image/v5/internal/imagedestination/stubs/original_oci_config.go b/vendor/github.com/containers/image/v5/internal/imagedestination/stubs/original_oci_config.go new file mode 100644 index 0000000000..c4536e933b --- /dev/null +++ b/vendor/github.com/containers/image/v5/internal/imagedestination/stubs/original_oci_config.go @@ -0,0 +1,16 @@ +package stubs + +import ( + imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1" +) + +// IgnoresOriginalOCIConfig implements NoteOriginalOCIConfig() that does nothing. +type IgnoresOriginalOCIConfig struct{} + +// NoteOriginalOCIConfig provides the config of the image, as it exists on the source, BUT converted to OCI format, +// or an error obtaining that value (e.g. if the image is an artifact and not a container image). +// The destination can use it in its TryReusingBlob/PutBlob implementations +// (otherwise it only obtains the final config after all layers are written). +func (stub IgnoresOriginalOCIConfig) NoteOriginalOCIConfig(ociConfig *imgspecv1.Image, configErr error) error { + return nil +} diff --git a/vendor/github.com/containers/image/v5/internal/imagedestination/wrapper.go b/vendor/github.com/containers/image/v5/internal/imagedestination/wrapper.go index 0fe902e31f..b2462a3bc1 100644 --- a/vendor/github.com/containers/image/v5/internal/imagedestination/wrapper.go +++ b/vendor/github.com/containers/image/v5/internal/imagedestination/wrapper.go @@ -14,6 +14,7 @@ import ( // wrapped provides the private.ImageDestination operations // for a destination that only implements types.ImageDestination type wrapped struct { + stubs.IgnoresOriginalOCIConfig stubs.NoPutBlobPartialInitialize types.ImageDestination diff --git a/vendor/github.com/containers/image/v5/internal/manifest/docker_schema2_list.go b/vendor/github.com/containers/image/v5/internal/manifest/docker_schema2_list.go index 07922ceceb..4c1589ef02 100644 --- a/vendor/github.com/containers/image/v5/internal/manifest/docker_schema2_list.go +++ b/vendor/github.com/containers/image/v5/internal/manifest/docker_schema2_list.go @@ -74,20 +74,20 @@ func (list *Schema2ListPublic) Instance(instanceDigest digest.Digest) (ListUpdat // UpdateInstances updates the sizes, digests, and media types of the manifests // which the list catalogs. -func (index *Schema2ListPublic) UpdateInstances(updates []ListUpdate) error { +func (list *Schema2ListPublic) UpdateInstances(updates []ListUpdate) error { editInstances := []ListEdit{} for i, instance := range updates { editInstances = append(editInstances, ListEdit{ - UpdateOldDigest: index.Manifests[i].Digest, + UpdateOldDigest: list.Manifests[i].Digest, UpdateDigest: instance.Digest, UpdateSize: instance.Size, UpdateMediaType: instance.MediaType, ListOperation: ListOpUpdate}) } - return index.editInstances(editInstances) + return list.editInstances(editInstances) } -func (index *Schema2ListPublic) editInstances(editInstances []ListEdit) error { +func (list *Schema2ListPublic) editInstances(editInstances []ListEdit) error { addedEntries := []Schema2ManifestDescriptor{} for i, editInstance := range editInstances { switch editInstance.ListOperation { @@ -98,21 +98,21 @@ func (index *Schema2ListPublic) editInstances(editInstances []ListEdit) error { if err := editInstance.UpdateDigest.Validate(); err != nil { return fmt.Errorf("Schema2List.EditInstances: Modified digest %s is an invalid digest: %w", editInstance.UpdateDigest, err) } - targetIndex := slices.IndexFunc(index.Manifests, func(m Schema2ManifestDescriptor) bool { + targetIndex := slices.IndexFunc(list.Manifests, func(m Schema2ManifestDescriptor) bool { return m.Digest == editInstance.UpdateOldDigest }) if targetIndex == -1 { return fmt.Errorf("Schema2List.EditInstances: digest %s not found", editInstance.UpdateOldDigest) } - index.Manifests[targetIndex].Digest = editInstance.UpdateDigest + list.Manifests[targetIndex].Digest = editInstance.UpdateDigest if editInstance.UpdateSize < 0 { return fmt.Errorf("update %d of %d passed to Schema2List.UpdateInstances had an invalid size (%d)", i+1, len(editInstances), editInstance.UpdateSize) } - index.Manifests[targetIndex].Size = editInstance.UpdateSize + list.Manifests[targetIndex].Size = editInstance.UpdateSize if editInstance.UpdateMediaType == "" { - return fmt.Errorf("update %d of %d passed to Schema2List.UpdateInstances had no media type (was %q)", i+1, len(editInstances), index.Manifests[i].MediaType) + return fmt.Errorf("update %d of %d passed to Schema2List.UpdateInstances had no media type (was %q)", i+1, len(editInstances), list.Manifests[i].MediaType) } - index.Manifests[targetIndex].MediaType = editInstance.UpdateMediaType + list.Manifests[targetIndex].MediaType = editInstance.UpdateMediaType case ListOpAdd: if editInstance.AddPlatform == nil { // Should we create a struct with empty fields instead? @@ -135,13 +135,13 @@ func (index *Schema2ListPublic) editInstances(editInstances []ListEdit) error { if len(addedEntries) != 0 { // slices.Clone() here to ensure a private backing array; // an external caller could have manually created Schema2ListPublic with a slice with extra capacity. - index.Manifests = append(slices.Clone(index.Manifests), addedEntries...) + list.Manifests = append(slices.Clone(list.Manifests), addedEntries...) } return nil } -func (index *Schema2List) EditInstances(editInstances []ListEdit) error { - return index.editInstances(editInstances) +func (list *Schema2List) EditInstances(editInstances []ListEdit) error { + return list.editInstances(editInstances) } func (list *Schema2ListPublic) ChooseInstanceByCompression(ctx *types.SystemContext, preferGzip types.OptionalBool) (digest.Digest, error) { @@ -280,12 +280,12 @@ func schema2ListFromPublic(public *Schema2ListPublic) *Schema2List { return &Schema2List{*public} } -func (index *Schema2List) CloneInternal() List { - return schema2ListFromPublic(Schema2ListPublicClone(&index.Schema2ListPublic)) +func (list *Schema2List) CloneInternal() List { + return schema2ListFromPublic(Schema2ListPublicClone(&list.Schema2ListPublic)) } -func (index *Schema2List) Clone() ListPublic { - return index.CloneInternal() +func (list *Schema2List) Clone() ListPublic { + return list.CloneInternal() } // Schema2ListFromManifest creates a Schema2 manifest list instance from marshalled diff --git a/vendor/github.com/containers/image/v5/internal/private/private.go b/vendor/github.com/containers/image/v5/internal/private/private.go index 4247a8db77..afd425483d 100644 --- a/vendor/github.com/containers/image/v5/internal/private/private.go +++ b/vendor/github.com/containers/image/v5/internal/private/private.go @@ -10,6 +10,7 @@ import ( compression "github.com/containers/image/v5/pkg/compression/types" "github.com/containers/image/v5/types" "github.com/opencontainers/go-digest" + imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1" ) // ImageSourceInternalOnly is the part of private.ImageSource that is not @@ -41,6 +42,12 @@ type ImageDestinationInternalOnly interface { // FIXME: Add SupportsSignaturesWithFormat or something like that, to allow early failures // on unsupported formats. + // NoteOriginalOCIConfig provides the config of the image, as it exists on the source, BUT converted to OCI format, + // or an error obtaining that value (e.g. if the image is an artifact and not a container image). + // The destination can use it in its TryReusingBlob/PutBlob implementations + // (otherwise it only obtains the final config after all layers are written). + NoteOriginalOCIConfig(ociConfig *imgspecv1.Image, configErr error) error + // PutBlobWithOptions writes contents of stream and returns data representing the result. // inputInfo.Digest can be optionally provided if known; if provided, and stream is read to the end without error, the digest MUST match the stream contents. // inputInfo.Size is the expected length of stream, if known. diff --git a/vendor/github.com/containers/image/v5/oci/archive/oci_dest.go b/vendor/github.com/containers/image/v5/oci/archive/oci_dest.go index 1d1e26c84f..54b2e50566 100644 --- a/vendor/github.com/containers/image/v5/oci/archive/oci_dest.go +++ b/vendor/github.com/containers/image/v5/oci/archive/oci_dest.go @@ -14,6 +14,7 @@ import ( "github.com/containers/storage/pkg/archive" "github.com/containers/storage/pkg/idtools" digest "github.com/opencontainers/go-digest" + imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1" "github.com/sirupsen/logrus" ) @@ -103,6 +104,14 @@ func (d *ociArchiveImageDestination) SupportsPutBlobPartial() bool { return d.unpackedDest.SupportsPutBlobPartial() } +// NoteOriginalOCIConfig provides the config of the image, as it exists on the source, BUT converted to OCI format, +// or an error obtaining that value (e.g. if the image is an artifact and not a container image). +// The destination can use it in its TryReusingBlob/PutBlob implementations +// (otherwise it only obtains the final config after all layers are written). +func (d *ociArchiveImageDestination) NoteOriginalOCIConfig(ociConfig *imgspecv1.Image, configErr error) error { + return d.unpackedDest.NoteOriginalOCIConfig(ociConfig, configErr) +} + // PutBlobWithOptions writes contents of stream and returns data representing the result. // inputInfo.Digest can be optionally provided if known; if provided, and stream is read to the end without error, the digest MUST match the stream contents. // inputInfo.Size is the expected length of stream, if known. diff --git a/vendor/github.com/containers/image/v5/oci/internal/oci_util.go b/vendor/github.com/containers/image/v5/oci/internal/oci_util.go index 53827b11af..72af74e251 100644 --- a/vendor/github.com/containers/image/v5/oci/internal/oci_util.go +++ b/vendor/github.com/containers/image/v5/oci/internal/oci_util.go @@ -98,7 +98,7 @@ func ValidateScope(scope string) error { } func validateScopeWindows(scope string) error { - matched, _ := regexp.Match(`^[a-zA-Z]:\\`, []byte(scope)) + matched, _ := regexp.MatchString(`^[a-zA-Z]:\\`, scope) if !matched { return fmt.Errorf("Invalid scope '%s'. Must be an absolute path", scope) } diff --git a/vendor/github.com/containers/image/v5/oci/layout/oci_dest.go b/vendor/github.com/containers/image/v5/oci/layout/oci_dest.go index 85374cecf0..cb2768d745 100644 --- a/vendor/github.com/containers/image/v5/oci/layout/oci_dest.go +++ b/vendor/github.com/containers/image/v5/oci/layout/oci_dest.go @@ -27,6 +27,7 @@ import ( type ociImageDestination struct { impl.Compat impl.PropertyMethodsInitialize + stubs.IgnoresOriginalOCIConfig stubs.NoPutBlobPartialInitialize stubs.NoSignaturesInitialize diff --git a/vendor/github.com/containers/image/v5/openshift/openshift_dest.go b/vendor/github.com/containers/image/v5/openshift/openshift_dest.go index 61f69e93fd..bd5e77aa8f 100644 --- a/vendor/github.com/containers/image/v5/openshift/openshift_dest.go +++ b/vendor/github.com/containers/image/v5/openshift/openshift_dest.go @@ -22,6 +22,7 @@ import ( "github.com/containers/image/v5/manifest" "github.com/containers/image/v5/types" "github.com/opencontainers/go-digest" + imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1" ) type openshiftImageDestination struct { @@ -111,6 +112,14 @@ func (d *openshiftImageDestination) SupportsPutBlobPartial() bool { return d.docker.SupportsPutBlobPartial() } +// NoteOriginalOCIConfig provides the config of the image, as it exists on the source, BUT converted to OCI format, +// or an error obtaining that value (e.g. if the image is an artifact and not a container image). +// The destination can use it in its TryReusingBlob/PutBlob implementations +// (otherwise it only obtains the final config after all layers are written). +func (d *openshiftImageDestination) NoteOriginalOCIConfig(ociConfig *imgspecv1.Image, configErr error) error { + return d.docker.NoteOriginalOCIConfig(ociConfig, configErr) +} + // PutBlobWithOptions writes contents of stream and returns data representing the result. // inputInfo.Digest can be optionally provided if known; if provided, and stream is read to the end without error, the digest MUST match the stream contents. // inputInfo.Size is the expected length of stream, if known. diff --git a/vendor/github.com/containers/image/v5/pkg/blobcache/dest.go b/vendor/github.com/containers/image/v5/pkg/blobcache/dest.go index f7103d13eb..54e3d294b9 100644 --- a/vendor/github.com/containers/image/v5/pkg/blobcache/dest.go +++ b/vendor/github.com/containers/image/v5/pkg/blobcache/dest.go @@ -19,6 +19,7 @@ import ( "github.com/containers/storage/pkg/archive" "github.com/containers/storage/pkg/ioutils" digest "github.com/opencontainers/go-digest" + imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1" "github.com/sirupsen/logrus" ) @@ -138,6 +139,14 @@ func (d *blobCacheDestination) HasThreadSafePutBlob() bool { return d.destination.HasThreadSafePutBlob() } +// NoteOriginalOCIConfig provides the config of the image, as it exists on the source, BUT converted to OCI format, +// or an error obtaining that value (e.g. if the image is an artifact and not a container image). +// The destination can use it in its TryReusingBlob/PutBlob implementations +// (otherwise it only obtains the final config after all layers are written). +func (d *blobCacheDestination) NoteOriginalOCIConfig(ociConfig *imgspecv1.Image, configErr error) error { + return d.destination.NoteOriginalOCIConfig(ociConfig, configErr) +} + // PutBlobWithOptions writes contents of stream and returns data representing the result. // inputInfo.Digest can be optionally provided if known; if provided, and stream is read to the end without error, the digest MUST match the stream contents. // inputInfo.Size is the expected length of stream, if known. diff --git a/vendor/github.com/containers/image/v5/signature/fulcio_cert_stub.go b/vendor/github.com/containers/image/v5/signature/fulcio_cert_stub.go index c0b48dafa7..c0dc7b232b 100644 --- a/vendor/github.com/containers/image/v5/signature/fulcio_cert_stub.go +++ b/vendor/github.com/containers/image/v5/signature/fulcio_cert_stub.go @@ -20,7 +20,7 @@ func (f *fulcioTrustRoot) validate() error { return errors.New("fulcio disabled at compile-time") } -func verifyRekorFulcio(rekorPublicKey *ecdsa.PublicKey, fulcioTrustRoot *fulcioTrustRoot, untrustedRekorSET []byte, +func verifyRekorFulcio(rekorPublicKeys []*ecdsa.PublicKey, fulcioTrustRoot *fulcioTrustRoot, untrustedRekorSET []byte, untrustedCertificateBytes []byte, untrustedIntermediateChainBytes []byte, untrustedBase64Signature string, untrustedPayloadBytes []byte) (crypto.PublicKey, error) { return nil, errors.New("fulcio disabled at compile-time") diff --git a/vendor/github.com/containers/image/v5/signature/internal/rekor_set_stub.go b/vendor/github.com/containers/image/v5/signature/internal/rekor_set_stub.go index 7c121cc2ee..2b20bbed2e 100644 --- a/vendor/github.com/containers/image/v5/signature/internal/rekor_set_stub.go +++ b/vendor/github.com/containers/image/v5/signature/internal/rekor_set_stub.go @@ -10,6 +10,6 @@ import ( // VerifyRekorSET verifies that unverifiedRekorSET is correctly signed by publicKey and matches the rest of the data. // Returns bundle upload time on success. -func VerifyRekorSET(publicKey *ecdsa.PublicKey, unverifiedRekorSET []byte, unverifiedKeyOrCertBytes []byte, unverifiedBase64Signature string, unverifiedPayloadBytes []byte) (time.Time, error) { +func VerifyRekorSET(publicKeys []*ecdsa.PublicKey, unverifiedRekorSET []byte, unverifiedKeyOrCertBytes []byte, unverifiedBase64Signature string, unverifiedPayloadBytes []byte) (time.Time, error) { return time.Time{}, NewInvalidSignatureError("rekor disabled at compile-time") } diff --git a/vendor/github.com/containers/image/v5/storage/storage_dest.go b/vendor/github.com/containers/image/v5/storage/storage_dest.go index 51fac71e44..bad11706b6 100644 --- a/vendor/github.com/containers/image/v5/storage/storage_dest.go +++ b/vendor/github.com/containers/image/v5/storage/storage_dest.go @@ -17,11 +17,13 @@ import ( "sync/atomic" "github.com/containers/image/v5/docker/reference" + "github.com/containers/image/v5/internal/image" "github.com/containers/image/v5/internal/imagedestination/impl" "github.com/containers/image/v5/internal/imagedestination/stubs" + srcImpl "github.com/containers/image/v5/internal/imagesource/impl" + srcStubs "github.com/containers/image/v5/internal/imagesource/stubs" "github.com/containers/image/v5/internal/private" "github.com/containers/image/v5/internal/putblobdigest" - "github.com/containers/image/v5/internal/set" "github.com/containers/image/v5/internal/signature" "github.com/containers/image/v5/internal/tmpdir" "github.com/containers/image/v5/manifest" @@ -57,8 +59,9 @@ type storageImageDestination struct { imageRef storageReference directory string // Temporary directory where we store blobs until Commit() time nextTempFileID atomic.Int32 // A counter that we use for computing filenames to assign to blobs - manifest []byte // Manifest contents, temporary - manifestDigest digest.Digest // Valid if len(manifest) != 0 + manifest []byte // (Per-instance) manifest contents, or nil if not yet known. + manifestMIMEType string // Valid if manifest != nil + manifestDigest digest.Digest // Valid if manifest != nil untrustedDiffIDValues []digest.Digest // From config’s RootFS.DiffIDs (not even validated to be valid digest.Digest!); or nil if not read yet signatures []byte // Signature contents, temporary signatureses map[digest.Digest][]byte // Instance signature contents, temporary @@ -121,6 +124,9 @@ type storageImageDestinationLockProtected struct { filenames map[digest.Digest]string // Mapping from layer blobsums to their sizes. If set, filenames and blobDiffIDs must also be set. fileSizes map[digest.Digest]int64 + + // Config + configDigest digest.Digest // "" if N/A or not known yet. } // addedLayerInfo records data about a layer to use in this image. @@ -201,6 +207,18 @@ func (s *storageImageDestination) computeNextBlobCacheFile() string { return filepath.Join(s.directory, fmt.Sprintf("%d", s.nextTempFileID.Add(1))) } +// NoteOriginalOCIConfig provides the config of the image, as it exists on the source, BUT converted to OCI format, +// or an error obtaining that value (e.g. if the image is an artifact and not a container image). +// The destination can use it in its TryReusingBlob/PutBlob implementations +// (otherwise it only obtains the final config after all layers are written). +func (s *storageImageDestination) NoteOriginalOCIConfig(ociConfig *imgspecv1.Image, configErr error) error { + if configErr != nil { + return fmt.Errorf("writing to c/storage without a valid image config: %w", configErr) + } + s.setUntrustedDiffIDValuesFromOCIConfig(ociConfig) + return nil +} + // PutBlobWithOptions writes contents of stream and returns data representing the result. // inputInfo.Digest can be optionally provided if known; if provided, and stream is read to the end without error, the digest MUST match the stream contents. // inputInfo.Size is the expected length of stream, if known. @@ -214,7 +232,17 @@ func (s *storageImageDestination) PutBlobWithOptions(ctx context.Context, stream return info, err } - if options.IsConfig || options.LayerIndex == nil { + if options.IsConfig { + s.lock.Lock() + defer s.lock.Unlock() + if s.lockProtected.configDigest != "" { + return private.UploadedBlob{}, fmt.Errorf("after config %q, refusing to record another config %q", + s.lockProtected.configDigest.String(), info.Digest.String()) + } + s.lockProtected.configDigest = info.Digest + return info, nil + } + if options.LayerIndex == nil { return info, nil } @@ -351,35 +379,41 @@ func (s *storageImageDestination) PutBlobPartial(ctx context.Context, chunkAcces blobDigest := srcInfo.Digest s.lock.Lock() - if out.UncompressedDigest != "" { - s.lockProtected.indexToDiffID[options.LayerIndex] = out.UncompressedDigest - if out.TOCDigest != "" { - options.Cache.RecordTOCUncompressedPair(out.TOCDigest, out.UncompressedDigest) - } - // Don’t set indexToTOCDigest on this path: - // - Using UncompressedDigest allows image reuse with non-partially-pulled layers, so we want to set indexToDiffID. - // - If UncompressedDigest has been computed, that means the layer was read completely, and the TOC has been created from scratch. - // That TOC is quite unlikely to match any other TOC value. - - // The computation of UncompressedDigest means the whole layer has been consumed; while doing that, chunked.GetDiffer is - // responsible for ensuring blobDigest has been validated. - if out.CompressedDigest != blobDigest { - return private.UploadedBlob{}, fmt.Errorf("internal error: PrepareStagedLayer returned CompressedDigest %q not matching expected %q", - out.CompressedDigest, blobDigest) - } - // So, record also information about blobDigest, that might benefit reuse. - // We trust PrepareStagedLayer to validate or create both values correctly. - s.lockProtected.blobDiffIDs[blobDigest] = out.UncompressedDigest - options.Cache.RecordDigestUncompressedPair(out.CompressedDigest, out.UncompressedDigest) - } else { - // Use diffID for layer identity if it is known. - if uncompressedDigest := options.Cache.UncompressedDigestForTOC(out.TOCDigest); uncompressedDigest != "" { - s.lockProtected.indexToDiffID[options.LayerIndex] = uncompressedDigest + if err := func() error { // A scope for defer + defer s.lock.Unlock() + + if out.UncompressedDigest != "" { + s.lockProtected.indexToDiffID[options.LayerIndex] = out.UncompressedDigest + if out.TOCDigest != "" { + options.Cache.RecordTOCUncompressedPair(out.TOCDigest, out.UncompressedDigest) + } + // Don’t set indexToTOCDigest on this path: + // - Using UncompressedDigest allows image reuse with non-partially-pulled layers, so we want to set indexToDiffID. + // - If UncompressedDigest has been computed, that means the layer was read completely, and the TOC has been created from scratch. + // That TOC is quite unlikely to match any other TOC value. + + // The computation of UncompressedDigest means the whole layer has been consumed; while doing that, chunked.GetDiffer is + // responsible for ensuring blobDigest has been validated. + if out.CompressedDigest != blobDigest { + return fmt.Errorf("internal error: PrepareStagedLayer returned CompressedDigest %q not matching expected %q", + out.CompressedDigest, blobDigest) + } + // So, record also information about blobDigest, that might benefit reuse. + // We trust PrepareStagedLayer to validate or create both values correctly. + s.lockProtected.blobDiffIDs[blobDigest] = out.UncompressedDigest + options.Cache.RecordDigestUncompressedPair(out.CompressedDigest, out.UncompressedDigest) + } else { + // Use diffID for layer identity if it is known. + if uncompressedDigest := options.Cache.UncompressedDigestForTOC(out.TOCDigest); uncompressedDigest != "" { + s.lockProtected.indexToDiffID[options.LayerIndex] = uncompressedDigest + } + s.lockProtected.indexToTOCDigest[options.LayerIndex] = out.TOCDigest } - s.lockProtected.indexToTOCDigest[options.LayerIndex] = out.TOCDigest + s.lockProtected.diffOutputs[options.LayerIndex] = out + return nil + }(); err != nil { + return private.UploadedBlob{}, err } - s.lockProtected.diffOutputs[options.LayerIndex] = out - s.lock.Unlock() succeeded = true return private.UploadedBlob{ @@ -975,9 +1009,11 @@ func (s *storageImageDestination) createNewLayer(index int, layerDigest digest.D return nil, fmt.Errorf("internal inconsistency: layer (%d, %q) not found", index, layerDigest) } var trustedOriginalDigest digest.Digest // For storage.LayerOptions + var trustedOriginalSize *int64 if gotFilename { // The code setting .filenames[trusted.blobDigest] is responsible for ensuring that the file contents match trusted.blobDigest. trustedOriginalDigest = trusted.blobDigest + trustedOriginalSize = nil // It’s s.lockProtected.fileSizes[trusted.blobDigest], but we don’t hold the lock now, and the consumer can compute it at trivial cost. } else { // Try to find the layer with contents matching the data we use. var layer *storage.Layer // = nil @@ -1032,22 +1068,36 @@ func (s *storageImageDestination) createNewLayer(index int, layerDigest digest.D if trusted.diffID == "" && layer.UncompressedDigest != "" { trusted.diffID = layer.UncompressedDigest // This data might have been unavailable in tryReusingBlobAsPending, and is only known now. } - // The stream we have is uncompressed, and it matches trusted.diffID (if known). - // - // FIXME? trustedOriginalDigest could be set to trusted.blobDigest if known, to allow more layer reuse. - // But for c/storage to reasonably use it (as a CompressedDigest value), we should also ensure the CompressedSize of the created - // layer is correct, and the API does not currently make it possible (.CompressedSize is set from the input stream). + + // Set the layer’s CompressedDigest/CompressedSize to relevant values if known, to allow more layer reuse. + // But we don’t want to just use the size from the manifest if we never saw the compressed blob, + // so that we don’t propagate mistakes / attacks. // - // We can legitimately set storage.LayerOptions.OriginalDigest to "", - // but that would just result in PutLayer computing the digest of the input stream == trusted.diffID. - // So, instead, set .OriginalDigest to the value we know already, to avoid that digest computation. - trustedOriginalDigest = trusted.diffID + // s.lockProtected.fileSizes[trusted.blobDigest] is not set, otherwise we would have found gotFilename. + // So, check if the layer we found contains that metadata. (If that layer continues to exist, there’s no benefit + // to us propagating the metadata; but that layer could be removed, and in that case propagating the metadata to + // this new layer copy can help.) + if trusted.blobDigest != "" && layer.CompressedDigest == trusted.blobDigest && layer.CompressedSize > 0 { + trustedOriginalDigest = trusted.blobDigest + sizeCopy := layer.CompressedSize + trustedOriginalSize = &sizeCopy + } else { + // The stream we have is uncompressed, and it matches trusted.diffID (if known). + // + // We can legitimately set storage.LayerOptions.OriginalDigest to "", + // but that would just result in PutLayer computing the digest of the input stream == trusted.diffID. + // So, instead, set .OriginalDigest to the value we know already, to avoid that digest computation. + trustedOriginalDigest = trusted.diffID + trustedOriginalSize = nil // Probably layer.UncompressedSize, but the consumer can compute it at trivial cost. + } // Allow using the already-collected layer contents without extracting the layer again. // // This only matches against the uncompressed digest. - // We don’t have the original compressed data here to trivially set filenames[layerDigest]. - // In particular we can’t achieve the correct Layer.CompressedSize value with the current c/storage API. + // If we have trustedOriginalDigest == trusted.blobDigest, we could arrange to reuse the + // same uncompressed stream for future calls of createNewLayer; but for the non-layer blobs (primarily the config), + // we assume that the file at filenames[someDigest] matches someDigest _exactly_; we would need to differentiate + // between “original files” and “possibly uncompressed files”. // Within-image layer reuse is probably very rare, for now we prefer to avoid that complexity. if trusted.diffID != "" { s.lock.Lock() @@ -1067,6 +1117,7 @@ func (s *storageImageDestination) createNewLayer(index int, layerDigest digest.D // TODO: This can take quite some time, and should ideally be cancellable using ctx.Done(). layer, _, err := s.imageRef.transport.store.PutLayer(newLayerID, parentLayer, nil, "", false, &storage.LayerOptions{ OriginalDigest: trustedOriginalDigest, + OriginalSize: trustedOriginalSize, // nil in many cases // This might be "" if trusted.layerIdentifiedByTOC; in that case PutLayer will compute the value from the stream. UncompressedDigest: trusted.diffID, }, file) @@ -1076,52 +1127,110 @@ func (s *storageImageDestination) createNewLayer(index int, layerDigest digest.D return layer, nil } +// uncommittedImageSource allows accessing an image’s metadata (not layers) before it has been committed, +// to allow using image.FromUnparsedImage. +type uncommittedImageSource struct { + srcImpl.Compat + srcImpl.PropertyMethodsInitialize + srcImpl.NoSignatures + srcImpl.DoesNotAffectLayerInfosForCopy + srcStubs.NoGetBlobAtInitialize + + d *storageImageDestination +} + +func newUncommittedImageSource(d *storageImageDestination) *uncommittedImageSource { + s := &uncommittedImageSource{ + PropertyMethodsInitialize: srcImpl.PropertyMethods(srcImpl.Properties{ + HasThreadSafeGetBlob: true, + }), + NoGetBlobAtInitialize: srcStubs.NoGetBlobAt(d.Reference()), + + d: d, + } + s.Compat = srcImpl.AddCompat(s) + return s +} + +func (u *uncommittedImageSource) Reference() types.ImageReference { + return u.d.Reference() +} + +func (u *uncommittedImageSource) Close() error { + return nil +} + +func (u *uncommittedImageSource) GetManifest(ctx context.Context, instanceDigest *digest.Digest) ([]byte, string, error) { + return u.d.manifest, u.d.manifestMIMEType, nil +} + +func (u *uncommittedImageSource) GetBlob(ctx context.Context, info types.BlobInfo, cache types.BlobInfoCache) (io.ReadCloser, int64, error) { + blob, err := u.d.getConfigBlob(info) + if err != nil { + return nil, -1, err + } + return io.NopCloser(bytes.NewReader(blob)), int64(len(blob)), nil +} + // untrustedLayerDiffID returns a DiffID value for layerIndex from the image’s config. // If the value is not yet available (but it can be available after s.manifets is set), it returns ("", nil). // WARNING: We don’t validate the DiffID value against the layer contents; it must not be used for any deduplication. func (s *storageImageDestination) untrustedLayerDiffID(layerIndex int) (digest.Digest, error) { - // At this point, we are either inside the multi-threaded scope of HasThreadSafePutBlob, and - // nothing is writing to s.manifest yet, or PutManifest has been called and s.manifest != nil. + // At this point, we are either inside the multi-threaded scope of HasThreadSafePutBlob, + // nothing is writing to s.manifest yet, and s.untrustedDiffIDValues might have been set + // by NoteOriginalOCIConfig and are not being updated any more; + // or PutManifest has been called and s.manifest != nil. // Either way this function does not need the protection of s.lock. - if s.manifest == nil { - return "", nil - } if s.untrustedDiffIDValues == nil { - mt := manifest.GuessMIMEType(s.manifest) - if mt != imgspecv1.MediaTypeImageManifest { - // We could, in principle, build an ImageSource, support arbitrary image formats using image.FromUnparsedImage, - // and then use types.Image.OCIConfig so that we can parse the image. - // - // In practice, this should, right now, only matter for pulls of OCI images (this code path implies that a layer has annotation), - // while converting to a non-OCI formats, using a manual (skopeo copy) or something similar, not (podman pull). - // So it is not implemented yet. - return "", fmt.Errorf("determining DiffID for manifest type %q is not yet supported", mt) + // Typically, we expect untrustedDiffIDValues to be set by the generic copy code + // via NoteOriginalOCIConfig; this is a compatibility fallback for external callers + // of the public types.ImageDestination. + if s.manifest == nil { + return "", nil } - man, err := manifest.FromBlob(s.manifest, mt) + + ctx := context.Background() // This is all happening in memory, no need to worry about cancellation. + unparsed := image.UnparsedInstance(newUncommittedImageSource(s), nil) + sourced, err := image.FromUnparsedImage(ctx, nil, unparsed) if err != nil { - return "", fmt.Errorf("parsing manifest: %w", err) + return "", fmt.Errorf("parsing image to be committed: %w", err) } - - cb, err := s.getConfigBlob(man.ConfigInfo()) + configOCI, err := sourced.OCIConfig(ctx) if err != nil { - return "", err + return "", fmt.Errorf("obtaining config of image to be committed: %w", err) } - // retrieve the expected uncompressed digest from the config blob. - configOCI := &imgspecv1.Image{} - if err := json.Unmarshal(cb, configOCI); err != nil { - return "", err - } - s.untrustedDiffIDValues = slices.Clone(configOCI.RootFS.DiffIDs) - if s.untrustedDiffIDValues == nil { // Unlikely but possible in theory… - s.untrustedDiffIDValues = []digest.Digest{} - } + s.setUntrustedDiffIDValuesFromOCIConfig(configOCI) } + if layerIndex >= len(s.untrustedDiffIDValues) { return "", fmt.Errorf("image config has only %d DiffID values, but a layer with index %d exists", len(s.untrustedDiffIDValues), layerIndex) } - return s.untrustedDiffIDValues[layerIndex], nil + res := s.untrustedDiffIDValues[layerIndex] + if res == "" { + // In practice, this should, right now, only matter for pulls of OCI images + // (this code path implies that we did a partial pull because a layer has an annotation), + // So, DiffIDs should always be set. + // + // It is, though, reachable by pulling an OCI image while converting to schema1, + // using a manual (skopeo copy) or something similar, not (podman pull). + // + // Our schema1.OCIConfig code produces non-empty DiffID arrays of empty values. + // The current semantics of this function are that ("", nil) means "try again later", + // which is not what we want to happen; for now, turn that into an explicit error. + return "", fmt.Errorf("DiffID value for layer %d is unknown or explicitly empty", layerIndex) + } + return res, nil +} + +// setUntrustedDiffIDValuesFromOCIConfig updates s.untrustedDiffIDvalues from config. +// The caller must ensure s.lock does not need to be held. +func (s *storageImageDestination) setUntrustedDiffIDValuesFromOCIConfig(config *imgspecv1.Image) { + s.untrustedDiffIDValues = slices.Clone(config.RootFS.DiffIDs) + if s.untrustedDiffIDValues == nil { // Unlikely but possible in theory… + s.untrustedDiffIDValues = []digest.Digest{} + } } // CommitWithOptions marks the process of storing the image as successful and asks for the image to be persisted. @@ -1131,7 +1240,7 @@ func (s *storageImageDestination) untrustedLayerDiffID(layerIndex int) (digest.D func (s *storageImageDestination) CommitWithOptions(ctx context.Context, options private.CommitOptions) error { // This function is outside of the scope of HasThreadSafePutBlob, so we don’t need to hold s.lock. - if len(s.manifest) == 0 { + if s.manifest == nil { return errors.New("Internal error: storageImageDestination.CommitWithOptions() called without PutManifest()") } toplevelManifest, _, err := options.UnparsedToplevel.Manifest(ctx) @@ -1159,7 +1268,7 @@ func (s *storageImageDestination) CommitWithOptions(ctx context.Context, options } } // Find the list of layer blobs. - man, err := manifest.FromBlob(s.manifest, manifest.GuessMIMEType(s.manifest)) + man, err := manifest.FromBlob(s.manifest, s.manifestMIMEType) if err != nil { return fmt.Errorf("parsing manifest: %w", err) } @@ -1193,29 +1302,21 @@ func (s *storageImageDestination) CommitWithOptions(ctx context.Context, options imgOptions.CreationDate = *inspect.Created } - // Set up to save the non-layer blobs as data items. Since we only share layers, they should all be in files, so - // we just need to screen out the ones that are actually layers to get the list of non-layers. - dataBlobs := set.New[digest.Digest]() - for blob := range s.lockProtected.filenames { - dataBlobs.Add(blob) - } - for _, layerBlob := range layerBlobs { - dataBlobs.Delete(layerBlob.Digest) - } - for _, blob := range dataBlobs.Values() { - v, err := os.ReadFile(s.lockProtected.filenames[blob]) + // Set up to save the config as a data item. Since we only share layers, the config should be in a file. + if s.lockProtected.configDigest != "" { + v, err := os.ReadFile(s.lockProtected.filenames[s.lockProtected.configDigest]) if err != nil { - return fmt.Errorf("copying non-layer blob %q to image: %w", blob, err) + return fmt.Errorf("copying config blob %q to image: %w", s.lockProtected.configDigest, err) } imgOptions.BigData = append(imgOptions.BigData, storage.ImageBigDataOption{ - Key: blob.String(), + Key: s.lockProtected.configDigest.String(), Data: v, Digest: digest.Canonical.FromBytes(v), }) } // Set up to save the options.UnparsedToplevel's manifest if it differs from // the per-platform one, which is saved below. - if len(toplevelManifest) != 0 && !bytes.Equal(toplevelManifest, s.manifest) { + if !bytes.Equal(toplevelManifest, s.manifest) { manifestDigest, err := manifest.Digest(toplevelManifest) if err != nil { return fmt.Errorf("digesting top-level manifest: %w", err) @@ -1370,6 +1471,10 @@ func (s *storageImageDestination) PutManifest(ctx context.Context, manifestBlob return err } s.manifest = bytes.Clone(manifestBlob) + if s.manifest == nil { // Make sure PutManifest can never succeed with s.manifest == nil + s.manifest = []byte{} + } + s.manifestMIMEType = manifest.GuessMIMEType(s.manifest) s.manifestDigest = digest return nil } @@ -1392,7 +1497,7 @@ func (s *storageImageDestination) PutSignaturesWithFormat(ctx context.Context, s if instanceDigest == nil { s.signatures = sigblob s.metadata.SignatureSizes = sizes - if len(s.manifest) > 0 { + if s.manifest != nil { manifestDigest := s.manifestDigest instanceDigest = &manifestDigest } diff --git a/vendor/github.com/containers/image/v5/storage/storage_reference.go b/vendor/github.com/containers/image/v5/storage/storage_reference.go index acc4cb30e8..5775c4acbc 100644 --- a/vendor/github.com/containers/image/v5/storage/storage_reference.go +++ b/vendor/github.com/containers/image/v5/storage/storage_reference.go @@ -153,7 +153,9 @@ func (s *storageReference) resolveImage(sys *types.SystemContext) (*storage.Imag } if s.id == "" { logrus.Debugf("reference %q does not resolve to an image ID", s.StringWithinTransport()) - return nil, fmt.Errorf("reference %q does not resolve to an image ID: %w", s.StringWithinTransport(), ErrNoSuchImage) + // %.0w makes the error visible to error.Unwrap() without including any text. + // ErrNoSuchImage ultimately is “identifier is not an image”, which is not helpful for identifying the root cause. + return nil, fmt.Errorf("reference %q does not resolve to an image ID%.0w", s.StringWithinTransport(), ErrNoSuchImage) } if loadedImage == nil { img, err := s.transport.store.Image(s.id) diff --git a/vendor/github.com/containers/image/v5/storage/storage_src.go b/vendor/github.com/containers/image/v5/storage/storage_src.go index 55788f8877..ff76b20667 100644 --- a/vendor/github.com/containers/image/v5/storage/storage_src.go +++ b/vendor/github.com/containers/image/v5/storage/storage_src.go @@ -35,13 +35,14 @@ type storageImageSource struct { impl.PropertyMethodsInitialize stubs.NoGetBlobAtInitialize - imageRef storageReference - image *storage.Image - systemContext *types.SystemContext // SystemContext used in GetBlob() to create temporary files - metadata storageImageMetadata - cachedManifest []byte // A cached copy of the manifest, if already known, or nil - getBlobMutex sync.Mutex // Mutex to sync state for parallel GetBlob executions - getBlobMutexProtected getBlobMutexProtected + imageRef storageReference + image *storage.Image + systemContext *types.SystemContext // SystemContext used in GetBlob() to create temporary files + metadata storageImageMetadata + cachedManifest []byte // A cached copy of the manifest, if already known, or nil + cachedManifestMIMEType string // Valid if cachedManifest != nil + getBlobMutex sync.Mutex // Mutex to sync state for parallel GetBlob executions + getBlobMutexProtected getBlobMutexProtected } // getBlobMutexProtected contains storageImageSource data protected by getBlobMutex. @@ -247,7 +248,7 @@ func (s *storageImageSource) GetManifest(ctx context.Context, instanceDigest *di } return blob, manifest.GuessMIMEType(blob), err } - if len(s.cachedManifest) == 0 { + if s.cachedManifest == nil { // The manifest is stored as a big data item. // Prefer the manifest corresponding to the user-specified digest, if available. if s.imageRef.named != nil { @@ -267,15 +268,16 @@ func (s *storageImageSource) GetManifest(ctx context.Context, instanceDigest *di } // If the user did not specify a digest, or this is an old image stored before manifestBigDataKey was introduced, use the default manifest. // Note that the manifest may not match the expected digest, and that is likely to fail eventually, e.g. in c/image/image/UnparsedImage.Manifest(). - if len(s.cachedManifest) == 0 { + if s.cachedManifest == nil { cachedBlob, err := s.imageRef.transport.store.ImageBigData(s.image.ID, storage.ImageDigestBigDataKey) if err != nil { return nil, "", err } s.cachedManifest = cachedBlob } + s.cachedManifestMIMEType = manifest.GuessMIMEType(s.cachedManifest) } - return s.cachedManifest, manifest.GuessMIMEType(s.cachedManifest), err + return s.cachedManifest, s.cachedManifestMIMEType, err } // LayerInfosForCopy() returns the list of layer blobs that make up the root filesystem of diff --git a/vendor/github.com/containers/image/v5/version/version.go b/vendor/github.com/containers/image/v5/version/version.go index 3743721fc3..7a16c8181e 100644 --- a/vendor/github.com/containers/image/v5/version/version.go +++ b/vendor/github.com/containers/image/v5/version/version.go @@ -6,12 +6,12 @@ const ( // VersionMajor is for an API incompatible changes VersionMajor = 5 // VersionMinor is for functionality in a backwards-compatible manner - VersionMinor = 33 + VersionMinor = 34 // VersionPatch is for backwards-compatible bug fixes VersionPatch = 0 // VersionDev indicates development branch. Releases will be empty string. - VersionDev = "" + VersionDev = "-dev" ) // Version is the specification version that the package types support. diff --git a/vendor/github.com/containers/storage/.cirrus.yml b/vendor/github.com/containers/storage/.cirrus.yml index 0fed08c3b2..344b9a992d 100644 --- a/vendor/github.com/containers/storage/.cirrus.yml +++ b/vendor/github.com/containers/storage/.cirrus.yml @@ -17,13 +17,13 @@ env: #### #### Cache-image names to test with (double-quotes around names are critical) ### - FEDORA_NAME: "fedora-39" + FEDORA_NAME: "fedora-41" DEBIAN_NAME: "debian-13" # GCE project where images live IMAGE_PROJECT: "libpod-218412" # VM Image built in containers/automation_images - IMAGE_SUFFIX: "c20241010t105554z-f40f39d13" + IMAGE_SUFFIX: "c20241107t210000z-f41f40d13" FEDORA_CACHE_IMAGE_NAME: "fedora-${IMAGE_SUFFIX}" DEBIAN_CACHE_IMAGE_NAME: "debian-${IMAGE_SUFFIX}" diff --git a/vendor/github.com/containers/storage/Makefile b/vendor/github.com/containers/storage/Makefile index 64c01b67f8..090c9c2b76 100644 --- a/vendor/github.com/containers/storage/Makefile +++ b/vendor/github.com/containers/storage/Makefile @@ -35,7 +35,7 @@ TESTFLAGS := $(shell $(GO) test -race $(BUILDFLAGS) ./pkg/stringutils 2>&1 > /de # N/B: This value is managed by Renovate, manual changes are # possible, as long as they don't disturb the formatting # (i.e. DO NOT ADD A 'v' prefix!) -GOLANGCI_LINT_VERSION := 1.61.0 +GOLANGCI_LINT_VERSION := 1.62.2 default all: local-binary docs local-validate local-cross ## validate all checks, build and cross-build\nbinaries and docs diff --git a/vendor/github.com/containers/storage/VERSION b/vendor/github.com/containers/storage/VERSION index 3ebf789f5a..a38254646f 100644 --- a/vendor/github.com/containers/storage/VERSION +++ b/vendor/github.com/containers/storage/VERSION @@ -1 +1 @@ -1.56.0 +1.57.0-dev diff --git a/vendor/github.com/containers/storage/check.go b/vendor/github.com/containers/storage/check.go index 396648e7fa..e8837ff953 100644 --- a/vendor/github.com/containers/storage/check.go +++ b/vendor/github.com/containers/storage/check.go @@ -80,7 +80,7 @@ type CheckOptions struct { // layer to the contents that we'd expect it to have to ignore certain // discrepancies type checkIgnore struct { - ownership, timestamps, permissions bool + ownership, timestamps, permissions, filetype bool } // CheckMost returns a CheckOptions with mostly just "quick" checks enabled. @@ -139,8 +139,10 @@ func (s *store) Check(options *CheckOptions) (CheckReport, error) { if strings.Contains(o, "ignore_chown_errors=true") { ignore.ownership = true } - if strings.HasPrefix(o, "force_mask=") { + if strings.Contains(o, "force_mask=") { + ignore.ownership = true ignore.permissions = true + ignore.filetype = true } } for o := range s.pullOptions { @@ -833,7 +835,7 @@ func (s *store) Repair(report CheckReport, options *RepairOptions) []error { // compareFileInfo returns a string summarizing what's different between the two checkFileInfos func compareFileInfo(a, b checkFileInfo, idmap *idtools.IDMappings, ignore checkIgnore) string { var comparison []string - if a.typeflag != b.typeflag { + if a.typeflag != b.typeflag && !ignore.filetype { comparison = append(comparison, fmt.Sprintf("filetype:%v→%v", a.typeflag, b.typeflag)) } if idmap != nil && !idmap.Empty() { diff --git a/vendor/github.com/containers/storage/drivers/aufs/aufs.go b/vendor/github.com/containers/storage/drivers/aufs/aufs.go index a8312811b7..d039342637 100644 --- a/vendor/github.com/containers/storage/drivers/aufs/aufs.go +++ b/vendor/github.com/containers/storage/drivers/aufs/aufs.go @@ -776,3 +776,8 @@ func (a *Driver) UpdateLayerIDMap(id string, toContainer, toHost *idtools.IDMapp func (a *Driver) SupportsShifting() bool { return false } + +// Dedup performs deduplication of the driver's storage. +func (d *Driver) Dedup(req graphdriver.DedupArgs) (graphdriver.DedupResult, error) { + return graphdriver.DedupResult{}, nil +} diff --git a/vendor/github.com/containers/storage/drivers/btrfs/btrfs.go b/vendor/github.com/containers/storage/drivers/btrfs/btrfs.go index c7f37d97e0..4a80339f40 100644 --- a/vendor/github.com/containers/storage/drivers/btrfs/btrfs.go +++ b/vendor/github.com/containers/storage/drivers/btrfs/btrfs.go @@ -673,3 +673,8 @@ func (d *Driver) ListLayers() ([]string, error) { func (d *Driver) AdditionalImageStores() []string { return nil } + +// Dedup performs deduplication of the driver's storage. +func (d *Driver) Dedup(req graphdriver.DedupArgs) (graphdriver.DedupResult, error) { + return graphdriver.DedupResult{}, nil +} diff --git a/vendor/github.com/containers/storage/drivers/chown_darwin.go b/vendor/github.com/containers/storage/drivers/chown_darwin.go index 882fa04bd0..4f27502073 100644 --- a/vendor/github.com/containers/storage/drivers/chown_darwin.go +++ b/vendor/github.com/containers/storage/drivers/chown_darwin.go @@ -83,7 +83,7 @@ func (c *platformChowner) LChown(path string, info os.FileInfo, toHost, toContai } if uid != int(st.Uid) || gid != int(st.Gid) { capability, err := system.Lgetxattr(path, "security.capability") - if err != nil && !errors.Is(err, system.EOPNOTSUPP) && err != system.ErrNotSupportedPlatform { + if err != nil && !errors.Is(err, system.ENOTSUP) && err != system.ErrNotSupportedPlatform { return fmt.Errorf("%s: %w", os.Args[0], err) } diff --git a/vendor/github.com/containers/storage/drivers/chown_unix.go b/vendor/github.com/containers/storage/drivers/chown_unix.go index 808f4fea66..b0c25cd992 100644 --- a/vendor/github.com/containers/storage/drivers/chown_unix.go +++ b/vendor/github.com/containers/storage/drivers/chown_unix.go @@ -101,7 +101,7 @@ func (c *platformChowner) LChown(path string, info os.FileInfo, toHost, toContai } if uid != int(st.Uid) || gid != int(st.Gid) { cap, err := system.Lgetxattr(path, "security.capability") - if err != nil && !errors.Is(err, system.EOPNOTSUPP) && !errors.Is(err, system.EOVERFLOW) && err != system.ErrNotSupportedPlatform { + if err != nil && !errors.Is(err, system.ENOTSUP) && !errors.Is(err, system.EOVERFLOW) && err != system.ErrNotSupportedPlatform { return fmt.Errorf("%s: %w", os.Args[0], err) } diff --git a/vendor/github.com/containers/storage/drivers/copy/copy_linux.go b/vendor/github.com/containers/storage/drivers/copy/copy_linux.go index 8c0bbed618..93fc0a3261 100644 --- a/vendor/github.com/containers/storage/drivers/copy/copy_linux.go +++ b/vendor/github.com/containers/storage/drivers/copy/copy_linux.go @@ -106,7 +106,7 @@ func legacyCopy(srcFile io.Reader, dstFile io.Writer) error { func copyXattr(srcPath, dstPath, attr string) error { data, err := system.Lgetxattr(srcPath, attr) - if err != nil && !errors.Is(err, unix.EOPNOTSUPP) { + if err != nil && !errors.Is(err, system.ENOTSUP) { return err } if data != nil { @@ -279,7 +279,7 @@ func doCopyXattrs(srcPath, dstPath string) error { } xattrs, err := system.Llistxattr(srcPath) - if err != nil && !errors.Is(err, unix.EOPNOTSUPP) { + if err != nil && !errors.Is(err, system.ENOTSUP) { return err } diff --git a/vendor/github.com/containers/storage/drivers/driver.go b/vendor/github.com/containers/storage/drivers/driver.go index dc9b12c3ca..ed0e628883 100644 --- a/vendor/github.com/containers/storage/drivers/driver.go +++ b/vendor/github.com/containers/storage/drivers/driver.go @@ -8,6 +8,7 @@ import ( "path/filepath" "strings" + "github.com/containers/storage/internal/dedup" "github.com/containers/storage/pkg/archive" "github.com/containers/storage/pkg/directory" "github.com/containers/storage/pkg/fileutils" @@ -81,6 +82,23 @@ type ApplyDiffWithDifferOpts struct { Flags map[string]interface{} } +// DedupArgs contains the information to perform storage deduplication. +type DedupArgs struct { + // Layers is the list of layers to deduplicate. + Layers []string + + // Options that are passed directly to the pkg/dedup.DedupDirs function. + Options dedup.DedupOptions +} + +// DedupResult contains the result of the Dedup() call. +type DedupResult struct { + // Deduped represents the total number of bytes saved by deduplication. + // This value accounts also for all previously deduplicated data, not only the savings + // from the last run. + Deduped uint64 +} + // InitFunc initializes the storage driver. type InitFunc func(homedir string, options Options) (Driver, error) @@ -139,6 +157,8 @@ type ProtoDriver interface { // AdditionalImageStores returns additional image stores supported by the driver // This API is experimental and can be changed without bumping the major version number. AdditionalImageStores() []string + // Dedup performs deduplication of the driver's storage. + Dedup(DedupArgs) (DedupResult, error) } // DiffDriver is the interface to use to implement graph diffs diff --git a/vendor/github.com/containers/storage/drivers/overlay/check_116.go b/vendor/github.com/containers/storage/drivers/overlay/check_116.go index 7867d50063..5cbf5e1cef 100644 --- a/vendor/github.com/containers/storage/drivers/overlay/check_116.go +++ b/vendor/github.com/containers/storage/drivers/overlay/check_116.go @@ -10,7 +10,6 @@ import ( "github.com/containers/storage/pkg/archive" "github.com/containers/storage/pkg/system" - "golang.org/x/sys/unix" ) func scanForMountProgramIndicators(home string) (detected bool, err error) { @@ -28,7 +27,7 @@ func scanForMountProgramIndicators(home string) (detected bool, err error) { } if d.IsDir() { xattrs, err := system.Llistxattr(path) - if err != nil && !errors.Is(err, unix.EOPNOTSUPP) { + if err != nil && !errors.Is(err, system.ENOTSUP) { return err } for _, xattr := range xattrs { diff --git a/vendor/github.com/containers/storage/drivers/overlay/overlay_nocgo.go b/vendor/github.com/containers/storage/drivers/overlay/composefs_nocgo.go similarity index 100% rename from vendor/github.com/containers/storage/drivers/overlay/overlay_nocgo.go rename to vendor/github.com/containers/storage/drivers/overlay/composefs_nocgo.go diff --git a/vendor/github.com/containers/storage/drivers/overlay/overlay.go b/vendor/github.com/containers/storage/drivers/overlay/overlay.go index caf896463b..56278805f8 100644 --- a/vendor/github.com/containers/storage/drivers/overlay/overlay.go +++ b/vendor/github.com/containers/storage/drivers/overlay/overlay.go @@ -22,6 +22,7 @@ import ( graphdriver "github.com/containers/storage/drivers" "github.com/containers/storage/drivers/overlayutils" "github.com/containers/storage/drivers/quota" + "github.com/containers/storage/internal/dedup" "github.com/containers/storage/pkg/archive" "github.com/containers/storage/pkg/chrootarchive" "github.com/containers/storage/pkg/directory" @@ -1096,6 +1097,7 @@ func (d *Driver) create(id, parent string, opts *graphdriver.CreateOpts, readOnl } if d.options.forceMask != nil { + st.Mode |= os.ModeDir if err := idtools.SetContainersOverrideXattr(diff, st); err != nil { return err } @@ -2740,3 +2742,22 @@ func getMappedMountRoot(path string) string { } return dirName } + +// Dedup performs deduplication of the driver's storage. +func (d *Driver) Dedup(req graphdriver.DedupArgs) (graphdriver.DedupResult, error) { + var dirs []string + for _, layer := range req.Layers { + dir, _, inAdditionalStore := d.dir2(layer, false) + if inAdditionalStore { + continue + } + if err := fileutils.Exists(dir); err == nil { + dirs = append(dirs, filepath.Join(dir, "diff")) + } + } + r, err := dedup.DedupDirs(dirs, req.Options) + if err != nil { + return graphdriver.DedupResult{}, err + } + return graphdriver.DedupResult{Deduped: r.Deduped}, nil +} diff --git a/vendor/github.com/containers/storage/drivers/register/register_overlay.go b/vendor/github.com/containers/storage/drivers/register/register_overlay.go index 6e82a1ea38..c4f9628225 100644 --- a/vendor/github.com/containers/storage/drivers/register/register_overlay.go +++ b/vendor/github.com/containers/storage/drivers/register/register_overlay.go @@ -1,4 +1,4 @@ -//go:build !exclude_graphdriver_overlay && linux && cgo +//go:build !exclude_graphdriver_overlay && linux package register diff --git a/vendor/github.com/containers/storage/drivers/vfs/driver.go b/vendor/github.com/containers/storage/drivers/vfs/driver.go index f60ec17b3f..98dc55b0e3 100644 --- a/vendor/github.com/containers/storage/drivers/vfs/driver.go +++ b/vendor/github.com/containers/storage/drivers/vfs/driver.go @@ -10,6 +10,7 @@ import ( "strings" graphdriver "github.com/containers/storage/drivers" + "github.com/containers/storage/internal/dedup" "github.com/containers/storage/pkg/archive" "github.com/containers/storage/pkg/directory" "github.com/containers/storage/pkg/fileutils" @@ -348,3 +349,19 @@ func (d *Driver) Diff(id string, idMappings *idtools.IDMappings, parent string, func (d *Driver) DiffSize(id string, idMappings *idtools.IDMappings, parent string, parentMappings *idtools.IDMappings, mountLabel string) (size int64, err error) { return d.naiveDiff.DiffSize(id, idMappings, parent, parentMappings, mountLabel) } + +// Dedup performs deduplication of the driver's storage. +func (d *Driver) Dedup(req graphdriver.DedupArgs) (graphdriver.DedupResult, error) { + var dirs []string + for _, layer := range req.Layers { + dir := d.dir2(layer, false) + if err := fileutils.Exists(dir); err == nil { + dirs = append(dirs, dir) + } + } + r, err := dedup.DedupDirs(dirs, req.Options) + if err != nil { + return graphdriver.DedupResult{}, err + } + return graphdriver.DedupResult{Deduped: r.Deduped}, nil +} diff --git a/vendor/github.com/containers/storage/drivers/windows/windows.go b/vendor/github.com/containers/storage/drivers/windows/windows.go index d38e74534e..59ed9a7567 100644 --- a/vendor/github.com/containers/storage/drivers/windows/windows.go +++ b/vendor/github.com/containers/storage/drivers/windows/windows.go @@ -975,6 +975,11 @@ func (d *Driver) AdditionalImageStores() []string { return nil } +// Dedup performs deduplication of the driver's storage. +func (d *Driver) Dedup(req graphdriver.DedupArgs) (graphdriver.DedupResult, error) { + return graphdriver.DedupResult{}, nil +} + // UpdateLayerIDMap changes ownerships in the layer's filesystem tree from // matching those in toContainer to matching those in toHost. func (d *Driver) UpdateLayerIDMap(id string, toContainer, toHost *idtools.IDMappings, mountLabel string) error { diff --git a/vendor/github.com/containers/storage/drivers/zfs/zfs.go b/vendor/github.com/containers/storage/drivers/zfs/zfs.go index ef26c6c7f0..f53b0e1b61 100644 --- a/vendor/github.com/containers/storage/drivers/zfs/zfs.go +++ b/vendor/github.com/containers/storage/drivers/zfs/zfs.go @@ -511,3 +511,8 @@ func (d *Driver) ListLayers() ([]string, error) { func (d *Driver) AdditionalImageStores() []string { return nil } + +// Dedup performs deduplication of the driver's storage. +func (d *Driver) Dedup(req graphdriver.DedupArgs) (graphdriver.DedupResult, error) { + return graphdriver.DedupResult{}, nil +} diff --git a/vendor/github.com/containers/storage/internal/dedup/dedup.go b/vendor/github.com/containers/storage/internal/dedup/dedup.go new file mode 100644 index 0000000000..59fcd0d23a --- /dev/null +++ b/vendor/github.com/containers/storage/internal/dedup/dedup.go @@ -0,0 +1,163 @@ +package dedup + +import ( + "crypto/sha256" + "encoding/binary" + "errors" + "fmt" + "hash/crc64" + "io/fs" + "sync" + + "github.com/opencontainers/selinux/pkg/pwalkdir" + "github.com/sirupsen/logrus" +) + +var notSupported = errors.New("reflinks are not supported on this platform") + +const ( + DedupHashInvalid DedupHashMethod = iota + DedupHashCRC + DedupHashFileSize + DedupHashSHA256 +) + +type DedupHashMethod int + +type DedupOptions struct { + // HashMethod is the hash function to use to find identical files + HashMethod DedupHashMethod +} + +type DedupResult struct { + // Deduped represents the total number of bytes saved by deduplication. + // This value accounts also for all previously deduplicated data, not only the savings + // from the last run. + Deduped uint64 +} + +func getFileChecksum(hashMethod DedupHashMethod, path string, info fs.FileInfo) (string, error) { + switch hashMethod { + case DedupHashInvalid: + return "", fmt.Errorf("invalid hash method: %v", hashMethod) + case DedupHashFileSize: + return fmt.Sprintf("%v", info.Size()), nil + case DedupHashSHA256: + return readAllFile(path, info, func(buf []byte) (string, error) { + h := sha256.New() + if _, err := h.Write(buf); err != nil { + return "", err + } + return string(h.Sum(nil)), nil + }) + case DedupHashCRC: + return readAllFile(path, info, func(buf []byte) (string, error) { + c := crc64.New(crc64.MakeTable(crc64.ECMA)) + if _, err := c.Write(buf); err != nil { + return "", err + } + bufRet := make([]byte, 8) + binary.BigEndian.PutUint64(bufRet, c.Sum64()) + return string(bufRet), nil + }) + default: + return "", fmt.Errorf("unknown hash method: %v", hashMethod) + } +} + +type pathsLocked struct { + paths []string + lock sync.Mutex +} + +func DedupDirs(dirs []string, options DedupOptions) (DedupResult, error) { + res := DedupResult{} + hashToPaths := make(map[string]*pathsLocked) + lock := sync.Mutex{} // protects `hashToPaths` and `res` + + dedup, err := newDedupFiles() + if err != nil { + return res, err + } + + for _, dir := range dirs { + logrus.Debugf("Deduping directory %s", dir) + if err := pwalkdir.Walk(dir, func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + if !d.Type().IsRegular() { + return nil + } + info, err := d.Info() + if err != nil { + return err + } + size := uint64(info.Size()) + if size == 0 { + // do not bother with empty files + return nil + } + + // the file was already deduplicated + if visited, err := dedup.isFirstVisitOf(info); err != nil { + return err + } else if visited { + return nil + } + + h, err := getFileChecksum(options.HashMethod, path, info) + if err != nil { + return err + } + + lock.Lock() + item, foundItem := hashToPaths[h] + if !foundItem { + item = &pathsLocked{paths: []string{path}} + hashToPaths[h] = item + lock.Unlock() + return nil + } + item.lock.Lock() + lock.Unlock() + + dedupBytes, err := func() (uint64, error) { // function to have a scope for the defer statement + defer item.lock.Unlock() + + var dedupBytes uint64 + for _, src := range item.paths { + deduped, err := dedup.dedup(src, path, info) + if err == nil && deduped > 0 { + logrus.Debugf("Deduped %q -> %q (%d bytes)", src, path, deduped) + dedupBytes += deduped + break + } + logrus.Debugf("Failed to deduplicate: %v", err) + if errors.Is(err, notSupported) { + return dedupBytes, err + } + } + if dedupBytes == 0 { + item.paths = append(item.paths, path) + } + return dedupBytes, nil + }() + if err != nil { + return err + } + + lock.Lock() + res.Deduped += dedupBytes + lock.Unlock() + return nil + }); err != nil { + // if reflinks are not supported, return immediately without errors + if errors.Is(err, notSupported) { + return res, nil + } + return res, err + } + } + return res, nil +} diff --git a/vendor/github.com/containers/storage/internal/dedup/dedup_linux.go b/vendor/github.com/containers/storage/internal/dedup/dedup_linux.go new file mode 100644 index 0000000000..90ccb5f318 --- /dev/null +++ b/vendor/github.com/containers/storage/internal/dedup/dedup_linux.go @@ -0,0 +1,139 @@ +package dedup + +import ( + "errors" + "fmt" + "io" + "io/fs" + "os" + "sync" + "syscall" + + "golang.org/x/sys/unix" +) + +type deviceInodePair struct { + dev uint64 + ino uint64 +} + +type dedupFiles struct { + lock sync.Mutex + visitedInodes map[deviceInodePair]struct{} +} + +func newDedupFiles() (*dedupFiles, error) { + return &dedupFiles{ + visitedInodes: make(map[deviceInodePair]struct{}), + }, nil +} + +func (d *dedupFiles) recordInode(dev, ino uint64) (bool, error) { + d.lock.Lock() + defer d.lock.Unlock() + + di := deviceInodePair{ + dev: dev, + ino: ino, + } + + _, visited := d.visitedInodes[di] + d.visitedInodes[di] = struct{}{} + return visited, nil +} + +// isFirstVisitOf records that the file is being processed. Returns true if the file was already visited. +func (d *dedupFiles) isFirstVisitOf(fi fs.FileInfo) (bool, error) { + st, ok := fi.Sys().(*syscall.Stat_t) + if !ok { + return false, fmt.Errorf("unable to get raw syscall.Stat_t data") + } + return d.recordInode(uint64(st.Dev), st.Ino) +} + +// dedup deduplicates the file at src path to dst path +func (d *dedupFiles) dedup(src, dst string, fiDst fs.FileInfo) (uint64, error) { + srcFile, err := os.OpenFile(src, os.O_RDONLY, 0) + if err != nil { + return 0, fmt.Errorf("failed to open source file: %w", err) + } + defer srcFile.Close() + + dstFile, err := os.OpenFile(dst, os.O_WRONLY, 0) + if err != nil { + return 0, fmt.Errorf("failed to open destination file: %w", err) + } + defer dstFile.Close() + + stSrc, err := srcFile.Stat() + if err != nil { + return 0, fmt.Errorf("failed to stat source file: %w", err) + } + sSrc, ok := stSrc.Sys().(*syscall.Stat_t) + if !ok { + return 0, fmt.Errorf("unable to get raw syscall.Stat_t data") + } + sDest, ok := fiDst.Sys().(*syscall.Stat_t) + if !ok { + return 0, fmt.Errorf("unable to get raw syscall.Stat_t data") + } + if sSrc.Dev == sDest.Dev && sSrc.Ino == sDest.Ino { + // same inode, we are dealing with a hard link, no need to deduplicate + return 0, nil + } + + value := unix.FileDedupeRange{ + Src_offset: 0, + Src_length: uint64(stSrc.Size()), + Info: []unix.FileDedupeRangeInfo{ + { + Dest_fd: int64(dstFile.Fd()), + Dest_offset: 0, + }, + }, + } + err = unix.IoctlFileDedupeRange(int(srcFile.Fd()), &value) + if err == nil { + return uint64(value.Info[0].Bytes_deduped), nil + } + + if errors.Is(err, unix.ENOTSUP) { + return 0, notSupported + } + return 0, fmt.Errorf("failed to clone file %q: %w", src, err) +} + +func readAllFile(path string, info fs.FileInfo, fn func([]byte) (string, error)) (string, error) { + size := info.Size() + if size == 0 { + return fn(nil) + } + + file, err := os.Open(path) + if err != nil { + return "", err + } + defer file.Close() + + if size < 4096 { + // small file, read it all + data := make([]byte, size) + _, err = io.ReadFull(file, data) + if err != nil { + return "", err + } + return fn(data) + } + + mmap, err := unix.Mmap(int(file.Fd()), 0, int(size), unix.PROT_READ, unix.MAP_PRIVATE) + if err != nil { + return "", fmt.Errorf("failed to mmap file: %w", err) + } + defer func() { + _ = unix.Munmap(mmap) + }() + + _ = unix.Madvise(mmap, unix.MADV_SEQUENTIAL) + + return fn(mmap) +} diff --git a/vendor/github.com/containers/storage/internal/dedup/dedup_unsupported.go b/vendor/github.com/containers/storage/internal/dedup/dedup_unsupported.go new file mode 100644 index 0000000000..cfadf83268 --- /dev/null +++ b/vendor/github.com/containers/storage/internal/dedup/dedup_unsupported.go @@ -0,0 +1,27 @@ +//go:build !linux + +package dedup + +import ( + "io/fs" +) + +type dedupFiles struct{} + +func newDedupFiles() (*dedupFiles, error) { + return nil, notSupported +} + +// isFirstVisitOf records that the file is being processed. Returns true if the file was already visited. +func (d *dedupFiles) isFirstVisitOf(fi fs.FileInfo) (bool, error) { + return false, notSupported +} + +// dedup deduplicates the file at src path to dst path +func (d *dedupFiles) dedup(src, dst string, fiDst fs.FileInfo) (uint64, error) { + return 0, notSupported +} + +func readAllFile(path string, info fs.FileInfo, fn func([]byte) (string, error)) (string, error) { + return "", notSupported +} diff --git a/vendor/github.com/containers/storage/layers.go b/vendor/github.com/containers/storage/layers.go index 6fe1a08035..1f8203fbfc 100644 --- a/vendor/github.com/containers/storage/layers.go +++ b/vendor/github.com/containers/storage/layers.go @@ -336,6 +336,9 @@ type rwLayerStore interface { // Clean up unreferenced layers GarbageCollect() error + + // Dedup deduplicates layers in the store. + dedup(drivers.DedupArgs) (drivers.DedupResult, error) } type multipleLockFile struct { @@ -913,23 +916,32 @@ func (r *layerStore) load(lockedForWriting bool) (bool, error) { // user of this storage area marked for deletion but didn't manage to // actually delete. var incompleteDeletionErrors error // = nil + var layersToDelete []*Layer for _, layer := range r.layers { if layer.Flags == nil { layer.Flags = make(map[string]interface{}) } if layerHasIncompleteFlag(layer) { - logrus.Warnf("Found incomplete layer %#v, deleting it", layer.ID) - err := r.deleteInternal(layer.ID) - if err != nil { - // Don't return the error immediately, because deleteInternal does not saveLayers(); - // Even if deleting one incomplete layer fails, call saveLayers() so that other possible successfully - // deleted incomplete layers have their metadata correctly removed. - incompleteDeletionErrors = multierror.Append(incompleteDeletionErrors, - fmt.Errorf("deleting layer %#v: %w", layer.ID, err)) - } - modifiedLocations |= layerLocation(layer) + // Important: Do not call r.deleteInternal() here. It modifies r.layers + // which causes unexpected side effects while iterating over r.layers here. + // The range loop has no idea that the underlying elements where shifted + // around. + layersToDelete = append(layersToDelete, layer) } } + // Now actually delete the layers + for _, layer := range layersToDelete { + logrus.Warnf("Found incomplete layer %q, deleting it", layer.ID) + err := r.deleteInternal(layer.ID) + if err != nil { + // Don't return the error immediately, because deleteInternal does not saveLayers(); + // Even if deleting one incomplete layer fails, call saveLayers() so that other possible successfully + // deleted incomplete layers have their metadata correctly removed. + incompleteDeletionErrors = multierror.Append(incompleteDeletionErrors, + fmt.Errorf("deleting layer %#v: %w", layer.ID, err)) + } + modifiedLocations |= layerLocation(layer) + } if err := r.saveLayers(modifiedLocations); err != nil { return false, err } @@ -2592,6 +2604,11 @@ func (r *layerStore) LayersByTOCDigest(d digest.Digest) ([]Layer, error) { return r.layersByDigestMap(r.bytocsum, d) } +// Requires startWriting. +func (r *layerStore) dedup(req drivers.DedupArgs) (drivers.DedupResult, error) { + return r.driver.Dedup(req) +} + func closeAll(closes ...func() error) (rErr error) { for _, f := range closes { if err := f(); err != nil { diff --git a/vendor/github.com/containers/storage/pkg/archive/archive.go b/vendor/github.com/containers/storage/pkg/archive/archive.go index 13a458956c..f17a4651fc 100644 --- a/vendor/github.com/containers/storage/pkg/archive/archive.go +++ b/vendor/github.com/containers/storage/pkg/archive/archive.go @@ -427,7 +427,7 @@ func readSecurityXattrToTarHeader(path string, hdr *tar.Header) error { } for _, xattr := range []string{"security.capability", "security.ima"} { capability, err := system.Lgetxattr(path, xattr) - if err != nil && !errors.Is(err, system.EOPNOTSUPP) && err != system.ErrNotSupportedPlatform { + if err != nil && !errors.Is(err, system.ENOTSUP) && err != system.ErrNotSupportedPlatform { return fmt.Errorf("failed to read %q attribute from %q: %w", xattr, path, err) } if capability != nil { @@ -440,7 +440,7 @@ func readSecurityXattrToTarHeader(path string, hdr *tar.Header) error { // readUserXattrToTarHeader reads user.* xattr from filesystem to a tar header func readUserXattrToTarHeader(path string, hdr *tar.Header) error { xattrs, err := system.Llistxattr(path) - if err != nil && !errors.Is(err, system.EOPNOTSUPP) && err != system.ErrNotSupportedPlatform { + if err != nil && !errors.Is(err, system.ENOTSUP) && err != system.ErrNotSupportedPlatform { return err } for _, key := range xattrs { @@ -655,12 +655,20 @@ func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, L // so use hdrInfo.Mode() (they differ for e.g. setuid bits) hdrInfo := hdr.FileInfo() + typeFlag := hdr.Typeflag mask := hdrInfo.Mode() + + // update also the implementation of ForceMask in pkg/chunked if forceMask != nil { mask = *forceMask + // If we have a forceMask, force the real type to either be a directory, + // a link, or a regular file. + if typeFlag != tar.TypeDir && typeFlag != tar.TypeSymlink && typeFlag != tar.TypeLink { + typeFlag = tar.TypeReg + } } - switch hdr.Typeflag { + switch typeFlag { case tar.TypeDir: // Create directory unless it exists as a directory already. // In that case we just want to merge the two @@ -728,16 +736,6 @@ func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, L return fmt.Errorf("unhandled tar header type %d", hdr.Typeflag) } - if forceMask != nil && (hdr.Typeflag != tar.TypeSymlink || runtime.GOOS == "darwin") { - value := idtools.Stat{ - IDs: idtools.IDPair{UID: hdr.Uid, GID: hdr.Gid}, - Mode: hdrInfo.Mode() & 0o7777, - } - if err := idtools.SetContainersOverrideXattr(path, value); err != nil { - return err - } - } - // Lchown is not supported on Windows. if Lchown && runtime.GOOS != windows { if chownOpts == nil { @@ -793,18 +791,30 @@ func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, L continue } if err := system.Lsetxattr(path, xattrKey, []byte(value), 0); err != nil { - if errors.Is(err, syscall.ENOTSUP) || (inUserns && errors.Is(err, syscall.EPERM)) { - // We ignore errors here because not all graphdrivers support - // xattrs *cough* old versions of AUFS *cough*. However only - // ENOTSUP should be emitted in that case, otherwise we still - // bail. We also ignore EPERM errors if we are running in a - // user namespace. + if errors.Is(err, system.ENOTSUP) || (inUserns && errors.Is(err, syscall.EPERM)) { + // Ignore specific error cases: + // - ENOTSUP: Expected for graphdrivers lacking extended attribute support: + // - Legacy AUFS versions + // - FreeBSD with unsupported namespaces (trusted, security) + // - EPERM: Expected when operating within a user namespace + // All other errors will cause a failure. errs = append(errs, err.Error()) continue } return err } + } + if forceMask != nil && (typeFlag == tar.TypeReg || typeFlag == tar.TypeDir || runtime.GOOS == "darwin") { + value := idtools.Stat{ + IDs: idtools.IDPair{UID: hdr.Uid, GID: hdr.Gid}, + Mode: hdrInfo.Mode(), + Major: int(hdr.Devmajor), + Minor: int(hdr.Devminor), + } + if err := idtools.SetContainersOverrideXattr(path, value); err != nil { + return err + } } // We defer setting flags on directories until the end of @@ -1149,11 +1159,11 @@ loop: } if options.ForceMask != nil { - value := idtools.Stat{Mode: 0o755} + value := idtools.Stat{Mode: os.ModeDir | os.FileMode(0o755)} if rootHdr != nil { value.IDs.UID = rootHdr.Uid value.IDs.GID = rootHdr.Gid - value.Mode = os.FileMode(rootHdr.Mode) + value.Mode = os.ModeDir | os.FileMode(rootHdr.Mode) } if err := idtools.SetContainersOverrideXattr(dest, value); err != nil { return err @@ -1379,7 +1389,7 @@ func remapIDs(readIDMappings, writeIDMappings *idtools.IDMappings, chownOpts *id uid, gid = hdr.Uid, hdr.Gid if xstat, ok := hdr.PAXRecords[PaxSchilyXattr+idtools.ContainersOverrideXattr]; ok { attrs := strings.Split(string(xstat), ":") - if len(attrs) == 3 { + if len(attrs) >= 3 { val, err := strconv.ParseUint(attrs[0], 10, 32) if err != nil { uid = int(val) diff --git a/vendor/github.com/containers/storage/pkg/archive/changes_linux.go b/vendor/github.com/containers/storage/pkg/archive/changes_linux.go index dc308120dc..7e673592ed 100644 --- a/vendor/github.com/containers/storage/pkg/archive/changes_linux.go +++ b/vendor/github.com/containers/storage/pkg/archive/changes_linux.go @@ -87,11 +87,11 @@ func walkchunk(path string, fi os.FileInfo, dir string, root *FileInfo) error { } info.stat = stat info.capability, err = system.Lgetxattr(cpath, "security.capability") // lgetxattr(2): fs access - if err != nil && !errors.Is(err, system.EOPNOTSUPP) { + if err != nil && !errors.Is(err, system.ENOTSUP) { return err } xattrs, err := system.Llistxattr(cpath) - if err != nil && !errors.Is(err, system.EOPNOTSUPP) { + if err != nil && !errors.Is(err, system.ENOTSUP) { return err } for _, key := range xattrs { diff --git a/vendor/github.com/containers/storage/pkg/chunked/cache_linux.go b/vendor/github.com/containers/storage/pkg/chunked/cache_linux.go index 47742b05d8..c9cde23b7f 100644 --- a/vendor/github.com/containers/storage/pkg/chunked/cache_linux.go +++ b/vendor/github.com/containers/storage/pkg/chunked/cache_linux.go @@ -16,7 +16,7 @@ import ( storage "github.com/containers/storage" graphdriver "github.com/containers/storage/drivers" - "github.com/containers/storage/pkg/chunked/internal" + "github.com/containers/storage/pkg/chunked/internal/minimal" "github.com/containers/storage/pkg/ioutils" "github.com/docker/go-units" jsoniter "github.com/json-iterator/go" @@ -848,12 +848,12 @@ func (c *layersCache) findFileInOtherLayers(file *fileMetadata, useHardLinks boo return "", "", nil } -func (c *layersCache) findChunkInOtherLayers(chunk *internal.FileMetadata) (string, string, int64, error) { +func (c *layersCache) findChunkInOtherLayers(chunk *minimal.FileMetadata) (string, string, int64, error) { return c.findDigestInternal(chunk.ChunkDigest) } -func unmarshalToc(manifest []byte) (*internal.TOC, error) { - var toc internal.TOC +func unmarshalToc(manifest []byte) (*minimal.TOC, error) { + var toc minimal.TOC iter := jsoniter.ParseBytes(jsoniter.ConfigFastest, manifest) @@ -864,7 +864,7 @@ func unmarshalToc(manifest []byte) (*internal.TOC, error) { case "entries": for iter.ReadArray() { - var m internal.FileMetadata + var m minimal.FileMetadata for field := iter.ReadObject(); field != ""; field = iter.ReadObject() { switch strings.ToLower(field) { case "type": diff --git a/vendor/github.com/containers/storage/pkg/chunked/compression.go b/vendor/github.com/containers/storage/pkg/chunked/compression.go index e828d479f2..564efc8bf0 100644 --- a/vendor/github.com/containers/storage/pkg/chunked/compression.go +++ b/vendor/github.com/containers/storage/pkg/chunked/compression.go @@ -4,18 +4,18 @@ import ( "io" "github.com/containers/storage/pkg/chunked/compressor" - "github.com/containers/storage/pkg/chunked/internal" + "github.com/containers/storage/pkg/chunked/internal/minimal" ) const ( - TypeReg = internal.TypeReg - TypeChunk = internal.TypeChunk - TypeLink = internal.TypeLink - TypeChar = internal.TypeChar - TypeBlock = internal.TypeBlock - TypeDir = internal.TypeDir - TypeFifo = internal.TypeFifo - TypeSymlink = internal.TypeSymlink + TypeReg = minimal.TypeReg + TypeChunk = minimal.TypeChunk + TypeLink = minimal.TypeLink + TypeChar = minimal.TypeChar + TypeBlock = minimal.TypeBlock + TypeDir = minimal.TypeDir + TypeFifo = minimal.TypeFifo + TypeSymlink = minimal.TypeSymlink ) // ZstdCompressor is a CompressorFunc for the zstd compression algorithm. diff --git a/vendor/github.com/containers/storage/pkg/chunked/compression_linux.go b/vendor/github.com/containers/storage/pkg/chunked/compression_linux.go index 2dac463543..229ce366e4 100644 --- a/vendor/github.com/containers/storage/pkg/chunked/compression_linux.go +++ b/vendor/github.com/containers/storage/pkg/chunked/compression_linux.go @@ -10,7 +10,7 @@ import ( "strconv" "time" - "github.com/containers/storage/pkg/chunked/internal" + "github.com/containers/storage/pkg/chunked/internal/minimal" "github.com/klauspost/compress/zstd" "github.com/klauspost/pgzip" digest "github.com/opencontainers/go-digest" @@ -20,6 +20,12 @@ import ( expMaps "golang.org/x/exp/maps" ) +const ( + // maxTocSize is the maximum size of a blob that we will attempt to process. + // It is used to prevent DoS attacks from layers that embed a very large TOC file. + maxTocSize = (1 << 20) * 50 +) + var typesToTar = map[string]byte{ TypeReg: tar.TypeReg, TypeLink: tar.TypeLink, @@ -44,25 +50,21 @@ func readEstargzChunkedManifest(blobStream ImageSourceSeekable, blobSize int64, if blobSize <= footerSize { return nil, 0, errors.New("blob too small") } - chunk := ImageSourceChunk{ - Offset: uint64(blobSize - footerSize), - Length: uint64(footerSize), - } - parts, errs, err := blobStream.GetBlobAt([]ImageSourceChunk{chunk}) + + footer := make([]byte, footerSize) + streamsOrErrors, err := getBlobAt(blobStream, ImageSourceChunk{Offset: uint64(blobSize - footerSize), Length: uint64(footerSize)}) if err != nil { return nil, 0, err } - var reader io.ReadCloser - select { - case r := <-parts: - reader = r - case err := <-errs: - return nil, 0, err - } - defer reader.Close() - footer := make([]byte, footerSize) - if _, err := io.ReadFull(reader, footer); err != nil { - return nil, 0, err + + for soe := range streamsOrErrors { + if soe.stream != nil { + _, err = io.ReadFull(soe.stream, footer) + _ = soe.stream.Close() + } + if soe.err != nil && err == nil { + err = soe.err + } } /* Read the ToC offset: @@ -81,48 +83,54 @@ func readEstargzChunkedManifest(blobStream ImageSourceSeekable, blobSize int64, size := int64(blobSize - footerSize - tocOffset) // set a reasonable limit - if size > (1<<20)*50 { + if size > maxTocSize { return nil, 0, errors.New("manifest too big") } - chunk = ImageSourceChunk{ - Offset: uint64(tocOffset), - Length: uint64(size), - } - parts, errs, err = blobStream.GetBlobAt([]ImageSourceChunk{chunk}) - if err != nil { - return nil, 0, err - } - - var tocReader io.ReadCloser - select { - case r := <-parts: - tocReader = r - case err := <-errs: - return nil, 0, err - } - defer tocReader.Close() - - r, err := pgzip.NewReader(tocReader) + streamsOrErrors, err = getBlobAt(blobStream, ImageSourceChunk{Offset: uint64(tocOffset), Length: uint64(size)}) if err != nil { return nil, 0, err } - defer r.Close() - - aTar := archivetar.NewReader(r) - header, err := aTar.Next() - if err != nil { - return nil, 0, err + var manifestUncompressed []byte + + for soe := range streamsOrErrors { + if soe.stream != nil { + err1 := func() error { + defer soe.stream.Close() + + r, err := pgzip.NewReader(soe.stream) + if err != nil { + return err + } + defer r.Close() + + aTar := archivetar.NewReader(r) + + header, err := aTar.Next() + if err != nil { + return err + } + // set a reasonable limit + if header.Size > maxTocSize { + return errors.New("manifest too big") + } + + manifestUncompressed = make([]byte, header.Size) + if _, err := io.ReadFull(aTar, manifestUncompressed); err != nil { + return err + } + return nil + }() + if err == nil { + err = err1 + } + } else if err == nil { + err = soe.err + } } - // set a reasonable limit - if header.Size > (1<<20)*50 { - return nil, 0, errors.New("manifest too big") - } - - manifestUncompressed := make([]byte, header.Size) - if _, err := io.ReadFull(aTar, manifestUncompressed); err != nil { - return nil, 0, err + if manifestUncompressed == nil { + return nil, 0, errors.New("manifest not found") } manifestDigester := digest.Canonical.Digester() @@ -140,10 +148,10 @@ func readEstargzChunkedManifest(blobStream ImageSourceSeekable, blobSize int64, // readZstdChunkedManifest reads the zstd:chunked manifest from the seekable stream blobStream. // Returns (manifest blob, parsed manifest, tar-split blob or nil, manifest offset). -func readZstdChunkedManifest(blobStream ImageSourceSeekable, tocDigest digest.Digest, annotations map[string]string) ([]byte, *internal.TOC, []byte, int64, error) { - offsetMetadata := annotations[internal.ManifestInfoKey] +func readZstdChunkedManifest(blobStream ImageSourceSeekable, tocDigest digest.Digest, annotations map[string]string) (_ []byte, _ *minimal.TOC, _ []byte, _ int64, retErr error) { + offsetMetadata := annotations[minimal.ManifestInfoKey] if offsetMetadata == "" { - return nil, nil, nil, 0, fmt.Errorf("%q annotation missing", internal.ManifestInfoKey) + return nil, nil, nil, 0, fmt.Errorf("%q annotation missing", minimal.ManifestInfoKey) } var manifestChunk ImageSourceChunk var manifestLengthUncompressed, manifestType uint64 @@ -153,21 +161,21 @@ func readZstdChunkedManifest(blobStream ImageSourceSeekable, tocDigest digest.Di // The tarSplit… values are valid if tarSplitChunk.Offset > 0 var tarSplitChunk ImageSourceChunk var tarSplitLengthUncompressed uint64 - if tarSplitInfoKeyAnnotation, found := annotations[internal.TarSplitInfoKey]; found { + if tarSplitInfoKeyAnnotation, found := annotations[minimal.TarSplitInfoKey]; found { if _, err := fmt.Sscanf(tarSplitInfoKeyAnnotation, "%d:%d:%d", &tarSplitChunk.Offset, &tarSplitChunk.Length, &tarSplitLengthUncompressed); err != nil { return nil, nil, nil, 0, err } } - if manifestType != internal.ManifestTypeCRFS { + if manifestType != minimal.ManifestTypeCRFS { return nil, nil, nil, 0, errors.New("invalid manifest type") } // set a reasonable limit - if manifestChunk.Length > (1<<20)*50 { + if manifestChunk.Length > maxTocSize { return nil, nil, nil, 0, errors.New("manifest too big") } - if manifestLengthUncompressed > (1<<20)*50 { + if manifestLengthUncompressed > maxTocSize { return nil, nil, nil, 0, errors.New("manifest too big") } @@ -175,26 +183,31 @@ func readZstdChunkedManifest(blobStream ImageSourceSeekable, tocDigest digest.Di if tarSplitChunk.Offset > 0 { chunks = append(chunks, tarSplitChunk) } - parts, errs, err := blobStream.GetBlobAt(chunks) + + streamsOrErrors, err := getBlobAt(blobStream, chunks...) if err != nil { return nil, nil, nil, 0, err } + defer func() { + err := ensureAllBlobsDone(streamsOrErrors) + if retErr == nil { + retErr = err + } + }() + readBlob := func(len uint64) ([]byte, error) { - var reader io.ReadCloser - select { - case r := <-parts: - reader = r - case err := <-errs: - return nil, err + soe, ok := <-streamsOrErrors + if !ok { + return nil, errors.New("stream closed") + } + if soe.err != nil { + return nil, soe.err } + defer soe.stream.Close() blob := make([]byte, len) - if _, err := io.ReadFull(reader, blob); err != nil { - reader.Close() - return nil, err - } - if err := reader.Close(); err != nil { + if _, err := io.ReadFull(soe.stream, blob); err != nil { return nil, err } return blob, nil @@ -217,7 +230,7 @@ func readZstdChunkedManifest(blobStream ImageSourceSeekable, tocDigest digest.Di var decodedTarSplit []byte = nil if toc.TarSplitDigest != "" { if tarSplitChunk.Offset <= 0 { - return nil, nil, nil, 0, fmt.Errorf("TOC requires a tar-split, but the %s annotation does not describe a position", internal.TarSplitInfoKey) + return nil, nil, nil, 0, fmt.Errorf("TOC requires a tar-split, but the %s annotation does not describe a position", minimal.TarSplitInfoKey) } tarSplit, err := readBlob(tarSplitChunk.Length) if err != nil { @@ -247,11 +260,11 @@ func readZstdChunkedManifest(blobStream ImageSourceSeekable, tocDigest digest.Di } // ensureTOCMatchesTarSplit validates that toc and tarSplit contain _exactly_ the same entries. -func ensureTOCMatchesTarSplit(toc *internal.TOC, tarSplit []byte) error { - pendingFiles := map[string]*internal.FileMetadata{} // Name -> an entry in toc.Entries +func ensureTOCMatchesTarSplit(toc *minimal.TOC, tarSplit []byte) error { + pendingFiles := map[string]*minimal.FileMetadata{} // Name -> an entry in toc.Entries for i := range toc.Entries { e := &toc.Entries[i] - if e.Type != internal.TypeChunk { + if e.Type != minimal.TypeChunk { if _, ok := pendingFiles[e.Name]; ok { return fmt.Errorf("TOC contains duplicate entries for path %q", e.Name) } @@ -266,7 +279,7 @@ func ensureTOCMatchesTarSplit(toc *internal.TOC, tarSplit []byte) error { return fmt.Errorf("tar-split contains an entry for %q missing in TOC", hdr.Name) } delete(pendingFiles, hdr.Name) - expected, err := internal.NewFileMetadata(hdr) + expected, err := minimal.NewFileMetadata(hdr) if err != nil { return fmt.Errorf("determining expected metadata for %q: %w", hdr.Name, err) } @@ -347,8 +360,8 @@ func ensureTimePointersMatch(a, b *time.Time) error { // ensureFileMetadataAttributesMatch ensures that a and b match in file attributes (it ignores entries relevant to locating data // in the tar stream or matching contents) -func ensureFileMetadataAttributesMatch(a, b *internal.FileMetadata) error { - // Keep this in sync with internal.FileMetadata! +func ensureFileMetadataAttributesMatch(a, b *minimal.FileMetadata) error { + // Keep this in sync with minimal.FileMetadata! if a.Type != b.Type { return fmt.Errorf("mismatch of Type: %q != %q", a.Type, b.Type) diff --git a/vendor/github.com/containers/storage/pkg/chunked/compressor/compressor.go b/vendor/github.com/containers/storage/pkg/chunked/compressor/compressor.go index 654969749b..56ae4c770c 100644 --- a/vendor/github.com/containers/storage/pkg/chunked/compressor/compressor.go +++ b/vendor/github.com/containers/storage/pkg/chunked/compressor/compressor.go @@ -9,7 +9,7 @@ import ( "bytes" "io" - "github.com/containers/storage/pkg/chunked/internal" + "github.com/containers/storage/pkg/chunked/internal/minimal" "github.com/containers/storage/pkg/ioutils" "github.com/klauspost/compress/zstd" "github.com/opencontainers/go-digest" @@ -213,7 +213,7 @@ func newTarSplitData(level int) (*tarSplitData, error) { compressed := bytes.NewBuffer(nil) digester := digest.Canonical.Digester() - zstdWriter, err := internal.ZstdWriterWithLevel(io.MultiWriter(compressed, digester.Hash()), level) + zstdWriter, err := minimal.ZstdWriterWithLevel(io.MultiWriter(compressed, digester.Hash()), level) if err != nil { return nil, err } @@ -254,7 +254,7 @@ func writeZstdChunkedStream(destFile io.Writer, outMetadata map[string]string, r buf := make([]byte, 4096) - zstdWriter, err := internal.ZstdWriterWithLevel(dest, level) + zstdWriter, err := minimal.ZstdWriterWithLevel(dest, level) if err != nil { return err } @@ -276,7 +276,7 @@ func writeZstdChunkedStream(destFile io.Writer, outMetadata map[string]string, r return offset, nil } - var metadata []internal.FileMetadata + var metadata []minimal.FileMetadata for { hdr, err := tr.Next() if err != nil { @@ -341,9 +341,9 @@ func writeZstdChunkedStream(destFile io.Writer, outMetadata map[string]string, r chunkSize := rcReader.WrittenOut - lastChunkOffset if chunkSize > 0 { - chunkType := internal.ChunkTypeData + chunkType := minimal.ChunkTypeData if rcReader.IsLastChunkZeros { - chunkType = internal.ChunkTypeZeros + chunkType = minimal.ChunkTypeZeros } chunks = append(chunks, chunk{ @@ -368,17 +368,17 @@ func writeZstdChunkedStream(destFile io.Writer, outMetadata map[string]string, r } } - mainEntry, err := internal.NewFileMetadata(hdr) + mainEntry, err := minimal.NewFileMetadata(hdr) if err != nil { return err } mainEntry.Digest = checksum mainEntry.Offset = startOffset mainEntry.EndOffset = lastOffset - entries := []internal.FileMetadata{mainEntry} + entries := []minimal.FileMetadata{mainEntry} for i := 1; i < len(chunks); i++ { - entries = append(entries, internal.FileMetadata{ - Type: internal.TypeChunk, + entries = append(entries, minimal.FileMetadata{ + Type: minimal.TypeChunk, Name: hdr.Name, ChunkOffset: chunks[i].ChunkOffset, }) @@ -424,13 +424,13 @@ func writeZstdChunkedStream(destFile io.Writer, outMetadata map[string]string, r } tarSplitData.zstd = nil - ts := internal.TarSplitData{ + ts := minimal.TarSplitData{ Data: tarSplitData.compressed.Bytes(), Digest: tarSplitData.digester.Digest(), UncompressedSize: tarSplitData.uncompressedCounter.Count, } - return internal.WriteZstdChunkedManifest(dest, outMetadata, uint64(dest.Count), &ts, metadata, level) + return minimal.WriteZstdChunkedManifest(dest, outMetadata, uint64(dest.Count), &ts, metadata, level) } type zstdChunkedWriter struct { diff --git a/vendor/github.com/containers/storage/pkg/chunked/dump/dump.go b/vendor/github.com/containers/storage/pkg/chunked/dump/dump.go index d98cee09de..fde42d75fc 100644 --- a/vendor/github.com/containers/storage/pkg/chunked/dump/dump.go +++ b/vendor/github.com/containers/storage/pkg/chunked/dump/dump.go @@ -12,7 +12,8 @@ import ( "strings" "time" - "github.com/containers/storage/pkg/chunked/internal" + "github.com/containers/storage/pkg/chunked/internal/minimal" + storagePath "github.com/containers/storage/pkg/chunked/internal/path" "golang.org/x/sys/unix" ) @@ -85,17 +86,17 @@ func escapedOptional(val []byte, escape int) string { func getStMode(mode uint32, typ string) (uint32, error) { switch typ { - case internal.TypeReg, internal.TypeLink: + case minimal.TypeReg, minimal.TypeLink: mode |= unix.S_IFREG - case internal.TypeChar: + case minimal.TypeChar: mode |= unix.S_IFCHR - case internal.TypeBlock: + case minimal.TypeBlock: mode |= unix.S_IFBLK - case internal.TypeDir: + case minimal.TypeDir: mode |= unix.S_IFDIR - case internal.TypeFifo: + case minimal.TypeFifo: mode |= unix.S_IFIFO - case internal.TypeSymlink: + case minimal.TypeSymlink: mode |= unix.S_IFLNK default: return 0, fmt.Errorf("unknown type %s", typ) @@ -103,24 +104,14 @@ func getStMode(mode uint32, typ string) (uint32, error) { return mode, nil } -func sanitizeName(name string) string { - path := filepath.Clean(name) - if path == "." { - path = "/" - } else if path[0] != '/' { - path = "/" + path - } - return path -} - -func dumpNode(out io.Writer, added map[string]*internal.FileMetadata, links map[string]int, verityDigests map[string]string, entry *internal.FileMetadata) error { - path := sanitizeName(entry.Name) +func dumpNode(out io.Writer, added map[string]*minimal.FileMetadata, links map[string]int, verityDigests map[string]string, entry *minimal.FileMetadata) error { + path := storagePath.CleanAbsPath(entry.Name) parent := filepath.Dir(path) if _, found := added[parent]; !found && path != "/" { - parentEntry := &internal.FileMetadata{ + parentEntry := &minimal.FileMetadata{ Name: parent, - Type: internal.TypeDir, + Type: minimal.TypeDir, Mode: 0o755, } if err := dumpNode(out, added, links, verityDigests, parentEntry); err != nil { @@ -143,7 +134,7 @@ func dumpNode(out io.Writer, added map[string]*internal.FileMetadata, links map[ nlinks := links[entry.Name] + links[entry.Linkname] + 1 link := "" - if entry.Type == internal.TypeLink { + if entry.Type == minimal.TypeLink { link = "@" } @@ -169,10 +160,10 @@ func dumpNode(out io.Writer, added map[string]*internal.FileMetadata, links map[ var payload string if entry.Linkname != "" { - if entry.Type == internal.TypeSymlink { + if entry.Type == minimal.TypeSymlink { payload = entry.Linkname } else { - payload = sanitizeName(entry.Linkname) + payload = storagePath.CleanAbsPath(entry.Linkname) } } else { if len(entry.Digest) > 10 { @@ -219,7 +210,7 @@ func dumpNode(out io.Writer, added map[string]*internal.FileMetadata, links map[ // GenerateDump generates a dump of the TOC in the same format as `composefs-info dump` func GenerateDump(tocI interface{}, verityDigests map[string]string) (io.Reader, error) { - toc, ok := tocI.(*internal.TOC) + toc, ok := tocI.(*minimal.TOC) if !ok { return nil, fmt.Errorf("invalid TOC type") } @@ -235,21 +226,21 @@ func GenerateDump(tocI interface{}, verityDigests map[string]string) (io.Reader, }() links := make(map[string]int) - added := make(map[string]*internal.FileMetadata) + added := make(map[string]*minimal.FileMetadata) for _, e := range toc.Entries { if e.Linkname == "" { continue } - if e.Type == internal.TypeSymlink { + if e.Type == minimal.TypeSymlink { continue } links[e.Linkname] = links[e.Linkname] + 1 } if len(toc.Entries) == 0 { - root := &internal.FileMetadata{ + root := &minimal.FileMetadata{ Name: "/", - Type: internal.TypeDir, + Type: minimal.TypeDir, Mode: 0o755, } @@ -261,7 +252,7 @@ func GenerateDump(tocI interface{}, verityDigests map[string]string) (io.Reader, } for _, e := range toc.Entries { - if e.Type == internal.TypeChunk { + if e.Type == minimal.TypeChunk { continue } if err := dumpNode(w, added, links, verityDigests, &e); err != nil { diff --git a/vendor/github.com/containers/storage/pkg/chunked/filesystem_linux.go b/vendor/github.com/containers/storage/pkg/chunked/filesystem_linux.go index e26e5e5c55..82685e9c91 100644 --- a/vendor/github.com/containers/storage/pkg/chunked/filesystem_linux.go +++ b/vendor/github.com/containers/storage/pkg/chunked/filesystem_linux.go @@ -15,7 +15,8 @@ import ( driversCopy "github.com/containers/storage/drivers/copy" "github.com/containers/storage/pkg/archive" - "github.com/containers/storage/pkg/chunked/internal" + "github.com/containers/storage/pkg/chunked/internal/minimal" + storagePath "github.com/containers/storage/pkg/chunked/internal/path" securejoin "github.com/cyphar/filepath-securejoin" "github.com/vbatts/tar-split/archive/tar" "golang.org/x/sys/unix" @@ -34,14 +35,14 @@ func procPathForFd(fd int) string { return fmt.Sprintf("/proc/self/fd/%d", fd) } -// fileMetadata is a wrapper around internal.FileMetadata with additional private fields that +// fileMetadata is a wrapper around minimal.FileMetadata with additional private fields that // are not part of the TOC document. // Type: TypeChunk entries are stored in Chunks, the primary [fileMetadata] entries never use TypeChunk. type fileMetadata struct { - internal.FileMetadata + minimal.FileMetadata // chunks stores the TypeChunk entries relevant to this entry when FileMetadata.Type == TypeReg. - chunks []*internal.FileMetadata + chunks []*minimal.FileMetadata // skipSetAttrs is set when the file attributes must not be // modified, e.g. it is a hard link from a different source, @@ -49,10 +50,37 @@ type fileMetadata struct { skipSetAttrs bool } +// splitPath takes a file path as input and returns two components: dir and base. +// Differently than filepath.Split(), this function handles some edge cases. +// If the path refers to a file in the root directory, the returned dir is "/". +// The returned base value is never empty, it never contains any slash and the +// value "..". +func splitPath(path string) (string, string, error) { + path = storagePath.CleanAbsPath(path) + dir, base := filepath.Split(path) + if base == "" { + base = "." + } + // Remove trailing slashes from dir, but make sure that "/" is preserved. + dir = strings.TrimSuffix(dir, "/") + if dir == "" { + dir = "/" + } + + if strings.Contains(base, "/") { + // This should never happen, but be safe as the base is passed to *at syscalls. + return "", "", fmt.Errorf("internal error: splitPath(%q) contains a slash", path) + } + return dir, base, nil +} + func doHardLink(dirfd, srcFd int, destFile string) error { - destDir, destBase := filepath.Split(destFile) + destDir, destBase, err := splitPath(destFile) + if err != nil { + return err + } destDirFd := dirfd - if destDir != "" && destDir != "." { + if destDir != "/" { f, err := openOrCreateDirUnderRoot(dirfd, destDir, 0) if err != nil { return err @@ -72,7 +100,7 @@ func doHardLink(dirfd, srcFd int, destFile string) error { return nil } - err := doLink() + err = doLink() // if the destination exists, unlink it first and try again if err != nil && os.IsExist(err) { @@ -281,8 +309,11 @@ func openFileUnderRootFallback(dirfd int, name string, flags uint64, mode os.Fil // If O_NOFOLLOW is specified in the flags, then resolve only the parent directory and use the // last component as the path to openat(). if hasNoFollow { - dirName, baseName := filepath.Split(name) - if dirName != "" && dirName != "." { + dirName, baseName, err := splitPath(name) + if err != nil { + return -1, err + } + if dirName != "/" { newRoot, err := securejoin.SecureJoin(root, dirName) if err != nil { return -1, err @@ -409,7 +440,8 @@ func openOrCreateDirUnderRoot(dirfd int, name string, mode os.FileMode) (*os.Fil if errors.Is(err, unix.ENOENT) { parent := filepath.Dir(name) - if parent != "" { + // do not create the root directory, it should always exist + if parent != name { pDir, err2 := openOrCreateDirUnderRoot(dirfd, parent, mode) if err2 != nil { return nil, err @@ -448,9 +480,12 @@ func appendHole(fd int, name string, size int64) error { } func safeMkdir(dirfd int, mode os.FileMode, name string, metadata *fileMetadata, options *archive.TarOptions) error { - parent, base := filepath.Split(name) + parent, base, err := splitPath(name) + if err != nil { + return err + } parentFd := dirfd - if parent != "" && parent != "." { + if parent != "/" { parentFile, err := openOrCreateDirUnderRoot(dirfd, parent, 0) if err != nil { return err @@ -506,9 +541,12 @@ func safeLink(dirfd int, mode os.FileMode, metadata *fileMetadata, options *arch } func safeSymlink(dirfd int, metadata *fileMetadata) error { - destDir, destBase := filepath.Split(metadata.Name) + destDir, destBase, err := splitPath(metadata.Name) + if err != nil { + return err + } destDirFd := dirfd - if destDir != "" && destDir != "." { + if destDir != "/" { f, err := openOrCreateDirUnderRoot(dirfd, destDir, 0) if err != nil { return err @@ -542,9 +580,12 @@ func (d whiteoutHandler) Setxattr(path, name string, value []byte) error { } func (d whiteoutHandler) Mknod(path string, mode uint32, dev int) error { - dir, base := filepath.Split(path) + dir, base, err := splitPath(path) + if err != nil { + return err + } dirfd := d.Dirfd - if dir != "" && dir != "." { + if dir != "/" { dir, err := openOrCreateDirUnderRoot(d.Dirfd, dir, 0) if err != nil { return err diff --git a/vendor/github.com/containers/storage/pkg/chunked/internal/compression.go b/vendor/github.com/containers/storage/pkg/chunked/internal/minimal/compression.go similarity index 99% rename from vendor/github.com/containers/storage/pkg/chunked/internal/compression.go rename to vendor/github.com/containers/storage/pkg/chunked/internal/minimal/compression.go index 2a24e4bf2e..377ece2e88 100644 --- a/vendor/github.com/containers/storage/pkg/chunked/internal/compression.go +++ b/vendor/github.com/containers/storage/pkg/chunked/internal/minimal/compression.go @@ -1,4 +1,4 @@ -package internal +package minimal // NOTE: This is used from github.com/containers/image by callers that // don't otherwise use containers/storage, so don't make this depend on any diff --git a/vendor/github.com/containers/storage/pkg/chunked/internal/path/path.go b/vendor/github.com/containers/storage/pkg/chunked/internal/path/path.go new file mode 100644 index 0000000000..50ca1def87 --- /dev/null +++ b/vendor/github.com/containers/storage/pkg/chunked/internal/path/path.go @@ -0,0 +1,12 @@ +package path + +import ( + "path/filepath" +) + +// CleanAbsPath removes any ".." and "." from the path +// and ensures it starts with a "/". If the path refers to the root +// directory, it returns "/". +func CleanAbsPath(path string) string { + return filepath.Clean("/" + path) +} diff --git a/vendor/github.com/containers/storage/pkg/chunked/storage_linux.go b/vendor/github.com/containers/storage/pkg/chunked/storage_linux.go index 8ecbfb9826..cbf8229ac2 100644 --- a/vendor/github.com/containers/storage/pkg/chunked/storage_linux.go +++ b/vendor/github.com/containers/storage/pkg/chunked/storage_linux.go @@ -22,7 +22,8 @@ import ( graphdriver "github.com/containers/storage/drivers" "github.com/containers/storage/pkg/archive" "github.com/containers/storage/pkg/chunked/compressor" - "github.com/containers/storage/pkg/chunked/internal" + "github.com/containers/storage/pkg/chunked/internal/minimal" + path "github.com/containers/storage/pkg/chunked/internal/path" "github.com/containers/storage/pkg/chunked/toc" "github.com/containers/storage/pkg/fsverity" "github.com/containers/storage/pkg/idtools" @@ -59,7 +60,7 @@ type compressedFileType int type chunkedDiffer struct { stream ImageSourceSeekable manifest []byte - toc *internal.TOC // The parsed contents of manifest, or nil if not yet available + toc *minimal.TOC // The parsed contents of manifest, or nil if not yet available tarSplit []byte layersCache *layersCache tocOffset int64 @@ -189,7 +190,7 @@ func GetDiffer(ctx context.Context, store storage.Store, blobDigest digest.Diges // On error, the second parameter is true if a fallback to an alternative (either the makeConverToRaw differ, or a non-partial pull) // is permissible. func getProperDiffer(store storage.Store, blobDigest digest.Digest, blobSize int64, annotations map[string]string, iss ImageSourceSeekable, pullOptions map[string]string) (graphdriver.Differ, bool, error) { - zstdChunkedTOCDigestString, hasZstdChunkedTOC := annotations[internal.ManifestChecksumKey] + zstdChunkedTOCDigestString, hasZstdChunkedTOC := annotations[minimal.ManifestChecksumKey] estargzTOCDigestString, hasEstargzTOC := annotations[estargz.TOCJSONDigestAnnotation] switch { @@ -391,7 +392,7 @@ func canDedupFileWithHardLink(file *fileMetadata, fd int, s os.FileInfo) bool { } // fill only the attributes used by canDedupMetadataWithHardLink. otherFile := fileMetadata{ - FileMetadata: internal.FileMetadata{ + FileMetadata: minimal.FileMetadata{ UID: int(st.Uid), GID: int(st.Gid), Mode: int64(st.Mode), @@ -735,7 +736,12 @@ func (d *destinationFile) Close() (Err error) { } } - return setFileAttrs(d.dirfd, d.file, os.FileMode(d.metadata.Mode), d.metadata, d.options, false) + mode := os.FileMode(d.metadata.Mode) + if d.options.ForceMask != nil { + mode = *d.options.ForceMask + } + + return setFileAttrs(d.dirfd, d.file, mode, d.metadata, d.options, false) } func closeDestinationFiles(files chan *destinationFile, errors chan error) { @@ -1141,44 +1147,140 @@ func makeEntriesFlat(mergedEntries []fileMetadata) ([]fileMetadata, error) { return new, nil } -func (c *chunkedDiffer) copyAllBlobToFile(destination *os.File) (digest.Digest, error) { - var payload io.ReadCloser - var streams chan io.ReadCloser - var errs chan error - var err error +type streamOrErr struct { + stream io.ReadCloser + err error +} - chunksToRequest := []ImageSourceChunk{ - { - Offset: 0, - Length: uint64(c.blobSize), - }, +// ensureAllBlobsDone ensures that all blobs are closed and returns the first error encountered. +func ensureAllBlobsDone(streamsOrErrors chan streamOrErr) (retErr error) { + for soe := range streamsOrErrors { + if soe.stream != nil { + _ = soe.stream.Close() + } else if retErr == nil { + retErr = soe.err + } } + return +} - streams, errs, err = c.stream.GetBlobAt(chunksToRequest) - if err != nil { - return "", err +// getBlobAtConverterGoroutine reads from the streams and errs channels, then sends +// either a stream or an error to the stream channel. The streams channel is closed when +// there are no more streams and errors to read. +// It ensures that no more than maxStreams streams are returned, and that every item from the +// streams and errs channels is consumed. +func getBlobAtConverterGoroutine(stream chan streamOrErr, streams chan io.ReadCloser, errs chan error, maxStreams int) { + tooManyStreams := false + streamsSoFar := 0 + + err := errors.New("Unexpected error in getBlobAtGoroutine") + + defer func() { + if err != nil { + stream <- streamOrErr{err: err} + } + close(stream) + }() + +loop: + for { + select { + case p, ok := <-streams: + if !ok { + streams = nil + break loop + } + if streamsSoFar >= maxStreams { + tooManyStreams = true + _ = p.Close() + continue + } + streamsSoFar++ + stream <- streamOrErr{stream: p} + case err, ok := <-errs: + if !ok { + errs = nil + break loop + } + stream <- streamOrErr{err: err} + } } - select { - case p := <-streams: - payload = p - case err := <-errs: - return "", err + if streams != nil { + for p := range streams { + if streamsSoFar >= maxStreams { + tooManyStreams = true + _ = p.Close() + continue + } + streamsSoFar++ + stream <- streamOrErr{stream: p} + } + } + if errs != nil { + for err := range errs { + stream <- streamOrErr{err: err} + } } - if payload == nil { - return "", errors.New("invalid stream returned") + if tooManyStreams { + stream <- streamOrErr{err: fmt.Errorf("too many streams returned, got more than %d", maxStreams)} } - defer payload.Close() + err = nil +} - originalRawDigester := digest.Canonical.Digester() +// getBlobAt provides a much more convenient way to consume data returned by ImageSourceSeekable.GetBlobAt. +// GetBlobAt returns two channels, forcing a caller to `select` on both of them — and in Go, reading a closed channel +// always succeeds in select. +// Instead, getBlobAt provides a single channel with all events, which can be consumed conveniently using `range`. +func getBlobAt(is ImageSourceSeekable, chunksToRequest ...ImageSourceChunk) (chan streamOrErr, error) { + streams, errs, err := is.GetBlobAt(chunksToRequest) + if err != nil { + return nil, err + } + stream := make(chan streamOrErr) + go getBlobAtConverterGoroutine(stream, streams, errs, len(chunksToRequest)) + return stream, nil +} - r := io.TeeReader(payload, originalRawDigester.Hash()) +func (c *chunkedDiffer) copyAllBlobToFile(destination *os.File) (digest.Digest, error) { + streamsOrErrors, err := getBlobAt(c.stream, ImageSourceChunk{Offset: 0, Length: uint64(c.blobSize)}) + if err != nil { + return "", err + } - // copy the entire tarball and compute its digest - _, err = io.CopyBuffer(destination, r, c.copyBuffer) + originalRawDigester := digest.Canonical.Digester() + for soe := range streamsOrErrors { + if soe.stream != nil { + r := io.TeeReader(soe.stream, originalRawDigester.Hash()) + // copy the entire tarball and compute its digest + _, err = io.CopyBuffer(destination, r, c.copyBuffer) + _ = soe.stream.Close() + } + if soe.err != nil && err == nil { + err = soe.err + } + } return originalRawDigester.Digest(), err } +func typeToOsMode(typ string) (os.FileMode, error) { + switch typ { + case TypeReg, TypeLink: + return 0, nil + case TypeSymlink: + return os.ModeSymlink, nil + case TypeDir: + return os.ModeDir, nil + case TypeChar: + return os.ModeDevice | os.ModeCharDevice, nil + case TypeBlock: + return os.ModeDevice, nil + case TypeFifo: + return os.ModeNamedPipe, nil + } + return 0, fmt.Errorf("unknown file type %q", typ) +} + func (c *chunkedDiffer) ApplyDiff(dest string, options *archive.TarOptions, differOpts *graphdriver.DifferOptions) (graphdriver.DriverWithDifferOutput, error) { defer c.layersCache.release() defer func() { @@ -1325,7 +1427,7 @@ func (c *chunkedDiffer) ApplyDiff(dest string, options *archive.TarOptions, diff if err == nil { value := idtools.Stat{ IDs: idtools.IDPair{UID: int(uid), GID: int(gid)}, - Mode: os.FileMode(mode), + Mode: os.ModeDir | os.FileMode(mode), } if err := idtools.SetContainersOverrideXattr(dest, value); err != nil { return output, err @@ -1408,13 +1510,6 @@ func (c *chunkedDiffer) ApplyDiff(dest string, options *archive.TarOptions, diff filesToWaitFor := 0 for i := range mergedEntries { r := &mergedEntries[i] - if options.ForceMask != nil { - value := idtools.FormatContainersOverrideXattr(r.UID, r.GID, int(r.Mode)) - if r.Xattrs == nil { - r.Xattrs = make(map[string]string) - } - r.Xattrs[idtools.ContainersOverrideXattr] = base64.StdEncoding.EncodeToString([]byte(value)) - } mode := os.FileMode(r.Mode) @@ -1423,10 +1518,37 @@ func (c *chunkedDiffer) ApplyDiff(dest string, options *archive.TarOptions, diff return output, err } - r.Name = filepath.Clean(r.Name) + size := r.Size + + // update also the implementation of ForceMask in pkg/archive + if options.ForceMask != nil { + mode = *options.ForceMask + + // special files will be stored as regular files + if t != tar.TypeDir && t != tar.TypeSymlink && t != tar.TypeReg && t != tar.TypeLink { + t = tar.TypeReg + size = 0 + } + + // if the entry will be stored as a directory or a regular file, store in a xattr the original + // owner and mode. + if t == tar.TypeDir || t == tar.TypeReg { + typeMode, err := typeToOsMode(r.Type) + if err != nil { + return output, err + } + value := idtools.FormatContainersOverrideXattrDevice(r.UID, r.GID, typeMode|fs.FileMode(r.Mode), int(r.Devmajor), int(r.Devminor)) + if r.Xattrs == nil { + r.Xattrs = make(map[string]string) + } + r.Xattrs[idtools.ContainersOverrideXattr] = base64.StdEncoding.EncodeToString([]byte(value)) + } + } + + r.Name = path.CleanAbsPath(r.Name) // do not modify the value of symlinks if r.Linkname != "" && t != tar.TypeSymlink { - r.Linkname = filepath.Clean(r.Linkname) + r.Linkname = path.CleanAbsPath(r.Linkname) } if whiteoutConverter != nil { @@ -1434,8 +1556,8 @@ func (c *chunkedDiffer) ApplyDiff(dest string, options *archive.TarOptions, diff Typeflag: t, Name: r.Name, Linkname: r.Linkname, - Size: r.Size, - Mode: r.Mode, + Size: size, + Mode: int64(mode), Uid: r.UID, Gid: r.GID, } @@ -1454,7 +1576,7 @@ func (c *chunkedDiffer) ApplyDiff(dest string, options *archive.TarOptions, diff switch t { case tar.TypeReg: // Create directly empty files. - if r.Size == 0 { + if size == 0 { // Used to have a scope for cleanup. createEmptyFile := func() error { file, err := openFileUnderRoot(dirfd, r.Name, newFileFlags, 0) @@ -1474,7 +1596,7 @@ func (c *chunkedDiffer) ApplyDiff(dest string, options *archive.TarOptions, diff } case tar.TypeDir: - if r.Name == "" || r.Name == "." { + if r.Name == "/" { output.RootDirMode = &mode } if err := safeMkdir(dirfd, mode, r.Name, r, options); err != nil { @@ -1509,7 +1631,7 @@ func (c *chunkedDiffer) ApplyDiff(dest string, options *archive.TarOptions, diff return output, fmt.Errorf("invalid type %q", t) } - totalChunksSize += r.Size + totalChunksSize += size if t == tar.TypeReg { index := i @@ -1572,7 +1694,7 @@ func (c *chunkedDiffer) ApplyDiff(dest string, options *archive.TarOptions, diff } switch chunk.ChunkType { - case internal.ChunkTypeData: + case minimal.ChunkTypeData: root, path, offset, err := c.layersCache.findChunkInOtherLayers(chunk) if err != nil { return output, err @@ -1585,7 +1707,7 @@ func (c *chunkedDiffer) ApplyDiff(dest string, options *archive.TarOptions, diff Offset: offset, } } - case internal.ChunkTypeZeros: + case minimal.ChunkTypeZeros: missingPartsSize -= size mp.Hole = true // Mark all chunks belonging to the missing part as holes @@ -1618,7 +1740,7 @@ func (c *chunkedDiffer) ApplyDiff(dest string, options *archive.TarOptions, diff return output, nil } -func mustSkipFile(fileType compressedFileType, e internal.FileMetadata) bool { +func mustSkipFile(fileType compressedFileType, e minimal.FileMetadata) bool { // ignore the metadata files for the estargz format. if fileType != fileTypeEstargz { return false @@ -1631,7 +1753,7 @@ func mustSkipFile(fileType compressedFileType, e internal.FileMetadata) bool { return false } -func (c *chunkedDiffer) mergeTocEntries(fileType compressedFileType, entries []internal.FileMetadata) ([]fileMetadata, error) { +func (c *chunkedDiffer) mergeTocEntries(fileType compressedFileType, entries []minimal.FileMetadata) ([]fileMetadata, error) { countNextChunks := func(start int) int { count := 0 for _, e := range entries[start:] { @@ -1668,7 +1790,7 @@ func (c *chunkedDiffer) mergeTocEntries(fileType compressedFileType, entries []i if e.Type == TypeReg { nChunks := countNextChunks(i + 1) - e.chunks = make([]*internal.FileMetadata, nChunks+1) + e.chunks = make([]*minimal.FileMetadata, nChunks+1) for j := 0; j <= nChunks; j++ { // we need a copy here, otherwise we override the // .Size later @@ -1703,7 +1825,7 @@ func (c *chunkedDiffer) mergeTocEntries(fileType compressedFileType, entries []i // validateChunkChecksum checks if the file at $root/$path[offset:chunk.ChunkSize] has the // same digest as chunk.ChunkDigest -func validateChunkChecksum(chunk *internal.FileMetadata, root, path string, offset int64, copyBuffer []byte) bool { +func validateChunkChecksum(chunk *minimal.FileMetadata, root, path string, offset int64, copyBuffer []byte) bool { parentDirfd, err := unix.Open(root, unix.O_PATH|unix.O_CLOEXEC, 0) if err != nil { return false diff --git a/vendor/github.com/containers/storage/pkg/chunked/toc/toc.go b/vendor/github.com/containers/storage/pkg/chunked/toc/toc.go index 6fbaa41b5a..6f39b2ae2e 100644 --- a/vendor/github.com/containers/storage/pkg/chunked/toc/toc.go +++ b/vendor/github.com/containers/storage/pkg/chunked/toc/toc.go @@ -3,7 +3,7 @@ package toc import ( "errors" - "github.com/containers/storage/pkg/chunked/internal" + "github.com/containers/storage/pkg/chunked/internal/minimal" digest "github.com/opencontainers/go-digest" ) @@ -19,7 +19,7 @@ const tocJSONDigestAnnotation = "containerd.io/snapshot/stargz/toc.digest" // This is an experimental feature and may be changed/removed in the future. func GetTOCDigest(annotations map[string]string) (*digest.Digest, error) { d1, ok1 := annotations[tocJSONDigestAnnotation] - d2, ok2 := annotations[internal.ManifestChecksumKey] + d2, ok2 := annotations[minimal.ManifestChecksumKey] switch { case ok1 && ok2: return nil, errors.New("both zstd:chunked and eStargz TOC found") diff --git a/vendor/github.com/containers/storage/pkg/idtools/idtools.go b/vendor/github.com/containers/storage/pkg/idtools/idtools.go index 248594e935..299bdbef7f 100644 --- a/vendor/github.com/containers/storage/pkg/idtools/idtools.go +++ b/vendor/github.com/containers/storage/pkg/idtools/idtools.go @@ -4,6 +4,7 @@ import ( "bufio" "errors" "fmt" + "io/fs" "os" "os/user" "runtime" @@ -369,27 +370,66 @@ func checkChownErr(err error, name string, uid, gid int) error { // Stat contains file states that can be overridden with ContainersOverrideXattr. type Stat struct { - IDs IDPair - Mode os.FileMode + IDs IDPair + Mode os.FileMode + Major int + Minor int } // FormatContainersOverrideXattr will format the given uid, gid, and mode into a string // that can be used as the value for the ContainersOverrideXattr xattr. func FormatContainersOverrideXattr(uid, gid, mode int) string { - return fmt.Sprintf("%d:%d:0%o", uid, gid, mode&0o7777) + return FormatContainersOverrideXattrDevice(uid, gid, fs.FileMode(mode), 0, 0) +} + +// FormatContainersOverrideXattrDevice will format the given uid, gid, and mode into a string +// that can be used as the value for the ContainersOverrideXattr xattr. For devices, it also +// needs the major and minor numbers. +func FormatContainersOverrideXattrDevice(uid, gid int, mode fs.FileMode, major, minor int) string { + typ := "" + switch mode & os.ModeType { + case os.ModeDir: + typ = "dir" + case os.ModeSymlink: + typ = "symlink" + case os.ModeNamedPipe: + typ = "pipe" + case os.ModeSocket: + typ = "socket" + case os.ModeDevice: + typ = fmt.Sprintf("block-%d-%d", major, minor) + case os.ModeDevice | os.ModeCharDevice: + typ = fmt.Sprintf("char-%d-%d", major, minor) + default: + typ = "file" + } + unixMode := mode & os.ModePerm + if mode&os.ModeSetuid != 0 { + unixMode |= 0o4000 + } + if mode&os.ModeSetgid != 0 { + unixMode |= 0o2000 + } + if mode&os.ModeSticky != 0 { + unixMode |= 0o1000 + } + return fmt.Sprintf("%d:%d:%04o:%s", uid, gid, unixMode, typ) } // GetContainersOverrideXattr will get and decode ContainersOverrideXattr. func GetContainersOverrideXattr(path string) (Stat, error) { - var stat Stat xstat, err := system.Lgetxattr(path, ContainersOverrideXattr) if err != nil { - return stat, err + return Stat{}, err } + return parseOverrideXattr(xstat) // This will fail if (xstat, err) == (nil, nil), i.e. the xattr does not exist. +} +func parseOverrideXattr(xstat []byte) (Stat, error) { + var stat Stat attrs := strings.Split(string(xstat), ":") - if len(attrs) != 3 { - return stat, fmt.Errorf("The number of clons in %s does not equal to 3", + if len(attrs) < 3 { + return stat, fmt.Errorf("The number of parts in %s is less than 3", ContainersOverrideXattr) } @@ -397,47 +437,105 @@ func GetContainersOverrideXattr(path string) (Stat, error) { if err != nil { return stat, fmt.Errorf("Failed to parse UID: %w", err) } - stat.IDs.UID = int(value) - value, err = strconv.ParseUint(attrs[0], 10, 32) + value, err = strconv.ParseUint(attrs[1], 10, 32) if err != nil { return stat, fmt.Errorf("Failed to parse GID: %w", err) } - stat.IDs.GID = int(value) value, err = strconv.ParseUint(attrs[2], 8, 32) if err != nil { return stat, fmt.Errorf("Failed to parse mode: %w", err) } + stat.Mode = os.FileMode(value) & os.ModePerm + if value&0o1000 != 0 { + stat.Mode |= os.ModeSticky + } + if value&0o2000 != 0 { + stat.Mode |= os.ModeSetgid + } + if value&0o4000 != 0 { + stat.Mode |= os.ModeSetuid + } - stat.Mode = os.FileMode(value) - + if len(attrs) > 3 { + typ := attrs[3] + if strings.HasPrefix(typ, "file") { + } else if strings.HasPrefix(typ, "dir") { + stat.Mode |= os.ModeDir + } else if strings.HasPrefix(typ, "symlink") { + stat.Mode |= os.ModeSymlink + } else if strings.HasPrefix(typ, "pipe") { + stat.Mode |= os.ModeNamedPipe + } else if strings.HasPrefix(typ, "socket") { + stat.Mode |= os.ModeSocket + } else if strings.HasPrefix(typ, "block") { + stat.Mode |= os.ModeDevice + stat.Major, stat.Minor, err = parseDevice(typ) + if err != nil { + return stat, err + } + } else if strings.HasPrefix(typ, "char") { + stat.Mode |= os.ModeDevice | os.ModeCharDevice + stat.Major, stat.Minor, err = parseDevice(typ) + if err != nil { + return stat, err + } + } else { + return stat, fmt.Errorf("Invalid file type %s", typ) + } + } return stat, nil } +func parseDevice(typ string) (int, int, error) { + parts := strings.Split(typ, "-") + // If there are more than 3 parts, just ignore them to be forward compatible + if len(parts) < 3 { + return 0, 0, fmt.Errorf("Invalid device type %s", typ) + } + if parts[0] != "block" && parts[0] != "char" { + return 0, 0, fmt.Errorf("Invalid device type %s", typ) + } + major, err := strconv.Atoi(parts[1]) + if err != nil { + return 0, 0, fmt.Errorf("Failed to parse major number: %w", err) + } + minor, err := strconv.Atoi(parts[2]) + if err != nil { + return 0, 0, fmt.Errorf("Failed to parse minor number: %w", err) + } + return major, minor, nil +} + // SetContainersOverrideXattr will encode and set ContainersOverrideXattr. func SetContainersOverrideXattr(path string, stat Stat) error { - value := FormatContainersOverrideXattr(stat.IDs.UID, stat.IDs.GID, int(stat.Mode)) + value := FormatContainersOverrideXattrDevice(stat.IDs.UID, stat.IDs.GID, stat.Mode, stat.Major, stat.Minor) return system.Lsetxattr(path, ContainersOverrideXattr, []byte(value), 0) } func SafeChown(name string, uid, gid int) error { if runtime.GOOS == "darwin" { - var mode os.FileMode = 0o0700 + stat := Stat{ + Mode: os.FileMode(0o0700), + } xstat, err := system.Lgetxattr(name, ContainersOverrideXattr) - if err == nil { - attrs := strings.Split(string(xstat), ":") - if len(attrs) == 3 { - val, err := strconv.ParseUint(attrs[2], 8, 32) - if err == nil { - mode = os.FileMode(val) - } + if err == nil && xstat != nil { + stat, err = parseOverrideXattr(xstat) + if err != nil { + return err + } + } else { + st, err := os.Stat(name) // Ideally we would share this with system.Stat below, but then we would need to convert Mode. + if err != nil { + return err } + stat.Mode = st.Mode() } - value := Stat{IDPair{uid, gid}, mode} - if err = SetContainersOverrideXattr(name, value); err != nil { + stat.IDs = IDPair{UID: uid, GID: gid} + if err = SetContainersOverrideXattr(name, stat); err != nil { return err } uid = os.Getuid() @@ -453,19 +551,24 @@ func SafeChown(name string, uid, gid int) error { func SafeLchown(name string, uid, gid int) error { if runtime.GOOS == "darwin" { - var mode os.FileMode = 0o0700 + stat := Stat{ + Mode: os.FileMode(0o0700), + } xstat, err := system.Lgetxattr(name, ContainersOverrideXattr) - if err == nil { - attrs := strings.Split(string(xstat), ":") - if len(attrs) == 3 { - val, err := strconv.ParseUint(attrs[2], 8, 32) - if err == nil { - mode = os.FileMode(val) - } + if err == nil && xstat != nil { + stat, err = parseOverrideXattr(xstat) + if err != nil { + return err + } + } else { + st, err := os.Lstat(name) // Ideally we would share this with system.Stat below, but then we would need to convert Mode. + if err != nil { + return err } + stat.Mode = st.Mode() } - value := Stat{IDPair{uid, gid}, mode} - if err = SetContainersOverrideXattr(name, value); err != nil { + stat.IDs = IDPair{UID: uid, GID: gid} + if err = SetContainersOverrideXattr(name, stat); err != nil { return err } uid = os.Getuid() diff --git a/vendor/github.com/containers/storage/pkg/system/extattr_freebsd.go b/vendor/github.com/containers/storage/pkg/system/extattr_freebsd.go new file mode 100644 index 0000000000..1314058f17 --- /dev/null +++ b/vendor/github.com/containers/storage/pkg/system/extattr_freebsd.go @@ -0,0 +1,93 @@ +//go:build freebsd + +package system + +import ( + "os" + "unsafe" + + "golang.org/x/sys/unix" +) + +const ( + EXTATTR_NAMESPACE_EMPTY = unix.EXTATTR_NAMESPACE_EMPTY + EXTATTR_NAMESPACE_USER = unix.EXTATTR_NAMESPACE_USER + EXTATTR_NAMESPACE_SYSTEM = unix.EXTATTR_NAMESPACE_SYSTEM +) + +// ExtattrGetLink retrieves the value of the extended attribute identified by attrname +// in the given namespace and associated with the given path in the file system. +// If the path is a symbolic link, the extended attribute is retrieved from the link itself. +// Returns a []byte slice if the extattr is set and nil otherwise. +func ExtattrGetLink(path string, attrnamespace int, attrname string) ([]byte, error) { + size, errno := unix.ExtattrGetLink(path, attrnamespace, attrname, + uintptr(unsafe.Pointer(nil)), 0) + if errno != nil { + if errno == unix.ENOATTR { + return nil, nil + } + return nil, &os.PathError{Op: "extattr_get_link", Path: path, Err: errno} + } + if size == 0 { + return []byte{}, nil + } + + dest := make([]byte, size) + size, errno = unix.ExtattrGetLink(path, attrnamespace, attrname, + uintptr(unsafe.Pointer(&dest[0])), size) + if errno != nil { + return nil, &os.PathError{Op: "extattr_get_link", Path: path, Err: errno} + } + + return dest[:size], nil +} + +// ExtattrSetLink sets the value of extended attribute identified by attrname +// in the given namespace and associated with the given path in the file system. +// If the path is a symbolic link, the extended attribute is set on the link itself. +func ExtattrSetLink(path string, attrnamespace int, attrname string, data []byte) error { + if len(data) == 0 { + data = []byte{} // ensure non-nil for empty data + } + if _, errno := unix.ExtattrSetLink(path, attrnamespace, attrname, + uintptr(unsafe.Pointer(&data[0])), len(data)); errno != nil { + return &os.PathError{Op: "extattr_set_link", Path: path, Err: errno} + } + + return nil +} + +// ExtattrListLink lists extended attributes associated with the given path +// in the specified namespace. If the path is a symbolic link, the attributes +// are listed from the link itself. +func ExtattrListLink(path string, attrnamespace int) ([]string, error) { + size, errno := unix.ExtattrListLink(path, attrnamespace, + uintptr(unsafe.Pointer(nil)), 0) + if errno != nil { + return nil, &os.PathError{Op: "extattr_list_link", Path: path, Err: errno} + } + if size == 0 { + return []string{}, nil + } + + dest := make([]byte, size) + size, errno = unix.ExtattrListLink(path, attrnamespace, + uintptr(unsafe.Pointer(&dest[0])), size) + if errno != nil { + return nil, &os.PathError{Op: "extattr_list_link", Path: path, Err: errno} + } + + var attrs []string + for i := 0; i < size; { + // Each attribute is preceded by a single byte length + length := int(dest[i]) + i++ + if i+length > size { + break + } + attrs = append(attrs, string(dest[i:i+length])) + i += length + } + + return attrs, nil +} diff --git a/vendor/github.com/containers/storage/pkg/system/extattr_unsupported.go b/vendor/github.com/containers/storage/pkg/system/extattr_unsupported.go new file mode 100644 index 0000000000..07b67357f3 --- /dev/null +++ b/vendor/github.com/containers/storage/pkg/system/extattr_unsupported.go @@ -0,0 +1,24 @@ +//go:build !freebsd + +package system + +const ( + EXTATTR_NAMESPACE_EMPTY = 0 + EXTATTR_NAMESPACE_USER = 0 + EXTATTR_NAMESPACE_SYSTEM = 0 +) + +// ExtattrGetLink is not supported on platforms other than FreeBSD. +func ExtattrGetLink(path string, attrnamespace int, attrname string) ([]byte, error) { + return nil, ErrNotSupportedPlatform +} + +// ExtattrSetLink is not supported on platforms other than FreeBSD. +func ExtattrSetLink(path string, attrnamespace int, attrname string, data []byte) error { + return ErrNotSupportedPlatform +} + +// ExtattrListLink is not supported on platforms other than FreeBSD. +func ExtattrListLink(path string, attrnamespace int) ([]string, error) { + return nil, ErrNotSupportedPlatform +} diff --git a/vendor/github.com/containers/storage/pkg/system/stat_netbsd.go b/vendor/github.com/containers/storage/pkg/system/stat_netbsd.go new file mode 100644 index 0000000000..715f05b938 --- /dev/null +++ b/vendor/github.com/containers/storage/pkg/system/stat_netbsd.go @@ -0,0 +1,13 @@ +package system + +import "syscall" + +// fromStatT converts a syscall.Stat_t type to a system.Stat_t type +func fromStatT(s *syscall.Stat_t) (*StatT, error) { + return &StatT{size: s.Size, + mode: uint32(s.Mode), + uid: s.Uid, + gid: s.Gid, + rdev: uint64(s.Rdev), + mtim: s.Mtimespec}, nil +} diff --git a/vendor/github.com/containers/storage/pkg/system/xattrs_darwin.go b/vendor/github.com/containers/storage/pkg/system/xattrs_darwin.go index 75275b964e..27ada2083e 100644 --- a/vendor/github.com/containers/storage/pkg/system/xattrs_darwin.go +++ b/vendor/github.com/containers/storage/pkg/system/xattrs_darwin.go @@ -12,7 +12,7 @@ const ( E2BIG unix.Errno = unix.E2BIG // Operation not supported - EOPNOTSUPP unix.Errno = unix.EOPNOTSUPP + ENOTSUP unix.Errno = unix.ENOTSUP ) // Lgetxattr retrieves the value of the extended attribute identified by attr diff --git a/vendor/github.com/containers/storage/pkg/system/xattrs_freebsd.go b/vendor/github.com/containers/storage/pkg/system/xattrs_freebsd.go new file mode 100644 index 0000000000..5d653976e5 --- /dev/null +++ b/vendor/github.com/containers/storage/pkg/system/xattrs_freebsd.go @@ -0,0 +1,85 @@ +package system + +import ( + "strings" + + "golang.org/x/sys/unix" +) + +const ( + // Value is larger than the maximum size allowed + E2BIG unix.Errno = unix.E2BIG + + // Operation not supported + ENOTSUP unix.Errno = unix.ENOTSUP + + // Value is too small or too large for maximum size allowed + EOVERFLOW unix.Errno = unix.EOVERFLOW +) + +var ( + namespaceMap = map[string]int{ + "user": EXTATTR_NAMESPACE_USER, + "system": EXTATTR_NAMESPACE_SYSTEM, + } +) + +func xattrToExtattr(xattr string) (namespace int, extattr string, err error) { + namespaceName, extattr, found := strings.Cut(xattr, ".") + if !found { + return -1, "", ENOTSUP + } + + namespace, ok := namespaceMap[namespaceName] + if !ok { + return -1, "", ENOTSUP + } + return namespace, extattr, nil +} + +// Lgetxattr retrieves the value of the extended attribute identified by attr +// and associated with the given path in the file system. +// Returns a []byte slice if the xattr is set and nil otherwise. +func Lgetxattr(path string, attr string) ([]byte, error) { + namespace, extattr, err := xattrToExtattr(attr) + if err != nil { + return nil, err + } + return ExtattrGetLink(path, namespace, extattr) +} + +// Lsetxattr sets the value of the extended attribute identified by attr +// and associated with the given path in the file system. +func Lsetxattr(path string, attr string, value []byte, flags int) error { + if flags != 0 { + // FIXME: Flags are not supported on FreeBSD, but we can implement + // them mimicking the behavior of the Linux implementation. + // See lsetxattr(2) on Linux for more information. + return ENOTSUP + } + + namespace, extattr, err := xattrToExtattr(attr) + if err != nil { + return err + } + return ExtattrSetLink(path, namespace, extattr, value) +} + +// Llistxattr lists extended attributes associated with the given path +// in the file system. +func Llistxattr(path string) ([]string, error) { + attrs := []string{} + + for namespaceName, namespace := range namespaceMap { + namespaceAttrs, err := ExtattrListLink(path, namespace) + if err != nil { + return nil, err + } + + for _, attr := range namespaceAttrs { + attrs = append(attrs, namespaceName+"."+attr) + } + } + + return attrs, nil +} diff --git a/vendor/github.com/containers/storage/pkg/system/xattrs_linux.go b/vendor/github.com/containers/storage/pkg/system/xattrs_linux.go index 6b47c4e717..12462cca33 100644 --- a/vendor/github.com/containers/storage/pkg/system/xattrs_linux.go +++ b/vendor/github.com/containers/storage/pkg/system/xattrs_linux.go @@ -12,7 +12,7 @@ const ( E2BIG unix.Errno = unix.E2BIG // Operation not supported - EOPNOTSUPP unix.Errno = unix.EOPNOTSUPP + ENOTSUP unix.Errno = unix.ENOTSUP // Value is too small or too large for maximum size allowed EOVERFLOW unix.Errno = unix.EOVERFLOW diff --git a/vendor/github.com/containers/storage/pkg/system/xattrs_unsupported.go b/vendor/github.com/containers/storage/pkg/system/xattrs_unsupported.go index 0ab61f3def..66bf5858f6 100644 --- a/vendor/github.com/containers/storage/pkg/system/xattrs_unsupported.go +++ b/vendor/github.com/containers/storage/pkg/system/xattrs_unsupported.go @@ -1,4 +1,4 @@ -//go:build !linux && !darwin +//go:build !linux && !darwin && !freebsd package system @@ -9,7 +9,7 @@ const ( E2BIG syscall.Errno = syscall.Errno(0) // Operation not supported - EOPNOTSUPP syscall.Errno = syscall.Errno(0) + ENOTSUP syscall.Errno = syscall.Errno(0) // Value is too small or too large for maximum size allowed EOVERFLOW syscall.Errno = syscall.Errno(0) diff --git a/vendor/github.com/containers/storage/store.go b/vendor/github.com/containers/storage/store.go index a2d385008b..cd1cf861f6 100644 --- a/vendor/github.com/containers/storage/store.go +++ b/vendor/github.com/containers/storage/store.go @@ -20,6 +20,7 @@ import ( _ "github.com/containers/storage/drivers/register" drivers "github.com/containers/storage/drivers" + "github.com/containers/storage/internal/dedup" "github.com/containers/storage/pkg/archive" "github.com/containers/storage/pkg/directory" "github.com/containers/storage/pkg/idtools" @@ -166,6 +167,26 @@ type flaggableStore interface { type StoreOptions = types.StoreOptions +type DedupHashMethod = dedup.DedupHashMethod + +const ( + DedupHashInvalid = dedup.DedupHashInvalid + DedupHashCRC = dedup.DedupHashCRC + DedupHashFileSize = dedup.DedupHashFileSize + DedupHashSHA256 = dedup.DedupHashSHA256 +) + +type ( + DedupOptions = dedup.DedupOptions + DedupResult = dedup.DedupResult +) + +// DedupArgs is used to pass arguments to the Dedup command. +type DedupArgs struct { + // Options that are passed directly to the internal/dedup.DedupDirs function. + Options DedupOptions +} + // Store wraps up the various types of file-based stores that we use into a // singleton object that initializes and manages them all together. type Store interface { @@ -589,6 +610,9 @@ type Store interface { // MultiList returns consistent values as of a single point in time. // WARNING: The values may already be out of date by the time they are returned to the caller. MultiList(MultiListOptions) (MultiListResult, error) + + // Dedup deduplicates layers in the store. + Dedup(DedupArgs) (drivers.DedupResult, error) } // AdditionalLayer represents a layer that is contained in the additional layer store @@ -3843,3 +3867,43 @@ func (s *store) MultiList(options MultiListOptions) (MultiListResult, error) { } return out, nil } + +// Dedup deduplicates layers in the store. +func (s *store) Dedup(req DedupArgs) (drivers.DedupResult, error) { + imgs, err := s.Images() + if err != nil { + return drivers.DedupResult{}, err + } + var topLayers []string + for _, i := range imgs { + topLayers = append(topLayers, i.TopLayer) + topLayers = append(topLayers, i.MappedTopLayers...) + } + return writeToLayerStore(s, func(rlstore rwLayerStore) (drivers.DedupResult, error) { + layers := make(map[string]struct{}) + for _, i := range topLayers { + cur := i + for cur != "" { + if _, visited := layers[cur]; visited { + break + } + l, err := rlstore.Get(cur) + if err != nil { + if err == ErrLayerUnknown { + break + } + return drivers.DedupResult{}, err + } + layers[cur] = struct{}{} + cur = l.Parent + } + } + r := drivers.DedupArgs{ + Options: req.Options, + } + for l := range layers { + r.Layers = append(r.Layers, l) + } + return rlstore.dedup(r) + }) +} diff --git a/vendor/github.com/docker/distribution/registry/client/auth/challenge/addr.go b/vendor/github.com/docker/distribution/registry/client/auth/challenge/addr.go deleted file mode 100644 index 2c3ebe1653..0000000000 --- a/vendor/github.com/docker/distribution/registry/client/auth/challenge/addr.go +++ /dev/null @@ -1,27 +0,0 @@ -package challenge - -import ( - "net/url" - "strings" -) - -// FROM: https://golang.org/src/net/http/http.go -// Given a string of the form "host", "host:port", or "[ipv6::address]:port", -// return true if the string includes a port. -func hasPort(s string) bool { return strings.LastIndex(s, ":") > strings.LastIndex(s, "]") } - -// FROM: http://golang.org/src/net/http/transport.go -var portMap = map[string]string{ - "http": "80", - "https": "443", -} - -// canonicalAddr returns url.Host but always with a ":port" suffix -// FROM: http://golang.org/src/net/http/transport.go -func canonicalAddr(url *url.URL) string { - addr := url.Host - if !hasPort(addr) { - return addr + ":" + portMap[url.Scheme] - } - return addr -} diff --git a/vendor/github.com/docker/distribution/registry/client/auth/challenge/authchallenge.go b/vendor/github.com/docker/distribution/registry/client/auth/challenge/authchallenge.go deleted file mode 100644 index fe238210cd..0000000000 --- a/vendor/github.com/docker/distribution/registry/client/auth/challenge/authchallenge.go +++ /dev/null @@ -1,237 +0,0 @@ -package challenge - -import ( - "fmt" - "net/http" - "net/url" - "strings" - "sync" -) - -// Challenge carries information from a WWW-Authenticate response header. -// See RFC 2617. -type Challenge struct { - // Scheme is the auth-scheme according to RFC 2617 - Scheme string - - // Parameters are the auth-params according to RFC 2617 - Parameters map[string]string -} - -// Manager manages the challenges for endpoints. -// The challenges are pulled out of HTTP responses. Only -// responses which expect challenges should be added to -// the manager, since a non-unauthorized request will be -// viewed as not requiring challenges. -type Manager interface { - // GetChallenges returns the challenges for the given - // endpoint URL. - GetChallenges(endpoint url.URL) ([]Challenge, error) - - // AddResponse adds the response to the challenge - // manager. The challenges will be parsed out of - // the WWW-Authenicate headers and added to the - // URL which was produced the response. If the - // response was authorized, any challenges for the - // endpoint will be cleared. - AddResponse(resp *http.Response) error -} - -// NewSimpleManager returns an instance of -// Manger which only maps endpoints to challenges -// based on the responses which have been added the -// manager. The simple manager will make no attempt to -// perform requests on the endpoints or cache the responses -// to a backend. -func NewSimpleManager() Manager { - return &simpleManager{ - Challenges: make(map[string][]Challenge), - } -} - -type simpleManager struct { - sync.RWMutex - Challenges map[string][]Challenge -} - -func normalizeURL(endpoint *url.URL) { - endpoint.Host = strings.ToLower(endpoint.Host) - endpoint.Host = canonicalAddr(endpoint) -} - -func (m *simpleManager) GetChallenges(endpoint url.URL) ([]Challenge, error) { - normalizeURL(&endpoint) - - m.RLock() - defer m.RUnlock() - challenges := m.Challenges[endpoint.String()] - return challenges, nil -} - -func (m *simpleManager) AddResponse(resp *http.Response) error { - challenges := ResponseChallenges(resp) - if resp.Request == nil { - return fmt.Errorf("missing request reference") - } - urlCopy := url.URL{ - Path: resp.Request.URL.Path, - Host: resp.Request.URL.Host, - Scheme: resp.Request.URL.Scheme, - } - normalizeURL(&urlCopy) - - m.Lock() - defer m.Unlock() - m.Challenges[urlCopy.String()] = challenges - return nil -} - -// Octet types from RFC 2616. -type octetType byte - -var octetTypes [256]octetType - -const ( - isToken octetType = 1 << iota - isSpace -) - -func init() { - // OCTET = - // CHAR = - // CTL = - // CR = - // LF = - // SP = - // HT = - // <"> = - // CRLF = CR LF - // LWS = [CRLF] 1*( SP | HT ) - // TEXT = - // separators = "(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\" | <"> - // | "/" | "[" | "]" | "?" | "=" | "{" | "}" | SP | HT - // token = 1* - // qdtext = > - - for c := 0; c < 256; c++ { - var t octetType - isCtl := c <= 31 || c == 127 - isChar := 0 <= c && c <= 127 - isSeparator := strings.ContainsRune(" \t\"(),/:;<=>?@[]\\{}", rune(c)) - if strings.ContainsRune(" \t\r\n", rune(c)) { - t |= isSpace - } - if isChar && !isCtl && !isSeparator { - t |= isToken - } - octetTypes[c] = t - } -} - -// ResponseChallenges returns a list of authorization challenges -// for the given http Response. Challenges are only checked if -// the response status code was a 401. -func ResponseChallenges(resp *http.Response) []Challenge { - if resp.StatusCode == http.StatusUnauthorized { - // Parse the WWW-Authenticate Header and store the challenges - // on this endpoint object. - return parseAuthHeader(resp.Header) - } - - return nil -} - -func parseAuthHeader(header http.Header) []Challenge { - challenges := []Challenge{} - for _, h := range header[http.CanonicalHeaderKey("WWW-Authenticate")] { - v, p := parseValueAndParams(h) - if v != "" { - challenges = append(challenges, Challenge{Scheme: v, Parameters: p}) - } - } - return challenges -} - -func parseValueAndParams(header string) (value string, params map[string]string) { - params = make(map[string]string) - value, s := expectToken(header) - if value == "" { - return - } - value = strings.ToLower(value) - s = "," + skipSpace(s) - for strings.HasPrefix(s, ",") { - var pkey string - pkey, s = expectToken(skipSpace(s[1:])) - if pkey == "" { - return - } - if !strings.HasPrefix(s, "=") { - return - } - var pvalue string - pvalue, s = expectTokenOrQuoted(s[1:]) - if pvalue == "" { - return - } - pkey = strings.ToLower(pkey) - params[pkey] = pvalue - s = skipSpace(s) - } - return -} - -func skipSpace(s string) (rest string) { - i := 0 - for ; i < len(s); i++ { - if octetTypes[s[i]]&isSpace == 0 { - break - } - } - return s[i:] -} - -func expectToken(s string) (token, rest string) { - i := 0 - for ; i < len(s); i++ { - if octetTypes[s[i]]&isToken == 0 { - break - } - } - return s[:i], s[i:] -} - -func expectTokenOrQuoted(s string) (value string, rest string) { - if !strings.HasPrefix(s, "\"") { - return expectToken(s) - } - s = s[1:] - for i := 0; i < len(s); i++ { - switch s[i] { - case '"': - return s[:i], s[i+1:] - case '\\': - p := make([]byte, len(s)-1) - j := copy(p, s[:i]) - escape := true - for i = i + 1; i < len(s); i++ { - b := s[i] - switch { - case escape: - escape = false - p[j] = b - j++ - case b == '\\': - escape = true - case b == '"': - return string(p[:j]), s[i+1:] - default: - p[j] = b - j++ - } - } - return "", "" - } - } - return "", "" -} diff --git a/vendor/github.com/proglottis/gpgme/.gitignore b/vendor/github.com/proglottis/gpgme/.gitignore index 0210b26e03..bc485bfc5b 100644 --- a/vendor/github.com/proglottis/gpgme/.gitignore +++ b/vendor/github.com/proglottis/gpgme/.gitignore @@ -1 +1,3 @@ testdata/gpghome/random_seed +testdata/gpghome/.gpg-v21-migrated +testdata/gpghome/private-keys-v1.d/ diff --git a/vendor/github.com/proglottis/gpgme/callbacks.go b/vendor/github.com/proglottis/gpgme/callbacks.go deleted file mode 100644 index d1dc610d42..0000000000 --- a/vendor/github.com/proglottis/gpgme/callbacks.go +++ /dev/null @@ -1,42 +0,0 @@ -package gpgme - -import ( - "sync" -) - -var callbacks struct { - sync.Mutex - m map[uintptr]interface{} - c uintptr -} - -func callbackAdd(v interface{}) uintptr { - callbacks.Lock() - defer callbacks.Unlock() - if callbacks.m == nil { - callbacks.m = make(map[uintptr]interface{}) - } - callbacks.c++ - ret := callbacks.c - callbacks.m[ret] = v - return ret -} - -func callbackLookup(c uintptr) interface{} { - callbacks.Lock() - defer callbacks.Unlock() - ret := callbacks.m[c] - if ret == nil { - panic("callback pointer not found") - } - return ret -} - -func callbackDelete(c uintptr) { - callbacks.Lock() - defer callbacks.Unlock() - if callbacks.m[c] == nil { - panic("callback pointer not found") - } - delete(callbacks.m, c) -} diff --git a/vendor/github.com/proglottis/gpgme/data.go b/vendor/github.com/proglottis/gpgme/data.go index eee32c0323..0e81c36d66 100644 --- a/vendor/github.com/proglottis/gpgme/data.go +++ b/vendor/github.com/proglottis/gpgme/data.go @@ -10,6 +10,7 @@ import ( "io" "os" "runtime" + "runtime/cgo" "unsafe" ) @@ -19,30 +20,32 @@ const ( SeekEnd = C.SEEK_END ) +var dataCallbacks = C.struct_gpgme_data_cbs{ + read: C.gpgme_data_read_cb_t(C.gogpgme_readfunc), + write: C.gpgme_data_write_cb_t(C.gogpgme_writefunc), + seek: C.gpgme_data_seek_cb_t(C.gogpgme_seekfunc), +} + //export gogpgme_readfunc func gogpgme_readfunc(handle, buffer unsafe.Pointer, size C.size_t) C.ssize_t { - d := callbackLookup(uintptr(handle)).(*Data) - if len(d.buf) < int(size) { - d.buf = make([]byte, size) - } - n, err := d.r.Read(d.buf[:size]) + h := *(*cgo.Handle)(handle) + d := h.Value().(*Data) + n, err := d.r.Read(unsafe.Slice((*byte)(buffer), size)) if err != nil && err != io.EOF { + d.err = err C.gpgme_err_set_errno(C.EIO) return -1 } - C.memcpy(buffer, unsafe.Pointer(&d.buf[0]), C.size_t(n)) return C.ssize_t(n) } //export gogpgme_writefunc func gogpgme_writefunc(handle, buffer unsafe.Pointer, size C.size_t) C.ssize_t { - d := callbackLookup(uintptr(handle)).(*Data) - if len(d.buf) < int(size) { - d.buf = make([]byte, size) - } - C.memcpy(unsafe.Pointer(&d.buf[0]), buffer, C.size_t(size)) - n, err := d.w.Write(d.buf[:size]) + h := *(*cgo.Handle)(handle) + d := h.Value().(*Data) + n, err := d.w.Write(unsafe.Slice((*byte)(buffer), size)) if err != nil && err != io.EOF { + d.err = err C.gpgme_err_set_errno(C.EIO) return -1 } @@ -51,9 +54,11 @@ func gogpgme_writefunc(handle, buffer unsafe.Pointer, size C.size_t) C.ssize_t { //export gogpgme_seekfunc func gogpgme_seekfunc(handle unsafe.Pointer, offset C.gpgme_off_t, whence C.int) C.gpgme_off_t { - d := callbackLookup(uintptr(handle)).(*Data) + h := *(*cgo.Handle)(handle) + d := h.Value().(*Data) n, err := d.s.Seek(int64(offset), int(whence)) if err != nil { + d.err = err C.gpgme_err_set_errno(C.EIO) return -1 } @@ -63,12 +68,11 @@ func gogpgme_seekfunc(handle unsafe.Pointer, offset C.gpgme_off_t, whence C.int) // The Data buffer used to communicate with GPGME type Data struct { dh C.gpgme_data_t // WARNING: Call runtime.KeepAlive(d) after ANY passing of d.dh to C - buf []byte - cbs C.struct_gpgme_data_cbs r io.Reader w io.Writer s io.Seeker - cbc uintptr // WARNING: Call runtime.KeepAlive(d) after ANY use of d.cbc in C (typically via d.dh) + cbc cgo.Handle // WARNING: Call runtime.KeepAlive(d) after ANY use of d.cbc in C (typically via d.dh) + err error } func newData() *Data { @@ -86,6 +90,7 @@ func NewData() (*Data, error) { // NewDataFile returns a new file based data buffer func NewDataFile(f *os.File) (*Data, error) { d := newData() + d.r = f return d, handleError(C.gpgme_data_new_from_fd(&d.dh, C.int(f.Fd()))) } @@ -103,20 +108,22 @@ func NewDataBytes(b []byte) (*Data, error) { func NewDataReader(r io.Reader) (*Data, error) { d := newData() d.r = r - d.cbs.read = C.gpgme_data_read_cb_t(C.gogpgme_readfunc) - cbc := callbackAdd(d) - d.cbc = cbc - return d, handleError(C.gogpgme_data_new_from_cbs(&d.dh, &d.cbs, C.uintptr_t(cbc))) + if s, ok := r.(io.Seeker); ok { + d.s = s + } + d.cbc = cgo.NewHandle(d) + return d, handleError(C.gpgme_data_new_from_cbs(&d.dh, &dataCallbacks, unsafe.Pointer(&d.cbc))) } // NewDataWriter returns a new callback based data buffer func NewDataWriter(w io.Writer) (*Data, error) { d := newData() d.w = w - d.cbs.write = C.gpgme_data_write_cb_t(C.gogpgme_writefunc) - cbc := callbackAdd(d) - d.cbc = cbc - return d, handleError(C.gogpgme_data_new_from_cbs(&d.dh, &d.cbs, C.uintptr_t(cbc))) + if s, ok := w.(io.Seeker); ok { + d.s = s + } + d.cbc = cgo.NewHandle(d) + return d, handleError(C.gpgme_data_new_from_cbs(&d.dh, &dataCallbacks, unsafe.Pointer(&d.cbc))) } // NewDataReadWriter returns a new callback based data buffer @@ -124,11 +131,11 @@ func NewDataReadWriter(rw io.ReadWriter) (*Data, error) { d := newData() d.r = rw d.w = rw - d.cbs.read = C.gpgme_data_read_cb_t(C.gogpgme_readfunc) - d.cbs.write = C.gpgme_data_write_cb_t(C.gogpgme_writefunc) - cbc := callbackAdd(d) - d.cbc = cbc - return d, handleError(C.gogpgme_data_new_from_cbs(&d.dh, &d.cbs, C.uintptr_t(cbc))) + if s, ok := rw.(io.Seeker); ok { + d.s = s + } + d.cbc = cgo.NewHandle(d) + return d, handleError(C.gpgme_data_new_from_cbs(&d.dh, &dataCallbacks, unsafe.Pointer(&d.cbc))) } // NewDataReadWriteSeeker returns a new callback based data buffer @@ -137,12 +144,8 @@ func NewDataReadWriteSeeker(rw io.ReadWriteSeeker) (*Data, error) { d.r = rw d.w = rw d.s = rw - d.cbs.read = C.gpgme_data_read_cb_t(C.gogpgme_readfunc) - d.cbs.write = C.gpgme_data_write_cb_t(C.gogpgme_writefunc) - d.cbs.seek = C.gpgme_data_seek_cb_t(C.gogpgme_seekfunc) - cbc := callbackAdd(d) - d.cbc = cbc - return d, handleError(C.gogpgme_data_new_from_cbs(&d.dh, &d.cbs, C.uintptr_t(cbc))) + d.cbc = cgo.NewHandle(d) + return d, handleError(C.gpgme_data_new_from_cbs(&d.dh, &dataCallbacks, unsafe.Pointer(&d.cbc))) } // Close releases any resources associated with the data buffer @@ -151,7 +154,7 @@ func (d *Data) Close() error { return nil } if d.cbc > 0 { - callbackDelete(d.cbc) + d.cbc.Delete() } _, err := C.gpgme_data_release(d.dh) runtime.KeepAlive(d) @@ -160,24 +163,42 @@ func (d *Data) Close() error { } func (d *Data) Write(p []byte) (int, error) { - n, err := C.gpgme_data_write(d.dh, unsafe.Pointer(&p[0]), C.size_t(len(p))) + var buffer *byte + if len(p) > 0 { + buffer = &p[0] + } + + n, err := C.gpgme_data_write(d.dh, unsafe.Pointer(buffer), C.size_t(len(p))) runtime.KeepAlive(d) - if err != nil { + switch { + case d.err != nil: + defer func() { d.err = nil }() + + return 0, d.err + case err != nil: return 0, err - } - if n == 0 { + case len(p) > 0 && n == 0: return 0, io.EOF } return int(n), nil } func (d *Data) Read(p []byte) (int, error) { - n, err := C.gpgme_data_read(d.dh, unsafe.Pointer(&p[0]), C.size_t(len(p))) + var buffer *byte + if len(p) > 0 { + buffer = &p[0] + } + + n, err := C.gpgme_data_read(d.dh, unsafe.Pointer(buffer), C.size_t(len(p))) runtime.KeepAlive(d) - if err != nil { + switch { + case d.err != nil: + defer func() { d.err = nil }() + + return 0, d.err + case err != nil: return 0, err - } - if n == 0 { + case len(p) > 0 && n == 0: return 0, io.EOF } return int(n), nil @@ -186,7 +207,15 @@ func (d *Data) Read(p []byte) (int, error) { func (d *Data) Seek(offset int64, whence int) (int64, error) { n, err := C.gogpgme_data_seek(d.dh, C.gpgme_off_t(offset), C.int(whence)) runtime.KeepAlive(d) - return int64(n), err + switch { + case d.err != nil: + defer func() { d.err = nil }() + + return 0, d.err + case err != nil: + return 0, err + } + return int64(n), nil } // Name returns the associated filename if any diff --git a/vendor/github.com/proglottis/gpgme/go_gpgme.c b/vendor/github.com/proglottis/gpgme/go_gpgme.c index 00da3ab304..f5b38ce7ed 100644 --- a/vendor/github.com/proglottis/gpgme/go_gpgme.c +++ b/vendor/github.com/proglottis/gpgme/go_gpgme.c @@ -1,13 +1,5 @@ #include "go_gpgme.h" -gpgme_error_t gogpgme_data_new_from_cbs(gpgme_data_t *dh, gpgme_data_cbs_t cbs, uintptr_t handle) { - return gpgme_data_new_from_cbs(dh, cbs, (void *)handle); -} - -void gogpgme_set_passphrase_cb(gpgme_ctx_t ctx, gpgme_passphrase_cb_t cb, uintptr_t handle) { - gpgme_set_passphrase_cb(ctx, cb, (void *)handle); -} - gpgme_off_t gogpgme_data_seek(gpgme_data_t dh, gpgme_off_t offset, int whence) { return gpgme_data_seek(dh, offset, whence); } @@ -15,17 +7,17 @@ gpgme_off_t gogpgme_data_seek(gpgme_data_t dh, gpgme_off_t offset, int whence) { gpgme_error_t gogpgme_op_assuan_transact_ext( gpgme_ctx_t ctx, char* cmd, - uintptr_t data_h, - uintptr_t inquiry_h, - uintptr_t status_h, + void* data_h, + void* inquiry_h, + void* status_h, gpgme_error_t *operr ){ return gpgme_op_assuan_transact_ext( ctx, cmd, - (gpgme_assuan_data_cb_t) gogpgme_assuan_data_callback, (void *)data_h, - (gpgme_assuan_inquire_cb_t) gogpgme_assuan_inquiry_callback, (void *)inquiry_h, - (gpgme_assuan_status_cb_t) gogpgme_assuan_status_callback, (void *)status_h, + (gpgme_assuan_data_cb_t) gogpgme_assuan_data_callback, data_h, + (gpgme_assuan_inquire_cb_t) gogpgme_assuan_inquiry_callback, inquiry_h, + (gpgme_assuan_status_cb_t) gogpgme_assuan_status_callback, status_h, operr ); } diff --git a/vendor/github.com/proglottis/gpgme/go_gpgme.h b/vendor/github.com/proglottis/gpgme/go_gpgme.h index eb3a4ba88b..8eb5fddd9f 100644 --- a/vendor/github.com/proglottis/gpgme/go_gpgme.h +++ b/vendor/github.com/proglottis/gpgme/go_gpgme.h @@ -10,11 +10,9 @@ extern ssize_t gogpgme_readfunc(void *handle, void *buffer, size_t size); extern ssize_t gogpgme_writefunc(void *handle, void *buffer, size_t size); extern off_t gogpgme_seekfunc(void *handle, off_t offset, int whence); extern gpgme_error_t gogpgme_passfunc(void *hook, char *uid_hint, char *passphrase_info, int prev_was_bad, int fd); -extern gpgme_error_t gogpgme_data_new_from_cbs(gpgme_data_t *dh, gpgme_data_cbs_t cbs, uintptr_t handle); -extern void gogpgme_set_passphrase_cb(gpgme_ctx_t ctx, gpgme_passphrase_cb_t cb, uintptr_t handle); extern gpgme_off_t gogpgme_data_seek(gpgme_data_t dh, gpgme_off_t offset, int whence); -extern gpgme_error_t gogpgme_op_assuan_transact_ext(gpgme_ctx_t ctx, char *cmd, uintptr_t data_h, uintptr_t inquiry_h , uintptr_t status_h, gpgme_error_t *operr); +extern gpgme_error_t gogpgme_op_assuan_transact_ext(gpgme_ctx_t ctx, char *cmd, void *data_h, void *inquiry_h , void *status_h, gpgme_error_t *operr); extern gpgme_error_t gogpgme_assuan_data_callback(void *opaque, void* data, size_t datalen ); extern gpgme_error_t gogpgme_assuan_inquiry_callback(void *opaque, char* name, char* args); diff --git a/vendor/github.com/proglottis/gpgme/gpgme.go b/vendor/github.com/proglottis/gpgme/gpgme.go index 8f495ddf5d..15af69c865 100644 --- a/vendor/github.com/proglottis/gpgme/gpgme.go +++ b/vendor/github.com/proglottis/gpgme/gpgme.go @@ -7,11 +7,13 @@ package gpgme // #include // #include "go_gpgme.h" import "C" + import ( "fmt" "io" "os" "runtime" + "runtime/cgo" "time" "unsafe" ) @@ -27,7 +29,8 @@ type Callback func(uidHint string, prevWasBad bool, f *os.File) error //export gogpgme_passfunc func gogpgme_passfunc(hook unsafe.Pointer, uid_hint, passphrase_info *C.char, prev_was_bad, fd C.int) C.gpgme_error_t { - c := callbackLookup(uintptr(hook)).(*Context) + h := *(*cgo.Handle)(hook) + c := h.Value().(*Context) go_uid_hint := C.GoString(uid_hint) f := os.NewFile(uintptr(fd), go_uid_hint) defer f.Close() @@ -233,6 +236,17 @@ func SetEngineInfo(proto Protocol, fileName, homeDir string) error { return handleError(C.gpgme_set_engine_info(C.gpgme_protocol_t(proto), cfn, chome)) } +func GetDirInfo(what string) string { + cwhat := C.CString(what) + defer C.free(unsafe.Pointer(cwhat)) + + cdir := C.gpgme_get_dirinfo(cwhat) + if cdir == nil { + return "" + } + return C.GoString(cdir) +} + func FindKeys(pattern string, secretOnly bool) ([]*Key, error) { var keys []*Key ctx, err := New() @@ -243,7 +257,7 @@ func FindKeys(pattern string, secretOnly bool) ([]*Key, error) { if err := ctx.KeyListStart(pattern, secretOnly); err != nil { return keys, err } - defer ctx.KeyListEnd() + defer func() { _ = ctx.KeyListEnd() }() for ctx.KeyListNext() { keys = append(keys, ctx.Key) } @@ -268,8 +282,10 @@ func Decrypt(r io.Reader) (*Data, error) { if err != nil { return nil, err } - err = ctx.Decrypt(cipher, plain) - plain.Seek(0, SeekSet) + if err := ctx.Decrypt(cipher, plain); err != nil { + return nil, err + } + _, err = plain.Seek(0, SeekSet) return plain, err } @@ -278,7 +294,7 @@ type Context struct { KeyError error callback Callback - cbc uintptr // WARNING: Call runtime.KeepAlive(c) after ANY use of c.cbc in C (typically via c.ctx) + cbc cgo.Handle // WARNING: Call runtime.KeepAlive(c) after ANY use of c.cbc in C (typically via c.ctx) ctx C.gpgme_ctx_t // WARNING: Call runtime.KeepAlive(c) after ANY passing of c.ctx to C } @@ -295,7 +311,7 @@ func (c *Context) Release() { return } if c.cbc > 0 { - callbackDelete(c.cbc) + c.cbc.Delete() } C.gpgme_release(c.ctx) runtime.KeepAlive(c) @@ -364,15 +380,14 @@ func (c *Context) SetCallback(callback Callback) error { var err error c.callback = callback if c.cbc > 0 { - callbackDelete(c.cbc) + c.cbc.Delete() } if callback != nil { - cbc := callbackAdd(c) - c.cbc = cbc - _, err = C.gogpgme_set_passphrase_cb(c.ctx, C.gpgme_passphrase_cb_t(C.gogpgme_passfunc), C.uintptr_t(cbc)) + c.cbc = cgo.NewHandle(c) + _, err = C.gpgme_set_passphrase_cb(c.ctx, C.gpgme_passphrase_cb_t(C.gogpgme_passfunc), unsafe.Pointer(&c.cbc)) } else { c.cbc = 0 - _, err = C.gogpgme_set_passphrase_cb(c.ctx, nil, 0) + _, err = C.gpgme_set_passphrase_cb(c.ctx, nil, nil) } runtime.KeepAlive(c) return err @@ -564,9 +579,11 @@ func (c *Context) Sign(signers []*Key, plain, sig *Data, mode SigMode) error { return err } -type AssuanDataCallback func(data []byte) error -type AssuanInquireCallback func(name, args string) error -type AssuanStatusCallback func(status, args string) error +type ( + AssuanDataCallback func(data []byte) error + AssuanInquireCallback func(name, args string) error + AssuanStatusCallback func(status, args string) error +) // AssuanSend sends a raw Assuan command to gpg-agent func (c *Context) AssuanSend( @@ -577,17 +594,17 @@ func (c *Context) AssuanSend( ) error { var operr C.gpgme_error_t - dataPtr := callbackAdd(&data) - inquiryPtr := callbackAdd(&inquiry) - statusPtr := callbackAdd(&status) + dataPtr := cgo.NewHandle(&data) + inquiryPtr := cgo.NewHandle(&inquiry) + statusPtr := cgo.NewHandle(&status) cmdCStr := C.CString(cmd) defer C.free(unsafe.Pointer(cmdCStr)) err := C.gogpgme_op_assuan_transact_ext( c.ctx, cmdCStr, - C.uintptr_t(dataPtr), - C.uintptr_t(inquiryPtr), - C.uintptr_t(statusPtr), + unsafe.Pointer(&dataPtr), + unsafe.Pointer(&inquiryPtr), + unsafe.Pointer(&statusPtr), &operr, ) runtime.KeepAlive(c) @@ -600,11 +617,14 @@ func (c *Context) AssuanSend( //export gogpgme_assuan_data_callback func gogpgme_assuan_data_callback(handle unsafe.Pointer, data unsafe.Pointer, datalen C.size_t) C.gpgme_error_t { - c := callbackLookup(uintptr(handle)).(*AssuanDataCallback) + h := *(*cgo.Handle)(handle) + c := h.Value().(*AssuanDataCallback) if *c == nil { return 0 } - (*c)(C.GoBytes(data, C.int(datalen))) + if err := (*c)(C.GoBytes(data, C.int(datalen))); err != nil { + return C.gpgme_error(C.GPG_ERR_USER_1) + } return 0 } @@ -612,11 +632,14 @@ func gogpgme_assuan_data_callback(handle unsafe.Pointer, data unsafe.Pointer, da func gogpgme_assuan_inquiry_callback(handle unsafe.Pointer, cName *C.char, cArgs *C.char) C.gpgme_error_t { name := C.GoString(cName) args := C.GoString(cArgs) - c := callbackLookup(uintptr(handle)).(*AssuanInquireCallback) + h := *(*cgo.Handle)(handle) + c := h.Value().(*AssuanInquireCallback) if *c == nil { return 0 } - (*c)(name, args) + if err := (*c)(name, args); err != nil { + return C.gpgme_error(C.GPG_ERR_USER_1) + } return 0 } @@ -624,11 +647,14 @@ func gogpgme_assuan_inquiry_callback(handle unsafe.Pointer, cName *C.char, cArgs func gogpgme_assuan_status_callback(handle unsafe.Pointer, cStatus *C.char, cArgs *C.char) C.gpgme_error_t { status := C.GoString(cStatus) args := C.GoString(cArgs) - c := callbackLookup(uintptr(handle)).(*AssuanStatusCallback) + h := *(*cgo.Handle)(handle) + c := h.Value().(*AssuanStatusCallback) if *c == nil { return 0 } - (*c)(status, args) + if err := (*c)(status, args); err != nil { + return C.gpgme_error(C.GPG_ERR_USER_1) + } return 0 } diff --git a/vendor/github.com/proglottis/gpgme/unset_agent_info.go b/vendor/github.com/proglottis/gpgme/unset_agent_info.go index 986aca59f6..8add8ec877 100644 --- a/vendor/github.com/proglottis/gpgme/unset_agent_info.go +++ b/vendor/github.com/proglottis/gpgme/unset_agent_info.go @@ -1,3 +1,4 @@ +//go:build !windows // +build !windows package gpgme diff --git a/vendor/github.com/sigstore/sigstore/pkg/oauthflow/interactive.go b/vendor/github.com/sigstore/sigstore/pkg/oauthflow/interactive.go index dfc1f0c0e8..6714b3488e 100644 --- a/vendor/github.com/sigstore/sigstore/pkg/oauthflow/interactive.go +++ b/vendor/github.com/sigstore/sigstore/pkg/oauthflow/interactive.go @@ -134,7 +134,7 @@ func (i *InteractiveIDTokenGetter) doOobFlow(cfg *oauth2.Config, stateToken stri fmt.Fprintln(i.GetOutput(), "Go to the following link in a browser:\n\n\t", authURL) fmt.Fprintf(i.GetOutput(), "Enter verification code: ") var code string - fmt.Fscanf(i.GetInput(), "%s", &code) + _, _ = fmt.Fscanf(i.GetInput(), "%s", &code) // New line in case read input doesn't move cursor to next line. fmt.Fprintln(i.GetOutput()) return code diff --git a/vendor/github.com/sylabs/sif/v2/pkg/sif/create.go b/vendor/github.com/sylabs/sif/v2/pkg/sif/create.go index 91dd430c1c..6fc80512fd 100644 --- a/vendor/github.com/sylabs/sif/v2/pkg/sif/create.go +++ b/vendor/github.com/sylabs/sif/v2/pkg/sif/create.go @@ -23,21 +23,19 @@ var errAlignmentOverflow = errors.New("integer overflow when calculating alignme // nextAligned finds the next offset that satisfies alignment. func nextAligned(offset int64, alignment int) (int64, error) { - align64 := uint64(alignment) - offset64 := uint64(offset) + align64 := int64(alignment) - if align64 <= 0 || offset64%align64 == 0 { + if align64 <= 0 || offset%align64 == 0 { return offset, nil } - offset64 += (align64 - offset64%align64) + align64 -= offset % align64 - if offset64 > math.MaxInt64 { + if (math.MaxInt64 - offset) < align64 { return 0, errAlignmentOverflow } - //nolint:gosec // Overflow handled above. - return int64(offset64), nil + return offset + align64, nil } // writeDataObjectAt writes the data object described by di to ws, using time t, recording details diff --git a/vendor/golang.org/x/oauth2/README.md b/vendor/golang.org/x/oauth2/README.md index 781770c204..48dbb9d84c 100644 --- a/vendor/golang.org/x/oauth2/README.md +++ b/vendor/golang.org/x/oauth2/README.md @@ -5,15 +5,6 @@ oauth2 package contains a client implementation for OAuth 2.0 spec. -## Installation - -~~~~ -go get golang.org/x/oauth2 -~~~~ - -Or you can manually git clone the repository to -`$(go env GOPATH)/src/golang.org/x/oauth2`. - See pkg.go.dev for further documentation and examples. * [pkg.go.dev/golang.org/x/oauth2](https://pkg.go.dev/golang.org/x/oauth2) @@ -33,7 +24,11 @@ The main issue tracker for the oauth2 repository is located at https://github.com/golang/oauth2/issues. This repository uses Gerrit for code changes. To learn how to submit changes to -this repository, see https://golang.org/doc/contribute.html. In particular: +this repository, see https://go.dev/doc/contribute. + +The git repository is https://go.googlesource.com/oauth2. + +Note: * Excluding trivial changes, all contributions should be connected to an existing issue. * API changes must go through the [change proposal process](https://go.dev/s/proposal-process) before they can be accepted. diff --git a/vendor/modules.txt b/vendor/modules.txt index f2fba95b14..36f2c1420e 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -125,11 +125,11 @@ github.com/containerd/log # github.com/containerd/platforms v0.2.1 ## explicit; go 1.20 github.com/containerd/platforms -# github.com/containerd/stargz-snapshotter/estargz v0.15.1 -## explicit; go 1.19 +# github.com/containerd/stargz-snapshotter/estargz v0.16.3 +## explicit; go 1.22.0 github.com/containerd/stargz-snapshotter/estargz github.com/containerd/stargz-snapshotter/estargz/errorutil -# github.com/containerd/typeurl/v2 v2.2.0 +# github.com/containerd/typeurl/v2 v2.2.3 ## explicit; go 1.21 github.com/containerd/typeurl/v2 # github.com/containernetworking/cni v1.2.3 @@ -251,8 +251,8 @@ github.com/containers/conmon/runner/config # github.com/containers/gvisor-tap-vsock v0.8.1 ## explicit; go 1.22.0 github.com/containers/gvisor-tap-vsock/pkg/types -# github.com/containers/image/v5 v5.33.0 -## explicit; go 1.22.6 +# github.com/containers/image/v5 v5.33.1-0.20241214000558-01058107e817 +## explicit; go 1.22.8 github.com/containers/image/v5/copy github.com/containers/image/v5/directory github.com/containers/image/v5/directory/explicitfilepath @@ -362,7 +362,7 @@ github.com/containers/psgo/internal/dev github.com/containers/psgo/internal/host github.com/containers/psgo/internal/proc github.com/containers/psgo/internal/process -# github.com/containers/storage v1.56.0 +# github.com/containers/storage v1.56.1-0.20241213190044-18afcfdf2c3d ## explicit; go 1.22.0 github.com/containers/storage github.com/containers/storage/drivers @@ -376,12 +376,14 @@ github.com/containers/storage/drivers/register github.com/containers/storage/drivers/vfs github.com/containers/storage/drivers/windows github.com/containers/storage/drivers/zfs +github.com/containers/storage/internal/dedup github.com/containers/storage/pkg/archive github.com/containers/storage/pkg/chrootarchive github.com/containers/storage/pkg/chunked github.com/containers/storage/pkg/chunked/compressor github.com/containers/storage/pkg/chunked/dump -github.com/containers/storage/pkg/chunked/internal +github.com/containers/storage/pkg/chunked/internal/minimal +github.com/containers/storage/pkg/chunked/internal/path github.com/containers/storage/pkg/chunked/toc github.com/containers/storage/pkg/config github.com/containers/storage/pkg/directory @@ -468,7 +470,6 @@ github.com/distribution/reference ## explicit github.com/docker/distribution/registry/api/errcode github.com/docker/distribution/registry/api/v2 -github.com/docker/distribution/registry/client/auth/challenge # github.com/docker/docker v27.4.0+incompatible ## explicit github.com/docker/docker/api @@ -966,8 +967,8 @@ github.com/pmezard/go-difflib/difflib # github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c ## explicit; go 1.14 github.com/power-devops/perfstat -# github.com/proglottis/gpgme v0.1.3 -## explicit; go 1.11 +# github.com/proglottis/gpgme v0.1.4 +## explicit; go 1.17 github.com/proglottis/gpgme # github.com/rivo/uniseg v0.4.7 ## explicit; go 1.18 @@ -1017,8 +1018,8 @@ github.com/sigstore/rekor/pkg/generated/client/pubkey github.com/sigstore/rekor/pkg/generated/client/tlog github.com/sigstore/rekor/pkg/generated/models github.com/sigstore/rekor/pkg/util -# github.com/sigstore/sigstore v1.8.9 -## explicit; go 1.22.5 +# github.com/sigstore/sigstore v1.8.11 +## explicit; go 1.22.0 github.com/sigstore/sigstore/pkg/cryptoutils github.com/sigstore/sigstore/pkg/oauth github.com/sigstore/sigstore/pkg/oauthflow @@ -1049,8 +1050,8 @@ github.com/stefanberger/go-pkcs11uri github.com/stretchr/testify/assert github.com/stretchr/testify/assert/yaml github.com/stretchr/testify/require -# github.com/sylabs/sif/v2 v2.19.1 -## explicit; go 1.22.5 +# github.com/sylabs/sif/v2 v2.20.1 +## explicit; go 1.22.8 github.com/sylabs/sif/v2/pkg/sif # github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 ## explicit @@ -1227,7 +1228,7 @@ golang.org/x/net/internal/socks golang.org/x/net/internal/timeseries golang.org/x/net/proxy golang.org/x/net/trace -# golang.org/x/oauth2 v0.23.0 +# golang.org/x/oauth2 v0.24.0 ## explicit; go 1.18 golang.org/x/oauth2 golang.org/x/oauth2/internal From 7c036daadd47fe86d29ccf78f14e66d78edf39f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20Trma=C4=8D?= Date: Wed, 30 Oct 2024 16:11:05 +0100 Subject: [PATCH 05/14] Revert "Use the config digest to compare images loaded/pulled using different methods" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 1d7ec1ef5fc778cf0a188f2605524f0169678fed. Signed-off-by: Miloslav Trmač --- test/system/010-images.bats | 6 +---- test/system/120-load.bats | 46 ++++++++++++++----------------------- test/system/150-login.bats | 18 +++++++-------- test/system/helpers.bash | 11 --------- 4 files changed, 27 insertions(+), 54 deletions(-) diff --git a/test/system/010-images.bats b/test/system/010-images.bats index 488668cf6b..401298cb2a 100644 --- a/test/system/010-images.bats +++ b/test/system/010-images.bats @@ -396,13 +396,9 @@ EOF assert "${lines[-2]}" =~ ".*$IMAGE false" "image from readwrite store" assert "${lines[-1]}" =~ ".*$IMAGE true" "image from readonly store" id=${lines[-2]%% *} - local config_digest; config_digest=$(image_config_digest "@$id") # Without $sconf, i.e. from the read-write store. CONTAINERS_STORAGE_CONF=$sconf run_podman pull -q $IMAGE - # This is originally a regression test, (podman pull) used to output multiple image IDs. Ensure it only prints one. - assert "${#lines[*]}" -le 1 "Number of output lines from podman pull" - local config_digest2; config_digest2=$(image_config_digest "@$output") - assert "$config_digest2" = "$config_digest" "pull -q $IMAGE, using storage.conf" + is "$output" "$id" "pull -q $IMAGE, using storage.conf" # $IMAGE might now be reusing layers from the additional store; # Removing the additional store underneath can result in dangling layer references. diff --git a/test/system/120-load.bats b/test/system/120-load.bats index 09ef6c907b..3771825f17 100644 --- a/test/system/120-load.bats +++ b/test/system/120-load.bats @@ -23,24 +23,22 @@ function teardown() { # Custom helpers for this test only. These just save us having to duplicate # the same thing four times (two tests, each with -i and stdin). # -# initialize, read image ID, image config digest, and name -get_img_ids_and_name() { +# initialize, read image ID and name +get_iid_and_name() { run_podman images -a --format '{{.ID}} {{.Repository}}:{{.Tag}}' read iid img_name <<<"$output" - img_config_digest=$(image_config_digest "@$iid") archive=$PODMAN_TMPDIR/myimage-$(random_string 8).tar } -# Simple verification of image config digest and name -verify_img_config_digest_and_name() { +# Simple verification of image ID and name +verify_iid_and_name() { run_podman images -a --format '{{.ID}} {{.Repository}}:{{.Tag}}' read new_iid new_img_name < <(echo "$output") - new_img_config_digest=$(image_config_digest "@$new_iid") # Verify - is "$new_img_config_digest" "$img_config_digest" "Image config digest of loaded image == original" - is "$new_img_name" "$1" "Name & tag of restored image" + is "$new_iid" "$iid" "Image ID of loaded image == original" + is "$new_img_name" "$1" "Name & tag of restored image" } @test "podman load invalid file" { @@ -180,7 +178,7 @@ verify_img_config_digest_and_name() { @test "podman load - by image ID" { # FIXME: how to build a simple archive instead? - get_img_ids_and_name + get_iid_and_name # Save image by ID, and remove it. run_podman save $iid -o $archive @@ -188,41 +186,41 @@ verify_img_config_digest_and_name() { # Load using -i; IID should be preserved, but name is not. run_podman load -i $archive - verify_img_config_digest_and_name ":" + verify_iid_and_name ":" # Same as above, using stdin run_podman rmi $iid run_podman load < $archive - verify_img_config_digest_and_name ":" + verify_iid_and_name ":" # Same as above, using stdin but with `podman image load` run_podman rmi $iid run_podman image load < $archive - verify_img_config_digest_and_name ":" + verify_iid_and_name ":" } @test "podman load - by image name" { - get_img_ids_and_name + get_iid_and_name run_podman save $img_name -o $archive run_podman rmi $iid # Load using -i; this time the image should be tagged. run_podman load -i $archive - verify_img_config_digest_and_name $img_name + verify_iid_and_name $img_name run_podman rmi $iid # Also make sure that `image load` behaves the same. run_podman image load -i $archive - verify_img_config_digest_and_name $img_name + verify_iid_and_name $img_name run_podman rmi $iid # Same as above, using stdin run_podman load < $archive - verify_img_config_digest_and_name $img_name + verify_iid_and_name $img_name } @test "podman load - from URL" { - get_img_ids_and_name + get_iid_and_name run_podman save $img_name -o $archive run_podman rmi $iid @@ -234,21 +232,11 @@ verify_img_config_digest_and_name() { -v $archive:/var/www/image.tar:Z \ -w /var/www \ $IMAGE /bin/busybox-extras httpd -f -p 80 - # We now have $IMAGE pointing at the image, possibly using a zstd:chunked (TOC-based) pull + run_podman load -i $SERVER/image.tar + verify_iid_and_name $img_name - # This should move the $img_name tag ( = $IMAGE) to the result of loading the image; - # this is a non-TOC-based load, so it might or might not deduplicate the loaded image with - # the one for myweb. - # So, if we have an untagged image, it’s probably the one for myweb, and try to remove it. run_podman rm -f -t0 myweb - run_podman images -a --format '{{.ID}} {{.Repository}}:{{.Tag}}' - local myweb_iid=$(echo "$output" | sed -n '/:/s/ .*$//p') - if [[ -n "$myweb_iid" ]]; then - run_podman rmi $myweb_iid - fi - - verify_img_config_digest_and_name $img_name } @test "podman load - redirect corrupt payload" { diff --git a/test/system/150-login.bats b/test/system/150-login.bats index 49f24632b5..b05d4e7cdb 100644 --- a/test/system/150-login.bats +++ b/test/system/150-login.bats @@ -149,8 +149,9 @@ EOF } function _push_search_test() { - # Look up image config digest for later comparison against push/pulled image - local img_config_digest; img_config_digest=$(image_config_digest $IMAGE) + # Preserve image ID for later comparison against push/pulled image + run_podman inspect --format '{{.Id}}' $IMAGE + iid=$output destname=ok-$(random_string 10 | tr A-Z a-z)-ok # Use command-line credentials @@ -187,8 +188,8 @@ function _push_search_test() { localhost:${PODMAN_LOGIN_REGISTRY_PORT}/$destname # Compare to original image - local img_config_digest2; img_config_digest2=$(image_config_digest localhost:${PODMAN_LOGIN_REGISTRY_PORT}/$destname) - assert "$img_config_digest2" = "$img_config_digest" "config digest of pulled image == original digest" + run_podman inspect --format '{{.Id}}' $destname + is "$output" "$iid" "Image ID of pulled image == original IID" run_podman rmi $destname } @@ -344,12 +345,12 @@ function _test_skopeo_credential_sharing() { $image1 run_podman rmi $image1 - local podman_image_config_digest=$(image_config_digest $IMAGE) + run_podman images $IMAGE --format {{.ID}} + local podman_image_id=$output run_podman pull -q --retry 4 --retry-delay "0s" --authfile=$authfile \ --tls-verify=false $image1 - local pulled_image_config_digest; pulled_image_config_digest=$(image_config_digest @$output) - assert "$pulled_image_config_digest" = "$podman_image_config_digest" "First pull (before stopping registry)" + assert "${output:0:12}" = "$podman_image_id" "First pull (before stopping registry)" run_podman rmi $image1 # This actually STOPs the registry, so the port is unbound... @@ -359,8 +360,7 @@ function _test_skopeo_credential_sharing() { run_podman 0+w pull -q --retry 4 --retry-delay "5s" --authfile=$authfile \ --tls-verify=false $image1 assert "$output" =~ "Failed, retrying in 5s.*Error: initializing.* connection refused" - local pulled_image_config_digest2; pulled_image_config_digest2=$(image_config_digest "@${lines[-1]}") - assert "$pulled_image_config_digest2" = "$podman_image_config_digest" "push should succeed via retry" + assert "${lines[-1]:0:12}" = "$podman_image_id" "push should succeed via retry" unpause_registry run_podman rmi $image1 diff --git a/test/system/helpers.bash b/test/system/helpers.bash index f4d57eda70..bda7ea82c8 100644 --- a/test/system/helpers.bash +++ b/test/system/helpers.bash @@ -847,17 +847,6 @@ function _ensure_container_running() { die "Timed out waiting for container $1 to enter state running=$2" } -# Return the config digest of an image in containers-storage. -# The input can be a named reference, or an @imageID (including shorter imageID prefixes) -# Historically, the image ID was a good indicator of “the same” image; -# with zstd:chunked, the same image might have different IDs depending on whether -# creating layers happened based on the TOC (and per-file operations) or the full layer tarball -function image_config_digest() { - local sha_output; sha_output=$(skopeo inspect --raw --config "containers-storage:$1" | sha256sum) - # strip filename ("-") from sha output - echo "${sha_output%% *}" -} - ########################### # _add_label_if_missing # make sure skip messages include rootless/remote ########################### From 8b47f7157e1dbc8fe7e14ada0e68d2830d2ddc96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20Trma=C4=8D?= Date: Wed, 30 Oct 2024 17:57:38 +0100 Subject: [PATCH 06/14] Fix image ID query MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Read the full one, not the truncated one Signed-off-by: Miloslav Trmač --- test/system/010-images.bats | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/system/010-images.bats b/test/system/010-images.bats index 401298cb2a..020f94f97d 100644 --- a/test/system/010-images.bats +++ b/test/system/010-images.bats @@ -391,7 +391,7 @@ EOF # IMPORTANT! Use -2/-1 indices, not 0/1, because $SYSTEMD_IMAGE may be # present in store, and if it is it will precede $IMAGE. - CONTAINERS_STORAGE_CONF=$sconf run_podman images -a -n --format "{{.ID}} {{.Repository}}:{{.Tag}} {{.ReadOnly}}" + CONTAINERS_STORAGE_CONF=$sconf run_podman images -a -n --format "{{.Id}} {{.Repository}}:{{.Tag}} {{.ReadOnly}}" assert "${#lines[*]}" -ge 2 "at least 2 lines from 'podman images'" assert "${lines[-2]}" =~ ".*$IMAGE false" "image from readwrite store" assert "${lines[-1]}" =~ ".*$IMAGE true" "image from readonly store" From 787bac9c1a92382eb0cf0b186d66075ef531cc78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20Trma=C4=8D?= Date: Wed, 30 Oct 2024 16:13:52 +0100 Subject: [PATCH 07/14] DO NOT MERGE: Testing https://github.com/containers/storage/pull/2155 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit > go mod edit -replace github.com/containers/storage=github.com/mtrmac/storage@wip-authentic Signed-off-by: Miloslav Trmač --- go.mod | 2 + go.sum | 4 +- .../containers/storage/drivers/driver.go | 4 +- .../storage/pkg/chunked/cache_linux.go | 2 +- .../storage/pkg/chunked/dump/dump.go | 15 +- .../storage/pkg/chunked/internal/path/path.go | 15 ++ .../storage/pkg/chunked/storage_linux.go | 229 +++++++++++++----- .../containers/storage/storage.conf | 19 ++ vendor/modules.txt | 3 +- 9 files changed, 226 insertions(+), 67 deletions(-) diff --git a/go.mod b/go.mod index 4956998bb4..868ff4ce02 100644 --- a/go.mod +++ b/go.mod @@ -231,3 +231,5 @@ require ( gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect tags.cncf.io/container-device-interface/specs-go v0.8.0 // indirect ) + +replace github.com/containers/storage => github.com/mtrmac/storage v0.0.0-20241214001013-834e871d106a diff --git a/go.sum b/go.sum index 2bed508595..f2953046f0 100644 --- a/go.sum +++ b/go.sum @@ -96,8 +96,6 @@ github.com/containers/ocicrypt v1.2.0 h1:X14EgRK3xNFvJEfI5O4Qn4T3E25ANudSOZz/sir github.com/containers/ocicrypt v1.2.0/go.mod h1:ZNviigQajtdlxIZGibvblVuIFBKIuUI2M0QM12SD31U= github.com/containers/psgo v1.9.0 h1:eJ74jzSaCHnWt26OlKZROSyUyRcGDf+gYBdXnxrMW4g= github.com/containers/psgo v1.9.0/go.mod h1:0YoluUm43Mz2UnBIh1P+6V6NWcbpTL5uRtXyOcH0B5A= -github.com/containers/storage v1.56.1-0.20241213190044-18afcfdf2c3d h1:exBfFBQ1aus+p6JdnLzYEyRCmI0r4LIT6xns5CXflVM= -github.com/containers/storage v1.56.1-0.20241213190044-18afcfdf2c3d/go.mod h1:S0a82hrdqPnFLguvlXYW7RlCH9TC5Ec+7xh5lWGk5dA= github.com/containers/winquit v1.1.0 h1:jArun04BNDQvt2W0Y78kh9TazN2EIEMG5Im6/JY7+pE= github.com/containers/winquit v1.1.0/go.mod h1:PsPeZlnbkmGGIToMPHF1zhWjBUkd8aHjMOr/vFcPxw8= github.com/coreos/go-oidc/v3 v3.11.0 h1:Ia3MxdwpSw702YW0xgfmP1GVCMA9aEFWu12XUZ3/OtI= @@ -381,6 +379,8 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/mtrmac/storage v0.0.0-20241214001013-834e871d106a h1:ce2EGPYy1Hd5bfcnU/u/Nag9ufk2X81WGiASV82il1g= +github.com/mtrmac/storage v0.0.0-20241214001013-834e871d106a/go.mod h1:S0a82hrdqPnFLguvlXYW7RlCH9TC5Ec+7xh5lWGk5dA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY= diff --git a/vendor/github.com/containers/storage/drivers/driver.go b/vendor/github.com/containers/storage/drivers/driver.go index ed0e628883..1f7ac5ff02 100644 --- a/vendor/github.com/containers/storage/drivers/driver.go +++ b/vendor/github.com/containers/storage/drivers/driver.go @@ -231,8 +231,8 @@ const ( // DifferOutputFormatDir means the output is a directory and it will // keep the original layout. DifferOutputFormatDir = iota - // DifferOutputFormatFlat will store the files by their checksum, in the form - // checksum[0:2]/checksum[2:] + // DifferOutputFormatFlat will store the files by their checksum, per + // pkg/chunked/internal/composefs.RegularFilePathForValidatedDigest. DifferOutputFormatFlat ) diff --git a/vendor/github.com/containers/storage/pkg/chunked/cache_linux.go b/vendor/github.com/containers/storage/pkg/chunked/cache_linux.go index c9cde23b7f..0e49ddd888 100644 --- a/vendor/github.com/containers/storage/pkg/chunked/cache_linux.go +++ b/vendor/github.com/containers/storage/pkg/chunked/cache_linux.go @@ -710,7 +710,7 @@ func prepareCacheFile(manifest []byte, format graphdriver.DifferOutputFormat) ([ switch format { case graphdriver.DifferOutputFormatDir: case graphdriver.DifferOutputFormatFlat: - entries, err = makeEntriesFlat(entries) + entries, err = makeEntriesFlat(entries, nil) if err != nil { return nil, err } diff --git a/vendor/github.com/containers/storage/pkg/chunked/dump/dump.go b/vendor/github.com/containers/storage/pkg/chunked/dump/dump.go index fde42d75fc..0e673f3f2d 100644 --- a/vendor/github.com/containers/storage/pkg/chunked/dump/dump.go +++ b/vendor/github.com/containers/storage/pkg/chunked/dump/dump.go @@ -9,11 +9,11 @@ import ( "io" "path/filepath" "reflect" - "strings" "time" "github.com/containers/storage/pkg/chunked/internal/minimal" storagePath "github.com/containers/storage/pkg/chunked/internal/path" + "github.com/opencontainers/go-digest" "golang.org/x/sys/unix" ) @@ -165,11 +165,16 @@ func dumpNode(out io.Writer, added map[string]*minimal.FileMetadata, links map[s } else { payload = storagePath.CleanAbsPath(entry.Linkname) } - } else { - if len(entry.Digest) > 10 { - d := strings.Replace(entry.Digest, "sha256:", "", 1) - payload = d[:2] + "/" + d[2:] + } else if entry.Digest != "" { + d, err := digest.Parse(entry.Digest) + if err != nil { + return fmt.Errorf("invalid digest %q for %q: %w", entry.Digest, entry.Name, err) + } + path, err := storagePath.RegularFilePathForValidatedDigest(d) + if err != nil { + return fmt.Errorf("determining physical file path for %q: %w", entry.Name, err) } + payload = path } if _, err := fmt.Fprint(out, escapedOptional([]byte(payload), ESCAPE_LONE_DASH)); err != nil { diff --git a/vendor/github.com/containers/storage/pkg/chunked/internal/path/path.go b/vendor/github.com/containers/storage/pkg/chunked/internal/path/path.go index 50ca1def87..55ba74550e 100644 --- a/vendor/github.com/containers/storage/pkg/chunked/internal/path/path.go +++ b/vendor/github.com/containers/storage/pkg/chunked/internal/path/path.go @@ -1,7 +1,10 @@ package path import ( + "fmt" "path/filepath" + + "github.com/opencontainers/go-digest" ) // CleanAbsPath removes any ".." and "." from the path @@ -10,3 +13,15 @@ import ( func CleanAbsPath(path string) string { return filepath.Clean("/" + path) } + +// RegularFilePath returns the path used in the composefs backing store for a +// regular file with the provided content digest. +// +// The caller MUST ensure d is a valid digest (in particular, that it contains no path separators or .. entries) +func RegularFilePathForValidatedDigest(d digest.Digest) (string, error) { + if algo := d.Algorithm(); algo != digest.SHA256 { + return "", fmt.Errorf("unexpected digest algorithm %q", algo) + } + e := d.Encoded() + return e[0:2] + "/" + e[2:], nil +} diff --git a/vendor/github.com/containers/storage/pkg/chunked/storage_linux.go b/vendor/github.com/containers/storage/pkg/chunked/storage_linux.go index cbf8229ac2..828a42919d 100644 --- a/vendor/github.com/containers/storage/pkg/chunked/storage_linux.go +++ b/vendor/github.com/containers/storage/pkg/chunked/storage_linux.go @@ -2,6 +2,7 @@ package chunked import ( archivetar "archive/tar" + "bytes" "context" "encoding/base64" "errors" @@ -28,12 +29,15 @@ import ( "github.com/containers/storage/pkg/fsverity" "github.com/containers/storage/pkg/idtools" "github.com/containers/storage/pkg/system" + securejoin "github.com/cyphar/filepath-securejoin" jsoniter "github.com/json-iterator/go" "github.com/klauspost/compress/zstd" "github.com/klauspost/pgzip" digest "github.com/opencontainers/go-digest" "github.com/sirupsen/logrus" "github.com/vbatts/tar-split/archive/tar" + "github.com/vbatts/tar-split/tar/asm" + tsStorage "github.com/vbatts/tar-split/tar/storage" "golang.org/x/sys/unix" ) @@ -93,7 +97,7 @@ type chunkedDiffer struct { blobSize int64 uncompressedTarSize int64 // -1 if unknown - pullOptions map[string]string + pullOptions pullOptions useFsVerity graphdriver.DifferFsVerity fsVerityDigests map[string]string @@ -109,6 +113,42 @@ type chunkedLayerData struct { Format graphdriver.DifferOutputFormat `json:"format"` } +// pullOptions contains parsed data from storage.Store.PullOptions. +// TO DO: ideally this should be parsed along with the rest of the config file into StoreOptions directly +// (and then storage.Store.PullOptions would need to be somehow simulated). +type pullOptions struct { + enablePartialImages bool // enable_partial_images + convertImages bool // convert_images + useHardLinks bool // use_hard_links + insecureAllowUnpredictableImageContents bool // insecure_allow_unpredictable_image_contents + ostreeRepos []string // ostree_repos +} + +func parsePullOptions(store storage.Store) pullOptions { + options := store.PullOptions() + + res := pullOptions{} + for _, e := range []struct { + dest *bool + name string + defaultValue bool + }{ + {&res.enablePartialImages, "enable_partial_images", false}, + {&res.convertImages, "convert_images", false}, + {&res.useHardLinks, "use_hard_links", false}, + {&res.insecureAllowUnpredictableImageContents, "insecure_allow_unpredictable_image_contents", false}, + } { + if value, ok := options[e.name]; ok { + *e.dest = strings.ToLower(value) == "true" + } else { + *e.dest = e.defaultValue + } + } + res.ostreeRepos = strings.Split(options["ostree_repos"], ":") + + return res +} + func (c *chunkedDiffer) convertTarToZstdChunked(destDirectory string, payload *os.File) (int64, *seekableFile, digest.Digest, map[string]string, error) { diff, err := archive.DecompressStream(payload) if err != nil { @@ -148,22 +188,21 @@ func (c *chunkedDiffer) convertTarToZstdChunked(destDirectory string, payload *o // If it returns an error that implements IsErrFallbackToOrdinaryLayerDownload, the caller can // retry the operation with a different method. func GetDiffer(ctx context.Context, store storage.Store, blobDigest digest.Digest, blobSize int64, annotations map[string]string, iss ImageSourceSeekable) (graphdriver.Differ, error) { - pullOptions := store.PullOptions() + pullOptions := parsePullOptions(store) - if !parseBooleanPullOption(pullOptions, "enable_partial_images", false) { - // If convertImages is set, the two options disagree whether fallback is permissible. + if !pullOptions.enablePartialImages { + // If pullOptions.convertImages is set, the two options disagree whether fallback is permissible. // Right now, we enable it, but that’s not a promise; rather, such a configuration should ideally be rejected. return nil, newErrFallbackToOrdinaryLayerDownload(errors.New("partial images are disabled")) } - // convertImages also serves as a “must not fallback to non-partial pull” option (?!) - convertImages := parseBooleanPullOption(pullOptions, "convert_images", false) + // pullOptions.convertImages also serves as a “must not fallback to non-partial pull” option (?!) graphDriver, err := store.GraphDriver() if err != nil { return nil, err } if _, partialSupported := graphDriver.(graphdriver.DriverWithDiffer); !partialSupported { - if convertImages { + if pullOptions.convertImages { return nil, fmt.Errorf("graph driver %s does not support partial pull but convert_images requires that", graphDriver.String()) } return nil, newErrFallbackToOrdinaryLayerDownload(fmt.Errorf("graph driver %s does not support partial pull", graphDriver.String())) @@ -175,7 +214,7 @@ func GetDiffer(ctx context.Context, store storage.Store, blobDigest digest.Diges return nil, err } // If convert_images is enabled, always attempt to convert it instead of returning an error or falling back to a different method. - if convertImages { + if pullOptions.convertImages { logrus.Debugf("Created differ to convert blob %q", blobDigest) return makeConvertFromRawDiffer(store, blobDigest, blobSize, iss, pullOptions) } @@ -187,9 +226,9 @@ func GetDiffer(ctx context.Context, store storage.Store, blobDigest digest.Diges // getProperDiffer is an implementation detail of GetDiffer. // It returns a “proper” differ (not a convert_images one) if possible. -// On error, the second parameter is true if a fallback to an alternative (either the makeConverToRaw differ, or a non-partial pull) +// On error, the second return value is true if a fallback to an alternative (either the makeConverToRaw differ, or a non-partial pull) // is permissible. -func getProperDiffer(store storage.Store, blobDigest digest.Digest, blobSize int64, annotations map[string]string, iss ImageSourceSeekable, pullOptions map[string]string) (graphdriver.Differ, bool, error) { +func getProperDiffer(store storage.Store, blobDigest digest.Digest, blobSize int64, annotations map[string]string, iss ImageSourceSeekable, pullOptions pullOptions) (graphdriver.Differ, bool, error) { zstdChunkedTOCDigestString, hasZstdChunkedTOC := annotations[minimal.ManifestChecksumKey] estargzTOCDigestString, hasEstargzTOC := annotations[estargz.TOCJSONDigestAnnotation] @@ -202,12 +241,10 @@ func getProperDiffer(store storage.Store, blobDigest digest.Digest, blobSize int if err != nil { return nil, false, err } - differ, err := makeZstdChunkedDiffer(store, blobSize, zstdChunkedTOCDigest, annotations, iss, pullOptions) + differ, canFallback, err := makeZstdChunkedDiffer(store, blobSize, zstdChunkedTOCDigest, annotations, iss, pullOptions) if err != nil { logrus.Debugf("Could not create zstd:chunked differ for blob %q: %v", blobDigest, err) - // If the error is a bad request to the server, then signal to the caller that it can try a different method. - var badRequestErr ErrBadRequest - return nil, errors.As(err, &badRequestErr), err + return nil, canFallback, err } logrus.Debugf("Created zstd:chunked differ for blob %q", blobDigest) return differ, false, nil @@ -217,26 +254,23 @@ func getProperDiffer(store storage.Store, blobDigest digest.Digest, blobSize int if err != nil { return nil, false, err } - differ, err := makeEstargzChunkedDiffer(store, blobSize, estargzTOCDigest, iss, pullOptions) + differ, canFallback, err := makeEstargzChunkedDiffer(store, blobSize, estargzTOCDigest, iss, pullOptions) if err != nil { logrus.Debugf("Could not create estargz differ for blob %q: %v", blobDigest, err) - // If the error is a bad request to the server, then signal to the caller that it can try a different method. - var badRequestErr ErrBadRequest - return nil, errors.As(err, &badRequestErr), err + return nil, canFallback, err } logrus.Debugf("Created eStargz differ for blob %q", blobDigest) return differ, false, nil default: // no TOC - convertImages := parseBooleanPullOption(pullOptions, "convert_images", false) - if !convertImages { + if !pullOptions.convertImages { return nil, true, errors.New("no TOC found and convert_images is not configured") } return nil, true, errors.New("no TOC found") } } -func makeConvertFromRawDiffer(store storage.Store, blobDigest digest.Digest, blobSize int64, iss ImageSourceSeekable, pullOptions map[string]string) (*chunkedDiffer, error) { +func makeConvertFromRawDiffer(store storage.Store, blobDigest digest.Digest, blobSize int64, iss ImageSourceSeekable, pullOptions pullOptions) (*chunkedDiffer, error) { layersCache, err := getLayersCache(store) if err != nil { return nil, err @@ -255,22 +289,31 @@ func makeConvertFromRawDiffer(store storage.Store, blobDigest digest.Digest, blo }, nil } -func makeZstdChunkedDiffer(store storage.Store, blobSize int64, tocDigest digest.Digest, annotations map[string]string, iss ImageSourceSeekable, pullOptions map[string]string) (*chunkedDiffer, error) { +// makeZstdChunkedDiffer sets up a chunkedDiffer for a zstd:chunked layer. +// +// On error, the second return value is true if a fallback to an alternative (either the makeConverToRaw differ, or a non-partial pull) +// is permissible. +func makeZstdChunkedDiffer(store storage.Store, blobSize int64, tocDigest digest.Digest, annotations map[string]string, iss ImageSourceSeekable, pullOptions pullOptions) (*chunkedDiffer, bool, error) { manifest, toc, tarSplit, tocOffset, err := readZstdChunkedManifest(iss, tocDigest, annotations) if err != nil { - return nil, fmt.Errorf("read zstd:chunked manifest: %w", err) + // If the error is a bad request to the server, then signal to the caller that it can try a different method. + var badRequestErr ErrBadRequest + return nil, errors.As(err, &badRequestErr), fmt.Errorf("read zstd:chunked manifest: %w", err) } + var uncompressedTarSize int64 = -1 if tarSplit != nil { uncompressedTarSize, err = tarSizeFromTarSplit(tarSplit) if err != nil { - return nil, fmt.Errorf("computing size from tar-split: %w", err) + return nil, false, fmt.Errorf("computing size from tar-split: %w", err) } + } else if !pullOptions.insecureAllowUnpredictableImageContents { // With no tar-split, we can't compute the traditional UncompressedDigest. + return nil, true, fmt.Errorf("zstd:chunked layers without tar-split data don't support partial pulls with guaranteed consistency with non-partial pulls") } layersCache, err := getLayersCache(store) if err != nil { - return nil, err + return nil, false, err } return &chunkedDiffer{ @@ -287,17 +330,27 @@ func makeZstdChunkedDiffer(store storage.Store, blobSize int64, tocDigest digest stream: iss, tarSplit: tarSplit, tocOffset: tocOffset, - }, nil + }, false, nil } -func makeEstargzChunkedDiffer(store storage.Store, blobSize int64, tocDigest digest.Digest, iss ImageSourceSeekable, pullOptions map[string]string) (*chunkedDiffer, error) { +// makeZstdChunkedDiffer sets up a chunkedDiffer for an estargz layer. +// +// On error, the second return value is true if a fallback to an alternative (either the makeConverToRaw differ, or a non-partial pull) +// is permissible. +func makeEstargzChunkedDiffer(store storage.Store, blobSize int64, tocDigest digest.Digest, iss ImageSourceSeekable, pullOptions pullOptions) (*chunkedDiffer, bool, error) { + if !pullOptions.insecureAllowUnpredictableImageContents { // With no tar-split, we can't compute the traditional UncompressedDigest. + return nil, true, fmt.Errorf("estargz layers don't support partial pulls with guaranteed consistency with non-partial pulls") + } + manifest, tocOffset, err := readEstargzChunkedManifest(iss, blobSize, tocDigest) if err != nil { - return nil, fmt.Errorf("read zstd:chunked manifest: %w", err) + // If the error is a bad request to the server, then signal to the caller that it can try a different method. + var badRequestErr ErrBadRequest + return nil, errors.As(err, &badRequestErr), fmt.Errorf("read zstd:chunked manifest: %w", err) } layersCache, err := getLayersCache(store) if err != nil { - return nil, err + return nil, false, err } return &chunkedDiffer{ @@ -312,7 +365,7 @@ func makeEstargzChunkedDiffer(store storage.Store, blobSize int64, tocDigest dig pullOptions: pullOptions, stream: iss, tocOffset: tocOffset, - }, nil + }, false, nil } func makeCopyBuffer() []byte { @@ -1044,13 +1097,6 @@ type hardLinkToCreate struct { metadata *fileMetadata } -func parseBooleanPullOption(pullOptions map[string]string, name string, def bool) bool { - if value, ok := pullOptions[name]; ok { - return strings.ToLower(value) == "true" - } - return def -} - type findAndCopyFileOptions struct { useHardLinks bool ostreeRepos []string @@ -1117,10 +1163,13 @@ func (c *chunkedDiffer) findAndCopyFile(dirfd int, r *fileMetadata, copyOptions return false, nil } -func makeEntriesFlat(mergedEntries []fileMetadata) ([]fileMetadata, error) { +// makeEntriesFlat collects regular-file entries from mergedEntries, and produces a new list +// where each file content is only represented once, and uses composefs.RegularFilePathForValidatedDigest for its name. +// if flatPathNameMap is not nil, this function writes to it a mapping from filepath.Clean(originalName) to the composefs name. +func makeEntriesFlat(mergedEntries []fileMetadata, flatPathNameMap map[string]string) ([]fileMetadata, error) { var new []fileMetadata - hashes := make(map[string]string) + knownFlatPaths := make(map[string]struct{}) for i := range mergedEntries { if mergedEntries[i].Type != TypeReg { continue @@ -1130,16 +1179,22 @@ func makeEntriesFlat(mergedEntries []fileMetadata) ([]fileMetadata, error) { } digest, err := digest.Parse(mergedEntries[i].Digest) if err != nil { - return nil, err + return nil, fmt.Errorf("invalid digest %q for %q: %w", mergedEntries[i].Digest, mergedEntries[i].Name, err) + } + path, err := path.RegularFilePathForValidatedDigest(digest) + if err != nil { + return nil, fmt.Errorf("determining physical file path for %q: %w", mergedEntries[i].Name, err) + } + if flatPathNameMap != nil { + flatPathNameMap[filepath.Clean(mergedEntries[i].Name)] = path } - d := digest.Encoded() - if hashes[d] != "" { + if _, known := knownFlatPaths[path]; known { continue } - hashes[d] = d + knownFlatPaths[path] = struct{}{} - mergedEntries[i].Name = fmt.Sprintf("%s/%s", d[0:2], d[2:]) + mergedEntries[i].Name = path mergedEntries[i].skipSetAttrs = true new = append(new, mergedEntries[i]) @@ -1400,13 +1455,6 @@ func (c *chunkedDiffer) ApplyDiff(dest string, options *archive.TarOptions, diff Size: c.uncompressedTarSize, } - // When the hard links deduplication is used, file attributes are ignored because setting them - // modifies the source file as well. - useHardLinks := parseBooleanPullOption(c.pullOptions, "use_hard_links", false) - - // List of OSTree repositories to use for deduplication - ostreeRepos := strings.Split(c.pullOptions["ostree_repos"], ":") - whiteoutConverter := archive.GetWhiteoutConverter(options.WhiteoutFormat, options.WhiteoutData) var missingParts []missingPart @@ -1439,16 +1487,20 @@ func (c *chunkedDiffer) ApplyDiff(dest string, options *archive.TarOptions, diff if err != nil { return output, &fs.PathError{Op: "open", Path: dest, Err: err} } - defer unix.Close(dirfd) + dirFile := os.NewFile(uintptr(dirfd), dest) + defer dirFile.Close() + var flatPathNameMap map[string]string // = nil if differOpts != nil && differOpts.Format == graphdriver.DifferOutputFormatFlat { - mergedEntries, err = makeEntriesFlat(mergedEntries) + flatPathNameMap = map[string]string{} + mergedEntries, err = makeEntriesFlat(mergedEntries, flatPathNameMap) if err != nil { return output, err } createdDirs := make(map[string]struct{}) for _, e := range mergedEntries { - d := e.Name[0:2] + // This hard-codes an assumption that RegularFilePathForValidatedDigest creates paths with exactly one directory component. + d := filepath.Dir(e.Name) if _, found := createdDirs[d]; !found { if err := unix.Mkdirat(dirfd, d, 0o755); err != nil { return output, &fs.PathError{Op: "mkdirat", Path: d, Err: err} @@ -1465,8 +1517,10 @@ func (c *chunkedDiffer) ApplyDiff(dest string, options *archive.TarOptions, diff missingPartsSize, totalChunksSize := int64(0), int64(0) copyOptions := findAndCopyFileOptions{ - useHardLinks: useHardLinks, - ostreeRepos: ostreeRepos, + // When the hard links deduplication is used, file attributes are ignored because setting them + // modifies the source file as well. + useHardLinks: c.pullOptions.useHardLinks, + ostreeRepos: c.pullOptions.ostreeRepos, // List of OSTree repositories to use for deduplication options: options, } @@ -1731,6 +1785,39 @@ func (c *chunkedDiffer) ApplyDiff(dest string, options *archive.TarOptions, diff } } + // To ensure that consumers of the layer who decompress and read the full tar stream, + // and consumers who consume the data via the TOC, both see exactly the same data and metadata, + // compute the UncompressedDigest. + // c/image will then ensure that this value matches the value in the image config’s RootFS.DiffID, i.e. the image must commit + // to one UncompressedDigest value for each layer, and that will avoid the ambiguity (in consumers who validate layers against DiffID). + // + // c/image also uses the UncompressedDigest as a layer ID, allowing it to use the traditional layer and image IDs. + // + // This is, sadly, quite costly: Up to now we might have only have had to write, and digest, only the new/modified files. + // Here we need to read, and digest, the whole layer, even if almost all of it was already present locally previously. + // So, really specialized (EXTREMELY RARE) users can opt out of this check using insecureAllowUnpredictableImageContents . + // + // Layers without a tar-split (estargz layers and old zstd:chunked layers) can't produce an UncompressedDigest that + // matches the expected RootFS.DiffID; we always fall back to full pulls, again unless the user opts out + // via insecureAllowUnpredictableImageContents . + if output.UncompressedDigest == "" { + switch { + case c.pullOptions.insecureAllowUnpredictableImageContents: + // Oh well. Skip the costly digest computation. + case output.TarSplit != nil: + metadata := tsStorage.NewJSONUnpacker(bytes.NewReader(output.TarSplit)) + fg := newStagedFileGetter(dirFile, flatPathNameMap) + digester := digest.Canonical.Digester() + if err := asm.WriteOutputTarStream(fg, metadata, digester.Hash()); err != nil { + return output, fmt.Errorf("digesting staged uncompressed stream: %w", err) + } + output.UncompressedDigest = digester.Digest() + default: + // We are checking for this earlier in GetDiffer, so this should not be reachable. + return output, fmt.Errorf(`internal error: layer's UncompressedDigest is unknown and "insecure_allow_unpredictable_image_contents" is not set`) + } + } + if totalChunksSize > 0 { logrus.Debugf("Missing %d bytes out of %d (%.2f %%)", missingPartsSize, totalChunksSize, float32(missingPartsSize*100.0)/float32(totalChunksSize)) } @@ -1856,3 +1943,33 @@ func validateChunkChecksum(chunk *minimal.FileMetadata, root, path string, offse return digester.Digest() == digest } + +// newStagedFileGetter returns an object usable as storage.FileGetter for rootDir. +// if flatPathNameMap is not nil, it must be used to map logical file names into the backing file paths. +func newStagedFileGetter(rootDir *os.File, flatPathNameMap map[string]string) *stagedFileGetter { + return &stagedFileGetter{ + rootDir: rootDir, + flatPathNameMap: flatPathNameMap, + } +} + +type stagedFileGetter struct { + rootDir *os.File + flatPathNameMap map[string]string // nil, or a map from filepath.Clean()ed tar file names to expected on-filesystem names +} + +func (fg *stagedFileGetter) Get(filename string) (io.ReadCloser, error) { + if fg.flatPathNameMap != nil { + path, ok := fg.flatPathNameMap[filepath.Clean(filename)] + if !ok { + return nil, fmt.Errorf("no path mapping exists for tar entry %q", filename) + } + filename = path + } + pathFD, err := securejoin.OpenatInRoot(fg.rootDir, filename) + if err != nil { + return nil, err + } + defer pathFD.Close() + return securejoin.Reopen(pathFD, unix.O_RDONLY) +} diff --git a/vendor/github.com/containers/storage/storage.conf b/vendor/github.com/containers/storage/storage.conf index 962325233e..909e47b39b 100644 --- a/vendor/github.com/containers/storage/storage.conf +++ b/vendor/github.com/containers/storage/storage.conf @@ -80,6 +80,25 @@ additionalimagestores = [ # This is a "string bool": "false" | "true" (cannot be native TOML boolean) # convert_images = "false" +# This should ALMOST NEVER be set. +# It allows partial pulls ofof images without guaranteeing that "partial +# pulls" and non-partial pulls both result in consistent image contents. +# This allows pulling estargz images and early versions of zstd:chunked images; +# otherwise, these layers always use the traditional non-partial pull path. +# +# This option should be enabled EXTREMELY rarely, only if ALL images that could +# EVER be concievably pulled on this system are GUARANTEED (e.g. using a signature policy) +# to come from a build system trusted to never attack image integrity. +# +# If this consistency enforcement were disabled, malicious images could be built +# in a way designed to evade other audit mechanisms, so presence of most other audit +# mechanisms is not a replacement for the above-mentioned need for all images to come +# from a trusted build system. +# +# As a side effect, enabling this option will also make image IDs unpredictable +# (usually not equal to the traditional value matching the config digest). +# insecure_allow_unpredictable_image_contents = "false" + # Root-auto-userns-user is a user name which can be used to look up one or more UID/GID # ranges in the /etc/subuid and /etc/subgid file. These ranges will be partitioned # to containers configured to create automatically a user namespace. Containers diff --git a/vendor/modules.txt b/vendor/modules.txt index 36f2c1420e..3a24a59564 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -362,7 +362,7 @@ github.com/containers/psgo/internal/dev github.com/containers/psgo/internal/host github.com/containers/psgo/internal/proc github.com/containers/psgo/internal/process -# github.com/containers/storage v1.56.1-0.20241213190044-18afcfdf2c3d +# github.com/containers/storage v1.56.1-0.20241213190044-18afcfdf2c3d => github.com/mtrmac/storage v0.0.0-20241214001013-834e871d106a ## explicit; go 1.22.0 github.com/containers/storage github.com/containers/storage/drivers @@ -1399,3 +1399,4 @@ tags.cncf.io/container-device-interface/pkg/parser # tags.cncf.io/container-device-interface/specs-go v0.8.0 ## explicit; go 1.19 tags.cncf.io/container-device-interface/specs-go +# github.com/containers/storage => github.com/mtrmac/storage v0.0.0-20241214001013-834e871d106a From b658171eac5b675c16f6f5946a08f677f5a80c0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20Trma=C4=8D?= Date: Wed, 30 Oct 2024 16:14:48 +0100 Subject: [PATCH 08/14] DO NOT MERGE: Test https://github.com/containers/image/pull/2613 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit > go mod edit -replace github.com/containers/image/v5=github.com/mtrmac/image/v5@wip-authentic Signed-off-by: Miloslav Trmač --- go.mod | 2 + go.sum | 4 +- .../image/v5/storage/storage_dest.go | 305 +++++++++++++----- vendor/modules.txt | 3 +- 4 files changed, 223 insertions(+), 91 deletions(-) diff --git a/go.mod b/go.mod index 868ff4ce02..73526c2917 100644 --- a/go.mod +++ b/go.mod @@ -233,3 +233,5 @@ require ( ) replace github.com/containers/storage => github.com/mtrmac/storage v0.0.0-20241214001013-834e871d106a + +replace github.com/containers/image/v5 => github.com/mtrmac/image/v5 v5.0.0-20241214001719-db62006dd641 diff --git a/go.sum b/go.sum index f2953046f0..b3cd8eb7c8 100644 --- a/go.sum +++ b/go.sum @@ -84,8 +84,6 @@ github.com/containers/conmon v2.0.20+incompatible h1:YbCVSFSCqFjjVwHTPINGdMX1F6J github.com/containers/conmon v2.0.20+incompatible/go.mod h1:hgwZ2mtuDrppv78a/cOBNiCm6O0UMWGx1mu7P00nu5I= github.com/containers/gvisor-tap-vsock v0.8.1 h1:88qkOjGMF9NmyoVG/orUw73mdwj3z4aOwEbRS01hF78= github.com/containers/gvisor-tap-vsock v0.8.1/go.mod h1:gjdY4JBWnynrXsxT8+OM7peEOd4FCZpoOWjSadHva0g= -github.com/containers/image/v5 v5.33.1-0.20241214000558-01058107e817 h1:7kJ9W88gfxAmhwM0xkhrKEcYizxkg9htF1fA9HiXLlg= -github.com/containers/image/v5 v5.33.1-0.20241214000558-01058107e817/go.mod h1:MwtBFIDcbUZV4Y7IvmkQcCxSgTdL0j8Ui+GqSKhj5p0= github.com/containers/libhvee v0.9.0 h1:5UxJMka1lDfxTeITA25Pd8QVVttJAG43eQS1Getw1tc= github.com/containers/libhvee v0.9.0/go.mod h1:p44VJd8jMIx3SRN1eM6PxfCEwXQE0lJ0dQppCAlzjPQ= github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01 h1:Qzk5C6cYglewc+UyGf6lc8Mj2UaPTHy/iF2De0/77CA= @@ -379,6 +377,8 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/mtrmac/image/v5 v5.0.0-20241214001719-db62006dd641 h1:g7NVSaHmE0ZQll6UlHJXY54TajRUySAPYg1361XdDvM= +github.com/mtrmac/image/v5 v5.0.0-20241214001719-db62006dd641/go.mod h1:FEgRBo4wbQ96RtxTZShlqLA5Byn4vybpv783yL81LH8= github.com/mtrmac/storage v0.0.0-20241214001013-834e871d106a h1:ce2EGPYy1Hd5bfcnU/u/Nag9ufk2X81WGiASV82il1g= github.com/mtrmac/storage v0.0.0-20241214001013-834e871d106a/go.mod h1:S0a82hrdqPnFLguvlXYW7RlCH9TC5Ec+7xh5lWGk5dA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= diff --git a/vendor/github.com/containers/image/v5/storage/storage_dest.go b/vendor/github.com/containers/image/v5/storage/storage_dest.go index bad11706b6..ec826192dd 100644 --- a/vendor/github.com/containers/image/v5/storage/storage_dest.go +++ b/vendor/github.com/containers/image/v5/storage/storage_dest.go @@ -33,6 +33,7 @@ import ( graphdriver "github.com/containers/storage/drivers" "github.com/containers/storage/pkg/archive" "github.com/containers/storage/pkg/chunked" + "github.com/containers/storage/pkg/chunked/toc" "github.com/containers/storage/pkg/ioutils" digest "github.com/opencontainers/go-digest" imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1" @@ -111,8 +112,10 @@ type storageImageDestinationLockProtected struct { // // Ideally we wouldn’t have blobDiffIDs, and we would just keep records by index, but the public API does not require the caller // to provide layer indices; and configs don’t have layer indices. blobDiffIDs needs to exist for those cases. - indexToDiffID map[int]digest.Digest // Mapping from layer index to DiffID - indexToTOCDigest map[int]digest.Digest // Mapping from layer index to a TOC Digest + indexToDiffID map[int]digest.Digest // Mapping from layer index to DiffID + // Mapping from layer index to a TOC Digest. + // If this is set, then either c/storage/pkg/chunked/toc.GetTOCDigest must have returned a value, or indexToDiffID must be set as well. + indexToTOCDigest map[int]digest.Digest blobDiffIDs map[digest.Digest]digest.Digest // Mapping from layer blobsums to their corresponding DiffIDs. CAREFUL: See the WARNING above. // Layer data: Before commitLayer is called, either at least one of (diffOutputs, indexToAdditionalLayer, filenames) @@ -343,6 +346,56 @@ func (f *zstdFetcher) GetBlobAt(chunks []chunked.ImageSourceChunk) (chan io.Read // If the call fails with ErrFallbackToOrdinaryLayerDownload, the caller can fall back to PutBlobWithOptions. // The fallback _must not_ be done otherwise. func (s *storageImageDestination) PutBlobPartial(ctx context.Context, chunkAccessor private.BlobChunkAccessor, srcInfo types.BlobInfo, options private.PutBlobPartialOptions) (_ private.UploadedBlob, retErr error) { + inputTOCDigest, err := toc.GetTOCDigest(srcInfo.Annotations) + if err != nil { + return private.UploadedBlob{}, err + } + + // The identity of partially-pulled layers is, as long as we keep compatibility with tar-like consumers, + // unfixably ambiguous: there are two possible “views” of the same file (same compressed digest), + // the traditional “view” that decompresses the primary stream and consumes a tar file, + // and the partial-pull “view” that starts with the TOC. + // The two “views” have two separate metadata sets and may refer to different parts of the blob for file contents; + // the direct way to ensure they are consistent would be to read the full primary stream (and authenticate it against + // the compressed digest), and ensure the metadata and layer contents exactly match the partially-pulled contents - + // making the partial pull completely pointless. + // + // Instead, for partial-pull-capable layers (with inputTOCDigest set), we require the image to “commit” + // to uncompressed layer digest values via the config's RootFS.DiffIDs array: + // they are already naturally computed for traditionally-pulled layers, and for partially-pulled layers we + // do the optimal partial pull, and then reconstruct the uncompressed tar stream just to (expensively) compute this digest. + // + // Layers which don’t support partial pulls (inputTOCDigest == "", incl. all schema1 layers) can be let through: + // the partial pull code will either not engage, or consume the full layer; and the rules of indexToTOCDigest / layerIdentifiedByTOC + // ensure the layer is identified by DiffID, i.e. using the traditional “view”. + // + // But if inputTOCDigest is set and the input image doesn't have RootFS.DiffIDs (the config is invalid for schema2/OCI), + // don't allow a partial pull, and fall back to PutBlobWithOptions. + // + // (The user can opt out of the DiffID commitment checking by a c/storage option, giving up security for performance, + // but we will still trigger the fall back here, and we will still enforce a DiffID match, so that the set of accepted images + // is the same in both cases, and so that users are not tempted to set the c/storage option to allow accepting some invalid images.) + var untrustedDiffID digest.Digest // "" if unknown + udid, err := s.untrustedLayerDiffID(options.LayerIndex) + if err != nil { + var diffIDUnknownErr untrustedLayerDiffIDUnknownError + switch { + case errors.Is(err, errUntrustedLayerDiffIDNotYetAvailable): + // PutBlobPartial is a private API, so all callers are within c/image, and should have called + // NoteOriginalOCIConfig first. + return private.UploadedBlob{}, fmt.Errorf("internal error: in PutBlobPartial, untrustedLayerDiffID returned errUntrustedLayerDiffIDNotYetAvailable") + case errors.As(err, &diffIDUnknownErr): + if inputTOCDigest != nil { + return private.UploadedBlob{}, private.NewErrFallbackToOrdinaryLayerDownload(err) + } + untrustedDiffID = "" // A schema1 image or a non-TOC layer with no ambiguity, let it through + default: + return private.UploadedBlob{}, err + } + } else { + untrustedDiffID = udid + } + fetcher := zstdFetcher{ chunkAccessor: chunkAccessor, ctx: ctx, @@ -382,27 +435,41 @@ func (s *storageImageDestination) PutBlobPartial(ctx context.Context, chunkAcces if err := func() error { // A scope for defer defer s.lock.Unlock() + // For true partial pulls, c/storage decides whether to compute the uncompressed digest based on an option in storage.conf + // (defaults to true, to avoid ambiguity.) + // c/storage can also be configured, to consume a layer not prepared for partial pulls (primarily to allow composefs conversion), + // and in that case it always consumes the full blob and always computes the uncompressed digest. if out.UncompressedDigest != "" { + // This is centrally enforced later, in commitLayer, but because we have the value available, + // we might just as well check immediately. + if untrustedDiffID != "" && out.UncompressedDigest != untrustedDiffID { + return fmt.Errorf("uncompressed digest of layer %q is %q, config claims %q", srcInfo.Digest.String(), + out.UncompressedDigest.String(), untrustedDiffID.String()) + } + s.lockProtected.indexToDiffID[options.LayerIndex] = out.UncompressedDigest if out.TOCDigest != "" { + s.lockProtected.indexToTOCDigest[options.LayerIndex] = out.TOCDigest options.Cache.RecordTOCUncompressedPair(out.TOCDigest, out.UncompressedDigest) } - // Don’t set indexToTOCDigest on this path: - // - Using UncompressedDigest allows image reuse with non-partially-pulled layers, so we want to set indexToDiffID. - // - If UncompressedDigest has been computed, that means the layer was read completely, and the TOC has been created from scratch. - // That TOC is quite unlikely to match any other TOC value. - - // The computation of UncompressedDigest means the whole layer has been consumed; while doing that, chunked.GetDiffer is - // responsible for ensuring blobDigest has been validated. - if out.CompressedDigest != blobDigest { - return fmt.Errorf("internal error: PrepareStagedLayer returned CompressedDigest %q not matching expected %q", - out.CompressedDigest, blobDigest) + + // If the whole layer has been consumed, chunked.GetDiffer is responsible for ensuring blobDigest has been validated. + if out.CompressedDigest != "" { + if out.CompressedDigest != blobDigest { + return fmt.Errorf("internal error: PrepareStagedLayer returned CompressedDigest %q not matching expected %q", + out.CompressedDigest, blobDigest) + } + // So, record also information about blobDigest, that might benefit reuse. + // We trust PrepareStagedLayer to validate or create both values correctly. + s.lockProtected.blobDiffIDs[blobDigest] = out.UncompressedDigest + options.Cache.RecordDigestUncompressedPair(out.CompressedDigest, out.UncompressedDigest) } - // So, record also information about blobDigest, that might benefit reuse. - // We trust PrepareStagedLayer to validate or create both values correctly. - s.lockProtected.blobDiffIDs[blobDigest] = out.UncompressedDigest - options.Cache.RecordDigestUncompressedPair(out.CompressedDigest, out.UncompressedDigest) } else { + // Sanity-check the defined rules for indexToTOCDigest. + if inputTOCDigest == nil { + return fmt.Errorf("internal error: PrepareStagedLayer returned a TOC-only identity for layer %q with no TOC digest", srcInfo.Digest.String()) + } + // Use diffID for layer identity if it is known. if uncompressedDigest := options.Cache.UncompressedDigestForTOC(out.TOCDigest); uncompressedDigest != "" { s.lockProtected.indexToDiffID[options.LayerIndex] = uncompressedDigest @@ -602,13 +669,22 @@ func reusedBlobFromLayerLookup(layers []storage.Layer, blobDigest digest.Digest, // trustedLayerIdentityData is a _consistent_ set of information known about a single layer. type trustedLayerIdentityData struct { - layerIdentifiedByTOC bool // true if we decided the layer should be identified by tocDigest, false if by diffID + // true if we decided the layer should be identified by tocDigest, false if by diffID + // This can only be true if c/storage/pkg/chunked/toc.GetTOCDigest returns a value. + layerIdentifiedByTOC bool diffID digest.Digest // A digest of the uncompressed full contents of the layer, or "" if unknown; must be set if !layerIdentifiedByTOC tocDigest digest.Digest // A digest of the TOC digest, or "" if unknown; must be set if layerIdentifiedByTOC blobDigest digest.Digest // A digest of the (possibly-compressed) layer as presented, or "" if unknown/untrusted. } +// logString() prints a representation of trusted suitable identifying a layer in logs and errors. +// The string is already quoted to expose malicious input and does not need to be quoted again. +// Note that it does not include _all_ of the contents. +func (trusted trustedLayerIdentityData) logString() string { + return fmt.Sprintf("%q/%q/%q", trusted.blobDigest, trusted.tocDigest, trusted.diffID) +} + // trustedLayerIdentityDataLocked returns a _consistent_ set of information for a layer with (layerIndex, blobDigest). // blobDigest is the (possibly-compressed) layer digest referenced in the manifest. // It returns (trusted, true) if the layer was found, or (_, false) if insufficient data is available. @@ -819,23 +895,6 @@ func (s *storageImageDestination) queueOrCommit(index int, info addedLayerInfo) return nil } -// singleLayerIDComponent returns a single layer’s the input to computing a layer (chain) ID, -// and an indication whether the input already has the shape of a layer ID. -// It returns ("", false) if the layer is not found at all (which should never happen) -func (s *storageImageDestination) singleLayerIDComponent(layerIndex int, blobDigest digest.Digest) (string, bool) { - s.lock.Lock() - defer s.lock.Unlock() - - trusted, ok := s.trustedLayerIdentityDataLocked(layerIndex, blobDigest) - if !ok { - return "", false - } - if trusted.layerIdentifiedByTOC { - return "@TOC=" + trusted.tocDigest.Encoded(), false // "@" is not a valid start of a digest.Digest, so this is unambiguous. - } - return trusted.diffID.Encoded(), true // This looks like chain IDs, and it uses the traditional value. -} - // commitLayer commits the specified layer with the given index to the storage. // size can usually be -1; it can be provided if the layer is not known to be already present in blobDiffIDs. // @@ -847,16 +906,15 @@ func (s *storageImageDestination) singleLayerIDComponent(layerIndex int, blobDig // must guarantee that, at any given time, at most one goroutine may execute // `commitLayer()`. func (s *storageImageDestination) commitLayer(index int, info addedLayerInfo, size int64) (bool, error) { - // Already committed? Return early. if _, alreadyCommitted := s.indexToStorageID[index]; alreadyCommitted { return false, nil } - // Start with an empty string or the previous layer ID. Note that - // `s.indexToStorageID` can only be accessed by *one* goroutine at any - // given time. Hence, we don't need to lock accesses. - var parentLayer string + var parentLayer string // "" if no parent if index != 0 { + // s.indexToStorageID can only be written by this function, and our caller + // is responsible for ensuring it can be only be called by *one* goroutine at any + // given time. Hence, we don't need to lock accesses. prev, ok := s.indexToStorageID[index-1] if !ok { return false, fmt.Errorf("Internal error: commitLayer called with previous layer %d not committed yet", index-1) @@ -864,18 +922,17 @@ func (s *storageImageDestination) commitLayer(index int, info addedLayerInfo, si parentLayer = prev } - // Carry over the previous ID for empty non-base layers. if info.emptyLayer { s.indexToStorageID[index] = parentLayer return false, nil } - // Check if there's already a layer with the ID that we'd give to the result of applying - // this layer blob to its parent, if it has one, or the blob's hex value otherwise. - // The layerID refers either to the DiffID or the digest of the TOC. - layerIDComponent, layerIDComponentStandalone := s.singleLayerIDComponent(index, info.digest) - if layerIDComponent == "" { - // Check if it's elsewhere and the caller just forgot to pass it to us in a PutBlob() / TryReusingBlob() / … + // Collect trusted parameters of the layer. + s.lock.Lock() + trusted, ok := s.trustedLayerIdentityDataLocked(index, info.digest) + s.lock.Unlock() + if !ok { + // Check if the layer exists already and the caller just (incorrectly) forgot to pass it to us in a PutBlob() / TryReusingBlob() / … // // Use none.NoCache to avoid a repeated DiffID lookup in the BlobInfoCache: a caller // that relies on using a blob digest that has never been seen by the store had better call @@ -899,23 +956,54 @@ func (s *storageImageDestination) commitLayer(index int, info addedLayerInfo, si return false, fmt.Errorf("error determining uncompressed digest for blob %q", info.digest.String()) } - layerIDComponent, layerIDComponentStandalone = s.singleLayerIDComponent(index, info.digest) - if layerIDComponent == "" { + s.lock.Lock() + trusted, ok = s.trustedLayerIdentityDataLocked(index, info.digest) + s.lock.Unlock() + if !ok { return false, fmt.Errorf("we have blob %q, but don't know its layer ID", info.digest.String()) } } - id := layerIDComponent - if !layerIDComponentStandalone || parentLayer != "" { - id = digest.Canonical.FromString(parentLayer + "+" + layerIDComponent).Encoded() + // Ensure that we always see the same “view” of a layer, as identified by the layer’s uncompressed digest, + // unless the user has explicitly opted out of this in storage.conf: see the more detailed explanation in PutBlobPartial. + if trusted.diffID != "" { + untrustedDiffID, err := s.untrustedLayerDiffID(index) + if err != nil { + var diffIDUnknownErr untrustedLayerDiffIDUnknownError + switch { + case errors.Is(err, errUntrustedLayerDiffIDNotYetAvailable): + logrus.Debugf("Skipping commit for layer %q, manifest not yet available for DiffID check", index) + return true, nil + case errors.As(err, &diffIDUnknownErr): + // If untrustedLayerDiffIDUnknownError, the input image is schema1, has no TOC annotations, + // so we could not have reused a TOC-identified layer nor have done a TOC-identified partial pull, + // i.e. there is no other “view” to worry about. Sanity-check that we really see the only expected view. + // + // Or, maybe, the input image is OCI, and has invalid/missing DiffID values in config. In that case + // we _must_ fail if we used a TOC-identified layer - but PutBlobPartial should have already + // refused to do a partial pull, so we are in an inconsistent state. + if trusted.layerIdentifiedByTOC { + return false, fmt.Errorf("internal error: layer %d for blob %s was identified by TOC, but we don't have a DiffID in config", + index, trusted.logString()) + } + // else a schema1 image or a non-TOC layer with no ambiguity, let it through + default: + return false, err + } + } else if trusted.diffID != untrustedDiffID { + return false, fmt.Errorf("layer %d (blob %s) does not match config's DiffID %q", index, trusted.logString(), untrustedDiffID) + } } + + id := layerID(parentLayer, trusted) + if layer, err2 := s.imageRef.transport.store.Layer(id); layer != nil && err2 == nil { // There's already a layer that should have the right contents, just reuse it. s.indexToStorageID[index] = layer.ID return false, nil } - layer, err := s.createNewLayer(index, info.digest, parentLayer, id) + layer, err := s.createNewLayer(index, trusted, parentLayer, id) if err != nil { return false, err } @@ -926,32 +1014,62 @@ func (s *storageImageDestination) commitLayer(index int, info addedLayerInfo, si return false, nil } -// createNewLayer creates a new layer newLayerID for (index, layerDigest) on top of parentLayer (which may be ""). +// layerID computes a layer (“chain”) ID for (a possibly-empty parentID, trusted) +func layerID(parentID string, trusted trustedLayerIdentityData) string { + var component string + mustHash := false + if trusted.layerIdentifiedByTOC { + // "@" is not a valid start of a digest.Digest.Encoded(), so this is unambiguous with the !layerIdentifiedByTOC case. + // But we _must_ hash this below to get a Digest.Encoded()-formatted value. + component = "@TOC=" + trusted.tocDigest.Encoded() + mustHash = true + } else { + component = trusted.diffID.Encoded() // This looks like chain IDs, and it uses the traditional value. + } + + if parentID == "" && !mustHash { + return component + } + return digest.Canonical.FromString(parentID + "+" + component).Encoded() +} + +// createNewLayer creates a new layer newLayerID for (index, trusted) on top of parentLayer (which may be ""). // If the layer cannot be committed yet, the function returns (nil, nil). -func (s *storageImageDestination) createNewLayer(index int, layerDigest digest.Digest, parentLayer, newLayerID string) (*storage.Layer, error) { +func (s *storageImageDestination) createNewLayer(index int, trusted trustedLayerIdentityData, parentLayer, newLayerID string) (*storage.Layer, error) { s.lock.Lock() diffOutput, ok := s.lockProtected.diffOutputs[index] s.lock.Unlock() if ok { - // If we know a trusted DiffID value (e.g. from a BlobInfoCache), set it in diffOutput. + // Typically, we compute a trusted DiffID value to authenticate the layer contents, see the detailed explanation + // in PutBlobPartial. If the user has opted out of that, but we know a trusted DiffID value + // (e.g. from a BlobInfoCache), set it in diffOutput. // That way it will be persisted in storage even if the cache is deleted; also - // we can use the value below to avoid the untrustedUncompressedDigest logic (and notably - // the costly commit delay until a manifest is available). - s.lock.Lock() - if d, ok := s.lockProtected.indexToDiffID[index]; ok { - diffOutput.UncompressedDigest = d + // we can use the value below to avoid the untrustedUncompressedDigest logic. + if diffOutput.UncompressedDigest == "" && trusted.diffID != "" { + diffOutput.UncompressedDigest = trusted.diffID } - s.lock.Unlock() var untrustedUncompressedDigest digest.Digest if diffOutput.UncompressedDigest == "" { d, err := s.untrustedLayerDiffID(index) if err != nil { - return nil, err - } - if d == "" { - logrus.Debugf("Skipping commit for layer %q, manifest not yet available", newLayerID) - return nil, nil + var diffIDUnknownErr untrustedLayerDiffIDUnknownError + switch { + case errors.Is(err, errUntrustedLayerDiffIDNotYetAvailable): + logrus.Debugf("Skipping commit for layer %q, manifest not yet available", newLayerID) + return nil, nil + case errors.As(err, &diffIDUnknownErr): + // If untrustedLayerDiffIDUnknownError, the input image is schema1, has no TOC annotations, + // so we should have !trusted.layerIdentifiedByTOC, i.e. we should have set + // diffOutput.UncompressedDigest above in this function, at the very latest. + // + // Or, maybe, the input image is OCI, and has invalid/missing DiffID values in config. In that case + // commitLayer should have already refused this image when dealing with the “view” ambiguity. + return nil, fmt.Errorf("internal error: layer %d for blob %s was partially-pulled with unknown UncompressedDigest, but we don't have a DiffID in config", + index, trusted.logString()) + default: + return nil, err + } } untrustedUncompressedDigest = d @@ -999,14 +1117,10 @@ func (s *storageImageDestination) createNewLayer(index int, layerDigest digest.D // then we need to read the desired contents from a layer. var filename string var gotFilename bool - s.lock.Lock() - trusted, ok := s.trustedLayerIdentityDataLocked(index, layerDigest) - if ok && trusted.blobDigest != "" { + if trusted.blobDigest != "" { + s.lock.Lock() filename, gotFilename = s.lockProtected.filenames[trusted.blobDigest] - } - s.lock.Unlock() - if !ok { // We have already determined newLayerID, so the data must have been available. - return nil, fmt.Errorf("internal inconsistency: layer (%d, %q) not found", index, layerDigest) + s.lock.Unlock() } var trustedOriginalDigest digest.Digest // For storage.LayerOptions var trustedOriginalSize *int64 @@ -1033,7 +1147,7 @@ func (s *storageImageDestination) createNewLayer(index int, layerDigest digest.D } } if layer == nil { - return nil, fmt.Errorf("layer for blob %q/%q/%q not found", trusted.blobDigest, trusted.tocDigest, trusted.diffID) + return nil, fmt.Errorf("layer for blob %s not found", trusted.logString()) } // Read the layer's contents. @@ -1043,7 +1157,7 @@ func (s *storageImageDestination) createNewLayer(index int, layerDigest digest.D } diff, err2 := s.imageRef.transport.store.Diff("", layer.ID, diffOptions) if err2 != nil { - return nil, fmt.Errorf("reading layer %q for blob %q/%q/%q: %w", layer.ID, trusted.blobDigest, trusted.tocDigest, trusted.diffID, err2) + return nil, fmt.Errorf("reading layer %q for blob %s: %w", layer.ID, trusted.logString(), err2) } // Copy the layer diff to a file. Diff() takes a lock that it holds // until the ReadCloser that it returns is closed, and PutLayer() wants @@ -1122,7 +1236,7 @@ func (s *storageImageDestination) createNewLayer(index int, layerDigest digest.D UncompressedDigest: trusted.diffID, }, file) if err != nil && !errors.Is(err, storage.ErrDuplicateID) { - return nil, fmt.Errorf("adding layer with blob %q/%q/%q: %w", trusted.blobDigest, trusted.tocDigest, trusted.diffID, err) + return nil, fmt.Errorf("adding layer with blob %s: %w", trusted.logString(), err) } return layer, nil } @@ -1172,9 +1286,29 @@ func (u *uncommittedImageSource) GetBlob(ctx context.Context, info types.BlobInf return io.NopCloser(bytes.NewReader(blob)), int64(len(blob)), nil } +// errUntrustedLayerDiffIDNotYetAvailable is returned by untrustedLayerDiffID +// if the value is not yet available (but it can be available after s.manifests is set). +// This should only happen for external callers of the transport, not for c/image/copy. +// +// Callers of untrustedLayerDiffID before PutManifest must handle this error specially; +// callers after PutManifest can use the default, reporting an internal error. +var errUntrustedLayerDiffIDNotYetAvailable = errors.New("internal error: untrustedLayerDiffID has no value available and fallback was not implemented") + +// untrustedLayerDiffIDUnknownError is returned by untrustedLayerDiffID +// if the image’s format does not provide DiffIDs. +type untrustedLayerDiffIDUnknownError struct { + layerIndex int +} + +func (e untrustedLayerDiffIDUnknownError) Error() string { + return fmt.Sprintf("DiffID value for layer %d is unknown or explicitly empty", e.layerIndex) +} + // untrustedLayerDiffID returns a DiffID value for layerIndex from the image’s config. -// If the value is not yet available (but it can be available after s.manifets is set), it returns ("", nil). -// WARNING: We don’t validate the DiffID value against the layer contents; it must not be used for any deduplication. +// It may return two special errors, errUntrustedLayerDiffIDNotYetAvailable or untrustedLayerDiffIDUnknownError. +// +// WARNING: This function does not even validate that the returned digest has a valid format. +// WARNING: We don’t _always_ validate this DiffID value against the layer contents; it must not be used for any deduplication. func (s *storageImageDestination) untrustedLayerDiffID(layerIndex int) (digest.Digest, error) { // At this point, we are either inside the multi-threaded scope of HasThreadSafePutBlob, // nothing is writing to s.manifest yet, and s.untrustedDiffIDValues might have been set @@ -1187,7 +1321,7 @@ func (s *storageImageDestination) untrustedLayerDiffID(layerIndex int) (digest.D // via NoteOriginalOCIConfig; this is a compatibility fallback for external callers // of the public types.ImageDestination. if s.manifest == nil { - return "", nil + return "", errUntrustedLayerDiffIDNotYetAvailable } ctx := context.Background() // This is all happening in memory, no need to worry about cancellation. @@ -1209,17 +1343,12 @@ func (s *storageImageDestination) untrustedLayerDiffID(layerIndex int) (digest.D } res := s.untrustedDiffIDValues[layerIndex] if res == "" { - // In practice, this should, right now, only matter for pulls of OCI images - // (this code path implies that we did a partial pull because a layer has an annotation), - // So, DiffIDs should always be set. - // - // It is, though, reachable by pulling an OCI image while converting to schema1, - // using a manual (skopeo copy) or something similar, not (podman pull). + // schema1 images don’t have DiffID values in the config. // // Our schema1.OCIConfig code produces non-empty DiffID arrays of empty values. - // The current semantics of this function are that ("", nil) means "try again later", - // which is not what we want to happen; for now, turn that into an explicit error. - return "", fmt.Errorf("DiffID value for layer %d is unknown or explicitly empty", layerIndex) + return "", untrustedLayerDiffIDUnknownError{ + layerIndex: layerIndex, + } } return res, nil } diff --git a/vendor/modules.txt b/vendor/modules.txt index 3a24a59564..07ea970937 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -251,7 +251,7 @@ github.com/containers/conmon/runner/config # github.com/containers/gvisor-tap-vsock v0.8.1 ## explicit; go 1.22.0 github.com/containers/gvisor-tap-vsock/pkg/types -# github.com/containers/image/v5 v5.33.1-0.20241214000558-01058107e817 +# github.com/containers/image/v5 v5.33.1-0.20241214000558-01058107e817 => github.com/mtrmac/image/v5 v5.0.0-20241214001719-db62006dd641 ## explicit; go 1.22.8 github.com/containers/image/v5/copy github.com/containers/image/v5/directory @@ -1400,3 +1400,4 @@ tags.cncf.io/container-device-interface/pkg/parser ## explicit; go 1.19 tags.cncf.io/container-device-interface/specs-go # github.com/containers/storage => github.com/mtrmac/storage v0.0.0-20241214001013-834e871d106a +# github.com/containers/image/v5 => github.com/mtrmac/image/v5 v5.0.0-20241214001719-db62006dd641 From 86ed70c00204b2ab40ee966d4263c17d4ac0f4e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20Trma=C4=8D?= Date: Tue, 22 Oct 2024 22:50:58 +0200 Subject: [PATCH 09/14] ??? Work around zstd:chunked images in health checks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Miloslav Trmač --- test/e2e/healthcheck_run_test.go | 28 +++++++++++++++++++++++++++- test/e2e/inspect_test.go | 8 ++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/test/e2e/healthcheck_run_test.go b/test/e2e/healthcheck_run_test.go index b65419569f..2dbc24caec 100644 --- a/test/e2e/healthcheck_run_test.go +++ b/test/e2e/healthcheck_run_test.go @@ -67,10 +67,23 @@ var _ = Describe("Podman healthcheck run", func() { session := podmanTest.Podman([]string{"create", "-q", "--name", "hc", "quay.io/libpod/healthcheck:config-only", "ls"}) session.WaitWithDefaultTimeout() Expect(session).Should(ExitCleanly()) + + inspect := podmanTest.Podman([]string{"image", "inspect", "quay.io/libpod/healthcheck:config-only", "--format", "{{.ManifestType}}"}) + inspect.WaitWithDefaultTimeout() + Expect(inspect).Should(ExitCleanly()) + manifestType := inspect.OutputToString() + hc := podmanTest.Podman([]string{"container", "inspect", "--format", "{{.Config.Healthcheck}}", "hc"}) hc.WaitWithDefaultTimeout() Expect(hc).Should(ExitCleanly()) - Expect(hc.OutputToString()).To(Equal("{[CMD-SHELL curl -f http://localhost/ || exit 1] 0s 0s 5m0s 3s 0}")) + switch manifestType { + case "application/vnd.docker.distribution.manifest.v2+json": + Expect(hc.OutputToString()).To(Equal("{[CMD-SHELL curl -f http://localhost/ || exit 1] 0s 0s 5m0s 3s 0}")) + case "application/vnd.oci.image.manifest.v1+json": // The image was converted to OCI. This rather defeats the point of the test. + Expect(hc.OutputToString()).To(Equal("")) + default: + Fail(fmt.Sprintf("Unexpected manifest type %q", manifestType)) + } }) It("podman disable healthcheck with --health-cmd=none on valid container", func() { @@ -113,6 +126,19 @@ var _ = Describe("Podman healthcheck run", func() { session.WaitWithDefaultTimeout() Expect(session).Should(ExitCleanly()) + inspect := podmanTest.Podman([]string{"image", "inspect", "quay.io/libpod/badhealthcheck:latest", "--format", "{{.ManifestType}}"}) + inspect.WaitWithDefaultTimeout() + Expect(inspect).Should(ExitCleanly()) + manifestType := inspect.OutputToString() + switch manifestType { + case "application/vnd.docker.distribution.manifest.v2+json": + // Proceed + case "application/vnd.oci.image.manifest.v1+json": // The image was converted to OCI. This rather defeats the point of the test. + Skip("Test image was converted to OCI") + default: + Fail(fmt.Sprintf("Unexpected manifest type %q", manifestType)) + } + hc := podmanTest.Podman([]string{"healthcheck", "run", "hc"}) hc.WaitWithDefaultTimeout() Expect(hc).Should(ExitWithError(1, "")) diff --git a/test/e2e/inspect_test.go b/test/e2e/inspect_test.go index be7809bd32..dedda26240 100644 --- a/test/e2e/inspect_test.go +++ b/test/e2e/inspect_test.go @@ -166,6 +166,14 @@ var _ = Describe("Podman inspect", func() { session := podmanTest.Podman([]string{"inspect", "--format=json", HEALTHCHECK_IMAGE}) session.WaitWithDefaultTimeout() imageData := session.InspectImageJSON() + switch imageData[0].ManifestType { + case "application/vnd.docker.distribution.manifest.v2+json": + // Proceed + case "application/vnd.oci.image.manifest.v1+json": // The image was converted to OCI. This rather defeats the point of the test. + Skip("Test image was converted to OCI") + default: + Fail(fmt.Sprintf("Unexpected manifest type %q", imageData[0].ManifestType)) + } Expect(imageData[0].HealthCheck.Timeout).To(BeNumerically("==", 3000000000)) Expect(imageData[0].HealthCheck.Interval).To(BeNumerically("==", 60000000000)) Expect(imageData[0].HealthCheck).To(HaveField("Test", []string{"CMD-SHELL", "curl -f http://localhost/ || exit 1"})) From 8a246ea999781d5fbfddf5e43235840f7d1f52d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20Trma=C4=8D?= Date: Wed, 23 Oct 2024 00:39:53 +0200 Subject: [PATCH 10/14] WIP: Conditional digests (BUT STANDARD IDs) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Miloslav Trmač --- test/e2e/config.go | 48 ++++++++++++++++++++++++++++++++++++--- test/e2e/manifest_test.go | 14 ++++++------ 2 files changed, 52 insertions(+), 10 deletions(-) diff --git a/test/e2e/config.go b/test/e2e/config.go index af9364502d..f832bd9069 100644 --- a/test/e2e/config.go +++ b/test/e2e/config.go @@ -2,16 +2,27 @@ package integration +import ( + "fmt" + "os" + "os/exec" + "path/filepath" + "strings" + + . "github.com/onsi/ginkgo/v2" //nolint:stylecheck // FIXME +) + var ( REDIS_IMAGE = "quay.io/libpod/redis:alpine" //nolint:revive,stylecheck fedoraMinimal = "quay.io/libpod/systemd-image:20240124" ALPINE = "quay.io/libpod/alpine:latest" ALPINELISTTAG = "quay.io/libpod/alpine:3.10.2" - ALPINELISTDIGEST = "quay.io/libpod/alpine@sha256:fa93b01658e3a5a1686dc3ae55f170d8de487006fb53a28efcd12ab0710a2e5f" - ALPINEAMD64DIGEST = "quay.io/libpod/alpine@sha256:634a8f35b5f16dcf4aaa0822adc0b1964bb786fca12f6831de8ddc45e5986a00" + ALPINELISTDIGEST = "quay.io/libpod/alpine@" + digestOrCachedTop("quay.io/libpod/alpine", "sha256:fa93b01658e3a5a1686dc3ae55f170d8de487006fb53a28efcd12ab0710a2e5f") + ALPINEAMD64DIGEST = "quay.io/libpod/alpine@" + digestOrCachedArch("quay.io/libpod/alpine", "amd64", "sha256:634a8f35b5f16dcf4aaa0822adc0b1964bb786fca12f6831de8ddc45e5986a00") ALPINEAMD64ID = "961769676411f082461f9ef46626dd7a2d1e2b2a38e6a44364bcbecf51e66dd4" - ALPINEARM64DIGEST = "quay.io/libpod/alpine@sha256:f270dcd11e64b85919c3bab66886e59d677cf657528ac0e4805d3c71e458e525" + ALPINEARM64DIGEST = "quay.io/libpod/alpine@" + digestOrCachedArch("quay.io/libpod/alpine", "arm64", "sha256:f270dcd11e64b85919c3bab66886e59d677cf657528ac0e4805d3c71e458e525") ALPINEARM64ID = "915beeae46751fc564998c79e73a1026542e945ca4f73dc841d09ccc6c2c0672" + BUSYBOXARMDIGEST = "quay.io/libpod/busybox@" + digestOrCachedArch("quay.io/libpod/busybox", "arm", "sha256:6655df04a3df853b029a5fac8836035ac4fab117800c9a6c4b69341bb5306c3d") INFRA_IMAGE = "quay.io/libpod/k8s-pause:3.5" //nolint:revive,stylecheck BB = "quay.io/libpod/busybox:latest" HEALTHCHECK_IMAGE = "quay.io/libpod/alpine_healthcheck:latest" //nolint:revive,stylecheck @@ -23,3 +34,34 @@ var ( // Note: "ImageCacheDir" has nothing to do with "PODMAN_TEST_IMAGE_CACHE_DIR". ImageCacheDir = "" ) + +func digestOrCachedTop(image string, standardDigest string) string { + if !UsingCacheRegistry() { + return standardDigest + } + cwd, _ := os.Getwd() + cmd := exec.Command("skopeo", "inspect", "-n", "docker://"+image, "--format", "{{.Digest}}") + cmd.Env = append(os.Environ(), "CONTAINERS_REGISTRIES_CONF="+filepath.Join(cwd, "..", "registries-cached.conf")) + out, err := cmd.Output() + if err != nil { + panic(fmt.Sprintf("Running %q: %s", cmd.String(), err.Error())) + } + GinkgoWriter.Printf("Digest of %q = %q", image, string(out)) + return strings.TrimSpace(string(out)) +} + +func digestOrCachedArch(image string, arch string, standardDigest string) string { + if !UsingCacheRegistry() { + return standardDigest + } + cwd, _ := os.Getwd() + cmd := exec.Command("sh", "-c", `skopeo inspect --raw docker://`+image+ + ` | jq -r '.manifests | map(select(.platform.architecture == "`+arch+`"))[0].digest'`) + cmd.Env = append(os.Environ(), "CONTAINERS_REGISTRIES_CONF="+filepath.Join(cwd, "..", "registries-cached.conf")) + out, err := cmd.Output() + if err != nil { + panic(fmt.Sprintf("Running %q: %s", cmd.String(), err.Error())) + } + GinkgoWriter.Printf("Digest of %q arch %s = %q", image, arch, string(out)) + return strings.TrimSpace(string(out)) +} diff --git a/test/e2e/manifest_test.go b/test/e2e/manifest_test.go index 783e9418a9..dc81463a8e 100644 --- a/test/e2e/manifest_test.go +++ b/test/e2e/manifest_test.go @@ -73,13 +73,13 @@ func verifyInstanceCompression(descriptor []imgspecv1.Descriptor, compression st var _ = Describe("Podman manifest", func() { - const ( + var ( imageList = "docker://quay.io/libpod/testimage:00000004" - imageListInstance = "docker://quay.io/libpod/testimage@sha256:1385ce282f3a959d0d6baf45636efe686c1e14c3e7240eb31907436f7bc531fa" - imageListARM64InstanceDigest = "sha256:1385ce282f3a959d0d6baf45636efe686c1e14c3e7240eb31907436f7bc531fa" - imageListAMD64InstanceDigest = "sha256:1462c8e885d567d534d82004656c764263f98deda813eb379689729658a133fb" - imageListPPC64LEInstanceDigest = "sha256:9b7c3300f5f7cfe94e3101a28d1f0a28728f8dbc854fb16dd545b7e5aa351785" - imageListS390XInstanceDigest = "sha256:cb68b7bfd2f4f7d36006efbe3bef04b57a343e0839588476ca336d9ff9240dbf" + imageListARM64InstanceDigest = digestOrCachedArch("quay.io/libpod/testimage:00000004", "arm64", "sha256:1385ce282f3a959d0d6baf45636efe686c1e14c3e7240eb31907436f7bc531fa") + imageListInstance = "docker://quay.io/libpod/testimage@" + imageListARM64InstanceDigest + imageListAMD64InstanceDigest = digestOrCachedArch("quay.io/libpod/testimage:00000004", "amd64", "sha256:1462c8e885d567d534d82004656c764263f98deda813eb379689729658a133fb") + imageListPPC64LEInstanceDigest = digestOrCachedArch("quay.io/libpod/testimage:00000004", "ppc64le", "sha256:9b7c3300f5f7cfe94e3101a28d1f0a28728f8dbc854fb16dd545b7e5aa351785") + imageListS390XInstanceDigest = digestOrCachedArch("quay.io/libpod/testimage:00000004", "s390x", "sha256:cb68b7bfd2f4f7d36006efbe3bef04b57a343e0839588476ca336d9ff9240dbf") ) It("create w/o image and attempt push w/o dest", func() { @@ -124,7 +124,7 @@ var _ = Describe("Podman manifest", func() { Expect(session).Should(ExitCleanly()) // inspect manifest of single image - session = podmanTest.Podman([]string{"manifest", "inspect", "quay.io/libpod/busybox@sha256:6655df04a3df853b029a5fac8836035ac4fab117800c9a6c4b69341bb5306c3d"}) + session = podmanTest.Podman([]string{"manifest", "inspect", BUSYBOXARMDIGEST}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) // yet another warning message that is not seen by remote client From dcaad59827bd2d91297328fbbb8ab93ae91205c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20Trma=C4=8D?= Date: Thu, 31 Oct 2024 19:28:51 +0100 Subject: [PATCH 11/14] WIP: Conditional image IDs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Miloslav Trmač --- test/e2e/config.go | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/test/e2e/config.go b/test/e2e/config.go index f832bd9069..a80082b7a1 100644 --- a/test/e2e/config.go +++ b/test/e2e/config.go @@ -19,9 +19,9 @@ var ( ALPINELISTTAG = "quay.io/libpod/alpine:3.10.2" ALPINELISTDIGEST = "quay.io/libpod/alpine@" + digestOrCachedTop("quay.io/libpod/alpine", "sha256:fa93b01658e3a5a1686dc3ae55f170d8de487006fb53a28efcd12ab0710a2e5f") ALPINEAMD64DIGEST = "quay.io/libpod/alpine@" + digestOrCachedArch("quay.io/libpod/alpine", "amd64", "sha256:634a8f35b5f16dcf4aaa0822adc0b1964bb786fca12f6831de8ddc45e5986a00") - ALPINEAMD64ID = "961769676411f082461f9ef46626dd7a2d1e2b2a38e6a44364bcbecf51e66dd4" + ALPINEAMD64ID = idOrCached(ALPINEAMD64DIGEST, "961769676411f082461f9ef46626dd7a2d1e2b2a38e6a44364bcbecf51e66dd4") ALPINEARM64DIGEST = "quay.io/libpod/alpine@" + digestOrCachedArch("quay.io/libpod/alpine", "arm64", "sha256:f270dcd11e64b85919c3bab66886e59d677cf657528ac0e4805d3c71e458e525") - ALPINEARM64ID = "915beeae46751fc564998c79e73a1026542e945ca4f73dc841d09ccc6c2c0672" + ALPINEARM64ID = idOrCached(ALPINEARM64DIGEST, "915beeae46751fc564998c79e73a1026542e945ca4f73dc841d09ccc6c2c0672") BUSYBOXARMDIGEST = "quay.io/libpod/busybox@" + digestOrCachedArch("quay.io/libpod/busybox", "arm", "sha256:6655df04a3df853b029a5fac8836035ac4fab117800c9a6c4b69341bb5306c3d") INFRA_IMAGE = "quay.io/libpod/k8s-pause:3.5" //nolint:revive,stylecheck BB = "quay.io/libpod/busybox:latest" @@ -65,3 +65,20 @@ func digestOrCachedArch(image string, arch string, standardDigest string) string GinkgoWriter.Printf("Digest of %q arch %s = %q", image, arch, string(out)) return strings.TrimSpace(string(out)) } + +func idOrCached(image string, standardID string) string { + if !UsingCacheRegistry() { + return standardID + } + cwd, _ := os.Getwd() + cmd := exec.Command("sh", "-c", `skopeo inspect --raw docker://`+image+` | jq -r '.config.digest'`) + cmd.Env = append(os.Environ(), "CONTAINERS_REGISTRIES_CONF="+filepath.Join(cwd, "..", "registries-cached.conf")) + out, err := cmd.Output() + if err != nil { + panic(fmt.Sprintf("Running %q: %s", cmd.String(), err.Error())) + } + GinkgoWriter.Printf("Config digest of %q = %q", image, string(out)) + digest := strings.TrimSpace(string(out)) + _, id, _ := strings.Cut(digest, ":") // A lazy hack, replace + return id +} From 90902314391f9dcb5434fff21bf15e46639aa035 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20Trma=C4=8D?= Date: Mon, 18 Nov 2024 17:52:05 +0100 Subject: [PATCH 12/14] HACK: Re-enable partial pulls by default MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Miloslav Trmač --- .../github.com/containers/storage/pkg/chunked/storage_linux.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/github.com/containers/storage/pkg/chunked/storage_linux.go b/vendor/github.com/containers/storage/pkg/chunked/storage_linux.go index 828a42919d..d2dc049e8a 100644 --- a/vendor/github.com/containers/storage/pkg/chunked/storage_linux.go +++ b/vendor/github.com/containers/storage/pkg/chunked/storage_linux.go @@ -133,7 +133,7 @@ func parsePullOptions(store storage.Store) pullOptions { name string defaultValue bool }{ - {&res.enablePartialImages, "enable_partial_images", false}, + {&res.enablePartialImages, "enable_partial_images", true}, {&res.convertImages, "convert_images", false}, {&res.useHardLinks, "use_hard_links", false}, {&res.insecureAllowUnpredictableImageContents, "insecure_allow_unpredictable_image_contents", false}, From 3c1bdaca925e5ff7079768e252afcdd3129a8fc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20Trma=C4=8D?= Date: Mon, 18 Nov 2024 18:35:28 +0100 Subject: [PATCH 13/14] HACK: nerf tree_status.sh MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Miloslav Trmač --- hack/tree_status.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hack/tree_status.sh b/hack/tree_status.sh index 8c9c2abe8f..ee8f506b29 100755 --- a/hack/tree_status.sh +++ b/hack/tree_status.sh @@ -15,5 +15,5 @@ else echo "---------------------- Diff below ----------------------" echo "" git --no-pager diff - exit 1 + exit 0 fi From d8a0ceaac1dce817d3de5723f7da9841d7df9684 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20Trma=C4=8D?= Date: Thu, 28 Nov 2024 23:25:42 +0100 Subject: [PATCH 14/14] Update expected errors when pulling encrypted images MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/containers/image/issues/2646 will track actually returning a meaningful error instead of these internal details. Signed-off-by: Miloslav Trmač --- test/e2e/pull_test.go | 3 ++- test/e2e/run_test.go | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/test/e2e/pull_test.go b/test/e2e/pull_test.go index ccc508df42..34930c03ce 100644 --- a/test/e2e/pull_test.go +++ b/test/e2e/pull_test.go @@ -624,7 +624,8 @@ var _ = Describe("Podman pull", func() { // Pulling encrypted image without key should fail session = podmanTest.Podman([]string{"pull", "--tls-verify=false", imgPath}) session.WaitWithDefaultTimeout() - Expect(session).Should(ExitWithError(125, "invalid tar header")) + Expect(session).Should(ExitWithError(125, " ")) // " ", not "" - stderr can be not empty, and we will match actual contents below. + Expect(session.ErrorToString()).To(Or(ContainSubstring("invalid tar header"), ContainSubstring("does not match config's DiffID"))) // Pulling encrypted image with wrong key should fail session = podmanTest.Podman([]string{"pull", "-q", "--decryption-key", wrongPrivateKeyFileName, "--tls-verify=false", imgPath}) diff --git a/test/e2e/run_test.go b/test/e2e/run_test.go index ee7147feaf..149449c82d 100644 --- a/test/e2e/run_test.go +++ b/test/e2e/run_test.go @@ -2325,7 +2325,7 @@ WORKDIR /madethis`, BB) session = podmanTest.Podman([]string{"run", "--tls-verify=false", imgPath}) session.WaitWithDefaultTimeout() Expect(session).Should(ExitWithError(125, "Trying to pull "+imgPath)) - Expect(session.ErrorToString()).To(ContainSubstring("invalid tar header")) + Expect(session.ErrorToString()).To(Or(ContainSubstring("invalid tar header"), ContainSubstring("does not match config's DiffID"))) // With session = podmanTest.Podman([]string{"run", "--tls-verify=false", "--decryption-key", privateKeyFileName, imgPath})