Port osbuild/images v0.33.0 with dot-notation to composer
Update the osbuild/images to the version which introduces "dot notation" for distro release versions. - Replace all uses of distroregistry by distrofactory. - Delete local version of reporegistry and use the one from the osbuild/images. - Weldr: unify `createWeldrAPI()` and `createWeldrAPI2()` into a single `createTestWeldrAPI()` function`. - store/fixture: rework fixtures to allow overriding the host distro name and host architecture name. A cleanup function to restore the host distro and arch names is always part of the fixture struct. - Delete `distro_mock` package, since it is no longer used. - Bump the required version of osbuild to 98, because the OSCAP customization is using the 'compress_results' stage option, which is not available in older versions of osbuild. Signed-off-by: Tomáš Hozza <thozza@redhat.com>
This commit is contained in:
parent
f6ff8c40dd
commit
625b1578fa
1166 changed files with 154457 additions and 5508 deletions
186
vendor/github.com/containers/storage/.cirrus.yml
generated
vendored
Normal file
186
vendor/github.com/containers/storage/.cirrus.yml
generated
vendored
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
---
|
||||
|
||||
# Main collection of env. vars to set for all tasks and scripts.
|
||||
env:
|
||||
####
|
||||
#### Global variables used for all tasks
|
||||
####
|
||||
# Overrides default location (/tmp/cirrus) for repo clone
|
||||
CIRRUS_WORKING_DIR: "/var/tmp/go/src/github.com/containers/storage"
|
||||
# Shell used to execute all script commands
|
||||
CIRRUS_SHELL: "/bin/bash"
|
||||
# Automation script path relative to $CIRRUS_WORKING_DIR)
|
||||
SCRIPT_BASE: "./contrib/cirrus"
|
||||
# No need to go crazy, but grab enough to cover most PRs
|
||||
CIRRUS_CLONE_DEPTH: 50
|
||||
|
||||
####
|
||||
#### Cache-image names to test with (double-quotes around names are critical)
|
||||
###
|
||||
FEDORA_NAME: "fedora-39ß"
|
||||
DEBIAN_NAME: "debian-13"
|
||||
|
||||
# GCE project where images live
|
||||
IMAGE_PROJECT: "libpod-218412"
|
||||
# VM Image built in containers/automation_images
|
||||
IMAGE_SUFFIX: "c20231004t194547z-f39f38d13"
|
||||
FEDORA_CACHE_IMAGE_NAME: "fedora-${IMAGE_SUFFIX}"
|
||||
DEBIAN_CACHE_IMAGE_NAME: "debian-${IMAGE_SUFFIX}"
|
||||
|
||||
####
|
||||
#### Command variables to help avoid duplication
|
||||
####
|
||||
# Command to prefix every output line with a timestamp
|
||||
# (can't do inline awk script, Cirrus-CI or YAML mangles quoting)
|
||||
_TIMESTAMP: 'awk --file ${CIRRUS_WORKING_DIR}/${SCRIPT_BASE}/timestamp.awk'
|
||||
_DFCMD: 'df -lhTx tmpfs'
|
||||
_RAUDITCMD: 'cat /var/log/audit/audit.log'
|
||||
_UAUDITCMD: 'cat /var/log/kern.log'
|
||||
_JOURNALCMD: 'journalctl -b'
|
||||
|
||||
gcp_credentials: ENCRYPTED[c87717f04fb15499d19a3b3fa0ad2cdedecc047e82967785d101e9bc418e93219f755e662feac8390088a2df1a4d8464]
|
||||
|
||||
# Default timeout for each task
|
||||
timeout_in: 120m
|
||||
|
||||
# Default VM to use unless set or modified by task
|
||||
gce_instance:
|
||||
image_project: "${IMAGE_PROJECT}"
|
||||
zone: "us-central1-b" # Required by Cirrus for the time being
|
||||
cpu: 2
|
||||
memory: "4Gb"
|
||||
disk: 200
|
||||
image_name: "${FEDORA_CACHE_IMAGE_NAME}"
|
||||
|
||||
|
||||
linux_testing: &linux_testing
|
||||
depends_on:
|
||||
- lint
|
||||
gce_instance: # Only need to specify differences from defaults (above)
|
||||
image_name: "${VM_IMAGE}"
|
||||
|
||||
# Separate scripts for separate outputs, makes debugging easier.
|
||||
setup_script: '${CIRRUS_WORKING_DIR}/${SCRIPT_BASE}/setup.sh |& ${_TIMESTAMP}'
|
||||
build_and_test_script: '${CIRRUS_WORKING_DIR}/${SCRIPT_BASE}/build_and_test.sh |& ${_TIMESTAMP}'
|
||||
|
||||
always:
|
||||
df_script: '${_DFCMD} || true'
|
||||
rh_audit_log_script: '${_RAUDITCMD} || true'
|
||||
debian_audit_log_script: '${_UAUDITCMD} || true'
|
||||
journal_log_script: '${_JOURNALCMD} || true'
|
||||
|
||||
|
||||
fedora_testing_task: &fedora_testing
|
||||
<<: *linux_testing
|
||||
alias: fedora_testing
|
||||
name: &std_test_name "${OS_NAME} ${TEST_DRIVER}"
|
||||
env:
|
||||
OS_NAME: "${FEDORA_NAME}"
|
||||
VM_IMAGE: "${FEDORA_CACHE_IMAGE_NAME}"
|
||||
# Not all $TEST_DRIVER combinations valid for all $VM_IMAGE types.
|
||||
matrix: &test_matrix
|
||||
- env:
|
||||
TEST_DRIVER: "vfs"
|
||||
- env:
|
||||
TEST_DRIVER: "overlay"
|
||||
- env:
|
||||
TEST_DRIVER: "overlay-transient"
|
||||
- env:
|
||||
TEST_DRIVER: "fuse-overlay"
|
||||
- env:
|
||||
TEST_DRIVER: "fuse-overlay-whiteout"
|
||||
- env:
|
||||
TEST_DRIVER: "btrfs"
|
||||
|
||||
|
||||
# aufs was dropped between 20.04 and 22.04, can't test it
|
||||
debian_testing_task: &debian_testing
|
||||
<<: *linux_testing
|
||||
alias: debian_testing
|
||||
name: *std_test_name
|
||||
env:
|
||||
OS_NAME: "${DEBIAN_NAME}"
|
||||
VM_IMAGE: "${DEBIAN_CACHE_IMAGE_NAME}"
|
||||
# Not all $TEST_DRIVER combinations valid for all $VM_IMAGE types.
|
||||
matrix:
|
||||
- env:
|
||||
TEST_DRIVER: "vfs"
|
||||
- env:
|
||||
TEST_DRIVER: "overlay"
|
||||
- env:
|
||||
TEST_DRIVER: "fuse-overlay"
|
||||
- env:
|
||||
TEST_DRIVER: "fuse-overlay-whiteout"
|
||||
- env:
|
||||
TEST_DRIVER: "btrfs"
|
||||
|
||||
|
||||
lint_task:
|
||||
env:
|
||||
CIRRUS_WORKING_DIR: "/go/src/github.com/containers/storage"
|
||||
container:
|
||||
image: golang
|
||||
modules_cache:
|
||||
fingerprint_script: cat go.sum
|
||||
folder: $GOPATH/pkg/mod
|
||||
build_script: |
|
||||
apt-get update
|
||||
apt-get install -y libbtrfs-dev libdevmapper-dev
|
||||
test_script: |
|
||||
make TAGS=regex_precompile local-validate
|
||||
make lint
|
||||
make clean
|
||||
|
||||
|
||||
# Update metadata on VM images referenced by this repository state
|
||||
meta_task:
|
||||
|
||||
container:
|
||||
image: "quay.io/libpod/imgts:latest"
|
||||
cpu: 1
|
||||
memory: 1
|
||||
|
||||
env:
|
||||
# Space-separated list of images used by this repository state
|
||||
IMGNAMES: |-
|
||||
${FEDORA_CACHE_IMAGE_NAME}
|
||||
${DEBIAN_CACHE_IMAGE_NAME}
|
||||
BUILDID: "${CIRRUS_BUILD_ID}"
|
||||
REPOREF: "${CIRRUS_CHANGE_IN_REPO}"
|
||||
GCPJSON: ENCRYPTED[244a93fe8b386b48b96f748342bf741350e43805eee81dd04b45093bdf737e540b993fc735df41f131835fa0f9b65826]
|
||||
GCPNAME: ENCRYPTED[91cf7aa421858b26b67835978d224b4a5c46afcf52a0f1ec1b69a99b248715dc8e92a1b56fde18e092acf256fa80ae9c]
|
||||
GCPPROJECT: ENCRYPTED[79b0f7eb5958e25bc7095d5d368fa8d94447a43ffacb9c693de438186e2f767b7efe9563d6954297ae4730220e10aa9c]
|
||||
CIRRUS_CLONE_DEPTH: 1 # source not used
|
||||
|
||||
script: '/usr/local/bin/entrypoint.sh |& ${_TIMESTAMP}'
|
||||
|
||||
|
||||
vendor_task:
|
||||
container:
|
||||
image: golang
|
||||
modules_cache:
|
||||
fingerprint_script: cat go.sum
|
||||
folder: $GOPATH/pkg/mod
|
||||
build_script: make vendor
|
||||
test_script: hack/tree_status.sh
|
||||
|
||||
|
||||
cross_task:
|
||||
container:
|
||||
image: golang:1.19
|
||||
build_script: make cross
|
||||
|
||||
|
||||
# Represent overall pass/fail status from required dependent tasks
|
||||
success_task:
|
||||
depends_on:
|
||||
- lint
|
||||
- fedora_testing
|
||||
- debian_testing
|
||||
- meta
|
||||
- vendor
|
||||
- cross
|
||||
container:
|
||||
image: golang:1.19
|
||||
clone_script: 'mkdir -p "$CIRRUS_WORKING_DIR"' # Source code not needed
|
||||
script: /bin/true
|
||||
3
vendor/github.com/containers/storage/.dockerignore
generated
vendored
Normal file
3
vendor/github.com/containers/storage/.dockerignore
generated
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
bundles
|
||||
.gopath
|
||||
vendor/pkg
|
||||
32
vendor/github.com/containers/storage/.gitignore
generated
vendored
Normal file
32
vendor/github.com/containers/storage/.gitignore
generated
vendored
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
# containers/storage project generated files to ignore
|
||||
# if you want to ignore files created by your editor/tools,
|
||||
# please consider a global .gitignore https://help.github.com/articles/ignoring-files
|
||||
*.1
|
||||
*.5
|
||||
*.exe
|
||||
*~
|
||||
*.orig
|
||||
*.test
|
||||
.*.swp
|
||||
.DS_Store
|
||||
.idea*
|
||||
# a .bashrc may be added to customize the build environment
|
||||
.bashrc
|
||||
.gopath/
|
||||
docs/AWS_S3_BUCKET
|
||||
docs/GITCOMMIT
|
||||
docs/GIT_BRANCH
|
||||
docs/VERSION
|
||||
docs/_build
|
||||
docs/_static
|
||||
docs/_templates
|
||||
docs/changed-files
|
||||
# generated by man/md2man-all.sh
|
||||
man/man1
|
||||
man/man5
|
||||
man/man8
|
||||
tests/tools/build
|
||||
vendor/pkg/
|
||||
.vagrant
|
||||
/containers-storage
|
||||
/containers-storage.*
|
||||
11
vendor/github.com/containers/storage/.golangci.yml
generated
vendored
Normal file
11
vendor/github.com/containers/storage/.golangci.yml
generated
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
---
|
||||
run:
|
||||
concurrency: 6
|
||||
deadline: 5m
|
||||
skip-dirs-use-default: true
|
||||
linters:
|
||||
enable:
|
||||
- gofumpt
|
||||
disable:
|
||||
- errcheck
|
||||
- staticcheck
|
||||
254
vendor/github.com/containers/storage/.mailmap
generated
vendored
Normal file
254
vendor/github.com/containers/storage/.mailmap
generated
vendored
Normal file
|
|
@ -0,0 +1,254 @@
|
|||
# Generate AUTHORS: hack/generate-authors.sh
|
||||
|
||||
# Tip for finding duplicates (besides scanning the output of AUTHORS for name
|
||||
# duplicates that aren't also email duplicates): scan the output of:
|
||||
# git log --format='%aE - %aN' | sort -uf
|
||||
#
|
||||
# For explanation on this file format: man git-shortlog
|
||||
|
||||
Patrick Stapleton <github@gdi2290.com>
|
||||
Shishir Mahajan <shishir.mahajan@redhat.com> <smahajan@redhat.com>
|
||||
Erwin van der Koogh <info@erronis.nl>
|
||||
Ahmed Kamal <email.ahmedkamal@googlemail.com>
|
||||
Tejesh Mehta <tejesh.mehta@gmail.com> <tj@init.me>
|
||||
Cristian Staretu <cristian.staretu@gmail.com>
|
||||
Cristian Staretu <cristian.staretu@gmail.com> <unclejacksons@gmail.com>
|
||||
Cristian Staretu <cristian.staretu@gmail.com> <unclejack@users.noreply.github.com>
|
||||
Marcus Linke <marcus.linke@gmx.de>
|
||||
Aleksandrs Fadins <aleks@s-ko.net>
|
||||
Christopher Latham <sudosurootdev@gmail.com>
|
||||
Hu Keping <hukeping@huawei.com>
|
||||
Wayne Chang <wayne@neverfear.org>
|
||||
Chen Chao <cc272309126@gmail.com>
|
||||
Daehyeok Mun <daehyeok@gmail.com>
|
||||
<daehyeok@gmail.com> <daehyeok@daehyeokui-MacBook-Air.local>
|
||||
<jt@yadutaf.fr> <admin@jtlebi.fr>
|
||||
<jeff@docker.com> <jefferya@programmerq.net>
|
||||
<charles.hooper@dotcloud.com> <chooper@plumata.com>
|
||||
<daniel.mizyrycki@dotcloud.com> <daniel@dotcloud.com>
|
||||
<daniel.mizyrycki@dotcloud.com> <mzdaniel@glidelink.net>
|
||||
Guillaume J. Charmes <guillaume.charmes@docker.com> <charmes.guillaume@gmail.com>
|
||||
<guillaume.charmes@docker.com> <guillaume@dotcloud.com>
|
||||
<guillaume.charmes@docker.com> <guillaume@docker.com>
|
||||
<guillaume.charmes@docker.com> <guillaume.charmes@dotcloud.com>
|
||||
<guillaume.charmes@docker.com> <guillaume@charmes.net>
|
||||
<kencochrane@gmail.com> <KenCochrane@gmail.com>
|
||||
Thatcher Peskens <thatcher@docker.com>
|
||||
Thatcher Peskens <thatcher@docker.com> <thatcher@dotcloud.com>
|
||||
Thatcher Peskens <thatcher@docker.com> dhrp <thatcher@gmx.net>
|
||||
Jérôme Petazzoni <jerome.petazzoni@dotcloud.com> jpetazzo <jerome.petazzoni@dotcloud.com>
|
||||
Jérôme Petazzoni <jerome.petazzoni@dotcloud.com> <jp@enix.org>
|
||||
Joffrey F <joffrey@docker.com>
|
||||
Joffrey F <joffrey@docker.com> <joffrey@dotcloud.com>
|
||||
Joffrey F <joffrey@docker.com> <f.joffrey@gmail.com>
|
||||
Tim Terhorst <mynamewastaken+git@gmail.com>
|
||||
Andy Smith <github@anarkystic.com>
|
||||
<kalessin@kalessin.fr> <louis@dotcloud.com>
|
||||
<victor.vieux@docker.com> <victor.vieux@dotcloud.com>
|
||||
<victor.vieux@docker.com> <victor@dotcloud.com>
|
||||
<victor.vieux@docker.com> <dev@vvieux.com>
|
||||
<victor.vieux@docker.com> <victor@docker.com>
|
||||
<victor.vieux@docker.com> <vieux@docker.com>
|
||||
<victor.vieux@docker.com> <victorvieux@gmail.com>
|
||||
<dominik@honnef.co> <dominikh@fork-bomb.org>
|
||||
<ehanchrow@ine.com> <eric.hanchrow@gmail.com>
|
||||
Walter Stanish <walter@pratyeka.org>
|
||||
<daniel@gasienica.ch> <dgasienica@zynga.com>
|
||||
Roberto Hashioka <roberto_hashioka@hotmail.com>
|
||||
Konstantin Pelykh <kpelykh@zettaset.com>
|
||||
David Sissitka <me@dsissitka.com>
|
||||
Nolan Darilek <nolan@thewordnerd.info>
|
||||
<mastahyeti@gmail.com> <mastahyeti@users.noreply.github.com>
|
||||
Benoit Chesneau <bchesneau@gmail.com>
|
||||
Jordan Arentsen <blissdev@gmail.com>
|
||||
Daniel Garcia <daniel@danielgarcia.info>
|
||||
Miguel Angel Fernández <elmendalerenda@gmail.com>
|
||||
Bhiraj Butala <abhiraj.butala@gmail.com>
|
||||
Faiz Khan <faizkhan00@gmail.com>
|
||||
Victor Lyuboslavsky <victor@victoreda.com>
|
||||
Jean-Baptiste Barth <jeanbaptiste.barth@gmail.com>
|
||||
Matthew Mueller <mattmuelle@gmail.com>
|
||||
<mosoni@ebay.com> <mohitsoni1989@gmail.com>
|
||||
Shih-Yuan Lee <fourdollars@gmail.com>
|
||||
Daniel Mizyrycki <daniel.mizyrycki@dotcloud.com> root <root@vagrant-ubuntu-12.10.vagrantup.com>
|
||||
Jean-Baptiste Dalido <jeanbaptiste@appgratis.com>
|
||||
<proppy@google.com> <proppy@aminche.com>
|
||||
<michael@docker.com> <michael@crosbymichael.com>
|
||||
<michael@docker.com> <crosby.michael@gmail.com>
|
||||
<michael@docker.com> <crosbymichael@gmail.com>
|
||||
<github@developersupport.net> <github@metaliveblog.com>
|
||||
<brandon@ifup.org> <brandon@ifup.co>
|
||||
<dano@spotify.com> <daniel.norberg@gmail.com>
|
||||
<danny@codeaholics.org> <Danny.Yates@mailonline.co.uk>
|
||||
<gurjeet@singh.im> <singh.gurjeet@gmail.com>
|
||||
<shawn@churchofgit.com> <shawnlandden@gmail.com>
|
||||
<sjoerd-github@linuxonly.nl> <sjoerd@byte.nl>
|
||||
<solomon@docker.com> <solomon.hykes@dotcloud.com>
|
||||
<solomon@docker.com> <solomon@dotcloud.com>
|
||||
<solomon@docker.com> <s@docker.com>
|
||||
Sven Dowideit <SvenDowideit@home.org.au>
|
||||
Sven Dowideit <SvenDowideit@home.org.au> <SvenDowideit@fosiki.com>
|
||||
Sven Dowideit <SvenDowideit@home.org.au> <SvenDowideit@docker.com>
|
||||
Sven Dowideit <SvenDowideit@home.org.au> <¨SvenDowideit@home.org.au¨>
|
||||
Sven Dowideit <SvenDowideit@home.org.au> <SvenDowideit@home.org.au>
|
||||
Sven Dowideit <SvenDowideit@home.org.au> <SvenDowideit@users.noreply.github.com>
|
||||
Sven Dowideit <SvenDowideit@home.org.au> <sven@t440s.home.gateway>
|
||||
<alexl@redhat.com> <alexander.larsson@gmail.com>
|
||||
Alexander Morozov <lk4d4@docker.com> <lk4d4math@gmail.com>
|
||||
Alexander Morozov <lk4d4@docker.com>
|
||||
<git.nivoc@neverbox.com> <kuehnle@online.de>
|
||||
O.S. Tezer <ostezer@gmail.com>
|
||||
<ostezer@gmail.com> <ostezer@users.noreply.github.com>
|
||||
Roberto G. Hashioka <roberto.hashioka@docker.com> <roberto_hashioka@hotmail.com>
|
||||
<justin.p.simonelis@gmail.com> <justin.simonelis@PTS-JSIMON2.toronto.exclamation.com>
|
||||
<taim@bosboot.org> <maztaim@users.noreply.github.com>
|
||||
<viktor.vojnovski@amadeus.com> <vojnovski@gmail.com>
|
||||
<vbatts@redhat.com> <vbatts@hashbangbash.com>
|
||||
<altsysrq@gmail.com> <iamironbob@gmail.com>
|
||||
Sridhar Ratnakumar <sridharr@activestate.com>
|
||||
Sridhar Ratnakumar <sridharr@activestate.com> <github@srid.name>
|
||||
Liang-Chi Hsieh <viirya@gmail.com>
|
||||
Aleksa Sarai <asarai@suse.de>
|
||||
Aleksa Sarai <asarai@suse.de> <asarai@suse.com>
|
||||
Aleksa Sarai <asarai@suse.de> <cyphar@cyphar.com>
|
||||
Will Weaver <monkey@buildingbananas.com>
|
||||
Timothy Hobbs <timothyhobbs@seznam.cz>
|
||||
Nathan LeClaire <nathan.leclaire@docker.com> <nathan.leclaire@gmail.com>
|
||||
Nathan LeClaire <nathan.leclaire@docker.com> <nathanleclaire@gmail.com>
|
||||
<github@hollensbe.org> <erik+github@hollensbe.org>
|
||||
<github@albersweb.de> <albers@users.noreply.github.com>
|
||||
<lsm5@fedoraproject.org> <lsm5@redhat.com>
|
||||
<marc@marc-abramowitz.com> <msabramo@gmail.com>
|
||||
Matthew Heon <mheon@redhat.com> <mheon@mheonlaptop.redhat.com>
|
||||
<bernat@luffy.cx> <vincent@bernat.im>
|
||||
<bernat@luffy.cx> <Vincent.Bernat@exoscale.ch>
|
||||
<p@pwaller.net> <peter@scraperwiki.com>
|
||||
<andrew.weiss@outlook.com> <andrew.weiss@microsoft.com>
|
||||
Francisco Carriedo <fcarriedo@gmail.com>
|
||||
<julienbordellier@gmail.com> <git@julienbordellier.com>
|
||||
<ahmetb@microsoft.com> <ahmetalpbalkan@gmail.com>
|
||||
<arnaud.porterie@docker.com> <icecrime@gmail.com>
|
||||
<baloo@gandi.net> <superbaloo+registrations.github@superbaloo.net>
|
||||
Brian Goff <cpuguy83@gmail.com>
|
||||
<cpuguy83@gmail.com> <bgoff@cpuguy83-mbp.home>
|
||||
<eric@windisch.us> <ewindisch@docker.com>
|
||||
<frank.rosquin+github@gmail.com> <frank.rosquin@gmail.com>
|
||||
Hollie Teal <hollie@docker.com>
|
||||
<hollie@docker.com> <hollie.teal@docker.com>
|
||||
<hollie@docker.com> <hollietealok@users.noreply.github.com>
|
||||
<huu@prismskylabs.com> <whoshuu@gmail.com>
|
||||
Jessica Frazelle <jess@mesosphere.com>
|
||||
Jessica Frazelle <jess@mesosphere.com> <jfrazelle@users.noreply.github.com>
|
||||
Jessica Frazelle <jess@mesosphere.com> <acidburn@docker.com>
|
||||
Jessica Frazelle <jess@mesosphere.com> <jess@docker.com>
|
||||
Jessica Frazelle <jess@mesosphere.com> <princess@docker.com>
|
||||
<konrad.wilhelm.kleine@gmail.com> <kwk@users.noreply.github.com>
|
||||
<tintypemolly@gmail.com> <tintypemolly@Ohui-MacBook-Pro.local>
|
||||
<estesp@linux.vnet.ibm.com> <estesp@gmail.com>
|
||||
<github@gone.nl> <thaJeztah@users.noreply.github.com>
|
||||
Thomas LEVEIL <thomasleveil@gmail.com> Thomas LÉVEIL <thomasleveil@users.noreply.github.com>
|
||||
<oi@truffles.me.uk> <timruffles@googlemail.com>
|
||||
<Vincent.Bernat@exoscale.ch> <bernat@luffy.cx>
|
||||
Antonio Murdaca <antonio.murdaca@gmail.com> <amurdaca@redhat.com>
|
||||
Antonio Murdaca <antonio.murdaca@gmail.com> <runcom@redhat.com>
|
||||
Antonio Murdaca <antonio.murdaca@gmail.com> <me@runcom.ninja>
|
||||
Antonio Murdaca <antonio.murdaca@gmail.com> <runcom@linux.com>
|
||||
Antonio Murdaca <antonio.murdaca@gmail.com> <runcom@users.noreply.github.com>
|
||||
Darren Shepherd <darren.s.shepherd@gmail.com> <darren@rancher.com>
|
||||
Deshi Xiao <dxiao@redhat.com> <dsxiao@dataman-inc.com>
|
||||
Deshi Xiao <dxiao@redhat.com> <xiaods@gmail.com>
|
||||
Doug Davis <dug@us.ibm.com> <duglin@users.noreply.github.com>
|
||||
Jacob Atzen <jacob@jacobatzen.dk> <jatzen@gmail.com>
|
||||
Jeff Nickoloff <jeff.nickoloff@gmail.com> <jeff@allingeek.com>
|
||||
John Howard (VM) <John.Howard@microsoft.com> <jhowardmsft@users.noreply.github.com>
|
||||
John Howard (VM) <John.Howard@microsoft.com>
|
||||
John Howard (VM) <John.Howard@microsoft.com> <john.howard@microsoft.com>
|
||||
John Howard (VM) <John.Howard@microsoft.com> <jhoward@microsoft.com>
|
||||
Madhu Venugopal <madhu@socketplane.io> <madhu@docker.com>
|
||||
Mary Anthony <mary.anthony@docker.com> <mary@docker.com>
|
||||
Mary Anthony <mary.anthony@docker.com> moxiegirl <mary@docker.com>
|
||||
Mary Anthony <mary.anthony@docker.com> <moxieandmore@gmail.com>
|
||||
mattyw <mattyw@me.com> <gh@mattyw.net>
|
||||
resouer <resouer@163.com> <resouer@gmail.com>
|
||||
AJ Bowen <aj@gandi.net> soulshake <amy@gandi.net>
|
||||
AJ Bowen <aj@gandi.net> soulshake <aj@gandi.net>
|
||||
Tibor Vass <teabee89@gmail.com> <tibor@docker.com>
|
||||
Tibor Vass <teabee89@gmail.com> <tiborvass@users.noreply.github.com>
|
||||
Vincent Bernat <bernat@luffy.cx> <Vincent.Bernat@exoscale.ch>
|
||||
Yestin Sun <sunyi0804@gmail.com> <yestin.sun@polyera.com>
|
||||
bin liu <liubin0329@users.noreply.github.com> <liubin0329@gmail.com>
|
||||
John Howard (VM) <John.Howard@microsoft.com> jhowardmsft <jhoward@microsoft.com>
|
||||
Ankush Agarwal <ankushagarwal11@gmail.com> <ankushagarwal@users.noreply.github.com>
|
||||
Tangi COLIN <tangicolin@gmail.com> tangicolin <tangicolin@gmail.com>
|
||||
Allen Sun <allen.sun@daocloud.io>
|
||||
Adrien Gallouët <adrien@gallouet.fr> <angt@users.noreply.github.com>
|
||||
<aanm90@gmail.com> <martins@noironetworks.com>
|
||||
Anuj Bahuguna <anujbahuguna.dev@gmail.com>
|
||||
Anusha Ragunathan <anusha.ragunathan@docker.com> <anusha@docker.com>
|
||||
Avi Miller <avi.miller@oracle.com> <avi.miller@gmail.com>
|
||||
Brent Salisbury <brent.salisbury@docker.com> <brent@docker.com>
|
||||
Chander G <chandergovind@gmail.com>
|
||||
Chun Chen <ramichen@tencent.com> <chenchun.feed@gmail.com>
|
||||
Ying Li <cyli@twistedmatrix.com>
|
||||
Daehyeok Mun <daehyeok@gmail.com> <daehyeok@daehyeok-ui-MacBook-Air.local>
|
||||
<dqminh@cloudflare.com> <dqminh89@gmail.com>
|
||||
Daniel, Dao Quang Minh <dqminh@cloudflare.com>
|
||||
Daniel Nephin <dnephin@docker.com> <dnephin@gmail.com>
|
||||
Dave Tucker <dt@docker.com> <dave@dtucker.co.uk>
|
||||
Doug Tangren <d.tangren@gmail.com>
|
||||
Frederick F. Kautz IV <fkautz@redhat.com> <fkautz@alumni.cmu.edu>
|
||||
Ben Golub <ben.golub@dotcloud.com>
|
||||
Harold Cooper <hrldcpr@gmail.com>
|
||||
hsinko <21551195@zju.edu.cn> <hsinko@users.noreply.github.com>
|
||||
Josh Hawn <josh.hawn@docker.com> <jlhawn@berkeley.edu>
|
||||
Justin Cormack <justin.cormack@docker.com>
|
||||
<justin.cormack@docker.com> <justin.cormack@unikernel.com>
|
||||
<justin.cormack@docker.com> <justin@specialbusservice.com>
|
||||
Kamil Domański <kamil@domanski.co>
|
||||
Lei Jitang <leijitang@huawei.com>
|
||||
<leijitang@huawei.com> <leijitang@gmail.com>
|
||||
Linus Heckemann <lheckemann@twig-world.com>
|
||||
<lheckemann@twig-world.com> <anonymouse2048@gmail.com>
|
||||
Lynda O'Leary <lyndaoleary29@gmail.com>
|
||||
<lyndaoleary29@gmail.com> <lyndaoleary@hotmail.com>
|
||||
Marianna Tessel <mtesselh@gmail.com>
|
||||
Michael Huettermann <michael@huettermann.net>
|
||||
Moysés Borges <moysesb@gmail.com>
|
||||
<moysesb@gmail.com> <moyses.furtado@wplex.com.br>
|
||||
Nigel Poulton <nigelpoulton@hotmail.com>
|
||||
Qiang Huang <h.huangqiang@huawei.com>
|
||||
<h.huangqiang@huawei.com> <qhuang@10.0.2.15>
|
||||
Boaz Shuster <ripcurld.github@gmail.com>
|
||||
Shuwei Hao <haosw@cn.ibm.com>
|
||||
<haosw@cn.ibm.com> <haoshuwei24@gmail.com>
|
||||
Soshi Katsuta <soshi.katsuta@gmail.com>
|
||||
<soshi.katsuta@gmail.com> <katsuta_soshi@cyberagent.co.jp>
|
||||
Stefan Berger <stefanb@linux.vnet.ibm.com>
|
||||
<stefanb@linux.vnet.ibm.com> <stefanb@us.ibm.com>
|
||||
Stephen Day <stephen.day@docker.com>
|
||||
<stephen.day@docker.com> <stevvooe@users.noreply.github.com>
|
||||
Toli Kuznets <toli@docker.com>
|
||||
Tristan Carel <tristan@cogniteev.com>
|
||||
<tristan@cogniteev.com> <tristan.carel@gmail.com>
|
||||
Vincent Demeester <vincent@sbr.pm>
|
||||
<vincent@sbr.pm> <vincent+github@demeester.fr>
|
||||
Vishnu Kannan <vishnuk@google.com>
|
||||
xlgao-zju <xlgao@zju.edu.cn> xlgao <xlgao@zju.edu.cn>
|
||||
yuchangchun <yuchangchun1@huawei.com> y00277921 <yuchangchun1@huawei.com>
|
||||
<zij@case.edu> <zjaffee@us.ibm.com>
|
||||
<anujbahuguna.dev@gmail.com> <abahuguna@fiberlink.com>
|
||||
<eungjun.yi@navercorp.com> <semtlenori@gmail.com>
|
||||
<haosw@cn.ibm.com> <haoshuwei1989@163.com>
|
||||
Hao Shu Wei <haosw@cn.ibm.com>
|
||||
<matt.bentley@docker.com> <mbentley@mbentley.net>
|
||||
<MihaiBorob@gmail.com> <MihaiBorobocea@gmail.com>
|
||||
<redmond.martin@gmail.com> <xgithub@redmond5.com>
|
||||
<redmond.martin@gmail.com> <martin@tinychat.com>
|
||||
<srbrahma@us.ibm.com> <sbrahma@us.ibm.com>
|
||||
<suda.akihiro@lab.ntt.co.jp> <suda.kyoto@gmail.com>
|
||||
<thomas@gazagnaire.org> <thomas@gazagnaire.com>
|
||||
Shengbo Song <thomassong@tencent.com> mYmNeo <mymneo@163.com>
|
||||
Shengbo Song <thomassong@tencent.com>
|
||||
<sylvain@ascribe.io> <sylvain.bellemare@ezeep.com>
|
||||
Sylvain Bellemare <sylvain@ascribe.io>
|
||||
|
||||
3
vendor/github.com/containers/storage/CODE-OF-CONDUCT.md
generated
vendored
Normal file
3
vendor/github.com/containers/storage/CODE-OF-CONDUCT.md
generated
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
## The Containers Storage Project Community Code of Conduct
|
||||
|
||||
The Containers Storage project follows the [Containers Community Code of Conduct](https://github.com/containers/common/blob/main/CODE-OF-CONDUCT.md).
|
||||
144
vendor/github.com/containers/storage/CONTRIBUTING.md
generated
vendored
Normal file
144
vendor/github.com/containers/storage/CONTRIBUTING.md
generated
vendored
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
# Contributing to Containers/Storage
|
||||
|
||||
We'd love to have you join the community! Below summarizes the processes
|
||||
that we follow.
|
||||
|
||||
## Topics
|
||||
|
||||
* [Reporting Issues](#reporting-issues)
|
||||
* [Submitting Pull Requests](#submitting-pull-requests)
|
||||
* [Communications](#communications)
|
||||
<!--
|
||||
* [Becoming a Maintainer](#becoming-a-maintainer)
|
||||
-->
|
||||
|
||||
## Reporting Issues
|
||||
|
||||
Before reporting an issue, check our backlog of
|
||||
[open issues](https://github.com/containers/storage/issues)
|
||||
to see if someone else has already reported it. If so, feel free to add
|
||||
your scenario, or additional information, to the discussion. Or simply
|
||||
"subscribe" to it to be notified when it is updated.
|
||||
|
||||
If you find a new issue with the project we'd love to hear about it! The most
|
||||
important aspect of a bug report is that it includes enough information for
|
||||
us to reproduce it. So, please include as much detail as possible and try
|
||||
to remove the extra stuff that doesn't really relate to the issue itself.
|
||||
The easier it is for us to reproduce it, the faster it'll be fixed!
|
||||
|
||||
Please don't include any private/sensitive information in your issue!
|
||||
|
||||
## Submitting Pull Requests
|
||||
|
||||
No Pull Request (PR) is too small! Typos, additional comments in the code,
|
||||
new testcases, bug fixes, new features, more documentation, ... it's all
|
||||
welcome!
|
||||
|
||||
While bug fixes can first be identified via an "issue", that is not required.
|
||||
It's ok to just open up a PR with the fix, but make sure you include the same
|
||||
information you would have included in an issue - like how to reproduce it.
|
||||
|
||||
PRs for new features should include some background on what use cases the
|
||||
new code is trying to address. When possible and when it makes sense, try to break-up
|
||||
larger PRs into smaller ones - it's easier to review smaller
|
||||
code changes. But only if those smaller ones make sense as stand-alone PRs.
|
||||
|
||||
Regardless of the type of PR, all PRs should include:
|
||||
* well documented code changes
|
||||
* additional testcases. Ideally, they should fail w/o your code change applied
|
||||
* documentation changes
|
||||
|
||||
Squash your commits into logical pieces of work that might want to be reviewed
|
||||
separate from the rest of the PRs. But, squashing down to just one commit is ok
|
||||
too since in the end the entire PR will be reviewed anyway. When in doubt,
|
||||
squash.
|
||||
|
||||
PRs that fix issues should include a reference like `Closes #XXXX` in the
|
||||
commit message so that github will automatically close the referenced issue
|
||||
when the PR is merged.
|
||||
|
||||
<!--
|
||||
All PRs require at least two LGTMs (Looks Good To Me) from maintainers.
|
||||
-->
|
||||
|
||||
### Sign your PRs
|
||||
|
||||
The sign-off is a line at the end of the explanation for the patch. Your
|
||||
signature certifies that you wrote the patch or otherwise have the right to pass
|
||||
it on as an open-source patch. The rules are simple: if you can certify
|
||||
the below (from [developercertificate.org](http://developercertificate.org/)):
|
||||
|
||||
```
|
||||
Developer Certificate of Origin
|
||||
Version 1.1
|
||||
|
||||
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
|
||||
660 York Street, Suite 102,
|
||||
San Francisco, CA 94110 USA
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim copies of this
|
||||
license document, but changing it is not allowed.
|
||||
|
||||
Developer's Certificate of Origin 1.1
|
||||
|
||||
By making a contribution to this project, I certify that:
|
||||
|
||||
(a) The contribution was created in whole or in part by me and I
|
||||
have the right to submit it under the open source license
|
||||
indicated in the file; or
|
||||
|
||||
(b) The contribution is based upon previous work that, to the best
|
||||
of my knowledge, is covered under an appropriate open source
|
||||
license and I have the right under that license to submit that
|
||||
work with modifications, whether created in whole or in part
|
||||
by me, under the same open source license (unless I am
|
||||
permitted to submit under a different license), as indicated
|
||||
in the file; or
|
||||
|
||||
(c) The contribution was provided directly to me by some other
|
||||
person who certified (a), (b) or (c) and I have not modified
|
||||
it.
|
||||
|
||||
(d) I understand and agree that this project and the contribution
|
||||
are public and that a record of the contribution (including all
|
||||
personal information I submit with it, including my sign-off) is
|
||||
maintained indefinitely and may be redistributed consistent with
|
||||
this project or the open source license(s) involved.
|
||||
```
|
||||
|
||||
Then you just add a line to every git commit message:
|
||||
|
||||
Signed-off-by: Joe Smith <joe.smith@email.com>
|
||||
|
||||
Use your real name (sorry, no pseudonyms or anonymous contributions.)
|
||||
|
||||
If you set your `user.name` and `user.email` git configs, you can sign your
|
||||
commit automatically with `git commit -s`.
|
||||
|
||||
## Communications
|
||||
|
||||
For general questions, or discussions, please use the
|
||||
IRC group on `irc.freenode.net` called `container-projects`
|
||||
that has been setup.
|
||||
|
||||
For discussions around issues/bugs and features, you can use the github
|
||||
[issues](https://github.com/containers/storage/issues)
|
||||
and
|
||||
[PRs](https://github.com/containers/storage/pulls)
|
||||
tracking system.
|
||||
|
||||
<!--
|
||||
## Becoming a Maintainer
|
||||
|
||||
To become a maintainer you must first be nominated by an existing maintainer.
|
||||
If a majority (>50%) of maintainers agree then the proposal is adopted and
|
||||
you will be added to the list.
|
||||
|
||||
Removing a maintainer requires at least 75% of the remaining maintainers
|
||||
approval, or if the person requests to be removed then it is automatic.
|
||||
Normally, a maintainer will only be removed if they are considered to be
|
||||
inactive for a long period of time or are viewed as disruptive to the community.
|
||||
|
||||
The current list of maintainers can be found in the
|
||||
[MAINTAINERS](MAINTAINERS) file.
|
||||
-->
|
||||
94
vendor/github.com/containers/storage/Makefile
generated
vendored
Normal file
94
vendor/github.com/containers/storage/Makefile
generated
vendored
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
.PHONY: \
|
||||
all \
|
||||
binary \
|
||||
clean \
|
||||
codespell \
|
||||
containers-storage \
|
||||
cross \
|
||||
default \
|
||||
docs \
|
||||
gccgo \
|
||||
help \
|
||||
install \
|
||||
install.docs \
|
||||
install.tools \
|
||||
lint \
|
||||
local-binary \
|
||||
local-cross \
|
||||
local-gccgo \
|
||||
local-test \
|
||||
local-test-integration \
|
||||
local-test-unit \
|
||||
local-validate \
|
||||
test-integration \
|
||||
test-unit \
|
||||
validate \
|
||||
vendor \
|
||||
vendor-in-container
|
||||
|
||||
NATIVETAGS :=
|
||||
AUTOTAGS := $(shell ./hack/btrfs_tag.sh) $(shell ./hack/libdm_tag.sh) $(shell ./hack/libsubid_tag.sh)
|
||||
BUILDFLAGS := -tags "$(AUTOTAGS) $(TAGS)" $(FLAGS)
|
||||
GO ?= go
|
||||
TESTFLAGS := $(shell $(GO) test -race $(BUILDFLAGS) ./pkg/stringutils 2>&1 > /dev/null && echo -race)
|
||||
|
||||
default all: local-binary docs local-validate local-cross ## validate all checks, build and cross-build\nbinaries and docs
|
||||
|
||||
clean: ## remove all built files
|
||||
$(RM) -f containers-storage containers-storage.* docs/*.1 docs/*.5
|
||||
|
||||
containers-storage: ## build using gc on the host
|
||||
$(GO) build -compiler gc $(BUILDFLAGS) ./cmd/containers-storage
|
||||
|
||||
codespell:
|
||||
codespell -S Makefile,build,buildah,buildah.spec,imgtype,copy,AUTHORS,bin,vendor,.git,go.sum,CHANGELOG.md,changelog.txt,seccomp.json,.cirrus.yml,"*.xz,*.gz,*.tar,*.tgz,*ico,*.png,*.1,*.5,*.orig,*.rej" -L worl,flate,uint,iff,od,ERRO -w
|
||||
|
||||
binary local-binary: containers-storage
|
||||
|
||||
local-gccgo gccgo: ## build using gccgo on the host
|
||||
GCCGO=$(PWD)/hack/gccgo-wrapper.sh $(GO) build -compiler gccgo $(BUILDFLAGS) -o containers-storage.gccgo ./cmd/containers-storage
|
||||
|
||||
local-cross cross: ## cross build the binaries for arm, darwin, and freebsd
|
||||
@for target in linux/amd64 linux/386 linux/arm linux/arm64 linux/ppc64 linux/ppc64le linux/riscv64 linux/s390x linux/mips linux/mipsle linux/mips64 linux/mips64le darwin/amd64 windows/amd64 freebsd/amd64 freebsd/arm64 ; do \
|
||||
os=`echo $${target} | cut -f1 -d/` ; \
|
||||
arch=`echo $${target} | cut -f2 -d/` ; \
|
||||
suffix=$${os}.$${arch} ; \
|
||||
echo env CGO_ENABLED=0 GOOS=$${os} GOARCH=$${arch} $(GO) build -compiler gc -tags \"$(NATIVETAGS) $(TAGS)\" $(FLAGS) -o containers-storage.$${suffix} ./cmd/containers-storage ; \
|
||||
env CGO_ENABLED=0 GOOS=$${os} GOARCH=$${arch} $(GO) build -compiler gc -tags "$(NATIVETAGS) $(TAGS)" $(FLAGS) -o containers-storage.$${suffix} ./cmd/containers-storage || exit 1 ; \
|
||||
done
|
||||
|
||||
docs: install.tools ## build the docs on the host
|
||||
$(MAKE) -C docs docs
|
||||
|
||||
local-test: local-binary local-test-unit local-test-integration ## build the binaries and run the tests
|
||||
|
||||
local-test-unit test-unit: local-binary ## run the unit tests on the host (requires\nsuperuser privileges)
|
||||
@$(GO) test -count 1 $(BUILDFLAGS) $(TESTFLAGS) ./...
|
||||
|
||||
local-test-integration test-integration: local-binary ## run the integration tests on the host (requires\nsuperuser privileges)
|
||||
@cd tests; ./test_runner.bash
|
||||
|
||||
local-validate validate: install.tools ## validate DCO on the host
|
||||
@./hack/git-validation.sh
|
||||
|
||||
install.tools:
|
||||
$(MAKE) -C tests/tools
|
||||
|
||||
install.docs: docs
|
||||
$(MAKE) -C docs install
|
||||
|
||||
install: install.docs
|
||||
|
||||
lint: install.tools
|
||||
tests/tools/build/golangci-lint run --build-tags="$(AUTOTAGS) $(TAGS)"
|
||||
|
||||
help: ## this help
|
||||
@awk 'BEGIN {FS = ":.*?## "} /^[a-z A-Z_-]+:.*?## / {gsub(" ",",",$$1);gsub("\\\\n",sprintf("\n%22c"," "), $$2);printf "\033[36m%-21s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)
|
||||
|
||||
vendor-in-container:
|
||||
podman run --privileged --rm --env HOME=/root -v `pwd`:/src -w /src golang make vendor
|
||||
|
||||
vendor:
|
||||
$(GO) mod tidy
|
||||
$(GO) mod vendor
|
||||
$(GO) mod verify
|
||||
32
vendor/github.com/containers/storage/OWNERS
generated
vendored
Normal file
32
vendor/github.com/containers/storage/OWNERS
generated
vendored
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
approvers:
|
||||
- Luap99
|
||||
- TomSweeneyRedHat
|
||||
- cevich
|
||||
- edsantiago
|
||||
- flouthoc
|
||||
- giuseppe
|
||||
- haircommander
|
||||
- kolyshkin
|
||||
- mrunalp
|
||||
- mtrmac
|
||||
- nalind
|
||||
- rhatdan
|
||||
- saschagrunert
|
||||
- umohnani8
|
||||
- vrothberg
|
||||
reviewers:
|
||||
- Luap99
|
||||
- TomSweeneyRedHat
|
||||
- cevich
|
||||
- edsantiago
|
||||
- flouthoc
|
||||
- giuseppe
|
||||
- haircommander
|
||||
- kolyshkin
|
||||
- mrunalp
|
||||
- mtrmac
|
||||
- nalind
|
||||
- rhatdan
|
||||
- saschagrunert
|
||||
- umohnani8
|
||||
- vrothberg
|
||||
46
vendor/github.com/containers/storage/README.md
generated
vendored
Normal file
46
vendor/github.com/containers/storage/README.md
generated
vendored
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
`storage` is a Go library which aims to provide methods for storing filesystem
|
||||
layers, container images, and containers. A `containers-storage` CLI wrapper
|
||||
is also included for manual and scripting use.
|
||||
|
||||
To build the CLI wrapper, use 'make binary'.
|
||||
|
||||
Operations which use VMs expect to launch them using 'vagrant', defaulting to
|
||||
using its 'libvirt' provider. The boxes used are also available for the
|
||||
'virtualbox' provider, and can be selected by setting $VAGRANT_PROVIDER to
|
||||
'virtualbox' before kicking off the build.
|
||||
|
||||
The library manages three types of items: layers, images, and containers.
|
||||
|
||||
A *layer* is a copy-on-write filesystem which is notionally stored as a set of
|
||||
changes relative to its *parent* layer, if it has one. A given layer can only
|
||||
have one parent, but any layer can be the parent of multiple layers. Layers
|
||||
which are parents of other layers should be treated as read-only.
|
||||
|
||||
An *image* is a reference to a particular layer (its _top_ layer), along with
|
||||
other information which the library can manage for the convenience of its
|
||||
caller. This information typically includes configuration templates for
|
||||
running a binary contained within the image's layers, and may include
|
||||
cryptographic signatures. Multiple images can reference the same layer, as the
|
||||
differences between two images may not be in their layer contents.
|
||||
|
||||
A *container* is a read-write layer which is a child of an image's top layer,
|
||||
along with information which the library can manage for the convenience of its
|
||||
caller. This information typically includes configuration information for
|
||||
running the specific container. Multiple containers can be derived from a
|
||||
single image.
|
||||
|
||||
Layers, images, and containers are represented primarily by 32 character
|
||||
hexadecimal IDs, but items of each kind can also have one or more arbitrary
|
||||
names attached to them, which the library will automatically resolve to IDs
|
||||
when they are passed in to API calls which expect IDs.
|
||||
|
||||
The library can store what it calls *metadata* for each of these types of
|
||||
items. This is expected to be a small piece of data, since it is cached in
|
||||
memory and stored along with the library's own bookkeeping information.
|
||||
|
||||
Additionally, the library can store one or more of what it calls *big data* for
|
||||
images and containers. This is a named chunk of larger data, which is only in
|
||||
memory when it is being read from or being written to its own disk file.
|
||||
|
||||
**[Contributing](CONTRIBUTING.md)**
|
||||
Information about contributing to this project.
|
||||
3
vendor/github.com/containers/storage/SECURITY.md
generated
vendored
Normal file
3
vendor/github.com/containers/storage/SECURITY.md
generated
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
## Security and Disclosure Information Policy for the Containers Storage Project
|
||||
|
||||
The Containers Storage Project follows the [Security and Disclosure Information Policy](https://github.com/containers/common/blob/main/SECURITY.md) for the Containers Projects.
|
||||
1
vendor/github.com/containers/storage/VERSION
generated
vendored
Normal file
1
vendor/github.com/containers/storage/VERSION
generated
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
1.51.0
|
||||
1153
vendor/github.com/containers/storage/check.go
generated
vendored
Normal file
1153
vendor/github.com/containers/storage/check.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
981
vendor/github.com/containers/storage/containers.go
generated
vendored
Normal file
981
vendor/github.com/containers/storage/containers.go
generated
vendored
Normal file
|
|
@ -0,0 +1,981 @@
|
|||
package storage
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/containers/storage/pkg/idtools"
|
||||
"github.com/containers/storage/pkg/ioutils"
|
||||
"github.com/containers/storage/pkg/lockfile"
|
||||
"github.com/containers/storage/pkg/stringid"
|
||||
"github.com/containers/storage/pkg/truncindex"
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type containerLocations uint8
|
||||
|
||||
// The backing store is split in two json files, one (the volatile)
|
||||
// that is written without fsync() meaning it isn't as robust to
|
||||
// unclean shutdown
|
||||
const (
|
||||
stableContainerLocation containerLocations = 1 << iota
|
||||
volatileContainerLocation
|
||||
|
||||
numContainerLocationIndex = iota
|
||||
)
|
||||
|
||||
func containerLocationFromIndex(index int) containerLocations {
|
||||
return 1 << index
|
||||
}
|
||||
|
||||
// A Container is a reference to a read-write layer with metadata.
|
||||
type Container struct {
|
||||
// ID is either one which was specified at create-time, or a random
|
||||
// value which was generated by the library.
|
||||
ID string `json:"id"`
|
||||
|
||||
// Names is an optional set of user-defined convenience values. The
|
||||
// container can be referred to by its ID or any of its names. Names
|
||||
// are unique among containers.
|
||||
Names []string `json:"names,omitempty"`
|
||||
|
||||
// ImageID is the ID of the image which was used to create the container.
|
||||
ImageID string `json:"image"`
|
||||
|
||||
// LayerID is the ID of the read-write layer for the container itself.
|
||||
// It is assumed that the image's top layer is the parent of the container's
|
||||
// read-write layer.
|
||||
LayerID string `json:"layer"`
|
||||
|
||||
// Metadata is data we keep for the convenience of the caller. It is not
|
||||
// expected to be large, since it is kept in memory.
|
||||
Metadata string `json:"metadata,omitempty"`
|
||||
|
||||
// BigDataNames is a list of names of data items that we keep for the
|
||||
// convenience of the caller. They can be large, and are only in
|
||||
// memory when being read from or written to disk.
|
||||
BigDataNames []string `json:"big-data-names,omitempty"`
|
||||
|
||||
// BigDataSizes maps the names in BigDataNames to the sizes of the data
|
||||
// that has been stored, if they're known.
|
||||
BigDataSizes map[string]int64 `json:"big-data-sizes,omitempty"`
|
||||
|
||||
// BigDataDigests maps the names in BigDataNames to the digests of the
|
||||
// data that has been stored, if they're known.
|
||||
BigDataDigests map[string]digest.Digest `json:"big-data-digests,omitempty"`
|
||||
|
||||
// Created is the datestamp for when this container was created. Older
|
||||
// versions of the library did not track this information, so callers
|
||||
// will likely want to use the IsZero() method to verify that a value
|
||||
// is set before using it.
|
||||
Created time.Time `json:"created,omitempty"`
|
||||
|
||||
// UIDMap and GIDMap are used for setting up a container's root
|
||||
// filesystem for use inside of a user namespace where UID mapping is
|
||||
// being used.
|
||||
UIDMap []idtools.IDMap `json:"uidmap,omitempty"`
|
||||
GIDMap []idtools.IDMap `json:"gidmap,omitempty"`
|
||||
|
||||
Flags map[string]interface{} `json:"flags,omitempty"`
|
||||
|
||||
// volatileStore is true if the container is from the volatile json file
|
||||
volatileStore bool `json:"-"`
|
||||
}
|
||||
|
||||
// rwContainerStore provides bookkeeping for information about Containers.
|
||||
type rwContainerStore interface {
|
||||
metadataStore
|
||||
containerBigDataStore
|
||||
flaggableStore
|
||||
|
||||
// startWriting makes sure the store is fresh, and locks it for writing.
|
||||
// If this succeeds, the caller MUST call stopWriting().
|
||||
startWriting() error
|
||||
|
||||
// stopWriting releases locks obtained by startWriting.
|
||||
stopWriting()
|
||||
|
||||
// startReading makes sure the store is fresh, and locks it for reading.
|
||||
// If this succeeds, the caller MUST call stopReading().
|
||||
startReading() error
|
||||
|
||||
// stopReading releases locks obtained by startReading.
|
||||
stopReading()
|
||||
|
||||
// create creates a container that has a specified ID (or generates a
|
||||
// random one if an empty value is supplied) and optional names,
|
||||
// based on the specified image, using the specified layer as its
|
||||
// read-write layer.
|
||||
// The maps in the container's options structure are recorded for the
|
||||
// convenience of the caller, nothing more.
|
||||
create(id string, names []string, image, layer string, options *ContainerOptions) (*Container, error)
|
||||
|
||||
// updateNames modifies names associated with a container based on (op, names).
|
||||
updateNames(id string, names []string, op updateNameOperation) error
|
||||
|
||||
// Get retrieves information about a container given an ID or name.
|
||||
Get(id string) (*Container, error)
|
||||
|
||||
// Exists checks if there is a container with the given ID or name.
|
||||
Exists(id string) bool
|
||||
|
||||
// Delete removes the record of the container.
|
||||
Delete(id string) error
|
||||
|
||||
// Wipe removes records of all containers.
|
||||
Wipe() error
|
||||
|
||||
// Lookup attempts to translate a name to an ID. Most methods do this
|
||||
// implicitly.
|
||||
Lookup(name string) (string, error)
|
||||
|
||||
// Containers returns a slice enumerating the known containers.
|
||||
Containers() ([]Container, error)
|
||||
|
||||
// Clean up unreferenced datadirs
|
||||
GarbageCollect() error
|
||||
}
|
||||
|
||||
type containerStore struct {
|
||||
// The following fields are only set when constructing containerStore, and must never be modified afterwards.
|
||||
// They are safe to access without any other locking.
|
||||
lockfile *lockfile.LockFile // Synchronizes readers vs. writers of the _filesystem data_, both cross-process and in-process.
|
||||
dir string
|
||||
jsonPath [numContainerLocationIndex]string
|
||||
|
||||
inProcessLock sync.RWMutex // Can _only_ be obtained with lockfile held.
|
||||
// The following fields can only be read/written with read/write ownership of inProcessLock, respectively.
|
||||
// Almost all users should use startReading() or startWriting().
|
||||
lastWrite lockfile.LastWrite
|
||||
containers []*Container
|
||||
idindex *truncindex.TruncIndex
|
||||
byid map[string]*Container
|
||||
bylayer map[string]*Container
|
||||
byname map[string]*Container
|
||||
}
|
||||
|
||||
func copyContainer(c *Container) *Container {
|
||||
return &Container{
|
||||
ID: c.ID,
|
||||
Names: copyStringSlice(c.Names),
|
||||
ImageID: c.ImageID,
|
||||
LayerID: c.LayerID,
|
||||
Metadata: c.Metadata,
|
||||
BigDataNames: copyStringSlice(c.BigDataNames),
|
||||
BigDataSizes: copyStringInt64Map(c.BigDataSizes),
|
||||
BigDataDigests: copyStringDigestMap(c.BigDataDigests),
|
||||
Created: c.Created,
|
||||
UIDMap: copyIDMap(c.UIDMap),
|
||||
GIDMap: copyIDMap(c.GIDMap),
|
||||
Flags: copyStringInterfaceMap(c.Flags),
|
||||
volatileStore: c.volatileStore,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Container) MountLabel() string {
|
||||
if label, ok := c.Flags[mountLabelFlag].(string); ok {
|
||||
return label
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (c *Container) ProcessLabel() string {
|
||||
if label, ok := c.Flags[processLabelFlag].(string); ok {
|
||||
return label
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (c *Container) MountOpts() []string {
|
||||
switch value := c.Flags[mountOptsFlag].(type) {
|
||||
case []string:
|
||||
return value
|
||||
case []interface{}:
|
||||
var mountOpts []string
|
||||
for _, v := range value {
|
||||
if flag, ok := v.(string); ok {
|
||||
mountOpts = append(mountOpts, flag)
|
||||
}
|
||||
}
|
||||
return mountOpts
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// The caller must hold r.inProcessLock for reading.
|
||||
func containerLocation(c *Container) containerLocations {
|
||||
if c.volatileStore {
|
||||
return volatileContainerLocation
|
||||
}
|
||||
return stableContainerLocation
|
||||
}
|
||||
|
||||
// startWritingWithReload makes sure the store is fresh if canReload, and locks it for writing.
|
||||
// If this succeeds, the caller MUST call stopWriting().
|
||||
//
|
||||
// This is an internal implementation detail of containerStore construction, every other caller
|
||||
// should use startWriting() instead.
|
||||
func (r *containerStore) startWritingWithReload(canReload bool) error {
|
||||
r.lockfile.Lock()
|
||||
r.inProcessLock.Lock()
|
||||
succeeded := false
|
||||
defer func() {
|
||||
if !succeeded {
|
||||
r.inProcessLock.Unlock()
|
||||
r.lockfile.Unlock()
|
||||
}
|
||||
}()
|
||||
|
||||
if canReload {
|
||||
if _, err := r.reloadIfChanged(true); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
succeeded = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// startWriting makes sure the store is fresh, and locks it for writing.
|
||||
// If this succeeds, the caller MUST call stopWriting().
|
||||
func (r *containerStore) startWriting() error {
|
||||
return r.startWritingWithReload(true)
|
||||
}
|
||||
|
||||
// stopWriting releases locks obtained by startWriting.
|
||||
func (r *containerStore) stopWriting() {
|
||||
r.inProcessLock.Unlock()
|
||||
r.lockfile.Unlock()
|
||||
}
|
||||
|
||||
// startReading makes sure the store is fresh, and locks it for reading.
|
||||
// If this succeeds, the caller MUST call stopReading().
|
||||
func (r *containerStore) startReading() error {
|
||||
// inProcessLocked calls the nested function with r.inProcessLock held for writing.
|
||||
inProcessLocked := func(fn func() error) error {
|
||||
r.inProcessLock.Lock()
|
||||
defer r.inProcessLock.Unlock()
|
||||
return fn()
|
||||
}
|
||||
|
||||
r.lockfile.RLock()
|
||||
unlockFn := r.lockfile.Unlock // A function to call to clean up, or nil.
|
||||
defer func() {
|
||||
if unlockFn != nil {
|
||||
unlockFn()
|
||||
}
|
||||
}()
|
||||
r.inProcessLock.RLock()
|
||||
unlockFn = r.stopReading
|
||||
|
||||
// If we are lucky, we can just hold the read locks, check that we are fresh, and continue.
|
||||
_, modified, err := r.modified()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if modified {
|
||||
// We are unlucky, and need to reload.
|
||||
// NOTE: Multiple goroutines can get to this place approximately simultaneously.
|
||||
r.inProcessLock.RUnlock()
|
||||
unlockFn = r.lockfile.Unlock
|
||||
|
||||
// r.lastWrite can change at this point if another goroutine reloads the store before us. That’s why we don’t unconditionally
|
||||
// trigger a load below; we (lock and) reloadIfChanged() again.
|
||||
|
||||
// First try reloading with r.lockfile held for reading.
|
||||
// r.inProcessLock will serialize all goroutines that got here;
|
||||
// each will re-check on-disk state vs. r.lastWrite, and the first one will actually reload the data.
|
||||
var tryLockedForWriting bool
|
||||
if err := inProcessLocked(func() error {
|
||||
// We could optimize this further: The r.lockfile.GetLastWrite() value shouldn’t change as long as we hold r.lockfile,
|
||||
// so if r.lastWrite was already updated, we don’t need to actually read the on-filesystem lock.
|
||||
var err error
|
||||
tryLockedForWriting, err = r.reloadIfChanged(false)
|
||||
return err
|
||||
}); err != nil {
|
||||
if !tryLockedForWriting {
|
||||
return err
|
||||
}
|
||||
// Not good enough, we need r.lockfile held for writing. So, let’s do that.
|
||||
unlockFn()
|
||||
unlockFn = nil
|
||||
|
||||
r.lockfile.Lock()
|
||||
unlockFn = r.lockfile.Unlock
|
||||
if err := inProcessLocked(func() error {
|
||||
_, err := r.reloadIfChanged(true)
|
||||
return err
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
unlockFn()
|
||||
unlockFn = nil
|
||||
|
||||
r.lockfile.RLock()
|
||||
unlockFn = r.lockfile.Unlock
|
||||
// We need to check for a reload once more because the on-disk state could have been modified
|
||||
// after we released the lock.
|
||||
// If that, _again_, finds inconsistent state, just give up.
|
||||
// We could, plausibly, retry a few times, but that inconsistent state (duplicate container names)
|
||||
// shouldn’t be saved (by correct implementations) in the first place.
|
||||
if err := inProcessLocked(func() error {
|
||||
_, err := r.reloadIfChanged(false)
|
||||
return err
|
||||
}); err != nil {
|
||||
return fmt.Errorf("(even after successfully cleaning up once:) %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE that we hold neither a read nor write inProcessLock at this point. That’s fine in ordinary operation, because
|
||||
// the on-filesystem r.lockfile should protect us against (cooperating) writers, and any use of r.inProcessLock
|
||||
// protects us against in-process writers modifying data.
|
||||
// In presence of non-cooperating writers, we just ensure that 1) the in-memory data is not clearly out-of-date
|
||||
// and 2) access to the in-memory data is not racy;
|
||||
// but we can’t protect against those out-of-process writers modifying _files_ while we are assuming they are in a consistent state.
|
||||
|
||||
r.inProcessLock.RLock()
|
||||
}
|
||||
unlockFn = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// stopReading releases locks obtained by startReading.
|
||||
func (r *containerStore) stopReading() {
|
||||
r.inProcessLock.RUnlock()
|
||||
r.lockfile.Unlock()
|
||||
}
|
||||
|
||||
// modified returns true if the on-disk state has changed (i.e. if reloadIfChanged may need to modify the store),
|
||||
// and a lockfile.LastWrite value for that update.
|
||||
//
|
||||
// The caller must hold r.lockfile for reading _or_ writing.
|
||||
// The caller must hold r.inProcessLock for reading or writing.
|
||||
func (r *containerStore) modified() (lockfile.LastWrite, bool, error) {
|
||||
return r.lockfile.ModifiedSince(r.lastWrite)
|
||||
}
|
||||
|
||||
// reloadIfChanged reloads the contents of the store from disk if it is changed.
|
||||
//
|
||||
// The caller must hold r.lockfile for reading _or_ writing; lockedForWriting is true
|
||||
// if it is held for writing.
|
||||
//
|
||||
// The caller must hold r.inProcessLock for WRITING.
|
||||
//
|
||||
// If !lockedForWriting and this function fails, the return value indicates whether
|
||||
// reloadIfChanged() with lockedForWriting could succeed.
|
||||
func (r *containerStore) reloadIfChanged(lockedForWriting bool) (bool, error) {
|
||||
lastWrite, modified, err := r.modified()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
// We require callers to always hold r.inProcessLock for WRITING, even if they might not end up calling r.load()
|
||||
// and modify no fields, to ensure they see fresh data:
|
||||
// r.lockfile.Modified() only returns true once per change. Without an exclusive lock,
|
||||
// one goroutine might see r.lockfile.Modified() == true and decide to load, and in the meanwhile another one could
|
||||
// see r.lockfile.Modified() == false and proceed to use in-memory data without noticing it is stale.
|
||||
if modified {
|
||||
if tryLockedForWriting, err := r.load(lockedForWriting); err != nil {
|
||||
return tryLockedForWriting, err // r.lastWrite is unchanged, so we will load the next time again.
|
||||
}
|
||||
r.lastWrite = lastWrite
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Requires startReading or startWriting.
|
||||
func (r *containerStore) Containers() ([]Container, error) {
|
||||
containers := make([]Container, len(r.containers))
|
||||
for i := range r.containers {
|
||||
containers[i] = *copyContainer(r.containers[i])
|
||||
}
|
||||
return containers, nil
|
||||
}
|
||||
|
||||
// This looks for datadirs in the store directory that are not referenced
|
||||
// by the json file and removes it. These can happen in the case of unclean
|
||||
// shutdowns or regular restarts in transient store mode.
|
||||
// Requires startReading.
|
||||
func (r *containerStore) GarbageCollect() error {
|
||||
entries, err := os.ReadDir(r.dir)
|
||||
if err != nil {
|
||||
// Unexpected, don't try any GC
|
||||
return err
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
id := entry.Name()
|
||||
// Does it look like a datadir directory?
|
||||
if !entry.IsDir() || stringid.ValidateID(id) != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Should the id be there?
|
||||
if r.byid[id] != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Otherwise remove datadir
|
||||
logrus.Debugf("removing %q", filepath.Join(r.dir, id))
|
||||
moreErr := os.RemoveAll(filepath.Join(r.dir, id))
|
||||
// Propagate first error
|
||||
if moreErr != nil && err == nil {
|
||||
err = moreErr
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *containerStore) datadir(id string) string {
|
||||
return filepath.Join(r.dir, id)
|
||||
}
|
||||
|
||||
func (r *containerStore) datapath(id, key string) string {
|
||||
return filepath.Join(r.datadir(id), makeBigDataBaseName(key))
|
||||
}
|
||||
|
||||
// load reloads the contents of the store from disk.
|
||||
//
|
||||
// Most callers should call reloadIfChanged() instead, to avoid overhead and to correctly
|
||||
// manage r.lastWrite.
|
||||
//
|
||||
// The caller must hold r.lockfile for reading _or_ writing; lockedForWriting is true
|
||||
// if it is held for writing.
|
||||
// The caller must hold r.inProcessLock for WRITING.
|
||||
//
|
||||
// If !lockedForWriting and this function fails, the return value indicates whether
|
||||
// retrying with lockedForWriting could succeed.
|
||||
func (r *containerStore) load(lockedForWriting bool) (bool, error) {
|
||||
var modifiedLocations containerLocations
|
||||
containers := []*Container{}
|
||||
|
||||
ids := make(map[string]*Container)
|
||||
|
||||
for locationIndex := 0; locationIndex < numContainerLocationIndex; locationIndex++ {
|
||||
location := containerLocationFromIndex(locationIndex)
|
||||
rpath := r.jsonPath[locationIndex]
|
||||
|
||||
data, err := os.ReadFile(rpath)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return false, err
|
||||
}
|
||||
|
||||
locationContainers := []*Container{}
|
||||
if len(data) != 0 {
|
||||
if err := json.Unmarshal(data, &locationContainers); err != nil {
|
||||
return false, fmt.Errorf("loading %q: %w", rpath, err)
|
||||
}
|
||||
}
|
||||
|
||||
for _, container := range locationContainers {
|
||||
// There should be no duplicated ids between json files, but lets check to be sure
|
||||
if ids[container.ID] != nil {
|
||||
continue // skip invalid duplicated container
|
||||
}
|
||||
// Remember where the container came from
|
||||
if location == volatileContainerLocation {
|
||||
container.volatileStore = true
|
||||
}
|
||||
containers = append(containers, container)
|
||||
ids[container.ID] = container
|
||||
}
|
||||
}
|
||||
|
||||
idlist := make([]string, 0, len(containers))
|
||||
layers := make(map[string]*Container)
|
||||
names := make(map[string]*Container)
|
||||
var errorToResolveBySaving error // == nil
|
||||
for n, container := range containers {
|
||||
idlist = append(idlist, container.ID)
|
||||
layers[container.LayerID] = containers[n]
|
||||
for _, name := range container.Names {
|
||||
if conflict, ok := names[name]; ok {
|
||||
r.removeName(conflict, name)
|
||||
errorToResolveBySaving = errors.New("container store is inconsistent and the current caller does not hold a write lock")
|
||||
modifiedLocations |= containerLocation(container)
|
||||
}
|
||||
names[name] = containers[n]
|
||||
}
|
||||
}
|
||||
|
||||
r.containers = containers
|
||||
r.idindex = truncindex.NewTruncIndex(idlist) // Invalid values in idlist are ignored: they are not a reason to refuse processing the whole store.
|
||||
r.byid = ids
|
||||
r.bylayer = layers
|
||||
r.byname = names
|
||||
if errorToResolveBySaving != nil {
|
||||
if !lockedForWriting {
|
||||
return true, errorToResolveBySaving
|
||||
}
|
||||
return false, r.save(modifiedLocations)
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// save saves the contents of the store to disk.
|
||||
// The caller must hold r.lockfile locked for writing.
|
||||
// The caller must hold r.inProcessLock for reading (but usually holds it for writing in order to make the desired changes).
|
||||
func (r *containerStore) save(saveLocations containerLocations) error {
|
||||
r.lockfile.AssertLockedForWriting()
|
||||
// This must be done before we write the file, because the process could be terminated
|
||||
// after the file is written but before the lock file is updated.
|
||||
lw, err := r.lockfile.RecordWrite()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.lastWrite = lw
|
||||
for locationIndex := 0; locationIndex < numContainerLocationIndex; locationIndex++ {
|
||||
location := containerLocationFromIndex(locationIndex)
|
||||
if location&saveLocations == 0 {
|
||||
continue
|
||||
}
|
||||
rpath := r.jsonPath[locationIndex]
|
||||
if err := os.MkdirAll(filepath.Dir(rpath), 0o700); err != nil {
|
||||
return err
|
||||
}
|
||||
subsetContainers := make([]*Container, 0, len(r.containers))
|
||||
for _, container := range r.containers {
|
||||
if containerLocation(container) == location {
|
||||
subsetContainers = append(subsetContainers, container)
|
||||
}
|
||||
}
|
||||
|
||||
jdata, err := json.Marshal(&subsetContainers)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var opts *ioutils.AtomicFileWriterOptions
|
||||
if location == volatileContainerLocation {
|
||||
opts = &ioutils.AtomicFileWriterOptions{
|
||||
NoSync: true,
|
||||
}
|
||||
}
|
||||
if err := ioutils.AtomicWriteFileWithOpts(rpath, jdata, 0o600, opts); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// saveFor saves the contents of the store relevant for modifiedContainer to disk.
|
||||
// The caller must hold r.lockfile locked for writing.
|
||||
// The caller must hold r.inProcessLock for reading (but usually holds it for writing in order to make the desired changes).
|
||||
func (r *containerStore) saveFor(modifiedContainer *Container) error {
|
||||
return r.save(containerLocation(modifiedContainer))
|
||||
}
|
||||
|
||||
func newContainerStore(dir string, runDir string, transient bool) (rwContainerStore, error) {
|
||||
if err := os.MkdirAll(dir, 0o700); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
volatileDir := dir
|
||||
if transient {
|
||||
if err := os.MkdirAll(runDir, 0o700); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
volatileDir = runDir
|
||||
}
|
||||
lockfile, err := lockfile.GetLockFile(filepath.Join(volatileDir, "containers.lock"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cstore := containerStore{
|
||||
lockfile: lockfile,
|
||||
dir: dir,
|
||||
jsonPath: [numContainerLocationIndex]string{
|
||||
filepath.Join(dir, "containers.json"),
|
||||
filepath.Join(volatileDir, "volatile-containers.json"),
|
||||
},
|
||||
|
||||
containers: []*Container{},
|
||||
byid: make(map[string]*Container),
|
||||
bylayer: make(map[string]*Container),
|
||||
byname: make(map[string]*Container),
|
||||
}
|
||||
|
||||
if err := cstore.startWritingWithReload(false); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cstore.lastWrite, err = cstore.lockfile.GetLastWrite()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer cstore.stopWriting()
|
||||
if _, err := cstore.load(true); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &cstore, nil
|
||||
}
|
||||
|
||||
// Requires startReading or startWriting.
|
||||
func (r *containerStore) lookup(id string) (*Container, bool) {
|
||||
if container, ok := r.byid[id]; ok {
|
||||
return container, ok
|
||||
} else if container, ok := r.byname[id]; ok {
|
||||
return container, ok
|
||||
} else if container, ok := r.bylayer[id]; ok {
|
||||
return container, ok
|
||||
} else if longid, err := r.idindex.Get(id); err == nil {
|
||||
if container, ok := r.byid[longid]; ok {
|
||||
return container, ok
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// Requires startWriting.
|
||||
func (r *containerStore) ClearFlag(id string, flag string) error {
|
||||
container, ok := r.lookup(id)
|
||||
if !ok {
|
||||
return ErrContainerUnknown
|
||||
}
|
||||
delete(container.Flags, flag)
|
||||
return r.saveFor(container)
|
||||
}
|
||||
|
||||
// Requires startWriting.
|
||||
func (r *containerStore) SetFlag(id string, flag string, value interface{}) error {
|
||||
container, ok := r.lookup(id)
|
||||
if !ok {
|
||||
return ErrContainerUnknown
|
||||
}
|
||||
if container.Flags == nil {
|
||||
container.Flags = make(map[string]interface{})
|
||||
}
|
||||
container.Flags[flag] = value
|
||||
return r.saveFor(container)
|
||||
}
|
||||
|
||||
// Requires startWriting.
|
||||
func (r *containerStore) create(id string, names []string, image, layer string, options *ContainerOptions) (container *Container, err error) {
|
||||
if options == nil {
|
||||
options = &ContainerOptions{}
|
||||
}
|
||||
if id == "" {
|
||||
id = stringid.GenerateRandomID()
|
||||
_, idInUse := r.byid[id]
|
||||
for idInUse {
|
||||
id = stringid.GenerateRandomID()
|
||||
_, idInUse = r.byid[id]
|
||||
}
|
||||
}
|
||||
if _, idInUse := r.byid[id]; idInUse {
|
||||
return nil, ErrDuplicateID
|
||||
}
|
||||
names = dedupeStrings(names)
|
||||
for _, name := range names {
|
||||
if _, nameInUse := r.byname[name]; nameInUse {
|
||||
return nil, fmt.Errorf("the container name %q is already in use by %s. You have to remove that container to be able to reuse that name: %w", name, r.byname[name].ID, ErrDuplicateName)
|
||||
}
|
||||
}
|
||||
if err := hasOverlappingRanges(options.UIDMap); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := hasOverlappingRanges(options.GIDMap); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
container = &Container{
|
||||
ID: id,
|
||||
Names: names,
|
||||
ImageID: image,
|
||||
LayerID: layer,
|
||||
Metadata: options.Metadata,
|
||||
BigDataNames: []string{},
|
||||
BigDataSizes: make(map[string]int64),
|
||||
BigDataDigests: make(map[string]digest.Digest),
|
||||
Created: time.Now().UTC(),
|
||||
Flags: copyStringInterfaceMap(options.Flags),
|
||||
UIDMap: copyIDMap(options.UIDMap),
|
||||
GIDMap: copyIDMap(options.GIDMap),
|
||||
volatileStore: options.Volatile,
|
||||
}
|
||||
if options.MountOpts != nil {
|
||||
container.Flags[mountOptsFlag] = append([]string{}, options.MountOpts...)
|
||||
}
|
||||
if options.Volatile {
|
||||
container.Flags[volatileFlag] = true
|
||||
}
|
||||
r.containers = append(r.containers, container)
|
||||
// This can only fail on duplicate IDs, which shouldn’t happen — and in
|
||||
// that case the index is already in the desired state anyway.
|
||||
// Implementing recovery from an unlikely and unimportant failure here
|
||||
// would be too risky.
|
||||
_ = r.idindex.Add(id)
|
||||
r.byid[id] = container
|
||||
r.bylayer[layer] = container
|
||||
for _, name := range names {
|
||||
r.byname[name] = container
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
// now that the in-memory structures know about the new
|
||||
// record, we can use regular Delete() to clean up if
|
||||
// anything breaks from here on out
|
||||
if e := r.Delete(id); e != nil {
|
||||
logrus.Debugf("while cleaning up partially-created container %q we failed to create: %v", id, e)
|
||||
}
|
||||
}
|
||||
}()
|
||||
err = r.saveFor(container)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, item := range options.BigData {
|
||||
if err = r.SetBigData(id, item.Key, item.Data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
container = copyContainer(container)
|
||||
return container, err
|
||||
}
|
||||
|
||||
// Requires startReading or startWriting.
|
||||
func (r *containerStore) Metadata(id string) (string, error) {
|
||||
if container, ok := r.lookup(id); ok {
|
||||
return container.Metadata, nil
|
||||
}
|
||||
return "", ErrContainerUnknown
|
||||
}
|
||||
|
||||
// Requires startWriting.
|
||||
func (r *containerStore) SetMetadata(id, metadata string) error {
|
||||
if container, ok := r.lookup(id); ok {
|
||||
container.Metadata = metadata
|
||||
return r.saveFor(container)
|
||||
}
|
||||
return ErrContainerUnknown
|
||||
}
|
||||
|
||||
// The caller must hold r.inProcessLock for writing.
|
||||
func (r *containerStore) removeName(container *Container, name string) {
|
||||
container.Names = stringSliceWithoutValue(container.Names, name)
|
||||
}
|
||||
|
||||
// Requires startWriting.
|
||||
func (r *containerStore) updateNames(id string, names []string, op updateNameOperation) error {
|
||||
container, ok := r.lookup(id)
|
||||
if !ok {
|
||||
return ErrContainerUnknown
|
||||
}
|
||||
oldNames := container.Names
|
||||
names, err := applyNameOperation(oldNames, names, op)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, name := range oldNames {
|
||||
delete(r.byname, name)
|
||||
}
|
||||
for _, name := range names {
|
||||
if otherContainer, ok := r.byname[name]; ok {
|
||||
r.removeName(otherContainer, name)
|
||||
}
|
||||
r.byname[name] = container
|
||||
}
|
||||
container.Names = names
|
||||
return r.saveFor(container)
|
||||
}
|
||||
|
||||
// Requires startWriting.
|
||||
func (r *containerStore) Delete(id string) error {
|
||||
container, ok := r.lookup(id)
|
||||
if !ok {
|
||||
return ErrContainerUnknown
|
||||
}
|
||||
id = container.ID
|
||||
toDeleteIndex := -1
|
||||
for i, candidate := range r.containers {
|
||||
if candidate.ID == id {
|
||||
toDeleteIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
delete(r.byid, id)
|
||||
// This can only fail if the ID is already missing, which shouldn’t happen — and in that case the index is already in the desired state anyway.
|
||||
// The store’s Delete method is used on various paths to recover from failures, so this should be robust against partially missing data.
|
||||
_ = r.idindex.Delete(id)
|
||||
delete(r.bylayer, container.LayerID)
|
||||
for _, name := range container.Names {
|
||||
delete(r.byname, name)
|
||||
}
|
||||
if toDeleteIndex != -1 {
|
||||
// delete the container at toDeleteIndex
|
||||
if toDeleteIndex == len(r.containers)-1 {
|
||||
r.containers = r.containers[:len(r.containers)-1]
|
||||
} else {
|
||||
r.containers = append(r.containers[:toDeleteIndex], r.containers[toDeleteIndex+1:]...)
|
||||
}
|
||||
}
|
||||
if err := r.saveFor(container); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.RemoveAll(r.datadir(id)); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Requires startReading or startWriting.
|
||||
func (r *containerStore) Get(id string) (*Container, error) {
|
||||
if container, ok := r.lookup(id); ok {
|
||||
return copyContainer(container), nil
|
||||
}
|
||||
return nil, ErrContainerUnknown
|
||||
}
|
||||
|
||||
// Requires startReading or startWriting.
|
||||
func (r *containerStore) Lookup(name string) (id string, err error) {
|
||||
if container, ok := r.lookup(name); ok {
|
||||
return container.ID, nil
|
||||
}
|
||||
return "", ErrContainerUnknown
|
||||
}
|
||||
|
||||
// Requires startReading or startWriting.
|
||||
func (r *containerStore) Exists(id string) bool {
|
||||
_, ok := r.lookup(id)
|
||||
return ok
|
||||
}
|
||||
|
||||
// Requires startReading or startWriting.
|
||||
func (r *containerStore) BigData(id, key string) ([]byte, error) {
|
||||
if key == "" {
|
||||
return nil, fmt.Errorf("can't retrieve container big data value for empty name: %w", ErrInvalidBigDataName)
|
||||
}
|
||||
c, ok := r.lookup(id)
|
||||
if !ok {
|
||||
return nil, ErrContainerUnknown
|
||||
}
|
||||
return os.ReadFile(r.datapath(c.ID, key))
|
||||
}
|
||||
|
||||
// Requires startWriting. Yes, really, WRITING (see SetBigData).
|
||||
func (r *containerStore) BigDataSize(id, key string) (int64, error) {
|
||||
if key == "" {
|
||||
return -1, fmt.Errorf("can't retrieve size of container big data with empty name: %w", ErrInvalidBigDataName)
|
||||
}
|
||||
c, ok := r.lookup(id)
|
||||
if !ok {
|
||||
return -1, ErrContainerUnknown
|
||||
}
|
||||
if size, ok := c.BigDataSizes[key]; ok { // This is valid, and returns ok == false, for BigDataSizes == nil.
|
||||
return size, nil
|
||||
}
|
||||
if data, err := r.BigData(id, key); err == nil && data != nil {
|
||||
if err = r.SetBigData(id, key, data); err == nil {
|
||||
c, ok := r.lookup(id)
|
||||
if !ok {
|
||||
return -1, ErrContainerUnknown
|
||||
}
|
||||
if size, ok := c.BigDataSizes[key]; ok {
|
||||
return size, nil
|
||||
}
|
||||
} else {
|
||||
return -1, err
|
||||
}
|
||||
}
|
||||
return -1, ErrSizeUnknown
|
||||
}
|
||||
|
||||
// Requires startWriting. Yes, really, WRITING (see SetBigData).
|
||||
func (r *containerStore) BigDataDigest(id, key string) (digest.Digest, error) {
|
||||
if key == "" {
|
||||
return "", fmt.Errorf("can't retrieve digest of container big data value with empty name: %w", ErrInvalidBigDataName)
|
||||
}
|
||||
c, ok := r.lookup(id)
|
||||
if !ok {
|
||||
return "", ErrContainerUnknown
|
||||
}
|
||||
if d, ok := c.BigDataDigests[key]; ok { // This is valid, and returns ok == false, for BigDataSizes == nil.
|
||||
return d, nil
|
||||
}
|
||||
if data, err := r.BigData(id, key); err == nil && data != nil {
|
||||
if err = r.SetBigData(id, key, data); err == nil {
|
||||
c, ok := r.lookup(id)
|
||||
if !ok {
|
||||
return "", ErrContainerUnknown
|
||||
}
|
||||
if d, ok := c.BigDataDigests[key]; ok {
|
||||
return d, nil
|
||||
}
|
||||
} else {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
return "", ErrDigestUnknown
|
||||
}
|
||||
|
||||
// Requires startReading or startWriting.
|
||||
func (r *containerStore) BigDataNames(id string) ([]string, error) {
|
||||
c, ok := r.lookup(id)
|
||||
if !ok {
|
||||
return nil, ErrContainerUnknown
|
||||
}
|
||||
return copyStringSlice(c.BigDataNames), nil
|
||||
}
|
||||
|
||||
// Requires startWriting.
|
||||
func (r *containerStore) SetBigData(id, key string, data []byte) error {
|
||||
if key == "" {
|
||||
return fmt.Errorf("can't set empty name for container big data item: %w", ErrInvalidBigDataName)
|
||||
}
|
||||
c, ok := r.lookup(id)
|
||||
if !ok {
|
||||
return ErrContainerUnknown
|
||||
}
|
||||
if err := os.MkdirAll(r.datadir(c.ID), 0o700); err != nil {
|
||||
return err
|
||||
}
|
||||
err := ioutils.AtomicWriteFile(r.datapath(c.ID, key), data, 0o600)
|
||||
if err == nil {
|
||||
save := false
|
||||
if c.BigDataSizes == nil {
|
||||
c.BigDataSizes = make(map[string]int64)
|
||||
}
|
||||
oldSize, sizeOk := c.BigDataSizes[key]
|
||||
c.BigDataSizes[key] = int64(len(data))
|
||||
if c.BigDataDigests == nil {
|
||||
c.BigDataDigests = make(map[string]digest.Digest)
|
||||
}
|
||||
oldDigest, digestOk := c.BigDataDigests[key]
|
||||
newDigest := digest.Canonical.FromBytes(data)
|
||||
c.BigDataDigests[key] = newDigest
|
||||
if !sizeOk || oldSize != c.BigDataSizes[key] || !digestOk || oldDigest != newDigest {
|
||||
save = true
|
||||
}
|
||||
addName := true
|
||||
for _, name := range c.BigDataNames {
|
||||
if name == key {
|
||||
addName = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if addName {
|
||||
c.BigDataNames = append(c.BigDataNames, key)
|
||||
save = true
|
||||
}
|
||||
if save {
|
||||
err = r.saveFor(c)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Requires startWriting.
|
||||
func (r *containerStore) Wipe() error {
|
||||
ids := make([]string, 0, len(r.byid))
|
||||
for id := range r.byid {
|
||||
ids = append(ids, id)
|
||||
}
|
||||
for _, id := range ids {
|
||||
if err := r.Delete(id); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
216
vendor/github.com/containers/storage/deprecated.go
generated
vendored
Normal file
216
vendor/github.com/containers/storage/deprecated.go
generated
vendored
Normal file
|
|
@ -0,0 +1,216 @@
|
|||
package storage
|
||||
|
||||
import (
|
||||
"io"
|
||||
"time"
|
||||
|
||||
drivers "github.com/containers/storage/drivers"
|
||||
"github.com/containers/storage/pkg/archive"
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
)
|
||||
|
||||
// The type definitions in this file exist ONLY to maintain formal API compatibility.
|
||||
// DO NOT ADD ANY NEW METHODS TO THESE INTERFACES.
|
||||
|
||||
// ROFileBasedStore is a deprecated interface with no documented way to use it from callers outside of c/storage.
|
||||
//
|
||||
// Deprecated: There is no way to use this from any external user of c/storage to invoke c/storage functionality.
|
||||
type ROFileBasedStore interface {
|
||||
Locker
|
||||
Load() error
|
||||
ReloadIfChanged() error
|
||||
}
|
||||
|
||||
// RWFileBasedStore is a deprecated interface with no documented way to use it from callers outside of c/storage.
|
||||
//
|
||||
// Deprecated: There is no way to use this from any external user of c/storage to invoke c/storage functionality.
|
||||
type RWFileBasedStore interface {
|
||||
Save() error
|
||||
}
|
||||
|
||||
// FileBasedStore is a deprecated interface with no documented way to use it from callers outside of c/storage.
|
||||
//
|
||||
// Deprecated: There is no way to use this from any external user of c/storage to invoke c/storage functionality.
|
||||
type FileBasedStore interface {
|
||||
ROFileBasedStore
|
||||
RWFileBasedStore
|
||||
}
|
||||
|
||||
// ROMetadataStore is a deprecated interface with no documented way to use it from callers outside of c/storage.
|
||||
//
|
||||
// Deprecated: There is no way to use this from any external user of c/storage to invoke c/storage functionality.
|
||||
type ROMetadataStore interface {
|
||||
Metadata(id string) (string, error)
|
||||
}
|
||||
|
||||
// RWMetadataStore is a deprecated interface with no documented way to use it from callers outside of c/storage.
|
||||
//
|
||||
// Deprecated: There is no way to use this from any external user of c/storage to invoke c/storage functionality.
|
||||
type RWMetadataStore interface {
|
||||
SetMetadata(id, metadata string) error
|
||||
}
|
||||
|
||||
// MetadataStore is a deprecated interface with no documented way to use it from callers outside of c/storage.
|
||||
//
|
||||
// Deprecated: There is no way to use this from any external user of c/storage to invoke c/storage functionality.
|
||||
type MetadataStore interface {
|
||||
ROMetadataStore
|
||||
RWMetadataStore
|
||||
}
|
||||
|
||||
// ROBigDataStore is a deprecated interface with no documented way to use it from callers outside of c/storage.
|
||||
//
|
||||
// Deprecated: There is no way to use this from any external user of c/storage to invoke c/storage functionality.
|
||||
type ROBigDataStore interface {
|
||||
BigData(id, key string) ([]byte, error)
|
||||
BigDataSize(id, key string) (int64, error)
|
||||
BigDataDigest(id, key string) (digest.Digest, error)
|
||||
BigDataNames(id string) ([]string, error)
|
||||
}
|
||||
|
||||
// RWImageBigDataStore is a deprecated interface with no documented way to use it from callers outside of c/storage.
|
||||
//
|
||||
// Deprecated: There is no way to use this from any external user of c/storage to invoke c/storage functionality.
|
||||
type RWImageBigDataStore interface {
|
||||
SetBigData(id, key string, data []byte, digestManifest func([]byte) (digest.Digest, error)) error
|
||||
}
|
||||
|
||||
// ContainerBigDataStore is a deprecated interface with no documented way to use it from callers outside of c/storage.
|
||||
//
|
||||
// Deprecated: There is no way to use this from any external user of c/storage to invoke c/storage functionality.
|
||||
type ContainerBigDataStore interface {
|
||||
ROBigDataStore
|
||||
SetBigData(id, key string, data []byte) error
|
||||
}
|
||||
|
||||
// ROLayerBigDataStore is a deprecated interface with no documented way to use it from callers outside of c/storage.
|
||||
//
|
||||
// Deprecated: There is no way to use this from any external user of c/storage to invoke c/storage functionality.
|
||||
type ROLayerBigDataStore interface {
|
||||
BigData(id, key string) (io.ReadCloser, error)
|
||||
BigDataNames(id string) ([]string, error)
|
||||
}
|
||||
|
||||
// RWLayerBigDataStore is a deprecated interface with no documented way to use it from callers outside of c/storage.
|
||||
//
|
||||
// Deprecated: There is no way to use this from any external user of c/storage to invoke c/storage functionality.
|
||||
type RWLayerBigDataStore interface {
|
||||
SetBigData(id, key string, data io.Reader) error
|
||||
}
|
||||
|
||||
// LayerBigDataStore is a deprecated interface with no documented way to use it from callers outside of c/storage.
|
||||
//
|
||||
// Deprecated: There is no way to use this from any external user of c/storage to invoke c/storage functionality.
|
||||
type LayerBigDataStore interface {
|
||||
ROLayerBigDataStore
|
||||
RWLayerBigDataStore
|
||||
}
|
||||
|
||||
// FlaggableStore is a deprecated interface with no documented way to use it from callers outside of c/storage.
|
||||
//
|
||||
// Deprecated: There is no way to use this from any external user of c/storage to invoke c/storage functionality.
|
||||
type FlaggableStore interface {
|
||||
ClearFlag(id string, flag string) error
|
||||
SetFlag(id string, flag string, value interface{}) error
|
||||
}
|
||||
|
||||
// ContainerStore is a deprecated interface with no documented way to use it from callers outside of c/storage.
|
||||
//
|
||||
// Deprecated: There is no way to use this from any external user of c/storage to invoke c/storage functionality.
|
||||
type ContainerStore interface {
|
||||
FileBasedStore
|
||||
MetadataStore
|
||||
ContainerBigDataStore
|
||||
FlaggableStore
|
||||
Create(id string, names []string, image, layer, metadata string, options *ContainerOptions) (*Container, error)
|
||||
SetNames(id string, names []string) error
|
||||
AddNames(id string, names []string) error
|
||||
RemoveNames(id string, names []string) error
|
||||
Get(id string) (*Container, error)
|
||||
Exists(id string) bool
|
||||
Delete(id string) error
|
||||
Wipe() error
|
||||
Lookup(name string) (string, error)
|
||||
Containers() ([]Container, error)
|
||||
}
|
||||
|
||||
// ROImageStore is a deprecated interface with no documented way to use it from callers outside of c/storage.
|
||||
//
|
||||
// Deprecated: There is no way to use this from any external user of c/storage to invoke c/storage functionality.
|
||||
type ROImageStore interface {
|
||||
ROFileBasedStore
|
||||
ROMetadataStore
|
||||
ROBigDataStore
|
||||
Exists(id string) bool
|
||||
Get(id string) (*Image, error)
|
||||
Lookup(name string) (string, error)
|
||||
Images() ([]Image, error)
|
||||
ByDigest(d digest.Digest) ([]*Image, error)
|
||||
}
|
||||
|
||||
// ImageStore is a deprecated interface with no documented way to use it from callers outside of c/storage.
|
||||
//
|
||||
// Deprecated: There is no way to use this from any external user of c/storage to invoke c/storage functionality.
|
||||
type ImageStore interface {
|
||||
ROImageStore
|
||||
RWFileBasedStore
|
||||
RWMetadataStore
|
||||
RWImageBigDataStore
|
||||
FlaggableStore
|
||||
Create(id string, names []string, layer, metadata string, created time.Time, searchableDigest digest.Digest) (*Image, error)
|
||||
SetNames(id string, names []string) error
|
||||
AddNames(id string, names []string) error
|
||||
RemoveNames(id string, names []string) error
|
||||
Delete(id string) error
|
||||
Wipe() error
|
||||
}
|
||||
|
||||
// ROLayerStore is a deprecated interface with no documented way to use it from callers outside of c/storage.
|
||||
//
|
||||
// Deprecated: There is no way to use this from any external user of c/storage to invoke c/storage functionality.
|
||||
type ROLayerStore interface {
|
||||
ROFileBasedStore
|
||||
ROMetadataStore
|
||||
ROLayerBigDataStore
|
||||
Exists(id string) bool
|
||||
Get(id string) (*Layer, error)
|
||||
Status() ([][2]string, error)
|
||||
Changes(from, to string) ([]archive.Change, error)
|
||||
Diff(from, to string, options *DiffOptions) (io.ReadCloser, error)
|
||||
DiffSize(from, to string) (int64, error)
|
||||
Size(name string) (int64, error)
|
||||
Lookup(name string) (string, error)
|
||||
LayersByCompressedDigest(d digest.Digest) ([]Layer, error)
|
||||
LayersByUncompressedDigest(d digest.Digest) ([]Layer, error)
|
||||
Layers() ([]Layer, error)
|
||||
}
|
||||
|
||||
// LayerStore is a deprecated interface with no documented way to use it from callers outside of c/storage.
|
||||
//
|
||||
// Deprecated: There is no way to use this from any external user of c/storage to invoke c/storage functionality.
|
||||
type LayerStore interface {
|
||||
ROLayerStore
|
||||
RWFileBasedStore
|
||||
RWMetadataStore
|
||||
FlaggableStore
|
||||
RWLayerBigDataStore
|
||||
Create(id string, parent *Layer, names []string, mountLabel string, options map[string]string, moreOptions *LayerOptions, writeable bool) (*Layer, error)
|
||||
CreateWithFlags(id string, parent *Layer, names []string, mountLabel string, options map[string]string, moreOptions *LayerOptions, writeable bool, flags map[string]interface{}) (layer *Layer, err error)
|
||||
Put(id string, parent *Layer, names []string, mountLabel string, options map[string]string, moreOptions *LayerOptions, writeable bool, flags map[string]interface{}, diff io.Reader) (*Layer, int64, error)
|
||||
SetNames(id string, names []string) error
|
||||
AddNames(id string, names []string) error
|
||||
RemoveNames(id string, names []string) error
|
||||
Delete(id string) error
|
||||
Wipe() error
|
||||
Mount(id string, options drivers.MountOpts) (string, error)
|
||||
Unmount(id string, force bool) (bool, error)
|
||||
Mounted(id string) (int, error)
|
||||
ParentOwners(id string) (uids, gids []int, err error)
|
||||
ApplyDiff(to string, diff io.Reader) (int64, error)
|
||||
ApplyDiffWithDiffer(to string, options *drivers.ApplyDiffOpts, differ drivers.Differ) (*drivers.DriverWithDifferOutput, error)
|
||||
CleanupStagingDirectory(stagingDirectory string) error
|
||||
ApplyDiffFromStagingDirectory(id, stagingDirectory string, diffOutput *drivers.DriverWithDifferOutput, options *drivers.ApplyDiffOpts) error
|
||||
DifferTarget(id string) (string, error)
|
||||
LoadLocked() error
|
||||
PutAdditionalLayer(id string, parentLayer *Layer, names []string, aLayer drivers.AdditionalLayer) (layer *Layer, err error)
|
||||
}
|
||||
781
vendor/github.com/containers/storage/drivers/aufs/aufs.go
generated
vendored
Normal file
781
vendor/github.com/containers/storage/drivers/aufs/aufs.go
generated
vendored
Normal file
|
|
@ -0,0 +1,781 @@
|
|||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
/*
|
||||
|
||||
aufs driver directory structure
|
||||
|
||||
.
|
||||
├── layers // Metadata of layers
|
||||
│ ├── 1
|
||||
│ ├── 2
|
||||
│ └── 3
|
||||
├── diff // Content of the layer
|
||||
│ ├── 1 // Contains layers that need to be mounted for the id
|
||||
│ ├── 2
|
||||
│ └── 3
|
||||
└── mnt // Mount points for the rw layers to be mounted
|
||||
├── 1
|
||||
├── 2
|
||||
└── 3
|
||||
|
||||
*/
|
||||
|
||||
package aufs
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
graphdriver "github.com/containers/storage/drivers"
|
||||
"github.com/containers/storage/pkg/archive"
|
||||
"github.com/containers/storage/pkg/chrootarchive"
|
||||
"github.com/containers/storage/pkg/directory"
|
||||
"github.com/containers/storage/pkg/idtools"
|
||||
"github.com/containers/storage/pkg/locker"
|
||||
mountpk "github.com/containers/storage/pkg/mount"
|
||||
"github.com/containers/storage/pkg/parsers"
|
||||
"github.com/containers/storage/pkg/system"
|
||||
"github.com/containers/storage/pkg/unshare"
|
||||
"github.com/opencontainers/selinux/go-selinux/label"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/vbatts/tar-split/tar/storage"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrAufsNotSupported is returned if aufs is not supported by the host.
|
||||
ErrAufsNotSupported = fmt.Errorf("aufs was not found in /proc/filesystems")
|
||||
// ErrAufsNested means aufs cannot be used bc we are in a user namespace
|
||||
ErrAufsNested = fmt.Errorf("aufs cannot be used in non-init user namespace")
|
||||
backingFs = "<unknown>"
|
||||
|
||||
enableDirpermLock sync.Once
|
||||
enableDirperm bool
|
||||
)
|
||||
|
||||
const defaultPerms = os.FileMode(0o555)
|
||||
|
||||
func init() {
|
||||
graphdriver.MustRegister("aufs", Init)
|
||||
}
|
||||
|
||||
// Driver contains information about the filesystem mounted.
|
||||
type Driver struct {
|
||||
sync.Mutex
|
||||
root string
|
||||
uidMaps []idtools.IDMap
|
||||
gidMaps []idtools.IDMap
|
||||
ctr *graphdriver.RefCounter
|
||||
pathCacheLock sync.Mutex
|
||||
pathCache map[string]string
|
||||
naiveDiff graphdriver.DiffDriver
|
||||
locker *locker.Locker
|
||||
mountOptions string
|
||||
}
|
||||
|
||||
// Init returns a new AUFS driver.
|
||||
// An error is returned if AUFS is not supported.
|
||||
func Init(home string, options graphdriver.Options) (graphdriver.Driver, error) {
|
||||
// Try to load the aufs kernel module
|
||||
if err := supportsAufs(); err != nil {
|
||||
return nil, fmt.Errorf("kernel does not support aufs: %w", graphdriver.ErrNotSupported)
|
||||
}
|
||||
|
||||
fsMagic, err := graphdriver.GetFSMagic(home)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if fsName, ok := graphdriver.FsNames[fsMagic]; ok {
|
||||
backingFs = fsName
|
||||
}
|
||||
|
||||
switch fsMagic {
|
||||
case graphdriver.FsMagicAufs, graphdriver.FsMagicBtrfs, graphdriver.FsMagicEcryptfs:
|
||||
logrus.Errorf("AUFS is not supported over %s", backingFs)
|
||||
return nil, fmt.Errorf("aufs is not supported over %q: %w", backingFs, graphdriver.ErrIncompatibleFS)
|
||||
}
|
||||
|
||||
var mountOptions string
|
||||
for _, option := range options.DriverOptions {
|
||||
key, val, err := parsers.ParseKeyValueOpt(option)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
key = strings.ToLower(key)
|
||||
switch key {
|
||||
case "aufs.mountopt":
|
||||
mountOptions = val
|
||||
default:
|
||||
return nil, fmt.Errorf("option %s not supported", option)
|
||||
}
|
||||
}
|
||||
paths := []string{
|
||||
"mnt",
|
||||
"diff",
|
||||
"layers",
|
||||
}
|
||||
|
||||
a := &Driver{
|
||||
root: home,
|
||||
uidMaps: options.UIDMaps,
|
||||
gidMaps: options.GIDMaps,
|
||||
pathCache: make(map[string]string),
|
||||
ctr: graphdriver.NewRefCounter(graphdriver.NewFsChecker(graphdriver.FsMagicAufs)),
|
||||
locker: locker.New(),
|
||||
mountOptions: mountOptions,
|
||||
}
|
||||
|
||||
rootUID, rootGID, err := idtools.GetRootUIDGID(options.UIDMaps, options.GIDMaps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Create the root aufs driver dir and return
|
||||
// if it already exists
|
||||
// If not populate the dir structure
|
||||
if err := idtools.MkdirAllAs(home, 0o700, rootUID, rootGID); err != nil {
|
||||
if os.IsExist(err) {
|
||||
return a, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := mountpk.MakePrivate(home); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Populate the dir structure
|
||||
for _, p := range paths {
|
||||
if err := idtools.MkdirAllAs(path.Join(home, p), 0o700, rootUID, rootGID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
logger := logrus.WithFields(logrus.Fields{
|
||||
"module": "graphdriver",
|
||||
"driver": "aufs",
|
||||
})
|
||||
|
||||
for _, path := range []string{"mnt", "diff"} {
|
||||
p := filepath.Join(home, path)
|
||||
entries, err := os.ReadDir(p)
|
||||
if err != nil {
|
||||
logger.WithError(err).WithField("dir", p).Error("error reading dir entries")
|
||||
continue
|
||||
}
|
||||
for _, entry := range entries {
|
||||
if !entry.IsDir() {
|
||||
continue
|
||||
}
|
||||
if strings.HasSuffix(entry.Name(), "-removing") {
|
||||
logger.WithField("dir", entry.Name()).Debug("Cleaning up stale layer dir")
|
||||
if err := system.EnsureRemoveAll(filepath.Join(p, entry.Name())); err != nil {
|
||||
logger.WithField("dir", entry.Name()).WithError(err).Error("Error removing stale layer dir")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
a.naiveDiff = graphdriver.NewNaiveDiffDriver(a, a)
|
||||
return a, nil
|
||||
}
|
||||
|
||||
// Return a nil error if the kernel supports aufs
|
||||
// We cannot modprobe because inside dind modprobe fails
|
||||
// to run
|
||||
func supportsAufs() error {
|
||||
// We can try to modprobe aufs first before looking at
|
||||
// proc/filesystems for when aufs is supported
|
||||
exec.Command("modprobe", "aufs").Run()
|
||||
|
||||
if unshare.IsRootless() {
|
||||
return ErrAufsNested
|
||||
}
|
||||
|
||||
f, err := os.Open("/proc/filesystems")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
s := bufio.NewScanner(f)
|
||||
for s.Scan() {
|
||||
if strings.Contains(s.Text(), "aufs") {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return ErrAufsNotSupported
|
||||
}
|
||||
|
||||
func (a *Driver) rootPath() string {
|
||||
return a.root
|
||||
}
|
||||
|
||||
func (*Driver) String() string {
|
||||
return "aufs"
|
||||
}
|
||||
|
||||
// Status returns current information about the filesystem such as root directory, number of directories mounted, etc.
|
||||
func (a *Driver) Status() [][2]string {
|
||||
ids, _ := loadIds(path.Join(a.rootPath(), "layers"))
|
||||
return [][2]string{
|
||||
{"Root Dir", a.rootPath()},
|
||||
{"Backing Filesystem", backingFs},
|
||||
{"Dirs", fmt.Sprintf("%d", len(ids))},
|
||||
{"Dirperm1 Supported", fmt.Sprintf("%v", useDirperm())},
|
||||
}
|
||||
}
|
||||
|
||||
// Metadata not implemented
|
||||
func (a *Driver) Metadata(id string) (map[string]string, error) {
|
||||
return nil, nil //nolint: nilnil
|
||||
}
|
||||
|
||||
// Exists returns true if the given id is registered with
|
||||
// this driver
|
||||
func (a *Driver) Exists(id string) bool {
|
||||
if _, err := os.Lstat(path.Join(a.rootPath(), "layers", id)); err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// ListLayers() returns all of the layers known to the driver.
|
||||
func (a *Driver) ListLayers() ([]string, error) {
|
||||
diffsDir := filepath.Join(a.rootPath(), "diff")
|
||||
entries, err := os.ReadDir(diffsDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
results := make([]string, 0, len(entries))
|
||||
for _, entry := range entries {
|
||||
if !entry.IsDir() {
|
||||
continue
|
||||
}
|
||||
results = append(results, entry.Name())
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// AdditionalImageStores returns additional image stores supported by the driver
|
||||
func (a *Driver) AdditionalImageStores() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateFromTemplate creates a layer with the same contents and parent as another layer.
|
||||
func (a *Driver) CreateFromTemplate(id, template string, templateIDMappings *idtools.IDMappings, parent string, parentIDMappings *idtools.IDMappings, opts *graphdriver.CreateOpts, readWrite bool) error {
|
||||
if opts == nil {
|
||||
opts = &graphdriver.CreateOpts{}
|
||||
}
|
||||
return graphdriver.NaiveCreateFromTemplate(a, id, template, templateIDMappings, parent, parentIDMappings, opts, readWrite)
|
||||
}
|
||||
|
||||
// CreateReadWrite creates a layer that is writable for use as a container
|
||||
// file system.
|
||||
func (a *Driver) CreateReadWrite(id, parent string, opts *graphdriver.CreateOpts) error {
|
||||
return a.Create(id, parent, opts)
|
||||
}
|
||||
|
||||
// Create three folders for each id
|
||||
// mnt, layers, and diff
|
||||
func (a *Driver) Create(id, parent string, opts *graphdriver.CreateOpts) error {
|
||||
if opts != nil && len(opts.StorageOpt) != 0 {
|
||||
return fmt.Errorf("--storage-opt is not supported for aufs")
|
||||
}
|
||||
|
||||
if err := a.createDirsFor(id, parent); err != nil {
|
||||
return err
|
||||
}
|
||||
// Write the layers metadata
|
||||
f, err := os.Create(path.Join(a.rootPath(), "layers", id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
if parent != "" {
|
||||
ids, err := getParentIDs(a.rootPath(), parent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := fmt.Fprintln(f, parent); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, i := range ids {
|
||||
if _, err := fmt.Fprintln(f, i); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// createDirsFor creates two directories for the given id.
|
||||
// mnt and diff
|
||||
func (a *Driver) createDirsFor(id, parent string) error {
|
||||
paths := []string{
|
||||
"mnt",
|
||||
"diff",
|
||||
}
|
||||
|
||||
// Directory permission is 0555.
|
||||
// The path of directories are <aufs_root_path>/mnt/<image_id>
|
||||
// and <aufs_root_path>/diff/<image_id>
|
||||
for _, p := range paths {
|
||||
rootPair := idtools.NewIDMappingsFromMaps(a.uidMaps, a.gidMaps).RootPair()
|
||||
rootPerms := defaultPerms
|
||||
if parent != "" {
|
||||
st, err := system.Stat(path.Join(a.rootPath(), p, parent))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rootPerms = os.FileMode(st.Mode())
|
||||
rootPair.UID = int(st.UID())
|
||||
rootPair.GID = int(st.GID())
|
||||
}
|
||||
if err := idtools.MkdirAllAndChownNew(path.Join(a.rootPath(), p, id), rootPerms, rootPair); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove will unmount and remove the given id.
|
||||
func (a *Driver) Remove(id string) error {
|
||||
a.locker.Lock(id)
|
||||
defer a.locker.Unlock(id)
|
||||
a.pathCacheLock.Lock()
|
||||
mountpoint, exists := a.pathCache[id]
|
||||
a.pathCacheLock.Unlock()
|
||||
if !exists {
|
||||
mountpoint = a.getMountpoint(id)
|
||||
}
|
||||
|
||||
logger := logrus.WithFields(logrus.Fields{
|
||||
"module": "graphdriver",
|
||||
"driver": "aufs",
|
||||
"layer": id,
|
||||
})
|
||||
|
||||
var retries int
|
||||
for {
|
||||
mounted, err := a.mounted(mountpoint)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
break
|
||||
}
|
||||
return err
|
||||
}
|
||||
if !mounted {
|
||||
break
|
||||
}
|
||||
|
||||
err = a.unmount(mountpoint)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
|
||||
if err != unix.EBUSY {
|
||||
return fmt.Errorf("aufs: unmount error: %s: %w", mountpoint, err)
|
||||
}
|
||||
if retries >= 5 {
|
||||
return fmt.Errorf("aufs: unmount error after retries: %s: %w", mountpoint, err)
|
||||
}
|
||||
// If unmount returns EBUSY, it could be a transient error. Sleep and retry.
|
||||
retries++
|
||||
logger.Warnf("unmount failed due to EBUSY: retry count: %d", retries)
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
|
||||
// Remove the layers file for the id
|
||||
if err := os.Remove(path.Join(a.rootPath(), "layers", id)); err != nil && !os.IsNotExist(err) {
|
||||
return fmt.Errorf("removing layers dir for %s: %w", id, err)
|
||||
}
|
||||
|
||||
if err := atomicRemove(a.getDiffPath(id)); err != nil {
|
||||
return fmt.Errorf("could not remove diff path for id %s: %w", id, err)
|
||||
}
|
||||
|
||||
// Atomically remove each directory in turn by first moving it out of the
|
||||
// way (so that container runtime doesn't find it anymore) before doing removal of
|
||||
// the whole tree.
|
||||
if err := atomicRemove(mountpoint); err != nil {
|
||||
if errors.Is(err, unix.EBUSY) {
|
||||
logger.WithField("dir", mountpoint).WithError(err).Warn("error performing atomic remove due to EBUSY")
|
||||
}
|
||||
return fmt.Errorf("could not remove mountpoint for id %s: %w", id, err)
|
||||
}
|
||||
|
||||
a.pathCacheLock.Lock()
|
||||
delete(a.pathCache, id)
|
||||
a.pathCacheLock.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func atomicRemove(source string) error {
|
||||
target := source + "-removing"
|
||||
|
||||
err := os.Rename(source, target)
|
||||
switch {
|
||||
case err == nil, os.IsNotExist(err):
|
||||
case os.IsExist(err):
|
||||
// Got error saying the target dir already exists, maybe the source doesn't exist due to a previous (failed) remove
|
||||
if _, e := os.Stat(source); !os.IsNotExist(e) {
|
||||
return fmt.Errorf("target rename dir '%s' exists but should not, this needs to be manually cleaned up: %w", target, err)
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("preparing atomic delete: %w", err)
|
||||
}
|
||||
|
||||
return system.EnsureRemoveAll(target)
|
||||
}
|
||||
|
||||
// Get returns the rootfs path for the id.
|
||||
// This will mount the dir at its given path
|
||||
func (a *Driver) Get(id string, options graphdriver.MountOpts) (string, error) {
|
||||
a.locker.Lock(id)
|
||||
defer a.locker.Unlock(id)
|
||||
parents, err := a.getParentLayerPaths(id)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return "", err
|
||||
}
|
||||
|
||||
a.pathCacheLock.Lock()
|
||||
m, exists := a.pathCache[id]
|
||||
a.pathCacheLock.Unlock()
|
||||
|
||||
if !exists {
|
||||
m = a.getDiffPath(id)
|
||||
if len(parents) > 0 {
|
||||
m = a.getMountpoint(id)
|
||||
}
|
||||
}
|
||||
if count := a.ctr.Increment(m); count > 1 {
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// If a dir does not have a parent ( no layers )do not try to mount
|
||||
// just return the diff path to the data
|
||||
if len(parents) > 0 {
|
||||
if err := a.mount(id, m, parents, options); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
a.pathCacheLock.Lock()
|
||||
a.pathCache[id] = m
|
||||
a.pathCacheLock.Unlock()
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// Put unmounts and updates list of active mounts.
|
||||
func (a *Driver) Put(id string) error {
|
||||
a.locker.Lock(id)
|
||||
defer a.locker.Unlock(id)
|
||||
a.pathCacheLock.Lock()
|
||||
m, exists := a.pathCache[id]
|
||||
if !exists {
|
||||
m = a.getMountpoint(id)
|
||||
a.pathCache[id] = m
|
||||
}
|
||||
a.pathCacheLock.Unlock()
|
||||
if count := a.ctr.Decrement(m); count > 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
err := a.unmount(m)
|
||||
if err != nil {
|
||||
logrus.Debugf("Failed to unmount %s aufs: %v", id, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// ReadWriteDiskUsage returns the disk usage of the writable directory for the ID.
|
||||
// For AUFS, it queries the mountpoint for this ID.
|
||||
func (a *Driver) ReadWriteDiskUsage(id string) (*directory.DiskUsage, error) {
|
||||
a.locker.Lock(id)
|
||||
defer a.locker.Unlock(id)
|
||||
a.pathCacheLock.Lock()
|
||||
m, exists := a.pathCache[id]
|
||||
if !exists {
|
||||
m = a.getMountpoint(id)
|
||||
a.pathCache[id] = m
|
||||
}
|
||||
a.pathCacheLock.Unlock()
|
||||
return directory.Usage(m)
|
||||
}
|
||||
|
||||
// isParent returns if the passed in parent is the direct parent of the passed in layer
|
||||
func (a *Driver) isParent(id, parent string) bool {
|
||||
parents, _ := getParentIDs(a.rootPath(), id)
|
||||
if parent == "" && len(parents) > 0 {
|
||||
return false
|
||||
}
|
||||
return !(len(parents) > 0 && parent != parents[0])
|
||||
}
|
||||
|
||||
// Diff produces an archive of the changes between the specified
|
||||
// layer and its parent layer which may be "".
|
||||
func (a *Driver) Diff(id string, idMappings *idtools.IDMappings, parent string, parentMappings *idtools.IDMappings, mountLabel string) (io.ReadCloser, error) {
|
||||
if !a.isParent(id, parent) {
|
||||
return a.naiveDiff.Diff(id, idMappings, parent, parentMappings, mountLabel)
|
||||
}
|
||||
|
||||
if idMappings == nil {
|
||||
idMappings = &idtools.IDMappings{}
|
||||
}
|
||||
|
||||
// AUFS doesn't need the parent layer to produce a diff.
|
||||
return archive.TarWithOptions(path.Join(a.rootPath(), "diff", id), &archive.TarOptions{
|
||||
Compression: archive.Uncompressed,
|
||||
ExcludePatterns: []string{archive.WhiteoutMetaPrefix + "*", "!" + archive.WhiteoutOpaqueDir},
|
||||
UIDMaps: idMappings.UIDs(),
|
||||
GIDMaps: idMappings.GIDs(),
|
||||
})
|
||||
}
|
||||
|
||||
type fileGetNilCloser struct {
|
||||
storage.FileGetter
|
||||
}
|
||||
|
||||
func (f fileGetNilCloser) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// DiffGetter returns a FileGetCloser that can read files from the directory that
|
||||
// contains files for the layer differences. Used for direct access for tar-split.
|
||||
func (a *Driver) DiffGetter(id string) (graphdriver.FileGetCloser, error) {
|
||||
p := path.Join(a.rootPath(), "diff", id)
|
||||
return fileGetNilCloser{storage.NewPathFileGetter(p)}, nil
|
||||
}
|
||||
|
||||
func (a *Driver) applyDiff(id string, idMappings *idtools.IDMappings, diff io.Reader) error {
|
||||
if idMappings == nil {
|
||||
idMappings = &idtools.IDMappings{}
|
||||
}
|
||||
return chrootarchive.UntarUncompressed(diff, path.Join(a.rootPath(), "diff", id), &archive.TarOptions{
|
||||
UIDMaps: idMappings.UIDs(),
|
||||
GIDMaps: idMappings.GIDs(),
|
||||
})
|
||||
}
|
||||
|
||||
// DiffSize calculates the changes between the specified id
|
||||
// and its parent and returns the size in bytes of the changes
|
||||
// relative to its base filesystem directory.
|
||||
func (a *Driver) DiffSize(id string, idMappings *idtools.IDMappings, parent string, parentMappings *idtools.IDMappings, mountLabel string) (size int64, err error) {
|
||||
if !a.isParent(id, parent) {
|
||||
return a.naiveDiff.DiffSize(id, idMappings, parent, parentMappings, mountLabel)
|
||||
}
|
||||
// AUFS doesn't need the parent layer to calculate the diff size.
|
||||
return directory.Size(path.Join(a.rootPath(), "diff", id))
|
||||
}
|
||||
|
||||
// ApplyDiff extracts the changeset from the given diff into the
|
||||
// layer with the specified id and parent, returning the size of the
|
||||
// new layer in bytes.
|
||||
func (a *Driver) ApplyDiff(id, parent string, options graphdriver.ApplyDiffOpts) (size int64, err error) {
|
||||
if !a.isParent(id, parent) {
|
||||
return a.naiveDiff.ApplyDiff(id, parent, options)
|
||||
}
|
||||
|
||||
// AUFS doesn't need the parent id to apply the diff if it is the direct parent.
|
||||
if err = a.applyDiff(id, options.Mappings, options.Diff); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return directory.Size(path.Join(a.rootPath(), "diff", id))
|
||||
}
|
||||
|
||||
// Changes produces a list of changes between the specified layer
|
||||
// and its parent layer. If parent is "", then all changes will be ADD changes.
|
||||
func (a *Driver) Changes(id string, idMappings *idtools.IDMappings, parent string, parentMappings *idtools.IDMappings, mountLabel string) ([]archive.Change, error) {
|
||||
if !a.isParent(id, parent) {
|
||||
return a.naiveDiff.Changes(id, idMappings, parent, parentMappings, mountLabel)
|
||||
}
|
||||
|
||||
// AUFS doesn't have snapshots, so we need to get changes from all parent
|
||||
// layers.
|
||||
layers, err := a.getParentLayerPaths(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return archive.Changes(layers, path.Join(a.rootPath(), "diff", id))
|
||||
}
|
||||
|
||||
func (a *Driver) getParentLayerPaths(id string) ([]string, error) {
|
||||
parentIds, err := getParentIDs(a.rootPath(), id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
layers := make([]string, len(parentIds))
|
||||
|
||||
// Get the diff paths for all the parent ids
|
||||
for i, p := range parentIds {
|
||||
layers[i] = path.Join(a.rootPath(), "diff", p)
|
||||
}
|
||||
return layers, nil
|
||||
}
|
||||
|
||||
func (a *Driver) mount(id string, target string, layers []string, options graphdriver.MountOpts) error {
|
||||
a.Lock()
|
||||
defer a.Unlock()
|
||||
|
||||
// If the id is mounted or we get an error return
|
||||
if mounted, err := a.mounted(target); err != nil || mounted {
|
||||
return err
|
||||
}
|
||||
|
||||
rw := a.getDiffPath(id)
|
||||
|
||||
if err := a.aufsMount(layers, rw, target, options); err != nil {
|
||||
return fmt.Errorf("creating aufs mount to %s: %w", target, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Driver) unmount(mountPath string) error {
|
||||
a.Lock()
|
||||
defer a.Unlock()
|
||||
|
||||
if mounted, err := a.mounted(mountPath); err != nil || !mounted {
|
||||
return err
|
||||
}
|
||||
if err := Unmount(mountPath); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Driver) mounted(mountpoint string) (bool, error) {
|
||||
return graphdriver.Mounted(graphdriver.FsMagicAufs, mountpoint)
|
||||
}
|
||||
|
||||
// Cleanup aufs and unmount all mountpoints
|
||||
func (a *Driver) Cleanup() error {
|
||||
var dirs []string
|
||||
if err := filepath.WalkDir(a.mntPath(), func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !d.IsDir() {
|
||||
return nil
|
||||
}
|
||||
dirs = append(dirs, path)
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, m := range dirs {
|
||||
if err := a.unmount(m); err != nil {
|
||||
logrus.Debugf("aufs error unmounting %s: %s", m, err)
|
||||
}
|
||||
}
|
||||
return mountpk.Unmount(a.root)
|
||||
}
|
||||
|
||||
func (a *Driver) aufsMount(ro []string, rw, target string, options graphdriver.MountOpts) (err error) {
|
||||
defer func() {
|
||||
if err != nil {
|
||||
Unmount(target)
|
||||
}
|
||||
}()
|
||||
|
||||
// Mount options are clipped to page size(4096 bytes). If there are more
|
||||
// layers then these are remounted individually using append.
|
||||
|
||||
offset := 54
|
||||
if useDirperm() {
|
||||
offset += len(",dirperm1")
|
||||
}
|
||||
b := make([]byte, unix.Getpagesize()-len(options.MountLabel)-offset) // room for xino & mountLabel
|
||||
bp := copy(b, fmt.Sprintf("br:%s=rw", rw))
|
||||
|
||||
index := 0
|
||||
for ; index < len(ro); index++ {
|
||||
layer := fmt.Sprintf(":%s=ro+wh", ro[index])
|
||||
if bp+len(layer) > len(b) {
|
||||
break
|
||||
}
|
||||
bp += copy(b[bp:], layer)
|
||||
}
|
||||
|
||||
opts := "dio,xino=/dev/shm/aufs.xino"
|
||||
mountOptions := a.mountOptions
|
||||
if len(options.Options) > 0 {
|
||||
mountOptions = strings.Join(options.Options, ",")
|
||||
}
|
||||
if mountOptions != "" {
|
||||
opts += fmt.Sprintf(",%s", mountOptions)
|
||||
}
|
||||
|
||||
if useDirperm() {
|
||||
opts += ",dirperm1"
|
||||
}
|
||||
data := label.FormatMountLabel(fmt.Sprintf("%s,%s", string(b[:bp]), opts), options.MountLabel)
|
||||
if err = mount("none", target, "aufs", 0, data); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for ; index < len(ro); index++ {
|
||||
layer := fmt.Sprintf(":%s=ro+wh", ro[index])
|
||||
data := label.FormatMountLabel(fmt.Sprintf("append%s", layer), options.MountLabel)
|
||||
if err = mount("none", target, "aufs", unix.MS_REMOUNT, data); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// useDirperm checks dirperm1 mount option can be used with the current
|
||||
// version of aufs.
|
||||
func useDirperm() bool {
|
||||
enableDirpermLock.Do(func() {
|
||||
base, err := os.MkdirTemp("", "storage-aufs-base")
|
||||
if err != nil {
|
||||
logrus.Errorf("Checking dirperm1: %v", err)
|
||||
return
|
||||
}
|
||||
defer os.RemoveAll(base)
|
||||
|
||||
union, err := os.MkdirTemp("", "storage-aufs-union")
|
||||
if err != nil {
|
||||
logrus.Errorf("Checking dirperm1: %v", err)
|
||||
return
|
||||
}
|
||||
defer os.RemoveAll(union)
|
||||
|
||||
opts := fmt.Sprintf("br:%s,dirperm1,xino=/dev/shm/aufs.xino", base)
|
||||
if err := mount("none", union, "aufs", 0, opts); err != nil {
|
||||
return
|
||||
}
|
||||
enableDirperm = true
|
||||
if err := Unmount(union); err != nil {
|
||||
logrus.Errorf("Checking dirperm1: failed to unmount %v", err)
|
||||
}
|
||||
})
|
||||
return enableDirperm
|
||||
}
|
||||
|
||||
// UpdateLayerIDMap updates ID mappings in a layer from matching the ones
|
||||
// specified by toContainer to those specified by toHost.
|
||||
func (a *Driver) UpdateLayerIDMap(id string, toContainer, toHost *idtools.IDMappings, mountLabel string) error {
|
||||
return fmt.Errorf("aufs doesn't support changing ID mappings")
|
||||
}
|
||||
|
||||
// SupportsShifting tells whether the driver support shifting of the UIDs/GIDs in an userNS
|
||||
func (a *Driver) SupportsShifting() bool {
|
||||
return false
|
||||
}
|
||||
64
vendor/github.com/containers/storage/drivers/aufs/dirs.go
generated
vendored
Normal file
64
vendor/github.com/containers/storage/drivers/aufs/dirs.go
generated
vendored
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package aufs
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"os"
|
||||
"path"
|
||||
)
|
||||
|
||||
// Return all the directories
|
||||
func loadIds(root string) ([]string, error) {
|
||||
dirs, err := os.ReadDir(root)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := []string{}
|
||||
for _, d := range dirs {
|
||||
if !d.IsDir() {
|
||||
out = append(out, d.Name())
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Read the layers file for the current id and return all the
|
||||
// layers represented by new lines in the file
|
||||
//
|
||||
// If there are no lines in the file then the id has no parent
|
||||
// and an empty slice is returned.
|
||||
func getParentIDs(root, id string) ([]string, error) {
|
||||
f, err := os.Open(path.Join(root, "layers", id))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
out := []string{}
|
||||
s := bufio.NewScanner(f)
|
||||
|
||||
for s.Scan() {
|
||||
if t := s.Text(); t != "" {
|
||||
out = append(out, s.Text())
|
||||
}
|
||||
}
|
||||
return out, s.Err()
|
||||
}
|
||||
|
||||
func (a *Driver) getMountpoint(id string) string {
|
||||
return path.Join(a.mntPath(), id)
|
||||
}
|
||||
|
||||
func (a *Driver) mntPath() string {
|
||||
return path.Join(a.rootPath(), "mnt")
|
||||
}
|
||||
|
||||
func (a *Driver) getDiffPath(id string) string {
|
||||
return path.Join(a.diffPath(), id)
|
||||
}
|
||||
|
||||
func (a *Driver) diffPath() string {
|
||||
return path.Join(a.rootPath(), "diff")
|
||||
}
|
||||
22
vendor/github.com/containers/storage/drivers/aufs/mount.go
generated
vendored
Normal file
22
vendor/github.com/containers/storage/drivers/aufs/mount.go
generated
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package aufs
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// Unmount the target specified.
|
||||
func Unmount(target string) error {
|
||||
if err := exec.Command("auplink", target, "flush").Run(); err != nil {
|
||||
logrus.Warnf("Couldn't run auplink before unmount %s: %s", target, err)
|
||||
}
|
||||
if err := unix.Unmount(target, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
7
vendor/github.com/containers/storage/drivers/aufs/mount_linux.go
generated
vendored
Normal file
7
vendor/github.com/containers/storage/drivers/aufs/mount_linux.go
generated
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
package aufs
|
||||
|
||||
import "golang.org/x/sys/unix"
|
||||
|
||||
func mount(source string, target string, fstype string, flags uintptr, data string) error {
|
||||
return unix.Mount(source, target, fstype, flags, data)
|
||||
}
|
||||
695
vendor/github.com/containers/storage/drivers/btrfs/btrfs.go
generated
vendored
Normal file
695
vendor/github.com/containers/storage/drivers/btrfs/btrfs.go
generated
vendored
Normal file
|
|
@ -0,0 +1,695 @@
|
|||
//go:build linux && cgo
|
||||
// +build linux,cgo
|
||||
|
||||
package btrfs
|
||||
|
||||
/*
|
||||
#include <stdlib.h>
|
||||
#include <dirent.h>
|
||||
|
||||
// keep struct field name compatible with btrfs-progs < 6.1.
|
||||
#define max_referenced max_rfer
|
||||
#include <btrfs/ioctl.h>
|
||||
#include <btrfs/ctree.h>
|
||||
|
||||
static void set_name_btrfs_ioctl_vol_args_v2(struct btrfs_ioctl_vol_args_v2* btrfs_struct, const char* value) {
|
||||
snprintf(btrfs_struct->name, BTRFS_SUBVOL_NAME_MAX, "%s", value);
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"math"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"unsafe"
|
||||
|
||||
graphdriver "github.com/containers/storage/drivers"
|
||||
"github.com/containers/storage/pkg/directory"
|
||||
"github.com/containers/storage/pkg/idtools"
|
||||
"github.com/containers/storage/pkg/mount"
|
||||
"github.com/containers/storage/pkg/parsers"
|
||||
"github.com/containers/storage/pkg/system"
|
||||
"github.com/docker/go-units"
|
||||
"github.com/opencontainers/selinux/go-selinux/label"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const defaultPerms = os.FileMode(0o555)
|
||||
|
||||
func init() {
|
||||
graphdriver.MustRegister("btrfs", Init)
|
||||
}
|
||||
|
||||
type btrfsOptions struct {
|
||||
minSpace uint64
|
||||
size uint64
|
||||
}
|
||||
|
||||
// Init returns a new BTRFS driver.
|
||||
// An error is returned if BTRFS is not supported.
|
||||
func Init(home string, options graphdriver.Options) (graphdriver.Driver, error) {
|
||||
fsMagic, err := graphdriver.GetFSMagic(home)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if fsMagic != graphdriver.FsMagicBtrfs {
|
||||
return nil, fmt.Errorf("%q is not on a btrfs filesystem: %w", home, graphdriver.ErrPrerequisites)
|
||||
}
|
||||
|
||||
rootUID, rootGID, err := idtools.GetRootUIDGID(options.UIDMaps, options.GIDMaps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := idtools.MkdirAllAs(filepath.Join(home, "subvolumes"), 0o700, rootUID, rootGID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := mount.MakePrivate(home); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
opt, userDiskQuota, err := parseOptions(options.DriverOptions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
driver := &Driver{
|
||||
home: home,
|
||||
uidMaps: options.UIDMaps,
|
||||
gidMaps: options.GIDMaps,
|
||||
options: opt,
|
||||
}
|
||||
|
||||
if userDiskQuota {
|
||||
if err := driver.enableQuota(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return graphdriver.NewNaiveDiffDriver(driver, graphdriver.NewNaiveLayerIDMapUpdater(driver)), nil
|
||||
}
|
||||
|
||||
func parseOptions(opt []string) (btrfsOptions, bool, error) {
|
||||
var options btrfsOptions
|
||||
userDiskQuota := false
|
||||
for _, option := range opt {
|
||||
key, val, err := parsers.ParseKeyValueOpt(option)
|
||||
if err != nil {
|
||||
return options, userDiskQuota, err
|
||||
}
|
||||
key = strings.ToLower(key)
|
||||
switch key {
|
||||
case "btrfs.min_space":
|
||||
minSpace, err := units.RAMInBytes(val)
|
||||
if err != nil {
|
||||
return options, userDiskQuota, err
|
||||
}
|
||||
userDiskQuota = true
|
||||
options.minSpace = uint64(minSpace)
|
||||
case "btrfs.mountopt":
|
||||
return options, userDiskQuota, fmt.Errorf("btrfs driver does not support mount options")
|
||||
default:
|
||||
return options, userDiskQuota, fmt.Errorf("unknown option %s (%q)", key, option)
|
||||
}
|
||||
}
|
||||
return options, userDiskQuota, nil
|
||||
}
|
||||
|
||||
// Driver contains information about the filesystem mounted.
|
||||
type Driver struct {
|
||||
// root of the file system
|
||||
home string
|
||||
uidMaps []idtools.IDMap
|
||||
gidMaps []idtools.IDMap
|
||||
options btrfsOptions
|
||||
quotaEnabled bool
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
// String prints the name of the driver (btrfs).
|
||||
func (d *Driver) String() string {
|
||||
return "btrfs"
|
||||
}
|
||||
|
||||
// Status returns current driver information in a two dimensional string array.
|
||||
// Output contains "Build Version" and "Library Version" of the btrfs libraries used.
|
||||
// Version information can be used to check compatibility with your kernel.
|
||||
func (d *Driver) Status() [][2]string {
|
||||
status := [][2]string{}
|
||||
if bv := btrfsBuildVersion(); bv != "-" {
|
||||
status = append(status, [2]string{"Build Version", bv})
|
||||
}
|
||||
if lv := btrfsLibVersion(); lv != -1 {
|
||||
status = append(status, [2]string{"Library Version", fmt.Sprintf("%d", lv)})
|
||||
}
|
||||
return status
|
||||
}
|
||||
|
||||
// Metadata returns empty metadata for this driver.
|
||||
func (d *Driver) Metadata(id string) (map[string]string, error) {
|
||||
return nil, nil //nolint: nilnil
|
||||
}
|
||||
|
||||
// Cleanup unmounts the home directory.
|
||||
func (d *Driver) Cleanup() error {
|
||||
return mount.Unmount(d.home)
|
||||
}
|
||||
|
||||
func free(p *C.char) {
|
||||
C.free(unsafe.Pointer(p))
|
||||
}
|
||||
|
||||
func openDir(path string) (*C.DIR, error) {
|
||||
Cpath := C.CString(path)
|
||||
defer free(Cpath)
|
||||
|
||||
dir := C.opendir(Cpath)
|
||||
if dir == nil {
|
||||
return nil, fmt.Errorf("can't open dir %s", path)
|
||||
}
|
||||
return dir, nil
|
||||
}
|
||||
|
||||
func closeDir(dir *C.DIR) {
|
||||
if dir != nil {
|
||||
C.closedir(dir)
|
||||
}
|
||||
}
|
||||
|
||||
func getDirFd(dir *C.DIR) uintptr {
|
||||
return uintptr(C.dirfd(dir))
|
||||
}
|
||||
|
||||
func subvolCreate(path, name string) error {
|
||||
dir, err := openDir(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer closeDir(dir)
|
||||
|
||||
var args C.struct_btrfs_ioctl_vol_args
|
||||
for i, c := range []byte(name) {
|
||||
args.name[i] = C.char(c)
|
||||
}
|
||||
|
||||
_, _, errno := unix.Syscall(unix.SYS_IOCTL, getDirFd(dir), C.BTRFS_IOC_SUBVOL_CREATE,
|
||||
uintptr(unsafe.Pointer(&args)))
|
||||
if errno != 0 {
|
||||
return fmt.Errorf("failed to create btrfs subvolume: %w", errno)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func subvolSnapshot(src, dest, name string) error {
|
||||
srcDir, err := openDir(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer closeDir(srcDir)
|
||||
|
||||
destDir, err := openDir(dest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer closeDir(destDir)
|
||||
|
||||
var args C.struct_btrfs_ioctl_vol_args_v2
|
||||
args.fd = C.__s64(getDirFd(srcDir))
|
||||
|
||||
cs := C.CString(name)
|
||||
C.set_name_btrfs_ioctl_vol_args_v2(&args, cs)
|
||||
C.free(unsafe.Pointer(cs))
|
||||
|
||||
_, _, errno := unix.Syscall(unix.SYS_IOCTL, getDirFd(destDir), C.BTRFS_IOC_SNAP_CREATE_V2,
|
||||
uintptr(unsafe.Pointer(&args)))
|
||||
if errno != 0 {
|
||||
return fmt.Errorf("failed to create btrfs snapshot: %w", errno)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func isSubvolume(p string) (bool, error) {
|
||||
var bufStat unix.Stat_t
|
||||
if err := unix.Lstat(p, &bufStat); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// return true if it is a btrfs subvolume
|
||||
return bufStat.Ino == C.BTRFS_FIRST_FREE_OBJECTID, nil
|
||||
}
|
||||
|
||||
func subvolDelete(dirpath, name string, quotaEnabled bool) error {
|
||||
dir, err := openDir(dirpath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer closeDir(dir)
|
||||
fullPath := path.Join(dirpath, name)
|
||||
|
||||
var args C.struct_btrfs_ioctl_vol_args
|
||||
|
||||
// walk the btrfs subvolumes
|
||||
walkSubvolumes := func(p string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) && p != fullPath {
|
||||
// missing most likely because the path was a subvolume that got removed in the previous iteration
|
||||
// since it's gone anyway, we don't care
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("walking subvolumes: %w", err)
|
||||
}
|
||||
// we want to check children only so skip itself
|
||||
// it will be removed after the filepath walk anyways
|
||||
if d.IsDir() && p != fullPath {
|
||||
sv, err := isSubvolume(p)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to test if %s is a btrfs subvolume: %w", p, err)
|
||||
}
|
||||
if sv {
|
||||
if err := subvolDelete(path.Dir(p), d.Name(), quotaEnabled); err != nil {
|
||||
return fmt.Errorf("failed to destroy btrfs child subvolume (%s) of parent (%s): %w", p, dirpath, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if err := filepath.WalkDir(path.Join(dirpath, name), walkSubvolumes); err != nil {
|
||||
return fmt.Errorf("recursively walking subvolumes for %s failed: %w", dirpath, err)
|
||||
}
|
||||
|
||||
if quotaEnabled {
|
||||
if qgroupid, err := subvolLookupQgroup(fullPath); err == nil {
|
||||
var args C.struct_btrfs_ioctl_qgroup_create_args
|
||||
args.qgroupid = C.__u64(qgroupid)
|
||||
|
||||
_, _, errno := unix.Syscall(unix.SYS_IOCTL, getDirFd(dir), C.BTRFS_IOC_QGROUP_CREATE,
|
||||
uintptr(unsafe.Pointer(&args)))
|
||||
if errno != 0 {
|
||||
logrus.Errorf("Failed to delete btrfs qgroup %v for %s: %v", qgroupid, fullPath, errno.Error())
|
||||
}
|
||||
} else {
|
||||
logrus.Errorf("Failed to lookup btrfs qgroup for %s: %v", fullPath, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// all subvolumes have been removed
|
||||
// now remove the one originally passed in
|
||||
for i, c := range []byte(name) {
|
||||
args.name[i] = C.char(c)
|
||||
}
|
||||
_, _, errno := unix.Syscall(unix.SYS_IOCTL, getDirFd(dir), C.BTRFS_IOC_SNAP_DESTROY,
|
||||
uintptr(unsafe.Pointer(&args)))
|
||||
if errno != 0 {
|
||||
return fmt.Errorf("failed to destroy btrfs snapshot %s for %s: %w", dirpath, name, errno)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Driver) updateQuotaStatus() {
|
||||
d.once.Do(func() {
|
||||
if !d.quotaEnabled {
|
||||
// In case quotaEnabled is not set, check qgroup and update quotaEnabled as needed
|
||||
if err := qgroupStatus(d.home); err != nil {
|
||||
// quota is still not enabled
|
||||
return
|
||||
}
|
||||
d.quotaEnabled = true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (d *Driver) enableQuota() error {
|
||||
d.updateQuotaStatus()
|
||||
|
||||
if d.quotaEnabled {
|
||||
return nil
|
||||
}
|
||||
|
||||
dir, err := openDir(d.home)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer closeDir(dir)
|
||||
|
||||
var args C.struct_btrfs_ioctl_quota_ctl_args
|
||||
args.cmd = C.BTRFS_QUOTA_CTL_ENABLE
|
||||
_, _, errno := unix.Syscall(unix.SYS_IOCTL, getDirFd(dir), C.BTRFS_IOC_QUOTA_CTL,
|
||||
uintptr(unsafe.Pointer(&args)))
|
||||
if errno != 0 {
|
||||
return fmt.Errorf("failed to enable btrfs quota for %s: %w", dir, errno)
|
||||
}
|
||||
|
||||
d.quotaEnabled = true
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Driver) subvolRescanQuota() error {
|
||||
d.updateQuotaStatus()
|
||||
|
||||
if !d.quotaEnabled {
|
||||
return nil
|
||||
}
|
||||
|
||||
dir, err := openDir(d.home)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer closeDir(dir)
|
||||
|
||||
var args C.struct_btrfs_ioctl_quota_rescan_args
|
||||
_, _, errno := unix.Syscall(unix.SYS_IOCTL, getDirFd(dir), C.BTRFS_IOC_QUOTA_RESCAN_WAIT,
|
||||
uintptr(unsafe.Pointer(&args)))
|
||||
if errno != 0 {
|
||||
return fmt.Errorf("failed to rescan btrfs quota for %s: %w", dir, errno)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func subvolLimitQgroup(path string, size uint64) error {
|
||||
dir, err := openDir(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer closeDir(dir)
|
||||
|
||||
var args C.struct_btrfs_ioctl_qgroup_limit_args
|
||||
args.lim.max_rfer = C.__u64(size)
|
||||
args.lim.flags = C.BTRFS_QGROUP_LIMIT_MAX_RFER
|
||||
_, _, errno := unix.Syscall(unix.SYS_IOCTL, getDirFd(dir), C.BTRFS_IOC_QGROUP_LIMIT,
|
||||
uintptr(unsafe.Pointer(&args)))
|
||||
if errno != 0 {
|
||||
return fmt.Errorf("failed to limit qgroup for %s: %w", dir, errno)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// qgroupStatus performs a BTRFS_IOC_TREE_SEARCH on the root path
|
||||
// with search key of BTRFS_QGROUP_STATUS_KEY.
|
||||
// In case qgroup is enabled, the returned key type will match BTRFS_QGROUP_STATUS_KEY.
|
||||
// For more details please see https://github.com/kdave/btrfs-progs/blob/v4.9/qgroup.c#L1035
|
||||
func qgroupStatus(path string) error {
|
||||
dir, err := openDir(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer closeDir(dir)
|
||||
|
||||
var args C.struct_btrfs_ioctl_search_args
|
||||
args.key.tree_id = C.BTRFS_QUOTA_TREE_OBJECTID
|
||||
args.key.min_type = C.BTRFS_QGROUP_STATUS_KEY
|
||||
args.key.max_type = C.BTRFS_QGROUP_STATUS_KEY
|
||||
args.key.max_objectid = C.__u64(math.MaxUint64)
|
||||
args.key.max_offset = C.__u64(math.MaxUint64)
|
||||
args.key.max_transid = C.__u64(math.MaxUint64)
|
||||
args.key.nr_items = 4096
|
||||
|
||||
_, _, errno := unix.Syscall(unix.SYS_IOCTL, getDirFd(dir), C.BTRFS_IOC_TREE_SEARCH,
|
||||
uintptr(unsafe.Pointer(&args)))
|
||||
if errno != 0 {
|
||||
return fmt.Errorf("failed to search qgroup for %s: %w", path, errno)
|
||||
}
|
||||
sh := (*C.struct_btrfs_ioctl_search_header)(unsafe.Pointer(&args.buf))
|
||||
if sh._type != C.BTRFS_QGROUP_STATUS_KEY {
|
||||
return fmt.Errorf("invalid qgroup search header type for %s: %v", path, sh._type)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func subvolLookupQgroup(path string) (uint64, error) {
|
||||
dir, err := openDir(path)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer closeDir(dir)
|
||||
|
||||
var args C.struct_btrfs_ioctl_ino_lookup_args
|
||||
args.objectid = C.BTRFS_FIRST_FREE_OBJECTID
|
||||
|
||||
_, _, errno := unix.Syscall(unix.SYS_IOCTL, getDirFd(dir), C.BTRFS_IOC_INO_LOOKUP,
|
||||
uintptr(unsafe.Pointer(&args)))
|
||||
if errno != 0 {
|
||||
return 0, fmt.Errorf("failed to lookup qgroup for %s: %w", dir, errno)
|
||||
}
|
||||
if args.treeid == 0 {
|
||||
return 0, fmt.Errorf("invalid qgroup id for %s: 0", dir)
|
||||
}
|
||||
|
||||
return uint64(args.treeid), nil
|
||||
}
|
||||
|
||||
func (d *Driver) subvolumesDir() string {
|
||||
return path.Join(d.home, "subvolumes")
|
||||
}
|
||||
|
||||
func (d *Driver) subvolumesDirID(id string) string {
|
||||
return path.Join(d.subvolumesDir(), id)
|
||||
}
|
||||
|
||||
func (d *Driver) quotasDir() string {
|
||||
return path.Join(d.home, "quotas")
|
||||
}
|
||||
|
||||
func (d *Driver) quotasDirID(id string) string {
|
||||
return path.Join(d.quotasDir(), id)
|
||||
}
|
||||
|
||||
// CreateFromTemplate creates a layer with the same contents and parent as another layer.
|
||||
func (d *Driver) CreateFromTemplate(id, template string, templateIDMappings *idtools.IDMappings, parent string, parentIDMappings *idtools.IDMappings, opts *graphdriver.CreateOpts, readWrite bool) error {
|
||||
return d.Create(id, template, opts)
|
||||
}
|
||||
|
||||
// CreateReadWrite creates a layer that is writable for use as a container
|
||||
// file system.
|
||||
func (d *Driver) CreateReadWrite(id, parent string, opts *graphdriver.CreateOpts) error {
|
||||
return d.Create(id, parent, opts)
|
||||
}
|
||||
|
||||
// Create the filesystem with given id.
|
||||
func (d *Driver) Create(id, parent string, opts *graphdriver.CreateOpts) error {
|
||||
quotas := d.quotasDir()
|
||||
subvolumes := d.subvolumesDir()
|
||||
rootUID, rootGID, err := idtools.GetRootUIDGID(d.uidMaps, d.gidMaps)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := idtools.MkdirAllAs(subvolumes, 0o700, rootUID, rootGID); err != nil {
|
||||
return err
|
||||
}
|
||||
if parent == "" {
|
||||
if err := subvolCreate(subvolumes, id); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.Chmod(path.Join(subvolumes, id), defaultPerms); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
parentDir := d.subvolumesDirID(parent)
|
||||
st, err := os.Stat(parentDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !st.IsDir() {
|
||||
return fmt.Errorf("%s: not a directory", parentDir)
|
||||
}
|
||||
if err := subvolSnapshot(parentDir, subvolumes, id); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
var storageOpt map[string]string
|
||||
if opts != nil {
|
||||
storageOpt = opts.StorageOpt
|
||||
}
|
||||
|
||||
if _, ok := storageOpt["size"]; ok {
|
||||
driver := &Driver{}
|
||||
if err := d.parseStorageOpt(storageOpt, driver); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := d.setStorageSize(path.Join(subvolumes, id), driver); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := idtools.MkdirAllAs(quotas, 0o700, rootUID, rootGID); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.WriteFile(path.Join(quotas, id), []byte(fmt.Sprint(driver.options.size)), 0o644); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// if we have a remapped root (user namespaces enabled), change the created snapshot
|
||||
// dir ownership to match
|
||||
if rootUID != 0 || rootGID != 0 {
|
||||
if err := os.Chown(path.Join(subvolumes, id), rootUID, rootGID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
mountLabel := ""
|
||||
if opts != nil {
|
||||
mountLabel = opts.MountLabel
|
||||
}
|
||||
|
||||
return label.Relabel(path.Join(subvolumes, id), mountLabel, false)
|
||||
}
|
||||
|
||||
// Parse btrfs storage options
|
||||
func (d *Driver) parseStorageOpt(storageOpt map[string]string, driver *Driver) error {
|
||||
// Read size to change the subvolume disk quota per container
|
||||
for key, val := range storageOpt {
|
||||
key := strings.ToLower(key)
|
||||
switch key {
|
||||
case "size":
|
||||
size, err := units.RAMInBytes(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
driver.options.size = uint64(size)
|
||||
default:
|
||||
return fmt.Errorf("unknown option %s (%q)", key, storageOpt)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Set btrfs storage size
|
||||
func (d *Driver) setStorageSize(dir string, driver *Driver) error {
|
||||
if driver.options.size <= 0 {
|
||||
return fmt.Errorf("btrfs: invalid storage size: %s", units.HumanSize(float64(driver.options.size)))
|
||||
}
|
||||
if d.options.minSpace > 0 && driver.options.size < d.options.minSpace {
|
||||
return fmt.Errorf("btrfs: storage size cannot be less than %s", units.HumanSize(float64(d.options.minSpace)))
|
||||
}
|
||||
|
||||
if err := d.enableQuota(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := subvolLimitQgroup(dir, driver.options.size); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove the filesystem with given id.
|
||||
func (d *Driver) Remove(id string) error {
|
||||
dir := d.subvolumesDirID(id)
|
||||
if _, err := os.Stat(dir); err != nil {
|
||||
return err
|
||||
}
|
||||
quotasDir := d.quotasDirID(id)
|
||||
if _, err := os.Stat(quotasDir); err == nil {
|
||||
if err := os.Remove(quotasDir); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
// Call updateQuotaStatus() to invoke status update
|
||||
d.updateQuotaStatus()
|
||||
|
||||
if err := subvolDelete(d.subvolumesDir(), id, d.quotaEnabled); err != nil {
|
||||
if d.quotaEnabled {
|
||||
return err
|
||||
}
|
||||
// If quota is not enabled, fallback to rmdir syscall to delete subvolumes.
|
||||
// This would allow unprivileged user to delete their owned subvolumes
|
||||
// in kernel >= 4.18 without user_subvol_rm_alowed mount option.
|
||||
}
|
||||
if err := system.EnsureRemoveAll(dir); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.subvolRescanQuota(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get the requested filesystem id.
|
||||
func (d *Driver) Get(id string, options graphdriver.MountOpts) (string, error) {
|
||||
dir := d.subvolumesDirID(id)
|
||||
st, err := os.Stat(dir)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
for _, opt := range options.Options {
|
||||
if opt == "ro" {
|
||||
// ignore "ro" option
|
||||
continue
|
||||
}
|
||||
return "", fmt.Errorf("btrfs driver does not support mount options")
|
||||
}
|
||||
if !st.IsDir() {
|
||||
return "", fmt.Errorf("%s: not a directory", dir)
|
||||
}
|
||||
|
||||
if quota, err := os.ReadFile(d.quotasDirID(id)); err == nil {
|
||||
if size, err := strconv.ParseUint(string(quota), 10, 64); err == nil && size >= d.options.minSpace {
|
||||
if err := d.enableQuota(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := subvolLimitQgroup(dir, size); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return dir, nil
|
||||
}
|
||||
|
||||
// Put is not implemented for BTRFS as there is no cleanup required for the id.
|
||||
func (d *Driver) Put(id string) error {
|
||||
// Get() creates no runtime resources (like e.g. mounts)
|
||||
// so this doesn't need to do anything.
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReadWriteDiskUsage returns the disk usage of the writable directory for the ID.
|
||||
// For BTRFS, it queries the subvolumes path for this ID.
|
||||
func (d *Driver) ReadWriteDiskUsage(id string) (*directory.DiskUsage, error) {
|
||||
return directory.Usage(d.subvolumesDirID(id))
|
||||
}
|
||||
|
||||
// Exists checks if the id exists in the filesystem.
|
||||
func (d *Driver) Exists(id string) bool {
|
||||
dir := d.subvolumesDirID(id)
|
||||
_, err := os.Stat(dir)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// List all of the layers known to the driver.
|
||||
func (d *Driver) ListLayers() ([]string, error) {
|
||||
entries, err := os.ReadDir(d.subvolumesDir())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
results := make([]string, 0, len(entries))
|
||||
for _, entry := range entries {
|
||||
if !entry.IsDir() {
|
||||
continue
|
||||
}
|
||||
results = append(results, entry.Name())
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// AdditionalImageStores returns additional image stores supported by the driver
|
||||
func (d *Driver) AdditionalImageStores() []string {
|
||||
return nil
|
||||
}
|
||||
4
vendor/github.com/containers/storage/drivers/btrfs/dummy_unsupported.go
generated
vendored
Normal file
4
vendor/github.com/containers/storage/drivers/btrfs/dummy_unsupported.go
generated
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
//go:build !linux || !cgo
|
||||
// +build !linux !cgo
|
||||
|
||||
package btrfs
|
||||
27
vendor/github.com/containers/storage/drivers/btrfs/version.go
generated
vendored
Normal file
27
vendor/github.com/containers/storage/drivers/btrfs/version.go
generated
vendored
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
//go:build linux && !btrfs_noversion && cgo
|
||||
// +build linux,!btrfs_noversion,cgo
|
||||
|
||||
package btrfs
|
||||
|
||||
/*
|
||||
#include <btrfs/version.h>
|
||||
|
||||
// around version 3.16, they did not define lib version yet
|
||||
#ifndef BTRFS_LIB_VERSION
|
||||
#define BTRFS_LIB_VERSION -1
|
||||
#endif
|
||||
|
||||
// upstream had removed it, but now it will be coming back
|
||||
#ifndef BTRFS_BUILD_VERSION
|
||||
#define BTRFS_BUILD_VERSION "-"
|
||||
#endif
|
||||
*/
|
||||
import "C"
|
||||
|
||||
func btrfsBuildVersion() string {
|
||||
return string(C.BTRFS_BUILD_VERSION)
|
||||
}
|
||||
|
||||
func btrfsLibVersion() int {
|
||||
return int(C.BTRFS_LIB_VERSION)
|
||||
}
|
||||
15
vendor/github.com/containers/storage/drivers/btrfs/version_none.go
generated
vendored
Normal file
15
vendor/github.com/containers/storage/drivers/btrfs/version_none.go
generated
vendored
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
//go:build linux && btrfs_noversion && cgo
|
||||
// +build linux,btrfs_noversion,cgo
|
||||
|
||||
package btrfs
|
||||
|
||||
// TODO(vbatts) remove this work-around once supported linux distros are on
|
||||
// btrfs utilities of >= 3.16.1
|
||||
|
||||
func btrfsBuildVersion() string {
|
||||
return "-"
|
||||
}
|
||||
|
||||
func btrfsLibVersion() int {
|
||||
return -1
|
||||
}
|
||||
135
vendor/github.com/containers/storage/drivers/chown.go
generated
vendored
Normal file
135
vendor/github.com/containers/storage/drivers/chown.go
generated
vendored
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
package graphdriver
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/containers/storage/pkg/idtools"
|
||||
"github.com/containers/storage/pkg/reexec"
|
||||
"github.com/opencontainers/selinux/pkg/pwalk"
|
||||
)
|
||||
|
||||
const (
|
||||
chownByMapsCmd = "storage-chown-by-maps"
|
||||
)
|
||||
|
||||
func init() {
|
||||
reexec.Register(chownByMapsCmd, chownByMapsMain)
|
||||
}
|
||||
|
||||
func chownByMapsMain() {
|
||||
if len(os.Args) < 2 {
|
||||
fmt.Fprintf(os.Stderr, "requires mapping configuration on stdin and directory path")
|
||||
os.Exit(1)
|
||||
}
|
||||
// Read and decode our configuration.
|
||||
discreteMaps := [4][]idtools.IDMap{}
|
||||
config := bytes.Buffer{}
|
||||
if _, err := config.ReadFrom(os.Stdin); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error reading configuration: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if err := json.Unmarshal(config.Bytes(), &discreteMaps); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error decoding configuration: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
// Try to chroot. This may not be possible, and on some systems that
|
||||
// means we just Chdir() to the directory, so from here on we should be
|
||||
// using relative paths.
|
||||
if err := chrootOrChdir(os.Args[1]); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error chrooting to %q: %v", os.Args[1], err)
|
||||
os.Exit(1)
|
||||
}
|
||||
// Build the mapping objects.
|
||||
toContainer := idtools.NewIDMappingsFromMaps(discreteMaps[0], discreteMaps[1])
|
||||
if len(toContainer.UIDs()) == 0 && len(toContainer.GIDs()) == 0 {
|
||||
toContainer = nil
|
||||
}
|
||||
toHost := idtools.NewIDMappingsFromMaps(discreteMaps[2], discreteMaps[3])
|
||||
if len(toHost.UIDs()) == 0 && len(toHost.GIDs()) == 0 {
|
||||
toHost = nil
|
||||
}
|
||||
|
||||
chowner := newLChowner()
|
||||
|
||||
chown := func(path string, info os.FileInfo, _ error) error {
|
||||
if path == "." {
|
||||
return nil
|
||||
}
|
||||
return chowner.LChown(path, info, toHost, toContainer)
|
||||
}
|
||||
if err := pwalk.Walk(".", chown); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error during chown: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
// ChownPathByMaps walks the filesystem tree, changing the ownership
|
||||
// information using the toContainer and toHost mappings, using them to replace
|
||||
// on-disk owner UIDs and GIDs which are "host" values in the first map with
|
||||
// UIDs and GIDs for "host" values from the second map which correspond to the
|
||||
// same "container" IDs.
|
||||
func ChownPathByMaps(path string, toContainer, toHost *idtools.IDMappings) error {
|
||||
if toContainer == nil {
|
||||
toContainer = &idtools.IDMappings{}
|
||||
}
|
||||
if toHost == nil {
|
||||
toHost = &idtools.IDMappings{}
|
||||
}
|
||||
|
||||
config, err := json.Marshal([4][]idtools.IDMap{toContainer.UIDs(), toContainer.GIDs(), toHost.UIDs(), toHost.GIDs()})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd := reexec.Command(chownByMapsCmd, path)
|
||||
cmd.Stdin = bytes.NewReader(config)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if len(output) > 0 && err != nil {
|
||||
return fmt.Errorf("%s: %w", string(output), err)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(output) > 0 {
|
||||
return errors.New(string(output))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type naiveLayerIDMapUpdater struct {
|
||||
ProtoDriver
|
||||
}
|
||||
|
||||
// NewNaiveLayerIDMapUpdater wraps the ProtoDriver in a LayerIDMapUpdater that
|
||||
// uses ChownPathByMaps to update the ownerships in a layer's filesystem tree.
|
||||
func NewNaiveLayerIDMapUpdater(driver ProtoDriver) LayerIDMapUpdater {
|
||||
return &naiveLayerIDMapUpdater{ProtoDriver: driver}
|
||||
}
|
||||
|
||||
// UpdateLayerIDMap walks the layer's filesystem tree, changing the ownership
|
||||
// information using the toContainer and toHost mappings, using them to replace
|
||||
// on-disk owner UIDs and GIDs which are "host" values in the first map with
|
||||
// UIDs and GIDs for "host" values from the second map which correspond to the
|
||||
// same "container" IDs.
|
||||
func (n *naiveLayerIDMapUpdater) UpdateLayerIDMap(id string, toContainer, toHost *idtools.IDMappings, mountLabel string) (retErr error) {
|
||||
driver := n.ProtoDriver
|
||||
options := MountOpts{
|
||||
MountLabel: mountLabel,
|
||||
}
|
||||
layerFs, err := driver.Get(id, options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer driverPut(driver, id, &retErr)
|
||||
|
||||
return ChownPathByMaps(layerFs, toContainer, toHost)
|
||||
}
|
||||
|
||||
// SupportsShifting tells whether the driver support shifting of the UIDs/GIDs in an userNS
|
||||
func (n *naiveLayerIDMapUpdater) SupportsShifting() bool {
|
||||
return false
|
||||
}
|
||||
109
vendor/github.com/containers/storage/drivers/chown_darwin.go
generated
vendored
Normal file
109
vendor/github.com/containers/storage/drivers/chown_darwin.go
generated
vendored
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
//go:build darwin
|
||||
// +build darwin
|
||||
|
||||
package graphdriver
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/containers/storage/pkg/idtools"
|
||||
"github.com/containers/storage/pkg/system"
|
||||
)
|
||||
|
||||
type inode struct {
|
||||
Dev uint64
|
||||
Ino uint64
|
||||
}
|
||||
|
||||
type platformChowner struct {
|
||||
mutex sync.Mutex
|
||||
inodes map[inode]bool
|
||||
}
|
||||
|
||||
func newLChowner() *platformChowner {
|
||||
return &platformChowner{
|
||||
inodes: make(map[inode]bool),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *platformChowner) LChown(path string, info os.FileInfo, toHost, toContainer *idtools.IDMappings) error {
|
||||
st, ok := info.Sys().(*syscall.Stat_t)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
i := inode{
|
||||
Dev: uint64(st.Dev),
|
||||
Ino: uint64(st.Ino),
|
||||
}
|
||||
c.mutex.Lock()
|
||||
_, found := c.inodes[i]
|
||||
if !found {
|
||||
c.inodes[i] = true
|
||||
}
|
||||
c.mutex.Unlock()
|
||||
|
||||
if found {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Map an on-disk UID/GID pair from host to container
|
||||
// using the first map, then back to the host using the
|
||||
// second map. Skip that first step if they're 0, to
|
||||
// compensate for cases where a parent layer should
|
||||
// have had a mapped value, but didn't.
|
||||
uid, gid := int(st.Uid), int(st.Gid)
|
||||
if toContainer != nil {
|
||||
pair := idtools.IDPair{
|
||||
UID: uid,
|
||||
GID: gid,
|
||||
}
|
||||
mappedUID, mappedGID, err := toContainer.ToContainer(pair)
|
||||
if err != nil {
|
||||
if (uid != 0) || (gid != 0) {
|
||||
return fmt.Errorf("mapping host ID pair %#v for %q to container: %w", pair, path, err)
|
||||
}
|
||||
mappedUID, mappedGID = uid, gid
|
||||
}
|
||||
uid, gid = mappedUID, mappedGID
|
||||
}
|
||||
if toHost != nil {
|
||||
pair := idtools.IDPair{
|
||||
UID: uid,
|
||||
GID: gid,
|
||||
}
|
||||
mappedPair, err := toHost.ToHostOverflow(pair)
|
||||
if err != nil {
|
||||
return fmt.Errorf("mapping container ID pair %#v for %q to host: %w", pair, path, err)
|
||||
}
|
||||
uid, gid = mappedPair.UID, mappedPair.GID
|
||||
}
|
||||
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 {
|
||||
return fmt.Errorf("%s: %w", os.Args[0], err)
|
||||
}
|
||||
|
||||
// Make the change.
|
||||
if err := system.Lchown(path, uid, gid); err != nil {
|
||||
return fmt.Errorf("%s: %w", os.Args[0], err)
|
||||
}
|
||||
// Restore the SUID and SGID bits if they were originally set.
|
||||
if (info.Mode()&os.ModeSymlink == 0) && info.Mode()&(os.ModeSetuid|os.ModeSetgid) != 0 {
|
||||
if err := system.Chmod(path, info.Mode()); err != nil {
|
||||
return fmt.Errorf("%s: %w", os.Args[0], err)
|
||||
}
|
||||
}
|
||||
if capability != nil {
|
||||
if err := system.Lsetxattr(path, "security.capability", capability, 0); err != nil {
|
||||
return fmt.Errorf("%s: %w", os.Args[0], err)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
127
vendor/github.com/containers/storage/drivers/chown_unix.go
generated
vendored
Normal file
127
vendor/github.com/containers/storage/drivers/chown_unix.go
generated
vendored
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
//go:build !windows && !darwin
|
||||
// +build !windows,!darwin
|
||||
|
||||
package graphdriver
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/containers/storage/pkg/idtools"
|
||||
"github.com/containers/storage/pkg/system"
|
||||
)
|
||||
|
||||
type inode struct {
|
||||
Dev uint64
|
||||
Ino uint64
|
||||
}
|
||||
|
||||
type platformChowner struct {
|
||||
mutex sync.Mutex
|
||||
inodes map[inode]string
|
||||
}
|
||||
|
||||
func newLChowner() *platformChowner {
|
||||
return &platformChowner{
|
||||
inodes: make(map[inode]string),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *platformChowner) LChown(path string, info os.FileInfo, toHost, toContainer *idtools.IDMappings) error {
|
||||
st, ok := info.Sys().(*syscall.Stat_t)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
i := inode{
|
||||
Dev: uint64(st.Dev),
|
||||
Ino: uint64(st.Ino),
|
||||
}
|
||||
|
||||
c.mutex.Lock()
|
||||
|
||||
oldTarget, found := c.inodes[i]
|
||||
if !found {
|
||||
c.inodes[i] = path
|
||||
}
|
||||
|
||||
// If we are dealing with a file with multiple links then keep the lock until the file is
|
||||
// chowned to avoid a race where we link to the old version if the file is copied up.
|
||||
if found || st.Nlink > 1 {
|
||||
defer c.mutex.Unlock()
|
||||
} else {
|
||||
c.mutex.Unlock()
|
||||
}
|
||||
|
||||
if found {
|
||||
// If the dev/inode was already chowned then create a link to the old target instead
|
||||
// of chowning it again. This is necessary when the underlying file system breaks
|
||||
// inodes on copy-up (as it is with overlay with index=off) to maintain the original
|
||||
// link and correct file ownership.
|
||||
|
||||
// The target already exists so remove it before creating the link to the new target.
|
||||
if err := os.Remove(path); err != nil {
|
||||
return err
|
||||
}
|
||||
return os.Link(oldTarget, path)
|
||||
}
|
||||
|
||||
// Map an on-disk UID/GID pair from host to container
|
||||
// using the first map, then back to the host using the
|
||||
// second map. Skip that first step if they're 0, to
|
||||
// compensate for cases where a parent layer should
|
||||
// have had a mapped value, but didn't.
|
||||
uid, gid := int(st.Uid), int(st.Gid)
|
||||
if toContainer != nil {
|
||||
pair := idtools.IDPair{
|
||||
UID: uid,
|
||||
GID: gid,
|
||||
}
|
||||
mappedUID, mappedGID, err := toContainer.ToContainer(pair)
|
||||
if err != nil {
|
||||
if (uid != 0) || (gid != 0) {
|
||||
return fmt.Errorf("mapping host ID pair %#v for %q to container: %w", pair, path, err)
|
||||
}
|
||||
mappedUID, mappedGID = uid, gid
|
||||
}
|
||||
uid, gid = mappedUID, mappedGID
|
||||
}
|
||||
if toHost != nil {
|
||||
pair := idtools.IDPair{
|
||||
UID: uid,
|
||||
GID: gid,
|
||||
}
|
||||
mappedPair, err := toHost.ToHostOverflow(pair)
|
||||
if err != nil {
|
||||
return fmt.Errorf("mapping container ID pair %#v for %q to host: %w", pair, path, err)
|
||||
}
|
||||
uid, gid = mappedPair.UID, mappedPair.GID
|
||||
}
|
||||
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 {
|
||||
return fmt.Errorf("%s: %w", os.Args[0], err)
|
||||
}
|
||||
|
||||
// Make the change.
|
||||
if err := system.Lchown(path, uid, gid); err != nil {
|
||||
return fmt.Errorf("%s: %w", os.Args[0], err)
|
||||
}
|
||||
// Restore the SUID and SGID bits if they were originally set.
|
||||
if (info.Mode()&os.ModeSymlink == 0) && info.Mode()&(os.ModeSetuid|os.ModeSetgid) != 0 {
|
||||
if err := system.Chmod(path, info.Mode()); err != nil {
|
||||
return fmt.Errorf("%s: %w", os.Args[0], err)
|
||||
}
|
||||
}
|
||||
if cap != nil {
|
||||
if err := system.Lsetxattr(path, "security.capability", cap, 0); err != nil {
|
||||
return fmt.Errorf("%s: %w", os.Args[0], err)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
21
vendor/github.com/containers/storage/drivers/chown_windows.go
generated
vendored
Normal file
21
vendor/github.com/containers/storage/drivers/chown_windows.go
generated
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package graphdriver
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"github.com/containers/storage/pkg/idtools"
|
||||
)
|
||||
|
||||
type platformChowner struct{}
|
||||
|
||||
func newLChowner() *platformChowner {
|
||||
return &platformChowner{}
|
||||
}
|
||||
|
||||
func (c *platformChowner) LChown(path string, info os.FileInfo, toHost, toContainer *idtools.IDMappings) error {
|
||||
return &os.PathError{"lchown", path, syscall.EWINDOWS}
|
||||
}
|
||||
22
vendor/github.com/containers/storage/drivers/chroot_unix.go
generated
vendored
Normal file
22
vendor/github.com/containers/storage/drivers/chroot_unix.go
generated
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
//go:build linux || darwin || freebsd || solaris
|
||||
// +build linux darwin freebsd solaris
|
||||
|
||||
package graphdriver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// chrootOrChdir() is either a chdir() to the specified path, or a chroot() to the
|
||||
// specified path followed by chdir() to the new root directory
|
||||
func chrootOrChdir(path string) error {
|
||||
if err := syscall.Chroot(path); err != nil {
|
||||
return fmt.Errorf("chrooting to %q: %w", path, err)
|
||||
}
|
||||
if err := syscall.Chdir(string(os.PathSeparator)); err != nil {
|
||||
return fmt.Errorf("changing to %q: %w", path, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
15
vendor/github.com/containers/storage/drivers/chroot_windows.go
generated
vendored
Normal file
15
vendor/github.com/containers/storage/drivers/chroot_windows.go
generated
vendored
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
package graphdriver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// chrootOrChdir() is either a chdir() to the specified path, or a chroot() to the
|
||||
// specified path followed by chdir() to the new root directory
|
||||
func chrootOrChdir(path string) error {
|
||||
if err := syscall.Chdir(path); err != nil {
|
||||
return fmt.Errorf("changing to %q: %w", path, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
307
vendor/github.com/containers/storage/drivers/copy/copy_linux.go
generated
vendored
Normal file
307
vendor/github.com/containers/storage/drivers/copy/copy_linux.go
generated
vendored
Normal file
|
|
@ -0,0 +1,307 @@
|
|||
//go:build cgo
|
||||
// +build cgo
|
||||
|
||||
package copy
|
||||
|
||||
/*
|
||||
#include <linux/fs.h>
|
||||
|
||||
#ifndef FICLONE
|
||||
#define FICLONE _IOW(0x94, 9, int)
|
||||
#endif
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/containers/storage/pkg/idtools"
|
||||
"github.com/containers/storage/pkg/pools"
|
||||
"github.com/containers/storage/pkg/system"
|
||||
"github.com/containers/storage/pkg/unshare"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// Mode indicates whether to use hardlink or copy content
|
||||
type Mode int
|
||||
|
||||
const (
|
||||
// Content creates a new file, and copies the content of the file
|
||||
Content Mode = iota
|
||||
// Hardlink creates a new hardlink to the existing file
|
||||
Hardlink
|
||||
)
|
||||
|
||||
// CopyRegularToFile copies the content of a file to another
|
||||
func CopyRegularToFile(srcPath string, dstFile *os.File, fileinfo os.FileInfo, copyWithFileRange, copyWithFileClone *bool) error { // nolint: revive,golint
|
||||
srcFile, err := os.Open(srcPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer srcFile.Close()
|
||||
|
||||
if *copyWithFileClone {
|
||||
_, _, err = unix.Syscall(unix.SYS_IOCTL, dstFile.Fd(), C.FICLONE, srcFile.Fd())
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
*copyWithFileClone = false
|
||||
if err == unix.EXDEV {
|
||||
*copyWithFileRange = false
|
||||
}
|
||||
}
|
||||
if *copyWithFileRange {
|
||||
err = doCopyWithFileRange(srcFile, dstFile, fileinfo)
|
||||
// Trying the file_clone may not have caught the exdev case
|
||||
// as the ioctl may not have been available (therefore EINVAL)
|
||||
if err == unix.EXDEV || err == unix.ENOSYS {
|
||||
*copyWithFileRange = false
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return legacyCopy(srcFile, dstFile)
|
||||
}
|
||||
|
||||
// CopyRegular copies the content of a file to another
|
||||
func CopyRegular(srcPath, dstPath string, fileinfo os.FileInfo, copyWithFileRange, copyWithFileClone *bool) error { // nolint: revive,golint
|
||||
// If the destination file already exists, we shouldn't blow it away
|
||||
dstFile, err := os.OpenFile(dstPath, os.O_WRONLY|os.O_CREATE|os.O_EXCL, fileinfo.Mode())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer dstFile.Close()
|
||||
|
||||
return CopyRegularToFile(srcPath, dstFile, fileinfo, copyWithFileRange, copyWithFileClone)
|
||||
}
|
||||
|
||||
func doCopyWithFileRange(srcFile, dstFile *os.File, fileinfo os.FileInfo) error {
|
||||
amountLeftToCopy := fileinfo.Size()
|
||||
|
||||
for amountLeftToCopy > 0 {
|
||||
n, err := unix.CopyFileRange(int(srcFile.Fd()), nil, int(dstFile.Fd()), nil, int(amountLeftToCopy), 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
amountLeftToCopy = amountLeftToCopy - int64(n)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func legacyCopy(srcFile io.Reader, dstFile io.Writer) error {
|
||||
_, err := pools.Copy(dstFile, srcFile)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func copyXattr(srcPath, dstPath, attr string) error {
|
||||
data, err := system.Lgetxattr(srcPath, attr)
|
||||
if err != nil && !errors.Is(err, unix.EOPNOTSUPP) {
|
||||
return err
|
||||
}
|
||||
if data != nil {
|
||||
if err := system.Lsetxattr(dstPath, attr, data, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type fileID struct {
|
||||
dev uint64
|
||||
ino uint64
|
||||
}
|
||||
|
||||
type dirMtimeInfo struct {
|
||||
dstPath *string
|
||||
stat *syscall.Stat_t
|
||||
}
|
||||
|
||||
// DirCopy copies or hardlinks the contents of one directory to another,
|
||||
// properly handling xattrs, and soft links
|
||||
//
|
||||
// Copying xattrs can be opted out of by passing false for copyXattrs.
|
||||
func DirCopy(srcDir, dstDir string, copyMode Mode, copyXattrs bool) error {
|
||||
copyWithFileRange := true
|
||||
copyWithFileClone := true
|
||||
|
||||
// This is a map of source file inodes to dst file paths
|
||||
copiedFiles := make(map[fileID]string)
|
||||
|
||||
dirsToSetMtimes := list.New()
|
||||
err := filepath.Walk(srcDir, func(srcPath string, f os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Rebase path
|
||||
relPath, err := filepath.Rel(srcDir, srcPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dstPath := filepath.Join(dstDir, relPath)
|
||||
stat, ok := f.Sys().(*syscall.Stat_t)
|
||||
if !ok {
|
||||
return fmt.Errorf("unable to get raw syscall.Stat_t data for %s", srcPath)
|
||||
}
|
||||
|
||||
isHardlink := false
|
||||
|
||||
switch mode := f.Mode(); {
|
||||
case mode.IsRegular():
|
||||
id := fileID{dev: uint64(stat.Dev), ino: stat.Ino}
|
||||
if copyMode == Hardlink {
|
||||
isHardlink = true
|
||||
if err2 := os.Link(srcPath, dstPath); err2 != nil {
|
||||
return err2
|
||||
}
|
||||
} else if hardLinkDstPath, ok := copiedFiles[id]; ok {
|
||||
if err2 := os.Link(hardLinkDstPath, dstPath); err2 != nil {
|
||||
return err2
|
||||
}
|
||||
} else {
|
||||
if err2 := CopyRegular(srcPath, dstPath, f, ©WithFileRange, ©WithFileClone); err2 != nil {
|
||||
return err2
|
||||
}
|
||||
copiedFiles[id] = dstPath
|
||||
}
|
||||
|
||||
case mode.IsDir():
|
||||
if err := os.Mkdir(dstPath, f.Mode()); err != nil && !os.IsExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
case mode&os.ModeSymlink != 0:
|
||||
link, err := os.Readlink(srcPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := os.Symlink(link, dstPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case mode&os.ModeNamedPipe != 0:
|
||||
if err := unix.Mkfifo(dstPath, stat.Mode); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case mode&os.ModeSocket != 0:
|
||||
s, err := net.Listen("unix", dstPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.Close()
|
||||
|
||||
case mode&os.ModeDevice != 0:
|
||||
if unshare.IsRootless() {
|
||||
// cannot create a device if running in user namespace
|
||||
return nil
|
||||
}
|
||||
if err := unix.Mknod(dstPath, stat.Mode, int(stat.Rdev)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
default:
|
||||
return fmt.Errorf("unknown file type with mode %v for %s", mode, srcPath)
|
||||
}
|
||||
|
||||
// Everything below is copying metadata from src to dst. All this metadata
|
||||
// already shares an inode for hardlinks.
|
||||
if isHardlink {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := idtools.SafeLchown(dstPath, int(stat.Uid), int(stat.Gid)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if copyXattrs {
|
||||
if err := doCopyXattrs(srcPath, dstPath); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
isSymlink := f.Mode()&os.ModeSymlink != 0
|
||||
|
||||
// There is no LChmod, so ignore mode for symlink. Also, this
|
||||
// must happen after chown, as that can modify the file mode
|
||||
if !isSymlink {
|
||||
if err := os.Chmod(dstPath, f.Mode()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// system.Chtimes doesn't support a NOFOLLOW flag atm
|
||||
// nolint: unconvert
|
||||
if f.IsDir() {
|
||||
dirsToSetMtimes.PushFront(&dirMtimeInfo{dstPath: &dstPath, stat: stat})
|
||||
} else if !isSymlink {
|
||||
aTime := time.Unix(int64(stat.Atim.Sec), int64(stat.Atim.Nsec))
|
||||
mTime := time.Unix(int64(stat.Mtim.Sec), int64(stat.Mtim.Nsec))
|
||||
if err := system.Chtimes(dstPath, aTime, mTime); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
ts := []syscall.Timespec{stat.Atim, stat.Mtim}
|
||||
if err := system.LUtimesNano(dstPath, ts); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for e := dirsToSetMtimes.Front(); e != nil; e = e.Next() {
|
||||
mtimeInfo := e.Value.(*dirMtimeInfo)
|
||||
ts := []syscall.Timespec{mtimeInfo.stat.Atim, mtimeInfo.stat.Mtim}
|
||||
if err := system.LUtimesNano(*mtimeInfo.dstPath, ts); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func doCopyXattrs(srcPath, dstPath string) error {
|
||||
if err := copyXattr(srcPath, dstPath, "security.capability"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
xattrs, err := system.Llistxattr(srcPath)
|
||||
if err != nil && !errors.Is(err, unix.EOPNOTSUPP) {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, key := range xattrs {
|
||||
if strings.HasPrefix(key, "user.") {
|
||||
if err := copyXattr(srcPath, dstPath, key); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if unshare.IsRootless() {
|
||||
return nil
|
||||
}
|
||||
|
||||
// We need to copy this attribute if it appears in an overlay upper layer, as
|
||||
// this function is used to copy those. It is set by overlay if a directory
|
||||
// is removed and then re-created and should not inherit anything from the
|
||||
// same dir in the lower dir.
|
||||
return copyXattr(srcPath, dstPath, "trusted.overlay.opaque")
|
||||
}
|
||||
41
vendor/github.com/containers/storage/drivers/copy/copy_unsupported.go
generated
vendored
Normal file
41
vendor/github.com/containers/storage/drivers/copy/copy_unsupported.go
generated
vendored
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
//go:build !linux || !cgo
|
||||
// +build !linux !cgo
|
||||
|
||||
package copy //nolint: predeclared
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/containers/storage/pkg/chrootarchive"
|
||||
)
|
||||
|
||||
// Mode indicates whether to use hardlink or copy content
|
||||
type Mode int
|
||||
|
||||
const (
|
||||
// Content creates a new file, and copies the content of the file
|
||||
Content Mode = iota
|
||||
)
|
||||
|
||||
// DirCopy copies or hardlinks the contents of one directory to another,
|
||||
// properly handling soft links
|
||||
func DirCopy(srcDir, dstDir string, _ Mode, _ bool) error {
|
||||
return chrootarchive.NewArchiver(nil).CopyWithTar(srcDir, dstDir)
|
||||
}
|
||||
|
||||
// CopyRegularToFile copies the content of a file to another
|
||||
func CopyRegularToFile(srcPath string, dstFile *os.File, fileinfo os.FileInfo, copyWithFileRange, copyWithFileClone *bool) error { //nolint: revive,golint // "func name will be used as copy.CopyRegularToFile by other packages, and that stutters"
|
||||
f, err := os.Open(srcPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
_, err = io.Copy(dstFile, f)
|
||||
return err
|
||||
}
|
||||
|
||||
// CopyRegular copies the content of a file to another
|
||||
func CopyRegular(srcPath, dstPath string, fileinfo os.FileInfo, copyWithFileRange, copyWithFileClone *bool) error { //nolint:revive,golint // "func name will be used as copy.CopyRegular by other packages, and that stutters"
|
||||
return chrootarchive.NewArchiver(nil).CopyWithTar(srcPath, dstPath)
|
||||
}
|
||||
68
vendor/github.com/containers/storage/drivers/counter.go
generated
vendored
Normal file
68
vendor/github.com/containers/storage/drivers/counter.go
generated
vendored
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
package graphdriver
|
||||
|
||||
import "sync"
|
||||
|
||||
type minfo struct {
|
||||
check bool
|
||||
count int
|
||||
}
|
||||
|
||||
// RefCounter is a generic counter for use by graphdriver Get/Put calls
|
||||
type RefCounter struct {
|
||||
counts map[string]*minfo
|
||||
mu sync.Mutex
|
||||
checker Checker
|
||||
}
|
||||
|
||||
// NewRefCounter returns a new RefCounter
|
||||
func NewRefCounter(c Checker) *RefCounter {
|
||||
return &RefCounter{
|
||||
checker: c,
|
||||
counts: make(map[string]*minfo),
|
||||
}
|
||||
}
|
||||
|
||||
// Increment increases the ref count for the given id and returns the current count
|
||||
func (c *RefCounter) Increment(path string) int {
|
||||
return c.incdec(path, func(minfo *minfo) {
|
||||
minfo.count++
|
||||
})
|
||||
}
|
||||
|
||||
// Decrement decreases the ref count for the given id and returns the current count
|
||||
func (c *RefCounter) Decrement(path string) int {
|
||||
return c.incdec(path, func(minfo *minfo) {
|
||||
minfo.count--
|
||||
})
|
||||
}
|
||||
|
||||
func (c *RefCounter) incdec(path string, infoOp func(minfo *minfo)) int {
|
||||
c.mu.Lock()
|
||||
m := c.counts[path]
|
||||
if m == nil {
|
||||
m = &minfo{}
|
||||
c.counts[path] = m
|
||||
}
|
||||
// if we are checking this path for the first time check to make sure
|
||||
// if it was already mounted on the system and make sure we have a correct ref
|
||||
// count if it is mounted as it is in use.
|
||||
if !m.check {
|
||||
m.check = true
|
||||
if c.checker.IsMounted(path) {
|
||||
m.count++
|
||||
}
|
||||
} else if !c.checker.IsMounted(path) {
|
||||
// if the unmount was performed outside of this process (e.g. conmon cleanup)
|
||||
// the ref counter would lose track of it. Check if it is still mounted.
|
||||
m.count = 0
|
||||
}
|
||||
infoOp(m)
|
||||
count := m.count
|
||||
if count <= 0 {
|
||||
// If the mounted path has been decremented enough have no references,
|
||||
// then its entry can be removed.
|
||||
delete(c.counts, path)
|
||||
}
|
||||
c.mu.Unlock()
|
||||
return count
|
||||
}
|
||||
254
vendor/github.com/containers/storage/drivers/devmapper/device_setup.go
generated
vendored
Normal file
254
vendor/github.com/containers/storage/drivers/devmapper/device_setup.go
generated
vendored
Normal file
|
|
@ -0,0 +1,254 @@
|
|||
//go:build linux && cgo
|
||||
// +build linux,cgo
|
||||
|
||||
package devmapper
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type directLVMConfig struct {
|
||||
Device string
|
||||
ThinpPercent uint64
|
||||
ThinpMetaPercent uint64
|
||||
AutoExtendPercent uint64
|
||||
AutoExtendThreshold uint64
|
||||
MetaDataSize string
|
||||
}
|
||||
|
||||
var (
|
||||
errThinpPercentMissing = errors.New("must set both `dm.thinp_percent` and `dm.thinp_metapercent` if either is specified")
|
||||
errThinpPercentTooBig = errors.New("combined `dm.thinp_percent` and `dm.thinp_metapercent` must not be greater than 100")
|
||||
errMissingSetupDevice = errors.New("must provide device path in `dm.directlvm_device` in order to configure direct-lvm")
|
||||
)
|
||||
|
||||
func validateLVMConfig(cfg directLVMConfig) error {
|
||||
if cfg.Device == "" {
|
||||
return errMissingSetupDevice
|
||||
}
|
||||
if (cfg.ThinpPercent > 0 && cfg.ThinpMetaPercent == 0) || cfg.ThinpMetaPercent > 0 && cfg.ThinpPercent == 0 {
|
||||
return errThinpPercentMissing
|
||||
}
|
||||
|
||||
if cfg.ThinpPercent+cfg.ThinpMetaPercent > 100 {
|
||||
return errThinpPercentTooBig
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkDevAvailable(dev string) error {
|
||||
lvmScan, err := exec.LookPath("lvmdiskscan")
|
||||
if err != nil {
|
||||
logrus.Debugf("could not find lvmdiskscan: %v", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
out, err := exec.Command(lvmScan).CombinedOutput()
|
||||
if err != nil {
|
||||
logrus.WithError(err).Error(string(out))
|
||||
return nil
|
||||
}
|
||||
|
||||
if !bytes.Contains(out, []byte(dev)) {
|
||||
return fmt.Errorf("%s is not available for use with devicemapper", dev)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkDevInVG(dev string) error {
|
||||
pvDisplay, err := exec.LookPath("pvdisplay")
|
||||
if err != nil {
|
||||
logrus.Debugf("could not find pvdisplay: %v", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
out, err := exec.Command(pvDisplay, dev).CombinedOutput()
|
||||
if err != nil {
|
||||
logrus.WithError(err).Error(string(out))
|
||||
return nil
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(bytes.NewReader(bytes.TrimSpace(out)))
|
||||
for scanner.Scan() {
|
||||
fields := strings.SplitAfter(strings.TrimSpace(scanner.Text()), "VG Name")
|
||||
if len(fields) > 1 {
|
||||
// got "VG Name" line"
|
||||
vg := strings.TrimSpace(fields[1])
|
||||
if len(vg) > 0 {
|
||||
return fmt.Errorf("%s is already part of a volume group %q: must remove this device from any volume group or provide a different device", dev, vg)
|
||||
}
|
||||
logrus.Error(fields)
|
||||
break
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkDevHasFS(dev string) error {
|
||||
blkid, err := exec.LookPath("blkid")
|
||||
if err != nil {
|
||||
logrus.Debugf("could not find blkid %v", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
out, err := exec.Command(blkid, dev).CombinedOutput()
|
||||
if err != nil {
|
||||
logrus.WithError(err).Error(string(out))
|
||||
return nil
|
||||
}
|
||||
|
||||
fields := bytes.Fields(out)
|
||||
for _, f := range fields {
|
||||
kv := bytes.Split(f, []byte{'='})
|
||||
if bytes.Equal(kv[0], []byte("TYPE")) {
|
||||
v := bytes.Trim(kv[1], "\"")
|
||||
if len(v) > 0 {
|
||||
return fmt.Errorf("%s has a filesystem already, use dm.directlvm_device_force=true if you want to wipe the device", dev)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func verifyBlockDevice(dev string, force bool) error {
|
||||
absPath, err := filepath.Abs(dev)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to get absolute path for %s: %s", dev, err)
|
||||
}
|
||||
realPath, err := filepath.EvalSymlinks(absPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to canonicalise path for %s: %s", dev, err)
|
||||
}
|
||||
if err := checkDevAvailable(absPath); err != nil {
|
||||
logrus.Infof("block device '%s' not available, checking '%s'", absPath, realPath)
|
||||
if err := checkDevAvailable(realPath); err != nil {
|
||||
return fmt.Errorf("neither '%s' nor '%s' are in the output of lvmdiskscan, can't use device", absPath, realPath)
|
||||
}
|
||||
}
|
||||
if err := checkDevInVG(realPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if force {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := checkDevHasFS(realPath); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func readLVMConfig(root string) (directLVMConfig, error) {
|
||||
var cfg directLVMConfig
|
||||
|
||||
p := filepath.Join(root, "setup-config.json")
|
||||
b, err := os.ReadFile(p)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return cfg, nil
|
||||
}
|
||||
return cfg, fmt.Errorf("reading existing setup config: %w", err)
|
||||
}
|
||||
|
||||
// check if this is just an empty file, no need to produce a json error later if so
|
||||
if len(b) == 0 {
|
||||
return cfg, nil
|
||||
}
|
||||
if err := json.Unmarshal(b, &cfg); err != nil {
|
||||
return cfg, fmt.Errorf("unmarshaling previous device setup config: %w", err)
|
||||
}
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func writeLVMConfig(root string, cfg directLVMConfig) error {
|
||||
p := filepath.Join(root, "setup-config.json")
|
||||
b, err := json.Marshal(cfg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshalling direct lvm config: %w", err)
|
||||
}
|
||||
if err := os.WriteFile(p, b, 0o600); err != nil {
|
||||
return fmt.Errorf("writing direct lvm config to file: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func setupDirectLVM(cfg directLVMConfig) error {
|
||||
lvmProfileDir := "/etc/lvm/profile"
|
||||
binaries := []string{"pvcreate", "vgcreate", "lvcreate", "lvconvert", "lvchange", "thin_check"}
|
||||
|
||||
for _, bin := range binaries {
|
||||
if _, err := exec.LookPath(bin); err != nil {
|
||||
return fmt.Errorf("looking up command `"+bin+"` while setting up direct lvm: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
err := os.MkdirAll(lvmProfileDir, 0o755)
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating lvm profile directory: %w", err)
|
||||
}
|
||||
|
||||
if cfg.AutoExtendPercent == 0 {
|
||||
cfg.AutoExtendPercent = 20
|
||||
}
|
||||
|
||||
if cfg.AutoExtendThreshold == 0 {
|
||||
cfg.AutoExtendThreshold = 80
|
||||
}
|
||||
|
||||
if cfg.ThinpPercent == 0 {
|
||||
cfg.ThinpPercent = 95
|
||||
}
|
||||
if cfg.ThinpMetaPercent == 0 {
|
||||
cfg.ThinpMetaPercent = 1
|
||||
}
|
||||
if cfg.MetaDataSize == "" {
|
||||
cfg.MetaDataSize = "128k"
|
||||
}
|
||||
|
||||
out, err := exec.Command("pvcreate", "--metadatasize", cfg.MetaDataSize, "-f", cfg.Device).CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v: %w", string(out), err)
|
||||
}
|
||||
|
||||
out, err = exec.Command("vgcreate", "storage", cfg.Device).CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v: %w", string(out), err)
|
||||
}
|
||||
|
||||
out, err = exec.Command("lvcreate", "--wipesignatures", "y", "-n", "thinpool", "storage", "--extents", fmt.Sprintf("%d%%VG", cfg.ThinpPercent)).CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v: %w", string(out), err)
|
||||
}
|
||||
out, err = exec.Command("lvcreate", "--wipesignatures", "y", "-n", "thinpoolmeta", "storage", "--extents", fmt.Sprintf("%d%%VG", cfg.ThinpMetaPercent)).CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v: %w", string(out), err)
|
||||
}
|
||||
|
||||
out, err = exec.Command("lvconvert", "-y", "--zero", "n", "-c", "512K", "--thinpool", "storage/thinpool", "--poolmetadata", "storage/thinpoolmeta").CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v: %w", string(out), err)
|
||||
}
|
||||
|
||||
profile := fmt.Sprintf("activation{\nthin_pool_autoextend_threshold=%d\nthin_pool_autoextend_percent=%d\n}", cfg.AutoExtendThreshold, cfg.AutoExtendPercent)
|
||||
err = os.WriteFile(lvmProfileDir+"/storage-thinpool.profile", []byte(profile), 0o600)
|
||||
if err != nil {
|
||||
return fmt.Errorf("writing storage thinp autoextend profile: %w", err)
|
||||
}
|
||||
|
||||
out, err = exec.Command("lvchange", "--metadataprofile", "storage-thinpool", "storage/thinpool").CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %w", string(out), err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
2888
vendor/github.com/containers/storage/drivers/devmapper/deviceset.go
generated
vendored
Normal file
2888
vendor/github.com/containers/storage/drivers/devmapper/deviceset.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
109
vendor/github.com/containers/storage/drivers/devmapper/devmapper_doc.go
generated
vendored
Normal file
109
vendor/github.com/containers/storage/drivers/devmapper/devmapper_doc.go
generated
vendored
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
//go:build linux && cgo
|
||||
// +build linux,cgo
|
||||
|
||||
package devmapper
|
||||
|
||||
// Definition of struct dm_task and sub structures (from lvm2)
|
||||
//
|
||||
// struct dm_ioctl {
|
||||
// /*
|
||||
// * The version number is made up of three parts:
|
||||
// * major - no backward or forward compatibility,
|
||||
// * minor - only backwards compatible,
|
||||
// * patch - both backwards and forwards compatible.
|
||||
// *
|
||||
// * All clients of the ioctl interface should fill in the
|
||||
// * version number of the interface that they were
|
||||
// * compiled with.
|
||||
// *
|
||||
// * All recognized ioctl commands (ie. those that don't
|
||||
// * return -ENOTTY) fill out this field, even if the
|
||||
// * command failed.
|
||||
// */
|
||||
// uint32_t version[3]; /* in/out */
|
||||
// uint32_t data_size; /* total size of data passed in
|
||||
// * including this struct */
|
||||
|
||||
// uint32_t data_start; /* offset to start of data
|
||||
// * relative to start of this struct */
|
||||
|
||||
// uint32_t target_count; /* in/out */
|
||||
// int32_t open_count; /* out */
|
||||
// uint32_t flags; /* in/out */
|
||||
|
||||
// /*
|
||||
// * event_nr holds either the event number (input and output) or the
|
||||
// * udev cookie value (input only).
|
||||
// * The DM_DEV_WAIT ioctl takes an event number as input.
|
||||
// * The DM_SUSPEND, DM_DEV_REMOVE and DM_DEV_RENAME ioctls
|
||||
// * use the field as a cookie to return in the DM_COOKIE
|
||||
// * variable with the uevents they issue.
|
||||
// * For output, the ioctls return the event number, not the cookie.
|
||||
// */
|
||||
// uint32_t event_nr; /* in/out */
|
||||
// uint32_t padding;
|
||||
|
||||
// uint64_t dev; /* in/out */
|
||||
|
||||
// char name[DM_NAME_LEN]; /* device name */
|
||||
// char uuid[DM_UUID_LEN]; /* unique identifier for
|
||||
// * the block device */
|
||||
// char data[7]; /* padding or data */
|
||||
// };
|
||||
|
||||
// struct target {
|
||||
// uint64_t start;
|
||||
// uint64_t length;
|
||||
// char *type;
|
||||
// char *params;
|
||||
|
||||
// struct target *next;
|
||||
// };
|
||||
|
||||
// typedef enum {
|
||||
// DM_ADD_NODE_ON_RESUME, /* add /dev/mapper node with dmsetup resume */
|
||||
// DM_ADD_NODE_ON_CREATE /* add /dev/mapper node with dmsetup create */
|
||||
// } dm_add_node_t;
|
||||
|
||||
// struct dm_task {
|
||||
// int type;
|
||||
// char *dev_name;
|
||||
// char *mangled_dev_name;
|
||||
|
||||
// struct target *head, *tail;
|
||||
|
||||
// int read_only;
|
||||
// uint32_t event_nr;
|
||||
// int major;
|
||||
// int minor;
|
||||
// int allow_default_major_fallback;
|
||||
// uid_t uid;
|
||||
// gid_t gid;
|
||||
// mode_t mode;
|
||||
// uint32_t read_ahead;
|
||||
// uint32_t read_ahead_flags;
|
||||
// union {
|
||||
// struct dm_ioctl *v4;
|
||||
// } dmi;
|
||||
// char *newname;
|
||||
// char *message;
|
||||
// char *geometry;
|
||||
// uint64_t sector;
|
||||
// int no_flush;
|
||||
// int no_open_count;
|
||||
// int skip_lockfs;
|
||||
// int query_inactive_table;
|
||||
// int suppress_identical_reload;
|
||||
// dm_add_node_t add_node;
|
||||
// uint64_t existing_table_size;
|
||||
// int cookie_set;
|
||||
// int new_uuid;
|
||||
// int secure_data;
|
||||
// int retry_remove;
|
||||
// int enable_checks;
|
||||
// int expected_errno;
|
||||
|
||||
// char *uuid;
|
||||
// char *mangled_uuid;
|
||||
// };
|
||||
//
|
||||
271
vendor/github.com/containers/storage/drivers/devmapper/driver.go
generated
vendored
Normal file
271
vendor/github.com/containers/storage/drivers/devmapper/driver.go
generated
vendored
Normal file
|
|
@ -0,0 +1,271 @@
|
|||
//go:build linux && cgo
|
||||
// +build linux,cgo
|
||||
|
||||
package devmapper
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
|
||||
graphdriver "github.com/containers/storage/drivers"
|
||||
"github.com/containers/storage/pkg/devicemapper"
|
||||
"github.com/containers/storage/pkg/directory"
|
||||
"github.com/containers/storage/pkg/idtools"
|
||||
"github.com/containers/storage/pkg/locker"
|
||||
"github.com/containers/storage/pkg/mount"
|
||||
units "github.com/docker/go-units"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const defaultPerms = os.FileMode(0o555)
|
||||
|
||||
func init() {
|
||||
graphdriver.MustRegister("devicemapper", Init)
|
||||
}
|
||||
|
||||
// Driver contains the device set mounted and the home directory
|
||||
type Driver struct {
|
||||
*DeviceSet
|
||||
home string
|
||||
uidMaps []idtools.IDMap
|
||||
gidMaps []idtools.IDMap
|
||||
ctr *graphdriver.RefCounter
|
||||
locker *locker.Locker
|
||||
}
|
||||
|
||||
// Init creates a driver with the given home and the set of options.
|
||||
func Init(home string, options graphdriver.Options) (graphdriver.Driver, error) {
|
||||
deviceSet, err := NewDeviceSet(home, true, options.DriverOptions, options.UIDMaps, options.GIDMaps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := mount.MakePrivate(home); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
d := &Driver{
|
||||
DeviceSet: deviceSet,
|
||||
home: home,
|
||||
uidMaps: options.UIDMaps,
|
||||
gidMaps: options.GIDMaps,
|
||||
ctr: graphdriver.NewRefCounter(graphdriver.NewDefaultChecker()),
|
||||
locker: locker.New(),
|
||||
}
|
||||
return graphdriver.NewNaiveDiffDriver(d, graphdriver.NewNaiveLayerIDMapUpdater(d)), nil
|
||||
}
|
||||
|
||||
func (d *Driver) String() string {
|
||||
return "devicemapper"
|
||||
}
|
||||
|
||||
// Status returns the status about the driver in a printable format.
|
||||
// Information returned contains Pool Name, Data File, Metadata file, disk usage by
|
||||
// the data and metadata, etc.
|
||||
func (d *Driver) Status() [][2]string {
|
||||
s := d.DeviceSet.Status()
|
||||
|
||||
status := [][2]string{
|
||||
{"Pool Name", s.PoolName},
|
||||
{"Pool Blocksize", units.HumanSize(float64(s.SectorSize))},
|
||||
{"Base Device Size", units.HumanSize(float64(s.BaseDeviceSize))},
|
||||
{"Backing Filesystem", s.BaseDeviceFS},
|
||||
{"Data file", s.DataFile},
|
||||
{"Metadata file", s.MetadataFile},
|
||||
{"Data Space Used", units.HumanSize(float64(s.Data.Used))},
|
||||
{"Data Space Total", units.HumanSize(float64(s.Data.Total))},
|
||||
{"Data Space Available", units.HumanSize(float64(s.Data.Available))},
|
||||
{"Metadata Space Used", units.HumanSize(float64(s.Metadata.Used))},
|
||||
{"Metadata Space Total", units.HumanSize(float64(s.Metadata.Total))},
|
||||
{"Metadata Space Available", units.HumanSize(float64(s.Metadata.Available))},
|
||||
{"Thin Pool Minimum Free Space", units.HumanSize(float64(s.MinFreeSpace))},
|
||||
{"Udev Sync Supported", fmt.Sprintf("%v", s.UdevSyncSupported)},
|
||||
{"Deferred Removal Enabled", fmt.Sprintf("%v", s.DeferredRemoveEnabled)},
|
||||
{"Deferred Deletion Enabled", fmt.Sprintf("%v", s.DeferredDeleteEnabled)},
|
||||
{"Deferred Deleted Device Count", fmt.Sprintf("%v", s.DeferredDeletedDeviceCount)},
|
||||
}
|
||||
if len(s.DataLoopback) > 0 {
|
||||
status = append(status, [2]string{"Data loop file", s.DataLoopback})
|
||||
}
|
||||
if len(s.MetadataLoopback) > 0 {
|
||||
status = append(status, [2]string{"Metadata loop file", s.MetadataLoopback})
|
||||
}
|
||||
if vStr, err := devicemapper.GetLibraryVersion(); err == nil {
|
||||
status = append(status, [2]string{"Library Version", vStr})
|
||||
}
|
||||
return status
|
||||
}
|
||||
|
||||
// Metadata returns a map of information about the device.
|
||||
func (d *Driver) Metadata(id string) (map[string]string, error) {
|
||||
m, err := d.DeviceSet.exportDeviceMetadata(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
metadata := make(map[string]string)
|
||||
metadata["DeviceId"] = strconv.Itoa(m.deviceID)
|
||||
metadata["DeviceSize"] = strconv.FormatUint(m.deviceSize, 10)
|
||||
metadata["DeviceName"] = m.deviceName
|
||||
return metadata, nil
|
||||
}
|
||||
|
||||
// Cleanup unmounts a device.
|
||||
func (d *Driver) Cleanup() error {
|
||||
err := d.DeviceSet.Shutdown(d.home)
|
||||
|
||||
umountErr := mount.Unmount(d.home)
|
||||
// in case we have two errors, prefer the one from Shutdown()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return umountErr
|
||||
}
|
||||
|
||||
// CreateFromTemplate creates a layer with the same contents and parent as another layer.
|
||||
func (d *Driver) CreateFromTemplate(id, template string, templateIDMappings *idtools.IDMappings, parent string, parentIDMappings *idtools.IDMappings, opts *graphdriver.CreateOpts, readWrite bool) error {
|
||||
return d.Create(id, template, opts)
|
||||
}
|
||||
|
||||
// CreateReadWrite creates a layer that is writable for use as a container
|
||||
// file system.
|
||||
func (d *Driver) CreateReadWrite(id, parent string, opts *graphdriver.CreateOpts) error {
|
||||
return d.Create(id, parent, opts)
|
||||
}
|
||||
|
||||
// Create adds a device with a given id and the parent.
|
||||
func (d *Driver) Create(id, parent string, opts *graphdriver.CreateOpts) error {
|
||||
var storageOpt map[string]string
|
||||
if opts != nil {
|
||||
storageOpt = opts.StorageOpt
|
||||
}
|
||||
|
||||
if err := d.DeviceSet.AddDevice(id, parent, storageOpt); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove removes a device with a given id, unmounts the filesystem, and removes the mount point.
|
||||
func (d *Driver) Remove(id string) error {
|
||||
d.locker.Lock(id)
|
||||
defer d.locker.Unlock(id)
|
||||
if !d.DeviceSet.HasDevice(id) {
|
||||
// Consider removing a non-existing device a no-op
|
||||
// This is useful to be able to progress on container removal
|
||||
// if the underlying device has gone away due to earlier errors
|
||||
return nil
|
||||
}
|
||||
|
||||
// This assumes the device has been properly Get/Put:ed and thus is unmounted
|
||||
if err := d.DeviceSet.DeleteDevice(id, false); err != nil {
|
||||
return fmt.Errorf("failed to remove device %s: %v", id, err)
|
||||
}
|
||||
|
||||
// Most probably the mount point is already removed on Put()
|
||||
// (see DeviceSet.UnmountDevice()), but just in case it was not
|
||||
// let's try to remove it here as well, ignoring errors as
|
||||
// an older kernel can return EBUSY if e.g. the mount was leaked
|
||||
// to other mount namespaces. A failure to remove the container's
|
||||
// mount point is not important and should not be treated
|
||||
// as a failure to remove the container.
|
||||
mp := path.Join(d.home, "mnt", id)
|
||||
err := unix.Rmdir(mp)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
logrus.WithField("storage-driver", "devicemapper").Warnf("unable to remove mount point %q: %s", mp, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get mounts a device with given id into the root filesystem
|
||||
func (d *Driver) Get(id string, options graphdriver.MountOpts) (string, error) {
|
||||
d.locker.Lock(id)
|
||||
defer d.locker.Unlock(id)
|
||||
mp := path.Join(d.home, "mnt", id)
|
||||
rootFs := path.Join(mp, "rootfs")
|
||||
if count := d.ctr.Increment(mp); count > 1 {
|
||||
return rootFs, nil
|
||||
}
|
||||
|
||||
uid, gid, err := idtools.GetRootUIDGID(d.uidMaps, d.gidMaps)
|
||||
if err != nil {
|
||||
d.ctr.Decrement(mp)
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Create the target directories if they don't exist
|
||||
if err := idtools.MkdirAllAs(path.Join(d.home, "mnt"), 0o755, uid, gid); err != nil {
|
||||
d.ctr.Decrement(mp)
|
||||
return "", err
|
||||
}
|
||||
if err := idtools.MkdirAs(mp, 0o755, uid, gid); err != nil && !os.IsExist(err) {
|
||||
d.ctr.Decrement(mp)
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Mount the device
|
||||
if err := d.DeviceSet.MountDevice(id, mp, options); err != nil {
|
||||
d.ctr.Decrement(mp)
|
||||
return "", err
|
||||
}
|
||||
|
||||
if err := idtools.MkdirAllAs(rootFs, defaultPerms, uid, gid); err != nil {
|
||||
d.ctr.Decrement(mp)
|
||||
d.DeviceSet.UnmountDevice(id, mp)
|
||||
return "", err
|
||||
}
|
||||
|
||||
idFile := path.Join(mp, "id")
|
||||
if _, err := os.Stat(idFile); err != nil && os.IsNotExist(err) {
|
||||
// Create an "id" file with the container/image id in it to help reconstruct this in case
|
||||
// of later problems
|
||||
if err := os.WriteFile(idFile, []byte(id), 0o600); err != nil {
|
||||
d.ctr.Decrement(mp)
|
||||
d.DeviceSet.UnmountDevice(id, mp)
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
return rootFs, nil
|
||||
}
|
||||
|
||||
// Put unmounts a device and removes it.
|
||||
func (d *Driver) Put(id string) error {
|
||||
d.locker.Lock(id)
|
||||
defer d.locker.Unlock(id)
|
||||
mp := path.Join(d.home, "mnt", id)
|
||||
if count := d.ctr.Decrement(mp); count > 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
err := d.DeviceSet.UnmountDevice(id, mp)
|
||||
if err != nil {
|
||||
logrus.Errorf("devmapper: Error unmounting device %s: %v", id, err)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// ReadWriteDiskUsage returns the disk usage of the writable directory for the ID.
|
||||
// For devmapper, it queries the mnt path for this ID.
|
||||
func (d *Driver) ReadWriteDiskUsage(id string) (*directory.DiskUsage, error) {
|
||||
d.locker.Lock(id)
|
||||
defer d.locker.Unlock(id)
|
||||
return directory.Usage(path.Join(d.home, "mnt", id))
|
||||
}
|
||||
|
||||
// Exists checks to see if the device exists.
|
||||
func (d *Driver) Exists(id string) bool {
|
||||
return d.DeviceSet.HasDevice(id)
|
||||
}
|
||||
|
||||
// AdditionalImageStores returns additional image stores supported by the driver
|
||||
func (d *Driver) AdditionalImageStores() []string {
|
||||
return nil
|
||||
}
|
||||
8
vendor/github.com/containers/storage/drivers/devmapper/jsoniter.go
generated
vendored
Normal file
8
vendor/github.com/containers/storage/drivers/devmapper/jsoniter.go
generated
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
//go:build linux && cgo
|
||||
// +build linux,cgo
|
||||
|
||||
package devmapper
|
||||
|
||||
import jsoniter "github.com/json-iterator/go"
|
||||
|
||||
var json = jsoniter.ConfigCompatibleWithStandardLibrary
|
||||
89
vendor/github.com/containers/storage/drivers/devmapper/mount.go
generated
vendored
Normal file
89
vendor/github.com/containers/storage/drivers/devmapper/mount.go
generated
vendored
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
//go:build linux && cgo
|
||||
// +build linux,cgo
|
||||
|
||||
package devmapper
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// FIXME: this is copy-pasted from the aufs driver.
|
||||
// It should be moved into the core.
|
||||
|
||||
// Mounted returns true if a mount point exists.
|
||||
func Mounted(mountpoint string) (bool, error) {
|
||||
var mntpointSt unix.Stat_t
|
||||
if err := unix.Stat(mountpoint, &mntpointSt); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
var parentSt unix.Stat_t
|
||||
if err := unix.Stat(filepath.Join(mountpoint, ".."), &parentSt); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return mntpointSt.Dev != parentSt.Dev, nil
|
||||
}
|
||||
|
||||
type probeData struct {
|
||||
fsName string
|
||||
magic string
|
||||
offset uint64
|
||||
}
|
||||
|
||||
// ProbeFsType returns the filesystem name for the given device id.
|
||||
func ProbeFsType(device string) (string, error) {
|
||||
probes := []probeData{
|
||||
{"btrfs", "_BHRfS_M", 0x10040},
|
||||
{"ext4", "\123\357", 0x438},
|
||||
{"xfs", "XFSB", 0},
|
||||
}
|
||||
|
||||
maxLen := uint64(0)
|
||||
for _, p := range probes {
|
||||
l := p.offset + uint64(len(p.magic))
|
||||
if l > maxLen {
|
||||
maxLen = l
|
||||
}
|
||||
}
|
||||
|
||||
file, err := os.Open(device)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
buffer := make([]byte, maxLen)
|
||||
l, err := file.Read(buffer)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if uint64(l) != maxLen {
|
||||
return "", fmt.Errorf("devmapper: unable to detect filesystem type of %s, short read", device)
|
||||
}
|
||||
|
||||
for _, p := range probes {
|
||||
if bytes.Equal([]byte(p.magic), buffer[p.offset:p.offset+uint64(len(p.magic))]) {
|
||||
return p.fsName, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("devmapper: Unknown filesystem type on %s", device)
|
||||
}
|
||||
|
||||
func joinMountOptions(a, b string) string {
|
||||
if a == "" {
|
||||
return b
|
||||
}
|
||||
if b == "" {
|
||||
return a
|
||||
}
|
||||
return a + "," + b
|
||||
}
|
||||
472
vendor/github.com/containers/storage/drivers/driver.go
generated
vendored
Normal file
472
vendor/github.com/containers/storage/drivers/driver.go
generated
vendored
Normal file
|
|
@ -0,0 +1,472 @@
|
|||
package graphdriver
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/containers/storage/pkg/archive"
|
||||
"github.com/containers/storage/pkg/directory"
|
||||
"github.com/containers/storage/pkg/idtools"
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/vbatts/tar-split/tar/storage"
|
||||
)
|
||||
|
||||
// FsMagic unsigned id of the filesystem in use.
|
||||
type FsMagic uint32
|
||||
|
||||
const (
|
||||
// FsMagicUnsupported is a predefined constant value other than a valid filesystem id.
|
||||
FsMagicUnsupported = FsMagic(0x00000000)
|
||||
)
|
||||
|
||||
var (
|
||||
// All registered drivers
|
||||
drivers map[string]InitFunc
|
||||
|
||||
// ErrNotSupported returned when driver is not supported.
|
||||
ErrNotSupported = errors.New("driver not supported")
|
||||
// ErrPrerequisites returned when driver does not meet prerequisites.
|
||||
ErrPrerequisites = errors.New("prerequisites for driver not satisfied (wrong filesystem?)")
|
||||
// ErrIncompatibleFS returned when file system is not supported.
|
||||
ErrIncompatibleFS = errors.New("backing file system is unsupported for this graph driver")
|
||||
// ErrLayerUnknown returned when the specified layer is unknown by the driver.
|
||||
ErrLayerUnknown = errors.New("unknown layer")
|
||||
)
|
||||
|
||||
// CreateOpts contains optional arguments for Create() and CreateReadWrite()
|
||||
// methods.
|
||||
type CreateOpts struct {
|
||||
MountLabel string
|
||||
StorageOpt map[string]string
|
||||
*idtools.IDMappings
|
||||
ignoreChownErrors bool
|
||||
}
|
||||
|
||||
// MountOpts contains optional arguments for Driver.Get() methods.
|
||||
type MountOpts struct {
|
||||
// Mount label is the MAC Labels to assign to mount point (SELINUX)
|
||||
MountLabel string
|
||||
// UidMaps & GidMaps are the User Namespace mappings to be assigned to content in the mount point
|
||||
UidMaps []idtools.IDMap //nolint: revive,golint
|
||||
GidMaps []idtools.IDMap //nolint: revive,golint
|
||||
Options []string
|
||||
|
||||
// Volatile specifies whether the container storage can be optimized
|
||||
// at the cost of not syncing all the dirty files in memory.
|
||||
Volatile bool
|
||||
|
||||
// DisableShifting forces the driver to not do any ID shifting at runtime.
|
||||
DisableShifting bool
|
||||
}
|
||||
|
||||
// ApplyDiffOpts contains optional arguments for ApplyDiff methods.
|
||||
type ApplyDiffOpts struct {
|
||||
Diff io.Reader
|
||||
Mappings *idtools.IDMappings
|
||||
MountLabel string
|
||||
IgnoreChownErrors bool
|
||||
ForceMask *os.FileMode
|
||||
}
|
||||
|
||||
// InitFunc initializes the storage driver.
|
||||
type InitFunc func(homedir string, options Options) (Driver, error)
|
||||
|
||||
// ProtoDriver defines the basic capabilities of a driver.
|
||||
// This interface exists solely to be a minimum set of methods
|
||||
// for client code which choose not to implement the entire Driver
|
||||
// interface and use the NaiveDiffDriver wrapper constructor.
|
||||
//
|
||||
// Use of ProtoDriver directly by client code is not recommended.
|
||||
type ProtoDriver interface {
|
||||
// String returns a string representation of this driver.
|
||||
String() string
|
||||
// CreateReadWrite creates a new, empty filesystem layer that is ready
|
||||
// to be used as the storage for a container. Additional options can
|
||||
// be passed in opts. parent may be "" and opts may be nil.
|
||||
CreateReadWrite(id, parent string, opts *CreateOpts) error
|
||||
// Create creates a new, empty, filesystem layer with the
|
||||
// specified id and parent and options passed in opts. Parent
|
||||
// may be "" and opts may be nil.
|
||||
Create(id, parent string, opts *CreateOpts) error
|
||||
// CreateFromTemplate creates a new filesystem layer with the specified id
|
||||
// and parent, with contents identical to the specified template layer.
|
||||
CreateFromTemplate(id, template string, templateIDMappings *idtools.IDMappings, parent string, parentIDMappings *idtools.IDMappings, opts *CreateOpts, readWrite bool) error
|
||||
// Remove attempts to remove the filesystem layer with this id.
|
||||
Remove(id string) error
|
||||
// Get returns the mountpoint for the layered filesystem referred
|
||||
// to by this id. You can optionally specify a mountLabel or "".
|
||||
// Optionally it gets the mappings used to create the layer.
|
||||
// Returns the absolute path to the mounted layered filesystem.
|
||||
Get(id string, options MountOpts) (dir string, err error)
|
||||
// Put releases the system resources for the specified id,
|
||||
// e.g, unmounting layered filesystem.
|
||||
Put(id string) error
|
||||
// Exists returns whether a filesystem layer with the specified
|
||||
// ID exists on this driver.
|
||||
Exists(id string) bool
|
||||
// Returns a list of layer ids that exist on this driver (does not include
|
||||
// additional storage layers). Not supported by all backends.
|
||||
// If the driver requires that layers be removed in a particular order,
|
||||
// usually due to parent-child relationships that it cares about, The
|
||||
// list should be sorted well enough so that if all layers need to be
|
||||
// removed, they can be removed in the order in which they're returned.
|
||||
ListLayers() ([]string, error)
|
||||
// Status returns a set of key-value pairs which give low
|
||||
// level diagnostic status about this driver.
|
||||
Status() [][2]string
|
||||
// Returns a set of key-value pairs which give low level information
|
||||
// about the image/container driver is managing.
|
||||
Metadata(id string) (map[string]string, error)
|
||||
// ReadWriteDiskUsage returns the disk usage of the writable directory for the specified ID.
|
||||
ReadWriteDiskUsage(id string) (*directory.DiskUsage, error)
|
||||
// Cleanup performs necessary tasks to release resources
|
||||
// held by the driver, e.g., unmounting all layered filesystems
|
||||
// known to this driver.
|
||||
Cleanup() error
|
||||
// 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
|
||||
}
|
||||
|
||||
// DiffDriver is the interface to use to implement graph diffs
|
||||
type DiffDriver interface {
|
||||
// Diff produces an archive of the changes between the specified
|
||||
// layer and its parent layer which may be "".
|
||||
Diff(id string, idMappings *idtools.IDMappings, parent string, parentIDMappings *idtools.IDMappings, mountLabel string) (io.ReadCloser, error)
|
||||
// Changes produces a list of changes between the specified layer
|
||||
// and its parent layer. If parent is "", then all changes will be ADD changes.
|
||||
Changes(id string, idMappings *idtools.IDMappings, parent string, parentIDMappings *idtools.IDMappings, mountLabel string) ([]archive.Change, error)
|
||||
// ApplyDiff extracts the changeset from the given diff into the
|
||||
// layer with the specified id and parent, returning the size of the
|
||||
// new layer in bytes.
|
||||
// The io.Reader must be an uncompressed stream.
|
||||
ApplyDiff(id string, parent string, options ApplyDiffOpts) (size int64, err error)
|
||||
// DiffSize calculates the changes between the specified id
|
||||
// and its parent and returns the size in bytes of the changes
|
||||
// relative to its base filesystem directory.
|
||||
DiffSize(id string, idMappings *idtools.IDMappings, parent string, parentIDMappings *idtools.IDMappings, mountLabel string) (size int64, err error)
|
||||
}
|
||||
|
||||
// LayerIDMapUpdater is the interface that implements ID map changes for layers.
|
||||
type LayerIDMapUpdater interface {
|
||||
// UpdateLayerIDMap walks the layer's filesystem tree, changing the ownership
|
||||
// information using the toContainer and toHost mappings, using them to replace
|
||||
// on-disk owner UIDs and GIDs which are "host" values in the first map with
|
||||
// UIDs and GIDs for "host" values from the second map which correspond to the
|
||||
// same "container" IDs. This method should only be called after a layer is
|
||||
// first created and populated, and before it is mounted, as other changes made
|
||||
// relative to a parent layer, but before this method is called, may be discarded
|
||||
// by Diff().
|
||||
UpdateLayerIDMap(id string, toContainer, toHost *idtools.IDMappings, mountLabel string) error
|
||||
|
||||
// SupportsShifting tells whether the driver support shifting of the UIDs/GIDs in a
|
||||
// image and it is not required to Chown the files when running in an user namespace.
|
||||
SupportsShifting() bool
|
||||
}
|
||||
|
||||
// Driver is the interface for layered/snapshot file system drivers.
|
||||
type Driver interface {
|
||||
ProtoDriver
|
||||
DiffDriver
|
||||
LayerIDMapUpdater
|
||||
}
|
||||
|
||||
// DriverWithDifferOutput is the result of ApplyDiffWithDiffer
|
||||
// This API is experimental and can be changed without bumping the major version number.
|
||||
type DriverWithDifferOutput struct {
|
||||
Differ Differ
|
||||
Target string
|
||||
Size int64
|
||||
UIDs []uint32
|
||||
GIDs []uint32
|
||||
UncompressedDigest digest.Digest
|
||||
Metadata string
|
||||
BigData map[string][]byte
|
||||
TarSplit []byte
|
||||
TOCDigest digest.Digest
|
||||
// Artifacts is a collection of additional artifacts
|
||||
// generated by the differ that the storage driver can use.
|
||||
Artifacts map[string]interface{}
|
||||
}
|
||||
|
||||
type DifferOutputFormat int
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
// DifferOptions overrides how the differ work
|
||||
type DifferOptions struct {
|
||||
// Format defines the destination directory layout format
|
||||
Format DifferOutputFormat
|
||||
}
|
||||
|
||||
// Differ defines the interface for using a custom differ.
|
||||
// This API is experimental and can be changed without bumping the major version number.
|
||||
type Differ interface {
|
||||
ApplyDiff(dest string, options *archive.TarOptions, differOpts *DifferOptions) (DriverWithDifferOutput, error)
|
||||
}
|
||||
|
||||
// DriverWithDiffer is the interface for direct diff access.
|
||||
// This API is experimental and can be changed without bumping the major version number.
|
||||
type DriverWithDiffer interface {
|
||||
Driver
|
||||
// ApplyDiffWithDiffer applies the changes using the callback function.
|
||||
// If id is empty, then a staging directory is created. The staging directory is guaranteed to be usable with ApplyDiffFromStagingDirectory.
|
||||
ApplyDiffWithDiffer(id, parent string, options *ApplyDiffOpts, differ Differ) (output DriverWithDifferOutput, err error)
|
||||
// ApplyDiffFromStagingDirectory applies the changes using the specified staging directory.
|
||||
ApplyDiffFromStagingDirectory(id, parent, stagingDirectory string, diffOutput *DriverWithDifferOutput, options *ApplyDiffOpts) error
|
||||
// CleanupStagingDirectory cleanups the staging directory. It can be used to cleanup the staging directory on errors
|
||||
CleanupStagingDirectory(stagingDirectory string) error
|
||||
// DifferTarget gets the location where files are stored for the layer.
|
||||
DifferTarget(id string) (string, error)
|
||||
}
|
||||
|
||||
// Capabilities defines a list of capabilities a driver may implement.
|
||||
// These capabilities are not required; however, they do determine how a
|
||||
// graphdriver can be used.
|
||||
type Capabilities struct {
|
||||
// Flags that this driver is capable of reproducing exactly equivalent
|
||||
// diffs for read-only layers. If set, clients can rely on the driver
|
||||
// for consistent tar streams, and avoid extra processing to account
|
||||
// for potential differences (eg: the layer store's use of tar-split).
|
||||
ReproducesExactDiffs bool
|
||||
}
|
||||
|
||||
// CapabilityDriver is the interface for layered file system drivers that
|
||||
// can report on their Capabilities.
|
||||
type CapabilityDriver interface {
|
||||
Capabilities() Capabilities
|
||||
}
|
||||
|
||||
// AdditionalLayer represents a layer that is stored in the additional layer store
|
||||
// This API is experimental and can be changed without bumping the major version number.
|
||||
type AdditionalLayer interface {
|
||||
// CreateAs creates a new layer from this additional layer
|
||||
CreateAs(id, parent string) error
|
||||
|
||||
// Info returns arbitrary information stored along with this layer (i.e. `info` file)
|
||||
Info() (io.ReadCloser, error)
|
||||
|
||||
// Blob returns a reader of the raw contents of this layer.
|
||||
Blob() (io.ReadCloser, error)
|
||||
|
||||
// Release tells the additional layer store that we don't use this handler.
|
||||
Release()
|
||||
}
|
||||
|
||||
// AdditionalLayerStoreDriver is the interface for driver that supports
|
||||
// additional layer store functionality.
|
||||
// This API is experimental and can be changed without bumping the major version number.
|
||||
type AdditionalLayerStoreDriver interface {
|
||||
Driver
|
||||
|
||||
// LookupAdditionalLayer looks up additional layer store by the specified
|
||||
// digest and ref and returns an object representing that layer.
|
||||
LookupAdditionalLayer(d digest.Digest, ref string) (AdditionalLayer, error)
|
||||
|
||||
// LookupAdditionalLayer looks up additional layer store by the specified
|
||||
// ID and returns an object representing that layer.
|
||||
LookupAdditionalLayerByID(id string) (AdditionalLayer, error)
|
||||
}
|
||||
|
||||
// DiffGetterDriver is the interface for layered file system drivers that
|
||||
// provide a specialized function for getting file contents for tar-split.
|
||||
type DiffGetterDriver interface {
|
||||
Driver
|
||||
// DiffGetter returns an interface to efficiently retrieve the contents
|
||||
// of files in a layer.
|
||||
DiffGetter(id string) (FileGetCloser, error)
|
||||
}
|
||||
|
||||
// FileGetCloser extends the storage.FileGetter interface with a Close method
|
||||
// for cleaning up.
|
||||
type FileGetCloser interface {
|
||||
storage.FileGetter
|
||||
// Close cleans up any resources associated with the FileGetCloser.
|
||||
Close() error
|
||||
}
|
||||
|
||||
// Checker makes checks on specified filesystems.
|
||||
type Checker interface {
|
||||
// IsMounted returns true if the provided path is mounted for the specific checker
|
||||
IsMounted(path string) bool
|
||||
}
|
||||
|
||||
func init() {
|
||||
drivers = make(map[string]InitFunc)
|
||||
}
|
||||
|
||||
// MustRegister registers an InitFunc for the driver, or panics.
|
||||
// It is suitable for package’s init() sections.
|
||||
func MustRegister(name string, initFunc InitFunc) {
|
||||
if err := Register(name, initFunc); err != nil {
|
||||
panic(fmt.Sprintf("failed to register containers/storage graph driver %q: %v", name, err))
|
||||
}
|
||||
}
|
||||
|
||||
// Register registers an InitFunc for the driver.
|
||||
func Register(name string, initFunc InitFunc) error {
|
||||
if _, exists := drivers[name]; exists {
|
||||
return fmt.Errorf("name already registered %s", name)
|
||||
}
|
||||
drivers[name] = initFunc
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetDriver initializes and returns the registered driver
|
||||
func GetDriver(name string, config Options) (Driver, error) {
|
||||
if initFunc, exists := drivers[name]; exists {
|
||||
return initFunc(filepath.Join(config.Root, name), config)
|
||||
}
|
||||
|
||||
logrus.Errorf("Failed to GetDriver graph %s %s", name, config.Root)
|
||||
return nil, fmt.Errorf("failed to GetDriver graph %s %s: %w", name, config.Root, ErrNotSupported)
|
||||
}
|
||||
|
||||
// getBuiltinDriver initializes and returns the registered driver, but does not try to load from plugins
|
||||
func getBuiltinDriver(name, home string, options Options) (Driver, error) {
|
||||
if initFunc, exists := drivers[name]; exists {
|
||||
return initFunc(filepath.Join(home, name), options)
|
||||
}
|
||||
logrus.Errorf("Failed to built-in GetDriver graph %s %s", name, home)
|
||||
return nil, fmt.Errorf("failed to built-in GetDriver graph %s %s: %w", name, home, ErrNotSupported)
|
||||
}
|
||||
|
||||
// Options is used to initialize a graphdriver
|
||||
type Options struct {
|
||||
Root string
|
||||
RunRoot string
|
||||
ImageStore string
|
||||
DriverPriority []string
|
||||
DriverOptions []string
|
||||
UIDMaps []idtools.IDMap
|
||||
GIDMaps []idtools.IDMap
|
||||
ExperimentalEnabled bool
|
||||
}
|
||||
|
||||
// New creates the driver and initializes it at the specified root.
|
||||
func New(name string, config Options) (Driver, error) {
|
||||
if name != "" {
|
||||
logrus.Debugf("[graphdriver] trying provided driver %q", name) // so the logs show specified driver
|
||||
return GetDriver(name, config)
|
||||
}
|
||||
|
||||
// Guess for prior driver
|
||||
driversMap := ScanPriorDrivers(config.Root)
|
||||
|
||||
// use the supplied priority list unless it is empty
|
||||
prioList := config.DriverPriority
|
||||
if len(prioList) == 0 {
|
||||
prioList = Priority
|
||||
}
|
||||
|
||||
for _, name := range prioList {
|
||||
if name == "vfs" && len(config.DriverPriority) == 0 {
|
||||
// don't use vfs even if there is state present and vfs
|
||||
// has not been explicitly added to the override driver
|
||||
// priority list
|
||||
continue
|
||||
}
|
||||
if _, prior := driversMap[name]; prior {
|
||||
// of the state found from prior drivers, check in order of our priority
|
||||
// which we would prefer
|
||||
driver, err := getBuiltinDriver(name, config.Root, config)
|
||||
if err != nil {
|
||||
// unlike below, we will return error here, because there is prior
|
||||
// state, and now it is no longer supported/prereq/compatible, so
|
||||
// something changed and needs attention. Otherwise the daemon's
|
||||
// images would just "disappear".
|
||||
logrus.Errorf("[graphdriver] prior storage driver %s failed: %s", name, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// abort starting when there are other prior configured drivers
|
||||
// to ensure the user explicitly selects the driver to load
|
||||
if len(driversMap)-1 > 0 {
|
||||
var driversSlice []string
|
||||
for name := range driversMap {
|
||||
driversSlice = append(driversSlice, name)
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("%s contains several valid graphdrivers: %s; Please cleanup or explicitly choose storage driver (-s <DRIVER>)", config.Root, strings.Join(driversSlice, ", "))
|
||||
}
|
||||
|
||||
logrus.Infof("[graphdriver] using prior storage driver: %s", name)
|
||||
return driver, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Check for priority drivers first
|
||||
for _, name := range prioList {
|
||||
driver, err := getBuiltinDriver(name, config.Root, config)
|
||||
if err != nil {
|
||||
if isDriverNotSupported(err) {
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return driver, nil
|
||||
}
|
||||
|
||||
// Check all registered drivers if no priority driver is found
|
||||
for name, initFunc := range drivers {
|
||||
driver, err := initFunc(filepath.Join(config.Root, name), config)
|
||||
if err != nil {
|
||||
if isDriverNotSupported(err) {
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return driver, nil
|
||||
}
|
||||
return nil, fmt.Errorf("no supported storage backend found")
|
||||
}
|
||||
|
||||
// isDriverNotSupported returns true if the error initializing
|
||||
// the graph driver is a non-supported error.
|
||||
func isDriverNotSupported(err error) bool {
|
||||
return errors.Is(err, ErrNotSupported) || errors.Is(err, ErrPrerequisites) || errors.Is(err, ErrIncompatibleFS)
|
||||
}
|
||||
|
||||
// scanPriorDrivers returns an un-ordered scan of directories of prior storage drivers
|
||||
func ScanPriorDrivers(root string) map[string]bool {
|
||||
driversMap := make(map[string]bool)
|
||||
|
||||
for driver := range drivers {
|
||||
p := filepath.Join(root, driver)
|
||||
if _, err := os.Stat(p); err == nil {
|
||||
driversMap[driver] = true
|
||||
}
|
||||
}
|
||||
return driversMap
|
||||
}
|
||||
|
||||
// driverPut is driver.Put, but errors are handled either by updating mainErr or just logging.
|
||||
// Typical usage:
|
||||
//
|
||||
// func …(…) (err error) {
|
||||
// …
|
||||
// defer driverPut(driver, id, &err)
|
||||
// }
|
||||
func driverPut(driver ProtoDriver, id string, mainErr *error) {
|
||||
if err := driver.Put(id); err != nil {
|
||||
err = fmt.Errorf("unmounting layer %s: %w", id, err)
|
||||
if *mainErr == nil {
|
||||
*mainErr = err
|
||||
} else {
|
||||
logrus.Errorf(err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
12
vendor/github.com/containers/storage/drivers/driver_darwin.go
generated
vendored
Normal file
12
vendor/github.com/containers/storage/drivers/driver_darwin.go
generated
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
package graphdriver
|
||||
|
||||
// Slice of drivers that should be used in order
|
||||
var Priority = []string{
|
||||
"vfs",
|
||||
}
|
||||
|
||||
// GetFSMagic returns the filesystem id given the path.
|
||||
func GetFSMagic(rootpath string) (FsMagic, error) {
|
||||
// Note it is OK to return FsMagicUnsupported on Windows.
|
||||
return FsMagicUnsupported, nil
|
||||
}
|
||||
48
vendor/github.com/containers/storage/drivers/driver_freebsd.go
generated
vendored
Normal file
48
vendor/github.com/containers/storage/drivers/driver_freebsd.go
generated
vendored
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
package graphdriver
|
||||
|
||||
import (
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/containers/storage/pkg/mount"
|
||||
)
|
||||
|
||||
const (
|
||||
// FsMagicZfs filesystem id for Zfs
|
||||
FsMagicZfs = FsMagic(0x2fc12fc1)
|
||||
)
|
||||
|
||||
var (
|
||||
// Slice of drivers that should be used in an order
|
||||
Priority = []string{
|
||||
"zfs",
|
||||
"vfs",
|
||||
}
|
||||
|
||||
// FsNames maps filesystem id to name of the filesystem.
|
||||
FsNames = map[FsMagic]string{
|
||||
FsMagicZfs: "zfs",
|
||||
}
|
||||
)
|
||||
|
||||
// NewDefaultChecker returns a check that parses /proc/mountinfo to check
|
||||
// if the specified path is mounted.
|
||||
// No-op on FreeBSD.
|
||||
func NewDefaultChecker() Checker {
|
||||
return &defaultChecker{}
|
||||
}
|
||||
|
||||
type defaultChecker struct{}
|
||||
|
||||
func (c *defaultChecker) IsMounted(path string) bool {
|
||||
m, _ := mount.Mounted(path)
|
||||
return m
|
||||
}
|
||||
|
||||
// Mounted checks if the given path is mounted as the fs type
|
||||
func Mounted(fsType FsMagic, mountPath string) (bool, error) {
|
||||
var buf unix.Statfs_t
|
||||
if err := unix.Statfs(mountPath, &buf); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return FsMagic(buf.Type) == fsType, nil
|
||||
}
|
||||
202
vendor/github.com/containers/storage/drivers/driver_linux.go
generated
vendored
Normal file
202
vendor/github.com/containers/storage/drivers/driver_linux.go
generated
vendored
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package graphdriver
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
|
||||
"github.com/containers/storage/pkg/mount"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const (
|
||||
// FsMagicAufs filesystem id for Aufs
|
||||
FsMagicAufs = FsMagic(0x61756673)
|
||||
// FsMagicBtrfs filesystem id for Btrfs
|
||||
FsMagicBtrfs = FsMagic(0x9123683E)
|
||||
// FsMagicCramfs filesystem id for Cramfs
|
||||
FsMagicCramfs = FsMagic(0x28cd3d45)
|
||||
// FsMagicEcryptfs filesystem id for eCryptfs
|
||||
FsMagicEcryptfs = FsMagic(0xf15f)
|
||||
// FsMagicExtfs filesystem id for Extfs
|
||||
FsMagicExtfs = FsMagic(0x0000EF53)
|
||||
// FsMagicF2fs filesystem id for F2fs
|
||||
FsMagicF2fs = FsMagic(0xF2F52010)
|
||||
// FsMagicGPFS filesystem id for GPFS
|
||||
FsMagicGPFS = FsMagic(0x47504653)
|
||||
// FsMagicJffs2Fs filesystem if for Jffs2Fs
|
||||
FsMagicJffs2Fs = FsMagic(0x000072b6)
|
||||
// FsMagicJfs filesystem id for Jfs
|
||||
FsMagicJfs = FsMagic(0x3153464a)
|
||||
// FsMagicNfsFs filesystem id for NfsFs
|
||||
FsMagicNfsFs = FsMagic(0x00006969)
|
||||
// FsMagicRAMFs filesystem id for RamFs
|
||||
FsMagicRAMFs = FsMagic(0x858458f6)
|
||||
// FsMagicReiserFs filesystem id for ReiserFs
|
||||
FsMagicReiserFs = FsMagic(0x52654973)
|
||||
// FsMagicSmbFs filesystem id for SmbFs
|
||||
FsMagicSmbFs = FsMagic(0x0000517B)
|
||||
// FsMagicSquashFs filesystem id for SquashFs
|
||||
FsMagicSquashFs = FsMagic(0x73717368)
|
||||
// FsMagicTmpFs filesystem id for TmpFs
|
||||
FsMagicTmpFs = FsMagic(0x01021994)
|
||||
// FsMagicVxFS filesystem id for VxFs
|
||||
FsMagicVxFS = FsMagic(0xa501fcf5)
|
||||
// FsMagicXfs filesystem id for Xfs
|
||||
FsMagicXfs = FsMagic(0x58465342)
|
||||
// FsMagicZfs filesystem id for Zfs
|
||||
FsMagicZfs = FsMagic(0x2fc12fc1)
|
||||
// FsMagicOverlay filesystem id for overlay
|
||||
FsMagicOverlay = FsMagic(0x794C7630)
|
||||
// FsMagicFUSE filesystem id for FUSE
|
||||
FsMagicFUSE = FsMagic(0x65735546)
|
||||
// FsMagicAcfs filesystem id for Acfs
|
||||
FsMagicAcfs = FsMagic(0x61636673)
|
||||
// FsMagicAfs filesystem id for Afs
|
||||
FsMagicAfs = FsMagic(0x5346414f)
|
||||
// FsMagicCephFs filesystem id for Ceph
|
||||
FsMagicCephFs = FsMagic(0x00C36400)
|
||||
// FsMagicCIFS filesystem id for CIFS
|
||||
FsMagicCIFS = FsMagic(0xFF534D42)
|
||||
// FsMagicEROFS filesystem id for EROFS
|
||||
FsMagicEROFS = FsMagic(0xE0F5E1E2)
|
||||
// FsMagicFHGFS filesystem id for FHGFS
|
||||
FsMagicFHGFSFs = FsMagic(0x19830326)
|
||||
// FsMagicIBRIX filesystem id for IBRIX
|
||||
FsMagicIBRIX = FsMagic(0x013111A8)
|
||||
// FsMagicKAFS filesystem id for KAFS
|
||||
FsMagicKAFS = FsMagic(0x6B414653)
|
||||
// FsMagicLUSTRE filesystem id for LUSTRE
|
||||
FsMagicLUSTRE = FsMagic(0x0BD00BD0)
|
||||
// FsMagicNCP filesystem id for NCP
|
||||
FsMagicNCP = FsMagic(0x564C)
|
||||
// FsMagicNFSD filesystem id for NFSD
|
||||
FsMagicNFSD = FsMagic(0x6E667364)
|
||||
// FsMagicOCFS2 filesystem id for OCFS2
|
||||
FsMagicOCFS2 = FsMagic(0x7461636F)
|
||||
// FsMagicPANFS filesystem id for PANFS
|
||||
FsMagicPANFS = FsMagic(0xAAD7AAEA)
|
||||
// FsMagicPRLFS filesystem id for PRLFS
|
||||
FsMagicPRLFS = FsMagic(0x7C7C6673)
|
||||
// FsMagicSMB2 filesystem id for SMB2
|
||||
FsMagicSMB2 = FsMagic(0xFE534D42)
|
||||
// FsMagicSNFS filesystem id for SNFS
|
||||
FsMagicSNFS = FsMagic(0xBEEFDEAD)
|
||||
// FsMagicVBOXSF filesystem id for VBOXSF
|
||||
FsMagicVBOXSF = FsMagic(0x786F4256)
|
||||
// FsMagicVXFS filesystem id for VXFS
|
||||
FsMagicVXFS = FsMagic(0xA501FCF5)
|
||||
)
|
||||
|
||||
var (
|
||||
// Slice of drivers that should be used in an order
|
||||
Priority = []string{
|
||||
"overlay",
|
||||
// We don't support devicemapper without configuration
|
||||
// "devicemapper",
|
||||
"aufs",
|
||||
"btrfs",
|
||||
"zfs",
|
||||
"vfs",
|
||||
}
|
||||
|
||||
// FsNames maps filesystem id to name of the filesystem.
|
||||
FsNames = map[FsMagic]string{
|
||||
FsMagicAufs: "aufs",
|
||||
FsMagicBtrfs: "btrfs",
|
||||
FsMagicCramfs: "cramfs",
|
||||
FsMagicEcryptfs: "ecryptfs",
|
||||
FsMagicEROFS: "erofs",
|
||||
FsMagicExtfs: "extfs",
|
||||
FsMagicF2fs: "f2fs",
|
||||
FsMagicGPFS: "gpfs",
|
||||
FsMagicJffs2Fs: "jffs2",
|
||||
FsMagicJfs: "jfs",
|
||||
FsMagicNfsFs: "nfs",
|
||||
FsMagicOverlay: "overlayfs",
|
||||
FsMagicRAMFs: "ramfs",
|
||||
FsMagicReiserFs: "reiserfs",
|
||||
FsMagicSmbFs: "smb",
|
||||
FsMagicSquashFs: "squashfs",
|
||||
FsMagicTmpFs: "tmpfs",
|
||||
FsMagicUnsupported: "unsupported",
|
||||
FsMagicVxFS: "vxfs",
|
||||
FsMagicXfs: "xfs",
|
||||
FsMagicZfs: "zfs",
|
||||
}
|
||||
)
|
||||
|
||||
// GetFSMagic returns the filesystem id given the path.
|
||||
func GetFSMagic(rootpath string) (FsMagic, error) {
|
||||
var buf unix.Statfs_t
|
||||
path := filepath.Dir(rootpath)
|
||||
if err := unix.Statfs(path, &buf); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if _, ok := FsNames[FsMagic(buf.Type)]; !ok {
|
||||
logrus.Debugf("Unknown filesystem type %#x reported for %s", buf.Type, path)
|
||||
}
|
||||
return FsMagic(buf.Type), nil
|
||||
}
|
||||
|
||||
// NewFsChecker returns a checker configured for the provided FsMagic
|
||||
func NewFsChecker(t FsMagic) Checker {
|
||||
return &fsChecker{
|
||||
t: t,
|
||||
}
|
||||
}
|
||||
|
||||
type fsChecker struct {
|
||||
t FsMagic
|
||||
}
|
||||
|
||||
func (c *fsChecker) IsMounted(path string) bool {
|
||||
m, _ := Mounted(c.t, path)
|
||||
return m
|
||||
}
|
||||
|
||||
// NewDefaultChecker returns a check that parses /proc/mountinfo to check
|
||||
// if the specified path is mounted.
|
||||
func NewDefaultChecker() Checker {
|
||||
return &defaultChecker{}
|
||||
}
|
||||
|
||||
type defaultChecker struct{}
|
||||
|
||||
func (c *defaultChecker) IsMounted(path string) bool {
|
||||
m, _ := mount.Mounted(path)
|
||||
return m
|
||||
}
|
||||
|
||||
// isMountPoint checks that the given path is a mount point
|
||||
func isMountPoint(mountPath string) (bool, error) {
|
||||
// it is already the root
|
||||
if mountPath == "/" {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
var s1, s2 unix.Stat_t
|
||||
if err := unix.Stat(mountPath, &s1); err != nil {
|
||||
return true, err
|
||||
}
|
||||
if err := unix.Stat(filepath.Dir(mountPath), &s2); err != nil {
|
||||
return true, err
|
||||
}
|
||||
return s1.Dev != s2.Dev, nil
|
||||
}
|
||||
|
||||
// Mounted checks if the given path is mounted as the fs type
|
||||
func Mounted(fsType FsMagic, mountPath string) (bool, error) {
|
||||
var buf unix.Statfs_t
|
||||
|
||||
if err := unix.Statfs(mountPath, &buf); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if FsMagic(buf.Type) != fsType {
|
||||
return false, nil
|
||||
}
|
||||
return isMountPoint(mountPath)
|
||||
}
|
||||
96
vendor/github.com/containers/storage/drivers/driver_solaris.go
generated
vendored
Normal file
96
vendor/github.com/containers/storage/drivers/driver_solaris.go
generated
vendored
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
//go:build solaris && cgo
|
||||
// +build solaris,cgo
|
||||
|
||||
package graphdriver
|
||||
|
||||
/*
|
||||
#include <sys/statvfs.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
static inline struct statvfs *getstatfs(char *s) {
|
||||
struct statvfs *buf;
|
||||
int err;
|
||||
buf = (struct statvfs *)malloc(sizeof(struct statvfs));
|
||||
err = statvfs(s, buf);
|
||||
return buf;
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"unsafe"
|
||||
|
||||
"github.com/containers/storage/pkg/mount"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
// FsMagicZfs filesystem id for Zfs
|
||||
FsMagicZfs = FsMagic(0x2fc12fc1)
|
||||
)
|
||||
|
||||
var (
|
||||
// Slice of drivers that should be used in an order
|
||||
Priority = []string{
|
||||
"zfs",
|
||||
}
|
||||
|
||||
// FsNames maps filesystem id to name of the filesystem.
|
||||
FsNames = map[FsMagic]string{
|
||||
FsMagicZfs: "zfs",
|
||||
}
|
||||
)
|
||||
|
||||
// GetFSMagic returns the filesystem id given the path.
|
||||
func GetFSMagic(rootpath string) (FsMagic, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
type fsChecker struct {
|
||||
t FsMagic
|
||||
}
|
||||
|
||||
func (c *fsChecker) IsMounted(path string) bool {
|
||||
m, _ := Mounted(c.t, path)
|
||||
return m
|
||||
}
|
||||
|
||||
// NewFsChecker returns a checker configured for the provided FsMagic
|
||||
func NewFsChecker(t FsMagic) Checker {
|
||||
return &fsChecker{
|
||||
t: t,
|
||||
}
|
||||
}
|
||||
|
||||
// NewDefaultChecker returns a check that parses /proc/mountinfo to check
|
||||
// if the specified path is mounted.
|
||||
// No-op on Solaris.
|
||||
func NewDefaultChecker() Checker {
|
||||
return &defaultChecker{}
|
||||
}
|
||||
|
||||
type defaultChecker struct{}
|
||||
|
||||
func (c *defaultChecker) IsMounted(path string) bool {
|
||||
m, _ := mount.Mounted(path)
|
||||
return m
|
||||
}
|
||||
|
||||
// Mounted checks if the given path is mounted as the fs type
|
||||
// Solaris supports only ZFS for now
|
||||
func Mounted(fsType FsMagic, mountPath string) (bool, error) {
|
||||
cs := C.CString(filepath.Dir(mountPath))
|
||||
defer C.free(unsafe.Pointer(cs))
|
||||
buf := C.getstatfs(cs)
|
||||
defer C.free(unsafe.Pointer(buf))
|
||||
|
||||
// on Solaris buf.f_basetype contains ['z', 'f', 's', 0 ... ]
|
||||
if (buf.f_basetype[0] != 122) || (buf.f_basetype[1] != 102) || (buf.f_basetype[2] != 115) ||
|
||||
(buf.f_basetype[3] != 0) {
|
||||
logrus.Debugf("[zfs] no zfs dataset found for rootdir '%s'", mountPath)
|
||||
return false, ErrPrerequisites
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
14
vendor/github.com/containers/storage/drivers/driver_unsupported.go
generated
vendored
Normal file
14
vendor/github.com/containers/storage/drivers/driver_unsupported.go
generated
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
//go:build !linux && !windows && !freebsd && !solaris && !darwin
|
||||
// +build !linux,!windows,!freebsd,!solaris,!darwin
|
||||
|
||||
package graphdriver
|
||||
|
||||
// Slice of drivers that should be used in an order
|
||||
var Priority = []string{
|
||||
"unsupported",
|
||||
}
|
||||
|
||||
// GetFSMagic returns the filesystem id given the path.
|
||||
func GetFSMagic(rootpath string) (FsMagic, error) {
|
||||
return FsMagicUnsupported, nil
|
||||
}
|
||||
12
vendor/github.com/containers/storage/drivers/driver_windows.go
generated
vendored
Normal file
12
vendor/github.com/containers/storage/drivers/driver_windows.go
generated
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
package graphdriver
|
||||
|
||||
// Slice of drivers that should be used in order
|
||||
var Priority = []string{
|
||||
"windowsfilter",
|
||||
}
|
||||
|
||||
// GetFSMagic returns the filesystem id given the path.
|
||||
func GetFSMagic(rootpath string) (FsMagic, error) {
|
||||
// Note it is OK to return FsMagicUnsupported on Windows.
|
||||
return FsMagicUnsupported, nil
|
||||
}
|
||||
229
vendor/github.com/containers/storage/drivers/fsdiff.go
generated
vendored
Normal file
229
vendor/github.com/containers/storage/drivers/fsdiff.go
generated
vendored
Normal file
|
|
@ -0,0 +1,229 @@
|
|||
package graphdriver
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/containers/storage/pkg/archive"
|
||||
"github.com/containers/storage/pkg/chrootarchive"
|
||||
"github.com/containers/storage/pkg/idtools"
|
||||
"github.com/containers/storage/pkg/ioutils"
|
||||
"github.com/containers/storage/pkg/unshare"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// ApplyUncompressedLayer defines the unpack method used by the graph
|
||||
// driver.
|
||||
var ApplyUncompressedLayer = chrootarchive.ApplyUncompressedLayer
|
||||
|
||||
// NaiveDiffDriver takes a ProtoDriver and adds the
|
||||
// capability of the Diffing methods which it may or may not
|
||||
// support on its own. See the comment on the exported
|
||||
// NewNaiveDiffDriver function below.
|
||||
// Notably, the AUFS driver doesn't need to be wrapped like this.
|
||||
type NaiveDiffDriver struct {
|
||||
ProtoDriver
|
||||
LayerIDMapUpdater
|
||||
}
|
||||
|
||||
// NewNaiveDiffDriver returns a fully functional driver that wraps the
|
||||
// given ProtoDriver and adds the capability of the following methods which
|
||||
// it may or may not support on its own:
|
||||
//
|
||||
// Diff(id string, idMappings *idtools.IDMappings, parent string, parentMappings *idtools.IDMappings, mountLabel string) (io.ReadCloser, error)
|
||||
// Changes(id string, idMappings *idtools.IDMappings, parent string, parentMappings *idtools.IDMappings, mountLabel string) ([]archive.Change, error)
|
||||
// ApplyDiff(id, parent string, options ApplyDiffOpts) (size int64, err error)
|
||||
// DiffSize(id string, idMappings *idtools.IDMappings, parent, parentMappings *idtools.IDMappings, mountLabel string) (size int64, err error)
|
||||
func NewNaiveDiffDriver(driver ProtoDriver, updater LayerIDMapUpdater) Driver {
|
||||
return &NaiveDiffDriver{ProtoDriver: driver, LayerIDMapUpdater: updater}
|
||||
}
|
||||
|
||||
// Diff produces an archive of the changes between the specified
|
||||
// layer and its parent layer which may be "".
|
||||
func (gdw *NaiveDiffDriver) Diff(id string, idMappings *idtools.IDMappings, parent string, parentMappings *idtools.IDMappings, mountLabel string) (arch io.ReadCloser, err error) {
|
||||
startTime := time.Now()
|
||||
driver := gdw.ProtoDriver
|
||||
|
||||
if idMappings == nil {
|
||||
idMappings = &idtools.IDMappings{}
|
||||
}
|
||||
if parentMappings == nil {
|
||||
parentMappings = &idtools.IDMappings{}
|
||||
}
|
||||
|
||||
options := MountOpts{
|
||||
MountLabel: mountLabel,
|
||||
Options: []string{"ro"},
|
||||
}
|
||||
layerFs, err := driver.Get(id, options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
driverPut(driver, id, &err)
|
||||
}
|
||||
}()
|
||||
|
||||
if parent == "" {
|
||||
archive, err := archive.TarWithOptions(layerFs, &archive.TarOptions{
|
||||
Compression: archive.Uncompressed,
|
||||
UIDMaps: idMappings.UIDs(),
|
||||
GIDMaps: idMappings.GIDs(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ioutils.NewReadCloserWrapper(archive, func() error {
|
||||
err := archive.Close()
|
||||
driverPut(driver, id, &err)
|
||||
return err
|
||||
}), nil
|
||||
}
|
||||
|
||||
options.Options = append(options.Options, "ro")
|
||||
parentFs, err := driver.Get(parent, options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer driverPut(driver, parent, &err)
|
||||
|
||||
changes, err := archive.ChangesDirs(layerFs, idMappings, parentFs, parentMappings)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
archive, err := archive.ExportChanges(layerFs, changes, idMappings.UIDs(), idMappings.GIDs())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ioutils.NewReadCloserWrapper(archive, func() error {
|
||||
err := archive.Close()
|
||||
driverPut(driver, id, &err)
|
||||
|
||||
// NaiveDiffDriver compares file metadata with parent layers. Parent layers
|
||||
// are extracted from tar's with full second precision on modified time.
|
||||
// We need this hack here to make sure calls within same second receive
|
||||
// correct result.
|
||||
time.Sleep(time.Until(startTime.Truncate(time.Second).Add(time.Second)))
|
||||
return err
|
||||
}), nil
|
||||
}
|
||||
|
||||
// Changes produces a list of changes between the specified layer
|
||||
// and its parent layer. If parent is "", then all changes will be ADD changes.
|
||||
func (gdw *NaiveDiffDriver) Changes(id string, idMappings *idtools.IDMappings, parent string, parentMappings *idtools.IDMappings, mountLabel string) (_ []archive.Change, retErr error) {
|
||||
driver := gdw.ProtoDriver
|
||||
|
||||
if idMappings == nil {
|
||||
idMappings = &idtools.IDMappings{}
|
||||
}
|
||||
if parentMappings == nil {
|
||||
parentMappings = &idtools.IDMappings{}
|
||||
}
|
||||
|
||||
options := MountOpts{
|
||||
MountLabel: mountLabel,
|
||||
}
|
||||
layerFs, err := driver.Get(id, options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer driverPut(driver, id, &retErr)
|
||||
|
||||
parentFs := ""
|
||||
|
||||
if parent != "" {
|
||||
options := MountOpts{
|
||||
MountLabel: mountLabel,
|
||||
Options: []string{"ro"},
|
||||
}
|
||||
parentFs, err = driver.Get(parent, options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer driverPut(driver, parent, &retErr)
|
||||
}
|
||||
|
||||
return archive.ChangesDirs(layerFs, idMappings, parentFs, parentMappings)
|
||||
}
|
||||
|
||||
// ApplyDiff extracts the changeset from the given diff into the
|
||||
// layer with the specified id and parent, returning the size of the
|
||||
// new layer in bytes.
|
||||
func (gdw *NaiveDiffDriver) ApplyDiff(id, parent string, options ApplyDiffOpts) (size int64, err error) {
|
||||
driver := gdw.ProtoDriver
|
||||
|
||||
if options.Mappings == nil {
|
||||
options.Mappings = &idtools.IDMappings{}
|
||||
}
|
||||
|
||||
// Mount the root filesystem so we can apply the diff/layer.
|
||||
mountOpts := MountOpts{
|
||||
MountLabel: options.MountLabel,
|
||||
}
|
||||
layerFs, err := driver.Get(id, mountOpts)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer driverPut(driver, id, &err)
|
||||
|
||||
defaultForceMask := os.FileMode(0o700)
|
||||
var forceMask *os.FileMode // = nil
|
||||
if runtime.GOOS == "darwin" {
|
||||
forceMask = &defaultForceMask
|
||||
}
|
||||
|
||||
tarOptions := &archive.TarOptions{
|
||||
InUserNS: unshare.IsRootless(),
|
||||
IgnoreChownErrors: options.IgnoreChownErrors,
|
||||
ForceMask: forceMask,
|
||||
}
|
||||
if options.Mappings != nil {
|
||||
tarOptions.UIDMaps = options.Mappings.UIDs()
|
||||
tarOptions.GIDMaps = options.Mappings.GIDs()
|
||||
}
|
||||
start := time.Now().UTC()
|
||||
logrus.Debug("Start untar layer")
|
||||
if size, err = ApplyUncompressedLayer(layerFs, options.Diff, tarOptions); err != nil {
|
||||
logrus.Errorf("While applying layer: %s", err)
|
||||
return
|
||||
}
|
||||
logrus.Debugf("Untar time: %vs", time.Now().UTC().Sub(start).Seconds())
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// DiffSize calculates the changes between the specified layer
|
||||
// and its parent and returns the size in bytes of the changes
|
||||
// relative to its base filesystem directory.
|
||||
func (gdw *NaiveDiffDriver) DiffSize(id string, idMappings *idtools.IDMappings, parent string, parentMappings *idtools.IDMappings, mountLabel string) (size int64, err error) {
|
||||
driver := gdw.ProtoDriver
|
||||
|
||||
if idMappings == nil {
|
||||
idMappings = &idtools.IDMappings{}
|
||||
}
|
||||
if parentMappings == nil {
|
||||
parentMappings = &idtools.IDMappings{}
|
||||
}
|
||||
|
||||
changes, err := gdw.Changes(id, idMappings, parent, parentMappings, mountLabel)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
options := MountOpts{
|
||||
MountLabel: mountLabel,
|
||||
}
|
||||
layerFs, err := driver.Get(id, options)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer driverPut(driver, id, &err)
|
||||
|
||||
return archive.ChangesSize(layerFs, changes), nil
|
||||
}
|
||||
5
vendor/github.com/containers/storage/drivers/jsoniter.go
generated
vendored
Normal file
5
vendor/github.com/containers/storage/drivers/jsoniter.go
generated
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
package graphdriver
|
||||
|
||||
import jsoniter "github.com/json-iterator/go"
|
||||
|
||||
var json = jsoniter.ConfigCompatibleWithStandardLibrary
|
||||
310
vendor/github.com/containers/storage/drivers/overlay/check.go
generated
vendored
Normal file
310
vendor/github.com/containers/storage/drivers/overlay/check.go
generated
vendored
Normal file
|
|
@ -0,0 +1,310 @@
|
|||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package overlay
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
|
||||
"github.com/containers/storage/pkg/archive"
|
||||
"github.com/containers/storage/pkg/idmap"
|
||||
"github.com/containers/storage/pkg/idtools"
|
||||
"github.com/containers/storage/pkg/ioutils"
|
||||
"github.com/containers/storage/pkg/mount"
|
||||
"github.com/containers/storage/pkg/system"
|
||||
"github.com/containers/storage/pkg/unshare"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// doesSupportNativeDiff checks whether the filesystem has a bug
|
||||
// which copies up the opaque flag when copying up an opaque
|
||||
// directory or the kernel enable CONFIG_OVERLAY_FS_REDIRECT_DIR.
|
||||
// When these exist naive diff should be used.
|
||||
func doesSupportNativeDiff(d, mountOpts string) error {
|
||||
td, err := os.MkdirTemp(d, "opaque-bug-check")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if err := os.RemoveAll(td); err != nil {
|
||||
logrus.Warnf("Failed to remove check directory %v: %v", td, err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Make directories l1/d, l1/d1, l2/d, l3, work, merged
|
||||
if err := os.MkdirAll(filepath.Join(td, "l1", "d"), 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Join(td, "l1", "d1"), 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Join(td, "l2", "d"), 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.Mkdir(filepath.Join(td, "l3"), 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.Mkdir(filepath.Join(td, "work"), 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.Mkdir(filepath.Join(td, "merged"), 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Mark l2/d as opaque
|
||||
if err := system.Lsetxattr(filepath.Join(td, "l2", "d"), archive.GetOverlayXattrName("opaque"), []byte("y"), 0); err != nil {
|
||||
return fmt.Errorf("failed to set opaque flag on middle layer: %w", err)
|
||||
}
|
||||
|
||||
mountFlags := "lowerdir=%s:%s,upperdir=%s,workdir=%s"
|
||||
if unshare.IsRootless() {
|
||||
mountFlags = mountFlags + ",userxattr"
|
||||
}
|
||||
|
||||
opts := fmt.Sprintf(mountFlags, path.Join(td, "l2"), path.Join(td, "l1"), path.Join(td, "l3"), path.Join(td, "work"))
|
||||
flags, data := mount.ParseOptions(mountOpts)
|
||||
if data != "" {
|
||||
opts = fmt.Sprintf("%s,%s", opts, data)
|
||||
}
|
||||
if err := unix.Mount("overlay", filepath.Join(td, "merged"), "overlay", uintptr(flags), opts); err != nil {
|
||||
return fmt.Errorf("failed to mount overlay: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
if err := unix.Unmount(filepath.Join(td, "merged"), 0); err != nil {
|
||||
logrus.Warnf("Failed to unmount check directory %v: %v", filepath.Join(td, "merged"), err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Touch file in d to force copy up of opaque directory "d" from "l2" to "l3"
|
||||
if err := os.WriteFile(filepath.Join(td, "merged", "d", "f"), []byte{}, 0o644); err != nil {
|
||||
return fmt.Errorf("failed to write to merged directory: %w", err)
|
||||
}
|
||||
|
||||
// Check l3/d does not have opaque flag
|
||||
xattrOpaque, err := system.Lgetxattr(filepath.Join(td, "l3", "d"), archive.GetOverlayXattrName("opaque"))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read opaque flag on upper layer: %w", err)
|
||||
}
|
||||
if string(xattrOpaque) == "y" {
|
||||
return errors.New("opaque flag erroneously copied up, consider update to kernel 4.8 or later to fix")
|
||||
}
|
||||
|
||||
// rename "d1" to "d2"
|
||||
if err := os.Rename(filepath.Join(td, "merged", "d1"), filepath.Join(td, "merged", "d2")); err != nil {
|
||||
// if rename failed with syscall.EXDEV, the kernel doesn't have CONFIG_OVERLAY_FS_REDIRECT_DIR enabled
|
||||
if err.(*os.LinkError).Err == syscall.EXDEV {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("failed to rename dir in merged directory: %w", err)
|
||||
}
|
||||
// get the xattr of "d2"
|
||||
xattrRedirect, err := system.Lgetxattr(filepath.Join(td, "l3", "d2"), archive.GetOverlayXattrName("redirect"))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read redirect flag on upper layer: %w", err)
|
||||
}
|
||||
|
||||
if string(xattrRedirect) == "d1" {
|
||||
return errors.New("kernel has CONFIG_OVERLAY_FS_REDIRECT_DIR enabled")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// doesMetacopy checks if the filesystem is going to optimize changes to
|
||||
// metadata by using nodes marked with an "overlay.metacopy" attribute to avoid
|
||||
// copying up a file from a lower layer unless/until its contents are being
|
||||
// modified
|
||||
func doesMetacopy(d, mountOpts string) (bool, error) {
|
||||
td, err := os.MkdirTemp(d, "metacopy-check")
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer func() {
|
||||
if err := os.RemoveAll(td); err != nil {
|
||||
logrus.Warnf("Failed to remove check directory %v: %v", td, err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Make directories l1, l2, work, merged
|
||||
if err := os.MkdirAll(filepath.Join(td, "l1"), 0o755); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if err := ioutils.AtomicWriteFile(filepath.Join(td, "l1", "f"), []byte{0xff}, 0o700); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Join(td, "l2"), 0o755); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if err := os.Mkdir(filepath.Join(td, "work"), 0o755); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if err := os.Mkdir(filepath.Join(td, "merged"), 0o755); err != nil {
|
||||
return false, err
|
||||
}
|
||||
// Mount using the mandatory options and configured options
|
||||
opts := fmt.Sprintf("lowerdir=%s,upperdir=%s,workdir=%s", path.Join(td, "l1"), path.Join(td, "l2"), path.Join(td, "work"))
|
||||
if unshare.IsRootless() {
|
||||
opts = fmt.Sprintf("%s,userxattr", opts)
|
||||
}
|
||||
flags, data := mount.ParseOptions(mountOpts)
|
||||
if data != "" {
|
||||
opts = fmt.Sprintf("%s,%s", opts, data)
|
||||
}
|
||||
if err := unix.Mount("overlay", filepath.Join(td, "merged"), "overlay", uintptr(flags), opts); err != nil {
|
||||
if errors.Is(err, unix.EINVAL) {
|
||||
logrus.Infof("overlay: metacopy option not supported on this kernel, checked using options %q", mountOpts)
|
||||
return false, nil
|
||||
}
|
||||
return false, fmt.Errorf("failed to mount overlay for metacopy check with %q options: %w", mountOpts, err)
|
||||
}
|
||||
defer func() {
|
||||
if err := unix.Unmount(filepath.Join(td, "merged"), 0); err != nil {
|
||||
logrus.Warnf("Failed to unmount check directory %v: %v", filepath.Join(td, "merged"), err)
|
||||
}
|
||||
}()
|
||||
// Make a change that only impacts the inode, and check if the pulled-up copy is marked
|
||||
// as a metadata-only copy
|
||||
if err := os.Chmod(filepath.Join(td, "merged", "f"), 0o600); err != nil {
|
||||
return false, fmt.Errorf("changing permissions on file for metacopy check: %w", err)
|
||||
}
|
||||
metacopy, err := system.Lgetxattr(filepath.Join(td, "l2", "f"), archive.GetOverlayXattrName("metacopy"))
|
||||
if err != nil {
|
||||
if errors.Is(err, unix.ENOTSUP) {
|
||||
logrus.Info("metacopy option not supported")
|
||||
return false, nil
|
||||
}
|
||||
return false, fmt.Errorf("metacopy flag was not set on file in upper layer: %w", err)
|
||||
}
|
||||
return metacopy != nil, nil
|
||||
}
|
||||
|
||||
// doesVolatile checks if the filesystem supports the "volatile" mount option
|
||||
func doesVolatile(d string) (bool, error) {
|
||||
td, err := os.MkdirTemp(d, "volatile-check")
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer func() {
|
||||
if err := os.RemoveAll(td); err != nil {
|
||||
logrus.Warnf("Failed to remove check directory %v: %v", td, err)
|
||||
}
|
||||
}()
|
||||
|
||||
if err := os.MkdirAll(filepath.Join(td, "lower"), 0o755); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Join(td, "upper"), 0o755); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if err := os.Mkdir(filepath.Join(td, "work"), 0o755); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if err := os.Mkdir(filepath.Join(td, "merged"), 0o755); err != nil {
|
||||
return false, err
|
||||
}
|
||||
// Mount using the mandatory options and configured options
|
||||
opts := fmt.Sprintf("volatile,lowerdir=%s,upperdir=%s,workdir=%s", path.Join(td, "lower"), path.Join(td, "upper"), path.Join(td, "work"))
|
||||
if unshare.IsRootless() {
|
||||
opts = fmt.Sprintf("%s,userxattr", opts)
|
||||
}
|
||||
if err := unix.Mount("overlay", filepath.Join(td, "merged"), "overlay", 0, opts); err != nil {
|
||||
return false, fmt.Errorf("failed to mount overlay for volatile check: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
if err := unix.Unmount(filepath.Join(td, "merged"), 0); err != nil {
|
||||
logrus.Warnf("Failed to unmount check directory %v: %v", filepath.Join(td, "merged"), err)
|
||||
}
|
||||
}()
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// supportsIdmappedLowerLayers checks if the kernel supports mounting overlay on top of
|
||||
// a idmapped lower layer.
|
||||
func supportsIdmappedLowerLayers(home string) (bool, error) {
|
||||
layerDir, err := os.MkdirTemp(home, "compat")
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer func() {
|
||||
_ = os.RemoveAll(layerDir)
|
||||
}()
|
||||
|
||||
mergedDir := filepath.Join(layerDir, "merged")
|
||||
lowerDir := filepath.Join(layerDir, "lower")
|
||||
lowerMappedDir := filepath.Join(layerDir, "lower-mapped")
|
||||
upperDir := filepath.Join(layerDir, "upper")
|
||||
workDir := filepath.Join(layerDir, "work")
|
||||
|
||||
_ = idtools.MkdirAs(mergedDir, 0o700, 0, 0)
|
||||
_ = idtools.MkdirAs(lowerDir, 0o700, 0, 0)
|
||||
_ = idtools.MkdirAs(lowerMappedDir, 0o700, 0, 0)
|
||||
_ = idtools.MkdirAs(upperDir, 0o700, 0, 0)
|
||||
_ = idtools.MkdirAs(workDir, 0o700, 0, 0)
|
||||
|
||||
mapping := []idtools.IDMap{
|
||||
{
|
||||
ContainerID: 0,
|
||||
HostID: 0,
|
||||
Size: 1,
|
||||
},
|
||||
}
|
||||
pid, cleanupFunc, err := idmap.CreateUsernsProcess(mapping, mapping)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer cleanupFunc()
|
||||
|
||||
if err := idmap.CreateIDMappedMount(lowerDir, lowerMappedDir, int(pid)); err != nil {
|
||||
return false, fmt.Errorf("create mapped mount: %w", err)
|
||||
}
|
||||
defer unix.Unmount(lowerMappedDir, unix.MNT_DETACH)
|
||||
|
||||
opts := fmt.Sprintf("lowerdir=%s,upperdir=%s,workdir=%s", lowerMappedDir, upperDir, workDir)
|
||||
flags := uintptr(0)
|
||||
if err := unix.Mount("overlay", mergedDir, "overlay", flags, opts); err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer func() {
|
||||
_ = unix.Unmount(mergedDir, unix.MNT_DETACH)
|
||||
}()
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// supportsDataOnlyLayers checks if the kernel supports mounting a overlay file system
|
||||
// that uses data-only layers.
|
||||
func supportsDataOnlyLayers(home string) (bool, error) {
|
||||
layerDir, err := os.MkdirTemp(home, "compat")
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer func() {
|
||||
_ = os.RemoveAll(layerDir)
|
||||
}()
|
||||
|
||||
mergedDir := filepath.Join(layerDir, "merged")
|
||||
lowerDir := filepath.Join(layerDir, "lower")
|
||||
lowerDirDataOnly := filepath.Join(layerDir, "lower-data")
|
||||
upperDir := filepath.Join(layerDir, "upper")
|
||||
workDir := filepath.Join(layerDir, "work")
|
||||
|
||||
_ = idtools.MkdirAs(mergedDir, 0o700, 0, 0)
|
||||
_ = idtools.MkdirAs(lowerDir, 0o700, 0, 0)
|
||||
_ = idtools.MkdirAs(lowerDirDataOnly, 0o700, 0, 0)
|
||||
_ = idtools.MkdirAs(upperDir, 0o700, 0, 0)
|
||||
_ = idtools.MkdirAs(workDir, 0o700, 0, 0)
|
||||
|
||||
opts := fmt.Sprintf("lowerdir=%s::%s,upperdir=%s,workdir=%s,metacopy=on", lowerDir, lowerDirDataOnly, upperDir, workDir)
|
||||
flags := uintptr(0)
|
||||
if err := unix.Mount("overlay", mergedDir, "overlay", flags, opts); err != nil {
|
||||
return false, err
|
||||
}
|
||||
_ = unix.Unmount(mergedDir, unix.MNT_DETACH)
|
||||
|
||||
return true, nil
|
||||
}
|
||||
45
vendor/github.com/containers/storage/drivers/overlay/check_116.go
generated
vendored
Normal file
45
vendor/github.com/containers/storage/drivers/overlay/check_116.go
generated
vendored
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package overlay
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/fs"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"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) {
|
||||
err = filepath.WalkDir(home, func(path string, d fs.DirEntry, err error) error {
|
||||
if detected {
|
||||
return fs.SkipDir
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
basename := filepath.Base(path)
|
||||
if strings.HasPrefix(basename, archive.WhiteoutPrefix) {
|
||||
detected = true
|
||||
return fs.SkipDir
|
||||
}
|
||||
if d.IsDir() {
|
||||
xattrs, err := system.Llistxattr(path)
|
||||
if err != nil && !errors.Is(err, unix.EOPNOTSUPP) {
|
||||
return err
|
||||
}
|
||||
for _, xattr := range xattrs {
|
||||
if strings.HasPrefix(xattr, "user.fuseoverlayfs.") || strings.HasPrefix(xattr, "user.containers.") {
|
||||
detected = true
|
||||
return fs.SkipDir
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return detected, err
|
||||
}
|
||||
24
vendor/github.com/containers/storage/drivers/overlay/composefs_notsupported.go
generated
vendored
Normal file
24
vendor/github.com/containers/storage/drivers/overlay/composefs_notsupported.go
generated
vendored
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
//go:build !linux || !composefs || !cgo
|
||||
// +build !linux !composefs !cgo
|
||||
|
||||
package overlay
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func composeFsSupported() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func generateComposeFsBlob(verityDigests map[string]string, toc interface{}, composefsDir string) error {
|
||||
return fmt.Errorf("composefs is not supported")
|
||||
}
|
||||
|
||||
func mountComposefsBlob(dataDir, mountPoint string) error {
|
||||
return fmt.Errorf("composefs is not supported")
|
||||
}
|
||||
|
||||
func enableVerityRecursive(path string) (map[string]string, error) {
|
||||
return nil, fmt.Errorf("composefs is not supported")
|
||||
}
|
||||
219
vendor/github.com/containers/storage/drivers/overlay/composefs_supported.go
generated
vendored
Normal file
219
vendor/github.com/containers/storage/drivers/overlay/composefs_supported.go
generated
vendored
Normal file
|
|
@ -0,0 +1,219 @@
|
|||
//go:build linux && composefs && cgo
|
||||
// +build linux,composefs,cgo
|
||||
|
||||
package overlay
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/containers/storage/pkg/chunked/dump"
|
||||
"github.com/containers/storage/pkg/loopback"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
var (
|
||||
composeFsHelperOnce sync.Once
|
||||
composeFsHelperPath string
|
||||
composeFsHelperErr error
|
||||
)
|
||||
|
||||
func getComposeFsHelper() (string, error) {
|
||||
composeFsHelperOnce.Do(func() {
|
||||
composeFsHelperPath, composeFsHelperErr = exec.LookPath("mkcomposefs")
|
||||
})
|
||||
return composeFsHelperPath, composeFsHelperErr
|
||||
}
|
||||
|
||||
func composeFsSupported() bool {
|
||||
_, err := getComposeFsHelper()
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func enableVerity(description string, fd int) error {
|
||||
enableArg := unix.FsverityEnableArg{
|
||||
Version: 1,
|
||||
Hash_algorithm: unix.FS_VERITY_HASH_ALG_SHA256,
|
||||
Block_size: 4096,
|
||||
}
|
||||
|
||||
_, _, e1 := syscall.Syscall(unix.SYS_IOCTL, uintptr(fd), uintptr(unix.FS_IOC_ENABLE_VERITY), uintptr(unsafe.Pointer(&enableArg)))
|
||||
if e1 != 0 && !errors.Is(e1, unix.EEXIST) {
|
||||
return fmt.Errorf("failed to enable verity for %q: %w", description, e1)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type verityDigest struct {
|
||||
Fsv unix.FsverityDigest
|
||||
Buf [64]byte
|
||||
}
|
||||
|
||||
func measureVerity(description string, fd int) (string, error) {
|
||||
var digest verityDigest
|
||||
digest.Fsv.Size = 64
|
||||
_, _, e1 := syscall.Syscall(unix.SYS_IOCTL, uintptr(fd), uintptr(unix.FS_IOC_MEASURE_VERITY), uintptr(unsafe.Pointer(&digest)))
|
||||
if e1 != 0 {
|
||||
return "", fmt.Errorf("failed to measure verity for %q: %w", description, e1)
|
||||
}
|
||||
return fmt.Sprintf("%x", digest.Buf[:digest.Fsv.Size]), nil
|
||||
}
|
||||
|
||||
func enableVerityRecursive(root string) (map[string]string, error) {
|
||||
digests := make(map[string]string)
|
||||
walkFn := func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !d.Type().IsRegular() {
|
||||
return nil
|
||||
}
|
||||
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
if err := enableVerity(path, int(f.Fd())); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
verity, err := measureVerity(path, int(f.Fd()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
relPath, err := filepath.Rel(root, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
digests[relPath] = verity
|
||||
return nil
|
||||
}
|
||||
err := filepath.WalkDir(root, walkFn)
|
||||
return digests, err
|
||||
}
|
||||
|
||||
func getComposefsBlob(dataDir string) string {
|
||||
return filepath.Join(dataDir, "composefs.blob")
|
||||
}
|
||||
|
||||
func generateComposeFsBlob(verityDigests map[string]string, toc interface{}, composefsDir string) error {
|
||||
if err := os.MkdirAll(composefsDir, 0o700); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dumpReader, err := dump.GenerateDump(toc, verityDigests)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
destFile := getComposefsBlob(composefsDir)
|
||||
writerJson, err := getComposeFsHelper()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to find mkcomposefs: %w", err)
|
||||
}
|
||||
|
||||
fd, err := unix.Openat(unix.AT_FDCWD, destFile, unix.O_WRONLY|unix.O_CREAT|unix.O_TRUNC|unix.O_EXCL|unix.O_CLOEXEC, 0o644)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open output file %q: %w", destFile, err)
|
||||
}
|
||||
outFd := os.NewFile(uintptr(fd), "outFd")
|
||||
|
||||
fd, err = unix.Open(fmt.Sprintf("/proc/self/fd/%d", outFd.Fd()), unix.O_RDONLY|unix.O_CLOEXEC, 0)
|
||||
if err != nil {
|
||||
outFd.Close()
|
||||
return fmt.Errorf("failed to dup output file: %w", err)
|
||||
}
|
||||
newFd := os.NewFile(uintptr(fd), "newFd")
|
||||
defer newFd.Close()
|
||||
|
||||
err = func() error {
|
||||
// a scope to close outFd before setting fsverity on the read-only fd.
|
||||
defer outFd.Close()
|
||||
|
||||
cmd := exec.Command(writerJson, "--from-file", "-", "/proc/self/fd/3")
|
||||
cmd.ExtraFiles = []*os.File{outFd}
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Stdin = dumpReader
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("failed to convert json to erofs: %w", err)
|
||||
}
|
||||
return nil
|
||||
}()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := enableVerity("manifest file", int(newFd.Fd())); err != nil && !errors.Is(err, unix.ENOTSUP) && !errors.Is(err, unix.ENOTTY) {
|
||||
logrus.Warningf("%s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
typedef enum {
|
||||
LCFS_EROFS_FLAGS_HAS_ACL = (1 << 0),
|
||||
} lcfs_erofs_flag_t;
|
||||
|
||||
struct lcfs_erofs_header_s {
|
||||
uint32_t magic;
|
||||
uint32_t version;
|
||||
uint32_t flags;
|
||||
uint32_t unused[5];
|
||||
} __attribute__((__packed__));
|
||||
*/
|
||||
|
||||
// hasACL returns true if the erofs blob has ACLs enabled
|
||||
func hasACL(path string) (bool, error) {
|
||||
const LCFS_EROFS_FLAGS_HAS_ACL = (1 << 0)
|
||||
|
||||
fd, err := unix.Openat(unix.AT_FDCWD, path, unix.O_RDONLY|unix.O_CLOEXEC, 0)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer unix.Close(fd)
|
||||
// do not worry about checking the magic number, if the file is invalid
|
||||
// we will fail to mount it anyway
|
||||
flags := make([]byte, 4)
|
||||
nread, err := unix.Pread(fd, flags, 8)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if nread != 4 {
|
||||
return false, fmt.Errorf("failed to read flags from %q", path)
|
||||
}
|
||||
return binary.LittleEndian.Uint32(flags)&LCFS_EROFS_FLAGS_HAS_ACL != 0, nil
|
||||
}
|
||||
|
||||
func mountComposefsBlob(dataDir, mountPoint string) error {
|
||||
blobFile := getComposefsBlob(dataDir)
|
||||
loop, err := loopback.AttachLoopDeviceRO(blobFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer loop.Close()
|
||||
|
||||
hasACL, err := hasACL(blobFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mountOpts := "ro"
|
||||
if !hasACL {
|
||||
mountOpts += ",noacl"
|
||||
}
|
||||
|
||||
return unix.Mount(loop.Name(), mountPoint, "erofs", unix.MS_RDONLY, mountOpts)
|
||||
}
|
||||
8
vendor/github.com/containers/storage/drivers/overlay/jsoniter.go
generated
vendored
Normal file
8
vendor/github.com/containers/storage/drivers/overlay/jsoniter.go
generated
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package overlay
|
||||
|
||||
import jsoniter "github.com/json-iterator/go"
|
||||
|
||||
var json = jsoniter.ConfigCompatibleWithStandardLibrary
|
||||
189
vendor/github.com/containers/storage/drivers/overlay/mount.go
generated
vendored
Normal file
189
vendor/github.com/containers/storage/drivers/overlay/mount.go
generated
vendored
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package overlay
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/containers/storage/pkg/reexec"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func init() {
|
||||
reexec.Register("storage-mountfrom", mountOverlayFromMain)
|
||||
}
|
||||
|
||||
func fatal(err error) {
|
||||
fmt.Fprint(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
type mountOptions struct {
|
||||
Device string
|
||||
Target string
|
||||
Type string
|
||||
Label string
|
||||
Flag uint32
|
||||
}
|
||||
|
||||
func mountOverlayFrom(dir, device, target, mType string, flags uintptr, label string) error {
|
||||
options := &mountOptions{
|
||||
Device: device,
|
||||
Target: target,
|
||||
Type: mType,
|
||||
Flag: uint32(flags),
|
||||
Label: label,
|
||||
}
|
||||
|
||||
cmd := reexec.Command("storage-mountfrom", dir)
|
||||
w, err := cmd.StdinPipe()
|
||||
if err != nil {
|
||||
return fmt.Errorf("mountfrom error on pipe creation: %w", err)
|
||||
}
|
||||
|
||||
output := bytes.NewBuffer(nil)
|
||||
cmd.Stdout = output
|
||||
cmd.Stderr = output
|
||||
if err := cmd.Start(); err != nil {
|
||||
w.Close()
|
||||
return fmt.Errorf("mountfrom error on re-exec cmd: %w", err)
|
||||
}
|
||||
// write the options to the pipe for the untar exec to read
|
||||
if err := json.NewEncoder(w).Encode(options); err != nil {
|
||||
w.Close()
|
||||
return fmt.Errorf("mountfrom json encode to pipe failed: %w", err)
|
||||
}
|
||||
w.Close()
|
||||
|
||||
if err := cmd.Wait(); err != nil {
|
||||
return fmt.Errorf("mountfrom re-exec output: %s: error: %w", output, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// mountfromMain is the entry-point for storage-mountfrom on re-exec.
|
||||
func mountOverlayFromMain() {
|
||||
runtime.LockOSThread()
|
||||
flag.Parse()
|
||||
|
||||
var options *mountOptions
|
||||
|
||||
if err := json.NewDecoder(os.Stdin).Decode(&options); err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
|
||||
// Mount the arguments passed from the specified directory. Some of the
|
||||
// paths mentioned in the values we pass to the kernel are relative to
|
||||
// the specified directory.
|
||||
homedir := flag.Arg(0)
|
||||
if err := os.Chdir(homedir); err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
|
||||
pageSize := unix.Getpagesize()
|
||||
if len(options.Label) < pageSize {
|
||||
if err := unix.Mount(options.Device, options.Target, options.Type, uintptr(options.Flag), options.Label); err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
// Those arguments still took up too much space. Open the diff
|
||||
// directories and use their descriptor numbers as lowers, using
|
||||
// /proc/self/fd as the current directory.
|
||||
|
||||
// Split out the various options, since we need to manipulate the
|
||||
// paths, but we don't want to mess with other options.
|
||||
var upperk, upperv, workk, workv, lowerk, lowerv, labelk, labelv, others string
|
||||
for _, arg := range strings.Split(options.Label, ",") {
|
||||
kv := strings.SplitN(arg, "=", 2)
|
||||
switch kv[0] {
|
||||
case "upperdir":
|
||||
upperk = "upperdir="
|
||||
upperv = kv[1]
|
||||
case "workdir":
|
||||
workk = "workdir="
|
||||
workv = kv[1]
|
||||
case "lowerdir":
|
||||
lowerk = "lowerdir="
|
||||
lowerv = kv[1]
|
||||
case "label":
|
||||
labelk = "label="
|
||||
labelv = kv[1]
|
||||
default:
|
||||
if others == "" {
|
||||
others = arg
|
||||
} else {
|
||||
others = others + "," + arg
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure upperdir, workdir, and the target are absolute paths.
|
||||
if upperv != "" && !filepath.IsAbs(upperv) {
|
||||
upperv = filepath.Join(homedir, upperv)
|
||||
}
|
||||
if workv != "" && !filepath.IsAbs(workv) {
|
||||
workv = filepath.Join(homedir, workv)
|
||||
}
|
||||
if !filepath.IsAbs(options.Target) {
|
||||
options.Target = filepath.Join(homedir, options.Target)
|
||||
}
|
||||
|
||||
// Get a descriptor for each lower, and use that descriptor's name as
|
||||
// the new value for the list of lowers, because it's shorter.
|
||||
if lowerv != "" {
|
||||
lowers := strings.Split(lowerv, ":")
|
||||
var newLowers []string
|
||||
dataOnly := false
|
||||
for _, lowerPath := range lowers {
|
||||
if lowerPath == "" {
|
||||
dataOnly = true
|
||||
continue
|
||||
}
|
||||
lowerFd, err := unix.Open(lowerPath, unix.O_RDONLY, 0)
|
||||
if err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
var lower string
|
||||
if dataOnly {
|
||||
lower = fmt.Sprintf(":%d", lowerFd)
|
||||
dataOnly = false
|
||||
} else {
|
||||
lower = fmt.Sprintf("%d", lowerFd)
|
||||
}
|
||||
newLowers = append(newLowers, lower)
|
||||
}
|
||||
lowerv = strings.Join(newLowers, ":")
|
||||
}
|
||||
|
||||
// Reconstruct the Label field.
|
||||
options.Label = upperk + upperv + "," + workk + workv + "," + lowerk + lowerv + "," + labelk + labelv + "," + others
|
||||
options.Label = strings.ReplaceAll(options.Label, ",,", ",")
|
||||
|
||||
// Okay, try this, if we managed to make the arguments fit.
|
||||
var err error
|
||||
if len(options.Label) < pageSize {
|
||||
if err := os.Chdir("/proc/self/fd"); err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
err = unix.Mount(options.Device, options.Target, options.Type, uintptr(options.Flag), options.Label)
|
||||
} else {
|
||||
err = fmt.Errorf("cannot mount layer, mount data %q too large %d >= page size %d", options.Label, len(options.Label), pageSize)
|
||||
}
|
||||
|
||||
// Clean up.
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "creating overlay mount to %s, mount_data=%q\n", options.Target, options.Label)
|
||||
fatal(err)
|
||||
}
|
||||
|
||||
os.Exit(0)
|
||||
}
|
||||
2556
vendor/github.com/containers/storage/drivers/overlay/overlay.go
generated
vendored
Normal file
2556
vendor/github.com/containers/storage/drivers/overlay/overlay.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
22
vendor/github.com/containers/storage/drivers/overlay/overlay_cgo.go
generated
vendored
Normal file
22
vendor/github.com/containers/storage/drivers/overlay/overlay_cgo.go
generated
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
//go:build linux && cgo
|
||||
// +build linux,cgo
|
||||
|
||||
package overlay
|
||||
|
||||
import (
|
||||
"path"
|
||||
|
||||
"github.com/containers/storage/pkg/directory"
|
||||
)
|
||||
|
||||
// ReadWriteDiskUsage returns the disk usage of the writable directory for the ID.
|
||||
// For Overlay, it attempts to check the XFS quota for size, and falls back to
|
||||
// finding the size of the "diff" directory.
|
||||
func (d *Driver) ReadWriteDiskUsage(id string) (*directory.DiskUsage, error) {
|
||||
usage := &directory.DiskUsage{}
|
||||
if d.quotaCtl != nil {
|
||||
err := d.quotaCtl.GetDiskUsage(d.dir(id), usage)
|
||||
return usage, err
|
||||
}
|
||||
return directory.Usage(path.Join(d.dir(id), "diff"))
|
||||
}
|
||||
17
vendor/github.com/containers/storage/drivers/overlay/overlay_nocgo.go
generated
vendored
Normal file
17
vendor/github.com/containers/storage/drivers/overlay/overlay_nocgo.go
generated
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
//go:build linux && !cgo
|
||||
// +build linux,!cgo
|
||||
|
||||
package overlay
|
||||
|
||||
import (
|
||||
"path"
|
||||
|
||||
"github.com/containers/storage/pkg/directory"
|
||||
)
|
||||
|
||||
// ReadWriteDiskUsage returns the disk usage of the writable directory for the ID.
|
||||
// For Overlay, it attempts to check the XFS quota for size, and falls back to
|
||||
// finding the size of the "diff" directory.
|
||||
func (d *Driver) ReadWriteDiskUsage(id string) (*directory.DiskUsage, error) {
|
||||
return directory.Usage(path.Join(d.dir(id), "diff"))
|
||||
}
|
||||
8
vendor/github.com/containers/storage/drivers/overlay/overlay_unsupported.go
generated
vendored
Normal file
8
vendor/github.com/containers/storage/drivers/overlay/overlay_unsupported.go
generated
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
//go:build !linux
|
||||
// +build !linux
|
||||
|
||||
package overlay
|
||||
|
||||
func SupportsNativeOverlay(graphroot, rundir string) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
82
vendor/github.com/containers/storage/drivers/overlay/randomid.go
generated
vendored
Normal file
82
vendor/github.com/containers/storage/drivers/overlay/randomid.go
generated
vendored
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package overlay
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/base32"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// generateID creates a new random string identifier with the given length
|
||||
func generateID(l int) string {
|
||||
const (
|
||||
// ensures we backoff for less than 450ms total. Use the following to
|
||||
// select new value, in units of 10ms:
|
||||
// n*(n+1)/2 = d -> n^2 + n - 2d -> n = (sqrt(8d + 1) - 1)/2
|
||||
maxretries = 9
|
||||
backoff = time.Millisecond * 10
|
||||
)
|
||||
|
||||
var (
|
||||
totalBackoff time.Duration
|
||||
count int
|
||||
retries int
|
||||
size = (l*5 + 7) / 8
|
||||
u = make([]byte, size)
|
||||
)
|
||||
// TODO: Include time component, counter component, random component
|
||||
|
||||
for {
|
||||
// This should never block but the read may fail. Because of this,
|
||||
// we just try to read the random number generator until we get
|
||||
// something. This is a very rare condition but may happen.
|
||||
b := time.Duration(retries) * backoff
|
||||
time.Sleep(b)
|
||||
totalBackoff += b
|
||||
|
||||
n, err := io.ReadFull(rand.Reader, u[count:])
|
||||
if err != nil {
|
||||
if retryOnError(err) && retries < maxretries {
|
||||
count += n
|
||||
retries++
|
||||
logrus.Errorf("Generating version 4 uuid, retrying: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Any other errors represent a system problem. What did someone
|
||||
// do to /dev/urandom?
|
||||
panic(fmt.Errorf("reading random number generator, retried for %v: %w", totalBackoff.String(), err))
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
s := base32.StdEncoding.EncodeToString(u)
|
||||
|
||||
return s[:l]
|
||||
}
|
||||
|
||||
// retryOnError tries to detect whether or not retrying would be fruitful.
|
||||
func retryOnError(err error) bool {
|
||||
switch err := err.(type) {
|
||||
case *os.PathError:
|
||||
return retryOnError(err.Err) // unpack the target error
|
||||
case syscall.Errno:
|
||||
if err == unix.EPERM {
|
||||
// EPERM represents an entropy pool exhaustion, a condition under
|
||||
// which we backoff and retry.
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
20
vendor/github.com/containers/storage/drivers/overlayutils/overlayutils.go
generated
vendored
Normal file
20
vendor/github.com/containers/storage/drivers/overlayutils/overlayutils.go
generated
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package overlayutils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
graphdriver "github.com/containers/storage/drivers"
|
||||
)
|
||||
|
||||
// ErrDTypeNotSupported denotes that the backing filesystem doesn't support d_type.
|
||||
func ErrDTypeNotSupported(driver, backingFs string) error {
|
||||
msg := fmt.Sprintf("%s: the backing %s filesystem is formatted without d_type support, which leads to incorrect behavior.", driver, backingFs)
|
||||
if backingFs == "xfs" {
|
||||
msg += " Reformat the filesystem with ftype=1 to enable d_type support."
|
||||
}
|
||||
msg += " Running without d_type is not supported."
|
||||
return fmt.Errorf("%s: %w", msg, graphdriver.ErrNotSupported)
|
||||
}
|
||||
453
vendor/github.com/containers/storage/drivers/quota/projectquota.go
generated
vendored
Normal file
453
vendor/github.com/containers/storage/drivers/quota/projectquota.go
generated
vendored
Normal file
|
|
@ -0,0 +1,453 @@
|
|||
//go:build linux && !exclude_disk_quota && cgo
|
||||
// +build linux,!exclude_disk_quota,cgo
|
||||
|
||||
//
|
||||
// projectquota.go - implements XFS project quota controls
|
||||
// for setting quota limits on a newly created directory.
|
||||
// It currently supports the legacy XFS specific ioctls.
|
||||
//
|
||||
// TODO: use generic quota control ioctl FS_IOC_FS{GET,SET}XATTR
|
||||
// for both xfs/ext4 for kernel version >= v4.5
|
||||
//
|
||||
|
||||
package quota
|
||||
|
||||
/*
|
||||
#include <stdlib.h>
|
||||
#include <dirent.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/quota.h>
|
||||
#include <linux/dqblk_xfs.h>
|
||||
|
||||
#ifndef FS_XFLAG_PROJINHERIT
|
||||
struct fsxattr {
|
||||
__u32 fsx_xflags;
|
||||
__u32 fsx_extsize;
|
||||
__u32 fsx_nextents;
|
||||
__u32 fsx_projid;
|
||||
unsigned char fsx_pad[12];
|
||||
};
|
||||
#define FS_XFLAG_PROJINHERIT 0x00000200
|
||||
#endif
|
||||
#ifndef FS_IOC_FSGETXATTR
|
||||
#define FS_IOC_FSGETXATTR _IOR ('X', 31, struct fsxattr)
|
||||
#endif
|
||||
#ifndef FS_IOC_FSSETXATTR
|
||||
#define FS_IOC_FSSETXATTR _IOW ('X', 32, struct fsxattr)
|
||||
#endif
|
||||
|
||||
#ifndef PRJQUOTA
|
||||
#define PRJQUOTA 2
|
||||
#endif
|
||||
#ifndef FS_PROJ_QUOTA
|
||||
#define FS_PROJ_QUOTA 2
|
||||
#endif
|
||||
#ifndef Q_XSETPQLIM
|
||||
#define Q_XSETPQLIM QCMD(Q_XSETQLIM, PRJQUOTA)
|
||||
#endif
|
||||
#ifndef Q_XGETPQUOTA
|
||||
#define Q_XGETPQUOTA QCMD(Q_XGETQUOTA, PRJQUOTA)
|
||||
#endif
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/containers/storage/pkg/directory"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const projectIDsAllocatedPerQuotaHome = 10000
|
||||
|
||||
// BackingFsBlockDeviceLink is the name of a file that we place in
|
||||
// the home directory of a driver that uses this package.
|
||||
const BackingFsBlockDeviceLink = "backingFsBlockDev"
|
||||
|
||||
// Quota limit params - currently we only control blocks hard limit and inodes
|
||||
type Quota struct {
|
||||
Size uint64
|
||||
Inodes uint64
|
||||
}
|
||||
|
||||
// Control - Context to be used by storage driver (e.g. overlay)
|
||||
// who wants to apply project quotas to container dirs
|
||||
type Control struct {
|
||||
backingFsBlockDev string
|
||||
nextProjectID uint32
|
||||
quotas *sync.Map
|
||||
basePath string
|
||||
}
|
||||
|
||||
// Attempt to generate a unigue projectid. Multiple directories
|
||||
// per file system can have quota and they need a group of unique
|
||||
// ids. This function attempts to allocate at least projectIDsAllocatedPerQuotaHome(10000)
|
||||
// unique projectids, based on the inode of the basepath.
|
||||
func generateUniqueProjectID(path string) (uint32, error) {
|
||||
fileinfo, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
stat, ok := fileinfo.Sys().(*syscall.Stat_t)
|
||||
if !ok {
|
||||
return 0, fmt.Errorf("not a syscall.Stat_t %s", path)
|
||||
}
|
||||
projectID := projectIDsAllocatedPerQuotaHome + (stat.Ino*projectIDsAllocatedPerQuotaHome)%(math.MaxUint32-projectIDsAllocatedPerQuotaHome)
|
||||
return uint32(projectID), nil
|
||||
}
|
||||
|
||||
// NewControl - initialize project quota support.
|
||||
// Test to make sure that quota can be set on a test dir and find
|
||||
// the first project id to be used for the next container create.
|
||||
//
|
||||
// Returns nil (and error) if project quota is not supported.
|
||||
//
|
||||
// First get the project id of the basePath directory.
|
||||
// This test will fail if the backing fs is not xfs.
|
||||
//
|
||||
// xfs_quota tool can be used to assign a project id to the driver home directory, e.g.:
|
||||
// echo 100000:/var/lib/containers/storage/overlay >> /etc/projects
|
||||
// echo 200000:/var/lib/containers/storage/volumes >> /etc/projects
|
||||
// echo storage:100000 >> /etc/projid
|
||||
// echo volumes:200000 >> /etc/projid
|
||||
// xfs_quota -x -c 'project -s storage volumes' /<xfs mount point>
|
||||
//
|
||||
// In the example above, the storage directory project id will be used as a
|
||||
// "start offset" and all containers will be assigned larger project ids
|
||||
// (e.g. >= 100000). Then the volumes directory project id will be used as a
|
||||
// "start offset" and all volumes will be assigned larger project ids
|
||||
// (e.g. >= 200000).
|
||||
// This is a way to prevent xfs_quota management from conflicting with
|
||||
// containers/storage.
|
||||
|
||||
// Then try to create a test directory with the next project id and set a quota
|
||||
// on it. If that works, continue to scan existing containers to map allocated
|
||||
// project ids.
|
||||
func NewControl(basePath string) (*Control, error) {
|
||||
//
|
||||
// Get project id of parent dir as minimal id to be used by driver
|
||||
//
|
||||
minProjectID, err := getProjectID(basePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if minProjectID == 0 {
|
||||
// Indicates the storage was never initialized
|
||||
// Generate a unique range of Projectids for this basepath
|
||||
minProjectID, err = generateUniqueProjectID(basePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
}
|
||||
//
|
||||
// create backing filesystem device node
|
||||
//
|
||||
backingFsBlockDev, err := makeBackingFsDev(basePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//
|
||||
// Test if filesystem supports project quotas by trying to set
|
||||
// a quota on the first available project id
|
||||
//
|
||||
quota := Quota{
|
||||
Size: 0,
|
||||
Inodes: 0,
|
||||
}
|
||||
|
||||
q := Control{
|
||||
backingFsBlockDev: backingFsBlockDev,
|
||||
nextProjectID: minProjectID + 1,
|
||||
quotas: &sync.Map{},
|
||||
basePath: basePath,
|
||||
}
|
||||
|
||||
if err := q.setProjectQuota(minProjectID, quota); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//
|
||||
// get first project id to be used for next container
|
||||
//
|
||||
err = q.findNextProjectID()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
logrus.Debugf("NewControl(%s): nextProjectID = %d", basePath, q.nextProjectID)
|
||||
return &q, nil
|
||||
}
|
||||
|
||||
// SetQuota - assign a unique project id to directory and set the quota limits
|
||||
// for that project id
|
||||
func (q *Control) SetQuota(targetPath string, quota Quota) error {
|
||||
var projectID uint32
|
||||
value, ok := q.quotas.Load(targetPath)
|
||||
if ok {
|
||||
projectID, ok = value.(uint32)
|
||||
}
|
||||
if !ok {
|
||||
projectID = q.nextProjectID
|
||||
|
||||
//
|
||||
// assign project id to new container directory
|
||||
//
|
||||
err := setProjectID(targetPath, projectID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
q.quotas.Store(targetPath, projectID)
|
||||
q.nextProjectID++
|
||||
}
|
||||
|
||||
//
|
||||
// set the quota limit for the container's project id
|
||||
//
|
||||
logrus.Debugf("SetQuota path=%s, size=%d, inodes=%d, projectID=%d", targetPath, quota.Size, quota.Inodes, projectID)
|
||||
return q.setProjectQuota(projectID, quota)
|
||||
}
|
||||
|
||||
// ClearQuota removes the map entry in the quotas map for targetPath.
|
||||
// It does so to prevent the map leaking entries as directories are deleted.
|
||||
func (q *Control) ClearQuota(targetPath string) {
|
||||
q.quotas.Delete(targetPath)
|
||||
}
|
||||
|
||||
// setProjectQuota - set the quota for project id on xfs block device
|
||||
func (q *Control) setProjectQuota(projectID uint32, quota Quota) error {
|
||||
var d C.fs_disk_quota_t
|
||||
d.d_version = C.FS_DQUOT_VERSION
|
||||
d.d_id = C.__u32(projectID)
|
||||
d.d_flags = C.FS_PROJ_QUOTA
|
||||
|
||||
if quota.Size > 0 {
|
||||
d.d_fieldmask = d.d_fieldmask | C.FS_DQ_BHARD | C.FS_DQ_BSOFT
|
||||
d.d_blk_hardlimit = C.__u64(quota.Size / 512)
|
||||
d.d_blk_softlimit = d.d_blk_hardlimit
|
||||
}
|
||||
if quota.Inodes > 0 {
|
||||
d.d_fieldmask = d.d_fieldmask | C.FS_DQ_IHARD | C.FS_DQ_ISOFT
|
||||
d.d_ino_hardlimit = C.__u64(quota.Inodes)
|
||||
d.d_ino_softlimit = d.d_ino_hardlimit
|
||||
}
|
||||
|
||||
cs := C.CString(q.backingFsBlockDev)
|
||||
defer C.free(unsafe.Pointer(cs))
|
||||
|
||||
runQuotactl := func() syscall.Errno {
|
||||
_, _, errno := unix.Syscall6(unix.SYS_QUOTACTL, C.Q_XSETPQLIM,
|
||||
uintptr(unsafe.Pointer(cs)), uintptr(d.d_id),
|
||||
uintptr(unsafe.Pointer(&d)), 0, 0)
|
||||
return errno
|
||||
}
|
||||
|
||||
errno := runQuotactl()
|
||||
|
||||
// If the backingFsBlockDev does not exist any more then try to recreate it.
|
||||
if errors.Is(errno, unix.ENOENT) {
|
||||
if _, err := makeBackingFsDev(q.basePath); err != nil {
|
||||
return fmt.Errorf(
|
||||
"failed to recreate missing backingFsBlockDev %s for projid %d: %w",
|
||||
q.backingFsBlockDev, projectID, err,
|
||||
)
|
||||
}
|
||||
|
||||
if errno := runQuotactl(); errno != 0 {
|
||||
return fmt.Errorf("failed to set quota limit for projid %d on %s after backingFsBlockDev recreation: %w",
|
||||
projectID, q.backingFsBlockDev, errno)
|
||||
}
|
||||
|
||||
} else if errno != 0 {
|
||||
return fmt.Errorf("failed to set quota limit for projid %d on %s: %w",
|
||||
projectID, q.backingFsBlockDev, errno)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetQuota - get the quota limits of a directory that was configured with SetQuota
|
||||
func (q *Control) GetQuota(targetPath string, quota *Quota) error {
|
||||
d, err := q.fsDiskQuotaFromPath(targetPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
quota.Size = uint64(d.d_blk_hardlimit) * 512
|
||||
quota.Inodes = uint64(d.d_ino_hardlimit)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetDiskUsage - get the current disk usage of a directory that was configured with SetQuota
|
||||
func (q *Control) GetDiskUsage(targetPath string, usage *directory.DiskUsage) error {
|
||||
d, err := q.fsDiskQuotaFromPath(targetPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
usage.Size = int64(d.d_bcount) * 512
|
||||
usage.InodeCount = int64(d.d_icount)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *Control) fsDiskQuotaFromPath(targetPath string) (C.fs_disk_quota_t, error) {
|
||||
var d C.fs_disk_quota_t
|
||||
var projectID uint32
|
||||
value, ok := q.quotas.Load(targetPath)
|
||||
if ok {
|
||||
projectID, ok = value.(uint32)
|
||||
}
|
||||
if !ok {
|
||||
return d, fmt.Errorf("quota not found for path : %s", targetPath)
|
||||
}
|
||||
|
||||
//
|
||||
// get the quota limit for the container's project id
|
||||
//
|
||||
cs := C.CString(q.backingFsBlockDev)
|
||||
defer C.free(unsafe.Pointer(cs))
|
||||
|
||||
_, _, errno := unix.Syscall6(unix.SYS_QUOTACTL, C.Q_XGETPQUOTA,
|
||||
uintptr(unsafe.Pointer(cs)), uintptr(C.__u32(projectID)),
|
||||
uintptr(unsafe.Pointer(&d)), 0, 0)
|
||||
if errno != 0 {
|
||||
return d, fmt.Errorf("failed to get quota limit for projid %d on %s: %w",
|
||||
projectID, q.backingFsBlockDev, errno)
|
||||
}
|
||||
|
||||
return d, nil
|
||||
}
|
||||
|
||||
// getProjectID - get the project id of path on xfs
|
||||
func getProjectID(targetPath string) (uint32, error) {
|
||||
dir, err := openDir(targetPath)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer closeDir(dir)
|
||||
|
||||
var fsx C.struct_fsxattr
|
||||
_, _, errno := unix.Syscall(unix.SYS_IOCTL, getDirFd(dir), C.FS_IOC_FSGETXATTR,
|
||||
uintptr(unsafe.Pointer(&fsx)))
|
||||
if errno != 0 {
|
||||
return 0, fmt.Errorf("failed to get projid for %s: %w", targetPath, errno)
|
||||
}
|
||||
|
||||
return uint32(fsx.fsx_projid), nil
|
||||
}
|
||||
|
||||
// setProjectID - set the project id of path on xfs
|
||||
func setProjectID(targetPath string, projectID uint32) error {
|
||||
dir, err := openDir(targetPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer closeDir(dir)
|
||||
|
||||
var fsx C.struct_fsxattr
|
||||
_, _, errno := unix.Syscall(unix.SYS_IOCTL, getDirFd(dir), C.FS_IOC_FSGETXATTR,
|
||||
uintptr(unsafe.Pointer(&fsx)))
|
||||
if errno != 0 {
|
||||
return fmt.Errorf("failed to get projid for %s: %w", targetPath, errno)
|
||||
}
|
||||
fsx.fsx_projid = C.__u32(projectID)
|
||||
fsx.fsx_xflags |= C.FS_XFLAG_PROJINHERIT
|
||||
_, _, errno = unix.Syscall(unix.SYS_IOCTL, getDirFd(dir), C.FS_IOC_FSSETXATTR,
|
||||
uintptr(unsafe.Pointer(&fsx)))
|
||||
if errno != 0 {
|
||||
return fmt.Errorf("failed to set projid for %s: %w", targetPath, errno)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// findNextProjectID - find the next project id to be used for containers
|
||||
// by scanning driver home directory to find used project ids
|
||||
func (q *Control) findNextProjectID() error {
|
||||
files, err := os.ReadDir(q.basePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read directory failed : %s", q.basePath)
|
||||
}
|
||||
for _, file := range files {
|
||||
if !file.IsDir() {
|
||||
continue
|
||||
}
|
||||
path := filepath.Join(q.basePath, file.Name())
|
||||
projid, err := getProjectID(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if projid > 0 {
|
||||
q.quotas.Store(path, projid)
|
||||
}
|
||||
if q.nextProjectID <= projid {
|
||||
q.nextProjectID = projid + 1
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func free(p *C.char) {
|
||||
C.free(unsafe.Pointer(p))
|
||||
}
|
||||
|
||||
func openDir(path string) (*C.DIR, error) {
|
||||
Cpath := C.CString(path)
|
||||
defer free(Cpath)
|
||||
|
||||
dir, errno := C.opendir(Cpath)
|
||||
if dir == nil {
|
||||
return nil, fmt.Errorf("can't open dir %v: %w", Cpath, errno)
|
||||
}
|
||||
return dir, nil
|
||||
}
|
||||
|
||||
func closeDir(dir *C.DIR) {
|
||||
if dir != nil {
|
||||
C.closedir(dir)
|
||||
}
|
||||
}
|
||||
|
||||
func getDirFd(dir *C.DIR) uintptr {
|
||||
return uintptr(C.dirfd(dir))
|
||||
}
|
||||
|
||||
// Get the backing block device of the driver home directory
|
||||
// and create a block device node under the home directory
|
||||
// to be used by quotactl commands
|
||||
func makeBackingFsDev(home string) (string, error) {
|
||||
var stat unix.Stat_t
|
||||
if err := unix.Stat(home, &stat); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
backingFsBlockDev := path.Join(home, BackingFsBlockDeviceLink)
|
||||
backingFsBlockDevTmp := backingFsBlockDev + ".tmp"
|
||||
// Re-create just in case someone copied the home directory over to a new device
|
||||
if err := unix.Mknod(backingFsBlockDevTmp, unix.S_IFBLK|0o600, int(stat.Dev)); err != nil {
|
||||
if !errors.Is(err, unix.EEXIST) {
|
||||
return "", fmt.Errorf("failed to mknod %s: %w", backingFsBlockDevTmp, err)
|
||||
}
|
||||
// On EEXIST, try again after unlinking any potential leftover.
|
||||
_ = unix.Unlink(backingFsBlockDevTmp)
|
||||
if err := unix.Mknod(backingFsBlockDevTmp, unix.S_IFBLK|0o600, int(stat.Dev)); err != nil {
|
||||
return "", fmt.Errorf("failed to mknod %s: %w", backingFsBlockDevTmp, err)
|
||||
}
|
||||
}
|
||||
if err := unix.Rename(backingFsBlockDevTmp, backingFsBlockDev); err != nil {
|
||||
return "", fmt.Errorf("failed to rename %s to %s: %w", backingFsBlockDevTmp, backingFsBlockDev, err)
|
||||
}
|
||||
|
||||
return backingFsBlockDev, nil
|
||||
}
|
||||
37
vendor/github.com/containers/storage/drivers/quota/projectquota_unsupported.go
generated
vendored
Normal file
37
vendor/github.com/containers/storage/drivers/quota/projectquota_unsupported.go
generated
vendored
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
//go:build !linux || exclude_disk_quota || !cgo
|
||||
// +build !linux exclude_disk_quota !cgo
|
||||
|
||||
package quota
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
// Quota limit params - currently we only control blocks hard limit
|
||||
type Quota struct {
|
||||
Size uint64
|
||||
Inodes uint64
|
||||
}
|
||||
|
||||
// Control - Context to be used by storage driver (e.g. overlay)
|
||||
// who wants to apply project quotas to container dirs
|
||||
type Control struct{}
|
||||
|
||||
func NewControl(basePath string) (*Control, error) {
|
||||
return nil, errors.New("filesystem does not support, or has not enabled quotas")
|
||||
}
|
||||
|
||||
// SetQuota - assign a unique project id to directory and set the quota limits
|
||||
// for that project id
|
||||
func (q *Control) SetQuota(targetPath string, quota Quota) error {
|
||||
return errors.New("filesystem does not support, or has not enabled quotas")
|
||||
}
|
||||
|
||||
// GetQuota - get the quota limits of a directory that was configured with SetQuota
|
||||
func (q *Control) GetQuota(targetPath string, quota *Quota) error {
|
||||
return errors.New("filesystem does not support, or has not enabled quotas")
|
||||
}
|
||||
|
||||
// ClearQuota removes the map entry in the quotas map for targetPath.
|
||||
// It does so to prevent the map leaking entries as directories are deleted.
|
||||
func (q *Control) ClearQuota(targetPath string) {}
|
||||
9
vendor/github.com/containers/storage/drivers/register/register_aufs.go
generated
vendored
Normal file
9
vendor/github.com/containers/storage/drivers/register/register_aufs.go
generated
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
//go:build !exclude_graphdriver_aufs && linux
|
||||
// +build !exclude_graphdriver_aufs,linux
|
||||
|
||||
package register
|
||||
|
||||
import (
|
||||
// register the aufs graphdriver
|
||||
_ "github.com/containers/storage/drivers/aufs"
|
||||
)
|
||||
9
vendor/github.com/containers/storage/drivers/register/register_btrfs.go
generated
vendored
Normal file
9
vendor/github.com/containers/storage/drivers/register/register_btrfs.go
generated
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
//go:build !exclude_graphdriver_btrfs && linux
|
||||
// +build !exclude_graphdriver_btrfs,linux
|
||||
|
||||
package register
|
||||
|
||||
import (
|
||||
// register the btrfs graphdriver
|
||||
_ "github.com/containers/storage/drivers/btrfs"
|
||||
)
|
||||
9
vendor/github.com/containers/storage/drivers/register/register_devicemapper.go
generated
vendored
Normal file
9
vendor/github.com/containers/storage/drivers/register/register_devicemapper.go
generated
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
//go:build !exclude_graphdriver_devicemapper && linux && cgo
|
||||
// +build !exclude_graphdriver_devicemapper,linux,cgo
|
||||
|
||||
package register
|
||||
|
||||
import (
|
||||
// register the devmapper graphdriver
|
||||
_ "github.com/containers/storage/drivers/devmapper"
|
||||
)
|
||||
9
vendor/github.com/containers/storage/drivers/register/register_overlay.go
generated
vendored
Normal file
9
vendor/github.com/containers/storage/drivers/register/register_overlay.go
generated
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
//go:build !exclude_graphdriver_overlay && linux && cgo
|
||||
// +build !exclude_graphdriver_overlay,linux,cgo
|
||||
|
||||
package register
|
||||
|
||||
import (
|
||||
// register the overlay graphdriver
|
||||
_ "github.com/containers/storage/drivers/overlay"
|
||||
)
|
||||
6
vendor/github.com/containers/storage/drivers/register/register_vfs.go
generated
vendored
Normal file
6
vendor/github.com/containers/storage/drivers/register/register_vfs.go
generated
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
package register
|
||||
|
||||
import (
|
||||
// register vfs
|
||||
_ "github.com/containers/storage/drivers/vfs"
|
||||
)
|
||||
6
vendor/github.com/containers/storage/drivers/register/register_windows.go
generated
vendored
Normal file
6
vendor/github.com/containers/storage/drivers/register/register_windows.go
generated
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
package register
|
||||
|
||||
import (
|
||||
// register the windows graph driver
|
||||
_ "github.com/containers/storage/drivers/windows"
|
||||
)
|
||||
9
vendor/github.com/containers/storage/drivers/register/register_zfs.go
generated
vendored
Normal file
9
vendor/github.com/containers/storage/drivers/register/register_zfs.go
generated
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
//go:build (!exclude_graphdriver_zfs && linux) || (!exclude_graphdriver_zfs && freebsd) || solaris
|
||||
// +build !exclude_graphdriver_zfs,linux !exclude_graphdriver_zfs,freebsd solaris
|
||||
|
||||
package register
|
||||
|
||||
import (
|
||||
// register the zfs driver
|
||||
_ "github.com/containers/storage/drivers/zfs"
|
||||
)
|
||||
52
vendor/github.com/containers/storage/drivers/template.go
generated
vendored
Normal file
52
vendor/github.com/containers/storage/drivers/template.go
generated
vendored
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
package graphdriver
|
||||
|
||||
import (
|
||||
"github.com/containers/storage/pkg/idtools"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// TemplateDriver is just barely enough of a driver that we can implement a
|
||||
// naive version of CreateFromTemplate on top of it.
|
||||
type TemplateDriver interface {
|
||||
DiffDriver
|
||||
CreateReadWrite(id, parent string, opts *CreateOpts) error
|
||||
Create(id, parent string, opts *CreateOpts) error
|
||||
Remove(id string) error
|
||||
}
|
||||
|
||||
// CreateFromTemplate creates a layer with the same contents and parent as
|
||||
// another layer. Internally, it may even depend on that other layer
|
||||
// continuing to exist, as if it were actually a child of the child layer.
|
||||
func NaiveCreateFromTemplate(d TemplateDriver, id, template string, templateIDMappings *idtools.IDMappings, parent string, parentIDMappings *idtools.IDMappings, opts *CreateOpts, readWrite bool) error {
|
||||
var err error
|
||||
if readWrite {
|
||||
err = d.CreateReadWrite(id, parent, opts)
|
||||
} else {
|
||||
err = d.Create(id, parent, opts)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
diff, err := d.Diff(template, templateIDMappings, parent, parentIDMappings, opts.MountLabel)
|
||||
if err != nil {
|
||||
if err2 := d.Remove(id); err2 != nil {
|
||||
logrus.Errorf("Removing layer %q: %v", id, err2)
|
||||
}
|
||||
return err
|
||||
}
|
||||
defer diff.Close()
|
||||
|
||||
applyOptions := ApplyDiffOpts{
|
||||
Diff: diff,
|
||||
Mappings: templateIDMappings,
|
||||
MountLabel: opts.MountLabel,
|
||||
IgnoreChownErrors: opts.ignoreChownErrors,
|
||||
}
|
||||
if _, err = d.ApplyDiff(id, parent, applyOptions); err != nil {
|
||||
if err2 := d.Remove(id); err2 != nil {
|
||||
logrus.Errorf("Removing layer %q: %v", id, err2)
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
7
vendor/github.com/containers/storage/drivers/vfs/copy_linux.go
generated
vendored
Normal file
7
vendor/github.com/containers/storage/drivers/vfs/copy_linux.go
generated
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
package vfs
|
||||
|
||||
import "github.com/containers/storage/drivers/copy"
|
||||
|
||||
func dirCopy(srcDir, dstDir string) error {
|
||||
return copy.DirCopy(srcDir, dstDir, copy.Content, true)
|
||||
}
|
||||
10
vendor/github.com/containers/storage/drivers/vfs/copy_unsupported.go
generated
vendored
Normal file
10
vendor/github.com/containers/storage/drivers/vfs/copy_unsupported.go
generated
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
//go:build !linux
|
||||
// +build !linux
|
||||
|
||||
package vfs // import "github.com/containers/storage/drivers/vfs"
|
||||
|
||||
import "github.com/containers/storage/pkg/chrootarchive"
|
||||
|
||||
func dirCopy(srcDir, dstDir string) error {
|
||||
return chrootarchive.NewArchiver(nil).CopyWithTar(srcDir, dstDir)
|
||||
}
|
||||
330
vendor/github.com/containers/storage/drivers/vfs/driver.go
generated
vendored
Normal file
330
vendor/github.com/containers/storage/drivers/vfs/driver.go
generated
vendored
Normal file
|
|
@ -0,0 +1,330 @@
|
|||
package vfs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
graphdriver "github.com/containers/storage/drivers"
|
||||
"github.com/containers/storage/pkg/archive"
|
||||
"github.com/containers/storage/pkg/directory"
|
||||
"github.com/containers/storage/pkg/idtools"
|
||||
"github.com/containers/storage/pkg/parsers"
|
||||
"github.com/containers/storage/pkg/system"
|
||||
"github.com/opencontainers/selinux/go-selinux/label"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/vbatts/tar-split/tar/storage"
|
||||
)
|
||||
|
||||
const defaultPerms = os.FileMode(0o555)
|
||||
|
||||
func init() {
|
||||
graphdriver.MustRegister("vfs", Init)
|
||||
}
|
||||
|
||||
// Init returns a new VFS driver.
|
||||
// This sets the home directory for the driver and returns NaiveDiffDriver.
|
||||
func Init(home string, options graphdriver.Options) (graphdriver.Driver, error) {
|
||||
d := &Driver{
|
||||
name: "vfs",
|
||||
homes: []string{home},
|
||||
idMappings: idtools.NewIDMappingsFromMaps(options.UIDMaps, options.GIDMaps),
|
||||
}
|
||||
|
||||
rootIDs := d.idMappings.RootPair()
|
||||
if err := idtools.MkdirAllAndChown(filepath.Join(home, "dir"), 0o700, rootIDs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, option := range options.DriverOptions {
|
||||
key, val, err := parsers.ParseKeyValueOpt(option)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
key = strings.ToLower(key)
|
||||
switch key {
|
||||
case "vfs.imagestore", ".imagestore":
|
||||
d.homes = append(d.homes, strings.Split(val, ",")...)
|
||||
continue
|
||||
case "vfs.mountopt":
|
||||
return nil, fmt.Errorf("vfs driver does not support mount options")
|
||||
case ".ignore_chown_errors", "vfs.ignore_chown_errors":
|
||||
logrus.Debugf("vfs: ignore_chown_errors=%s", val)
|
||||
var err error
|
||||
d.ignoreChownErrors, err = strconv.ParseBool(val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("vfs driver does not support %s options", key)
|
||||
}
|
||||
}
|
||||
// If --imagestore is provided, lets add writable graphRoot
|
||||
// to vfs's additional image store, as it is done for
|
||||
// `overlay` driver.
|
||||
if options.ImageStore != "" {
|
||||
d.homes = append(d.homes, options.ImageStore)
|
||||
}
|
||||
d.updater = graphdriver.NewNaiveLayerIDMapUpdater(d)
|
||||
d.naiveDiff = graphdriver.NewNaiveDiffDriver(d, d.updater)
|
||||
|
||||
return d, nil
|
||||
}
|
||||
|
||||
// Driver holds information about the driver, home directory of the driver.
|
||||
// Driver implements graphdriver.ProtoDriver. It uses only basic vfs operations.
|
||||
// In order to support layering, files are copied from the parent layer into the new layer. There is no copy-on-write support.
|
||||
// Driver must be wrapped in NaiveDiffDriver to be used as a graphdriver.Driver
|
||||
type Driver struct {
|
||||
name string
|
||||
homes []string
|
||||
idMappings *idtools.IDMappings
|
||||
ignoreChownErrors bool
|
||||
naiveDiff graphdriver.DiffDriver
|
||||
updater graphdriver.LayerIDMapUpdater
|
||||
}
|
||||
|
||||
func (d *Driver) String() string {
|
||||
return "vfs"
|
||||
}
|
||||
|
||||
// Status is used for implementing the graphdriver.ProtoDriver interface. VFS does not currently have any status information.
|
||||
func (d *Driver) Status() [][2]string {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Metadata is used for implementing the graphdriver.ProtoDriver interface. VFS does not currently have any meta data.
|
||||
func (d *Driver) Metadata(id string) (map[string]string, error) {
|
||||
return nil, nil //nolint: nilnil
|
||||
}
|
||||
|
||||
// Cleanup is used to implement graphdriver.ProtoDriver. There is no cleanup required for this driver.
|
||||
func (d *Driver) Cleanup() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type fileGetNilCloser struct {
|
||||
storage.FileGetter
|
||||
}
|
||||
|
||||
func (f fileGetNilCloser) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// DiffGetter returns a FileGetCloser that can read files from the directory that
|
||||
// contains files for the layer differences. Used for direct access for tar-split.
|
||||
func (d *Driver) DiffGetter(id string) (graphdriver.FileGetCloser, error) {
|
||||
p := d.dir(id)
|
||||
return fileGetNilCloser{storage.NewPathFileGetter(p)}, nil
|
||||
}
|
||||
|
||||
// CreateFromTemplate creates a layer with the same contents and parent as another layer.
|
||||
func (d *Driver) CreateFromTemplate(id, template string, templateIDMappings *idtools.IDMappings, parent string, parentIDMappings *idtools.IDMappings, opts *graphdriver.CreateOpts, readWrite bool) error {
|
||||
if readWrite {
|
||||
return d.CreateReadWrite(id, template, opts)
|
||||
}
|
||||
return d.Create(id, template, opts)
|
||||
}
|
||||
|
||||
// ApplyDiff applies the new layer into a root
|
||||
func (d *Driver) ApplyDiff(id, parent string, options graphdriver.ApplyDiffOpts) (size int64, err error) {
|
||||
if d.ignoreChownErrors {
|
||||
options.IgnoreChownErrors = d.ignoreChownErrors
|
||||
}
|
||||
return d.naiveDiff.ApplyDiff(id, parent, options)
|
||||
}
|
||||
|
||||
// CreateReadWrite creates a layer that is writable for use as a container
|
||||
// file system.
|
||||
func (d *Driver) CreateReadWrite(id, parent string, opts *graphdriver.CreateOpts) error {
|
||||
return d.create(id, parent, opts, false)
|
||||
}
|
||||
|
||||
// Create prepares the filesystem for the VFS driver and copies the directory for the given id under the parent.
|
||||
func (d *Driver) Create(id, parent string, opts *graphdriver.CreateOpts) error {
|
||||
return d.create(id, parent, opts, true)
|
||||
}
|
||||
|
||||
func (d *Driver) create(id, parent string, opts *graphdriver.CreateOpts, ro bool) (retErr error) {
|
||||
if opts != nil && len(opts.StorageOpt) != 0 {
|
||||
return fmt.Errorf("--storage-opt is not supported for vfs")
|
||||
}
|
||||
|
||||
idMappings := d.idMappings
|
||||
if opts != nil && opts.IDMappings != nil {
|
||||
idMappings = opts.IDMappings
|
||||
}
|
||||
|
||||
dir := d.dir(id)
|
||||
rootIDs := idMappings.RootPair()
|
||||
if err := idtools.MkdirAllAndChown(filepath.Dir(dir), 0o700, rootIDs); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if retErr != nil {
|
||||
os.RemoveAll(dir)
|
||||
}
|
||||
}()
|
||||
|
||||
rootPerms := defaultPerms
|
||||
if runtime.GOOS == "darwin" {
|
||||
rootPerms = os.FileMode(0o700)
|
||||
}
|
||||
|
||||
if parent != "" {
|
||||
st, err := system.Stat(d.dir(parent))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rootPerms = os.FileMode(st.Mode())
|
||||
rootIDs.UID = int(st.UID())
|
||||
rootIDs.GID = int(st.GID())
|
||||
}
|
||||
if err := idtools.MkdirAndChown(dir, rootPerms, rootIDs); err != nil {
|
||||
return err
|
||||
}
|
||||
labelOpts := []string{"level:s0"}
|
||||
if _, mountLabel, err := label.InitLabels(labelOpts); err == nil {
|
||||
label.SetFileLabel(dir, mountLabel)
|
||||
}
|
||||
if parent != "" {
|
||||
parentDir, err := d.Get(parent, graphdriver.MountOpts{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %w", parent, err)
|
||||
}
|
||||
if err := dirCopy(parentDir, dir); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Driver) dir(id string) string {
|
||||
for i, home := range d.homes {
|
||||
if i > 0 {
|
||||
home = filepath.Join(home, d.String())
|
||||
}
|
||||
candidate := filepath.Join(home, "dir", filepath.Base(id))
|
||||
fi, err := os.Stat(candidate)
|
||||
if err == nil && fi.IsDir() {
|
||||
return candidate
|
||||
}
|
||||
}
|
||||
return filepath.Join(d.homes[0], "dir", filepath.Base(id))
|
||||
}
|
||||
|
||||
// Remove deletes the content from the directory for a given id.
|
||||
func (d *Driver) Remove(id string) error {
|
||||
return system.EnsureRemoveAll(d.dir(id))
|
||||
}
|
||||
|
||||
// Get returns the directory for the given id.
|
||||
func (d *Driver) Get(id string, options graphdriver.MountOpts) (_ string, retErr error) {
|
||||
dir := d.dir(id)
|
||||
|
||||
for _, opt := range options.Options {
|
||||
if opt == "ro" {
|
||||
// ignore "ro" option
|
||||
continue
|
||||
}
|
||||
return "", fmt.Errorf("vfs driver does not support mount options")
|
||||
}
|
||||
if st, err := os.Stat(dir); err != nil {
|
||||
return "", err
|
||||
} else if !st.IsDir() {
|
||||
return "", fmt.Errorf("%s: not a directory", dir)
|
||||
}
|
||||
return dir, nil
|
||||
}
|
||||
|
||||
// Put is a noop for vfs that return nil for the error, since this driver has no runtime resources to clean up.
|
||||
func (d *Driver) Put(id string) error {
|
||||
// The vfs driver has no runtime resources (e.g. mounts)
|
||||
// to clean up, so we don't need anything here
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReadWriteDiskUsage returns the disk usage of the writable directory for the ID.
|
||||
// For VFS, it queries the directory for this ID.
|
||||
func (d *Driver) ReadWriteDiskUsage(id string) (*directory.DiskUsage, error) {
|
||||
return directory.Usage(d.dir(id))
|
||||
}
|
||||
|
||||
// Exists checks to see if the directory exists for the given id.
|
||||
func (d *Driver) Exists(id string) bool {
|
||||
_, err := os.Stat(d.dir(id))
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// List layers (not including additional image stores)
|
||||
func (d *Driver) ListLayers() ([]string, error) {
|
||||
entries, err := os.ReadDir(filepath.Join(d.homes[0], "dir"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
layers := make([]string, 0)
|
||||
|
||||
for _, entry := range entries {
|
||||
id := entry.Name()
|
||||
// Does it look like a datadir directory?
|
||||
if !entry.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
layers = append(layers, id)
|
||||
}
|
||||
|
||||
return layers, err
|
||||
}
|
||||
|
||||
// AdditionalImageStores returns additional image stores supported by the driver
|
||||
func (d *Driver) AdditionalImageStores() []string {
|
||||
if len(d.homes) > 1 {
|
||||
return d.homes[1:]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SupportsShifting tells whether the driver support shifting of the UIDs/GIDs in an userNS
|
||||
func (d *Driver) SupportsShifting() bool {
|
||||
return d.updater.SupportsShifting()
|
||||
}
|
||||
|
||||
// UpdateLayerIDMap updates ID mappings in a from matching the ones specified
|
||||
// by toContainer to those specified by toHost.
|
||||
func (d *Driver) UpdateLayerIDMap(id string, toContainer, toHost *idtools.IDMappings, mountLabel string) error {
|
||||
if err := d.updater.UpdateLayerIDMap(id, toContainer, toHost, mountLabel); err != nil {
|
||||
return err
|
||||
}
|
||||
dir := d.dir(id)
|
||||
rootIDs, err := toHost.ToHost(idtools.IDPair{UID: 0, GID: 0})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.Chown(dir, rootIDs.UID, rootIDs.GID)
|
||||
}
|
||||
|
||||
// Changes produces a list of changes between the specified layer
|
||||
// and its parent layer. If parent is "", then all changes will be ADD changes.
|
||||
func (d *Driver) Changes(id string, idMappings *idtools.IDMappings, parent string, parentMappings *idtools.IDMappings, mountLabel string) ([]archive.Change, error) {
|
||||
return d.naiveDiff.Changes(id, idMappings, parent, parentMappings, mountLabel)
|
||||
}
|
||||
|
||||
// Diff produces an archive of the changes between the specified
|
||||
// layer and its parent layer which may be "".
|
||||
func (d *Driver) Diff(id string, idMappings *idtools.IDMappings, parent string, parentMappings *idtools.IDMappings, mountLabel string) (io.ReadCloser, error) {
|
||||
return d.naiveDiff.Diff(id, idMappings, parent, parentMappings, mountLabel)
|
||||
}
|
||||
|
||||
// DiffSize calculates the changes between the specified id
|
||||
// and its parent and returns the size in bytes of the changes
|
||||
// relative to its base filesystem directory.
|
||||
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)
|
||||
}
|
||||
5
vendor/github.com/containers/storage/drivers/windows/jsoniter_windows.go
generated
vendored
Normal file
5
vendor/github.com/containers/storage/drivers/windows/jsoniter_windows.go
generated
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
package windows
|
||||
|
||||
import jsoniter "github.com/json-iterator/go"
|
||||
|
||||
var json = jsoniter.ConfigCompatibleWithStandardLibrary
|
||||
1010
vendor/github.com/containers/storage/drivers/windows/windows.go
generated
vendored
Normal file
1010
vendor/github.com/containers/storage/drivers/windows/windows.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
2
vendor/github.com/containers/storage/drivers/zfs/MAINTAINERS
generated
vendored
Normal file
2
vendor/github.com/containers/storage/drivers/zfs/MAINTAINERS
generated
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
Jörg Thalheim <joerg@higgsboson.tk> (@Mic92)
|
||||
Arthur Gautier <baloo@gandi.net> (@baloose)
|
||||
518
vendor/github.com/containers/storage/drivers/zfs/zfs.go
generated
vendored
Normal file
518
vendor/github.com/containers/storage/drivers/zfs/zfs.go
generated
vendored
Normal file
|
|
@ -0,0 +1,518 @@
|
|||
//go:build linux || freebsd
|
||||
// +build linux freebsd
|
||||
|
||||
package zfs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
graphdriver "github.com/containers/storage/drivers"
|
||||
"github.com/containers/storage/pkg/directory"
|
||||
"github.com/containers/storage/pkg/idtools"
|
||||
"github.com/containers/storage/pkg/mount"
|
||||
"github.com/containers/storage/pkg/parsers"
|
||||
zfs "github.com/mistifyio/go-zfs/v3"
|
||||
"github.com/opencontainers/selinux/go-selinux/label"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
type zfsOptions struct {
|
||||
fsName string
|
||||
mountPath string
|
||||
mountOptions string
|
||||
}
|
||||
|
||||
const defaultPerms = os.FileMode(0o555)
|
||||
|
||||
func init() {
|
||||
graphdriver.MustRegister("zfs", Init)
|
||||
}
|
||||
|
||||
// Logger returns a zfs logger implementation.
|
||||
type Logger struct{}
|
||||
|
||||
// Log wraps log message from ZFS driver with a prefix '[zfs]'.
|
||||
func (*Logger) Log(cmd []string) {
|
||||
logrus.WithField("storage-driver", "zfs").Debugf("%s", strings.Join(cmd, " "))
|
||||
}
|
||||
|
||||
// Init returns a new ZFS driver.
|
||||
// It takes base mount path and an array of options which are represented as key value pairs.
|
||||
// Each option is in the for key=value. 'zfs.fsname' is expected to be a valid key in the options.
|
||||
func Init(base string, opt graphdriver.Options) (graphdriver.Driver, error) {
|
||||
var err error
|
||||
|
||||
logger := logrus.WithField("storage-driver", "zfs")
|
||||
|
||||
if _, err := exec.LookPath("zfs"); err != nil {
|
||||
logger.Debugf("zfs command is not available: %v", err)
|
||||
return nil, fmt.Errorf("the 'zfs' command is not available: %w", graphdriver.ErrPrerequisites)
|
||||
}
|
||||
|
||||
file, err := unix.Open("/dev/zfs", unix.O_RDWR, 0o600)
|
||||
if err != nil {
|
||||
logger.Debugf("cannot open /dev/zfs: %v", err)
|
||||
return nil, fmt.Errorf("could not open /dev/zfs: %v: %w", err, graphdriver.ErrPrerequisites)
|
||||
}
|
||||
defer unix.Close(file)
|
||||
|
||||
options, err := parseOptions(opt.DriverOptions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
options.mountPath = base
|
||||
|
||||
rootdir := path.Dir(base)
|
||||
|
||||
if options.fsName == "" {
|
||||
err = checkRootdirFs(rootdir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if options.fsName == "" {
|
||||
options.fsName, err = lookupZfsDataset(rootdir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
zfs.SetLogger(new(Logger))
|
||||
|
||||
filesystems, err := zfs.Filesystems(options.fsName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot find root filesystem %s: %w", options.fsName, err)
|
||||
}
|
||||
|
||||
filesystemsCache := make(map[string]bool, len(filesystems))
|
||||
var rootDataset *zfs.Dataset
|
||||
for _, fs := range filesystems {
|
||||
if fs.Name == options.fsName {
|
||||
rootDataset = fs
|
||||
}
|
||||
filesystemsCache[fs.Name] = true
|
||||
}
|
||||
|
||||
if rootDataset == nil {
|
||||
return nil, fmt.Errorf("zfs get all -t filesystem -rHp '%s' should contain '%s'", options.fsName, options.fsName)
|
||||
}
|
||||
|
||||
rootUID, rootGID, err := idtools.GetRootUIDGID(opt.UIDMaps, opt.GIDMaps)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get root uid/gid: %w", err)
|
||||
}
|
||||
if err := idtools.MkdirAllAs(base, 0o700, rootUID, rootGID); err != nil {
|
||||
return nil, fmt.Errorf("failed to create '%s': %w", base, err)
|
||||
}
|
||||
|
||||
d := &Driver{
|
||||
dataset: rootDataset,
|
||||
options: options,
|
||||
filesystemsCache: filesystemsCache,
|
||||
uidMaps: opt.UIDMaps,
|
||||
gidMaps: opt.GIDMaps,
|
||||
ctr: graphdriver.NewRefCounter(graphdriver.NewDefaultChecker()),
|
||||
}
|
||||
return graphdriver.NewNaiveDiffDriver(d, graphdriver.NewNaiveLayerIDMapUpdater(d)), nil
|
||||
}
|
||||
|
||||
func parseOptions(opt []string) (zfsOptions, error) {
|
||||
var options zfsOptions
|
||||
options.fsName = ""
|
||||
for _, option := range opt {
|
||||
key, val, err := parsers.ParseKeyValueOpt(option)
|
||||
if err != nil {
|
||||
return options, err
|
||||
}
|
||||
key = strings.ToLower(key)
|
||||
switch key {
|
||||
case "zfs.fsname":
|
||||
options.fsName = val
|
||||
case "zfs.mountopt":
|
||||
options.mountOptions = val
|
||||
default:
|
||||
return options, fmt.Errorf("unknown option %s", key)
|
||||
}
|
||||
}
|
||||
return options, nil
|
||||
}
|
||||
|
||||
func lookupZfsDataset(rootdir string) (string, error) {
|
||||
var stat unix.Stat_t
|
||||
if err := unix.Stat(rootdir, &stat); err != nil {
|
||||
return "", fmt.Errorf("failed to access '%s': %w", rootdir, err)
|
||||
}
|
||||
wantedDev := stat.Dev
|
||||
|
||||
mounts, err := mount.GetMounts()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
for _, m := range mounts {
|
||||
if err := unix.Stat(m.Mountpoint, &stat); err != nil {
|
||||
logrus.WithField("storage-driver", "zfs").Debugf("failed to stat '%s' while scanning for zfs mount: %v", m.Mountpoint, err)
|
||||
continue // may fail on fuse file systems
|
||||
}
|
||||
|
||||
if stat.Dev == wantedDev && m.FSType == "zfs" {
|
||||
return m.Source, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("failed to find zfs dataset mounted on '%s' in /proc/mounts", rootdir)
|
||||
}
|
||||
|
||||
// Driver holds information about the driver, such as zfs dataset, options and cache.
|
||||
type Driver struct {
|
||||
dataset *zfs.Dataset
|
||||
options zfsOptions
|
||||
sync.Mutex // protects filesystem cache against concurrent access
|
||||
filesystemsCache map[string]bool
|
||||
uidMaps []idtools.IDMap
|
||||
gidMaps []idtools.IDMap
|
||||
ctr *graphdriver.RefCounter
|
||||
}
|
||||
|
||||
func (d *Driver) String() string {
|
||||
return "zfs"
|
||||
}
|
||||
|
||||
// Cleanup is called on when program exits, it is a no-op for ZFS.
|
||||
func (d *Driver) Cleanup() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Status returns information about the ZFS filesystem. It returns a two dimensional array of information
|
||||
// such as pool name, dataset name, disk usage, parent quota and compression used.
|
||||
// Currently it return 'Zpool', 'Zpool Health', 'Parent Dataset', 'Space Used By Parent',
|
||||
// 'Space Available', 'Parent Quota' and 'Compression'.
|
||||
func (d *Driver) Status() [][2]string {
|
||||
parts := strings.Split(d.dataset.Name, "/")
|
||||
pool, err := zfs.GetZpool(parts[0])
|
||||
|
||||
var poolName, poolHealth string
|
||||
if err == nil {
|
||||
poolName = pool.Name
|
||||
poolHealth = pool.Health
|
||||
} else {
|
||||
poolName = fmt.Sprintf("error while getting pool information %v", err)
|
||||
poolHealth = "not available"
|
||||
}
|
||||
|
||||
quota := "no"
|
||||
if d.dataset.Quota != 0 {
|
||||
quota = strconv.FormatUint(d.dataset.Quota, 10)
|
||||
}
|
||||
|
||||
return [][2]string{
|
||||
{"Zpool", poolName},
|
||||
{"Zpool Health", poolHealth},
|
||||
{"Parent Dataset", d.dataset.Name},
|
||||
{"Space Used By Parent", strconv.FormatUint(d.dataset.Used, 10)},
|
||||
{"Space Available", strconv.FormatUint(d.dataset.Avail, 10)},
|
||||
{"Parent Quota", quota},
|
||||
{"Compression", d.dataset.Compression},
|
||||
}
|
||||
}
|
||||
|
||||
// Metadata returns image/container metadata related to graph driver
|
||||
func (d *Driver) Metadata(id string) (map[string]string, error) {
|
||||
return map[string]string{
|
||||
"Mountpoint": d.mountPath(id),
|
||||
"Dataset": d.zfsPath(id),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *Driver) cloneFilesystem(name, parentName string) error {
|
||||
snapshotName := fmt.Sprintf("%d", time.Now().Nanosecond())
|
||||
parentDataset := zfs.Dataset{Name: parentName}
|
||||
snapshot, err := parentDataset.Snapshot(snapshotName /*recursive */, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = snapshot.Clone(name, map[string]string{"mountpoint": "legacy"})
|
||||
if err == nil {
|
||||
d.Lock()
|
||||
d.filesystemsCache[name] = true
|
||||
d.Unlock()
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
snapshot.Destroy(zfs.DestroyDeferDeletion)
|
||||
return err
|
||||
}
|
||||
return snapshot.Destroy(zfs.DestroyDeferDeletion)
|
||||
}
|
||||
|
||||
func (d *Driver) zfsPath(id string) string {
|
||||
return d.options.fsName + "/" + id
|
||||
}
|
||||
|
||||
func (d *Driver) mountPath(id string) string {
|
||||
return path.Join(d.options.mountPath, "graph", getMountpoint(id))
|
||||
}
|
||||
|
||||
// CreateFromTemplate creates a layer with the same contents and parent as another layer.
|
||||
func (d *Driver) CreateFromTemplate(id, template string, templateIDMappings *idtools.IDMappings, parent string, parentIDMappings *idtools.IDMappings, opts *graphdriver.CreateOpts, readWrite bool) error {
|
||||
return d.Create(id, template, opts)
|
||||
}
|
||||
|
||||
// CreateReadWrite creates a layer that is writable for use as a container
|
||||
// file system.
|
||||
func (d *Driver) CreateReadWrite(id, parent string, opts *graphdriver.CreateOpts) error {
|
||||
return d.Create(id, parent, opts)
|
||||
}
|
||||
|
||||
// Create prepares the dataset and filesystem for the ZFS driver for the given id under the parent.
|
||||
func (d *Driver) Create(id, parent string, opts *graphdriver.CreateOpts) error {
|
||||
err := d.create(id, parent, opts)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if zfsError, ok := err.(*zfs.Error); ok {
|
||||
if !strings.HasSuffix(zfsError.Stderr, "dataset already exists\n") {
|
||||
return err
|
||||
}
|
||||
// aborted build -> cleanup
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
|
||||
dataset := zfs.Dataset{Name: d.zfsPath(id)}
|
||||
if err := dataset.Destroy(zfs.DestroyRecursiveClones); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// retry
|
||||
return d.create(id, parent, opts)
|
||||
}
|
||||
|
||||
func (d *Driver) create(id, parent string, opts *graphdriver.CreateOpts) error {
|
||||
var storageOpt map[string]string
|
||||
if opts != nil {
|
||||
storageOpt = opts.StorageOpt
|
||||
}
|
||||
|
||||
name := d.zfsPath(id)
|
||||
mountpoint := d.mountPath(id)
|
||||
quota, err := parseStorageOpt(storageOpt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if parent == "" {
|
||||
var rootUID, rootGID int
|
||||
var mountLabel string
|
||||
if opts != nil {
|
||||
rootUID, rootGID, err = idtools.GetRootUIDGID(opts.UIDs(), opts.GIDs())
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get root uid/gid: %w", err)
|
||||
}
|
||||
mountLabel = opts.MountLabel
|
||||
}
|
||||
mountoptions := map[string]string{"mountpoint": "legacy"}
|
||||
fs, err := zfs.CreateFilesystem(name, mountoptions)
|
||||
if err == nil {
|
||||
err = setQuota(name, quota)
|
||||
if err == nil {
|
||||
d.Lock()
|
||||
d.filesystemsCache[fs.Name] = true
|
||||
d.Unlock()
|
||||
}
|
||||
|
||||
if err := idtools.MkdirAllAs(mountpoint, defaultPerms, rootUID, rootGID); err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if err := unix.Rmdir(mountpoint); err != nil && !os.IsNotExist(err) {
|
||||
logrus.Debugf("Failed to remove %s mount point %s: %v", id, mountpoint, err)
|
||||
}
|
||||
}()
|
||||
|
||||
mountOpts := label.FormatMountLabel(d.options.mountOptions, mountLabel)
|
||||
|
||||
if err := mount.Mount(name, mountpoint, "zfs", mountOpts); err != nil {
|
||||
return fmt.Errorf("creating zfs mount: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
if err := detachUnmount(mountpoint); err != nil {
|
||||
logrus.Warnf("failed to unmount %s mount %s: %v", id, mountpoint, err)
|
||||
}
|
||||
}()
|
||||
|
||||
if err := os.Chmod(mountpoint, defaultPerms); err != nil {
|
||||
return fmt.Errorf("setting permissions on zfs mount: %w", err)
|
||||
}
|
||||
|
||||
// this is our first mount after creation of the filesystem, and the root dir may still have root
|
||||
// permissions instead of the remapped root uid:gid (if user namespaces are enabled):
|
||||
if err := os.Chown(mountpoint, rootUID, rootGID); err != nil {
|
||||
return fmt.Errorf("modifying zfs mountpoint (%s) ownership: %w", mountpoint, err)
|
||||
}
|
||||
|
||||
}
|
||||
return err
|
||||
}
|
||||
err = d.cloneFilesystem(name, d.zfsPath(parent))
|
||||
if err == nil {
|
||||
err = setQuota(name, quota)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func parseStorageOpt(storageOpt map[string]string) (string, error) {
|
||||
// Read size to change the disk quota per container
|
||||
for k, v := range storageOpt {
|
||||
key := strings.ToLower(k)
|
||||
switch key {
|
||||
case "size":
|
||||
return v, nil
|
||||
default:
|
||||
return "0", fmt.Errorf("unknown option %s", key)
|
||||
}
|
||||
}
|
||||
return "0", nil
|
||||
}
|
||||
|
||||
func setQuota(name string, quota string) error {
|
||||
if quota == "0" {
|
||||
return nil
|
||||
}
|
||||
fs, err := zfs.GetDataset(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return fs.SetProperty("quota", quota)
|
||||
}
|
||||
|
||||
// Remove deletes the dataset, filesystem and the cache for the given id.
|
||||
func (d *Driver) Remove(id string) error {
|
||||
name := d.zfsPath(id)
|
||||
dataset := zfs.Dataset{Name: name}
|
||||
err := dataset.Destroy(zfs.DestroyRecursive)
|
||||
if err == nil {
|
||||
d.Lock()
|
||||
delete(d.filesystemsCache, name)
|
||||
d.Unlock()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Get returns the mountpoint for the given id after creating the target directories if necessary.
|
||||
func (d *Driver) Get(id string, options graphdriver.MountOpts) (_ string, retErr error) {
|
||||
mountpoint := d.mountPath(id)
|
||||
if count := d.ctr.Increment(mountpoint); count > 1 {
|
||||
return mountpoint, nil
|
||||
}
|
||||
defer func() {
|
||||
if retErr != nil {
|
||||
if c := d.ctr.Decrement(mountpoint); c <= 0 {
|
||||
if mntErr := unix.Unmount(mountpoint, 0); mntErr != nil {
|
||||
logrus.WithField("storage-driver", "zfs").Errorf("Error unmounting %v: %v", mountpoint, mntErr)
|
||||
}
|
||||
if rmErr := unix.Rmdir(mountpoint); rmErr != nil && !os.IsNotExist(rmErr) {
|
||||
logrus.WithField("storage-driver", "zfs").Debugf("Failed to remove %s: %v", id, rmErr)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// In the case of a read-only mount we first mount read-write so we can set the
|
||||
// correct permissions on the mount point and remount read-only afterwards.
|
||||
remountReadOnly := false
|
||||
mountOptions := d.options.mountOptions
|
||||
if len(options.Options) > 0 {
|
||||
var newOptions []string
|
||||
for _, option := range options.Options {
|
||||
if option == "ro" {
|
||||
// Filter out read-only mount option but remember for later remounting.
|
||||
remountReadOnly = true
|
||||
} else {
|
||||
newOptions = append(newOptions, option)
|
||||
}
|
||||
}
|
||||
mountOptions = strings.Join(newOptions, ",")
|
||||
}
|
||||
|
||||
filesystem := d.zfsPath(id)
|
||||
opts := label.FormatMountLabel(mountOptions, options.MountLabel)
|
||||
logrus.WithField("storage-driver", "zfs").Debugf(`mount("%s", "%s", "%s")`, filesystem, mountpoint, opts)
|
||||
|
||||
rootUID, rootGID, err := idtools.GetRootUIDGID(d.uidMaps, d.gidMaps)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// Create the target directories if they don't exist
|
||||
if err := idtools.MkdirAllAs(mountpoint, 0o755, rootUID, rootGID); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if err := mount.Mount(filesystem, mountpoint, "zfs", opts); err != nil {
|
||||
return "", fmt.Errorf("creating zfs mount: %w", err)
|
||||
}
|
||||
|
||||
if remountReadOnly {
|
||||
opts = label.FormatMountLabel("remount,ro", options.MountLabel)
|
||||
if err := mount.Mount(filesystem, mountpoint, "zfs", opts); err != nil {
|
||||
return "", fmt.Errorf("remounting zfs mount read-only: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return mountpoint, nil
|
||||
}
|
||||
|
||||
// Put removes the existing mountpoint for the given id if it exists.
|
||||
func (d *Driver) Put(id string) error {
|
||||
mountpoint := d.mountPath(id)
|
||||
if count := d.ctr.Decrement(mountpoint); count > 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
logger := logrus.WithField("storage-driver", "zfs")
|
||||
|
||||
logger.Debugf(`unmount("%s")`, mountpoint)
|
||||
|
||||
if err := detachUnmount(mountpoint); err != nil {
|
||||
logger.Warnf("Failed to unmount %s mount %s: %v", id, mountpoint, err)
|
||||
}
|
||||
if err := unix.Rmdir(mountpoint); err != nil && !os.IsNotExist(err) {
|
||||
logger.Debugf("Failed to remove %s mount point %s: %v", id, mountpoint, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReadWriteDiskUsage returns the disk usage of the writable directory for the ID.
|
||||
// For ZFS, it queries the full mount path for this ID.
|
||||
func (d *Driver) ReadWriteDiskUsage(id string) (*directory.DiskUsage, error) {
|
||||
return directory.Usage(d.mountPath(id))
|
||||
}
|
||||
|
||||
// Exists checks to see if the cache entry exists for the given id.
|
||||
func (d *Driver) Exists(id string) bool {
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
return d.filesystemsCache[d.zfsPath(id)]
|
||||
}
|
||||
|
||||
// List layers (not including additional image stores). Our layers aren't all
|
||||
// dependent on a single well-known dataset, so we can't reliably tell which
|
||||
// datasets are ours and which ones just look like they could be ours.
|
||||
func (d *Driver) ListLayers() ([]string, error) {
|
||||
return nil, graphdriver.ErrNotSupported
|
||||
}
|
||||
|
||||
// AdditionalImageStores returns additional image stores supported by the driver
|
||||
func (d *Driver) AdditionalImageStores() []string {
|
||||
return nil
|
||||
}
|
||||
33
vendor/github.com/containers/storage/drivers/zfs/zfs_freebsd.go
generated
vendored
Normal file
33
vendor/github.com/containers/storage/drivers/zfs/zfs_freebsd.go
generated
vendored
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
package zfs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
graphdriver "github.com/containers/storage/drivers"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func checkRootdirFs(rootdir string) error {
|
||||
var buf unix.Statfs_t
|
||||
if err := unix.Statfs(rootdir, &buf); err != nil {
|
||||
return fmt.Errorf("failed to access '%s': %s", rootdir, err)
|
||||
}
|
||||
|
||||
// on FreeBSD buf.Fstypename contains ['z', 'f', 's', 0 ... ]
|
||||
if (buf.Fstypename[0] != 122) || (buf.Fstypename[1] != 102) || (buf.Fstypename[2] != 115) || (buf.Fstypename[3] != 0) {
|
||||
logrus.WithField("storage-driver", "zfs").Debugf("no zfs dataset found for rootdir '%s'", rootdir)
|
||||
return fmt.Errorf("no zfs dataset found for rootdir '%s': %w", rootdir, graphdriver.ErrPrerequisites)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getMountpoint(id string) string {
|
||||
return id
|
||||
}
|
||||
|
||||
func detachUnmount(mountpoint string) error {
|
||||
// FreeBSD's MNT_FORCE is roughly equivalent to MNT_DETACH
|
||||
return unix.Unmount(mountpoint, unix.MNT_FORCE)
|
||||
}
|
||||
35
vendor/github.com/containers/storage/drivers/zfs/zfs_linux.go
generated
vendored
Normal file
35
vendor/github.com/containers/storage/drivers/zfs/zfs_linux.go
generated
vendored
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
package zfs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
graphdriver "github.com/containers/storage/drivers"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func checkRootdirFs(rootDir string) error {
|
||||
fsMagic, err := graphdriver.GetFSMagic(rootDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
backingFS := "unknown"
|
||||
if fsName, ok := graphdriver.FsNames[fsMagic]; ok {
|
||||
backingFS = fsName
|
||||
}
|
||||
|
||||
if fsMagic != graphdriver.FsMagicZfs {
|
||||
logrus.WithField("root", rootDir).WithField("backingFS", backingFS).WithField("storage-driver", "zfs").Error("No zfs dataset found for root")
|
||||
return fmt.Errorf("no zfs dataset found for rootdir '%s': %w", rootDir, graphdriver.ErrPrerequisites)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getMountpoint(id string) string {
|
||||
return id
|
||||
}
|
||||
|
||||
func detachUnmount(mountpoint string) error {
|
||||
return unix.Unmount(mountpoint, unix.MNT_DETACH)
|
||||
}
|
||||
4
vendor/github.com/containers/storage/drivers/zfs/zfs_unsupported.go
generated
vendored
Normal file
4
vendor/github.com/containers/storage/drivers/zfs/zfs_unsupported.go
generated
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
//go:build !linux && !freebsd
|
||||
// +build !linux,!freebsd
|
||||
|
||||
package zfs
|
||||
65
vendor/github.com/containers/storage/errors.go
generated
vendored
Normal file
65
vendor/github.com/containers/storage/errors.go
generated
vendored
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
package storage
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/containers/storage/types"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrContainerUnknown indicates that there was no container with the specified name or ID.
|
||||
ErrContainerUnknown = types.ErrContainerUnknown
|
||||
// ErrDigestUnknown indicates that we were unable to compute the digest of a specified item.
|
||||
ErrDigestUnknown = types.ErrDigestUnknown
|
||||
// ErrDuplicateID indicates that an ID which is to be assigned to a new item is already being used.
|
||||
ErrDuplicateID = types.ErrDuplicateID
|
||||
// ErrDuplicateImageNames indicates that the read-only store uses the same name for multiple images.
|
||||
ErrDuplicateImageNames = types.ErrDuplicateImageNames
|
||||
// ErrDuplicateLayerNames indicates that the read-only store uses the same name for multiple layers.
|
||||
ErrDuplicateLayerNames = types.ErrDuplicateLayerNames
|
||||
// ErrDuplicateName indicates that a name which is to be assigned to a new item is already being used.
|
||||
ErrDuplicateName = types.ErrDuplicateName
|
||||
// ErrImageUnknown indicates that there was no image with the specified name or ID.
|
||||
ErrImageUnknown = types.ErrImageUnknown
|
||||
// ErrImageUsedByContainer is returned when the caller attempts to delete an image that is a container's image.
|
||||
ErrImageUsedByContainer = types.ErrImageUsedByContainer
|
||||
// ErrIncompleteOptions is returned when the caller attempts to initialize a Store without providing required information.
|
||||
ErrIncompleteOptions = types.ErrIncompleteOptions
|
||||
// ErrInvalidBigDataName indicates that the name for a big data item is not acceptable; it may be empty.
|
||||
ErrInvalidBigDataName = types.ErrInvalidBigDataName
|
||||
// ErrLayerHasChildren is returned when the caller attempts to delete a layer that has children.
|
||||
ErrLayerHasChildren = types.ErrLayerHasChildren
|
||||
// ErrLayerNotMounted is returned when the requested information can only be computed for a mounted layer, and the layer is not mounted.
|
||||
ErrLayerNotMounted = types.ErrLayerNotMounted
|
||||
// ErrLayerUnknown indicates that there was no layer with the specified name or ID.
|
||||
ErrLayerUnknown = types.ErrLayerUnknown
|
||||
// ErrLayerUsedByContainer is returned when the caller attempts to delete a layer that is a container's layer.
|
||||
ErrLayerUsedByContainer = types.ErrLayerUsedByContainer
|
||||
// ErrLayerUsedByImage is returned when the caller attempts to delete a layer that is an image's top layer.
|
||||
ErrLayerUsedByImage = types.ErrLayerUsedByImage
|
||||
// ErrLoadError indicates that there was an initialization error.
|
||||
ErrLoadError = types.ErrLoadError
|
||||
// ErrNotAContainer is returned when the caller attempts to delete a container that isn't a container.
|
||||
ErrNotAContainer = types.ErrNotAContainer
|
||||
// ErrNotALayer is returned when the caller attempts to delete a layer that isn't a layer.
|
||||
ErrNotALayer = types.ErrNotALayer
|
||||
// ErrNotAnID is returned when the caller attempts to read or write metadata from an item that doesn't exist.
|
||||
ErrNotAnID = types.ErrNotAnID
|
||||
// ErrNotAnImage is returned when the caller attempts to delete an image that isn't an image.
|
||||
ErrNotAnImage = types.ErrNotAnImage
|
||||
// ErrParentIsContainer is returned when a caller attempts to create a layer as a child of a container's layer.
|
||||
ErrParentIsContainer = types.ErrParentIsContainer
|
||||
// ErrParentUnknown indicates that we didn't record the ID of the parent of the specified layer.
|
||||
ErrParentUnknown = types.ErrParentUnknown
|
||||
// ErrSizeUnknown is returned when the caller asks for the size of a big data item, but the Store couldn't determine the answer.
|
||||
ErrSizeUnknown = types.ErrSizeUnknown
|
||||
// ErrStoreIsReadOnly is returned when the caller makes a call to a read-only store that would require modifying its contents.
|
||||
ErrStoreIsReadOnly = types.ErrStoreIsReadOnly
|
||||
// ErrNotSupported is returned when the requested functionality is not supported.
|
||||
ErrNotSupported = types.ErrNotSupported
|
||||
// ErrInvalidMappings is returned when the specified mappings are invalid.
|
||||
ErrInvalidMappings = types.ErrInvalidMappings
|
||||
// ErrInvalidNameOperation is returned when updateName is called with invalid operation.
|
||||
// Internal error
|
||||
errInvalidUpdateNameOperation = errors.New("invalid update name operation")
|
||||
)
|
||||
265
vendor/github.com/containers/storage/idset.go
generated
vendored
Normal file
265
vendor/github.com/containers/storage/idset.go
generated
vendored
Normal file
|
|
@ -0,0 +1,265 @@
|
|||
package storage
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/containers/storage/pkg/idtools"
|
||||
"github.com/containers/storage/types"
|
||||
"github.com/google/go-intervals/intervalset"
|
||||
)
|
||||
|
||||
// idSet represents a set of integer IDs. It is stored as an ordered set of intervals.
|
||||
type idSet struct {
|
||||
set *intervalset.ImmutableSet
|
||||
}
|
||||
|
||||
func newIDSet(intervals []interval) *idSet {
|
||||
s := intervalset.Empty()
|
||||
for _, i := range intervals {
|
||||
s.Add(intervalset.NewSet([]intervalset.Interval{i}))
|
||||
}
|
||||
return &idSet{set: s.ImmutableSet()}
|
||||
}
|
||||
|
||||
// getHostIDs returns all the host ids in the id map.
|
||||
func getHostIDs(idMaps []idtools.IDMap) *idSet {
|
||||
var intervals []interval
|
||||
for _, m := range idMaps {
|
||||
intervals = append(intervals, interval{start: m.HostID, end: m.HostID + m.Size})
|
||||
}
|
||||
return newIDSet(intervals)
|
||||
}
|
||||
|
||||
// getContainerIDs returns all the container ids in the id map.
|
||||
func getContainerIDs(idMaps []idtools.IDMap) *idSet {
|
||||
var intervals []interval
|
||||
for _, m := range idMaps {
|
||||
intervals = append(intervals, interval{start: m.ContainerID, end: m.ContainerID + m.Size})
|
||||
}
|
||||
return newIDSet(intervals)
|
||||
}
|
||||
|
||||
// subtract returns the subtraction of `s` and `t`. `s` and `t` are unchanged.
|
||||
func (s *idSet) subtract(t *idSet) *idSet {
|
||||
if s == nil || t == nil {
|
||||
return s
|
||||
}
|
||||
return &idSet{set: s.set.Sub(t.set)}
|
||||
}
|
||||
|
||||
// union returns the union of `s` and `t`. `s` and `t` are unchanged.
|
||||
func (s *idSet) union(t *idSet) *idSet {
|
||||
if s == nil {
|
||||
return t
|
||||
} else if t == nil {
|
||||
return s
|
||||
}
|
||||
return &idSet{set: s.set.Union(t.set)}
|
||||
}
|
||||
|
||||
// Methods to iterate over the intervals of the idSet. intervalset doesn't provide one :-(
|
||||
|
||||
// iterator to idSet. Returns nil if iteration finishes.
|
||||
type iteratorFn func() *interval
|
||||
|
||||
// cancelFn must be called exactly once unless iteratorFn returns nil, otherwise go routine might
|
||||
// leak.
|
||||
type cancelFn func()
|
||||
|
||||
func (s *idSet) iterator() (iteratorFn, cancelFn) {
|
||||
if s == nil {
|
||||
return func() *interval { return nil }, func() {}
|
||||
}
|
||||
cancelCh := make(chan byte)
|
||||
dataCh := make(chan interval)
|
||||
go func() {
|
||||
s.set.Intervals(func(ii intervalset.Interval) bool {
|
||||
select {
|
||||
case <-cancelCh:
|
||||
return false
|
||||
case dataCh <- ii.(interval):
|
||||
return true
|
||||
}
|
||||
})
|
||||
close(dataCh)
|
||||
}()
|
||||
iterator := func() *interval {
|
||||
i, ok := <-dataCh
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return &i
|
||||
}
|
||||
return iterator, func() { close(cancelCh) }
|
||||
}
|
||||
|
||||
// size returns the total number of ids in the ID set.
|
||||
func (s *idSet) size() int {
|
||||
var size int
|
||||
iterator, cancel := s.iterator()
|
||||
defer cancel()
|
||||
for i := iterator(); i != nil; i = iterator() {
|
||||
size += i.length()
|
||||
}
|
||||
return size
|
||||
}
|
||||
|
||||
// findAvailable finds the `n` ids from `s`.
|
||||
func (s *idSet) findAvailable(n int) (*idSet, error) {
|
||||
var intervals []intervalset.Interval
|
||||
iterator, cancel := s.iterator()
|
||||
defer cancel()
|
||||
for i := iterator(); n > 0 && i != nil; i = iterator() {
|
||||
i.end = minInt(i.end, i.start+n)
|
||||
intervals = append(intervals, *i)
|
||||
n -= i.length()
|
||||
}
|
||||
if n > 0 {
|
||||
return nil, types.ErrNoAvailableIDs
|
||||
}
|
||||
return &idSet{set: intervalset.NewImmutableSet(intervals)}, nil
|
||||
}
|
||||
|
||||
// zip creates an id map from `s` (host ids) and container ids.
|
||||
func (s *idSet) zip(container *idSet) []idtools.IDMap {
|
||||
hostIterator, hostCancel := s.iterator()
|
||||
defer hostCancel()
|
||||
containerIterator, containerCancel := container.iterator()
|
||||
defer containerCancel()
|
||||
var out []idtools.IDMap
|
||||
for h, c := hostIterator(), containerIterator(); h != nil && c != nil; {
|
||||
if n := minInt(h.length(), c.length()); n > 0 {
|
||||
out = append(out, idtools.IDMap{
|
||||
ContainerID: c.start,
|
||||
HostID: h.start,
|
||||
Size: n,
|
||||
})
|
||||
h.start += n
|
||||
c.start += n
|
||||
}
|
||||
if h.IsZero() {
|
||||
h = hostIterator()
|
||||
}
|
||||
if c.IsZero() {
|
||||
c = containerIterator()
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// interval represents an interval of integers [start, end). Note it is allowed to have
|
||||
// start >= end, in which case it is treated as an empty interval. It implements interface
|
||||
// intervalset.Interval.
|
||||
type interval struct {
|
||||
// Start of the interval (inclusive).
|
||||
start int
|
||||
// End of the interval (exclusive).
|
||||
end int
|
||||
}
|
||||
|
||||
func (i interval) length() int {
|
||||
return maxInt(0, i.end-i.start)
|
||||
}
|
||||
|
||||
func (i interval) Intersect(other intervalset.Interval) intervalset.Interval {
|
||||
j := other.(interval)
|
||||
return interval{start: maxInt(i.start, j.start), end: minInt(i.end, j.end)}
|
||||
}
|
||||
|
||||
func (i interval) Before(other intervalset.Interval) bool {
|
||||
j := other.(interval)
|
||||
return !i.IsZero() && !j.IsZero() && i.end < j.start
|
||||
}
|
||||
|
||||
func (i interval) IsZero() bool {
|
||||
return i.length() <= 0
|
||||
}
|
||||
|
||||
func (i interval) Bisect(other intervalset.Interval) (intervalset.Interval, intervalset.Interval) {
|
||||
j := other.(interval)
|
||||
if j.IsZero() {
|
||||
return i, interval{}
|
||||
}
|
||||
// Subtracting [j.start, j.end) is equivalent to the union of intersecting (-inf, j.start) and
|
||||
// [j.end, +inf).
|
||||
left := interval{start: i.start, end: minInt(i.end, j.start)}
|
||||
right := interval{start: maxInt(i.start, j.end), end: i.end}
|
||||
return left, right
|
||||
}
|
||||
|
||||
func (i interval) Adjoin(other intervalset.Interval) intervalset.Interval {
|
||||
j := other.(interval)
|
||||
if !i.IsZero() && !j.IsZero() && (i.end == j.start || j.end == i.start) {
|
||||
return interval{start: minInt(i.start, j.start), end: maxInt(i.end, j.end)}
|
||||
}
|
||||
return interval{}
|
||||
}
|
||||
|
||||
func (i interval) Encompass(other intervalset.Interval) intervalset.Interval {
|
||||
j := other.(interval)
|
||||
switch {
|
||||
case i.IsZero():
|
||||
return j
|
||||
case j.IsZero():
|
||||
return i
|
||||
default:
|
||||
return interval{start: minInt(i.start, j.start), end: maxInt(i.end, j.end)}
|
||||
}
|
||||
}
|
||||
|
||||
func minInt(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func maxInt(a, b int) int {
|
||||
if a < b {
|
||||
return b
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
func hasOverlappingRanges(mappings []idtools.IDMap) error {
|
||||
hostIntervals := intervalset.Empty()
|
||||
containerIntervals := intervalset.Empty()
|
||||
|
||||
var conflicts []string
|
||||
|
||||
for _, m := range mappings {
|
||||
c := interval{start: m.ContainerID, end: m.ContainerID + m.Size}
|
||||
h := interval{start: m.HostID, end: m.HostID + m.Size}
|
||||
|
||||
added := false
|
||||
overlaps := false
|
||||
|
||||
containerIntervals.IntervalsBetween(c, func(x intervalset.Interval) bool {
|
||||
overlaps = true
|
||||
return false
|
||||
})
|
||||
if overlaps {
|
||||
conflicts = append(conflicts, fmt.Sprintf("%v:%v:%v", m.ContainerID, m.HostID, m.Size))
|
||||
added = true
|
||||
}
|
||||
containerIntervals.Add(intervalset.NewSet([]intervalset.Interval{c}))
|
||||
|
||||
hostIntervals.IntervalsBetween(h, func(x intervalset.Interval) bool {
|
||||
overlaps = true
|
||||
return false
|
||||
})
|
||||
if overlaps && !added {
|
||||
conflicts = append(conflicts, fmt.Sprintf("%v:%v:%v", m.ContainerID, m.HostID, m.Size))
|
||||
}
|
||||
hostIntervals.Add(intervalset.NewSet([]intervalset.Interval{h}))
|
||||
}
|
||||
|
||||
if conflicts != nil {
|
||||
if len(conflicts) == 1 {
|
||||
return fmt.Errorf("the specified UID and/or GID mapping %s conflicts with other mappings: %w", conflicts[0], ErrInvalidMappings)
|
||||
}
|
||||
return fmt.Errorf("the specified UID and/or GID mappings %s conflict with other mappings: %w", strings.Join(conflicts, ", "), ErrInvalidMappings)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
1097
vendor/github.com/containers/storage/images.go
generated
vendored
Normal file
1097
vendor/github.com/containers/storage/images.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
5
vendor/github.com/containers/storage/jsoniter.go
generated
vendored
Normal file
5
vendor/github.com/containers/storage/jsoniter.go
generated
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
package storage
|
||||
|
||||
import jsoniter "github.com/json-iterator/go"
|
||||
|
||||
var json = jsoniter.ConfigCompatibleWithStandardLibrary
|
||||
2508
vendor/github.com/containers/storage/layers.go
generated
vendored
Normal file
2508
vendor/github.com/containers/storage/layers.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
18
vendor/github.com/containers/storage/lockfile_compat.go
generated
vendored
Normal file
18
vendor/github.com/containers/storage/lockfile_compat.go
generated
vendored
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
package storage
|
||||
|
||||
import (
|
||||
"github.com/containers/storage/pkg/lockfile"
|
||||
)
|
||||
|
||||
// Deprecated: Use lockfile.*LockFile.
|
||||
type Locker = lockfile.Locker //lint:ignore SA1019 // lockfile.Locker is deprecated
|
||||
|
||||
// Deprecated: Use lockfile.GetLockFile.
|
||||
func GetLockfile(path string) (lockfile.Locker, error) {
|
||||
return lockfile.GetLockfile(path)
|
||||
}
|
||||
|
||||
// Deprecated: Use lockfile.GetROLockFile.
|
||||
func GetROLockfile(path string) (lockfile.Locker, error) {
|
||||
return lockfile.GetROLockfile(path)
|
||||
}
|
||||
176
vendor/github.com/containers/storage/pkg/chrootarchive/archive.go
generated
vendored
Normal file
176
vendor/github.com/containers/storage/pkg/chrootarchive/archive.go
generated
vendored
Normal file
|
|
@ -0,0 +1,176 @@
|
|||
package chrootarchive
|
||||
|
||||
import (
|
||||
stdtar "archive/tar"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"github.com/containers/storage/pkg/archive"
|
||||
"github.com/containers/storage/pkg/idtools"
|
||||
"github.com/containers/storage/pkg/unshare"
|
||||
)
|
||||
|
||||
// NewArchiver returns a new Archiver which uses chrootarchive.Untar
|
||||
func NewArchiver(idMappings *idtools.IDMappings) *archive.Archiver {
|
||||
archiver := archive.NewArchiver(idMappings)
|
||||
archiver.Untar = Untar
|
||||
return archiver
|
||||
}
|
||||
|
||||
// NewArchiverWithChown returns a new Archiver which uses chrootarchive.Untar and the provided ID mapping configuration on both ends
|
||||
func NewArchiverWithChown(tarIDMappings *idtools.IDMappings, chownOpts *idtools.IDPair, untarIDMappings *idtools.IDMappings) *archive.Archiver {
|
||||
archiver := archive.NewArchiverWithChown(tarIDMappings, chownOpts, untarIDMappings)
|
||||
archiver.Untar = Untar
|
||||
return archiver
|
||||
}
|
||||
|
||||
// Untar reads a stream of bytes from `archive`, parses it as a tar archive,
|
||||
// and unpacks it into the directory at `dest`.
|
||||
// The archive may be compressed with one of the following algorithms:
|
||||
//
|
||||
// identity (uncompressed), gzip, bzip2, xz.
|
||||
func Untar(tarArchive io.Reader, dest string, options *archive.TarOptions) error {
|
||||
return untarHandler(tarArchive, dest, options, true, dest)
|
||||
}
|
||||
|
||||
// UntarWithRoot is the same as `Untar`, but allows you to pass in a root directory
|
||||
// The root directory is the directory that will be chrooted to.
|
||||
// `dest` must be a path within `root`, if it is not an error will be returned.
|
||||
//
|
||||
// `root` should set to a directory which is not controlled by any potentially
|
||||
// malicious process.
|
||||
//
|
||||
// This should be used to prevent a potential attacker from manipulating `dest`
|
||||
// such that it would provide access to files outside of `dest` through things
|
||||
// like symlinks. Normally `ResolveSymlinksInScope` would handle this, however
|
||||
// sanitizing symlinks in this manner is inherrently racey:
|
||||
// ref: CVE-2018-15664
|
||||
func UntarWithRoot(tarArchive io.Reader, dest string, options *archive.TarOptions, root string) error {
|
||||
return untarHandler(tarArchive, dest, options, true, root)
|
||||
}
|
||||
|
||||
// UntarUncompressed reads a stream of bytes from `archive`, parses it as a tar archive,
|
||||
// and unpacks it into the directory at `dest`.
|
||||
// The archive must be an uncompressed stream.
|
||||
func UntarUncompressed(tarArchive io.Reader, dest string, options *archive.TarOptions) error {
|
||||
return untarHandler(tarArchive, dest, options, false, dest)
|
||||
}
|
||||
|
||||
// Handler for teasing out the automatic decompression
|
||||
func untarHandler(tarArchive io.Reader, dest string, options *archive.TarOptions, decompress bool, root string) error {
|
||||
if tarArchive == nil {
|
||||
return fmt.Errorf("empty archive")
|
||||
}
|
||||
if options == nil {
|
||||
options = &archive.TarOptions{}
|
||||
options.InUserNS = unshare.IsRootless()
|
||||
}
|
||||
if options.ExcludePatterns == nil {
|
||||
options.ExcludePatterns = []string{}
|
||||
}
|
||||
|
||||
idMappings := idtools.NewIDMappingsFromMaps(options.UIDMaps, options.GIDMaps)
|
||||
rootIDs := idMappings.RootPair()
|
||||
|
||||
dest = filepath.Clean(dest)
|
||||
if _, err := os.Stat(dest); os.IsNotExist(err) {
|
||||
if err := idtools.MkdirAllAndChownNew(dest, 0o755, rootIDs); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
r := tarArchive
|
||||
if decompress {
|
||||
decompressedArchive, err := archive.DecompressStream(tarArchive)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer decompressedArchive.Close()
|
||||
r = decompressedArchive
|
||||
}
|
||||
|
||||
return invokeUnpack(r, dest, options, root)
|
||||
}
|
||||
|
||||
// Tar tars the requested path while chrooted to the specified root.
|
||||
func Tar(srcPath string, options *archive.TarOptions, root string) (io.ReadCloser, error) {
|
||||
if options == nil {
|
||||
options = &archive.TarOptions{}
|
||||
}
|
||||
return invokePack(srcPath, options, root)
|
||||
}
|
||||
|
||||
// CopyFileWithTarAndChown returns a function which copies a single file from outside
|
||||
// of any container into our working container, mapping permissions using the
|
||||
// container's ID maps, possibly overridden using the passed-in chownOpts
|
||||
func CopyFileWithTarAndChown(chownOpts *idtools.IDPair, hasher io.Writer, uidmap []idtools.IDMap, gidmap []idtools.IDMap) func(src, dest string) error {
|
||||
untarMappings := idtools.NewIDMappingsFromMaps(uidmap, gidmap)
|
||||
archiver := NewArchiverWithChown(nil, chownOpts, untarMappings)
|
||||
if hasher != nil {
|
||||
originalUntar := archiver.Untar
|
||||
archiver.Untar = func(tarArchive io.Reader, dest string, options *archive.TarOptions) error {
|
||||
contentReader, contentWriter, err := os.Pipe()
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating pipe extract data to %q: %w", dest, err)
|
||||
}
|
||||
defer contentReader.Close()
|
||||
defer contentWriter.Close()
|
||||
var hashError error
|
||||
var hashWorker sync.WaitGroup
|
||||
hashWorker.Add(1)
|
||||
go func() {
|
||||
t := stdtar.NewReader(contentReader)
|
||||
_, err := t.Next()
|
||||
if err != nil {
|
||||
hashError = err
|
||||
}
|
||||
if _, err = io.Copy(hasher, t); err != nil && err != io.EOF {
|
||||
hashError = err
|
||||
}
|
||||
hashWorker.Done()
|
||||
}()
|
||||
if err = originalUntar(io.TeeReader(tarArchive, contentWriter), dest, options); err != nil {
|
||||
err = fmt.Errorf("extracting data to %q while copying: %w", dest, err)
|
||||
}
|
||||
hashWorker.Wait()
|
||||
if err == nil && hashError != nil {
|
||||
err = fmt.Errorf("calculating digest of data for %q while copying: %w", dest, hashError)
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
return archiver.CopyFileWithTar
|
||||
}
|
||||
|
||||
// CopyWithTarAndChown returns a function which copies a directory tree from outside of
|
||||
// any container into our working container, mapping permissions using the
|
||||
// container's ID maps, possibly overridden using the passed-in chownOpts
|
||||
func CopyWithTarAndChown(chownOpts *idtools.IDPair, hasher io.Writer, uidmap []idtools.IDMap, gidmap []idtools.IDMap) func(src, dest string) error {
|
||||
untarMappings := idtools.NewIDMappingsFromMaps(uidmap, gidmap)
|
||||
archiver := NewArchiverWithChown(nil, chownOpts, untarMappings)
|
||||
if hasher != nil {
|
||||
originalUntar := archiver.Untar
|
||||
archiver.Untar = func(tarArchive io.Reader, dest string, options *archive.TarOptions) error {
|
||||
return originalUntar(io.TeeReader(tarArchive, hasher), dest, options)
|
||||
}
|
||||
}
|
||||
return archiver.CopyWithTar
|
||||
}
|
||||
|
||||
// UntarPathAndChown returns a function which extracts an archive in a specified
|
||||
// location into our working container, mapping permissions using the
|
||||
// container's ID maps, possibly overridden using the passed-in chownOpts
|
||||
func UntarPathAndChown(chownOpts *idtools.IDPair, hasher io.Writer, uidmap []idtools.IDMap, gidmap []idtools.IDMap) func(src, dest string) error {
|
||||
untarMappings := idtools.NewIDMappingsFromMaps(uidmap, gidmap)
|
||||
archiver := NewArchiverWithChown(nil, chownOpts, untarMappings)
|
||||
if hasher != nil {
|
||||
originalUntar := archiver.Untar
|
||||
archiver.Untar = func(tarArchive io.Reader, dest string, options *archive.TarOptions) error {
|
||||
return originalUntar(io.TeeReader(tarArchive, hasher), dest, options)
|
||||
}
|
||||
}
|
||||
return archiver.UntarPath
|
||||
}
|
||||
18
vendor/github.com/containers/storage/pkg/chrootarchive/archive_darwin.go
generated
vendored
Normal file
18
vendor/github.com/containers/storage/pkg/chrootarchive/archive_darwin.go
generated
vendored
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
package chrootarchive
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/containers/storage/pkg/archive"
|
||||
)
|
||||
|
||||
func invokeUnpack(decompressedArchive io.Reader,
|
||||
dest string,
|
||||
options *archive.TarOptions, root string,
|
||||
) error {
|
||||
return archive.Unpack(decompressedArchive, dest, options)
|
||||
}
|
||||
|
||||
func invokePack(srcPath string, options *archive.TarOptions, root string) (io.ReadCloser, error) {
|
||||
return archive.TarWithOptions(srcPath, options)
|
||||
}
|
||||
209
vendor/github.com/containers/storage/pkg/chrootarchive/archive_unix.go
generated
vendored
Normal file
209
vendor/github.com/containers/storage/pkg/chrootarchive/archive_unix.go
generated
vendored
Normal file
|
|
@ -0,0 +1,209 @@
|
|||
//go:build !windows && !darwin
|
||||
// +build !windows,!darwin
|
||||
|
||||
package chrootarchive
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/containers/storage/pkg/archive"
|
||||
"github.com/containers/storage/pkg/reexec"
|
||||
)
|
||||
|
||||
// untar is the entry-point for storage-untar on re-exec. This is not used on
|
||||
// Windows as it does not support chroot, hence no point sandboxing through
|
||||
// chroot and rexec.
|
||||
func untar() {
|
||||
runtime.LockOSThread()
|
||||
flag.Parse()
|
||||
|
||||
var options archive.TarOptions
|
||||
|
||||
// read the options from the pipe "ExtraFiles"
|
||||
if err := json.NewDecoder(os.NewFile(3, "options")).Decode(&options); err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
|
||||
dst := flag.Arg(0)
|
||||
var root string
|
||||
if len(flag.Args()) > 1 {
|
||||
root = flag.Arg(1)
|
||||
}
|
||||
|
||||
if root == "" {
|
||||
root = dst
|
||||
}
|
||||
|
||||
if err := chroot(root); err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
|
||||
if err := archive.Unpack(os.Stdin, dst, &options); err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
// fully consume stdin in case it is zero padded
|
||||
if _, err := flush(os.Stdin); err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
func invokeUnpack(decompressedArchive io.Reader, dest string, options *archive.TarOptions, root string) error {
|
||||
if root == "" {
|
||||
return errors.New("must specify a root to chroot to")
|
||||
}
|
||||
|
||||
// We can't pass a potentially large exclude list directly via cmd line
|
||||
// because we easily overrun the kernel's max argument/environment size
|
||||
// when the full image list is passed (e.g. when this is used by
|
||||
// `docker load`). We will marshall the options via a pipe to the
|
||||
// child
|
||||
r, w, err := os.Pipe()
|
||||
if err != nil {
|
||||
return fmt.Errorf("untar pipe failure: %w", err)
|
||||
}
|
||||
|
||||
if root != "" {
|
||||
relDest, err := filepath.Rel(root, dest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if relDest == "." {
|
||||
relDest = "/"
|
||||
}
|
||||
if relDest[0] != '/' {
|
||||
relDest = "/" + relDest
|
||||
}
|
||||
dest = relDest
|
||||
}
|
||||
|
||||
cmd := reexec.Command("storage-untar", dest, root)
|
||||
cmd.Stdin = decompressedArchive
|
||||
|
||||
cmd.ExtraFiles = append(cmd.ExtraFiles, r)
|
||||
output := bytes.NewBuffer(nil)
|
||||
cmd.Stdout = output
|
||||
cmd.Stderr = output
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
w.Close()
|
||||
return fmt.Errorf("untar error on re-exec cmd: %w", err)
|
||||
}
|
||||
|
||||
// write the options to the pipe for the untar exec to read
|
||||
if err := json.NewEncoder(w).Encode(options); err != nil {
|
||||
w.Close()
|
||||
return fmt.Errorf("untar json encode to pipe failed: %w", err)
|
||||
}
|
||||
w.Close()
|
||||
|
||||
if err := cmd.Wait(); err != nil {
|
||||
// when `xz -d -c -q | storage-untar ...` failed on storage-untar side,
|
||||
// we need to exhaust `xz`'s output, otherwise the `xz` side will be
|
||||
// pending on write pipe forever
|
||||
io.Copy(io.Discard, decompressedArchive)
|
||||
|
||||
return fmt.Errorf("processing tar file(%s): %w", output, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func tar() {
|
||||
runtime.LockOSThread()
|
||||
flag.Parse()
|
||||
|
||||
src := flag.Arg(0)
|
||||
var root string
|
||||
if len(flag.Args()) > 1 {
|
||||
root = flag.Arg(1)
|
||||
}
|
||||
|
||||
if root == "" {
|
||||
root = src
|
||||
}
|
||||
|
||||
if err := realChroot(root); err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
|
||||
var options archive.TarOptions
|
||||
if err := json.NewDecoder(os.Stdin).Decode(&options); err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
|
||||
rdr, err := archive.TarWithOptions(src, &options)
|
||||
if err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
defer rdr.Close()
|
||||
|
||||
if _, err := io.Copy(os.Stdout, rdr); err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
func invokePack(srcPath string, options *archive.TarOptions, root string) (io.ReadCloser, error) {
|
||||
if root == "" {
|
||||
return nil, errors.New("root path must not be empty")
|
||||
}
|
||||
|
||||
relSrc, err := filepath.Rel(root, srcPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if relSrc == "." {
|
||||
relSrc = "/"
|
||||
}
|
||||
if relSrc[0] != '/' {
|
||||
relSrc = "/" + relSrc
|
||||
}
|
||||
|
||||
// make sure we didn't trim a trailing slash with the call to `Rel`
|
||||
if strings.HasSuffix(srcPath, "/") && !strings.HasSuffix(relSrc, "/") {
|
||||
relSrc += "/"
|
||||
}
|
||||
|
||||
cmd := reexec.Command("storage-tar", relSrc, root)
|
||||
|
||||
errBuff := bytes.NewBuffer(nil)
|
||||
cmd.Stderr = errBuff
|
||||
|
||||
tarR, tarW := io.Pipe()
|
||||
cmd.Stdout = tarW
|
||||
|
||||
stdin, err := cmd.StdinPipe()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getting options pipe for tar process: %w", err)
|
||||
}
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
return nil, fmt.Errorf("tar error on re-exec cmd: %w", err)
|
||||
}
|
||||
|
||||
go func() {
|
||||
err := cmd.Wait()
|
||||
if err != nil {
|
||||
err = fmt.Errorf("processing tar file(%s): %w", errBuff, err)
|
||||
}
|
||||
tarW.CloseWithError(err)
|
||||
}()
|
||||
|
||||
if err := json.NewEncoder(stdin).Encode(options); err != nil {
|
||||
stdin.Close()
|
||||
return nil, fmt.Errorf("tar json encode to pipe failed: %w", err)
|
||||
}
|
||||
stdin.Close()
|
||||
|
||||
return tarR, nil
|
||||
}
|
||||
30
vendor/github.com/containers/storage/pkg/chrootarchive/archive_windows.go
generated
vendored
Normal file
30
vendor/github.com/containers/storage/pkg/chrootarchive/archive_windows.go
generated
vendored
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
package chrootarchive
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/containers/storage/pkg/archive"
|
||||
"github.com/containers/storage/pkg/longpath"
|
||||
)
|
||||
|
||||
// chroot is not supported by Windows
|
||||
func chroot(path string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func invokeUnpack(decompressedArchive io.Reader,
|
||||
dest string,
|
||||
options *archive.TarOptions, root string,
|
||||
) error {
|
||||
// Windows is different to Linux here because Windows does not support
|
||||
// chroot. Hence there is no point sandboxing a chrooted process to
|
||||
// do the unpack. We call inline instead within the daemon process.
|
||||
return archive.Unpack(decompressedArchive, longpath.AddPrefix(dest), options)
|
||||
}
|
||||
|
||||
func invokePack(srcPath string, options *archive.TarOptions, root string) (io.ReadCloser, error) {
|
||||
// Windows is different to Linux here because Windows does not support
|
||||
// chroot. Hence there is no point sandboxing a chrooted process to
|
||||
// do the pack. We call inline instead within the daemon process.
|
||||
return archive.TarWithOptions(srcPath, options)
|
||||
}
|
||||
120
vendor/github.com/containers/storage/pkg/chrootarchive/chroot_linux.go
generated
vendored
Normal file
120
vendor/github.com/containers/storage/pkg/chrootarchive/chroot_linux.go
generated
vendored
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
package chrootarchive
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/containers/storage/pkg/mount"
|
||||
"github.com/syndtr/gocapability/capability"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// chroot on linux uses pivot_root instead of chroot
|
||||
// pivot_root takes a new root and an old root.
|
||||
// Old root must be a sub-dir of new root, it is where the current rootfs will reside after the call to pivot_root.
|
||||
// New root is where the new rootfs is set to.
|
||||
// Old root is removed after the call to pivot_root so it is no longer available under the new root.
|
||||
// This is similar to how libcontainer sets up a container's rootfs
|
||||
func chroot(path string) (err error) {
|
||||
caps, err := capability.NewPid(0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// initialize nss libraries in Glibc so that the dynamic libraries are loaded in the host
|
||||
// environment not in the chroot from untrusted files.
|
||||
_, _ = user.Lookup("storage")
|
||||
_, _ = net.LookupHost("localhost")
|
||||
|
||||
// if the process doesn't have CAP_SYS_ADMIN, but does have CAP_SYS_CHROOT, we need to use the actual chroot
|
||||
if !caps.Get(capability.EFFECTIVE, capability.CAP_SYS_ADMIN) && caps.Get(capability.EFFECTIVE, capability.CAP_SYS_CHROOT) {
|
||||
return realChroot(path)
|
||||
}
|
||||
|
||||
if err := unix.Unshare(unix.CLONE_NEWNS); err != nil {
|
||||
return fmt.Errorf("creating mount namespace before pivot: %w", err)
|
||||
}
|
||||
|
||||
// make everything in new ns private
|
||||
if err := mount.MakeRPrivate("/"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if mounted, _ := mount.Mounted(path); !mounted {
|
||||
if err := mount.Mount(path, path, "bind", "rbind,rw"); err != nil {
|
||||
return realChroot(path)
|
||||
}
|
||||
}
|
||||
|
||||
// setup oldRoot for pivot_root
|
||||
pivotDir, err := os.MkdirTemp(path, ".pivot_root")
|
||||
if err != nil {
|
||||
return fmt.Errorf("setting up pivot dir: %w", err)
|
||||
}
|
||||
|
||||
var mounted bool
|
||||
defer func() {
|
||||
if mounted {
|
||||
// make sure pivotDir is not mounted before we try to remove it
|
||||
if errCleanup := unix.Unmount(pivotDir, unix.MNT_DETACH); errCleanup != nil {
|
||||
if err == nil {
|
||||
err = errCleanup
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
errCleanup := os.Remove(pivotDir)
|
||||
// pivotDir doesn't exist if pivot_root failed and chroot+chdir was successful
|
||||
// because we already cleaned it up on failed pivot_root
|
||||
if errCleanup != nil && !os.IsNotExist(errCleanup) {
|
||||
errCleanup = fmt.Errorf("cleaning up after pivot: %w", errCleanup)
|
||||
if err == nil {
|
||||
err = errCleanup
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
if err := unix.PivotRoot(path, pivotDir); err != nil {
|
||||
// If pivot fails, fall back to the normal chroot after cleaning up temp dir
|
||||
if err := os.Remove(pivotDir); err != nil {
|
||||
return fmt.Errorf("cleaning up after failed pivot: %w", err)
|
||||
}
|
||||
return realChroot(path)
|
||||
}
|
||||
mounted = true
|
||||
|
||||
// This is the new path for where the old root (prior to the pivot) has been moved to
|
||||
// This dir contains the rootfs of the caller, which we need to remove so it is not visible during extraction
|
||||
pivotDir = filepath.Join("/", filepath.Base(pivotDir))
|
||||
|
||||
if err := unix.Chdir("/"); err != nil {
|
||||
return fmt.Errorf("changing to new root: %w", err)
|
||||
}
|
||||
|
||||
// Make the pivotDir (where the old root lives) private so it can be unmounted without propagating to the host
|
||||
if err := unix.Mount("", pivotDir, "", unix.MS_PRIVATE|unix.MS_REC, ""); err != nil {
|
||||
return fmt.Errorf("making old root private after pivot: %w", err)
|
||||
}
|
||||
|
||||
// Now unmount the old root so it's no longer visible from the new root
|
||||
if err := unix.Unmount(pivotDir, unix.MNT_DETACH); err != nil {
|
||||
return fmt.Errorf("while unmounting old root after pivot: %w", err)
|
||||
}
|
||||
mounted = false
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func realChroot(path string) error {
|
||||
if err := unix.Chroot(path); err != nil {
|
||||
return fmt.Errorf("after fallback to chroot: %w", err)
|
||||
}
|
||||
if err := unix.Chdir("/"); err != nil {
|
||||
return fmt.Errorf("changing to new root after chroot: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
17
vendor/github.com/containers/storage/pkg/chrootarchive/chroot_unix.go
generated
vendored
Normal file
17
vendor/github.com/containers/storage/pkg/chrootarchive/chroot_unix.go
generated
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
//go:build !windows && !linux && !darwin
|
||||
// +build !windows,!linux,!darwin
|
||||
|
||||
package chrootarchive
|
||||
|
||||
import "golang.org/x/sys/unix"
|
||||
|
||||
func realChroot(path string) error {
|
||||
if err := unix.Chroot(path); err != nil {
|
||||
return err
|
||||
}
|
||||
return unix.Chdir("/")
|
||||
}
|
||||
|
||||
func chroot(path string) error {
|
||||
return realChroot(path)
|
||||
}
|
||||
23
vendor/github.com/containers/storage/pkg/chrootarchive/diff.go
generated
vendored
Normal file
23
vendor/github.com/containers/storage/pkg/chrootarchive/diff.go
generated
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
package chrootarchive
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/containers/storage/pkg/archive"
|
||||
)
|
||||
|
||||
// ApplyLayer parses a diff in the standard layer format from `layer`,
|
||||
// and applies it to the directory `dest`. The stream `layer` can only be
|
||||
// uncompressed.
|
||||
// Returns the size in bytes of the contents of the layer.
|
||||
func ApplyLayer(dest string, layer io.Reader) (size int64, err error) {
|
||||
return applyLayerHandler(dest, layer, &archive.TarOptions{}, true)
|
||||
}
|
||||
|
||||
// ApplyUncompressedLayer parses a diff in the standard layer format from
|
||||
// `layer`, and applies it to the directory `dest`. The stream `layer`
|
||||
// can only be uncompressed.
|
||||
// Returns the size in bytes of the contents of the layer.
|
||||
func ApplyUncompressedLayer(dest string, layer io.Reader, options *archive.TarOptions) (int64, error) {
|
||||
return applyLayerHandler(dest, layer, options, false)
|
||||
}
|
||||
40
vendor/github.com/containers/storage/pkg/chrootarchive/diff_darwin.go
generated
vendored
Normal file
40
vendor/github.com/containers/storage/pkg/chrootarchive/diff_darwin.go
generated
vendored
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
package chrootarchive
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/containers/storage/pkg/archive"
|
||||
)
|
||||
|
||||
// applyLayerHandler parses a diff in the standard layer format from `layer`, and
|
||||
// applies it to the directory `dest`. Returns the size in bytes of the
|
||||
// contents of the layer.
|
||||
func applyLayerHandler(dest string, layer io.Reader, options *archive.TarOptions, decompress bool) (size int64, err error) {
|
||||
dest = filepath.Clean(dest)
|
||||
|
||||
if decompress {
|
||||
decompressed, err := archive.DecompressStream(layer)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer decompressed.Close()
|
||||
|
||||
layer = decompressed
|
||||
}
|
||||
|
||||
tmpDir, err := os.MkdirTemp(os.Getenv("temp"), "temp-storage-extract")
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("ApplyLayer failed to create temp-storage-extract under %s: %w", dest, err)
|
||||
}
|
||||
|
||||
s, err := archive.UnpackLayer(dest, layer, options)
|
||||
os.RemoveAll(tmpDir)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("ApplyLayer %s failed UnpackLayer to %s: %w", layer, dest, err)
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
128
vendor/github.com/containers/storage/pkg/chrootarchive/diff_unix.go
generated
vendored
Normal file
128
vendor/github.com/containers/storage/pkg/chrootarchive/diff_unix.go
generated
vendored
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
//go:build !windows && !darwin
|
||||
// +build !windows,!darwin
|
||||
|
||||
package chrootarchive
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
||||
"github.com/containers/storage/pkg/archive"
|
||||
"github.com/containers/storage/pkg/reexec"
|
||||
"github.com/containers/storage/pkg/system"
|
||||
"github.com/containers/storage/pkg/unshare"
|
||||
)
|
||||
|
||||
type applyLayerResponse struct {
|
||||
LayerSize int64 `json:"layerSize"`
|
||||
}
|
||||
|
||||
// applyLayer is the entry-point for storage-applylayer on re-exec. This is not
|
||||
// used on Windows as it does not support chroot, hence no point sandboxing
|
||||
// through chroot and rexec.
|
||||
func applyLayer() {
|
||||
var (
|
||||
tmpDir string
|
||||
err error
|
||||
options *archive.TarOptions
|
||||
)
|
||||
runtime.LockOSThread()
|
||||
flag.Parse()
|
||||
|
||||
inUserns := unshare.IsRootless()
|
||||
if err := chroot(flag.Arg(0)); err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
|
||||
// We need to be able to set any perms
|
||||
oldmask, err := system.Umask(0)
|
||||
defer system.Umask(oldmask)
|
||||
if err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
|
||||
if err := json.Unmarshal([]byte(os.Getenv("OPT")), &options); err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
|
||||
if inUserns {
|
||||
options.InUserNS = true
|
||||
}
|
||||
|
||||
if tmpDir, err = os.MkdirTemp("/", "temp-storage-extract"); err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
|
||||
os.Setenv("TMPDIR", tmpDir)
|
||||
size, err := archive.UnpackLayer("/", os.Stdin, options)
|
||||
os.RemoveAll(tmpDir)
|
||||
if err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
|
||||
encoder := json.NewEncoder(os.Stdout)
|
||||
if err := encoder.Encode(applyLayerResponse{size}); err != nil {
|
||||
fatal(fmt.Errorf("unable to encode layerSize JSON: %w", err))
|
||||
}
|
||||
|
||||
if _, err := flush(os.Stdin); err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
// applyLayerHandler parses a diff in the standard layer format from `layer`, and
|
||||
// applies it to the directory `dest`. Returns the size in bytes of the
|
||||
// contents of the layer.
|
||||
func applyLayerHandler(dest string, layer io.Reader, options *archive.TarOptions, decompress bool) (size int64, err error) {
|
||||
dest = filepath.Clean(dest)
|
||||
if decompress {
|
||||
decompressed, err := archive.DecompressStream(layer)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer decompressed.Close()
|
||||
|
||||
layer = decompressed
|
||||
}
|
||||
if options == nil {
|
||||
options = &archive.TarOptions{}
|
||||
if unshare.IsRootless() {
|
||||
options.InUserNS = true
|
||||
}
|
||||
}
|
||||
if options.ExcludePatterns == nil {
|
||||
options.ExcludePatterns = []string{}
|
||||
}
|
||||
|
||||
data, err := json.Marshal(options)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("ApplyLayer json encode: %w", err)
|
||||
}
|
||||
|
||||
cmd := reexec.Command("storage-applyLayer", dest)
|
||||
cmd.Stdin = layer
|
||||
cmd.Env = append(os.Environ(), fmt.Sprintf("OPT=%s", data))
|
||||
|
||||
outBuf, errBuf := new(bytes.Buffer), new(bytes.Buffer)
|
||||
cmd.Stdout, cmd.Stderr = outBuf, errBuf
|
||||
|
||||
if err = cmd.Run(); err != nil {
|
||||
return 0, fmt.Errorf("ApplyLayer stdout: %s stderr: %s %w", outBuf, errBuf, err)
|
||||
}
|
||||
|
||||
// Stdout should be a valid JSON struct representing an applyLayerResponse.
|
||||
response := applyLayerResponse{}
|
||||
decoder := json.NewDecoder(outBuf)
|
||||
if err = decoder.Decode(&response); err != nil {
|
||||
return 0, fmt.Errorf("unable to decode ApplyLayer JSON response: %w", err)
|
||||
}
|
||||
|
||||
return response.LayerSize, nil
|
||||
}
|
||||
44
vendor/github.com/containers/storage/pkg/chrootarchive/diff_windows.go
generated
vendored
Normal file
44
vendor/github.com/containers/storage/pkg/chrootarchive/diff_windows.go
generated
vendored
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
package chrootarchive
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/containers/storage/pkg/archive"
|
||||
"github.com/containers/storage/pkg/longpath"
|
||||
)
|
||||
|
||||
// applyLayerHandler parses a diff in the standard layer format from `layer`, and
|
||||
// applies it to the directory `dest`. Returns the size in bytes of the
|
||||
// contents of the layer.
|
||||
func applyLayerHandler(dest string, layer io.Reader, options *archive.TarOptions, decompress bool) (size int64, err error) {
|
||||
dest = filepath.Clean(dest)
|
||||
|
||||
// Ensure it is a Windows-style volume path
|
||||
dest = longpath.AddPrefix(dest)
|
||||
|
||||
if decompress {
|
||||
decompressed, err := archive.DecompressStream(layer)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer decompressed.Close()
|
||||
|
||||
layer = decompressed
|
||||
}
|
||||
|
||||
tmpDir, err := os.MkdirTemp(os.Getenv("temp"), "temp-storage-extract")
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("ApplyLayer failed to create temp-storage-extract under %s. %s", dest, err)
|
||||
}
|
||||
|
||||
s, err := archive.UnpackLayer(dest, layer, nil)
|
||||
os.RemoveAll(tmpDir)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("ApplyLayer %s failed UnpackLayer to %s: %s", layer, dest, err)
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
4
vendor/github.com/containers/storage/pkg/chrootarchive/init_darwin.go
generated
vendored
Normal file
4
vendor/github.com/containers/storage/pkg/chrootarchive/init_darwin.go
generated
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
package chrootarchive
|
||||
|
||||
func init() {
|
||||
}
|
||||
29
vendor/github.com/containers/storage/pkg/chrootarchive/init_unix.go
generated
vendored
Normal file
29
vendor/github.com/containers/storage/pkg/chrootarchive/init_unix.go
generated
vendored
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
//go:build !windows && !darwin
|
||||
// +build !windows,!darwin
|
||||
|
||||
package chrootarchive
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/containers/storage/pkg/reexec"
|
||||
)
|
||||
|
||||
func init() {
|
||||
reexec.Register("storage-applyLayer", applyLayer)
|
||||
reexec.Register("storage-untar", untar)
|
||||
reexec.Register("storage-tar", tar)
|
||||
}
|
||||
|
||||
func fatal(err error) {
|
||||
fmt.Fprint(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// flush consumes all the bytes from the reader discarding
|
||||
// any errors
|
||||
func flush(r io.Reader) (bytes int64, err error) {
|
||||
return io.Copy(io.Discard, r)
|
||||
}
|
||||
4
vendor/github.com/containers/storage/pkg/chrootarchive/init_windows.go
generated
vendored
Normal file
4
vendor/github.com/containers/storage/pkg/chrootarchive/init_windows.go
generated
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
package chrootarchive
|
||||
|
||||
func init() {
|
||||
}
|
||||
8
vendor/github.com/containers/storage/pkg/chrootarchive/jsoniter.go
generated
vendored
Normal file
8
vendor/github.com/containers/storage/pkg/chrootarchive/jsoniter.go
generated
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
//go:build !windows && !darwin
|
||||
// +build !windows,!darwin
|
||||
|
||||
package chrootarchive
|
||||
|
||||
import jsoniter "github.com/json-iterator/go"
|
||||
|
||||
var json = jsoniter.ConfigCompatibleWithStandardLibrary
|
||||
664
vendor/github.com/containers/storage/pkg/chunked/cache_linux.go
generated
vendored
Normal file
664
vendor/github.com/containers/storage/pkg/chunked/cache_linux.go
generated
vendored
Normal file
|
|
@ -0,0 +1,664 @@
|
|||
package chunked
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
storage "github.com/containers/storage"
|
||||
graphdriver "github.com/containers/storage/drivers"
|
||||
"github.com/containers/storage/pkg/chunked/internal"
|
||||
"github.com/containers/storage/pkg/ioutils"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
cacheKey = "chunked-manifest-cache"
|
||||
cacheVersion = 1
|
||||
|
||||
digestSha256Empty = "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
)
|
||||
|
||||
type metadata struct {
|
||||
tagLen int
|
||||
digestLen int
|
||||
tags []byte
|
||||
vdata []byte
|
||||
}
|
||||
|
||||
type layer struct {
|
||||
id string
|
||||
metadata *metadata
|
||||
target string
|
||||
}
|
||||
|
||||
type layersCache struct {
|
||||
layers []layer
|
||||
refs int
|
||||
store storage.Store
|
||||
mutex sync.RWMutex
|
||||
created time.Time
|
||||
}
|
||||
|
||||
var (
|
||||
cacheMutex sync.Mutex
|
||||
cache *layersCache
|
||||
)
|
||||
|
||||
func (c *layersCache) release() {
|
||||
cacheMutex.Lock()
|
||||
defer cacheMutex.Unlock()
|
||||
|
||||
c.refs--
|
||||
if c.refs == 0 {
|
||||
cache = nil
|
||||
}
|
||||
}
|
||||
|
||||
func getLayersCacheRef(store storage.Store) *layersCache {
|
||||
cacheMutex.Lock()
|
||||
defer cacheMutex.Unlock()
|
||||
if cache != nil && cache.store == store && time.Since(cache.created).Minutes() < 10 {
|
||||
cache.refs++
|
||||
return cache
|
||||
}
|
||||
cache := &layersCache{
|
||||
store: store,
|
||||
refs: 1,
|
||||
created: time.Now(),
|
||||
}
|
||||
return cache
|
||||
}
|
||||
|
||||
func getLayersCache(store storage.Store) (*layersCache, error) {
|
||||
c := getLayersCacheRef(store)
|
||||
|
||||
if err := c.load(); err != nil {
|
||||
c.release()
|
||||
return nil, err
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *layersCache) load() error {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
|
||||
allLayers, err := c.store.Layers()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
existingLayers := make(map[string]string)
|
||||
for _, r := range c.layers {
|
||||
existingLayers[r.id] = r.target
|
||||
}
|
||||
|
||||
currentLayers := make(map[string]string)
|
||||
for _, r := range allLayers {
|
||||
currentLayers[r.ID] = r.ID
|
||||
if _, found := existingLayers[r.ID]; found {
|
||||
continue
|
||||
}
|
||||
|
||||
bigData, err := c.store.LayerBigData(r.ID, cacheKey)
|
||||
// if the cache already exists, read and use it
|
||||
if err == nil {
|
||||
defer bigData.Close()
|
||||
metadata, err := readMetadataFromCache(bigData)
|
||||
if err == nil {
|
||||
c.addLayer(r.ID, metadata)
|
||||
continue
|
||||
}
|
||||
logrus.Warningf("Error reading cache file for layer %q: %v", r.ID, err)
|
||||
} else if !errors.Is(err, os.ErrNotExist) {
|
||||
return err
|
||||
}
|
||||
|
||||
var lcd chunkedLayerData
|
||||
|
||||
clFile, err := c.store.LayerBigData(r.ID, chunkedLayerDataKey)
|
||||
if err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||
return err
|
||||
}
|
||||
if clFile != nil {
|
||||
cl, err := io.ReadAll(clFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("open manifest file for layer %q: %w", r.ID, err)
|
||||
}
|
||||
json := jsoniter.ConfigCompatibleWithStandardLibrary
|
||||
if err := json.Unmarshal(cl, &lcd); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// otherwise create it from the layer TOC.
|
||||
manifestReader, err := c.store.LayerBigData(r.ID, bigDataKey)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
defer manifestReader.Close()
|
||||
|
||||
manifest, err := io.ReadAll(manifestReader)
|
||||
if err != nil {
|
||||
return fmt.Errorf("open manifest file for layer %q: %w", r.ID, err)
|
||||
}
|
||||
|
||||
metadata, err := writeCache(manifest, lcd.Format, r.ID, c.store)
|
||||
if err == nil {
|
||||
c.addLayer(r.ID, metadata)
|
||||
}
|
||||
}
|
||||
|
||||
var newLayers []layer
|
||||
for _, l := range c.layers {
|
||||
if _, found := currentLayers[l.id]; found {
|
||||
newLayers = append(newLayers, l)
|
||||
}
|
||||
}
|
||||
c.layers = newLayers
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// calculateHardLinkFingerprint calculates a hash that can be used to verify if a file
|
||||
// is usable for deduplication with hardlinks.
|
||||
// To calculate the digest, it uses the file payload digest, UID, GID, mode and xattrs.
|
||||
func calculateHardLinkFingerprint(f *internal.FileMetadata) (string, error) {
|
||||
digester := digest.Canonical.Digester()
|
||||
|
||||
modeString := fmt.Sprintf("%d:%d:%o", f.UID, f.GID, f.Mode)
|
||||
hash := digester.Hash()
|
||||
|
||||
if _, err := hash.Write([]byte(f.Digest)); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if _, err := hash.Write([]byte(modeString)); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if len(f.Xattrs) > 0 {
|
||||
keys := make([]string, 0, len(f.Xattrs))
|
||||
for k := range f.Xattrs {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
for _, k := range keys {
|
||||
if _, err := hash.Write([]byte(k)); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if _, err := hash.Write([]byte(f.Xattrs[k])); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
}
|
||||
return string(digester.Digest()), nil
|
||||
}
|
||||
|
||||
// generateFileLocation generates a file location in the form $OFFSET@$PATH
|
||||
func generateFileLocation(path string, offset uint64) []byte {
|
||||
return []byte(fmt.Sprintf("%d@%s", offset, path))
|
||||
}
|
||||
|
||||
// generateTag generates a tag in the form $DIGEST$OFFSET@LEN.
|
||||
// the [OFFSET; LEN] points to the variable length data where the file locations
|
||||
// are stored. $DIGEST has length digestLen stored in the metadata file header.
|
||||
func generateTag(digest string, offset, len uint64) string {
|
||||
return fmt.Sprintf("%s%.20d@%.20d", digest, offset, len)
|
||||
}
|
||||
|
||||
type setBigData interface {
|
||||
// SetLayerBigData stores a (possibly large) chunk of named data
|
||||
SetLayerBigData(id, key string, data io.Reader) error
|
||||
}
|
||||
|
||||
// writeCache write a cache for the layer ID.
|
||||
// It generates a sorted list of digests with their offset to the path location and offset.
|
||||
// The same cache is used to lookup files, chunks and candidates for deduplication with hard links.
|
||||
// There are 3 kind of digests stored:
|
||||
// - digest(file.payload))
|
||||
// - digest(digest(file.payload) + file.UID + file.GID + file.mode + file.xattrs)
|
||||
// - digest(i) for each i in chunks(file payload)
|
||||
func writeCache(manifest []byte, format graphdriver.DifferOutputFormat, id string, dest setBigData) (*metadata, error) {
|
||||
var vdata bytes.Buffer
|
||||
tagLen := 0
|
||||
digestLen := 0
|
||||
var tagsBuffer bytes.Buffer
|
||||
|
||||
toc, err := prepareMetadata(manifest, format)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var tags []string
|
||||
for _, k := range toc {
|
||||
if k.Digest != "" {
|
||||
location := generateFileLocation(k.Name, 0)
|
||||
|
||||
off := uint64(vdata.Len())
|
||||
l := uint64(len(location))
|
||||
|
||||
d := generateTag(k.Digest, off, l)
|
||||
if tagLen == 0 {
|
||||
tagLen = len(d)
|
||||
}
|
||||
if tagLen != len(d) {
|
||||
return nil, errors.New("digest with different length found")
|
||||
}
|
||||
tags = append(tags, d)
|
||||
|
||||
fp, err := calculateHardLinkFingerprint(k)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
d = generateTag(fp, off, l)
|
||||
if tagLen != len(d) {
|
||||
return nil, errors.New("digest with different length found")
|
||||
}
|
||||
tags = append(tags, d)
|
||||
|
||||
if _, err := vdata.Write(location); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
digestLen = len(k.Digest)
|
||||
}
|
||||
if k.ChunkDigest != "" {
|
||||
location := generateFileLocation(k.Name, uint64(k.ChunkOffset))
|
||||
off := uint64(vdata.Len())
|
||||
l := uint64(len(location))
|
||||
d := generateTag(k.ChunkDigest, off, l)
|
||||
if tagLen == 0 {
|
||||
tagLen = len(d)
|
||||
}
|
||||
if tagLen != len(d) {
|
||||
return nil, errors.New("digest with different length found")
|
||||
}
|
||||
tags = append(tags, d)
|
||||
|
||||
if _, err := vdata.Write(location); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
digestLen = len(k.ChunkDigest)
|
||||
}
|
||||
}
|
||||
|
||||
sort.Strings(tags)
|
||||
|
||||
for _, t := range tags {
|
||||
if _, err := tagsBuffer.Write([]byte(t)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
pipeReader, pipeWriter := io.Pipe()
|
||||
errChan := make(chan error, 1)
|
||||
go func() {
|
||||
defer pipeWriter.Close()
|
||||
defer close(errChan)
|
||||
|
||||
// version
|
||||
if err := binary.Write(pipeWriter, binary.LittleEndian, uint64(cacheVersion)); err != nil {
|
||||
errChan <- err
|
||||
return
|
||||
}
|
||||
|
||||
// len of a tag
|
||||
if err := binary.Write(pipeWriter, binary.LittleEndian, uint64(tagLen)); err != nil {
|
||||
errChan <- err
|
||||
return
|
||||
}
|
||||
|
||||
// len of a digest
|
||||
if err := binary.Write(pipeWriter, binary.LittleEndian, uint64(digestLen)); err != nil {
|
||||
errChan <- err
|
||||
return
|
||||
}
|
||||
|
||||
// tags length
|
||||
if err := binary.Write(pipeWriter, binary.LittleEndian, uint64(tagsBuffer.Len())); err != nil {
|
||||
errChan <- err
|
||||
return
|
||||
}
|
||||
|
||||
// vdata length
|
||||
if err := binary.Write(pipeWriter, binary.LittleEndian, uint64(vdata.Len())); err != nil {
|
||||
errChan <- err
|
||||
return
|
||||
}
|
||||
|
||||
// tags
|
||||
if _, err := pipeWriter.Write(tagsBuffer.Bytes()); err != nil {
|
||||
errChan <- err
|
||||
return
|
||||
}
|
||||
|
||||
// variable length data
|
||||
if _, err := pipeWriter.Write(vdata.Bytes()); err != nil {
|
||||
errChan <- err
|
||||
return
|
||||
}
|
||||
|
||||
errChan <- nil
|
||||
}()
|
||||
defer pipeReader.Close()
|
||||
|
||||
counter := ioutils.NewWriteCounter(io.Discard)
|
||||
|
||||
r := io.TeeReader(pipeReader, counter)
|
||||
|
||||
if err := dest.SetLayerBigData(id, cacheKey, r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := <-errChan; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
logrus.Debugf("Written lookaside cache for layer %q with length %v", id, counter.Count)
|
||||
|
||||
return &metadata{
|
||||
digestLen: digestLen,
|
||||
tagLen: tagLen,
|
||||
tags: tagsBuffer.Bytes(),
|
||||
vdata: vdata.Bytes(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func readMetadataFromCache(bigData io.Reader) (*metadata, error) {
|
||||
var version, tagLen, digestLen, tagsLen, vdataLen uint64
|
||||
if err := binary.Read(bigData, binary.LittleEndian, &version); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if version != cacheVersion {
|
||||
return nil, nil //nolint: nilnil
|
||||
}
|
||||
if err := binary.Read(bigData, binary.LittleEndian, &tagLen); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := binary.Read(bigData, binary.LittleEndian, &digestLen); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := binary.Read(bigData, binary.LittleEndian, &tagsLen); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := binary.Read(bigData, binary.LittleEndian, &vdataLen); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tags := make([]byte, tagsLen)
|
||||
if _, err := bigData.Read(tags); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
vdata := make([]byte, vdataLen)
|
||||
if _, err := bigData.Read(vdata); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &metadata{
|
||||
tagLen: int(tagLen),
|
||||
digestLen: int(digestLen),
|
||||
tags: tags,
|
||||
vdata: vdata,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func prepareMetadata(manifest []byte, format graphdriver.DifferOutputFormat) ([]*internal.FileMetadata, error) {
|
||||
toc, err := unmarshalToc(manifest)
|
||||
if err != nil {
|
||||
// ignore errors here. They might be caused by a different manifest format.
|
||||
logrus.Debugf("could not unmarshal manifest: %v", err)
|
||||
return nil, nil //nolint: nilnil
|
||||
}
|
||||
|
||||
switch format {
|
||||
case graphdriver.DifferOutputFormatDir:
|
||||
case graphdriver.DifferOutputFormatFlat:
|
||||
toc.Entries, err = makeEntriesFlat(toc.Entries)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown format %q", format)
|
||||
}
|
||||
|
||||
var r []*internal.FileMetadata
|
||||
chunkSeen := make(map[string]bool)
|
||||
for i := range toc.Entries {
|
||||
d := toc.Entries[i].Digest
|
||||
if d != "" {
|
||||
r = append(r, &toc.Entries[i])
|
||||
continue
|
||||
}
|
||||
|
||||
// chunks do not use hard link dedup so keeping just one candidate is enough
|
||||
cd := toc.Entries[i].ChunkDigest
|
||||
if cd != "" && !chunkSeen[cd] {
|
||||
r = append(r, &toc.Entries[i])
|
||||
chunkSeen[cd] = true
|
||||
}
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (c *layersCache) addLayer(id string, metadata *metadata) error {
|
||||
target, err := c.store.DifferTarget(id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get checkout directory layer %q: %w", id, err)
|
||||
}
|
||||
|
||||
l := layer{
|
||||
id: id,
|
||||
metadata: metadata,
|
||||
target: target,
|
||||
}
|
||||
c.layers = append(c.layers, l)
|
||||
return nil
|
||||
}
|
||||
|
||||
func byteSliceAsString(b []byte) string {
|
||||
return *(*string)(unsafe.Pointer(&b))
|
||||
}
|
||||
|
||||
func findTag(digest string, metadata *metadata) (string, uint64, uint64) {
|
||||
if len(digest) != metadata.digestLen {
|
||||
return "", 0, 0
|
||||
}
|
||||
|
||||
nElements := len(metadata.tags) / metadata.tagLen
|
||||
|
||||
i := sort.Search(nElements, func(i int) bool {
|
||||
d := byteSliceAsString(metadata.tags[i*metadata.tagLen : i*metadata.tagLen+metadata.digestLen])
|
||||
return strings.Compare(d, digest) >= 0
|
||||
})
|
||||
if i < nElements {
|
||||
d := string(metadata.tags[i*metadata.tagLen : i*metadata.tagLen+len(digest)])
|
||||
if digest == d {
|
||||
startOff := i*metadata.tagLen + metadata.digestLen
|
||||
parts := strings.Split(string(metadata.tags[startOff:(i+1)*metadata.tagLen]), "@")
|
||||
off, _ := strconv.ParseInt(parts[0], 10, 64)
|
||||
len, _ := strconv.ParseInt(parts[1], 10, 64)
|
||||
return digest, uint64(off), uint64(len)
|
||||
}
|
||||
}
|
||||
return "", 0, 0
|
||||
}
|
||||
|
||||
func (c *layersCache) findDigestInternal(digest string) (string, string, int64, error) {
|
||||
if digest == "" {
|
||||
return "", "", -1, nil
|
||||
}
|
||||
|
||||
c.mutex.RLock()
|
||||
defer c.mutex.RUnlock()
|
||||
|
||||
for _, layer := range c.layers {
|
||||
digest, off, len := findTag(digest, layer.metadata)
|
||||
if digest != "" {
|
||||
position := string(layer.metadata.vdata[off : off+len])
|
||||
parts := strings.SplitN(position, "@", 2)
|
||||
offFile, _ := strconv.ParseInt(parts[0], 10, 64)
|
||||
return layer.target, parts[1], offFile, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", "", -1, nil
|
||||
}
|
||||
|
||||
// findFileInOtherLayers finds the specified file in other layers.
|
||||
// file is the file to look for.
|
||||
func (c *layersCache) findFileInOtherLayers(file *internal.FileMetadata, useHardLinks bool) (string, string, error) {
|
||||
digest := file.Digest
|
||||
if useHardLinks {
|
||||
var err error
|
||||
digest, err = calculateHardLinkFingerprint(file)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
}
|
||||
target, name, off, err := c.findDigestInternal(digest)
|
||||
if off == 0 {
|
||||
return target, name, err
|
||||
}
|
||||
return "", "", nil
|
||||
}
|
||||
|
||||
func (c *layersCache) findChunkInOtherLayers(chunk *internal.FileMetadata) (string, string, int64, error) {
|
||||
return c.findDigestInternal(chunk.ChunkDigest)
|
||||
}
|
||||
|
||||
func unmarshalToc(manifest []byte) (*internal.TOC, error) {
|
||||
var buf bytes.Buffer
|
||||
count := 0
|
||||
var toc internal.TOC
|
||||
|
||||
iter := jsoniter.ParseBytes(jsoniter.ConfigFastest, manifest)
|
||||
for field := iter.ReadObject(); field != ""; field = iter.ReadObject() {
|
||||
if strings.ToLower(field) != "entries" {
|
||||
iter.Skip()
|
||||
continue
|
||||
}
|
||||
for iter.ReadArray() {
|
||||
for field := iter.ReadObject(); field != ""; field = iter.ReadObject() {
|
||||
switch strings.ToLower(field) {
|
||||
case "type", "name", "linkname", "digest", "chunkdigest", "chunktype", "modtime", "accesstime", "changetime":
|
||||
count += len(iter.ReadStringAsSlice())
|
||||
case "xattrs":
|
||||
for key := iter.ReadObject(); key != ""; key = iter.ReadObject() {
|
||||
count += len(iter.ReadStringAsSlice())
|
||||
}
|
||||
default:
|
||||
iter.Skip()
|
||||
}
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
buf.Grow(count)
|
||||
|
||||
getString := func(b []byte) string {
|
||||
from := buf.Len()
|
||||
buf.Write(b)
|
||||
to := buf.Len()
|
||||
return byteSliceAsString(buf.Bytes()[from:to])
|
||||
}
|
||||
|
||||
iter = jsoniter.ParseBytes(jsoniter.ConfigFastest, manifest)
|
||||
for field := iter.ReadObject(); field != ""; field = iter.ReadObject() {
|
||||
if strings.ToLower(field) == "version" {
|
||||
toc.Version = iter.ReadInt()
|
||||
continue
|
||||
}
|
||||
if strings.ToLower(field) != "entries" {
|
||||
iter.Skip()
|
||||
continue
|
||||
}
|
||||
for iter.ReadArray() {
|
||||
var m internal.FileMetadata
|
||||
for field := iter.ReadObject(); field != ""; field = iter.ReadObject() {
|
||||
switch strings.ToLower(field) {
|
||||
case "type":
|
||||
m.Type = getString(iter.ReadStringAsSlice())
|
||||
case "name":
|
||||
m.Name = getString(iter.ReadStringAsSlice())
|
||||
case "linkname":
|
||||
m.Linkname = getString(iter.ReadStringAsSlice())
|
||||
case "mode":
|
||||
m.Mode = iter.ReadInt64()
|
||||
case "size":
|
||||
m.Size = iter.ReadInt64()
|
||||
case "uid":
|
||||
m.UID = iter.ReadInt()
|
||||
case "gid":
|
||||
m.GID = iter.ReadInt()
|
||||
case "modtime":
|
||||
time, err := time.Parse(time.RFC3339, byteSliceAsString(iter.ReadStringAsSlice()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m.ModTime = &time
|
||||
case "accesstime":
|
||||
time, err := time.Parse(time.RFC3339, byteSliceAsString(iter.ReadStringAsSlice()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m.AccessTime = &time
|
||||
case "changetime":
|
||||
time, err := time.Parse(time.RFC3339, byteSliceAsString(iter.ReadStringAsSlice()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m.ChangeTime = &time
|
||||
case "devmajor":
|
||||
m.Devmajor = iter.ReadInt64()
|
||||
case "devminor":
|
||||
m.Devminor = iter.ReadInt64()
|
||||
case "digest":
|
||||
m.Digest = getString(iter.ReadStringAsSlice())
|
||||
case "offset":
|
||||
m.Offset = iter.ReadInt64()
|
||||
case "endoffset":
|
||||
m.EndOffset = iter.ReadInt64()
|
||||
case "chunksize":
|
||||
m.ChunkSize = iter.ReadInt64()
|
||||
case "chunkoffset":
|
||||
m.ChunkOffset = iter.ReadInt64()
|
||||
case "chunkdigest":
|
||||
m.ChunkDigest = getString(iter.ReadStringAsSlice())
|
||||
case "chunktype":
|
||||
m.ChunkType = getString(iter.ReadStringAsSlice())
|
||||
case "xattrs":
|
||||
m.Xattrs = make(map[string]string)
|
||||
for key := iter.ReadObject(); key != ""; key = iter.ReadObject() {
|
||||
value := iter.ReadStringAsSlice()
|
||||
m.Xattrs[key] = getString(value)
|
||||
}
|
||||
default:
|
||||
iter.Skip()
|
||||
}
|
||||
}
|
||||
if m.Type == TypeReg && m.Size == 0 && m.Digest == "" {
|
||||
m.Digest = digestSha256Empty
|
||||
}
|
||||
toc.Entries = append(toc.Entries, m)
|
||||
}
|
||||
break
|
||||
}
|
||||
toc.StringsBuf = buf
|
||||
return &toc, nil
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue