diff --git a/.github/workflows/snapshotter.yaml b/.github/workflows/snapshotter.yaml new file mode 100644 index 0000000000..16cc52c837 --- /dev/null +++ b/.github/workflows/snapshotter.yaml @@ -0,0 +1,71 @@ +name: Snapshotter Testing +on: + push: + paths-ignore: + - "install.sh" + - "tests/cgroup2/**" + - "tests/install/**" + - "tests/integration/**" + - "tests/unitcoverage/**" + pull_request: + paths-ignore: + - "install.sh" + - "tests/cgroup2/**" + - "tests/install/**" + - "tests/integration/**" + - "tests/unitcoverage/**" + workflow_dispatch: {} +jobs: + build: + name: "Build" + runs-on: ubuntu-20.04 + timeout-minutes: 40 + steps: + - name: "Checkout" + uses: actions/checkout@v2 + with: + fetch-depth: 1 + - name: "Build" + run: DOCKER_BUILDKIT=1 SKIP_VALIDATE=1 make + - name: "Upload Binary" + uses: actions/upload-artifact@v2 + with: + name: k3s + path: dist/artifacts/k3s + - name: "Upload Images" + uses: actions/upload-artifact@v2 + with: + name: k3s-airgap-images-amd64.tar + path: dist/artifacts/k3s-airgap-images-amd64.tar + test: + name: "Test" + # nested virtualization is only available on macOS hosts + runs-on: macos-10.15 + needs: build + timeout-minutes: 40 + strategy: + matrix: + vm: [opensuse-leap] + steps: + - name: "Checkout" + uses: actions/checkout@v2 + with: + fetch-depth: 1 + - name: "Download Binary" + uses: actions/download-artifact@v2 + with: + name: k3s + path: dist/artifacts/ + - name: "Download Images" + uses: actions/download-artifact@v2 + with: + name: k3s-airgap-images-amd64.tar + path: dist/artifacts/ + - name: "Vagrant Plugin(s)" + working-directory: tests/snapshotter/btrfs/${{ matrix.vm }} + run: vagrant plugin install vagrant-k3s + - name: "Vagrant VM" + working-directory: tests/snapshotter/btrfs/${{ matrix.vm }} + env: + VAGRANT_EXPERIMENTAL: disks + run: vagrant up diff --git a/Dockerfile.dapper b/Dockerfile.dapper index e2347c09f9..2521a51b46 100644 --- a/Dockerfile.dapper +++ b/Dockerfile.dapper @@ -11,7 +11,7 @@ ENV no_proxy=$no_proxy RUN apk -U --no-cache add bash git gcc musl-dev docker vim less file curl wget ca-certificates jq linux-headers \ zlib-dev tar zip squashfs-tools npm coreutils python3 openssl-dev libffi-dev libseccomp libseccomp-dev \ libseccomp-static make libuv-static sqlite-dev sqlite-static libselinux libselinux-dev zlib-dev zlib-static \ - zstd gzip alpine-sdk binutils-gold + zstd gzip alpine-sdk binutils-gold btrfs-progs-dev btrfs-progs-static RUN if [ "$(go env GOARCH)" = "arm64" ]; then \ wget https://github.com/aquasecurity/trivy/releases/download/v0.16.0/trivy_0.16.0_Linux-ARM64.tar.gz && \ tar -zxvf trivy_0.16.0_Linux-ARM64.tar.gz && \ diff --git a/go.sum b/go.sum index 1c2c0d4fbe..113e359bf4 100644 --- a/go.sum +++ b/go.sum @@ -155,6 +155,7 @@ github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u9 github.com/container-storage-interface/spec v1.5.0 h1:lvKxe3uLgqQeVQcrnL2CPQKISoKjTJxojEs9cBk+HXo= github.com/container-storage-interface/spec v1.5.0/go.mod h1:8K96oQNkJ7pFcC2R9Z1ynGGBB1I93kcS6PGg3SsOk8s= github.com/containerd/aufs v1.0.0/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU= +github.com/containerd/btrfs v1.0.0 h1:osn1exbzdub9L5SouXO5swW4ea/xVdJZ3wokxN5GrnA= github.com/containerd/btrfs v1.0.0/go.mod h1:zMcX3qkXTAi9GI50+0HOeuV8LU2ryCE/V2vG/ZBiTss= github.com/containerd/cgroups v1.0.1 h1:iJnMvco9XGvKUvNQkv88bE4uJXxRQH18efbKo9w5vHQ= github.com/containerd/cgroups v1.0.1/go.mod h1:0SJrPIenamHDcZhEcJMNBB85rHcUsw4f25ZfBiPYRkU= diff --git a/pkg/containerd/builtins_linux.go b/pkg/containerd/builtins_linux.go index 5de89207ab..a49b4c65a0 100644 --- a/pkg/containerd/builtins_linux.go +++ b/pkg/containerd/builtins_linux.go @@ -24,6 +24,7 @@ import ( _ "github.com/containerd/containerd/runtime/v2/runc/options" _ "github.com/containerd/containerd/snapshots/native/plugin" _ "github.com/containerd/containerd/snapshots/overlay/plugin" + _ "github.com/containerd/containerd/snapshots/btrfs/plugin" _ "github.com/containerd/fuse-overlayfs-snapshotter/plugin" _ "github.com/containerd/stargz-snapshotter/service/plugin" ) diff --git a/scripts/build b/scripts/build index 3b8b9275fd..bd0690a18e 100755 --- a/scripts/build +++ b/scripts/build @@ -42,7 +42,7 @@ STATIC=" STATIC_SQLITE=" -extldflags '-static -lm -ldl -lz -lpthread' " -TAGS="ctrd apparmor seccomp no_btrfs netcgo osusergo providerless" +TAGS="ctrd apparmor seccomp netcgo osusergo providerless" RUNC_TAGS="apparmor seccomp" RUNC_STATIC="static" diff --git a/tests/snapshotter/btrfs/opensuse-leap/Vagrantfile b/tests/snapshotter/btrfs/opensuse-leap/Vagrantfile new file mode 100644 index 0000000000..975224a19f --- /dev/null +++ b/tests/snapshotter/btrfs/opensuse-leap/Vagrantfile @@ -0,0 +1,100 @@ +# -*- mode: ruby -*- +# vi: set ft=ruby : +# + +ENV['TEST_VM_NAME'] ||= 'snapshotter-btrfs' +ENV['TEST_VM_HOSTNAME'] ||= 'test' + +Vagrant.configure("2") do |config| + config.vagrant.plugins = { + 'vagrant-k3s' => {:version => '~> 0.1.3'}, + } + config.vm.define ENV['TEST_VM_NAME'], primary: true do |test| + test.vm.box = "opensuse/Leap-15.3.x86_64" + test.vm.hostname = ENV['TEST_VM_HOSTNAME'] + test.vm.provision 'k3s-prepare', type: 'shell', run: 'once', privileged: true do |sh| + sh.inline = <<~EOF + #!/usr/bin/env bash + set -eu -o pipefail + zypper install -y btrfsprogs hostname + mkdir -p /var/lib/rancher/k3s /etc/rancher/k3s /usr/local/bin + if ! mountpoint -q /var/lib/rancher/k3s; then + : ${BTRFS_DEV:=#{ENV['BTRFS_DEV']}} + for disk in sd[b-d] vd[b-d] xd[b-d]; do + if [ -n "${BTRFS_DEV}" ]; then break; fi + : ${BTRFS_DEV:=$(test -b /dev/$disk && echo $disk)} + done + btrfs filesystem show /dev/${BTRFS_DEV:?unable to determine automatically, please specify} 2>/dev/null || mkfs -t btrfs /dev/${BTRFS_DEV} + mountpoint -q /mnt || mount -t btrfs /dev/${BTRFS_DEV} /mnt + btrfs subvolume show /mnt/@k3s 2>/dev/null || btrfs subvolume create /mnt/@k3s + umount /mnt + mount -t btrfs -o subvol=@k3s /dev/${BTRFS_DEV} /var/lib/rancher/k3s + fi + if [ -e /vagrant/k3s ]; then + cp -vf /vagrant/k3s /usr/local/bin/ + chmod -v +x /usr/local/bin/k3s + fi + if [ -e /vagrant/*.tar ]; then + mkdir -vp /var/lib/rancher/k3s/agent/images + for tar in /vagrant/*.tar; do + cp -vf $tar /var/lib/rancher/k3s/agent/images/ + done + fi + EOF + end + test.vm.provision 'k3s-install', type: 'k3s', run: 'once' do |k3s| + k3s.args = %w[server --snapshotter=btrfs] + k3s.env = %w[INSTALL_K3S_NAME=server INSTALL_K3S_SKIP_DOWNLOAD=true K3S_TOKEN=vagrant] + k3s.config = { + 'disable' => %w[local-storage metrics-server servicelb traefik], + 'disable-helm-controller' => true, + 'disable-network-policy' => true, + 'write-kubeconfig-mode' => '0644', + } + k3s.config_mode = '0644' # side-step https://github.com/k3s-io/k3s/issues/4321 + end + test.vm.provision "k3s-ready", type: "shell", run: "once" do |sh| + sh.env = { :PATH => "/usr/local/bin:/usr/local/sbin:/sbin:/bin:/usr/sbin:/usr/bin:/root/bin" } + sh.inline = <<~SHELL + #!/usr/bin/env bash + set -eu -o pipefail + echo 'Waiting for node to be ready ...' + time timeout 120 bash -c 'while ! (kubectl wait --for condition=ready node/$(hostname) 2>/dev/null); do sleep 5; done' + time timeout 300 bash -c 'while ! (kubectl --namespace kube-system rollout status --timeout 10s deploy/coredns 2>/dev/null); do sleep 5; done' + SHELL + end + test.vm.provision "k3s-status", type: "shell", run: "once" do |sh| + sh.env = { :PATH => "/usr/local/bin:/usr/local/sbin:/sbin:/bin:/usr/sbin:/usr/bin:/root/bin" } + sh.inline = <<~SHELL + #!/usr/bin/env bash + set -eux -o pipefail + kubectl get node,all -A -o wide + btrfs subvolume list /var/lib/rancher/k3s/agent/containerd/io.containerd.snapshotter.v1.btrfs + SHELL + end + end + + %w[hyperv libvirt virtualbox vmware_desktop].each do |p| + config.vm.provider p do |v| + v.cpus = ENV['CPUS'] || 2 + v.memory = ENV['MEMORY'] || 2048 + end + end + + config.vm.provider :hyperv do |v,o| + o.vm.disk :disk, name: "btrfs", size: "8GB" # Requires VAGRANT_EXPERIMENTAL="disks" + end + + config.vm.provider :libvirt do |v,o| + v.storage :file, :size => '8G' + end + + config.vm.provider :virtualbox do |v,o| + v.gui = false + v.check_guest_additions = false + o.vm.disk :disk, name: "btrfs", size: "8GB" # Requires VAGRANT_EXPERIMENTAL="disks" + end + + config.vm.synced_folder '../../../../dist/artifacts', '/vagrant', type: 'rsync', disabled: ['1', 'true'].include?(ENV['RSYNC_DISABLE']), + rsync__exclude: ENV['RSYNC_EXCLUDE'] || '*.tar.*' +end diff --git a/vendor/github.com/containerd/btrfs/.gitignore b/vendor/github.com/containerd/btrfs/.gitignore new file mode 100644 index 0000000000..9b781b52b9 --- /dev/null +++ b/vendor/github.com/containerd/btrfs/.gitignore @@ -0,0 +1,28 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so +bin/ + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof + +# Support running go modules in vendor mode for local development +/vendor/ diff --git a/vendor/github.com/containerd/btrfs/LICENSE b/vendor/github.com/containerd/btrfs/LICENSE new file mode 100644 index 0000000000..8dada3edaf --- /dev/null +++ b/vendor/github.com/containerd/btrfs/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/containerd/btrfs/Makefile b/vendor/github.com/containerd/btrfs/Makefile new file mode 100644 index 0000000000..e89dd46ad1 --- /dev/null +++ b/vendor/github.com/containerd/btrfs/Makefile @@ -0,0 +1,34 @@ +# Copyright The containerd Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +.PHONY: clean binaries generate lint vet test +all: vet lint test binaries + +binaries: bin/btrfs-test + +vet: + go vet ./... + +lint: + golint ./... + +test: + go test -v ./... + +bin/%: ./cmd/% *.go + go build -o ./$@ ./$< + +clean: + rm -rf bin/* diff --git a/vendor/github.com/containerd/btrfs/README.md b/vendor/github.com/containerd/btrfs/README.md new file mode 100644 index 0000000000..505f39b121 --- /dev/null +++ b/vendor/github.com/containerd/btrfs/README.md @@ -0,0 +1,46 @@ +# go-btrfs + +[![PkgGoDev](https://pkg.go.dev/badge/github.com/containerd/btrfs)](https://pkg.go.dev/github.com/containerd/btrfs) +[![Build Status](https://github.com/containerd/btrfs/workflows/CI/badge.svg)](https://github.com/containerd/btrfs/actions?query=workflow%3ACI) +[![Go Report Card](https://goreportcard.com/badge/github.com/containerd/btrfs)](https://goreportcard.com/report/github.com/containerd/btrfs) + +Native Go bindings for btrfs. + +# Status + +These are in the early stages. We will try to maintain stability, but please +vendor if you are relying on these directly. + +# Contribute + +This package may not cover all the use cases for btrfs. If something you need +is missing, please don't hesitate to submit a PR. + +Note that due to struct alignment issues, this isn't yet fully native. +Preferably, this could be resolved, so contributions in this direction are +greatly appreciated. + +## Applying License Header to New Files + +If you submit a contribution that adds a new file, please add the license +header. You can do so manually or use the `ltag` tool: + + +```console +$ go get github.com/kunalkushwaha/ltag +$ ltag -t ./license-templates +``` + +The above will add the appropriate licenses to Go files. New templates will +need to be added if other kinds of files are added. Please consult the +documentation at https://github.com/kunalkushwaha/ltag + +## Project details + +btrfs is a containerd sub-project, licensed under the [Apache 2.0 license](./LICENSE). +As a containerd sub-project, you will find the: + * [Project governance](https://github.com/containerd/project/blob/master/GOVERNANCE.md), + * [Maintainers](https://github.com/containerd/project/blob/master/MAINTAINERS), + * and [Contributing guidelines](https://github.com/containerd/project/blob/master/CONTRIBUTING.md) + +information in our [`containerd/project`](https://github.com/containerd/project) repository. diff --git a/vendor/github.com/containerd/btrfs/btrfs.c b/vendor/github.com/containerd/btrfs/btrfs.c new file mode 100644 index 0000000000..f0da012f08 --- /dev/null +++ b/vendor/github.com/containerd/btrfs/btrfs.c @@ -0,0 +1,33 @@ +/* + Copyright The containerd Authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#include +#include +#include +#include + +#include "btrfs.h" + +void unpack_root_item(struct gosafe_btrfs_root_item* dst, struct btrfs_root_item* src) { + memcpy(dst->uuid, src->uuid, BTRFS_UUID_SIZE); + memcpy(dst->parent_uuid, src->parent_uuid, BTRFS_UUID_SIZE); + memcpy(dst->received_uuid, src->received_uuid, BTRFS_UUID_SIZE); + dst->gen = btrfs_root_generation(src); + dst->ogen = btrfs_root_otransid(src); + dst->flags = btrfs_root_flags(src); +} + +/* unpack_root_ref(struct gosafe_btrfs_root_ref* dst, struct btrfs_root_ref* src) { */ diff --git a/vendor/github.com/containerd/btrfs/btrfs.go b/vendor/github.com/containerd/btrfs/btrfs.go new file mode 100644 index 0000000000..f9c30b3dd5 --- /dev/null +++ b/vendor/github.com/containerd/btrfs/btrfs.go @@ -0,0 +1,412 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package btrfs + +/* +#include +#include +#include "btrfs.h" + +static char* get_name_btrfs_ioctl_vol_args_v2(struct btrfs_ioctl_vol_args_v2* btrfs_struct) { + return btrfs_struct->name; +} +*/ +import "C" + +import ( + "os" + "path/filepath" + "sort" + "syscall" + "unsafe" + + "github.com/pkg/errors" +) + +// maxByteSliceSize is the smallest size that Go supports on various platforms. +// On mipsle, 1<<31-1 overflows the address space. +const maxByteSliceSize = 1 << 30 + +// IsSubvolume returns nil if the path is a valid subvolume. An error is +// returned if the path does not exist or the path is not a valid subvolume. +func IsSubvolume(path string) error { + fi, err := os.Lstat(path) + if err != nil { + return err + } + + if err := isFileInfoSubvol(fi); err != nil { + return err + } + + var statfs syscall.Statfs_t + if err := syscall.Statfs(path, &statfs); err != nil { + return err + } + + return isStatfsSubvol(&statfs) +} + +// SubvolID returns the subvolume ID for the provided path +func SubvolID(path string) (uint64, error) { + fp, err := openSubvolDir(path) + if err != nil { + return 0, err + } + defer fp.Close() + + return subvolID(fp.Fd()) +} + +// SubvolInfo returns information about the subvolume at the provided path. +func SubvolInfo(path string) (info Info, err error) { + path, err = filepath.EvalSymlinks(path) + if err != nil { + return info, err + } + + fp, err := openSubvolDir(path) + if err != nil { + return info, err + } + defer fp.Close() + + id, err := subvolID(fp.Fd()) + if err != nil { + return info, err + } + + subvolsByID, err := subvolMap(path) + if err != nil { + return info, err + } + + if info, ok := subvolsByID[id]; ok { + return *info, nil + } + + return info, errors.Errorf("%q not found", path) +} + +func subvolMap(path string) (map[uint64]*Info, error) { + fp, err := openSubvolDir(path) + if err != nil { + return nil, err + } + defer fp.Close() + + var args C.struct_btrfs_ioctl_search_args + + args.key.tree_id = C.BTRFS_ROOT_TREE_OBJECTID + args.key.min_type = C.BTRFS_ROOT_ITEM_KEY + args.key.max_type = C.BTRFS_ROOT_BACKREF_KEY + args.key.min_objectid = C.BTRFS_FS_TREE_OBJECTID + args.key.max_objectid = C.BTRFS_LAST_FREE_OBJECTID + args.key.max_offset = ^C.__u64(0) + args.key.max_transid = ^C.__u64(0) + + subvolsByID := make(map[uint64]*Info) + + for { + args.key.nr_items = 4096 + if err := ioctl(fp.Fd(), C.BTRFS_IOC_TREE_SEARCH, uintptr(unsafe.Pointer(&args))); err != nil { + return nil, err + } + + if args.key.nr_items == 0 { + break + } + + var ( + sh C.struct_btrfs_ioctl_search_header + shSize = unsafe.Sizeof(sh) + buf = (*[maxByteSliceSize]byte)(unsafe.Pointer(&args.buf[0]))[:C.BTRFS_SEARCH_ARGS_BUFSIZE] + ) + + for i := 0; i < int(args.key.nr_items); i++ { + sh = (*(*C.struct_btrfs_ioctl_search_header)(unsafe.Pointer(&buf[0]))) + buf = buf[shSize:] + + info := subvolsByID[uint64(sh.objectid)] + if info == nil { + info = &Info{} + } + info.ID = uint64(sh.objectid) + + if sh._type == C.BTRFS_ROOT_BACKREF_KEY { + rr := (*(*C.struct_btrfs_root_ref)(unsafe.Pointer(&buf[0]))) + + // This branch processes the backrefs from the root object. We + // get an entry of the objectid, with name, but the parent is + // the offset. + + nname := C.btrfs_stack_root_ref_name_len(&rr) + name := string(buf[C.sizeof_struct_btrfs_root_ref : C.sizeof_struct_btrfs_root_ref+uintptr(nname)]) + + info.ID = uint64(sh.objectid) + info.ParentID = uint64(sh.offset) + info.Name = name + info.DirID = uint64(C.btrfs_stack_root_ref_dirid(&rr)) + + subvolsByID[uint64(sh.objectid)] = info + } else if sh._type == C.BTRFS_ROOT_ITEM_KEY && + (sh.objectid >= C.BTRFS_ROOT_ITEM_KEY || + sh.objectid == C.BTRFS_FS_TREE_OBJECTID) { + + var ( + ri = (*C.struct_btrfs_root_item)(unsafe.Pointer(&buf[0])) + gri C.struct_gosafe_btrfs_root_item + ) + + C.unpack_root_item(&gri, ri) + + if gri.flags&C.BTRFS_ROOT_SUBVOL_RDONLY != 0 { + info.Readonly = true + } + + // in this case, the offset is the actual offset. + info.Offset = uint64(sh.offset) + + info.UUID = uuidString(&gri.uuid) + info.ParentUUID = uuidString(&gri.parent_uuid) + info.ReceivedUUID = uuidString(&gri.received_uuid) + + info.Generation = uint64(gri.gen) + info.OriginalGeneration = uint64(gri.ogen) + + subvolsByID[uint64(sh.objectid)] = info + } + + args.key.min_objectid = sh.objectid + args.key.min_offset = sh.offset + args.key.min_type = sh._type // this is very questionable. + + buf = buf[sh.len:] + } + + args.key.min_offset++ + if args.key.min_offset == 0 { + args.key.min_type++ + } else { + continue + } + + if args.key.min_type > C.BTRFS_ROOT_BACKREF_KEY { + args.key.min_type = C.BTRFS_ROOT_ITEM_KEY + args.key.min_objectid++ + } else { + continue + } + + if args.key.min_objectid > args.key.max_objectid { + break + } + } + + mnt, err := findMountPoint(path) + if err != nil { + return nil, err + } + + for _, sv := range subvolsByID { + path := sv.Name + parentID := sv.ParentID + + for parentID != 0 { + parent, ok := subvolsByID[parentID] + if !ok { + break + } + + parentID = parent.ParentID + path = filepath.Join(parent.Name, path) + } + + sv.Path = filepath.Join(mnt, path) + } + return subvolsByID, nil +} + +// SubvolList will return the information for all subvolumes corresponding to +// the provided path. +func SubvolList(path string) ([]Info, error) { + subvolsByID, err := subvolMap(path) + if err != nil { + return nil, err + } + + subvols := make([]Info, 0, len(subvolsByID)) + for _, sv := range subvolsByID { + subvols = append(subvols, *sv) + } + + sort.Sort(infosByID(subvols)) + + return subvols, nil +} + +// SubvolCreate creates a subvolume at the provided path. +func SubvolCreate(path string) error { + dir, name := filepath.Split(path) + + fp, err := os.Open(dir) + if err != nil { + return err + } + defer fp.Close() + + var args C.struct_btrfs_ioctl_vol_args + args.fd = C.__s64(fp.Fd()) + + if len(name) > C.BTRFS_PATH_NAME_MAX { + return errors.Errorf("%q too long for subvolume", name) + } + nameptr := (*[maxByteSliceSize]byte)(unsafe.Pointer(&args.name[0]))[:C.BTRFS_PATH_NAME_MAX:C.BTRFS_PATH_NAME_MAX] + copy(nameptr[:C.BTRFS_PATH_NAME_MAX], []byte(name)) + + if err := ioctl(fp.Fd(), C.BTRFS_IOC_SUBVOL_CREATE, uintptr(unsafe.Pointer(&args))); err != nil { + return errors.Wrap(err, "btrfs subvolume create failed") + } + + return nil +} + +// SubvolSnapshot creates a snapshot in dst from src. If readonly is true, the +// snapshot will be readonly. +func SubvolSnapshot(dst, src string, readonly bool) error { + dstdir, dstname := filepath.Split(dst) + + dstfp, err := openSubvolDir(dstdir) + if err != nil { + return errors.Wrapf(err, "opening snapshot destination subvolume failed") + } + defer dstfp.Close() + + srcfp, err := openSubvolDir(src) + if err != nil { + return errors.Wrapf(err, "opening snapshot source subvolume failed") + } + defer srcfp.Close() + + // dstdir is the ioctl arg, wile srcdir gets set on the args + var args C.struct_btrfs_ioctl_vol_args_v2 + args.fd = C.__s64(srcfp.Fd()) + name := C.get_name_btrfs_ioctl_vol_args_v2(&args) + + if len(dstname) > C.BTRFS_SUBVOL_NAME_MAX { + return errors.Errorf("%q too long for subvolume", dstname) + } + + nameptr := (*[maxByteSliceSize]byte)(unsafe.Pointer(name))[:C.BTRFS_SUBVOL_NAME_MAX:C.BTRFS_SUBVOL_NAME_MAX] + copy(nameptr[:C.BTRFS_SUBVOL_NAME_MAX], []byte(dstname)) + + if readonly { + args.flags |= C.BTRFS_SUBVOL_RDONLY + } + + if err := ioctl(dstfp.Fd(), C.BTRFS_IOC_SNAP_CREATE_V2, uintptr(unsafe.Pointer(&args))); err != nil { + return errors.Wrapf(err, "snapshot create failed") + } + + return nil +} + +// SubvolDelete deletes the subvolumes under the given path. +func SubvolDelete(path string) error { + dir, name := filepath.Split(path) + fp, err := openSubvolDir(dir) + if err != nil { + return errors.Wrapf(err, "failed opening %v", path) + } + defer fp.Close() + + // remove child subvolumes + if err := filepath.Walk(path, func(p string, fi os.FileInfo, err error) error { + if err != nil { + if os.IsNotExist(err) || p == path { + return nil + } + + return errors.Wrapf(err, "failed walking subvolume %v", p) + } + + if !fi.IsDir() { + return nil // just ignore it! + } + + if p == path { + return nil + } + + if err := isFileInfoSubvol(fi); err != nil { + return nil + } + + if err := SubvolDelete(p); err != nil { + return errors.Wrapf(err, "recursive delete of %v failed", p) + } + + return filepath.SkipDir // children get walked by call above. + }); err != nil { + return err + } + + var args C.struct_btrfs_ioctl_vol_args + if len(name) > C.BTRFS_SUBVOL_NAME_MAX { + return errors.Errorf("%q too long for subvolume", name) + } + + nameptr := (*[maxByteSliceSize]byte)(unsafe.Pointer(&args.name[0]))[:C.BTRFS_SUBVOL_NAME_MAX:C.BTRFS_SUBVOL_NAME_MAX] + copy(nameptr[:C.BTRFS_SUBVOL_NAME_MAX], []byte(name)) + + if err := ioctl(fp.Fd(), C.BTRFS_IOC_SNAP_DESTROY, uintptr(unsafe.Pointer(&args))); err != nil { + return errors.Wrapf(err, "failed removing subvolume %v", path) + } + + return nil +} + +func openSubvolDir(path string) (*os.File, error) { + fp, err := os.Open(path) + if err != nil { + return nil, errors.Wrapf(err, "opening %v as subvolume failed", path) + } + + return fp, nil +} + +func isStatfsSubvol(statfs *syscall.Statfs_t) error { + if int64(statfs.Type) != int64(C.BTRFS_SUPER_MAGIC) { + return errors.Errorf("not a btrfs filesystem") + } + + return nil +} + +func isFileInfoSubvol(fi os.FileInfo) error { + if !fi.IsDir() { + errors.Errorf("must be a directory") + } + + stat := fi.Sys().(*syscall.Stat_t) + + if stat.Ino != C.BTRFS_FIRST_FREE_OBJECTID { + return errors.Errorf("incorrect inode type") + } + + return nil +} diff --git a/vendor/github.com/containerd/btrfs/btrfs.h b/vendor/github.com/containerd/btrfs/btrfs.h new file mode 100644 index 0000000000..1ec451ed1b --- /dev/null +++ b/vendor/github.com/containerd/btrfs/btrfs.h @@ -0,0 +1,37 @@ +/* + Copyright The containerd Authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#include +#include +#include +#include + +// unfortunately, we need to define "alignment safe" C structs to populate for +// packed structs that aren't handled by cgo. Fields will be added here, as +// needed. + +struct gosafe_btrfs_root_item { + u8 uuid[BTRFS_UUID_SIZE]; + u8 parent_uuid[BTRFS_UUID_SIZE]; + u8 received_uuid[BTRFS_UUID_SIZE]; + + u64 gen; + u64 ogen; + u64 flags; +}; + +void unpack_root_item(struct gosafe_btrfs_root_item* dst, struct btrfs_root_item* src); +/* void unpack_root_ref(struct gosafe_btrfs_root_ref* dst, struct btrfs_root_ref* src); */ diff --git a/vendor/github.com/containerd/btrfs/doc.go b/vendor/github.com/containerd/btrfs/doc.go new file mode 100644 index 0000000000..6aaf2d056c --- /dev/null +++ b/vendor/github.com/containerd/btrfs/doc.go @@ -0,0 +1,18 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +// Package btrfs provides bindings for working with btrfs partitions from Go. +package btrfs diff --git a/vendor/github.com/containerd/btrfs/go.mod b/vendor/github.com/containerd/btrfs/go.mod new file mode 100644 index 0000000000..36889c0be9 --- /dev/null +++ b/vendor/github.com/containerd/btrfs/go.mod @@ -0,0 +1,5 @@ +module github.com/containerd/btrfs + +go 1.15 + +require github.com/pkg/errors v0.9.1 diff --git a/vendor/github.com/containerd/btrfs/go.sum b/vendor/github.com/containerd/btrfs/go.sum new file mode 100644 index 0000000000..7c401c3f58 --- /dev/null +++ b/vendor/github.com/containerd/btrfs/go.sum @@ -0,0 +1,2 @@ +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= diff --git a/vendor/github.com/containerd/btrfs/helpers.go b/vendor/github.com/containerd/btrfs/helpers.go new file mode 100644 index 0000000000..475f1c60f6 --- /dev/null +++ b/vendor/github.com/containerd/btrfs/helpers.go @@ -0,0 +1,102 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package btrfs + +/* +#include +#include +#include +*/ +import "C" + +import ( + "bufio" + "bytes" + "fmt" + "os" + "strings" + "unsafe" + + "github.com/pkg/errors" +) + +func subvolID(fd uintptr) (uint64, error) { + var args C.struct_btrfs_ioctl_ino_lookup_args + args.objectid = C.BTRFS_FIRST_FREE_OBJECTID + + if err := ioctl(fd, C.BTRFS_IOC_INO_LOOKUP, uintptr(unsafe.Pointer(&args))); err != nil { + return 0, err + } + + return uint64(args.treeid), nil +} + +var ( + zeroArray = [16]byte{} + zeros = zeroArray[:] +) + +func uuidString(uuid *[C.BTRFS_UUID_SIZE]C.u8) string { + b := (*[maxByteSliceSize]byte)(unsafe.Pointer(uuid))[:C.BTRFS_UUID_SIZE] + + if bytes.Equal(b, zeros) { + return "" + } + + return fmt.Sprintf("%x-%x-%x-%x-%x", b[:4], b[4:4+2], b[6:6+2], b[8:8+2], b[10:16]) +} + +func findMountPoint(path string) (string, error) { + fp, err := os.Open("/proc/self/mounts") + if err != nil { + return "", err + } + defer fp.Close() + + const ( + deviceIdx = 0 + pathIdx = 1 + typeIdx = 2 + options = 3 + ) + + var ( + mount string + scanner = bufio.NewScanner(fp) + ) + + for scanner.Scan() { + fields := strings.Fields(scanner.Text()) + if fields[typeIdx] != "btrfs" { + continue // skip non-btrfs + } + + if strings.HasPrefix(path, fields[pathIdx]) { + mount = fields[pathIdx] + } + } + + if scanner.Err() != nil { + return "", scanner.Err() + } + + if mount == "" { + return "", errors.Errorf("mount point of %v not found", path) + } + + return mount, nil +} diff --git a/vendor/github.com/containerd/btrfs/info.go b/vendor/github.com/containerd/btrfs/info.go new file mode 100644 index 0000000000..0f96be6b87 --- /dev/null +++ b/vendor/github.com/containerd/btrfs/info.go @@ -0,0 +1,45 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package btrfs + +// Info describes metadata about a btrfs subvolume. +type Info struct { + ID uint64 // subvolume id + ParentID uint64 // aka ref_tree + TopLevelID uint64 // not actually clear what this is, not set for now. + Offset uint64 // key offset for root + DirID uint64 + + Generation uint64 + OriginalGeneration uint64 + + UUID string + ParentUUID string + ReceivedUUID string + + Name string + Path string // absolute path of subvolume + Root string // path of root mount point + + Readonly bool // true if the snaps hot is readonly, extracted from flags +} + +type infosByID []Info + +func (b infosByID) Len() int { return len(b) } +func (b infosByID) Less(i, j int) bool { return b[i].ID < b[j].ID } +func (b infosByID) Swap(i, j int) { b[i], b[j] = b[j], b[i] } diff --git a/vendor/github.com/containerd/btrfs/ioctl.go b/vendor/github.com/containerd/btrfs/ioctl.go new file mode 100644 index 0000000000..bac1dbdd6c --- /dev/null +++ b/vendor/github.com/containerd/btrfs/ioctl.go @@ -0,0 +1,27 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package btrfs + +import "syscall" + +func ioctl(fd, request, args uintptr) error { + _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, fd, request, args) + if errno != 0 { + return errno + } + return nil +} diff --git a/vendor/github.com/containerd/containerd/snapshots/btrfs/btrfs.go b/vendor/github.com/containerd/containerd/snapshots/btrfs/btrfs.go new file mode 100644 index 0000000000..dc274ee371 --- /dev/null +++ b/vendor/github.com/containerd/containerd/snapshots/btrfs/btrfs.go @@ -0,0 +1,416 @@ +// +build linux,!no_btrfs,cgo + +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package btrfs + +import ( + "context" + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/containerd/btrfs" + "github.com/containerd/continuity/fs" + + "github.com/containerd/containerd/log" + "github.com/containerd/containerd/mount" + "github.com/containerd/containerd/plugin" + "github.com/containerd/containerd/snapshots" + "github.com/containerd/containerd/snapshots/storage" + + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +type snapshotter struct { + device string // device of the root + root string // root provides paths for internal storage. + ms *storage.MetaStore +} + +// NewSnapshotter returns a Snapshotter using btrfs. Uses the provided +// root directory for snapshots and stores the metadata in +// a file in the provided root. +// root needs to be a mount point of btrfs. +func NewSnapshotter(root string) (snapshots.Snapshotter, error) { + // If directory does not exist, create it + if st, err := os.Stat(root); err != nil { + if !os.IsNotExist(err) { + return nil, err + } + if err := os.Mkdir(root, 0700); err != nil { + return nil, err + } + } else if st.Mode()&os.ModePerm != 0700 { + if err := os.Chmod(root, 0700); err != nil { + return nil, err + } + } + + mnt, err := mount.Lookup(root) + if err != nil { + return nil, err + } + if mnt.FSType != "btrfs" { + return nil, errors.Wrapf(plugin.ErrSkipPlugin, "path %s (%s) must be a btrfs filesystem to be used with the btrfs snapshotter", root, mnt.FSType) + } + var ( + active = filepath.Join(root, "active") + view = filepath.Join(root, "view") + snapshots = filepath.Join(root, "snapshots") + ) + + for _, path := range []string{ + active, + view, + snapshots, + } { + if err := os.Mkdir(path, 0755); err != nil && !os.IsExist(err) { + return nil, err + } + } + ms, err := storage.NewMetaStore(filepath.Join(root, "metadata.db")) + if err != nil { + return nil, err + } + + return &snapshotter{ + device: mnt.Source, + root: root, + ms: ms, + }, nil +} + +// Stat returns the info for an active or committed snapshot by name or +// key. +// +// Should be used for parent resolution, existence checks and to discern +// the kind of snapshot. +func (b *snapshotter) Stat(ctx context.Context, key string) (snapshots.Info, error) { + ctx, t, err := b.ms.TransactionContext(ctx, false) + if err != nil { + return snapshots.Info{}, err + } + defer t.Rollback() + _, info, _, err := storage.GetInfo(ctx, key) + if err != nil { + return snapshots.Info{}, err + } + + return info, nil +} + +func (b *snapshotter) Update(ctx context.Context, info snapshots.Info, fieldpaths ...string) (snapshots.Info, error) { + ctx, t, err := b.ms.TransactionContext(ctx, true) + if err != nil { + return snapshots.Info{}, err + } + + info, err = storage.UpdateInfo(ctx, info, fieldpaths...) + if err != nil { + t.Rollback() + return snapshots.Info{}, err + } + + if err := t.Commit(); err != nil { + return snapshots.Info{}, err + } + + return info, nil +} + +// Usage retrieves the disk usage of the top-level snapshot. +func (b *snapshotter) Usage(ctx context.Context, key string) (snapshots.Usage, error) { + return b.usage(ctx, key) +} + +func (b *snapshotter) usage(ctx context.Context, key string) (snapshots.Usage, error) { + ctx, t, err := b.ms.TransactionContext(ctx, false) + if err != nil { + return snapshots.Usage{}, err + } + id, info, usage, err := storage.GetInfo(ctx, key) + var parentID string + if err == nil && info.Kind == snapshots.KindActive && info.Parent != "" { + parentID, _, _, err = storage.GetInfo(ctx, info.Parent) + + } + t.Rollback() // transaction no longer needed at this point. + + if err != nil { + return snapshots.Usage{}, err + } + + if info.Kind == snapshots.KindActive { + var du fs.Usage + p := filepath.Join(b.root, "active", id) + if parentID != "" { + du, err = fs.DiffUsage(ctx, filepath.Join(b.root, "snapshots", parentID), p) + } else { + du, err = fs.DiskUsage(ctx, p) + } + if err != nil { + // TODO(stevvooe): Consider not reporting an error in this case. + return snapshots.Usage{}, err + } + + usage = snapshots.Usage(du) + } + + return usage, nil +} + +// Walk the committed snapshots. +func (b *snapshotter) Walk(ctx context.Context, fn snapshots.WalkFunc, fs ...string) error { + ctx, t, err := b.ms.TransactionContext(ctx, false) + if err != nil { + return err + } + defer t.Rollback() + return storage.WalkInfo(ctx, fn, fs...) +} + +func (b *snapshotter) Prepare(ctx context.Context, key, parent string, opts ...snapshots.Opt) ([]mount.Mount, error) { + return b.makeSnapshot(ctx, snapshots.KindActive, key, parent, opts) +} + +func (b *snapshotter) View(ctx context.Context, key, parent string, opts ...snapshots.Opt) ([]mount.Mount, error) { + return b.makeSnapshot(ctx, snapshots.KindView, key, parent, opts) +} + +func (b *snapshotter) makeSnapshot(ctx context.Context, kind snapshots.Kind, key, parent string, opts []snapshots.Opt) ([]mount.Mount, error) { + ctx, t, err := b.ms.TransactionContext(ctx, true) + if err != nil { + return nil, err + } + defer func() { + if err != nil && t != nil { + if rerr := t.Rollback(); rerr != nil { + log.G(ctx).WithError(rerr).Warn("failed to rollback transaction") + } + } + }() + + s, err := storage.CreateSnapshot(ctx, kind, key, parent, opts...) + if err != nil { + return nil, err + } + + target := filepath.Join(b.root, strings.ToLower(s.Kind.String()), s.ID) + + if len(s.ParentIDs) == 0 { + // create new subvolume + // btrfs subvolume create /dir + if err = btrfs.SubvolCreate(target); err != nil { + return nil, err + } + } else { + parentp := filepath.Join(b.root, "snapshots", s.ParentIDs[0]) + + var readonly bool + if kind == snapshots.KindView { + readonly = true + } + + // btrfs subvolume snapshot /parent /subvol + if err = btrfs.SubvolSnapshot(target, parentp, readonly); err != nil { + return nil, err + } + } + err = t.Commit() + t = nil + if err != nil { + if derr := btrfs.SubvolDelete(target); derr != nil { + log.G(ctx).WithError(derr).WithField("subvolume", target).Error("failed to delete subvolume") + } + return nil, err + } + + return b.mounts(target, s) +} + +func (b *snapshotter) mounts(dir string, s storage.Snapshot) ([]mount.Mount, error) { + var options []string + + // get the subvolume id back out for the mount + sid, err := btrfs.SubvolID(dir) + if err != nil { + return nil, err + } + + options = append(options, fmt.Sprintf("subvolid=%d", sid)) + + if s.Kind != snapshots.KindActive { + options = append(options, "ro") + } + + return []mount.Mount{ + { + Type: "btrfs", + Source: b.device, + // NOTE(stevvooe): While it would be nice to use to uuids for + // mounts, they don't work reliably if the uuids are missing. + Options: options, + }, + }, nil +} + +func (b *snapshotter) Commit(ctx context.Context, name, key string, opts ...snapshots.Opt) (err error) { + usage, err := b.usage(ctx, key) + if err != nil { + return errors.Wrap(err, "failed to compute usage") + } + + ctx, t, err := b.ms.TransactionContext(ctx, true) + if err != nil { + return err + } + defer func() { + if err != nil && t != nil { + if rerr := t.Rollback(); rerr != nil { + log.G(ctx).WithError(rerr).Warn("failed to rollback transaction") + } + } + }() + + id, err := storage.CommitActive(ctx, key, name, usage, opts...) // TODO(stevvooe): Resolve a usage value for btrfs + if err != nil { + return errors.Wrap(err, "failed to commit") + } + + source := filepath.Join(b.root, "active", id) + target := filepath.Join(b.root, "snapshots", id) + + if err := btrfs.SubvolSnapshot(target, source, true); err != nil { + return err + } + + err = t.Commit() + t = nil + if err != nil { + if derr := btrfs.SubvolDelete(target); derr != nil { + log.G(ctx).WithError(derr).WithField("subvolume", target).Error("failed to delete subvolume") + } + return err + } + + if derr := btrfs.SubvolDelete(source); derr != nil { + // Log as warning, only needed for cleanup, will not cause name collision + log.G(ctx).WithError(derr).WithField("subvolume", source).Warn("failed to delete subvolume") + } + + return nil +} + +// Mounts returns the mounts for the transaction identified by key. Can be +// called on an read-write or readonly transaction. +// +// This can be used to recover mounts after calling View or Prepare. +func (b *snapshotter) Mounts(ctx context.Context, key string) ([]mount.Mount, error) { + ctx, t, err := b.ms.TransactionContext(ctx, false) + if err != nil { + return nil, err + } + s, err := storage.GetSnapshot(ctx, key) + t.Rollback() + if err != nil { + return nil, errors.Wrap(err, "failed to get active snapshot") + } + + dir := filepath.Join(b.root, strings.ToLower(s.Kind.String()), s.ID) + return b.mounts(dir, s) +} + +// Remove abandons the transaction identified by key. All resources +// associated with the key will be removed. +func (b *snapshotter) Remove(ctx context.Context, key string) (err error) { + var ( + source, removed string + readonly bool + ) + + ctx, t, err := b.ms.TransactionContext(ctx, true) + if err != nil { + return err + } + defer func() { + if err != nil && t != nil { + if rerr := t.Rollback(); rerr != nil { + log.G(ctx).WithError(rerr).Warn("failed to rollback transaction") + } + } + + if removed != "" { + if derr := btrfs.SubvolDelete(removed); derr != nil { + log.G(ctx).WithError(derr).WithField("subvolume", removed).Warn("failed to delete subvolume") + } + } + }() + + id, k, err := storage.Remove(ctx, key) + if err != nil { + return errors.Wrap(err, "failed to remove snapshot") + } + + switch k { + case snapshots.KindView: + source = filepath.Join(b.root, "view", id) + removed = filepath.Join(b.root, "view", "rm-"+id) + readonly = true + case snapshots.KindActive: + source = filepath.Join(b.root, "active", id) + removed = filepath.Join(b.root, "active", "rm-"+id) + case snapshots.KindCommitted: + source = filepath.Join(b.root, "snapshots", id) + removed = filepath.Join(b.root, "snapshots", "rm-"+id) + readonly = true + } + + if err := btrfs.SubvolSnapshot(removed, source, readonly); err != nil { + removed = "" + return err + } + + if err := btrfs.SubvolDelete(source); err != nil { + return errors.Wrapf(err, "failed to remove snapshot %v", source) + } + + err = t.Commit() + t = nil + if err != nil { + // Attempt to restore source + if err1 := btrfs.SubvolSnapshot(source, removed, readonly); err1 != nil { + log.G(ctx).WithFields(logrus.Fields{ + logrus.ErrorKey: err1, + "subvolume": source, + "renamed": removed, + }).Error("failed to restore subvolume from renamed") + // Keep removed to allow for manual restore + removed = "" + } + return err + } + + return nil +} + +// Close closes the snapshotter +func (b *snapshotter) Close() error { + return b.ms.Close() +} diff --git a/vendor/github.com/containerd/containerd/snapshots/btrfs/plugin/plugin.go b/vendor/github.com/containerd/containerd/snapshots/btrfs/plugin/plugin.go new file mode 100644 index 0000000000..a70af58429 --- /dev/null +++ b/vendor/github.com/containerd/containerd/snapshots/btrfs/plugin/plugin.go @@ -0,0 +1,59 @@ +// +build linux,!no_btrfs,cgo + +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package plugin + +import ( + "errors" + + ocispec "github.com/opencontainers/image-spec/specs-go/v1" + + "github.com/containerd/containerd/platforms" + "github.com/containerd/containerd/plugin" + "github.com/containerd/containerd/snapshots/btrfs" +) + +// Config represents configuration for the btrfs plugin. +type Config struct { + // Root directory for the plugin + RootPath string `toml:"root_path"` +} + +func init() { + plugin.Register(&plugin.Registration{ + ID: "btrfs", + Type: plugin.SnapshotPlugin, + Config: &Config{}, + InitFn: func(ic *plugin.InitContext) (interface{}, error) { + ic.Meta.Platforms = []ocispec.Platform{platforms.DefaultSpec()} + + config, ok := ic.Config.(*Config) + if !ok { + return nil, errors.New("invalid btrfs configuration") + } + + root := ic.Root + if len(config.RootPath) != 0 { + root = config.RootPath + } + + ic.Meta.Exports = map[string]string{"root": root} + return btrfs.NewSnapshotter(root) + }, + }) +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 59337f0c6e..c382c584ef 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -174,6 +174,8 @@ github.com/cilium/ebpf/internal/unix github.com/cilium/ebpf/link # github.com/container-storage-interface/spec v1.5.0 github.com/container-storage-interface/spec/lib/go/csi +# github.com/containerd/btrfs v1.0.0 => github.com/containerd/btrfs v1.0.0 +github.com/containerd/btrfs # github.com/containerd/cgroups v1.0.1 => github.com/containerd/cgroups v1.0.1 ## explicit github.com/containerd/cgroups @@ -345,6 +347,8 @@ github.com/containerd/containerd/services/snapshots github.com/containerd/containerd/services/tasks github.com/containerd/containerd/services/version github.com/containerd/containerd/snapshots +github.com/containerd/containerd/snapshots/btrfs +github.com/containerd/containerd/snapshots/btrfs/plugin github.com/containerd/containerd/snapshots/native github.com/containerd/containerd/snapshots/native/plugin github.com/containerd/containerd/snapshots/overlay