From 16e18c5f172f62146de01b89f8b750aa8fe1094c Mon Sep 17 00:00:00 2001 From: Akihiro Suda Date: Thu, 1 Jul 2021 15:00:18 +0900 Subject: [PATCH 01/38] Remove all the files In the next batch of commits, the content of the repo will be replaced with the content of github.com/dennwc/btrfs Signed-off-by: Akihiro Suda --- .github/workflows/ci.yml | 43 ---- .gitignore | 28 --- LICENSE | 201 ------------------- Makefile | 34 ---- README.md | 46 ----- btrfs.c | 33 ---- btrfs.go | 412 --------------------------------------- btrfs.h | 37 ---- cmd/btrfs-test/main.go | 81 -------- doc.go | 18 -- go.mod | 5 - go.sum | 2 - helpers.go | 102 ---------- info.go | 45 ----- ioctl.go | 27 --- 15 files changed, 1114 deletions(-) delete mode 100644 .github/workflows/ci.yml delete mode 100644 .gitignore delete mode 100644 LICENSE delete mode 100644 Makefile delete mode 100644 README.md delete mode 100644 btrfs.c delete mode 100644 btrfs.go delete mode 100644 btrfs.h delete mode 100644 cmd/btrfs-test/main.go delete mode 100644 doc.go delete mode 100644 go.mod delete mode 100644 go.sum delete mode 100644 helpers.go delete mode 100644 info.go delete mode 100644 ioctl.go diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index b472cf2..0000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,43 +0,0 @@ -name: CI - -on: - push: - branches: [ main ] - pull_request: - branches: [ main ] - -jobs: - - build: - name: Btrfs CI - runs-on: ubuntu-18.04 - timeout-minutes: 5 - steps: - - - name: Set up Go - uses: actions/setup-go@v2 - with: - go-version: 1.16.x - id: go - - - name: Setup environment - shell: bash - run: | - echo "GOPATH=${{ github.workspace }}" >> $GITHUB_ENV - echo "${{ github.workspace }}/bin" >> $GITHUB_PATH - - - name: Check out code - uses: actions/checkout@v2 - with: - path: src/github.com/containerd/btrfs - fetch-depth: 25 - - - name: Project checks - uses: containerd/project-checks@v1 - with: - working-directory: src/github.com/containerd/btrfs - - - name: Build - working-directory: src/github.com/containerd/btrfs - run: | - make vet binaries diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 9b781b5..0000000 --- a/.gitignore +++ /dev/null @@ -1,28 +0,0 @@ -# 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/LICENSE b/LICENSE deleted file mode 100644 index 8dada3e..0000000 --- a/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright {yyyy} {name of copyright owner} - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/Makefile b/Makefile deleted file mode 100644 index e89dd46..0000000 --- a/Makefile +++ /dev/null @@ -1,34 +0,0 @@ -# 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/README.md b/README.md deleted file mode 100644 index 505f39b..0000000 --- a/README.md +++ /dev/null @@ -1,46 +0,0 @@ -# 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/btrfs.c b/btrfs.c deleted file mode 100644 index f0da012..0000000 --- a/btrfs.c +++ /dev/null @@ -1,33 +0,0 @@ -/* - 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/btrfs.go b/btrfs.go deleted file mode 100644 index f9c30b3..0000000 --- a/btrfs.go +++ /dev/null @@ -1,412 +0,0 @@ -/* - 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/btrfs.h b/btrfs.h deleted file mode 100644 index 1ec451e..0000000 --- a/btrfs.h +++ /dev/null @@ -1,37 +0,0 @@ -/* - 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/cmd/btrfs-test/main.go b/cmd/btrfs-test/main.go deleted file mode 100644 index cc56eb0..0000000 --- a/cmd/btrfs-test/main.go +++ /dev/null @@ -1,81 +0,0 @@ -/* - 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 main - -import ( - "flag" - "fmt" - "log" - "os" - "text/tabwriter" - - "github.com/containerd/btrfs" -) - -var ( - readonly bool -) - -func init() { - flag.BoolVar(&readonly, "readonly", false, "readonly snapshot") -} - -func main() { - flag.Parse() - - switch os.Args[1] { - case "create": - if err := btrfs.SubvolCreate(os.Args[2]); err != nil { - log.Fatalln(err) - } - case "snapshot": - if err := btrfs.SubvolSnapshot(os.Args[3], os.Args[2], readonly); err != nil { - log.Fatalln(err) - } - case "delete": - if err := btrfs.SubvolDelete(os.Args[2]); err != nil { - log.Fatalln(err) - } - case "list": - infos, err := btrfs.SubvolList(os.Args[2]) - if err != nil { - log.Fatalln(err) - } - tw := tabwriter.NewWriter(os.Stdout, 0, 8, 4, '\t', 0) - - fmt.Fprintf(tw, "ID\tParent\tTopLevel\tGen\tOGen\tUUID\tParentUUID\tPath\n") - - for _, subvol := range infos { - fmt.Fprintf(tw, "%d\t%d\t%d\t%d\t%d\t%s\t%s\t%s\n", - subvol.ID, subvol.ParentID, subvol.TopLevelID, - subvol.Generation, subvol.OriginalGeneration, subvol.UUID, subvol.ParentUUID, - subvol.Path) - - } - - tw.Flush() - case "show": - info, err := btrfs.SubvolInfo(os.Args[2]) - if err != nil { - log.Fatalln(err) - } - - fmt.Printf("%#v\n", info) - default: - log.Fatal("unknown command", os.Args[1]) - } -} diff --git a/doc.go b/doc.go deleted file mode 100644 index 6aaf2d0..0000000 --- a/doc.go +++ /dev/null @@ -1,18 +0,0 @@ -/* - 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/go.mod b/go.mod deleted file mode 100644 index 36889c0..0000000 --- a/go.mod +++ /dev/null @@ -1,5 +0,0 @@ -module github.com/containerd/btrfs - -go 1.15 - -require github.com/pkg/errors v0.9.1 diff --git a/go.sum b/go.sum deleted file mode 100644 index 7c401c3..0000000 --- a/go.sum +++ /dev/null @@ -1,2 +0,0 @@ -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= diff --git a/helpers.go b/helpers.go deleted file mode 100644 index 475f1c6..0000000 --- a/helpers.go +++ /dev/null @@ -1,102 +0,0 @@ -/* - 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/info.go b/info.go deleted file mode 100644 index 0f96be6..0000000 --- a/info.go +++ /dev/null @@ -1,45 +0,0 @@ -/* - 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/ioctl.go b/ioctl.go deleted file mode 100644 index bac1dbd..0000000 --- a/ioctl.go +++ /dev/null @@ -1,27 +0,0 @@ -/* - 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 -} From 81237e838ec053d372ac0984f8e341f526ef4d8c Mon Sep 17 00:00:00 2001 From: Denys Smirnov Date: Wed, 14 Sep 2016 20:29:46 +0300 Subject: [PATCH 02/38] Initial version. WIP. (cherry picked from commit dennwc/btrfs@2d0abe4795989e548a54a8ac08a42d21c8e3c73e) Signed-off-by: Akihiro Suda --- btrfs.go | 141 +++++++++ btrfs_h.go | 56 ++++ btrfs_tree_h.go | 23 ++ cmd/hgen.go | 125 ++++++++ errors.go | 44 +++ ioctl/ioctl.go | 86 ++++++ ioctl/ioctl_test.go | 21 ++ ioctl_h.go | 727 ++++++++++++++++++++++++++++++++++++++++++++ ioctl_h_test.go | 58 ++++ subvolume.go | 21 ++ 10 files changed, 1302 insertions(+) create mode 100644 btrfs.go create mode 100644 btrfs_h.go create mode 100644 btrfs_tree_h.go create mode 100644 cmd/hgen.go create mode 100644 errors.go create mode 100644 ioctl/ioctl.go create mode 100644 ioctl/ioctl_test.go create mode 100644 ioctl_h.go create mode 100644 ioctl_h_test.go create mode 100644 subvolume.go diff --git a/btrfs.go b/btrfs.go new file mode 100644 index 0000000..5d8e054 --- /dev/null +++ b/btrfs.go @@ -0,0 +1,141 @@ +package btrfs + +import ( + "github.com/dennwc/btrfs/ioctl" + "os" +) + +//go:generate go run cmd/hgen.go -p btrfs -o btrfs_tree_hc.go btrfs_tree.h +//go:generate go fmt -w btrfs_tree_hc.go + +const SuperMagic = 0x9123683E + +func Open(path string) (*FS, error) { + if ok, err := IsSubVolume(path); err != nil { + return nil, err + } else if !ok { + return nil, ErrNotBtrfs{Path: path} + } + dir, err := os.Open(path) + if err != nil { + return nil, err + } + return &FS{f: dir}, nil +} + +type FS struct { + f *os.File +} + +func (f *FS) Close() error { + return f.f.Close() +} + +type Info struct { + MaxID uint64 + NumDevices uint64 + FSID FSID + NodeSize uint32 + SectorSize uint32 + CloneAlignment uint32 +} + +func (f *FS) Info() (out Info, err error) { + var arg btrfs_ioctl_fs_info_args + if err = ioctl.Do(f.f, _BTRFS_IOC_FS_INFO, &arg); err != nil { + return + } + out = Info{ + MaxID: arg.max_id, + NumDevices: arg.num_devices, + FSID: arg.fsid, + NodeSize: arg.nodesize, + SectorSize: arg.sectorsize, + CloneAlignment: arg.clone_alignment, + } + return +} + +type DevStats struct { + WriteErrs uint64 + ReadErrs uint64 + FlushErrs uint64 + // Checksum error, bytenr error or contents is illegal: this is an + // indication that the block was damaged during read or write, or written to + // wrong location or read from wrong location. + CorruptionErrs uint64 + // An indication that blocks have not been written. + GenerationErrs uint64 + Unknown []uint64 +} + +func (f *FS) GetDevStats(id uint64) (out DevStats, err error) { + var arg btrfs_ioctl_get_dev_stats + arg.devid = id + //arg.nr_items = _BTRFS_DEV_STAT_VALUES_MAX + arg.flags = 0 + if err = ioctl.Do(f.f, _BTRFS_IOC_GET_DEV_STATS, &arg); err != nil { + return + } + i := 0 + out.WriteErrs = arg.values[i] + i++ + out.ReadErrs = arg.values[i] + i++ + out.FlushErrs = arg.values[i] + i++ + out.CorruptionErrs = arg.values[i] + i++ + out.GenerationErrs = arg.values[i] + i++ + if int(arg.nr_items) > i { + out.Unknown = arg.values[i:arg.nr_items] + } + return +} + +type FSFeatureFlags struct { + Compatible FeatureFlags + CompatibleRO FeatureFlags + Incompatible IncompatFeatures +} + +func (f *FS) GetFeatures() (out FSFeatureFlags, err error) { + var arg btrfs_ioctl_feature_flags + if err = ioctl.Do(f.f, _BTRFS_IOC_GET_FEATURES, &arg); err != nil { + return + } + out = FSFeatureFlags{ + Compatible: arg.compat_flags, + CompatibleRO: arg.compat_ro_flags, + Incompatible: arg.incompat_flags, + } + return +} + +func (f *FS) GetSupportedFeatures() (out FSFeatureFlags, err error) { + var arg [3]btrfs_ioctl_feature_flags + if err = ioctl.Do(f.f, _BTRFS_IOC_GET_SUPPORTED_FEATURES, &arg); err != nil { + return + } + out = FSFeatureFlags{ + Compatible: arg[0].compat_flags, + CompatibleRO: arg[0].compat_ro_flags, + Incompatible: arg[0].incompat_flags, + } + //for i, a := range arg { + // out[i] = FSFeatureFlags{ + // Compatible: a.compat_flags, + // CompatibleRO: a.compat_ro_flags, + // Incompatible: a.incompat_flags, + // } + //} + return +} + +func (f *FS) Sync() (err error) { + if err = ioctl.Do(f.f, _BTRFS_IOC_START_SYNC, nil); err != nil { + return + } + return ioctl.Do(f.f, _BTRFS_IOC_WAIT_SYNC, nil) +} diff --git a/btrfs_h.go b/btrfs_h.go new file mode 100644 index 0000000..2206361 --- /dev/null +++ b/btrfs_h.go @@ -0,0 +1,56 @@ +package btrfs + +import "strings" + +const BTRFS_LABEL_SIZE = 256 + +type FeatureFlags uint64 + +const ( + FeatureCompatROFreeSpaceTree = FeatureFlags(1 << 0) +) + +type IncompatFeatures uint64 + +func (f IncompatFeatures) String() string { + var s []string + for i, name := range incompatFeatureNames { + if uint64(f)&uint64(i) != 0 { + s = append(s, name) + } + } + return strings.Join(s, ",") +} + +var incompatFeatureNames = []string{ + "DefaultSubvol", + "MixedGroups", + "CompressLZO", + "CompressLZOv2", + "BigMetadata", + "ExtendedIRef", + "RAID56", + "SkinnyMetadata", + "NoHoles", +} + +const ( + FeatureIncompatMixedBackRef = IncompatFeatures(1 << 0) + FeatureIncompatDefaultSubvol = IncompatFeatures(1 << 1) + FeatureIncompatMixedGroups = IncompatFeatures(1 << 2) + FeatureIncompatCompressLZO = IncompatFeatures(1 << 3) + + // Some patches floated around with a second compression method + // lets save that incompat here for when they do get in. + // Note we don't actually support it, we're just reserving the number. + FeatureIncompatCompressLZOv2 = IncompatFeatures(1 << 4) + + // Older kernels tried to do bigger metadata blocks, but the + // code was pretty buggy. Lets not let them try anymore. + FeatureIncompatBigMetadata = IncompatFeatures(1 << 5) + + FeatureIncompatExtendedIRef = IncompatFeatures(1 << 6) + FeatureIncompatRAID56 = IncompatFeatures(1 << 7) + FeatureIncompatSkinnyMetadata = IncompatFeatures(1 << 8) + FeatureIncompatNoHoles = IncompatFeatures(1 << 9) +) diff --git a/btrfs_tree_h.go b/btrfs_tree_h.go new file mode 100644 index 0000000..e35273d --- /dev/null +++ b/btrfs_tree_h.go @@ -0,0 +1,23 @@ +package btrfs + +// This header contains the structure definitions and constants used +// by file system objects that can be retrieved using +// the BTRFS_IOC_SEARCH_TREE ioctl. That means basically anything that +// is needed to describe a leaf node's key or item contents. + +const ( + // Holds pointers to all of the tree roots + BTRFS_ROOT_TREE_OBJECTID = 1 + + // Stores information about which extents are in use, and reference counts + BTRFS_EXTENT_TREE_OBJECTID = 2 + + // Chunk tree stores translations from logical -> physical block numbering + // the super block points to the chunk tree. + BTRFS_CHUNK_TREE_OBJECTID = 3 + + // All files have objectids in this range. + BTRFS_FIRST_FREE_OBJECTID = 256 + BTRFS_LAST_FREE_OBJECTID = 0xffffff00 // -256 + BTRFS_FIRST_CHUNK_TREE_OBJECTID = 256 +) diff --git a/cmd/hgen.go b/cmd/hgen.go new file mode 100644 index 0000000..33bbc9a --- /dev/null +++ b/cmd/hgen.go @@ -0,0 +1,125 @@ +package main + +import ( + "bufio" + "bytes" + "flag" + "fmt" + "io" + "log" + "os" + "regexp" + "strings" + "unicode" +) + +var ( + f_pkg = flag.String("p", "main", "package name for generated file") + f_out = flag.String("o", "-", "output file") +) + +var ( + reDefineIntConst = regexp.MustCompile(`#define\s+([A-Za-z_]+)\s+(\(?\d+(?:U?LL)?(?:\s*<<\s*\d+)?\)?)`) +) + +func constName(s string) string { + return s +} + +func process(w io.Writer, path string) error { + file, err := os.Open(path) + if err != nil { + return err + } + defer file.Close() + r := bufio.NewReader(file) + + var ( + comment = false + firstComment = true + firstLineInComment = false + ) + + nl := true + defer fmt.Fprintln(w, ")") + for { + line, err := r.ReadBytes('\n') + if err == io.EOF { + return nil + } else if err != nil { + return err + } + line = bytes.TrimSpace(line) + if len(line) == 0 { + if !nl { + nl = true + w.Write([]byte("\n")) + } + continue + } + nl = false + + if bytes.HasPrefix(line, []byte("/*")) { + comment = true + firstLineInComment = true + line = bytes.TrimPrefix(line, []byte("/*")) + } + if comment { + ends := bytes.HasSuffix(line, []byte("*/")) + if ends { + comment = false + line = bytes.TrimSuffix(line, []byte("*/")) + } + line = bytes.TrimLeft(line, " \t*") + if len(line) > 0 { + if !firstComment { + w.Write([]byte("\t")) + } + w.Write([]byte("// ")) + if firstLineInComment { + line[0] = byte(unicode.ToUpper(rune(line[0]))) + } + line = bytes.Replace(line, []byte(" "), []byte(" "), -1) + w.Write(line) + w.Write([]byte("\n")) + firstLineInComment = false + } + if ends && firstComment { + firstComment = false + fmt.Fprint(w, "\nconst (\n") + nl = true + } + firstLineInComment = firstLineInComment && !ends + continue + } + if bytes.HasPrefix(line, []byte("#define")) { + sub := reDefineIntConst.FindStringSubmatch(string(line)) + if len(sub) > 0 { + name, val := sub[1], sub[2] + val = strings.Replace(val, "ULL", "", -1) + fmt.Fprintf(w, "\t%s = %s\n", constName(name), val) + continue + } + } + } +} + +func main() { + flag.Parse() + var w io.Writer = os.Stdout + if path := *f_out; path != "" && path != "-" { + file, err := os.Create(path) + if err != nil { + log.Fatal(err) + } + defer file.Close() + w = file + } + + fmt.Fprintf(w, "package %s\n", *f_pkg) + for _, path := range flag.Args() { + if err := process(w, path); err != nil { + log.Fatal(err) + } + } +} diff --git a/errors.go b/errors.go new file mode 100644 index 0000000..5259ae7 --- /dev/null +++ b/errors.go @@ -0,0 +1,44 @@ +package btrfs + +import "fmt" + +type ErrNotBtrfs struct { + Path string +} + +func (e ErrNotBtrfs) Error() string { + return fmt.Sprintf("path %q is not a btrfs", e.Path) +} + +// Error codes as returned by the kernel +type ErrCode int + +func (e ErrCode) Error() string { + s, ok := errorString[e] + if ok { + return s + } + return fmt.Sprintf("error %d", int(e)) +} + +const ( + ErrDevRAID1MinNotMet = ErrCode(iota + 1) + ErrDevRAID10MinNotMet + ErrDevRAID5MinNotMet + ErrDevRAID6MinNotMet + ErrDevTargetReplace + ErrDevMissingNotFound + ErrDevOnlyWritable + ErrDevExclRunInProgress +) + +var errorString = map[ErrCode]string{ + ErrDevRAID1MinNotMet: "unable to go below two devices on raid1", + ErrDevRAID10MinNotMet: "unable to go below four devices on raid10", + ErrDevRAID5MinNotMet: "unable to go below two devices on raid5", + ErrDevRAID6MinNotMet: "unable to go below three devices on raid6", + ErrDevTargetReplace: "unable to remove the dev_replace target dev", + ErrDevMissingNotFound: "no missing devices found to remove", + ErrDevOnlyWritable: "unable to remove the only writeable device", + ErrDevExclRunInProgress: "add/delete/balance/replace/resize operation in progress", +} diff --git a/ioctl/ioctl.go b/ioctl/ioctl.go new file mode 100644 index 0000000..d2061a0 --- /dev/null +++ b/ioctl/ioctl.go @@ -0,0 +1,86 @@ +package ioctl + +import ( + "fmt" + "os" + "reflect" + "syscall" +) + +const ( + nrBits = 8 + typeBits = 8 + sizeBits = 14 + dirBits = 2 +) + +const ( + nrMask = ((1 << nrBits) - 1) + typeMask = ((1 << typeBits) - 1) + sizeMask = ((1 << sizeBits) - 1) + dirMask = ((1 << dirBits) - 1) +) + +const ( + nrShift = 0 + typeShift = (nrShift + nrBits) + sizeShift = (typeShift + typeBits) + dirShift = (sizeShift + sizeBits) +) + +const ( + None = 0 + Write = 1 + Read = 2 +) + +func IOC(dir, typ, nr, size uintptr) uintptr { + return (dir << dirShift) | + (typ << typeShift) | + (nr << nrShift) | + (size << sizeShift) +} + +func IO(typ, nr uintptr) uintptr { + return IOC(None, typ, nr, 0) +} + +func IOR(typ, nr, size uintptr) uintptr { + return IOC(Read, typ, nr, size) +} + +func IOW(typ, nr, size uintptr) uintptr { + return IOC(Write, typ, nr, size) +} + +func IOWR(typ, nr, size uintptr) uintptr { + return IOC(Read|Write, typ, nr, size) +} + +func Ioctl(f *os.File, ioc uintptr, addr uintptr) error { + _, _, e := syscall.Syscall(syscall.SYS_IOCTL, f.Fd(), ioc, addr) + if e != 0 { + return e + } + return nil +} + +func SizeOf(arg interface{}) uintptr { + return reflect.TypeOf(arg).Size() +} + +func Do(f *os.File, ioc uintptr, arg interface{}) error { + var addr uintptr + if arg != nil { + v := reflect.ValueOf(arg) + switch v.Kind() { + case reflect.Ptr: + addr = v.Elem().UnsafeAddr() + case reflect.Slice: + addr = v.Index(0).UnsafeAddr() + default: + return fmt.Errorf("expected ptr or slice, got %T") + } + } + return Ioctl(f, ioc, addr) +} diff --git a/ioctl/ioctl_test.go b/ioctl/ioctl_test.go new file mode 100644 index 0000000..565fc83 --- /dev/null +++ b/ioctl/ioctl_test.go @@ -0,0 +1,21 @@ +package ioctl + +import ( + "testing" +) + +var casesIOC = []struct { + got uintptr + expect uintptr +}{ + {got: IOC(1, 2, 3, 4), expect: 0x40040203}, +} + +func TestIOC(t *testing.T) { + for i, c := range casesIOC { + if c.got != c.expect { + t.Errorf("unexpected ioc (case %d): %x(%b) vs %x(%b)", + i+1, c.got, c.got, c.expect, c.expect) + } + } +} diff --git a/ioctl_h.go b/ioctl_h.go new file mode 100644 index 0000000..7613fa9 --- /dev/null +++ b/ioctl_h.go @@ -0,0 +1,727 @@ +package btrfs + +import ( + "encoding/binary" + "encoding/hex" + "github.com/dennwc/btrfs/ioctl" + "os" + "unsafe" +) + +var order = binary.LittleEndian + +const ioctlMagic = 0x94 + +const devicePathNameMax = 1024 + +const ( + FSIDSize = 16 + UUIDSize = 16 +) + +type UUID [UUIDSize]byte + +func (id UUID) String() string { return hex.EncodeToString(id[:]) } + +type FSID [FSIDSize]byte + +func (id FSID) String() string { return hex.EncodeToString(id[:]) } + +const volNameMax = 4087 + +// this should be 4k +type btrfs_ioctl_vol_args struct { + fd int64 + name [volNameMax + 1]byte +} + +type btrfs_qgroup_limit struct { + flags uint64 + max_referenced uint64 + max_exclusive uint64 + rsv_referenced uint64 + rsv_exclusive uint64 +} + +type btrfs_qgroup_inherit struct { + flags uint64 + num_qgroups uint64 + num_ref_copies uint64 + num_excl_copies uint64 + lim btrfs_qgroup_limit + //qgroups [0]uint64 +} + +type btrfs_ioctl_qgroup_limit_args struct { + qgroupid uint64 + lim btrfs_qgroup_limit +} + +type btrfs_ioctl_vol_args_v2_u1 struct { + size uint64 + qgroup_inherit *btrfs_qgroup_inherit +} + +const subvolNameMax = 4039 + +type btrfs_ioctl_vol_args_v2 struct { + fd int64 + transid uint64 + flags uint64 + btrfs_ioctl_vol_args_v2_u1 + unused [2]uint64 + name [subvolNameMax + 1]byte +} + +// structure to report errors and progress to userspace, either as a +// result of a finished scrub, a canceled scrub or a progress inquiry +type btrfs_scrub_progress struct { + data_extents_scrubbed uint64 // # of data extents scrubbed + tree_extents_scrubbed uint64 // # of tree extents scrubbed + data_bytes_scrubbed uint64 // # of data bytes scrubbed + tree_bytes_scrubbed uint64 // # of tree bytes scrubbed + read_errors uint64 // # of read errors encountered (EIO) + csum_errors uint64 // # of failed csum checks + // # of occurences, where the metadata of a tree block did not match the expected values, like generation or logical + verify_errors uint64 + // # of 4k data block for which no csum is present, probably the result of data written with nodatasum + no_csum uint64 + csum_discards uint64 // # of csum for which no data was found in the extent tree. + super_errors uint64 // # of bad super blocks encountered + malloc_errors uint64 // # of internal kmalloc errors. These will likely cause an incomplete scrub + uncorrectable_errors uint64 // # of errors where either no intact copy was found or the writeback failed + corrected_errors uint64 // # of errors corrected + // last physical address scrubbed. In case a scrub was aborted, this can be used to restart the scrub + last_physical uint64 + // # of occurences where a read for a full (64k) bio failed, but the re- + // check succeeded for each 4k piece. Intermittent error. + unverified_errors uint64 +} + +type btrfs_ioctl_scrub_args struct { + devid uint64 // in + start uint64 // in + end uint64 // in + flags uint64 // in + progress btrfs_scrub_progress // out + // pad to 1k + unused [1024 - 4*8 - unsafe.Sizeof(btrfs_scrub_progress{})]byte +} + +type contReadingFromSrcdevMode uint64 + +const ( + _BTRFS_IOCTL_DEV_REPLACE_CONT_READING_FROM_SRCDEV_MODE_ALWAYS contReadingFromSrcdevMode = 0 + _BTRFS_IOCTL_DEV_REPLACE_CONT_READING_FROM_SRCDEV_MODE_AVOID contReadingFromSrcdevMode = 1 +) + +type btrfs_ioctl_dev_replace_start_params struct { + srcdevid uint64 // in, if 0, use srcdev_name instead + cont_reading_from_srcdev_mode contReadingFromSrcdevMode // in + srcdev_name [devicePathNameMax + 1]byte // in + tgtdev_name [devicePathNameMax + 1]byte // in +} + +type devReplaceState uint64 + +const ( + _BTRFS_IOCTL_DEV_REPLACE_STATE_NEVER_STARTED devReplaceState = 0 + _BTRFS_IOCTL_DEV_REPLACE_STATE_STARTED devReplaceState = 1 + _BTRFS_IOCTL_DEV_REPLACE_STATE_FINISHED devReplaceState = 2 + _BTRFS_IOCTL_DEV_REPLACE_STATE_CANCELED devReplaceState = 3 + _BTRFS_IOCTL_DEV_REPLACE_STATE_SUSPENDED devReplaceState = 4 +) + +type btrfs_ioctl_dev_replace_status_params struct { + replace_state devReplaceState // out + progress_1000 uint64 // out, 0 <= x <= 1000 + time_started uint64 // out, seconds since 1-Jan-1970 + time_stopped uint64 // out, seconds since 1-Jan-1970 + num_write_errors uint64 // out + num_uncorrectable_read_errors uint64 // out +} + +type btrfs_ioctl_dev_replace_args_u1 struct { + cmd uint64 // in + result uint64 // out + start btrfs_ioctl_dev_replace_start_params // in + spare [64]uint64 +} + +type btrfs_ioctl_dev_replace_args_u2 struct { + cmd uint64 // in + result uint64 // out + status btrfs_ioctl_dev_replace_status_params // out + unused [unsafe.Sizeof(btrfs_ioctl_dev_replace_start_params{}) - unsafe.Sizeof(btrfs_ioctl_dev_replace_status_params{})]byte + spare [64]uint64 +} + +type btrfs_ioctl_dev_info_args struct { + devid uint64 // in/out + uuid UUID // in/out + bytes_used uint64 // out + total_bytes uint64 // out + unused [379]uint64 // pad to 4k + path [devicePathNameMax]byte // out +} + +type btrfs_ioctl_fs_info_args struct { + max_id uint64 // out + num_devices uint64 // out + fsid FSID // out + nodesize uint32 // out + sectorsize uint32 // out + clone_alignment uint32 // out + reserved [122*8 + 4]byte // pad to 1k +} + +type btrfs_ioctl_feature_flags struct { + compat_flags FeatureFlags + compat_ro_flags FeatureFlags + incompat_flags IncompatFeatures +} + +type argRange [8]byte + +func (u argRange) asN() uint64 { + return order.Uint64(u[:]) +} +func (u argRange) asMinMax() (min, max uint32) { + return order.Uint32(u[:4]), order.Uint32(u[4:]) +} + +// balance control ioctl modes +//#define BTRFS_BALANCE_CTL_PAUSE 1 +//#define BTRFS_BALANCE_CTL_CANCEL 2 +//#define BTRFS_BALANCE_CTL_RESUME 3 + +// this is packed, because it should be exactly the same as its disk +// byte order counterpart (struct btrfs_disk_balance_args) +type btrfs_balance_args struct { + profiles uint64 + // usage filter + // BTRFS_BALANCE_ARGS_USAGE with a single value means '0..N' + // BTRFS_BALANCE_ARGS_USAGE_RANGE - range syntax, min..max + usage argRange + devid uint64 + pstart uint64 + pend uint64 + vstart uint64 + vend uint64 + target uint64 + flags uint64 + // BTRFS_BALANCE_ARGS_LIMIT with value 'limit' (limit number of processed chunks) + // BTRFS_BALANCE_ARGS_LIMIT_RANGE - the extend version can use minimum and maximum + limit argRange + stripes_min uint32 + stripes_max uint32 + unused [48]byte +} + +// report balance progress to userspace +type btrfs_balance_progress struct { + expected uint64 // estimated # of chunks that will be relocated to fulfill the request + considered uint64 // # of chunks we have considered so far + completed uint64 // # of chunks relocated so far +} + +type balanceState uint64 + +const ( + _BTRFS_BALANCE_STATE_RUNNING balanceState = (1 << 0) + _BTRFS_BALANCE_STATE_PAUSE_REQ balanceState = (1 << 1) + _BTRFS_BALANCE_STATE_CANCEL_REQ balanceState = (1 << 2) +) + +type btrfs_ioctl_balance_args struct { + flags uint64 // in/out + state balanceState // out + data btrfs_balance_args // in/out + meta btrfs_balance_args // in/out + sys btrfs_balance_args // in/out + stat btrfs_balance_progress // out + unused [72 * 8]byte // pad to 1k +} + +const _BTRFS_INO_LOOKUP_PATH_MAX = 4080 + +type btrfs_ioctl_ino_lookup_args struct { + treeid uint64 + objectid uint64 + name [_BTRFS_INO_LOOKUP_PATH_MAX]byte +} + +type btrfs_ioctl_search_key struct { + tree_id uint64 // which root are we searching. 0 is the tree of tree roots + // keys returned will be >= min and <= max + min_objectid uint64 + max_objectid uint64 + // keys returned will be >= min and <= max + min_offset uint64 + max_offset uint64 + // max and min transids to search for + min_transid uint64 + max_transid uint64 + // keys returned will be >= min and <= max + min_type uint32 + max_type uint32 + // how many items did userland ask for, and how many are we returning + nr_items uint32 + unused [4 + 4*8]byte +} + +type btrfs_ioctl_search_header struct { + transid uint64 + objectid uint64 + offset uint64 + typ uint32 + len uint32 +} + +const _BTRFS_SEARCH_ARGS_BUFSIZE = (4096 - unsafe.Sizeof(btrfs_ioctl_search_key{})) + +// the buf is an array of search headers where +// each header is followed by the actual item +// the type field is expanded to 32 bits for alignment +type btrfs_ioctl_search_args struct { + key btrfs_ioctl_search_key + buf [_BTRFS_SEARCH_ARGS_BUFSIZE]byte +} + +// Extended version of TREE_SEARCH ioctl that can return more than 4k of bytes. +// The allocated size of the buffer is set in buf_size. +type btrfs_ioctl_search_args_v2 struct { + key btrfs_ioctl_search_key // in/out - search parameters + buf_size uint64 // in - size of buffer; out - on EOVERFLOW: needed size to store item + //buf [0]uint64 // out - found items +} + +// With a @src_length of zero, the range from @src_offset->EOF is cloned! +type btrfs_ioctl_clone_range_args struct { + src_fd int64 + src_offset uint64 + src_length uint64 + dest_offset uint64 +} + +// flags for the defrag range ioctl +type defragRange uint64 + +const ( + _BTRFS_DEFRAG_RANGE_COMPRESS defragRange = 1 + _BTRFS_DEFRAG_RANGE_START_IO defragRange = 2 +) + +const _BTRFS_SAME_DATA_DIFFERS = 1 + +// For extent-same ioctl +type btrfs_ioctl_same_extent_info struct { + fd int64 // in - destination file + logical_offset uint64 // in - start of extent in destination + bytes_deduped uint64 // out - total # of bytes we were able to dedupe from this file + // out; status of this dedupe operation: + // 0 if dedup succeeds + // < 0 for error + // == BTRFS_SAME_DATA_DIFFERS if data differs + status int32 + reserved uint32 +} + +type btrfs_ioctl_same_args struct { + logical_offset uint64 // in - start of extent in source + length uint64 // in - length of extent + dest_count uint16 // in - total elements in info array + reserved [6]byte + //info [0]btrfs_ioctl_same_extent_info +} + +type btrfs_ioctl_defrag_range_args struct { + start uint64 // start of the defrag operation + len uint64 // number of bytes to defrag, use (u64)-1 to say all + // flags for the operation, which can include turning + // on compression for this one defrag + flags uint64 + // any extent bigger than this will be considered + // already defragged. Use 0 to take the kernel default + // Use 1 to say every single extent must be rewritten + extent_thresh uint32 + // which compression method to use if turning on compression + // for this defrag operation. If unspecified, zlib will be used + compress_type uint32 + unused [16]byte // spare for later +} + +type btrfs_ioctl_space_info struct { + flags uint64 + total_bytes uint64 + used_bytes uint64 +} + +type btrfs_ioctl_space_args struct { + space_slots uint64 + total_spaces uint64 + //spaces [0]btrfs_ioctl_space_info +} + +type btrfs_data_container struct { + bytes_left uint32 // out -- bytes not needed to deliver output + bytes_missing uint32 // out -- additional bytes needed for result + elem_cnt uint32 // out + elem_missed uint32 // out + //val [0]uint64 +} + +type btrfs_ioctl_ino_path_args struct { + inum uint64 // in + size uint64 // in + reserved [32]byte + // struct btrfs_data_container *fspath; out + fspath uint64 // out +} + +type btrfs_ioctl_logical_ino_args struct { + logical uint64 // in + size uint64 // in + reserved [32]byte + // struct btrfs_data_container *inodes; out + inodes uint64 +} + +// disk I/O failure stats +const ( + _BTRFS_DEV_STAT_WRITE_ERRS = iota // EIO or EREMOTEIO from lower layers + _BTRFS_DEV_STAT_READ_ERRS // EIO or EREMOTEIO from lower layers + _BTRFS_DEV_STAT_FLUSH_ERRS // EIO or EREMOTEIO from lower layers + + // stats for indirect indications for I/O failures + + // checksum error, bytenr error or contents is illegal: this is an + // indication that the block was damaged during read or write, or written to + // wrong location or read from wrong location + _BTRFS_DEV_STAT_CORRUPTION_ERRS + _BTRFS_DEV_STAT_GENERATION_ERRS // an indication that blocks have not been written + + _BTRFS_DEV_STAT_VALUES_MAX +) + +// Reset statistics after reading; needs SYS_ADMIN capability +const _BTRFS_DEV_STATS_RESET = (1 << 0) + +type btrfs_ioctl_get_dev_stats struct { + devid uint64 // in + nr_items uint64 // in/out + flags uint64 // in/out + values [_BTRFS_DEV_STAT_VALUES_MAX]uint64 // out values + unused [128 - 2 - _BTRFS_DEV_STAT_VALUES_MAX]uint64 // pad to 1k +} + +const ( + _BTRFS_QUOTA_CTL_ENABLE = 1 + _BTRFS_QUOTA_CTL_DISABLE = 2 + // 3 has formerly been reserved for BTRFS_QUOTA_CTL_RESCAN +) + +type btrfs_ioctl_quota_ctl_args struct { + cmd uint64 + status uint64 +} + +type btrfs_ioctl_quota_rescan_args struct { + flags uint64 + progress uint64 + reserved [6]uint64 +} + +type btrfs_ioctl_qgroup_assign_args struct { + assign uint64 + src uint64 + dst uint64 +} + +type btrfs_ioctl_qgroup_create_args struct { + create uint64 + qgroupid uint64 +} + +type btrfs_ioctl_timespec struct { + sec uint64 + nsec uint32 +} + +type btrfs_ioctl_received_subvol_args struct { + uuid UUID // in + stransid uint64 // in + rtransid uint64 // out + stime btrfs_ioctl_timespec // in + rtime btrfs_ioctl_timespec // out + flags uint64 // in + reserved [16]uint64 // in +} + +const ( + // Caller doesn't want file data in the send stream, even if the + // search of clone sources doesn't find an extent. UPDATE_EXTENT + // commands will be sent instead of WRITE commands. + _BTRFS_SEND_FLAG_NO_FILE_DATA = 0x1 + // Do not add the leading stream header. Used when multiple snapshots + // are sent back to back. + _BTRFS_SEND_FLAG_OMIT_STREAM_HEADER = 0x2 + // Omit the command at the end of the stream that indicated the end + // of the stream. This option is used when multiple snapshots are + // sent back to back. + _BTRFS_SEND_FLAG_OMIT_END_CMD = 0x4 + + _BTRFS_SEND_FLAG_MASK = _BTRFS_SEND_FLAG_NO_FILE_DATA | + _BTRFS_SEND_FLAG_OMIT_STREAM_HEADER | + _BTRFS_SEND_FLAG_OMIT_END_CMD +) + +type btrfs_ioctl_send_args struct { + send_fd int64 // in + clone_sources_count uint64 // in + clone_sources *uint64 // in + parent_root uint64 // in + flags uint64 // in + reserved [4]uint64 // in +} + +var ( + _BTRFS_IOC_SNAP_CREATE = ioctl.IOW(ioctlMagic, 1, unsafe.Sizeof(btrfs_ioctl_vol_args{})) + _BTRFS_IOC_DEFRAG = ioctl.IOW(ioctlMagic, 2, unsafe.Sizeof(btrfs_ioctl_vol_args{})) + _BTRFS_IOC_RESIZE = ioctl.IOW(ioctlMagic, 3, unsafe.Sizeof(btrfs_ioctl_vol_args{})) + _BTRFS_IOC_SCAN_DEV = ioctl.IOW(ioctlMagic, 4, unsafe.Sizeof(btrfs_ioctl_vol_args{})) + _BTRFS_IOC_TRANS_START = ioctl.IO(ioctlMagic, 6) + _BTRFS_IOC_TRANS_END = ioctl.IO(ioctlMagic, 7) + _BTRFS_IOC_SYNC = ioctl.IO(ioctlMagic, 8) + _BTRFS_IOC_CLONE = ioctl.IOW(ioctlMagic, 9, 4) // int32 + _BTRFS_IOC_ADD_DEV = ioctl.IOW(ioctlMagic, 10, unsafe.Sizeof(btrfs_ioctl_vol_args{})) + _BTRFS_IOC_RM_DEV = ioctl.IOW(ioctlMagic, 11, unsafe.Sizeof(btrfs_ioctl_vol_args{})) + _BTRFS_IOC_BALANCE = ioctl.IOW(ioctlMagic, 12, unsafe.Sizeof(btrfs_ioctl_vol_args{})) + _BTRFS_IOC_CLONE_RANGE = ioctl.IOW(ioctlMagic, 13, unsafe.Sizeof(btrfs_ioctl_clone_range_args{})) + _BTRFS_IOC_SUBVOL_CREATE = ioctl.IOW(ioctlMagic, 14, unsafe.Sizeof(btrfs_ioctl_vol_args{})) + _BTRFS_IOC_SNAP_DESTROY = ioctl.IOW(ioctlMagic, 15, unsafe.Sizeof(btrfs_ioctl_vol_args{})) + _BTRFS_IOC_DEFRAG_RANGE = ioctl.IOW(ioctlMagic, 16, unsafe.Sizeof(btrfs_ioctl_defrag_range_args{})) + _BTRFS_IOC_TREE_SEARCH = ioctl.IOWR(ioctlMagic, 17, unsafe.Sizeof(btrfs_ioctl_search_args{})) + _BTRFS_IOC_INO_LOOKUP = ioctl.IOWR(ioctlMagic, 18, unsafe.Sizeof(btrfs_ioctl_ino_lookup_args{})) + _BTRFS_IOC_DEFAULT_SUBVOL = ioctl.IOW(ioctlMagic, 19, 8) // uint64 + _BTRFS_IOC_SPACE_INFO = ioctl.IOWR(ioctlMagic, 20, unsafe.Sizeof(btrfs_ioctl_space_args{})) + _BTRFS_IOC_START_SYNC = ioctl.IOR(ioctlMagic, 24, 8) // uint64 + _BTRFS_IOC_WAIT_SYNC = ioctl.IOW(ioctlMagic, 22, 8) // uint64 + _BTRFS_IOC_SUBVOL_GETFLAGS = ioctl.IOR(ioctlMagic, 25, 8) // uint64 + _BTRFS_IOC_SUBVOL_SETFLAGS = ioctl.IOW(ioctlMagic, 26, 8) // uint64 + _BTRFS_IOC_SCRUB = ioctl.IOWR(ioctlMagic, 27, unsafe.Sizeof(btrfs_ioctl_scrub_args{})) + _BTRFS_IOC_SCRUB_CANCEL = ioctl.IO(ioctlMagic, 28) + _BTRFS_IOC_SCRUB_PROGRESS = ioctl.IOWR(ioctlMagic, 29, unsafe.Sizeof(btrfs_ioctl_scrub_args{})) + _BTRFS_IOC_DEV_INFO = ioctl.IOWR(ioctlMagic, 30, unsafe.Sizeof(btrfs_ioctl_dev_info_args{})) + _BTRFS_IOC_FS_INFO = ioctl.IOR(ioctlMagic, 31, unsafe.Sizeof(btrfs_ioctl_fs_info_args{})) + _BTRFS_IOC_BALANCE_CTL = ioctl.IOW(ioctlMagic, 33, 4) // int32 + _BTRFS_IOC_BALANCE_PROGRESS = ioctl.IOR(ioctlMagic, 34, unsafe.Sizeof(btrfs_ioctl_balance_args{})) + _BTRFS_IOC_INO_PATHS = ioctl.IOWR(ioctlMagic, 35, unsafe.Sizeof(btrfs_ioctl_ino_path_args{})) + _BTRFS_IOC_LOGICAL_INO = ioctl.IOWR(ioctlMagic, 36, unsafe.Sizeof(btrfs_ioctl_ino_path_args{})) + _BTRFS_IOC_SET_RECEIVED_SUBVOL = ioctl.IOWR(ioctlMagic, 37, unsafe.Sizeof(btrfs_ioctl_received_subvol_args{})) + _BTRFS_IOC_SEND = ioctl.IOW(ioctlMagic, 38, unsafe.Sizeof(btrfs_ioctl_send_args{})) + _BTRFS_IOC_DEVICES_READY = ioctl.IOR(ioctlMagic, 39, unsafe.Sizeof(btrfs_ioctl_vol_args{})) + _BTRFS_IOC_QUOTA_CTL = ioctl.IOWR(ioctlMagic, 40, unsafe.Sizeof(btrfs_ioctl_quota_ctl_args{})) + _BTRFS_IOC_QGROUP_ASSIGN = ioctl.IOW(ioctlMagic, 41, unsafe.Sizeof(btrfs_ioctl_qgroup_assign_args{})) + _BTRFS_IOC_QGROUP_CREATE = ioctl.IOW(ioctlMagic, 42, unsafe.Sizeof(btrfs_ioctl_qgroup_create_args{})) + _BTRFS_IOC_QGROUP_LIMIT = ioctl.IOR(ioctlMagic, 43, unsafe.Sizeof(btrfs_ioctl_qgroup_limit_args{})) + _BTRFS_IOC_QUOTA_RESCAN = ioctl.IOW(ioctlMagic, 44, unsafe.Sizeof(btrfs_ioctl_quota_rescan_args{})) + _BTRFS_IOC_QUOTA_RESCAN_STATUS = ioctl.IOR(ioctlMagic, 45, unsafe.Sizeof(btrfs_ioctl_quota_rescan_args{})) + _BTRFS_IOC_QUOTA_RESCAN_WAIT = ioctl.IO(ioctlMagic, 46) + _BTRFS_IOC_GET_FSLABEL = ioctl.IOR(ioctlMagic, 49, BTRFS_LABEL_SIZE) + _BTRFS_IOC_SET_FSLABEL = ioctl.IOW(ioctlMagic, 50, BTRFS_LABEL_SIZE) + _BTRFS_IOC_GET_DEV_STATS = ioctl.IOWR(ioctlMagic, 52, unsafe.Sizeof(btrfs_ioctl_get_dev_stats{})) + _BTRFS_IOC_DEV_REPLACE = ioctl.IOWR(ioctlMagic, 53, unsafe.Sizeof(btrfs_ioctl_dev_replace_args_u1{})) + _BTRFS_IOC_FILE_EXTENT_SAME = ioctl.IOWR(ioctlMagic, 54, unsafe.Sizeof(btrfs_ioctl_same_args{})) + _BTRFS_IOC_GET_FEATURES = ioctl.IOR(ioctlMagic, 57, unsafe.Sizeof(btrfs_ioctl_feature_flags{})) + _BTRFS_IOC_SET_FEATURES = ioctl.IOW(ioctlMagic, 57, unsafe.Sizeof([2]btrfs_ioctl_feature_flags{})) + _BTRFS_IOC_GET_SUPPORTED_FEATURES = ioctl.IOR(ioctlMagic, 57, unsafe.Sizeof([3]btrfs_ioctl_feature_flags{})) +) + +func iocSnapCreate(f *os.File, out *btrfs_ioctl_vol_args) error { + return ioctl.Do(f, _BTRFS_IOC_SNAP_CREATE, out) +} + +func iocDefrag(f *os.File, out *btrfs_ioctl_vol_args) error { + return ioctl.Do(f, _BTRFS_IOC_DEFRAG, out) +} + +func iocResize(f *os.File, out *btrfs_ioctl_vol_args) error { + return ioctl.Do(f, _BTRFS_IOC_RESIZE, out) +} + +func iocScanDev(f *os.File, out *btrfs_ioctl_vol_args) error { + return ioctl.Do(f, _BTRFS_IOC_SCAN_DEV, out) +} + +func iocTransStart(f *os.File) error { + return ioctl.Do(f, _BTRFS_IOC_TRANS_START, nil) +} + +func iocTransEnd(f *os.File) error { + return ioctl.Do(f, _BTRFS_IOC_TRANS_END, nil) +} + +func iocSync(f *os.File) error { + return ioctl.Do(f, _BTRFS_IOC_SYNC, nil) +} + +func iocClone(f *os.File, out *int32) error { + return ioctl.Do(f, _BTRFS_IOC_CLONE, out) +} + +func iocAddDev(f *os.File, out *btrfs_ioctl_vol_args) error { + return ioctl.Do(f, _BTRFS_IOC_ADD_DEV, out) +} + +func iocRmDev(f *os.File, out *btrfs_ioctl_vol_args) error { + return ioctl.Do(f, _BTRFS_IOC_RM_DEV, out) +} + +func iocBalance(f *os.File, out *btrfs_ioctl_vol_args) error { + return ioctl.Do(f, _BTRFS_IOC_BALANCE, out) +} + +func iocCloneRange(f *os.File, out *btrfs_ioctl_clone_range_args) error { + return ioctl.Do(f, _BTRFS_IOC_CLONE_RANGE, out) +} + +func iocSubvolCreate(f *os.File, out *btrfs_ioctl_vol_args) error { + return ioctl.Do(f, _BTRFS_IOC_SUBVOL_CREATE, out) +} + +func iocSnapDestroy(f *os.File, out *btrfs_ioctl_vol_args) error { + return ioctl.Do(f, _BTRFS_IOC_SNAP_DESTROY, out) +} + +func iocDefragRange(f *os.File, out *btrfs_ioctl_defrag_range_args) error { + return ioctl.Do(f, _BTRFS_IOC_DEFRAG_RANGE, out) +} + +func iocTreeSearch(f *os.File, out *btrfs_ioctl_search_args) error { + return ioctl.Do(f, _BTRFS_IOC_TREE_SEARCH, out) +} + +func iocInoLookup(f *os.File, out *btrfs_ioctl_ino_lookup_args) error { + return ioctl.Do(f, _BTRFS_IOC_INO_LOOKUP, out) +} + +func iocDefaultSubvol(f *os.File, out *uint64) error { + return ioctl.Do(f, _BTRFS_IOC_DEFAULT_SUBVOL, out) +} + +func iocSpaceInfo(f *os.File, out *btrfs_ioctl_space_args) error { + return ioctl.Do(f, _BTRFS_IOC_SPACE_INFO, out) +} + +func iocStartSync(f *os.File, out *uint64) error { + return ioctl.Do(f, _BTRFS_IOC_START_SYNC, out) +} + +func iocWaitSync(f *os.File, out *uint64) error { + return ioctl.Do(f, _BTRFS_IOC_WAIT_SYNC, out) +} + +func iocSubvolGetflags(f *os.File, out *uint64) error { + return ioctl.Do(f, _BTRFS_IOC_SUBVOL_GETFLAGS, out) +} + +func iocSubvolSetflags(f *os.File, out *uint64) error { + return ioctl.Do(f, _BTRFS_IOC_SUBVOL_SETFLAGS, out) +} + +func iocScrub(f *os.File, out *btrfs_ioctl_scrub_args) error { + return ioctl.Do(f, _BTRFS_IOC_SCRUB, out) +} + +func iocScrubCancel(f *os.File) error { + return ioctl.Do(f, _BTRFS_IOC_SCRUB_CANCEL, nil) +} + +func iocScrubProgress(f *os.File, out *btrfs_ioctl_scrub_args) error { + return ioctl.Do(f, _BTRFS_IOC_SCRUB_PROGRESS, out) +} + +func iocDevInfo(f *os.File, out *btrfs_ioctl_dev_info_args) error { + return ioctl.Do(f, _BTRFS_IOC_DEV_INFO, out) +} + +func iocBalanceCtl(f *os.File, out *int32) error { + return ioctl.Do(f, _BTRFS_IOC_BALANCE_CTL, out) +} + +func iocBalanceProgress(f *os.File, out *btrfs_ioctl_balance_args) error { + return ioctl.Do(f, _BTRFS_IOC_BALANCE_PROGRESS, out) +} + +func iocInoPaths(f *os.File, out *btrfs_ioctl_ino_path_args) error { + return ioctl.Do(f, _BTRFS_IOC_INO_PATHS, out) +} + +func iocLogicalIno(f *os.File, out *btrfs_ioctl_ino_path_args) error { + return ioctl.Do(f, _BTRFS_IOC_LOGICAL_INO, out) +} + +func iocSetReceivedSubvol(f *os.File, out *btrfs_ioctl_received_subvol_args) error { + return ioctl.Do(f, _BTRFS_IOC_SET_RECEIVED_SUBVOL, out) +} + +func iocSend(f *os.File, out *btrfs_ioctl_send_args) error { + return ioctl.Do(f, _BTRFS_IOC_SEND, out) +} + +func iocDevicesReady(f *os.File, out *btrfs_ioctl_vol_args) error { + return ioctl.Do(f, _BTRFS_IOC_DEVICES_READY, out) +} + +func iocQuotaCtl(f *os.File, out *btrfs_ioctl_quota_ctl_args) error { + return ioctl.Do(f, _BTRFS_IOC_QUOTA_CTL, out) +} + +func iocQgroupAssign(f *os.File, out *btrfs_ioctl_qgroup_assign_args) error { + return ioctl.Do(f, _BTRFS_IOC_QGROUP_ASSIGN, out) +} + +func iocQgroupCreate(f *os.File, out *btrfs_ioctl_qgroup_create_args) error { + return ioctl.Do(f, _BTRFS_IOC_QGROUP_CREATE, out) +} + +func iocQgroupLimit(f *os.File, out *btrfs_ioctl_qgroup_limit_args) error { + return ioctl.Do(f, _BTRFS_IOC_QGROUP_LIMIT, out) +} + +func iocQuotaRescan(f *os.File, out *btrfs_ioctl_quota_rescan_args) error { + return ioctl.Do(f, _BTRFS_IOC_QUOTA_RESCAN, out) +} + +func iocQuotaRescanStatus(f *os.File, out *btrfs_ioctl_quota_rescan_args) error { + return ioctl.Do(f, _BTRFS_IOC_QUOTA_RESCAN_STATUS, out) +} + +func iocQuotaRescanWait(f *os.File) error { + return ioctl.Do(f, _BTRFS_IOC_QUOTA_RESCAN_WAIT, nil) +} + +func iocGetFslabel(f *os.File, out *[BTRFS_LABEL_SIZE]byte) error { + return ioctl.Do(f, _BTRFS_IOC_GET_FSLABEL, out) +} + +func iocSetFslabel(f *os.File, out *[BTRFS_LABEL_SIZE]byte) error { + return ioctl.Do(f, _BTRFS_IOC_SET_FSLABEL, out) +} + +func iocGetDevStats(f *os.File, out *btrfs_ioctl_get_dev_stats) error { + return ioctl.Do(f, _BTRFS_IOC_GET_DEV_STATS, out) +} + +//func iocDevReplace(f *os.File, out *btrfs_ioctl_dev_replace_args) error { +// return ioctl.Do(f, _BTRFS_IOC_DEV_REPLACE, out) +//} + +func iocFileExtentSame(f *os.File, out *btrfs_ioctl_same_args) error { + return ioctl.Do(f, _BTRFS_IOC_FILE_EXTENT_SAME, out) +} + +func iocSetFeatures(f *os.File, out *[2]btrfs_ioctl_feature_flags) error { + return ioctl.Do(f, _BTRFS_IOC_SET_FEATURES, out) +} diff --git a/ioctl_h_test.go b/ioctl_h_test.go new file mode 100644 index 0000000..f8f0582 --- /dev/null +++ b/ioctl_h_test.go @@ -0,0 +1,58 @@ +package btrfs + +import ( + "reflect" + "testing" +) + +var caseSizes = []struct { + obj interface{} + size int +}{ + {obj: btrfs_ioctl_vol_args{}, size: 4096}, + {obj: btrfs_qgroup_limit{}, size: 40}, + {obj: btrfs_qgroup_inherit{}, size: 72}, + {obj: btrfs_ioctl_qgroup_limit_args{}, size: 48}, + {obj: btrfs_ioctl_vol_args_v2{}, size: 4096}, + {obj: btrfs_scrub_progress{}, size: 120}, + {obj: btrfs_ioctl_scrub_args{}, size: 1024}, + {obj: btrfs_ioctl_dev_replace_start_params{}, size: 2072}, + {obj: btrfs_ioctl_dev_replace_status_params{}, size: 48}, + {obj: btrfs_ioctl_dev_replace_args_u1{}, size: 2600}, + {obj: btrfs_ioctl_dev_replace_args_u2{}, size: 2600}, + {obj: btrfs_ioctl_dev_info_args{}, size: 4096}, + {obj: btrfs_ioctl_fs_info_args{}, size: 1024}, + {obj: btrfs_ioctl_feature_flags{}, size: 24}, + {obj: btrfs_balance_args{}, size: 136}, + {obj: btrfs_balance_progress{}, size: 24}, + {obj: btrfs_ioctl_balance_args{}, size: 1024}, + {obj: btrfs_ioctl_ino_lookup_args{}, size: 4096}, + {obj: btrfs_ioctl_search_key{}, size: 104}, + {obj: btrfs_ioctl_search_header{}, size: 32}, + {obj: btrfs_ioctl_search_args{}, size: 4096}, + {obj: btrfs_ioctl_search_args_v2{}, size: 112}, + {obj: btrfs_ioctl_clone_range_args{}, size: 32}, + {obj: btrfs_ioctl_same_extent_info{}, size: 32}, + {obj: btrfs_ioctl_same_args{}, size: 24}, + {obj: btrfs_ioctl_defrag_range_args{}, size: 48}, + {obj: btrfs_ioctl_space_info{}, size: 24}, + {obj: btrfs_ioctl_space_args{}, size: 16}, + {obj: btrfs_data_container{}, size: 16}, + {obj: btrfs_ioctl_ino_path_args{}, size: 56}, + {obj: btrfs_ioctl_logical_ino_args{}, size: 56}, + {obj: btrfs_ioctl_get_dev_stats{}, size: 1032}, + {obj: btrfs_ioctl_quota_ctl_args{}, size: 16}, + {obj: btrfs_ioctl_qgroup_assign_args{}, size: 24}, + {obj: btrfs_ioctl_qgroup_create_args{}, size: 16}, + {obj: btrfs_ioctl_timespec{}, size: 16}, + {obj: btrfs_ioctl_received_subvol_args{}, size: 200}, + {obj: btrfs_ioctl_send_args{}, size: 72}, +} + +func TestSizes(t *testing.T) { + for _, c := range caseSizes { + if sz := int(reflect.ValueOf(c.obj).Type().Size()); sz != c.size { + t.Fatalf("unexpected size of %T: %d", c.obj, sz) + } + } +} diff --git a/subvolume.go b/subvolume.go new file mode 100644 index 0000000..be7db56 --- /dev/null +++ b/subvolume.go @@ -0,0 +1,21 @@ +package btrfs + +import ( + "syscall" +) + +func IsSubVolume(path string) (bool, error) { + var st syscall.Stat_t + if err := syscall.Stat(path, &st); err != nil { + return false, err + } + if st.Ino != BTRFS_FIRST_FREE_OBJECTID || + st.Mode&syscall.S_IFMT != syscall.S_IFDIR { + return false, nil + } + var stfs syscall.Statfs_t + if err := syscall.Statfs(path, &stfs); err != nil { + return false, err + } + return stfs.Type == SuperMagic, nil +} From 9aa3cf08560d24a46a7882fd77aec7b895f33a02 Mon Sep 17 00:00:00 2001 From: Denys Smirnov Date: Thu, 15 Sep 2016 20:39:46 +0300 Subject: [PATCH 03/38] Implement basic subvolume and snapshot commands. Exec-based send and receive. * better code generator * regenerate btrfs_tree.h constants * implement subvolume create, delete and snapshot create commands * exec-based snapshot send and receive (cherry picked from commit dennwc/btrfs@b300237e77a7cfcac0e6caff3b4857cfb30ce4a6) Signed-off-by: Akihiro Suda --- btrfs.go | 48 ++++- btrfs_list.go | 13 ++ btrfs_tree_h.go | 23 --- btrfs_tree_hc.go | 482 +++++++++++++++++++++++++++++++++++++++++++++++ cmd/gbtrfs.go | 94 +++++++++ cmd/hgen.go | 43 ++++- errors.go | 2 +- headers.go | 4 + ioctl/ioctl.go | 4 - ioctl_h.go | 42 ++++- receive.go | 32 ++++ send.go | 46 +++++ subvolume.go | 130 ++++++++++++- utils.go | 37 ++++ 14 files changed, 953 insertions(+), 47 deletions(-) create mode 100644 btrfs_list.go delete mode 100644 btrfs_tree_h.go create mode 100644 btrfs_tree_hc.go create mode 100644 cmd/gbtrfs.go create mode 100644 headers.go create mode 100644 receive.go create mode 100644 send.go create mode 100644 utils.go diff --git a/btrfs.go b/btrfs.go index 5d8e054..478b43a 100644 --- a/btrfs.go +++ b/btrfs.go @@ -1,13 +1,13 @@ package btrfs import ( + "fmt" "github.com/dennwc/btrfs/ioctl" + "io" "os" + "path/filepath" ) -//go:generate go run cmd/hgen.go -p btrfs -o btrfs_tree_hc.go btrfs_tree.h -//go:generate go fmt -w btrfs_tree_hc.go - const SuperMagic = 0x9123683E func Open(path string) (*FS, error) { @@ -19,6 +19,12 @@ func Open(path string) (*FS, error) { dir, err := os.Open(path) if err != nil { return nil, err + } else if st, err := dir.Stat(); err != nil { + dir.Close() + return nil, err + } else if !st.IsDir() { + dir.Close() + return nil, fmt.Errorf("not a directory: %s", path) } return &FS{f: dir}, nil } @@ -139,3 +145,39 @@ func (f *FS) Sync() (err error) { } return ioctl.Do(f.f, _BTRFS_IOC_WAIT_SYNC, nil) } + +func (f *FS) CreateSubVolume(name string) error { + return CreateSubVolume(filepath.Join(f.f.Name(), name)) +} + +func (f *FS) DeleteSubVolume(name string) error { + return DeleteSubVolume(filepath.Join(f.f.Name(), name)) +} + +func (f *FS) Snapshot(dst string, ro bool) error { + return SnapshotSubVolume(f.f.Name(), filepath.Join(f.f.Name(), dst), ro) +} + +func (f *FS) SnapshotSubVolume(name string, dst string, ro bool) error { + return SnapshotSubVolume(filepath.Join(f.f.Name(), name), + filepath.Join(f.f.Name(), dst), ro) +} + +func (f *FS) Send(w io.Writer, parent string, subvols ...string) error { + if parent != "" { + parent = filepath.Join(f.f.Name(), parent) + } + sub := make([]string, 0, len(subvols)) + for _, s := range subvols { + sub = append(sub, filepath.Join(f.f.Name(), s)) + } + return Send(w, parent, sub...) +} + +func (f *FS) Receive(r io.Reader) error { + return Receive(r, f.f.Name()) +} + +func (f *FS) ReceiveTo(r io.Reader, mount string) error { + return Receive(r, filepath.Join(f.f.Name(), mount)) +} diff --git a/btrfs_list.go b/btrfs_list.go new file mode 100644 index 0000000..4b4a941 --- /dev/null +++ b/btrfs_list.go @@ -0,0 +1,13 @@ +package btrfs + +import "os" + +func getPathRootID(file *os.File) (uint64, error) { + args := btrfs_ioctl_ino_lookup_args{ + objectid: firstFreeObjectid, + } + if err := iocInoLookup(file, &args); err != nil { + return 0, err + } + return args.treeid, nil +} diff --git a/btrfs_tree_h.go b/btrfs_tree_h.go deleted file mode 100644 index e35273d..0000000 --- a/btrfs_tree_h.go +++ /dev/null @@ -1,23 +0,0 @@ -package btrfs - -// This header contains the structure definitions and constants used -// by file system objects that can be retrieved using -// the BTRFS_IOC_SEARCH_TREE ioctl. That means basically anything that -// is needed to describe a leaf node's key or item contents. - -const ( - // Holds pointers to all of the tree roots - BTRFS_ROOT_TREE_OBJECTID = 1 - - // Stores information about which extents are in use, and reference counts - BTRFS_EXTENT_TREE_OBJECTID = 2 - - // Chunk tree stores translations from logical -> physical block numbering - // the super block points to the chunk tree. - BTRFS_CHUNK_TREE_OBJECTID = 3 - - // All files have objectids in this range. - BTRFS_FIRST_FREE_OBJECTID = 256 - BTRFS_LAST_FREE_OBJECTID = 0xffffff00 // -256 - BTRFS_FIRST_CHUNK_TREE_OBJECTID = 256 -) diff --git a/btrfs_tree_hc.go b/btrfs_tree_hc.go new file mode 100644 index 0000000..5b74214 --- /dev/null +++ b/btrfs_tree_hc.go @@ -0,0 +1,482 @@ +package btrfs + +// This code was auto-generated; DO NOT EDIT! + +// This header contains the structure definitions and constants used +// by file system objects that can be retrieved using +// the BTRFS_IOC_SEARCH_TREE ioctl. That means basically anything that +// is needed to describe a leaf node's key or item contents. + +const ( + // Holds pointers to all of the tree roots + rootTreeObjectid = 1 + + // Stores information about which extents are in use, and reference counts + extentTreeObjectid = 2 + + // Chunk tree stores translations from logical -> physical block numbering + // the super block points to the chunk tree + chunkTreeObjectid = 3 + + // Stores information about which areas of a given device are in use. + // one per device. The tree of tree roots points to the device tree + devTreeObjectid = 4 + + // One per subvolume, storing files and directories + fsTreeObjectid = 5 + + // Directory objectid inside the root tree + rootTreeDirObjectid = 6 + + // Holds checksums of all the data extents + csumTreeObjectid = 7 + + // Holds quota configuration and tracking + quotaTreeObjectid = 8 + + // For storing items that use the BTRFS_UUID_KEY* types + uuidTreeObjectid = 9 + + // Tracks free space in block groups. + freeSpaceTreeObjectid = 10 + + // Device stats in the device tree + devStatsObjectid = 0 + + // For storing balance parameters in the root tree + balanceObjectid = 0xfffffffffffffffc /* -4 */ + + // Orhpan objectid for tracking unlinked/truncated files + orphanObjectid = 0xfffffffffffffffb /* -5 */ + + // Does write ahead logging to speed up fsyncs + treeLogObjectid = 0xfffffffffffffffa /* -6 */ + treeLogFixupObjectid = 0xfffffffffffffff9 /* -7 */ + + // For space balancing + treeRelocObjectid = 0xfffffffffffffff8 /* -8 */ + dataRelocTreeObjectid = 0xfffffffffffffff7 /* -9 */ + + // Extent checksums all have this objectid + // this allows them to share the logging tree + // for fsyncs + extentCsumObjectid = 0xfffffffffffffff6 /* -10 */ + + // For storing free space cache + freeSpaceObjectid = 0xfffffffffffffff5 /* -11 */ + + // The inode number assigned to the special inode for storing + // free ino cache + freeInoObjectid = 0xfffffffffffffff4 /* -12 */ + + // Dummy objectid represents multiple objectids + multipleObjectids = 0xffffffffffffff01 /* -255 */ + + // All files have objectids in this range. + firstFreeObjectid = 256 + lastFreeObjectid = 0xffffffffffffff00 /* -256 */ + firstChunkTreeObjectid = 256 + + // The device items go into the chunk tree. The key is in the form + // [ 1 BTRFS_DEV_ITEM_KEY device_id ] + devItemsObjectid = 1 + + btreeInodeObjectid = 1 + + emptySubvolDirObjectid = 2 + + devReplaceDevid = 0 + + // Inode items have the data typically returned from stat and store other + // info about object characteristics. There is one for every file and dir in + // the FS + inodeItemKey = 1 + inodeRefKey = 12 + inodeExtrefKey = 13 + xattrItemKey = 24 + orphanItemKey = 48 + // Reserve 2-15 close to the inode for later flexibility + + // Dir items are the name -> inode pointers in a directory. There is one + // for every name in a directory. + dirLogItemKey = 60 + dirLogIndexKey = 72 + dirItemKey = 84 + dirIndexKey = 96 + // Extent data is for file data + extentDataKey = 108 + + // Extent csums are stored in a separate tree and hold csums for + // an entire extent on disk. + extentCsumKey = 128 + + // Root items point to tree roots. They are typically in the root + // tree used by the super block to find all the other trees + rootItemKey = 132 + + // Root backrefs tie subvols and snapshots to the directory entries that + // reference them + rootBackrefKey = 144 + + // Root refs make a fast index for listing all of the snapshots and + // subvolumes referenced by a given root. They point directly to the + // directory item in the root that references the subvol + rootRefKey = 156 + + // Extent items are in the extent map tree. These record which blocks + // are used, and how many references there are to each block + extentItemKey = 168 + + // The same as the BTRFS_EXTENT_ITEM_KEY, except it's metadata we already know + // the length, so we save the level in key->offset instead of the length. + metadataItemKey = 169 + + treeBlockRefKey = 176 + + extentDataRefKey = 178 + + sharedBlockRefKey = 182 + + sharedDataRefKey = 184 + + // Block groups give us hints into the extent allocation trees. Which + // blocks are free etc etc + blockGroupItemKey = 192 + + // Every block group is represented in the free space tree by a free space info + // item, which stores some accounting information. It is keyed on + // (block_group_start, FREE_SPACE_INFO, block_group_length). + freeSpaceInfoKey = 198 + + // A free space extent tracks an extent of space that is free in a block group. + // It is keyed on (start, FREE_SPACE_EXTENT, length). + freeSpaceExtentKey = 199 + + // When a block group becomes very fragmented, we convert it to use bitmaps + // instead of extents. A free space bitmap is keyed on + // (start, FREE_SPACE_BITMAP, length); the corresponding item is a bitmap with + // (length / sectorsize) bits. + freeSpaceBitmapKey = 200 + + devExtentKey = 204 + devItemKey = 216 + chunkItemKey = 228 + + // Records the overall state of the qgroups. + // There's only one instance of this key present, + // (0, BTRFS_QGROUP_STATUS_KEY, 0) + qgroupStatusKey = 240 + // Records the currently used space of the qgroup. + // One key per qgroup, (0, BTRFS_QGROUP_INFO_KEY, qgroupid). + qgroupInfoKey = 242 + // Contains the user configured limits for the qgroup. + // One key per qgroup, (0, BTRFS_QGROUP_LIMIT_KEY, qgroupid). + qgroupLimitKey = 244 + // Records the child-parent relationship of qgroups. For + // each relation, 2 keys are present: + // (childid, BTRFS_QGROUP_RELATION_KEY, parentid) + // (parentid, BTRFS_QGROUP_RELATION_KEY, childid) + qgroupRelationKey = 246 + + // Obsolete name, see BTRFS_TEMPORARY_ITEM_KEY. + balanceItemKey = 248 + + // The key type for tree items that are stored persistently, but do not need to + // exist for extended period of time. The items can exist in any tree. + // [subtype, BTRFS_TEMPORARY_ITEM_KEY, data] + // Existing items: + // - balance status item + // (BTRFS_BALANCE_OBJECTID, BTRFS_TEMPORARY_ITEM_KEY, 0) + temporaryItemKey = 248 + + // Obsolete name, see BTRFS_PERSISTENT_ITEM_KEY + devStatsKey = 249 + + // The key type for tree items that are stored persistently and usually exist + // for a long period, eg. filesystem lifetime. The item kinds can be status + // information, stats or preference values. The item can exist in any tree. + // [subtype, BTRFS_PERSISTENT_ITEM_KEY, data] + // Existing items: + // - device statistics, store IO stats in the device tree, one key for all + // stats + // (BTRFS_DEV_STATS_OBJECTID, BTRFS_DEV_STATS_KEY, 0) + persistentItemKey = 249 + + // Persistantly stores the device replace state in the device tree. + // The key is built like this: (0, BTRFS_DEV_REPLACE_KEY, 0). + devReplaceKey = 250 + + // Stores items that allow to quickly map UUIDs to something else. + // These items are part of the filesystem UUID tree. + // The key is built like this: + // (UUID_upper_64_bits, BTRFS_UUID_KEY*, UUID_lower_64_bits). + uuidKeySubvol = 251 + uuidKeyReceivedSubvol = 252 + + // String items are for debugging. They just store a short string of + // data in the FS + stringItemKey = 253 + + // 32 bytes in various csum fields + csumSize = 32 + + // Csum types + + // Flags definitions for directory entry item type + // Used by: + // struct btrfs_dir_item.type + ftUnknown = 0 + ftRegFile = 1 + ftDir = 2 + ftChrdev = 3 + ftBlkdev = 4 + ftFifo = 5 + ftSock = 6 + ftSymlink = 7 + ftXattr = 8 + ftMax = 9 + + // The key defines the order in the tree, and so it also defines (optimal) + // block layout. + // objectid corresponds to the inode number. + // type tells us things about the object, and is a kind of stream selector. + // so for a given inode, keys with type of 1 might refer to the inode data, + // type of 2 may point to file data in the btree and type == 3 may point to + // extents. + // offset is the starting byte offset for this key in the stream. + // btrfs_disk_key is in disk byte order. struct btrfs_key is always + // in cpu native order. Otherwise they are identical and their sizes + // should be the same (ie both packed) + + // The internal btrfs device id + + // Size of the device + + // Bytes used + + // Optimal io alignment for this device + + // Optimal io width for this device + + // Minimal io size for this device + + // Type and info about this device + + // Expected generation for this device + + // Starting byte of this partition on the device, + // to allow for stripe alignment in the future + + // Grouping information for allocation decisions + + // Seek speed 0-100 where 100 is fastest + + // Bandwidth 0-100 where 100 is fastest + + // Btrfs generated uuid for this device + + // Uuid of FS who owns this device + + // Size of this chunk in bytes + + // Objectid of the root referencing this chunk + + // Optimal io alignment for this chunk + + // Optimal io width for this chunk + + // Minimal io size for this chunk + + // 2^16 stripes is quite a lot, a second limit is the size of a single + // item in the btree + + // Sub stripes only matter for raid10 + // Additional stripes go here + + freeSpaceExtent = 1 + freeSpaceBitmap = 2 + + headerFlagWritten = (1 << 0) + headerFlagReloc = (1 << 1) + + // Super block flags + // Errors detected + superFlagError = (1 << 2) + + superFlagSeeding = (1 << 32) + superFlagMetadump = (1 << 33) + + // Items in the extent btree are used to record the objectid of the + // owner of the block and the number of references + + extentFlagData = (1 << 0) + extentFlagTreeBlock = (1 << 1) + + // Following flags only apply to tree blocks + + // Use full backrefs for extent pointers in the block + blockFlagFullBackref = (1 << 8) + + // This flag is only used internally by scrub and may be changed at any time + // it is only declared here to avoid collisions + extentFlagSuper = (1 << 48) + + // Old style backrefs item + + // Dev extents record free space on individual devices. The owner + // field points back to the chunk allocation mapping tree that allocated + // the extent. The chunk tree uuid field is a way to double check the owner + + // Name goes here + + // Name goes here + + // Nfs style generation number + // Transid that last touched this inode + + // Modification sequence number for NFS + + // A little future expansion, for more than this we can + // just grow the inode item and version it + + rootSubvolRdonly = (1 << 0) + + // Internal in-memory flag that a subvolume has been marked for deletion but + // still visible as a directory + rootSubvolDead = (1 << 48) + + // The following fields appear after subvol_uuids+subvol_times + // were introduced. + + // This generation number is used to test if the new fields are valid + // and up to date while reading the root item. Every time the root item + // is written out, the "generation" field is copied into this field. If + // anyone ever mounted the fs with an older kernel, we will have + // mismatching generation values here and thus must invalidate the + // new fields. See btrfs_update_root and btrfs_find_last_root for + // details. + // the offset of generation_v2 is also used as the start for the memset + // when invalidating the fields. + + // This is used for both forward and backward root refs + + // Profiles to operate on, single is denoted by + // BTRFS_AVAIL_ALLOC_BIT_SINGLE + + // Usage filter + // BTRFS_BALANCE_ARGS_USAGE with a single value means '0..N' + // BTRFS_BALANCE_ARGS_USAGE_RANGE - range syntax, min..max + + // Devid filter + + // Devid subset filter [pstart..pend) + + // Btrfs virtual address space subset filter [vstart..vend) + + // Profile to convert to, single is denoted by + // BTRFS_AVAIL_ALLOC_BIT_SINGLE + + // BTRFS_BALANCE_ARGS_* + + // BTRFS_BALANCE_ARGS_LIMIT with value 'limit' + // BTRFS_BALANCE_ARGS_LIMIT_RANGE - the extend version can use minimum + // and maximum + + // Process chunks that cross stripes_min..stripes_max devices, + // BTRFS_BALANCE_ARGS_STRIPES_RANGE + + // Store balance parameters to disk so that balance can be properly + // resumed after crash or unmount + // BTRFS_BALANCE_* + + fileExtentInline = 0 + fileExtentReg = 1 + fileExtentPrealloc = 2 + + // Transaction id that created this extent + // Max number of bytes to hold this extent in ram + // when we split a compressed extent we can't know how big + // each of the resulting pieces will be. So, this is + // an upper limit on the size of the extent in ram instead of + // an exact limit. + + // 32 bits for the various ways we might encode the data, + // including compression and encryption. If any of these + // are set to something a given disk format doesn't understand + // it is treated like an incompat flag for reading and writing, + // but not for stat. + + // Are we inline data or a real extent? + + // Disk space consumed by the extent, checksum blocks are included + // in these numbers + // At this offset in the structure, the inline extent data start. + // The logical offset in file blocks (no csums) + // this extent record is for. This allows a file extent to point + // into the middle of an existing extent on disk, sharing it + // between two snapshots (useful if some bytes in the middle of the + // extent have changed + // The logical number of file blocks (no csums included). This + // always reflects the size uncompressed and without encoding. + + // Grow this item struct at the end for future enhancements and keep + // the existing values unchanged + + devReplaceItemContReadingFromSrcdevModeAlways = 0 + devReplaceItemContReadingFromSrcdevModeAvoid = 1 + devReplaceItemStateNeverStarted = 0 + devReplaceItemStateStarted = 1 + devReplaceItemStateSuspended = 2 + devReplaceItemStateFinished = 3 + devReplaceItemStateCanceled = 4 + + // Grow this item struct at the end for future enhancements and keep + // the existing values unchanged + + // Different types of block groups (and chunks) + blockGroupData = (1 << 0) + blockGroupSystem = (1 << 1) + blockGroupMetadata = (1 << 2) + blockGroupDup = (1 << 5) + + // We need a bit for restriper to be able to tell when chunks of type + // SINGLE are available. This "extended" profile format is used in + // fs_info->avail_*_alloc_bits (in-memory) and balance item fields + // (on-disk). The corresponding on-disk bit in chunk.type is reserved + // to avoid remappings between two formats in future. + availAllocBitSingle = (1 << 48) + + // A fake block group type that is used to communicate global block reserve + // size to userspace via the SPACE_INFO ioctl. + spaceInfoGlobalRsv = (1 << 49) + + freeSpaceUsingBitmaps = (1 << 0) + + qgroupLevelShift = 48 + + // Is subvolume quota turned on? + qgroupStatusFlagOn = (1 << 0) + // RESCAN is set during the initialization phase + qgroupStatusFlagRescan = (1 << 1) + // Some qgroup entries are known to be out of date, + // either because the configuration has changed in a way that + // makes a rescan necessary, or because the fs has been mounted + // with a non-qgroup-aware version. + // Turning qouta off and on again makes it inconsistent, too. + qgroupStatusFlagInconsistent = (1 << 2) + + qgroupStatusVersion = 1 + + // The generation is updated during every commit. As older + // versions of btrfs are not aware of qgroups, it will be + // possible to detect inconsistencies by checking the + // generation on mount time + + // Flag definitions see above + + // Only used during scanning to record the progress + // of the scan. It contains a logical address + + // Only updated when any of the other values change + +) diff --git a/cmd/gbtrfs.go b/cmd/gbtrfs.go new file mode 100644 index 0000000..2389992 --- /dev/null +++ b/cmd/gbtrfs.go @@ -0,0 +1,94 @@ +package main + +import ( + "fmt" + "github.com/dennwc/btrfs" + "github.com/spf13/cobra" + "os" +) + +func init() { + RootCmd.AddCommand( + SubvolumeCmd, + SendCmd, + ReceiveCmd, + ) + + SubvolumeCmd.AddCommand( + SubvolumeCreateCmd, + SubvolumeDeleteCmd, + ) +} + +var RootCmd = &cobra.Command{ + Use: "btrfs [--help] [--version] [...] []", + Short: "Use --help as an argument for information on a specific group or command.", +} + +var SubvolumeCmd = &cobra.Command{ + Use: "subvolume ", +} + +var SubvolumeCreateCmd = &cobra.Command{ + Use: "create [-i ] [/]", + Short: "Create a subvolume", + Long: `Create a subvolume in . If is not given subvolume will be created in the current directory.`, + RunE: func(cmd *cobra.Command, args []string) error { + if len(args) == 0 { + return fmt.Errorf("subvolume not specified") + } else if len(args) > 1 { + return fmt.Errorf("only one subvolume name is allowed") + } + return btrfs.CreateSubVolume(args[0]) + }, +} + +var SubvolumeDeleteCmd = &cobra.Command{ + Use: "delete [options] [...]", + Short: "Delete subvolume(s)", + Long: `Delete subvolumes from the filesystem. The corresponding directory +is removed instantly but the data blocks are removed later. +The deletion does not involve full commit by default due to +performance reasons (as a consequence, the subvolume may appear again +after a crash). Use one of the --commit options to wait until the +operation is safely stored on the media.`, + RunE: func(cmd *cobra.Command, args []string) error { + for _, arg := range args { + if err := btrfs.DeleteSubVolume(arg); err != nil { + return err + } + } + return nil + }, +} + +var SendCmd = &cobra.Command{ + Use: "send [-ve] [-p ] [-c ] [-f ] [...]", + Short: "Send the subvolume(s) to stdout.", + Long: `Sends the subvolume(s) specified by to stdout. + should be read-only here.`, + RunE: func(cmd *cobra.Command, args []string) error { + return btrfs.Send(os.Stdout, "", args...) + }, +} + +var ReceiveCmd = &cobra.Command{ + Use: "receive [-ve] [-f ] [--max-errors ] ", + Short: "Receive subvolumes from stdin.", + Long: `Receives one or more subvolumes that were previously +sent with btrfs send. The received subvolumes are stored +into .`, + RunE: func(cmd *cobra.Command, args []string) error { + if len(args) != 1 { + return fmt.Errorf("expected one destination argument") + } + return btrfs.Receive(os.Stdin, args[0]) + }, +} + +func main() { + if err := RootCmd.Execute(); err != nil { + fmt.Println(err) + os.Exit(-1) + } +} diff --git a/cmd/hgen.go b/cmd/hgen.go index 33bbc9a..02616a4 100644 --- a/cmd/hgen.go +++ b/cmd/hgen.go @@ -9,20 +9,47 @@ import ( "log" "os" "regexp" + "strconv" "strings" "unicode" ) var ( - f_pkg = flag.String("p", "main", "package name for generated file") - f_out = flag.String("o", "-", "output file") + f_pkg = flag.String("p", "main", "package name for generated file") + f_out = flag.String("o", "-", "output file") + f_unexport = flag.Bool("u", true, "make all definitions unexported") + f_goname = flag.Bool("g", true, "rename symbols to follow Go conventions") + f_trim = flag.String("t", "", "prefix to trim from names") ) var ( - reDefineIntConst = regexp.MustCompile(`#define\s+([A-Za-z_]+)\s+(\(?\d+(?:U?LL)?(?:\s*<<\s*\d+)?\)?)`) + reDefineIntConst = regexp.MustCompile(`#define\s+([A-Za-z_]+)\s+(\(?-?\d+(?:U?LL)?(?:\s*<<\s*\d+)?\)?)`) + reNegULL = regexp.MustCompile(`-(\d+)ULL`) ) func constName(s string) string { + s = strings.TrimPrefix(s, *f_trim) + if *f_goname { + buf := bytes.NewBuffer(nil) + buf.Grow(len(s)) + up := !*f_unexport + for _, r := range s { + if r == '_' { + up = true + continue + } + if up { + up = false + r = unicode.ToUpper(r) + } else { + r = unicode.ToLower(r) + } + buf.WriteRune(r) + } + s = buf.String() + } else if *f_unexport { + s = "_" + s + } return s } @@ -41,6 +68,7 @@ func process(w io.Writer, path string) error { ) nl := true + fmt.Fprint(w, "// This code was auto-generated; DO NOT EDIT!\n\n") defer fmt.Fprintln(w, ")") for { line, err := r.ReadBytes('\n') @@ -96,6 +124,15 @@ func process(w io.Writer, path string) error { sub := reDefineIntConst.FindStringSubmatch(string(line)) if len(sub) > 0 { name, val := sub[1], sub[2] + if sub := reNegULL.FindAllStringSubmatch(val, -1); len(sub) > 0 { + for _, s := range sub { + v, err := strconv.ParseInt(s[1], 10, 64) + if err != nil { + panic(err) + } + val = strings.Replace(val, s[0], fmt.Sprintf("0x%x /* -%s */", uint64(-v), s[1]), -1) + } + } val = strings.Replace(val, "ULL", "", -1) fmt.Fprintf(w, "\t%s = %s\n", constName(name), val) continue diff --git a/errors.go b/errors.go index 5259ae7..cda99f8 100644 --- a/errors.go +++ b/errors.go @@ -7,7 +7,7 @@ type ErrNotBtrfs struct { } func (e ErrNotBtrfs) Error() string { - return fmt.Sprintf("path %q is not a btrfs", e.Path) + return fmt.Sprintf("not a btrfs filesystem: %s", e.Path) } // Error codes as returned by the kernel diff --git a/headers.go b/headers.go new file mode 100644 index 0000000..f0b0a0f --- /dev/null +++ b/headers.go @@ -0,0 +1,4 @@ +package btrfs + +//go:generate go run ./cmd/hgen.go -u -g -t BTRFS_ -p btrfs -o btrfs_tree_hc.go btrfs_tree.h +//go:generate gofmt -l -w btrfs_tree_hc.go diff --git a/ioctl/ioctl.go b/ioctl/ioctl.go index d2061a0..0571c5b 100644 --- a/ioctl/ioctl.go +++ b/ioctl/ioctl.go @@ -65,10 +65,6 @@ func Ioctl(f *os.File, ioc uintptr, addr uintptr) error { return nil } -func SizeOf(arg interface{}) uintptr { - return reflect.TypeOf(arg).Size() -} - func Do(f *os.File, ioc uintptr, arg interface{}) error { var addr uintptr if arg != nil { diff --git a/ioctl_h.go b/ioctl_h.go index 7613fa9..386246d 100644 --- a/ioctl_h.go +++ b/ioctl_h.go @@ -64,10 +64,26 @@ type btrfs_ioctl_vol_args_v2_u1 struct { const subvolNameMax = 4039 +type subvolFlags uint64 + +// flags for subvolumes +// +// Used by: +// struct btrfs_ioctl_vol_args_v2.flags +// +// BTRFS_SUBVOL_RDONLY is also provided/consumed by the following ioctls: +// - BTRFS_IOC_SUBVOL_GETFLAGS +// - BTRFS_IOC_SUBVOL_SETFLAGS +const ( + subvolCreateAsync = subvolFlags(1 << 0) + subvolReadOnly = subvolFlags(1 << 1) + subvolQGroupInherit = subvolFlags(1 << 2) +) + type btrfs_ioctl_vol_args_v2 struct { fd int64 transid uint64 - flags uint64 + flags subvolFlags btrfs_ioctl_vol_args_v2_u1 unused [2]uint64 name [subvolNameMax + 1]byte @@ -267,7 +283,7 @@ type btrfs_ioctl_search_key struct { max_type uint32 // how many items did userland ask for, and how many are we returning nr_items uint32 - unused [4 + 4*8]byte + unused [36]byte } type btrfs_ioctl_search_header struct { @@ -507,6 +523,8 @@ var ( _BTRFS_IOC_SPACE_INFO = ioctl.IOWR(ioctlMagic, 20, unsafe.Sizeof(btrfs_ioctl_space_args{})) _BTRFS_IOC_START_SYNC = ioctl.IOR(ioctlMagic, 24, 8) // uint64 _BTRFS_IOC_WAIT_SYNC = ioctl.IOW(ioctlMagic, 22, 8) // uint64 + _BTRFS_IOC_SNAP_CREATE_V2 = ioctl.IOW(ioctlMagic, 23, unsafe.Sizeof(btrfs_ioctl_vol_args_v2{})) + _BTRFS_IOC_SUBVOL_CREATE_V2 = ioctl.IOW(ioctlMagic, 24, unsafe.Sizeof(btrfs_ioctl_vol_args_v2{})) _BTRFS_IOC_SUBVOL_GETFLAGS = ioctl.IOR(ioctlMagic, 25, 8) // uint64 _BTRFS_IOC_SUBVOL_SETFLAGS = ioctl.IOW(ioctlMagic, 26, 8) // uint64 _BTRFS_IOC_SCRUB = ioctl.IOWR(ioctlMagic, 27, unsafe.Sizeof(btrfs_ioctl_scrub_args{})) @@ -538,8 +556,12 @@ var ( _BTRFS_IOC_GET_SUPPORTED_FEATURES = ioctl.IOR(ioctlMagic, 57, unsafe.Sizeof([3]btrfs_ioctl_feature_flags{})) ) -func iocSnapCreate(f *os.File, out *btrfs_ioctl_vol_args) error { - return ioctl.Do(f, _BTRFS_IOC_SNAP_CREATE, out) +func iocSnapCreate(f *os.File, in *btrfs_ioctl_vol_args) error { + return ioctl.Do(f, _BTRFS_IOC_SNAP_CREATE, in) +} + +func iocSnapCreateV2(f *os.File, in *btrfs_ioctl_vol_args_v2) error { + return ioctl.Do(f, _BTRFS_IOC_SNAP_CREATE_V2, in) } func iocDefrag(f *os.File, out *btrfs_ioctl_vol_args) error { @@ -586,12 +608,16 @@ func iocCloneRange(f *os.File, out *btrfs_ioctl_clone_range_args) error { return ioctl.Do(f, _BTRFS_IOC_CLONE_RANGE, out) } -func iocSubvolCreate(f *os.File, out *btrfs_ioctl_vol_args) error { - return ioctl.Do(f, _BTRFS_IOC_SUBVOL_CREATE, out) +func iocSubvolCreate(f *os.File, in *btrfs_ioctl_vol_args) error { + return ioctl.Do(f, _BTRFS_IOC_SUBVOL_CREATE, in) +} + +func iocSubvolCreateV2(f *os.File, in *btrfs_ioctl_vol_args_v2) error { + return ioctl.Do(f, _BTRFS_IOC_SUBVOL_CREATE, in) } -func iocSnapDestroy(f *os.File, out *btrfs_ioctl_vol_args) error { - return ioctl.Do(f, _BTRFS_IOC_SNAP_DESTROY, out) +func iocSnapDestroy(f *os.File, in *btrfs_ioctl_vol_args) error { + return ioctl.Do(f, _BTRFS_IOC_SNAP_DESTROY, in) } func iocDefragRange(f *os.File, out *btrfs_ioctl_defrag_range_args) error { diff --git a/receive.go b/receive.go new file mode 100644 index 0000000..3e2a74a --- /dev/null +++ b/receive.go @@ -0,0 +1,32 @@ +package btrfs + +import ( + "bytes" + "errors" + "io" + "os/exec" +) + +func Receive(r io.Reader, mount string) error { + // TODO: write a native implementation? + //tf, err := ioutil.TempFile("","btrfs_snap") + //if err != nil { + // return err + //} + //defer func(){ + // name := tf.Name() + // tf.Close() + // os.Remove(name) + //}() + buf := bytes.NewBuffer(nil) + cmd := exec.Command("btrfs", "receive", mount) + cmd.Stdin = r + cmd.Stderr = buf + if err := cmd.Run(); err != nil { + if buf.Len() != 0 { + return errors.New(buf.String()) + } + return err + } + return nil +} diff --git a/send.go b/send.go new file mode 100644 index 0000000..4c4b584 --- /dev/null +++ b/send.go @@ -0,0 +1,46 @@ +package btrfs + +import ( + "bytes" + "errors" + "io" + "io/ioutil" + "os" + "os/exec" +) + +func Send(w io.Writer, parent string, subvols ...string) error { + if len(subvols) == 0 { + return nil + } + // TODO: write a native implementation? + args := []string{ + "send", + } + if parent != "" { + args = append(args, "-p", parent) + } + tf, err := ioutil.TempFile("", "btrfs_snap") + if err != nil { + return err + } + defer func() { + name := tf.Name() + tf.Close() + os.Remove(name) + }() + args = append(args, "-f", tf.Name()) + buf := bytes.NewBuffer(nil) + args = append(args, subvols...) + cmd := exec.Command("btrfs", args...) + cmd.Stderr = buf + if err = cmd.Run(); err != nil { + if buf.Len() != 0 { + return errors.New(buf.String()) + } + return err + } + tf.Seek(0, 0) + _, err = io.Copy(w, tf) + return err +} diff --git a/subvolume.go b/subvolume.go index be7db56..42ee9c2 100644 --- a/subvolume.go +++ b/subvolume.go @@ -1,21 +1,141 @@ package btrfs import ( + "fmt" + "os" + "path/filepath" + "strings" "syscall" ) +func checkSubVolumeName(name string) bool { + return name != "" && name[0] != 0 && !strings.ContainsRune(name, '/') && + name != "." && name != ".." +} + func IsSubVolume(path string) (bool, error) { var st syscall.Stat_t if err := syscall.Stat(path, &st); err != nil { return false, err } - if st.Ino != BTRFS_FIRST_FREE_OBJECTID || + if st.Ino != firstFreeObjectid || st.Mode&syscall.S_IFMT != syscall.S_IFDIR { return false, nil } - var stfs syscall.Statfs_t - if err := syscall.Statfs(path, &stfs); err != nil { - return false, err + return isBtrfs(path) +} + +func CreateSubVolume(path string) error { + var inherit *btrfs_qgroup_inherit // TODO + + cpath, err := filepath.Abs(path) + if err != nil { + return err + } + newName := filepath.Base(cpath) + dstDir := filepath.Dir(cpath) + if !checkSubVolumeName(newName) { + return fmt.Errorf("invalid subvolume name: %s", newName) + } else if len(newName) >= volNameMax { + return fmt.Errorf("subvolume name too long: %s", newName) + } + dst, err := openDir(dstDir) + if err != nil { + return err + } + defer dst.Close() + if inherit != nil { + panic("not implemented") // TODO + args := btrfs_ioctl_vol_args_v2{ + flags: subvolQGroupInherit, + btrfs_ioctl_vol_args_v2_u1: btrfs_ioctl_vol_args_v2_u1{ + //size: qgroup_inherit_size(inherit), + qgroup_inherit: inherit, + }, + } + copy(args.name[:], newName) + return iocSubvolCreateV2(dst, &args) + } + var args btrfs_ioctl_vol_args + copy(args.name[:], newName) + return iocSubvolCreate(dst, &args) +} + +func DeleteSubVolume(path string) error { + if ok, err := IsSubVolume(path); err != nil { + return err + } else if !ok { + return fmt.Errorf("not a subvolume: %s", path) + } + cpath, err := filepath.Abs(path) + if err != nil { + return err + } + dname := filepath.Dir(cpath) + vname := filepath.Base(cpath) + + dir, err := openDir(dname) + if err != nil { + return err + } + defer dir.Close() + var args btrfs_ioctl_vol_args + copy(args.name[:], vname) + return iocSnapDestroy(dir, &args) +} + +func SnapshotSubVolume(subvol, dst string, ro bool) error { + if ok, err := IsSubVolume(subvol); err != nil { + return err + } else if !ok { + return fmt.Errorf("not a subvolume: %s", subvol) + } + exists := false + if st, err := os.Stat(dst); err != nil && !os.IsNotExist(err) { + return err + } else if err == nil { + if !st.IsDir() { + return fmt.Errorf("'%s' exists and it is not a directory", dst) + } + exists = true + } + var ( + newName string + dstDir string + ) + if exists { + newName = filepath.Base(subvol) + dstDir = dst + } else { + newName = filepath.Base(dst) + dstDir = filepath.Dir(dst) + } + if !checkSubVolumeName(newName) { + return fmt.Errorf("invalid snapshot name '%s'", newName) + } else if len(newName) >= volNameMax { + return fmt.Errorf("snapshot name too long '%s'", newName) + } + fdst, err := openDir(dstDir) + if err != nil { + return err + } + // TODO: make SnapshotSubVolume a method on FS to use existing fd + f, err := openDir(subvol) + if err != nil { + return err + } + args := btrfs_ioctl_vol_args_v2{ + fd: int64(f.Fd()), + } + if ro { + args.flags |= subvolReadOnly } - return stfs.Type == SuperMagic, nil + // TODO + //if inherit != nil { + // args.flags |= subvolQGroupInherit + // args.size = qgroup_inherit_size(inherit) + // args.qgroup_inherit = inherit + //} + copy(args.name[:], newName) + return iocSnapCreateV2(fdst, &args) } diff --git a/utils.go b/utils.go new file mode 100644 index 0000000..9a6fe0d --- /dev/null +++ b/utils.go @@ -0,0 +1,37 @@ +package btrfs + +import ( + "fmt" + "os" + "syscall" +) + +func isBtrfs(path string) (bool, error) { + var stfs syscall.Statfs_t + if err := syscall.Statfs(path, &stfs); err != nil { + return false, err + } + return stfs.Type == SuperMagic, nil +} + +// openDir does the following checks before calling Open: +// 1: path is in a btrfs filesystem +// 2: path is a directory +func openDir(path string) (*os.File, error) { + if ok, err := isBtrfs(path); err != nil { + return nil, err + } else if !ok { + return nil, ErrNotBtrfs{Path: path} + } + file, err := os.Open(path) + if err != nil { + return nil, err + } else if st, err := file.Stat(); err != nil { + file.Close() + return nil, err + } else if !st.IsDir() { + file.Close() + return nil, fmt.Errorf("not a directory: %s", path) + } + return file, nil +} From 4d60b726dffa5329011913dab0753e952b28e7ea Mon Sep 17 00:00:00 2001 From: Denys Smirnov Date: Fri, 16 Sep 2016 20:09:14 +0300 Subject: [PATCH 04/38] implement subvolumes list; regenerate headers; generate btrfs_tree.h (cherry picked from commit dennwc/btrfs@f03fa748e70cfb3681f803b369563b7886aa4448) Signed-off-by: Akihiro Suda --- btrfs_h.go | 2 + btrfs_tree.go | 54 +++ btrfs_tree_h.go | 745 ++++++++++++++++++++++++++++++++ btrfs_tree_hc.go | 30 +- cmd/gbtrfs.go | 18 + cmd/hgen.go | 9 +- ioctl_h.go | 74 ++-- ioctl_h_test.go => size_test.go | 4 + subvolume.go | 122 ++++++ utils.go | 33 ++ 10 files changed, 1047 insertions(+), 44 deletions(-) create mode 100644 btrfs_tree.go create mode 100644 btrfs_tree_h.go rename ioctl_h_test.go => size_test.go (95%) diff --git a/btrfs_h.go b/btrfs_h.go index 2206361..b1af5c4 100644 --- a/btrfs_h.go +++ b/btrfs_h.go @@ -2,6 +2,8 @@ package btrfs import "strings" +const maxUint64 = 1<<64 - 1 + const BTRFS_LABEL_SIZE = 256 type FeatureFlags uint64 diff --git a/btrfs_tree.go b/btrfs_tree.go new file mode 100644 index 0000000..a7e6f6b --- /dev/null +++ b/btrfs_tree.go @@ -0,0 +1,54 @@ +package btrfs + +import ( + "time" + "unsafe" +) + +const ( + _BTRFS_BLOCK_GROUP_PROFILE_MASK = (blockGroupRaid0 | + blockGroupRaid1 | + blockGroupRaid5 | + blockGroupRaid6 | + blockGroupDup | + blockGroupRaid10) +) + +type rootRef struct { + DirID uint64 + Sequence uint64 + Name string +} + +func (rootRef) btrfsSize() int { return 18 } + +func asUint64(p []byte) uint64 { + return *(*uint64)(unsafe.Pointer(&p[0])) +} + +func asUint32(p []byte) uint32 { + return *(*uint32)(unsafe.Pointer(&p[0])) +} + +func asUint16(p []byte) uint16 { + return *(*uint16)(unsafe.Pointer(&p[0])) +} + +func asTime(p []byte) time.Time { + sec, nsec := asUint64(p[0:]), asUint32(p[8:]) + return time.Unix(int64(sec), int64(nsec)) +} + +func asRootRef(p []byte) rootRef { + const sz = 18 + // assuming that it is highly unsafe to have sizeof(struct) > len(data) + // (*btrfs_root_ref)(unsafe.Pointer(&p[0])) and sizeof(btrfs_root_ref) == 24 + ref := rootRef{ + DirID: asUint64(p[0:]), + Sequence: asUint64(p[8:]), + } + if n := asUint16(p[16:]); n > 0 { + ref.Name = string(p[sz : sz+n : sz+n]) + } + return ref +} diff --git a/btrfs_tree_h.go b/btrfs_tree_h.go new file mode 100644 index 0000000..d5ebd66 --- /dev/null +++ b/btrfs_tree_h.go @@ -0,0 +1,745 @@ +package btrfs + +/* + * This header contains the structure definitions and constants used + * by file system objects that can be retrieved using + * the _BTRFS_IOC_SEARCH_TREE ioctl. That means basically anything that + * is needed to describe a leaf node's key or item contents. + */ + +/* holds pointers to all of the tree roots */ + +/* stores information about which extents are in use, and reference counts */ + +/* + * chunk tree stores translations from logical -> physical block numbering + * the super block points to the chunk tree + */ + +/* + * stores information about which areas of a given device are in use. + * one per device. The tree of tree roots points to the device tree + */ + +/* one per subvolume, storing files and directories */ + +/* directory objectid inside the root tree */ + +/* holds checksums of all the data extents */ + +/* holds quota configuration and tracking */ + +/* for storing items that use the _BTRFS_UUID_KEY* types */ + +/* tracks free space in block groups. */ + +/* device stats in the device tree */ + +/* for storing balance parameters in the root tree */ + +/* orhpan objectid for tracking unlinked/truncated files */ + +/* does write ahead logging to speed up fsyncs */ + +/* for space balancing */ + +/* + * extent checksums all have this objectid + * this allows them to share the logging tree + * for fsyncs + */ + +/* For storing free space cache */ + +/* + * The inode number assigned to the special inode for storing + * free ino cache + */ + +/* dummy objectid represents multiple objectids */ + +/* + * All files have objectids in this range. + */ + +/* + * the device items go into the chunk tree. The key is in the form + * [ 1 _BTRFS_DEV_ITEM_KEY device_id ] + */ + +/* + * inode items have the data typically returned from stat and store other + * info about object characteristics. There is one for every file and dir in + * the FS + */ + +/* reserve 2-15 close to the inode for later flexibility */ + +/* + * dir items are the name -> inode pointers in a directory. There is one + * for every name in a directory. + */ + +/* + * extent data is for file data + */ + +/* + * extent csums are stored in a separate tree and hold csums for + * an entire extent on disk. + */ + +/* + * root items point to tree roots. They are typically in the root + * tree used by the super block to find all the other trees + */ + +/* + * root backrefs tie subvols and snapshots to the directory entries that + * reference them + */ + +/* + * root refs make a fast index for listing all of the snapshots and + * subvolumes referenced by a given root. They point directly to the + * directory item in the root that references the subvol + */ + +/* + * extent items are in the extent map tree. These record which blocks + * are used, and how many references there are to each block + */ + +/* + * The same as the _BTRFS_EXTENT_ITEM_KEY, except it's metadata we already know + * the length, so we save the level in key->offset instead of the length. + */ + +/* + * block groups give us hints into the extent allocation trees. Which + * blocks are free etc etc + */ + +/* + * Every block group is represented in the free space tree by a free space info + * item, which stores some accounting information. It is keyed on + * (block_group_start, FREE_SPACE_INFO, block_group_length). + */ + +/* + * A free space extent tracks an extent of space that is free in a block group. + * It is keyed on (start, FREE_SPACE_EXTENT, length). + */ + +/* + * When a block group becomes very fragmented, we convert it to use bitmaps + * instead of extents. A free space bitmap is keyed on + * (start, FREE_SPACE_BITMAP, length); the corresponding item is a bitmap with + * (length / sectorsize) bits. + */ + +/* + * Records the overall state of the qgroups. + * There's only one instance of this key present, + * (0, _BTRFS_QGROUP_STATUS_KEY, 0) + */ + +/* + * Records the currently used space of the qgroup. + * One key per qgroup, (0, _BTRFS_QGROUP_INFO_KEY, qgroupid). + */ + +/* + * Contains the user configured limits for the qgroup. + * One key per qgroup, (0, _BTRFS_QGROUP_LIMIT_KEY, qgroupid). + */ + +/* + * Records the child-parent relationship of qgroups. For + * each relation, 2 keys are present: + * (childid, _BTRFS_QGROUP_RELATION_KEY, parentid) + * (parentid, _BTRFS_QGROUP_RELATION_KEY, childid) + */ + +/* + * Obsolete name, see _BTRFS_TEMPORARY_ITEM_KEY. + */ + +/* + * The key type for tree items that are stored persistently, but do not need to + * exist for extended period of time. The items can exist in any tree. + * + * [subtype, _BTRFS_TEMPORARY_ITEM_KEY, data] + * + * Existing items: + * + * - balance status item + * (_BTRFS_BALANCE_OBJECTID, _BTRFS_TEMPORARY_ITEM_KEY, 0) + */ + +/* + * Obsolete name, see _BTRFS_PERSISTENT_ITEM_KEY + */ + +/* + * The key type for tree items that are stored persistently and usually exist + * for a long period, eg. filesystem lifetime. The item kinds can be status + * information, stats or preference values. The item can exist in any tree. + * + * [subtype, _BTRFS_PERSISTENT_ITEM_KEY, data] + * + * Existing items: + * + * - device statistics, store IO stats in the device tree, one key for all + * stats + * (_BTRFS_DEV_STATS_OBJECTID, _BTRFS_DEV_STATS_KEY, 0) + */ + +/* + * Persistantly stores the device replace state in the device tree. + * The key is built like this: (0, _BTRFS_DEV_REPLACE_KEY, 0). + */ + +/* + * Stores items that allow to quickly map UUIDs to something else. + * These items are part of the filesystem UUID tree. + * The key is built like this: + * (UUID_upper_64_bits, _BTRFS_UUID_KEY*, UUID_lower_64_bits). + */ + +/* for UUIDs assigned to * received subvols */ + +/* + * string items are for debugging. They just store a short string of + * data in the FS + */ + +/* 32 bytes in various csum fields */ + +/* csum types */ + +/* + * flags definitions for directory entry item type + * + * Used by: + * struct btrfs_dir_item.type + */ + +/* + * The key defines the order in the tree, and so it also defines (optimal) + * block layout. + * + * objectid corresponds to the inode number. + * + * type tells us things about the object, and is a kind of stream selector. + * so for a given inode, keys with type of 1 might refer to the inode data, + * type of 2 may point to file data in the btree and type == 3 may point to + * extents. + * + * offset is the starting byte offset for this key in the stream. + * + * btrfs_disk_key is in disk byte order. struct btrfs_key is always + * in cpu native order. Otherwise they are identical and their sizes + * should be the same (ie both packed) + */ +type btrfs_disk_key struct { + objectid uint64 + type_ uint8 + offset uint64 +} + +type btrfs_key struct { + objectid uint64 + type_ uint8 + offset uint64 +} + +type btrfs_dev_item struct { + devid uint64 + total_bytes uint64 + bytes_used uint64 + io_align uint32 + io_width uint32 + sector_size uint32 + type_ uint64 + generation uint64 + start_offset uint64 + dev_group uint32 + seek_speed uint8 + bandwidth uint8 + uuid UUID + fsid FSID +} + +type btrfs_stripe struct { + devid uint64 + offset uint64 + dev_uuid UUID +} + +type btrfs_chunk struct { + length uint64 + owner uint64 + stripe_len uint64 + type_ uint64 + io_align uint32 + io_width uint32 + sector_size uint32 + num_stripes uint16 + sub_stripes uint16 + stripe struct { + devid uint64 + offset uint64 + dev_uuid UUID + } +} + +/* additional stripes go here */ +type btrfs_free_space_entry struct { + offset uint64 + bytes uint64 + type_ uint8 +} + +type btrfs_free_space_header struct { + location struct { + objectid uint64 + type_ uint8 + offset uint64 + } + generation uint64 + num_entries uint64 + num_bitmaps uint64 +} + +/* Super block flags */ +/* Errors detected */ + +/* + * items in the extent btree are used to record the objectid of the + * owner of the block and the number of references + */ +type btrfs_extent_item struct { + refs uint64 + generation uint64 + flags uint64 +} + +type btrfs_extent_item_v0 struct { + refs uint32 +} + +/* following flags only apply to tree blocks */ + +/* use full backrefs for extent pointers in the block */ + +/* + * this flag is only used internally by scrub and may be changed at any time + * it is only declared here to avoid collisions + */ +type btrfs_tree_block_info struct { + key struct { + objectid uint64 + type_ uint8 + offset uint64 + } + level uint8 +} + +type btrfs_extent_data_ref struct { + root uint64 + objectid uint64 + offset uint64 + count uint32 +} + +type btrfs_shared_data_ref struct { + count uint32 +} + +type btrfs_extent_inline_ref struct { + type_ uint8 + offset uint64 +} + +/* old style backrefs item */ +type btrfs_extent_ref_v0 struct { + root uint64 + generation uint64 + objectid uint64 + count uint32 +} + +/* dev extents record free space on individual devices. The owner + * field points back to the chunk allocation mapping tree that allocated + * the extent. The chunk tree uuid field is a way to double check the owner + */ +type btrfs_dev_extent struct { + chunk_tree uint64 + chunk_objectid uint64 + chunk_offset uint64 + length uint64 + chunk_tree_uuid UUID +} + +type btrfs_inode_ref struct { + index uint64 + name_len uint16 +} + +/* name goes here */ +type btrfs_inode_extref struct { + parent_objectid uint64 + index uint64 + name_len uint16 + //name [0]uint8 +} + +/* name goes here */ +type btrfs_timespec struct { + sec uint64 + nsec uint32 +} + +type btrfs_inode_item struct { + generation uint64 + transid uint64 + size uint64 + nbytes uint64 + block_group uint64 + nlink uint32 + uid uint32 + gid uint32 + mode uint32 + rdev uint64 + flags uint64 + sequence uint64 + reserved [4]uint64 + atime struct { + sec uint64 + nsec uint32 + } + ctime struct { + sec uint64 + nsec uint32 + } + mtime struct { + sec uint64 + nsec uint32 + } + otime struct { + sec uint64 + nsec uint32 + } +} + +type btrfs_dir_log_item struct { + end uint64 +} + +type btrfs_dir_item struct { + location struct { + objectid uint64 + type_ uint8 + offset uint64 + } + transid uint64 + data_len uint16 + name_len uint16 + type_ uint8 +} + +/* + * Internal in-memory flag that a subvolume has been marked for deletion but + * still visible as a directory + */ +type btrfs_root_item struct { + inode struct { + generation uint64 + transid uint64 + size uint64 + nbytes uint64 + block_group uint64 + nlink uint32 + uid uint32 + gid uint32 + mode uint32 + rdev uint64 + flags uint64 + sequence uint64 + reserved [4]uint64 + atime struct { + sec uint64 + nsec uint32 + } + ctime struct { + sec uint64 + nsec uint32 + } + mtime struct { + sec uint64 + nsec uint32 + } + otime struct { + sec uint64 + nsec uint32 + } + } + generation uint64 + root_dirid uint64 + bytenr uint64 + byte_limit uint64 + bytes_used uint64 + last_snapshot uint64 + flags uint64 + refs uint32 + drop_progress struct { + objectid uint64 + type_ uint8 + offset uint64 + } + drop_level uint8 + level uint8 + generation_v2 uint64 + uuid UUID + parent_uuid UUID + received_uuid UUID + ctransid uint64 + otransid uint64 + stransid uint64 + rtransid uint64 + ctime struct { + sec uint64 + nsec uint32 + } + otime struct { + sec uint64 + nsec uint32 + } + stime struct { + sec uint64 + nsec uint32 + } + rtime struct { + sec uint64 + nsec uint32 + } + reserved [8]uint64 +} + +/* + * this is used for both forward and backward root refs + */ +type btrfs_root_ref struct { + dirid uint64 + sequence uint64 + name_len uint16 +} + +type btrfs_disk_balance_args struct { + profiles uint64 + usage uint64 + usage_min uint32 + usage_max uint32 + devid uint64 + pstart uint64 + pend uint64 + vstart uint64 + vend uint64 + target uint64 + flags uint64 + limit uint64 + limit_min uint32 + limit_max uint32 + stripes_min uint32 + stripes_max uint32 + unused [6]uint64 +} + +/* + * store balance parameters to disk so that balance can be properly + * resumed after crash or unmount + */ +type btrfs_balance_item struct { + flags uint64 + data struct { + profiles uint64 + usage uint64 + usage_min uint32 + usage_max uint32 + devid uint64 + pstart uint64 + pend uint64 + vstart uint64 + vend uint64 + target uint64 + flags uint64 + limit uint64 + limit_min uint32 + limit_max uint32 + stripes_min uint32 + stripes_max uint32 + unused [6]uint64 + } + meta struct { + profiles uint64 + usage uint64 + usage_min uint32 + usage_max uint32 + devid uint64 + pstart uint64 + pend uint64 + vstart uint64 + vend uint64 + target uint64 + flags uint64 + limit uint64 + limit_min uint32 + limit_max uint32 + stripes_min uint32 + stripes_max uint32 + unused [6]uint64 + } + sys struct { + profiles uint64 + usage uint64 + usage_min uint32 + usage_max uint32 + devid uint64 + pstart uint64 + pend uint64 + vstart uint64 + vend uint64 + target uint64 + flags uint64 + limit uint64 + limit_min uint32 + limit_max uint32 + stripes_min uint32 + stripes_max uint32 + unused [6]uint64 + } + unused [4]uint64 +} + +type btrfs_file_extent_item struct { + generation uint64 + ram_bytes uint64 + compression uint8 + encryption uint8 + other_encoding uint16 + type_ uint8 + disk_bytenr uint64 + disk_num_bytes uint64 + offset uint64 + num_bytes uint64 +} + +type btrfs_csum_item struct { + csum uint8 +} + +type btrfs_dev_stats_item struct { + values [_BTRFS_DEV_STAT_VALUES_MAX]uint64 +} + +type btrfs_dev_replace_item struct { + src_devid uint64 + cursor_left uint64 + cursor_right uint64 + cont_reading_from_srcdev_mode uint64 + replace_state uint64 + time_started uint64 + time_stopped uint64 + num_write_errors uint64 + num_uncorrectable_read_errors uint64 +} + +/* different types of block groups (and chunks) */ +const ( + _BTRFS_RAID_RAID10 = iota + _BTRFS_RAID_RAID1 + _BTRFS_RAID_DUP + _BTRFS_RAID_RAID0 + _BTRFS_RAID_SINGLE + _BTRFS_RAID_RAID5 + _BTRFS_RAID_RAID6 + _BTRFS_NR_RAID_TYPES +) + +/* + * We need a bit for restriper to be able to tell when chunks of type + * SINGLE are available. This "extended" profile format is used in + * fs_info->avail_*_alloc_bits (in-memory) and balance item fields + * (on-disk). The corresponding on-disk bit in chunk.type is reserved + * to avoid remappings between two formats in future. + */ + +/* + * A fake block group type that is used to communicate global block reserve + * size to userspace via the SPACE_INFO ioctl. + */ +func chunk_to_extended(flags uint64) uint64 { + if flags&uint64(_BTRFS_BLOCK_GROUP_PROFILE_MASK) == 0 { + flags |= uint64(availAllocBitSingle) + } + + return flags +} + +func extended_to_chunk(flags uint64) uint64 { + return flags &^ uint64(availAllocBitSingle) +} + +type btrfs_block_group_item struct { + used uint64 + chunk_objectid uint64 + flags uint64 +} + +type btrfs_free_space_info struct { + extent_count uint32 + flags uint32 +} + +func btrfs_qgroup_level(qgroupid uint64) uint64 { + return qgroupid >> uint32(qgroupLevelShift) +} + +/* + * is subvolume quota turned on? + */ + +/* + * RESCAN is set during the initialization phase + */ + +/* + * Some qgroup entries are known to be out of date, + * either because the configuration has changed in a way that + * makes a rescan necessary, or because the fs has been mounted + * with a non-qgroup-aware version. + * Turning qouta off and on again makes it inconsistent, too. + */ +type btrfs_qgroup_status_item struct { + version uint64 + generation uint64 + flags uint64 + rescan uint64 +} + +type btrfs_qgroup_info_item struct { + generation uint64 + rfer uint64 + rfer_cmpr uint64 + excl uint64 + excl_cmpr uint64 +} + +type btrfs_qgroup_limit_item struct { + flags uint64 + max_rfer uint64 + max_excl uint64 + rsv_rfer uint64 + rsv_excl uint64 +} diff --git a/btrfs_tree_hc.go b/btrfs_tree_hc.go index 5b74214..75c392c 100644 --- a/btrfs_tree_hc.go +++ b/btrfs_tree_hc.go @@ -44,37 +44,37 @@ const ( devStatsObjectid = 0 // For storing balance parameters in the root tree - balanceObjectid = 0xfffffffffffffffc /* -4 */ + balanceObjectid = (1<<64 - 4) // Orhpan objectid for tracking unlinked/truncated files - orphanObjectid = 0xfffffffffffffffb /* -5 */ + orphanObjectid = (1<<64 - 5) // Does write ahead logging to speed up fsyncs - treeLogObjectid = 0xfffffffffffffffa /* -6 */ - treeLogFixupObjectid = 0xfffffffffffffff9 /* -7 */ + treeLogObjectid = (1<<64 - 6) + treeLogFixupObjectid = (1<<64 - 7) // For space balancing - treeRelocObjectid = 0xfffffffffffffff8 /* -8 */ - dataRelocTreeObjectid = 0xfffffffffffffff7 /* -9 */ + treeRelocObjectid = (1<<64 - 8) + dataRelocTreeObjectid = (1<<64 - 9) // Extent checksums all have this objectid // this allows them to share the logging tree // for fsyncs - extentCsumObjectid = 0xfffffffffffffff6 /* -10 */ + extentCsumObjectid = (1<<64 - 10) // For storing free space cache - freeSpaceObjectid = 0xfffffffffffffff5 /* -11 */ + freeSpaceObjectid = (1<<64 - 11) // The inode number assigned to the special inode for storing // free ino cache - freeInoObjectid = 0xfffffffffffffff4 /* -12 */ + freeInoObjectid = (1<<64 - 12) // Dummy objectid represents multiple objectids - multipleObjectids = 0xffffffffffffff01 /* -255 */ + multipleObjectids = (1<<64 - 255) // All files have objectids in this range. firstFreeObjectid = 256 - lastFreeObjectid = 0xffffffffffffff00 /* -256 */ + lastFreeObjectid = (1<<64 - 256) firstChunkTreeObjectid = 256 // The device items go into the chunk tree. The key is in the form @@ -135,6 +135,8 @@ const ( extentDataRefKey = 178 + extentRefV0Key = 180 + sharedBlockRefKey = 182 sharedDataRefKey = 184 @@ -221,6 +223,7 @@ const ( csumSize = 32 // Csum types + csumTypeCrc32 = 0 // Flags definitions for directory entry item type // Used by: @@ -437,7 +440,12 @@ const ( blockGroupData = (1 << 0) blockGroupSystem = (1 << 1) blockGroupMetadata = (1 << 2) + blockGroupRaid0 = (1 << 3) + blockGroupRaid1 = (1 << 4) blockGroupDup = (1 << 5) + blockGroupRaid10 = (1 << 6) + blockGroupRaid5 = (1 << 7) + blockGroupRaid6 = (1 << 8) // We need a bit for restriper to be able to tell when chunks of type // SINGLE are available. This "extended" profile format is used in diff --git a/cmd/gbtrfs.go b/cmd/gbtrfs.go index 2389992..3c42a77 100644 --- a/cmd/gbtrfs.go +++ b/cmd/gbtrfs.go @@ -17,6 +17,7 @@ func init() { SubvolumeCmd.AddCommand( SubvolumeCreateCmd, SubvolumeDeleteCmd, + SubvolumeListCmd, ) } @@ -62,6 +63,23 @@ operation is safely stored on the media.`, }, } +var SubvolumeListCmd = &cobra.Command{ + Use: "list ", + Short: "List subvolumes", + RunE: func(cmd *cobra.Command, args []string) error { + if len(args) != 1 { + return fmt.Errorf("expected one destination argument") + } + list, err := btrfs.ListSubVolumes(args[0]) + if err == nil { + for _, v := range list { + fmt.Printf("%+v\n", v) + } + } + return err + }, +} + var SendCmd = &cobra.Command{ Use: "send [-ve] [-p ] [-c ] [-f ] [...]", Short: "Send the subvolume(s) to stdout.", diff --git a/cmd/hgen.go b/cmd/hgen.go index 02616a4..89d2b61 100644 --- a/cmd/hgen.go +++ b/cmd/hgen.go @@ -9,7 +9,6 @@ import ( "log" "os" "regexp" - "strconv" "strings" "unicode" ) @@ -23,7 +22,7 @@ var ( ) var ( - reDefineIntConst = regexp.MustCompile(`#define\s+([A-Za-z_]+)\s+(\(?-?\d+(?:U?LL)?(?:\s*<<\s*\d+)?\)?)`) + reDefineIntConst = regexp.MustCompile(`#define\s+([A-Za-z_][A-Za-z\d_]*)\s+(\(?-?\d+(?:U?LL)?(?:\s*<<\s*\d+)?\)?)`) reNegULL = regexp.MustCompile(`-(\d+)ULL`) ) @@ -126,11 +125,7 @@ func process(w io.Writer, path string) error { name, val := sub[1], sub[2] if sub := reNegULL.FindAllStringSubmatch(val, -1); len(sub) > 0 { for _, s := range sub { - v, err := strconv.ParseInt(s[1], 10, 64) - if err != nil { - panic(err) - } - val = strings.Replace(val, s[0], fmt.Sprintf("0x%x /* -%s */", uint64(-v), s[1]), -1) + val = strings.Replace(val, s[0], fmt.Sprintf("(1<<64 - %s)", s[1]), -1) } } val = strings.Replace(val, "ULL", "", -1) diff --git a/ioctl_h.go b/ioctl_h.go index 386246d..1c73d5f 100644 --- a/ioctl_h.go +++ b/ioctl_h.go @@ -19,9 +19,31 @@ const ( UUIDSize = 16 ) +var zeroUUID UUID + type UUID [UUIDSize]byte -func (id UUID) String() string { return hex.EncodeToString(id[:]) } +func (id UUID) String() string { + if id == zeroUUID { + return "" + } + buf := make([]byte, UUIDSize*2+4) + i := 0 + i += hex.Encode(buf[i:], id[:4]) + buf[i] = '-' + i++ + i += hex.Encode(buf[i:], id[4:6]) + buf[i] = '-' + i++ + i += hex.Encode(buf[i:], id[6:8]) + buf[i] = '-' + i++ + i += hex.Encode(buf[i:], id[8:10]) + buf[i] = '-' + i++ + i += hex.Encode(buf[i:], id[10:]) + return string(buf) +} type FSID [FSIDSize]byte @@ -121,7 +143,7 @@ type btrfs_ioctl_scrub_args struct { flags uint64 // in progress btrfs_scrub_progress // out // pad to 1k - unused [1024 - 4*8 - unsafe.Sizeof(btrfs_scrub_progress{})]byte + _ [1024 - 4*8 - unsafe.Sizeof(btrfs_scrub_progress{})]byte } type contReadingFromSrcdevMode uint64 @@ -168,7 +190,7 @@ type btrfs_ioctl_dev_replace_args_u2 struct { cmd uint64 // in result uint64 // out status btrfs_ioctl_dev_replace_status_params // out - unused [unsafe.Sizeof(btrfs_ioctl_dev_replace_start_params{}) - unsafe.Sizeof(btrfs_ioctl_dev_replace_status_params{})]byte + _ [unsafe.Sizeof(btrfs_ioctl_dev_replace_start_params{}) - unsafe.Sizeof(btrfs_ioctl_dev_replace_status_params{})]byte spare [64]uint64 } @@ -177,7 +199,7 @@ type btrfs_ioctl_dev_info_args struct { uuid UUID // in/out bytes_used uint64 // out total_bytes uint64 // out - unused [379]uint64 // pad to 4k + _ [379]uint64 // pad to 4k path [devicePathNameMax]byte // out } @@ -188,7 +210,7 @@ type btrfs_ioctl_fs_info_args struct { nodesize uint32 // out sectorsize uint32 // out clone_alignment uint32 // out - reserved [122*8 + 4]byte // pad to 1k + _ [122*8 + 4]byte // pad to 1k } type btrfs_ioctl_feature_flags struct { @@ -231,7 +253,7 @@ type btrfs_balance_args struct { limit argRange stripes_min uint32 stripes_max uint32 - unused [48]byte + _ [48]byte } // report balance progress to userspace @@ -250,13 +272,13 @@ const ( ) type btrfs_ioctl_balance_args struct { - flags uint64 // in/out - state balanceState // out - data btrfs_balance_args // in/out - meta btrfs_balance_args // in/out - sys btrfs_balance_args // in/out - stat btrfs_balance_progress // out - unused [72 * 8]byte // pad to 1k + flags uint64 // in/out + state balanceState // out + data btrfs_balance_args // in/out + meta btrfs_balance_args // in/out + sys btrfs_balance_args // in/out + stat btrfs_balance_progress // out + _ [72 * 8]byte // pad to 1k } const _BTRFS_INO_LOOKUP_PATH_MAX = 4080 @@ -283,7 +305,7 @@ type btrfs_ioctl_search_key struct { max_type uint32 // how many items did userland ask for, and how many are we returning nr_items uint32 - unused [36]byte + _ [36]byte } type btrfs_ioctl_search_header struct { @@ -347,7 +369,7 @@ type btrfs_ioctl_same_args struct { logical_offset uint64 // in - start of extent in source length uint64 // in - length of extent dest_count uint16 // in - total elements in info array - reserved [6]byte + _ [6]byte //info [0]btrfs_ioctl_same_extent_info } @@ -364,7 +386,7 @@ type btrfs_ioctl_defrag_range_args struct { // which compression method to use if turning on compression // for this defrag operation. If unspecified, zlib will be used compress_type uint32 - unused [16]byte // spare for later + _ [16]byte // spare for later } type btrfs_ioctl_space_info struct { @@ -388,17 +410,17 @@ type btrfs_data_container struct { } type btrfs_ioctl_ino_path_args struct { - inum uint64 // in - size uint64 // in - reserved [32]byte + inum uint64 // in + size uint64 // in + _ [32]byte // struct btrfs_data_container *fspath; out fspath uint64 // out } type btrfs_ioctl_logical_ino_args struct { - logical uint64 // in - size uint64 // in - reserved [32]byte + logical uint64 // in + size uint64 // in + _ [32]byte // struct btrfs_data_container *inodes; out inodes uint64 } @@ -428,7 +450,7 @@ type btrfs_ioctl_get_dev_stats struct { nr_items uint64 // in/out flags uint64 // in/out values [_BTRFS_DEV_STAT_VALUES_MAX]uint64 // out values - unused [128 - 2 - _BTRFS_DEV_STAT_VALUES_MAX]uint64 // pad to 1k + _ [128 - 2 - _BTRFS_DEV_STAT_VALUES_MAX]uint64 // pad to 1k } const ( @@ -445,7 +467,7 @@ type btrfs_ioctl_quota_ctl_args struct { type btrfs_ioctl_quota_rescan_args struct { flags uint64 progress uint64 - reserved [6]uint64 + _ [6]uint64 } type btrfs_ioctl_qgroup_assign_args struct { @@ -471,7 +493,7 @@ type btrfs_ioctl_received_subvol_args struct { stime btrfs_ioctl_timespec // in rtime btrfs_ioctl_timespec // out flags uint64 // in - reserved [16]uint64 // in + _ [16]uint64 // in } const ( @@ -498,7 +520,7 @@ type btrfs_ioctl_send_args struct { clone_sources *uint64 // in parent_root uint64 // in flags uint64 // in - reserved [4]uint64 // in + _ [4]uint64 // in } var ( diff --git a/ioctl_h_test.go b/size_test.go similarity index 95% rename from ioctl_h_test.go rename to size_test.go index f8f0582..edc114a 100644 --- a/ioctl_h_test.go +++ b/size_test.go @@ -47,6 +47,10 @@ var caseSizes = []struct { {obj: btrfs_ioctl_timespec{}, size: 16}, {obj: btrfs_ioctl_received_subvol_args{}, size: 200}, {obj: btrfs_ioctl_send_args{}, size: 72}, + + //{obj:btrfs_root_ref{},size:18}, + //{obj:btrfs_root_item{},size:439}, + //{obj:btrfs_inode_item{},size:160}, } func TestSizes(t *testing.T) { diff --git a/subvolume.go b/subvolume.go index 42ee9c2..e860830 100644 --- a/subvolume.go +++ b/subvolume.go @@ -6,6 +6,7 @@ import ( "path/filepath" "strings" "syscall" + "time" ) func checkSubVolumeName(name string) bool { @@ -119,11 +120,13 @@ func SnapshotSubVolume(subvol, dst string, ro bool) error { if err != nil { return err } + defer fdst.Close() // TODO: make SnapshotSubVolume a method on FS to use existing fd f, err := openDir(subvol) if err != nil { return err } + defer f.Close() args := btrfs_ioctl_vol_args_v2{ fd: int64(f.Fd()), } @@ -139,3 +142,122 @@ func SnapshotSubVolume(subvol, dst string, ro bool) error { copy(args.name[:], newName) return iocSnapCreateV2(fdst, &args) } + +func ListSubVolumes(path string) ([]Subvolume, error) { + f, err := openDir(path) + if err != nil { + return nil, err + } + defer f.Close() + //root, err := getPathRootID(f) + //if err != nil { + // return nil, fmt.Errorf("can't get rootid for '%s': %v", path, err) + //} + m, err := listSubVolumes(f) + if err != nil { + return nil, err + } + out := make([]Subvolume, 0, len(m)) + for _, v := range m { + out = append(out, v) + } + return out, nil +} + +type Subvolume struct { + ObjectID uint64 + TransID uint64 + Name string + RefTree uint64 + DirID uint64 + Gen uint64 + OGen uint64 + Flags uint64 + UUID UUID + ParentUUID UUID + ReceivedUUID UUID + OTime time.Time + CTime time.Time +} + +func listSubVolumes(f *os.File) (map[uint64]Subvolume, error) { + sk := btrfs_ioctl_search_key{ + // search in the tree of tree roots + tree_id: 1, + + // Set the min and max to backref keys. The search will + // only send back this type of key now. + min_type: rootBackrefKey, + max_type: rootBackrefKey, + + min_objectid: firstFreeObjectid, + + // Set all the other params to the max, we'll take any objectid + // and any trans. + max_objectid: lastFreeObjectid, + max_offset: maxUint64, + max_transid: maxUint64, + + nr_items: 4096, // just a big number, doesn't matter much + } + m := make(map[uint64]Subvolume) + for { + out, err := treeSearchRaw(f, sk) + if err != nil { + return nil, err + } else if len(out) == 0 { + break + } + for _, obj := range out { + switch obj.Type { + case rootBackrefKey: + ref := asRootRef(obj.Data) + o := m[obj.ObjectID] + o.TransID = obj.TransID + o.ObjectID = obj.ObjectID + o.RefTree = obj.Offset + o.DirID = ref.DirID + o.Name = ref.Name + m[obj.ObjectID] = o + case rootItemKey: + o := m[obj.ObjectID] + o.TransID = obj.TransID + o.ObjectID = obj.ObjectID + // TODO: decode whole object? + o.Gen = asUint64(obj.Data[160:]) // size of btrfs_inode_item + o.Flags = asUint64(obj.Data[160+6*8:]) + const sz = 439 + const toff = sz - 8*8 - 4*12 + o.CTime = asTime(obj.Data[toff+0*12:]) + o.OTime = asTime(obj.Data[toff+1*12:]) + o.OGen = asUint64(obj.Data[toff-3*8:]) + const uoff = toff - 4*8 - 3*UUIDSize + copy(o.UUID[:], obj.Data[uoff+0*UUIDSize:]) + copy(o.ParentUUID[:], obj.Data[uoff+1*UUIDSize:]) + copy(o.ReceivedUUID[:], obj.Data[uoff+2*UUIDSize:]) + m[obj.ObjectID] = o + } + } + // record the mins in key so we can make sure the + // next search doesn't repeat this root + last := out[len(out)-1] + sk.min_objectid = last.ObjectID + sk.min_type = last.Type + sk.min_offset = last.Offset + 1 + if sk.min_offset == 0 { // overflow + sk.min_type++ + } else { + continue + } + if sk.min_type > rootBackrefKey { + sk.min_type = rootItemKey + sk.min_objectid++ + } else { + continue + } + if sk.min_objectid > sk.max_objectid { + break + } + } + return m, nil +} diff --git a/utils.go b/utils.go index 9a6fe0d..6071f85 100644 --- a/utils.go +++ b/utils.go @@ -4,6 +4,7 @@ import ( "fmt" "os" "syscall" + "unsafe" ) func isBtrfs(path string) (bool, error) { @@ -35,3 +36,35 @@ func openDir(path string) (*os.File, error) { } return file, nil } + +type rawItem struct { + TransID uint64 + ObjectID uint64 + Type uint32 + Offset uint64 + Data []byte +} + +func treeSearchRaw(f *os.File, key btrfs_ioctl_search_key) (out []rawItem, _ error) { + args := btrfs_ioctl_search_args{ + key: key, + } + if err := iocTreeSearch(f, &args); err != nil { + return nil, err + } + out = make([]rawItem, 0, args.key.nr_items) + buf := args.buf[:] + for i := 0; i < int(args.key.nr_items); i++ { + h := (*btrfs_ioctl_search_header)(unsafe.Pointer(&buf[0])) + buf = buf[unsafe.Sizeof(btrfs_ioctl_search_header{}):] + out = append(out, rawItem{ + TransID: h.transid, + ObjectID: h.objectid, + Type: h.typ, + Offset: h.offset, + Data: buf[:h.len], // TODO: reallocate? + }) + buf = buf[h.len:] + } + return out, nil +} From 9e60e558a775fba672239e9203997ced1d40428e Mon Sep 17 00:00:00 2001 From: Denys Smirnov Date: Mon, 19 Sep 2016 20:01:22 +0300 Subject: [PATCH 05/38] native Send implementation (cherry picked from commit dennwc/btrfs@6a4b966441d9dd44c811e68b30a503db54f8c03a) Signed-off-by: Akihiro Suda --- btrfs.go | 17 ++++++- cmd/gbtrfs.go | 15 ++++-- ioctl_h.go | 39 +++++++++++---- send.go | 128 ++++++++++++++++++++++++++++++++++++++++---------- subvolume.go | 2 +- utils.go | 74 +++++++++++++++++++++++++++++ 6 files changed, 233 insertions(+), 42 deletions(-) diff --git a/btrfs.go b/btrfs.go index 478b43a..0100677 100644 --- a/btrfs.go +++ b/btrfs.go @@ -6,17 +6,26 @@ import ( "io" "os" "path/filepath" + "syscall" ) const SuperMagic = 0x9123683E -func Open(path string) (*FS, error) { +func Open(path string, ro bool) (*FS, error) { if ok, err := IsSubVolume(path); err != nil { return nil, err } else if !ok { return nil, ErrNotBtrfs{Path: path} } - dir, err := os.Open(path) + var ( + dir *os.File + err error + ) + if ro { + dir, err = os.OpenFile(path, os.O_RDONLY|syscall.O_NOATIME, 0644) + } else { + dir, err = os.Open(path) + } if err != nil { return nil, err } else if st, err := dir.Stat(); err != nil { @@ -139,6 +148,10 @@ func (f *FS) GetSupportedFeatures() (out FSFeatureFlags, err error) { return } +func (f *FS) GetFlags() (SubvolFlags, error) { + return iocSubvolGetflags(f.f) +} + func (f *FS) Sync() (err error) { if err = ioctl.Do(f.f, _BTRFS_IOC_START_SYNC, nil); err != nil { return diff --git a/cmd/gbtrfs.go b/cmd/gbtrfs.go index 3c42a77..dee48f0 100644 --- a/cmd/gbtrfs.go +++ b/cmd/gbtrfs.go @@ -19,6 +19,8 @@ func init() { SubvolumeDeleteCmd, SubvolumeListCmd, ) + + SendCmd.Flags().StringP("parent", "p", "", "Send an incremental stream from to .") } var RootCmd = &cobra.Command{ @@ -27,7 +29,8 @@ var RootCmd = &cobra.Command{ } var SubvolumeCmd = &cobra.Command{ - Use: "subvolume ", + Use: "subvolume ", + Aliases: []string{"subvol", "sub", "sv"}, } var SubvolumeCreateCmd = &cobra.Command{ @@ -64,8 +67,9 @@ operation is safely stored on the media.`, } var SubvolumeListCmd = &cobra.Command{ - Use: "list ", - Short: "List subvolumes", + Use: "list ", + Short: "List subvolumes", + Aliases: []string{"ls"}, RunE: func(cmd *cobra.Command, args []string) error { if len(args) != 1 { return fmt.Errorf("expected one destination argument") @@ -81,12 +85,13 @@ var SubvolumeListCmd = &cobra.Command{ } var SendCmd = &cobra.Command{ - Use: "send [-ve] [-p ] [-c ] [-f ] [...]", + Use: "send [-v] [-p ] [-c ] [-f ] [...]", Short: "Send the subvolume(s) to stdout.", Long: `Sends the subvolume(s) specified by to stdout. should be read-only here.`, RunE: func(cmd *cobra.Command, args []string) error { - return btrfs.Send(os.Stdout, "", args...) + parent, _ := cmd.Flags().GetString("parent") + return btrfs.Send(os.Stdout, parent, args...) }, } diff --git a/ioctl_h.go b/ioctl_h.go index 1c73d5f..a0fcb98 100644 --- a/ioctl_h.go +++ b/ioctl_h.go @@ -5,6 +5,8 @@ import ( "encoding/hex" "github.com/dennwc/btrfs/ioctl" "os" + "strconv" + "strings" "unsafe" ) @@ -86,7 +88,25 @@ type btrfs_ioctl_vol_args_v2_u1 struct { const subvolNameMax = 4039 -type subvolFlags uint64 +type SubvolFlags uint64 + +func (f SubvolFlags) ReadOnly() bool { + return f&SubvolReadOnly != 0 +} +func (f SubvolFlags) String() string { + if f == 0 { + return "" + } + var out []string + if f&SubvolReadOnly != 0 { + out = append(out, "RO") + f = f & (^SubvolReadOnly) + } + if f != 0 { + out = append(out, "0x"+strconv.FormatInt(int64(f), 16)) + } + return strings.Join(out, "|") +} // flags for subvolumes // @@ -97,15 +117,15 @@ type subvolFlags uint64 // - BTRFS_IOC_SUBVOL_GETFLAGS // - BTRFS_IOC_SUBVOL_SETFLAGS const ( - subvolCreateAsync = subvolFlags(1 << 0) - subvolReadOnly = subvolFlags(1 << 1) - subvolQGroupInherit = subvolFlags(1 << 2) + subvolCreateAsync = SubvolFlags(1 << 0) + SubvolReadOnly = SubvolFlags(1 << 1) + subvolQGroupInherit = SubvolFlags(1 << 2) ) type btrfs_ioctl_vol_args_v2 struct { fd int64 transid uint64 - flags subvolFlags + flags SubvolFlags btrfs_ioctl_vol_args_v2_u1 unused [2]uint64 name [subvolNameMax + 1]byte @@ -670,8 +690,9 @@ func iocWaitSync(f *os.File, out *uint64) error { return ioctl.Do(f, _BTRFS_IOC_WAIT_SYNC, out) } -func iocSubvolGetflags(f *os.File, out *uint64) error { - return ioctl.Do(f, _BTRFS_IOC_SUBVOL_GETFLAGS, out) +func iocSubvolGetflags(f *os.File) (out SubvolFlags, err error) { + err = ioctl.Do(f, _BTRFS_IOC_SUBVOL_GETFLAGS, &out) + return } func iocSubvolSetflags(f *os.File, out *uint64) error { @@ -714,8 +735,8 @@ func iocSetReceivedSubvol(f *os.File, out *btrfs_ioctl_received_subvol_args) err return ioctl.Do(f, _BTRFS_IOC_SET_RECEIVED_SUBVOL, out) } -func iocSend(f *os.File, out *btrfs_ioctl_send_args) error { - return ioctl.Do(f, _BTRFS_IOC_SEND, out) +func iocSend(f *os.File, in *btrfs_ioctl_send_args) error { + return ioctl.Do(f, _BTRFS_IOC_SEND, in) } func iocDevicesReady(f *os.File, out *btrfs_ioctl_vol_args) error { diff --git a/send.go b/send.go index 4c4b584..bfd6046 100644 --- a/send.go +++ b/send.go @@ -1,46 +1,124 @@ package btrfs import ( - "bytes" - "errors" + "fmt" "io" - "io/ioutil" "os" - "os/exec" + "path/filepath" ) func Send(w io.Writer, parent string, subvols ...string) error { if len(subvols) == 0 { return nil } - // TODO: write a native implementation? - args := []string{ - "send", + // use first send subvol to determine mount_root + subvol, err := filepath.Abs(subvols[0]) + if err != nil { + return err + } + mountRoot, err := findMountRoot(subvol) + if err == os.ErrNotExist { + return fmt.Errorf("cannot find a mountpoint for %s", subvol) + } else if err != nil { + return err } + var ( + cloneSrc []uint64 + parentID uint64 + ) if parent != "" { - args = append(args, "-p", parent) + parent, err = filepath.Abs(parent) + if err != nil { + return err + } + f, err := os.Open(parent) + if err != nil { + return fmt.Errorf("cannot open parent: %v", err) + } + id, err := getPathRootID(f) + f.Close() + if err != nil { + return fmt.Errorf("cannot get parent root id: %v", err) + } + parentID = id + cloneSrc = append(cloneSrc, id) + } + // check all subvolumes + paths := make([]string, 0, len(subvols)) + for _, sub := range subvols { + sub, err = filepath.Abs(sub) + if err != nil { + return err + } + paths = append(paths, sub) + mount, err := findMountRoot(sub) + if err != nil { + return err + } else if mount != mountRoot { + return fmt.Errorf("all subvolumes must be from the same filesystem (%s is not)", sub) + } + ok, err := IsReadOnly(sub) + if err != nil { + return err + } else if !ok { + return fmt.Errorf("subvolume %s is not read-only", sub) + } } - tf, err := ioutil.TempFile("", "btrfs_snap") + //full := len(cloneSrc) == 0 + for i, sub := range paths { + //if len(cloneSrc) > 1 { + // // TODO: find_good_parent + //} + //if !full { // TODO + // cloneSrc = append(cloneSrc, ) + //} + fs, err := Open(sub, true) + if err != nil { + return err + } + var flags uint64 + if i != 0 { // not first + flags |= _BTRFS_SEND_FLAG_OMIT_STREAM_HEADER + } + if i < len(paths)-1 { // not last + flags |= _BTRFS_SEND_FLAG_OMIT_END_CMD + } + err = send(w, fs.f, parentID, cloneSrc, flags) + if err != nil { + return fmt.Errorf("error sending %s: %v", sub, err) + } + } + return nil +} + +func send(w io.Writer, subvol *os.File, parent uint64, sources []uint64, flags uint64) error { + pr, pw, err := os.Pipe() if err != nil { return err } - defer func() { - name := tf.Name() - tf.Close() - os.Remove(name) + errc := make(chan error, 1) + go func() { + defer pr.Close() + _, err := io.Copy(w, pr) + errc <- err }() - args = append(args, "-f", tf.Name()) - buf := bytes.NewBuffer(nil) - args = append(args, subvols...) - cmd := exec.Command("btrfs", args...) - cmd.Stderr = buf - if err = cmd.Run(); err != nil { - if buf.Len() != 0 { - return errors.New(buf.String()) - } + fd := pw.Fd() + wait := func() error { + pw.Close() + return <-errc + } + args := &btrfs_ioctl_send_args{ + send_fd: int64(fd), + parent_root: parent, + flags: flags, + } + if len(sources) != 0 { + args.clone_sources = &sources[0] + args.clone_sources_count = uint64(len(sources)) + } + if err := iocSend(subvol, args); err != nil { + wait() return err } - tf.Seek(0, 0) - _, err = io.Copy(w, tf) - return err + return wait() } diff --git a/subvolume.go b/subvolume.go index e860830..74d8207 100644 --- a/subvolume.go +++ b/subvolume.go @@ -131,7 +131,7 @@ func SnapshotSubVolume(subvol, dst string, ro bool) error { fd: int64(f.Fd()), } if ro { - args.flags |= subvolReadOnly + args.flags |= SubvolReadOnly } // TODO //if inherit != nil { diff --git a/utils.go b/utils.go index 6071f85..9151aa9 100644 --- a/utils.go +++ b/utils.go @@ -1,8 +1,12 @@ package btrfs import ( + "bufio" "fmt" + "io" "os" + "path/filepath" + "strings" "syscall" "unsafe" ) @@ -15,6 +19,76 @@ func isBtrfs(path string) (bool, error) { return stfs.Type == SuperMagic, nil } +func IsReadOnly(path string) (bool, error) { + fs, err := Open(path, true) + if err != nil { + return false, err + } + defer fs.Close() + f, err := fs.GetFlags() + if err != nil { + return false, err + } + return f.ReadOnly(), nil +} + +type mountPoint struct { + Dev string + Mount string + Type string + Opts string +} + +func getMounts() ([]mountPoint, error) { + file, err := os.Open("/etc/mtab") + if err != nil { + return nil, err + } + defer file.Close() + r := bufio.NewReader(file) + var out []mountPoint + for { + line, err := r.ReadString('\n') + if err == io.EOF { + break + } else if err != nil { + return nil, err + } + fields := strings.Fields(line) + out = append(out, mountPoint{ + Dev: fields[0], + Mount: fields[1], + Type: fields[2], + Opts: fields[3], + }) + } + return out, nil +} + +func findMountRoot(path string) (string, error) { + mounts, err := getMounts() + if err != nil { + return "", err + } + longest := "" + isBtrfs := false + for _, m := range mounts { + if !strings.HasPrefix(path, m.Mount) { + continue + } + if len(longest) < len(m.Mount) { + longest = m.Mount + isBtrfs = m.Type == "btrfs" + } + } + if longest == "" { + return "", os.ErrNotExist + } else if !isBtrfs { + return "", ErrNotBtrfs{Path: longest} + } + return filepath.Abs(longest) +} + // openDir does the following checks before calling Open: // 1: path is in a btrfs filesystem // 2: path is a directory From ea3cdb769b5ecc42b8e4f3d253df8269e398e871 Mon Sep 17 00:00:00 2001 From: Denys Smirnov Date: Wed, 21 Sep 2016 15:31:08 +0300 Subject: [PATCH 06/38] native Recv WIP (cherry picked from commit dennwc/btrfs@43632657e8ca98f6b726f6a7acb553c1c09e99a7) Signed-off-by: Akihiro Suda --- cmd/gbtrfs.go | 2 +- receive.go | 142 ++++++++++++++++++++++++++++++++++++++++++------- send_h.go | 144 ++++++++++++++++++++++++++++++++++++++++++++++++++ subvolume.go | 9 ++-- utils.go | 2 +- 5 files changed, 275 insertions(+), 24 deletions(-) create mode 100644 send_h.go diff --git a/cmd/gbtrfs.go b/cmd/gbtrfs.go index dee48f0..b499077 100644 --- a/cmd/gbtrfs.go +++ b/cmd/gbtrfs.go @@ -96,7 +96,7 @@ var SendCmd = &cobra.Command{ } var ReceiveCmd = &cobra.Command{ - Use: "receive [-ve] [-f ] [--max-errors ] ", + Use: "receive [-v] [-f ] [--max-errors ] ", Short: "Receive subvolumes from stdin.", Long: `Receives one or more subvolumes that were previously sent with btrfs send. The received subvolumes are stored diff --git a/receive.go b/receive.go index 3e2a74a..1136f7b 100644 --- a/receive.go +++ b/receive.go @@ -2,31 +2,135 @@ package btrfs import ( "bytes" + "encoding/binary" "errors" + "fmt" + "hash/crc32" "io" + "os" "os/exec" + "path/filepath" + "syscall" ) -func Receive(r io.Reader, mount string) error { - // TODO: write a native implementation? - //tf, err := ioutil.TempFile("","btrfs_snap") - //if err != nil { - // return err - //} - //defer func(){ - // name := tf.Name() - // tf.Close() - // os.Remove(name) - //}() - buf := bytes.NewBuffer(nil) - cmd := exec.Command("btrfs", "receive", mount) - cmd.Stdin = r - cmd.Stderr = buf - if err := cmd.Run(); err != nil { - if buf.Len() != 0 { - return errors.New(buf.String()) +const nativeReceive = false + +func Receive(r io.Reader, dstDir string) error { + if !nativeReceive { + buf := bytes.NewBuffer(nil) + cmd := exec.Command("btrfs", "receive", dstDir) + cmd.Stdin = r + cmd.Stderr = buf + if err := cmd.Run(); err != nil { + if buf.Len() != 0 { + return errors.New(buf.String()) + } + return err } + return nil + } + var err error + dstDir, err = filepath.Abs(dstDir) + if err != nil { + return err + } + realMnt, err := findMountRoot(dstDir) + if err != nil { + return err + } + dir, err := os.OpenFile(dstDir, os.O_RDONLY|syscall.O_NOATIME, 0755) + if err != nil { + return err + } + mnt, err := os.OpenFile(realMnt, os.O_RDONLY|syscall.O_NOATIME, 0755) + if err != nil { return err } - return nil + // We want to resolve the path to the subvolume we're sitting in + // so that we can adjust the paths of any subvols we want to receive in. + subvolID, err := getPathRootID(mnt) + if err != nil { + return err + } + sr, err := newStreamReader(r) + if err != nil { + return err + } + _, _, _ = dir, subvolID, sr + panic("not implemented") +} + +type streamReader struct { + r io.Reader + hbuf []byte + buf *bytes.Buffer +} +type sendCommandArgs struct { + Type tlvType + Data []byte +} +type sendCommand struct { + Type sendCmd + Args []sendCommandArgs +} + +func (sr *streamReader) ReadCommand() (*sendCommand, error) { + sr.buf.Reset() + var h cmdHeader + if sr.hbuf == nil { + sr.hbuf = make([]byte, h.Size()) + } + if _, err := io.ReadFull(sr.r, sr.hbuf); err != nil { + return nil, err + } else if err = h.Unmarshal(sr.hbuf); err != nil { + return nil, err + } + if sr.buf == nil { + sr.buf = bytes.NewBuffer(nil) + } + if _, err := io.CopyN(sr.buf, sr.r, int64(h.Len)); err != nil { + return nil, err + } + tbl := crc32.MakeTable(0) + crc := crc32.Checksum(sr.buf.Bytes(), tbl) + if crc != h.Crc { + return nil, fmt.Errorf("crc missmatch in command: %x vs %x", crc, h.Crc) + } + cmd := sendCommand{Type: sendCmd(h.Cmd)} + var th tlvHeader + data := sr.buf.Bytes() + for { + if n := len(data); n < th.Size() { + if n != 0 { + return nil, io.ErrUnexpectedEOF + } + break + } + if err := th.Unmarshal(data); err != nil { + return nil, err + } + data = data[th.Size():] + if th.Type > _BTRFS_SEND_A_MAX { // || th.Len > _BTRFS_SEND_BUF_SIZE { + return nil, fmt.Errorf("invalid tlv in cmd: %+v", th) + } + b := make([]byte, th.Len) + copy(b, data) + cmd.Args = append(cmd.Args, sendCommandArgs{Type: th.Type, Data: b}) + } + return &cmd, nil +} + +func newStreamReader(r io.Reader) (*streamReader, error) { + buf := make([]byte, sendStreamMagicSize+4) + _, err := io.ReadFull(r, buf) + if err != nil { + return nil, err + } else if bytes.Compare(buf[:sendStreamMagicSize], []byte(_BTRFS_SEND_STREAM_MAGIC)) != 0 { + return nil, errors.New("unexpected stream header") + } + version := binary.LittleEndian.Uint32(buf[sendStreamMagicSize:]) + if version > _BTRFS_SEND_STREAM_VERSION { + return nil, fmt.Errorf("stream version %d not supported", version) + } + return &streamReader{r: r}, nil } diff --git a/send_h.go b/send_h.go new file mode 100644 index 0000000..9c93c92 --- /dev/null +++ b/send_h.go @@ -0,0 +1,144 @@ +package btrfs + +import ( + "encoding/binary" + "io" +) + +const ( + _BTRFS_SEND_STREAM_MAGIC = "btrfs-stream" + sendStreamMagicSize = len(_BTRFS_SEND_STREAM_MAGIC) + _BTRFS_SEND_STREAM_VERSION = 1 +) + +const ( + _BTRFS_SEND_BUF_SIZE = 64 * 1024 + _BTRFS_SEND_READ_SIZE = 48 * 1024 +) + +type tlvType uint16 + +const ( + tlvU8 = tlvType(iota) + tlvU16 + tlvU32 + tlvU64 + tlvBinary + tlvString + tlvUUID + tlvTimespec +) + +type streamHeader struct { + Magic [len(_BTRFS_SEND_STREAM_MAGIC)]byte + Version uint32 +} + +type cmdHeader struct { + Len uint32 // len excluding the header + Cmd uint16 + Crc uint32 // crc including the header with zero crc field +} + +func (h *cmdHeader) Size() int { return 10 } +func (h *cmdHeader) Unmarshal(p []byte) error { + if len(p) < h.Size() { + return io.ErrUnexpectedEOF + } + h.Len = binary.LittleEndian.Uint32(p[0:]) + h.Cmd = binary.LittleEndian.Uint16(p[4:]) + h.Crc = binary.LittleEndian.Uint32(p[6:]) + return nil +} + +type tlvHeader struct { + Type tlvType + Len uint16 // len excluding the header +} + +func (h *tlvHeader) Size() int { return 4 } +func (h *tlvHeader) Unmarshal(p []byte) error { + if len(p) < h.Size() { + return io.ErrUnexpectedEOF + } + h.Type = tlvType(binary.LittleEndian.Uint16(p[0:])) + h.Len = binary.LittleEndian.Uint16(p[2:]) + return nil +} + +type sendCmd uint16 + +const ( + _BTRFS_SEND_C_UNSPEC = sendCmd(iota) + + _BTRFS_SEND_C_SUBVOL + _BTRFS_SEND_C_SNAPSHOT + + _BTRFS_SEND_C_MKFILE + _BTRFS_SEND_C_MKDIR + _BTRFS_SEND_C_MKNOD + _BTRFS_SEND_C_MKFIFO + _BTRFS_SEND_C_MKSOCK + _BTRFS_SEND_C_SYMLINK + + _BTRFS_SEND_C_RENAME + _BTRFS_SEND_C_LINK + _BTRFS_SEND_C_UNLINK + _BTRFS_SEND_C_RMDIR + + _BTRFS_SEND_C_SET_XATTR + _BTRFS_SEND_C_REMOVE_XATTR + + _BTRFS_SEND_C_WRITE + _BTRFS_SEND_C_CLONE + + _BTRFS_SEND_C_TRUNCATE + _BTRFS_SEND_C_CHMOD + _BTRFS_SEND_C_CHOWN + _BTRFS_SEND_C_UTIMES + + _BTRFS_SEND_C_END + _BTRFS_SEND_C_UPDATE_EXTENT + __BTRFS_SEND_C_MAX +) + +const _BTRFS_SEND_C_MAX = __BTRFS_SEND_C_MAX - 1 + +type sendCmdAttr uint16 + +const ( + _BTRFS_SEND_A_UNSPEC = iota + + _BTRFS_SEND_A_UUID + _BTRFS_SEND_A_CTRANSID + + _BTRFS_SEND_A_INO + _BTRFS_SEND_A_SIZE + _BTRFS_SEND_A_MODE + _BTRFS_SEND_A_UID + _BTRFS_SEND_A_GID + _BTRFS_SEND_A_RDEV + _BTRFS_SEND_A_CTIME + _BTRFS_SEND_A_MTIME + _BTRFS_SEND_A_ATIME + _BTRFS_SEND_A_OTIME + + _BTRFS_SEND_A_XATTR_NAME + _BTRFS_SEND_A_XATTR_DATA + + _BTRFS_SEND_A_PATH + _BTRFS_SEND_A_PATH_TO + _BTRFS_SEND_A_PATH_LINK + + _BTRFS_SEND_A_FILE_OFFSET + _BTRFS_SEND_A_DATA + + _BTRFS_SEND_A_CLONE_UUID + _BTRFS_SEND_A_CLONE_CTRANSID + _BTRFS_SEND_A_CLONE_PATH + _BTRFS_SEND_A_CLONE_OFFSET + _BTRFS_SEND_A_CLONE_LEN + + __BTRFS_SEND_A_MAX +) +const _BTRFS_SEND_A_MAX = __BTRFS_SEND_A_MAX - 1 diff --git a/subvolume.go b/subvolume.go index 74d8207..1cfe913 100644 --- a/subvolume.go +++ b/subvolume.go @@ -17,7 +17,7 @@ func checkSubVolumeName(name string) bool { func IsSubVolume(path string) (bool, error) { var st syscall.Stat_t if err := syscall.Stat(path, &st); err != nil { - return false, err + return false, &os.PathError{Op: "stat", Path: path, Err: err} } if st.Ino != firstFreeObjectid || st.Mode&syscall.S_IFMT != syscall.S_IFDIR { @@ -124,7 +124,7 @@ func SnapshotSubVolume(subvol, dst string, ro bool) error { // TODO: make SnapshotSubVolume a method on FS to use existing fd f, err := openDir(subvol) if err != nil { - return err + return fmt.Errorf("cannot open dest dir: %v", err) } defer f.Close() args := btrfs_ioctl_vol_args_v2{ @@ -140,7 +140,10 @@ func SnapshotSubVolume(subvol, dst string, ro bool) error { // args.qgroup_inherit = inherit //} copy(args.name[:], newName) - return iocSnapCreateV2(fdst, &args) + if err := iocSnapCreateV2(fdst, &args); err != nil { + return fmt.Errorf("ioc failed: %v", err) + } + return nil } func ListSubVolumes(path string) ([]Subvolume, error) { diff --git a/utils.go b/utils.go index 9151aa9..3a76988 100644 --- a/utils.go +++ b/utils.go @@ -14,7 +14,7 @@ import ( func isBtrfs(path string) (bool, error) { var stfs syscall.Statfs_t if err := syscall.Statfs(path, &stfs); err != nil { - return false, err + return false, &os.PathError{Op: "statfs", Path: path, Err: err} } return stfs.Type == SuperMagic, nil } From 02f01e27ff8136e4a43cc9533b88a75c8c7d3a41 Mon Sep 17 00:00:00 2001 From: Denys Smirnov Date: Wed, 21 Sep 2016 18:34:04 +0300 Subject: [PATCH 07/38] SetFlags and SetCompression (cherry picked from commit dennwc/btrfs@41809caf7d430591a170ea34eb5ec05576929a2d) Signed-off-by: Akihiro Suda --- btrfs.go | 30 ++++++++++++++++++++++++++++++ ioctl_h.go | 5 +++-- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/btrfs.go b/btrfs.go index 0100677..3fe2100 100644 --- a/btrfs.go +++ b/btrfs.go @@ -11,6 +11,8 @@ import ( const SuperMagic = 0x9123683E +const xattrPrefix = "btrfs." + func Open(path string, ro bool) (*FS, error) { if ok, err := IsSubVolume(path); err != nil { return nil, err @@ -152,6 +154,10 @@ func (f *FS) GetFlags() (SubvolFlags, error) { return iocSubvolGetflags(f.f) } +func (f *FS) SetFlags(flags SubvolFlags) error { + return iocSubvolSetflags(f.f, flags) +} + func (f *FS) Sync() (err error) { if err = ioctl.Do(f.f, _BTRFS_IOC_START_SYNC, nil); err != nil { return @@ -194,3 +200,27 @@ func (f *FS) Receive(r io.Reader) error { func (f *FS) ReceiveTo(r io.Reader, mount string) error { return Receive(r, filepath.Join(f.f.Name(), mount)) } + +type Compression string + +const ( + CompressionNone = Compression("") + LZO = Compression("lzo") + ZLIB = Compression("zlib") +) + +func SetCompression(path string, v Compression) error { + var value []byte + if v != CompressionNone { + var err error + value, err = syscall.ByteSliceFromString(string(v)) + if err != nil { + return err + } + } + err := syscall.Setxattr(path, xattrPrefix+"compression", value, 0) + if err != nil { + return &os.PathError{Op: "setxattr", Path: path, Err: err} + } + return nil +} diff --git a/ioctl_h.go b/ioctl_h.go index a0fcb98..8a8ef98 100644 --- a/ioctl_h.go +++ b/ioctl_h.go @@ -695,8 +695,9 @@ func iocSubvolGetflags(f *os.File) (out SubvolFlags, err error) { return } -func iocSubvolSetflags(f *os.File, out *uint64) error { - return ioctl.Do(f, _BTRFS_IOC_SUBVOL_SETFLAGS, out) +func iocSubvolSetflags(f *os.File, flags SubvolFlags) error { + v := uint64(flags) + return ioctl.Do(f, _BTRFS_IOC_SUBVOL_SETFLAGS, &v) } func iocScrub(f *os.File, out *btrfs_ioctl_scrub_args) error { From c30c81b0cca9db25e5f4b46bce810cc2e83c7d9b Mon Sep 17 00:00:00 2001 From: Denys Smirnov Date: Sat, 1 Oct 2016 14:44:34 +0300 Subject: [PATCH 08/38] fix fd leak (cherry picked from commit dennwc/btrfs@7eae92f5719c0a55706ee7f9f01761f01eec7524) Signed-off-by: Akihiro Suda --- send.go | 1 + 1 file changed, 1 insertion(+) diff --git a/send.go b/send.go index bfd6046..356cd3e 100644 --- a/send.go +++ b/send.go @@ -84,6 +84,7 @@ func Send(w io.Writer, parent string, subvols ...string) error { flags |= _BTRFS_SEND_FLAG_OMIT_END_CMD } err = send(w, fs.f, parentID, cloneSrc, flags) + fs.Close() if err != nil { return fmt.Errorf("error sending %s: %v", sub, err) } From b8392423ad5670455c1a2680666d590c988453db Mon Sep 17 00:00:00 2001 From: Denys Smirnov Date: Sat, 1 Oct 2016 15:10:12 +0300 Subject: [PATCH 09/38] refactor ListSubvolumes (cherry picked from commit dennwc/btrfs@8f48d3ebf52a35f3ddc7dd223bd08ac30955d6c1) Signed-off-by: Akihiro Suda --- btrfs.go | 19 +++++++++++++++++++ cmd/gbtrfs.go | 7 ++++++- subvolume.go | 21 --------------------- 3 files changed, 25 insertions(+), 22 deletions(-) diff --git a/btrfs.go b/btrfs.go index 3fe2100..aef7e10 100644 --- a/btrfs.go +++ b/btrfs.go @@ -201,6 +201,25 @@ func (f *FS) ReceiveTo(r io.Reader, mount string) error { return Receive(r, filepath.Join(f.f.Name(), mount)) } +func (f *FS) ListSubvolumes(filter func(Subvolume) bool) ([]Subvolume, error) { + //root, err := getPathRootID(f) + //if err != nil { + // return nil, fmt.Errorf("can't get rootid for '%s': %v", path, err) + //} + m, err := listSubVolumes(f.f) + if err != nil { + return nil, err + } + out := make([]Subvolume, 0, len(m)) + for _, v := range m { + if filter != nil && !filter(v) { + continue + } + out = append(out, v) + } + return out, nil +} + type Compression string const ( diff --git a/cmd/gbtrfs.go b/cmd/gbtrfs.go index b499077..03c3c6f 100644 --- a/cmd/gbtrfs.go +++ b/cmd/gbtrfs.go @@ -74,7 +74,12 @@ var SubvolumeListCmd = &cobra.Command{ if len(args) != 1 { return fmt.Errorf("expected one destination argument") } - list, err := btrfs.ListSubVolumes(args[0]) + fs, err := btrfs.Open(args[0], true) + if err != nil { + return err + } + defer fs.Close() + list, err := fs.ListSubvolumes(nil) if err == nil { for _, v := range list { fmt.Printf("%+v\n", v) diff --git a/subvolume.go b/subvolume.go index 1cfe913..f8aee57 100644 --- a/subvolume.go +++ b/subvolume.go @@ -146,27 +146,6 @@ func SnapshotSubVolume(subvol, dst string, ro bool) error { return nil } -func ListSubVolumes(path string) ([]Subvolume, error) { - f, err := openDir(path) - if err != nil { - return nil, err - } - defer f.Close() - //root, err := getPathRootID(f) - //if err != nil { - // return nil, fmt.Errorf("can't get rootid for '%s': %v", path, err) - //} - m, err := listSubVolumes(f) - if err != nil { - return nil, err - } - out := make([]Subvolume, 0, len(m)) - for _, v := range m { - out = append(out, v) - } - return out, nil -} - type Subvolume struct { ObjectID uint64 TransID uint64 From 470099efbc4c923e2a46ae13c8350234aa327b3e Mon Sep 17 00:00:00 2001 From: Denys Smirnov Date: Wed, 5 Oct 2016 14:21:14 +0300 Subject: [PATCH 10/38] expose GetMounts as a sub-package (cherry picked from commit dennwc/btrfs@14de038f2f2d5129e665819b1afaa13cc869a8d9) Signed-off-by: Akihiro Suda --- mtab/mtab.go | 42 ++++++++++++++++++++++++++++++++++++++++++ utils.go | 38 ++------------------------------------ 2 files changed, 44 insertions(+), 36 deletions(-) create mode 100644 mtab/mtab.go diff --git a/mtab/mtab.go b/mtab/mtab.go new file mode 100644 index 0000000..4c48be1 --- /dev/null +++ b/mtab/mtab.go @@ -0,0 +1,42 @@ +package mtab + +import ( + "bufio" + "io" + "os" + "strings" +) + +type MountPoint struct { + Dev string + Mount string + Type string + Opts string +} + +// Mounts returns a list of mount point from /etc/mtab. +func Mounts() ([]MountPoint, error) { + file, err := os.Open("/etc/mtab") + if err != nil { + return nil, err + } + defer file.Close() + r := bufio.NewReader(file) + var out []MountPoint + for { + line, err := r.ReadString('\n') + if err == io.EOF { + break + } else if err != nil { + return nil, err + } + fields := strings.Fields(line) + out = append(out, MountPoint{ + Dev: fields[0], + Mount: fields[1], + Type: fields[2], + Opts: fields[3], + }) + } + return out, nil +} diff --git a/utils.go b/utils.go index 3a76988..b4ebeca 100644 --- a/utils.go +++ b/utils.go @@ -1,9 +1,8 @@ package btrfs import ( - "bufio" "fmt" - "io" + "github.com/dennwc/btrfs/mtab" "os" "path/filepath" "strings" @@ -32,41 +31,8 @@ func IsReadOnly(path string) (bool, error) { return f.ReadOnly(), nil } -type mountPoint struct { - Dev string - Mount string - Type string - Opts string -} - -func getMounts() ([]mountPoint, error) { - file, err := os.Open("/etc/mtab") - if err != nil { - return nil, err - } - defer file.Close() - r := bufio.NewReader(file) - var out []mountPoint - for { - line, err := r.ReadString('\n') - if err == io.EOF { - break - } else if err != nil { - return nil, err - } - fields := strings.Fields(line) - out = append(out, mountPoint{ - Dev: fields[0], - Mount: fields[1], - Type: fields[2], - Opts: fields[3], - }) - } - return out, nil -} - func findMountRoot(path string) (string, error) { - mounts, err := getMounts() + mounts, err := mtab.Mounts() if err != nil { return "", err } From fbb23e5aae06ebbbd9d47bb9fa09e8419d768e5f Mon Sep 17 00:00:00 2001 From: Denys Smirnov Date: Wed, 5 Oct 2016 16:45:00 +0300 Subject: [PATCH 11/38] some docs (cherry picked from commit dennwc/btrfs@b56d643295189995fb73a15b83bf5ef60933587b) Signed-off-by: Akihiro Suda --- mtab/mtab.go | 1 + 1 file changed, 1 insertion(+) diff --git a/mtab/mtab.go b/mtab/mtab.go index 4c48be1..e1651ed 100644 --- a/mtab/mtab.go +++ b/mtab/mtab.go @@ -1,3 +1,4 @@ +// Package mtab contains tools to work with /etc/mtab file. package mtab import ( From c6db02536b42db9ad50dc288df9cab33dad5affc Mon Sep 17 00:00:00 2001 From: Denys Smirnov Date: Wed, 5 Oct 2016 16:45:57 +0300 Subject: [PATCH 12/38] implement GetCompression; add tests (cherry picked from commit dennwc/btrfs@33433246133d23da7acc90d00ffd7f9c518bbc9c) Signed-off-by: Akihiro Suda --- btrfs.go | 35 ++++++++++++++- btrfs_test.go | 97 ++++++++++++++++++++++++++++++++++++++++++ test/btrfstest.go | 106 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 237 insertions(+), 1 deletion(-) create mode 100644 btrfs_test.go create mode 100644 test/btrfstest.go diff --git a/btrfs.go b/btrfs.go index aef7e10..a99204b 100644 --- a/btrfs.go +++ b/btrfs.go @@ -1,6 +1,7 @@ package btrfs import ( + "bytes" "fmt" "github.com/dennwc/btrfs/ioctl" "io" @@ -228,6 +229,8 @@ const ( ZLIB = Compression("zlib") ) +const xattrCompression = xattrPrefix + "compression" + func SetCompression(path string, v Compression) error { var value []byte if v != CompressionNone { @@ -237,9 +240,39 @@ func SetCompression(path string, v Compression) error { return err } } - err := syscall.Setxattr(path, xattrPrefix+"compression", value, 0) + err := syscall.Setxattr(path, xattrCompression, value, 0) if err != nil { return &os.PathError{Op: "setxattr", Path: path, Err: err} } return nil } + +func GetCompression(path string) (Compression, error) { + var buf []byte + for { + sz, err := syscall.Getxattr(path, xattrCompression, nil) + if err == syscall.ENODATA || sz == 0 { + return CompressionNone, nil + } else if err != nil { + return CompressionNone, &os.PathError{Op: "getxattr", Path: path, Err: err} + } + if cap(buf) < sz { + buf = make([]byte, sz) + } else { + buf = buf[:sz] + } + sz, err = syscall.Getxattr(path, xattrCompression, buf) + if err == syscall.ENODATA { + return CompressionNone, nil + } else if err == syscall.ERANGE { + // xattr changed by someone else, and is larger than our current buffer + continue + } else if err != nil { + return CompressionNone, &os.PathError{Op: "getxattr", Path: path, Err: err} + } + buf = buf[:sz] + break + } + buf = bytes.TrimSuffix(buf, []byte{0}) + return Compression(buf), nil +} diff --git a/btrfs_test.go b/btrfs_test.go new file mode 100644 index 0000000..e09abe1 --- /dev/null +++ b/btrfs_test.go @@ -0,0 +1,97 @@ +package btrfs + +import ( + "github.com/dennwc/btrfs/test" + "os" + "path/filepath" + "testing" +) + +const sizeDef = 256 * 1024 * 1024 + +func TestOpen(t *testing.T) { + dir, closer := btrfstest.New(t, sizeDef) + defer closer() + fs, err := Open(dir, true) + if err != nil { + t.Fatal(err) + } + if err = fs.Close(); err != nil { + t.Fatal(err) + } +} + +func TestIsSubvolume(t *testing.T) { + dir, closer := btrfstest.New(t, sizeDef) + defer closer() + + isSubvol := func(path string, expect bool) { + ok, err := IsSubVolume(path) + if err != nil { + t.Errorf("failed to check subvolume %v: %v", path, err) + return + } else if ok != expect { + t.Errorf("unexpected result for %v", path) + } + } + mkdir := func(path string) { + path = filepath.Join(dir, path) + if err := os.MkdirAll(path, 0755); err != nil { + t.Fatalf("cannot create dir %v: %v", path, err) + } + isSubvol(path, false) + } + + mksub := func(path string) { + path = filepath.Join(dir, path) + if err := CreateSubVolume(path); err != nil { + t.Fatalf("cannot create subvolume %v: %v", path, err) + } + isSubvol(path, true) + } + + mksub("v1") + + mkdir("v1/d2") + mksub("v1/v2") + + mkdir("v1/d2/d3") + mksub("v1/d2/v3") + + mkdir("v1/v2/d3") + mksub("v1/v2/v3") + + mkdir("d1") + + mkdir("d1/d2") + mksub("d1/v2") + + mkdir("d1/d2/d3") + mksub("d1/d2/v3") + + mkdir("d1/v2/d3") + mksub("d1/v2/v3") +} + +func TestCompression(t *testing.T) { + dir, closer := btrfstest.New(t, sizeDef) + defer closer() + fs, err := Open(dir, true) + if err != nil { + t.Fatal(err) + } + defer fs.Close() + if err := fs.CreateSubVolume("sub"); err != nil { + t.Fatal(err) + } + path := filepath.Join(dir, "sub") + + if err := SetCompression(path, LZO); err != nil { + t.Fatal(err) + } + if c, err := GetCompression(path); err != nil { + t.Fatal(err) + } else if c != LZO { + t.Fatalf("unexpected compression returned: %q", string(c)) + } +} diff --git a/test/btrfstest.go b/test/btrfstest.go new file mode 100644 index 0000000..bb1cced --- /dev/null +++ b/test/btrfstest.go @@ -0,0 +1,106 @@ +package btrfstest + +import ( + "bytes" + "errors" + "io/ioutil" + "log" + "os" + "os/exec" + "strings" + "testing" + "time" +) + +func run(name string, args ...string) error { + buf := bytes.NewBuffer(nil) + cmd := exec.Command(name, args...) + cmd.Stdout = buf + cmd.Stderr = buf + err := cmd.Run() + if err == nil { + return nil + } else if buf.Len() == 0 { + return err + } + return errors.New("error: " + strings.TrimSpace(string(buf.Bytes()))) +} + +func Mkfs(file string, size int64) error { + f, err := os.Create(file) + if err != nil { + return err + } + if err = f.Truncate(size); err != nil { + f.Close() + return err + } + if err = f.Close(); err != nil { + return err + } + if err = run("mkfs.btrfs", file); err != nil { + os.Remove(file) + return err + } + return err +} + +func Mount(mount string, file string) error { + if err := run("mount", file, mount); err != nil { + return err + } + return nil +} + +func New(t testing.TB, size int64) (string, func()) { + f, err := ioutil.TempFile("", "btrfs_vol") + if err != nil { + t.Fatal(err) + } + name := f.Name() + f.Close() + rm := func() { + os.Remove(name) + } + if err = Mkfs(name, size); err != nil { + rm() + } + mount, err := ioutil.TempDir("", "btrfs_mount") + if err != nil { + rm() + t.Fatal(err) + } + if err = Mount(mount, name); err != nil { + rm() + os.RemoveAll(mount) + if txt := err.Error(); strings.Contains(txt, "permission denied") || + strings.Contains(txt, "only root") { + t.Skip(err) + } else { + t.Fatal(err) + } + } + done := false + return mount, func() { + if done { + return + } + for i := 0; i < 5; i++ { + if err := run("umount", mount); err == nil { + break + } else { + log.Println("umount failed:", err) + if strings.Contains(err.Error(), "busy") { + time.Sleep(time.Second) + } else { + break + } + } + } + if err := os.Remove(mount); err != nil { + log.Println("cleanup failed:", err) + } + rm() + done = true + } +} From 24cd949a9f8409d37089be5799c75382127ff401 Mon Sep 17 00:00:00 2001 From: Denys Smirnov Date: Thu, 6 Oct 2016 14:26:05 +0300 Subject: [PATCH 13/38] implement usage stats (cherry picked from commit dennwc/btrfs@6f59d604fca01ab494d2c18844fc4769eea6373c) Signed-off-by: Akihiro Suda --- btrfs.go | 83 ++++-------------------- btrfs_tree.go | 3 + ioctl_h.go | 57 ++++++++++++++-- usage.go | 175 ++++++++++++++++++++++++++++++++++++++++++++++++++ xattr.go | 66 +++++++++++++++++++ 5 files changed, 310 insertions(+), 74 deletions(-) create mode 100644 usage.go create mode 100644 xattr.go diff --git a/btrfs.go b/btrfs.go index a99204b..4477f86 100644 --- a/btrfs.go +++ b/btrfs.go @@ -1,7 +1,6 @@ package btrfs import ( - "bytes" "fmt" "github.com/dennwc/btrfs/ioctl" "io" @@ -12,8 +11,6 @@ import ( const SuperMagic = 0x9123683E -const xattrPrefix = "btrfs." - func Open(path string, ro bool) (*FS, error) { if ok, err := IsSubVolume(path); err != nil { return nil, err @@ -60,16 +57,16 @@ type Info struct { func (f *FS) Info() (out Info, err error) { var arg btrfs_ioctl_fs_info_args - if err = ioctl.Do(f.f, _BTRFS_IOC_FS_INFO, &arg); err != nil { - return - } - out = Info{ - MaxID: arg.max_id, - NumDevices: arg.num_devices, - FSID: arg.fsid, - NodeSize: arg.nodesize, - SectorSize: arg.sectorsize, - CloneAlignment: arg.clone_alignment, + arg, err = iocFsInfo(f.f) + if err == nil { + out = Info{ + MaxID: arg.max_id, + NumDevices: arg.num_devices, + FSID: arg.fsid, + NodeSize: arg.nodesize, + SectorSize: arg.sectorsize, + CloneAlignment: arg.clone_alignment, + } } return } @@ -160,10 +157,10 @@ func (f *FS) SetFlags(flags SubvolFlags) error { } func (f *FS) Sync() (err error) { - if err = ioctl.Do(f.f, _BTRFS_IOC_START_SYNC, nil); err != nil { + if err = ioctl.Ioctl(f.f, _BTRFS_IOC_START_SYNC, 0); err != nil { return } - return ioctl.Do(f.f, _BTRFS_IOC_WAIT_SYNC, nil) + return ioctl.Ioctl(f.f, _BTRFS_IOC_WAIT_SYNC, 0) } func (f *FS) CreateSubVolume(name string) error { @@ -221,58 +218,4 @@ func (f *FS) ListSubvolumes(filter func(Subvolume) bool) ([]Subvolume, error) { return out, nil } -type Compression string - -const ( - CompressionNone = Compression("") - LZO = Compression("lzo") - ZLIB = Compression("zlib") -) - -const xattrCompression = xattrPrefix + "compression" - -func SetCompression(path string, v Compression) error { - var value []byte - if v != CompressionNone { - var err error - value, err = syscall.ByteSliceFromString(string(v)) - if err != nil { - return err - } - } - err := syscall.Setxattr(path, xattrCompression, value, 0) - if err != nil { - return &os.PathError{Op: "setxattr", Path: path, Err: err} - } - return nil -} - -func GetCompression(path string) (Compression, error) { - var buf []byte - for { - sz, err := syscall.Getxattr(path, xattrCompression, nil) - if err == syscall.ENODATA || sz == 0 { - return CompressionNone, nil - } else if err != nil { - return CompressionNone, &os.PathError{Op: "getxattr", Path: path, Err: err} - } - if cap(buf) < sz { - buf = make([]byte, sz) - } else { - buf = buf[:sz] - } - sz, err = syscall.Getxattr(path, xattrCompression, buf) - if err == syscall.ENODATA { - return CompressionNone, nil - } else if err == syscall.ERANGE { - // xattr changed by someone else, and is larger than our current buffer - continue - } else if err != nil { - return CompressionNone, &os.PathError{Op: "getxattr", Path: path, Err: err} - } - buf = buf[:sz] - break - } - buf = bytes.TrimSuffix(buf, []byte{0}) - return Compression(buf), nil -} +func (f *FS) Usage() (UsageInfo, error) { return spaceUsage(f.f) } diff --git a/btrfs_tree.go b/btrfs_tree.go index a7e6f6b..5d0ebca 100644 --- a/btrfs_tree.go +++ b/btrfs_tree.go @@ -6,6 +6,9 @@ import ( ) const ( + _BTRFS_BLOCK_GROUP_TYPE_MASK = (blockGroupData | + blockGroupSystem | + blockGroupMetadata) _BTRFS_BLOCK_GROUP_PROFILE_MASK = (blockGroupRaid0 | blockGroupRaid1 | blockGroupRaid5 | diff --git a/ioctl_h.go b/ioctl_h.go index 8a8ef98..0691e89 100644 --- a/ioctl_h.go +++ b/ioctl_h.go @@ -678,8 +678,49 @@ func iocDefaultSubvol(f *os.File, out *uint64) error { return ioctl.Do(f, _BTRFS_IOC_DEFAULT_SUBVOL, out) } -func iocSpaceInfo(f *os.File, out *btrfs_ioctl_space_args) error { - return ioctl.Do(f, _BTRFS_IOC_SPACE_INFO, out) +type spaceInfo struct { + Flags uint64 + TotalBytes uint64 + UsedBytes uint64 +} + +func iocSpaceInfo(f *os.File) ([]spaceInfo, error) { + arg := &btrfs_ioctl_space_args{} + if err := ioctl.Do(f, _BTRFS_IOC_SPACE_INFO, arg); err != nil { + return nil, err + } + n := arg.total_spaces + if n == 0 { + return nil, nil + } + const ( + argSize = unsafe.Sizeof(btrfs_ioctl_space_args{}) + infoSize = unsafe.Sizeof(btrfs_ioctl_space_info{}) + ) + buf := make([]byte, argSize+uintptr(n)*infoSize) + basePtr := unsafe.Pointer(&buf[0]) + arg = (*btrfs_ioctl_space_args)(basePtr) + arg.space_slots = n + if err := ioctl.Do(f, _BTRFS_IOC_SPACE_INFO, arg); err != nil { + return nil, err + } else if arg.total_spaces == 0 { + return nil, nil + } + if n > arg.total_spaces { + n = arg.total_spaces + } + out := make([]spaceInfo, n) + ptr := uintptr(basePtr) + argSize + for i := 0; i < int(n); i++ { + info := (*btrfs_ioctl_space_info)(unsafe.Pointer(ptr)) + out[i] = spaceInfo{ + Flags: info.flags, + TotalBytes: info.total_bytes, + UsedBytes: info.used_bytes, + } + ptr += infoSize + } + return out, nil } func iocStartSync(f *os.File, out *uint64) error { @@ -712,8 +753,16 @@ func iocScrubProgress(f *os.File, out *btrfs_ioctl_scrub_args) error { return ioctl.Do(f, _BTRFS_IOC_SCRUB_PROGRESS, out) } -func iocDevInfo(f *os.File, out *btrfs_ioctl_dev_info_args) error { - return ioctl.Do(f, _BTRFS_IOC_DEV_INFO, out) +func iocFsInfo(f *os.File) (out btrfs_ioctl_fs_info_args, err error) { + err = ioctl.Do(f, _BTRFS_IOC_FS_INFO, &out) + return +} + +func iocDevInfo(f *os.File, devid uint64, uuid UUID) (out btrfs_ioctl_dev_info_args, err error) { + out.devid = devid + out.uuid = uuid + err = ioctl.Do(f, _BTRFS_IOC_DEV_INFO, &out) + return } func iocBalanceCtl(f *os.File, out *int32) error { diff --git a/usage.go b/usage.go new file mode 100644 index 0000000..6f10f6a --- /dev/null +++ b/usage.go @@ -0,0 +1,175 @@ +package btrfs + +import ( + "os" + "sort" + "syscall" +) + +func cmpChunkBlockGroup(f1, f2 uint64) int { + var mask uint64 + + if (f1 & _BTRFS_BLOCK_GROUP_TYPE_MASK) == + (f2 & _BTRFS_BLOCK_GROUP_TYPE_MASK) { + mask = _BTRFS_BLOCK_GROUP_PROFILE_MASK + } else if f2&blockGroupSystem != 0 { + return -1 + } else if f1&blockGroupSystem != 0 { + return +1 + } else { + mask = _BTRFS_BLOCK_GROUP_TYPE_MASK + } + + if (f1 & mask) > (f2 & mask) { + return +1 + } else if (f1 & mask) < (f2 & mask) { + return -1 + } else { + return 0 + } +} + +type spaceInfoByBlockGroup []spaceInfo + +func (a spaceInfoByBlockGroup) Len() int { return len(a) } +func (a spaceInfoByBlockGroup) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a spaceInfoByBlockGroup) Less(i, j int) bool { + return cmpChunkBlockGroup(a[i].Flags, a[j].Flags) < 0 +} + +type UsageInfo struct { + Total uint64 + TotalUnused uint64 + TotalUsed uint64 + TotalChunks uint64 + + FreeEstimated uint64 + FreeMin uint64 + + LogicalDataChunks uint64 + RawDataChunks uint64 + RawDataUsed uint64 + + LogicalMetaChunks uint64 + RawMetaChunks uint64 + RawMetaUsed uint64 + + SystemUsed uint64 + SystemChunks uint64 + + DataRatio float64 + MetadataRatio float64 + + GlobalReserve uint64 + GlobalReserveUsed uint64 +} + +const minUnallocatedThreshold = 16 * 1024 * 1024 + +func spaceUsage(f *os.File) (UsageInfo, error) { + info, err := iocFsInfo(f) + if err != nil { + return UsageInfo{}, err + } + var u UsageInfo + for i := uint64(0); i <= info.max_id; i++ { + dev, err := iocDevInfo(f, i, UUID{}) + if err == syscall.ENODEV { + continue + } else if err != nil { + return UsageInfo{}, err + } + u.Total += dev.total_bytes + } + + spaces, err := iocSpaceInfo(f) + if err != nil { + return UsageInfo{}, err + } + sort.Sort(spaceInfoByBlockGroup(spaces)) + var ( + maxDataRatio int = 1 + mixed bool + ) + for _, s := range spaces { + ratio := 1 + switch { + case s.Flags&blockGroupRaid0 != 0: + ratio = 1 + case s.Flags&blockGroupRaid1 != 0: + ratio = 2 + case s.Flags&blockGroupRaid5 != 0: + ratio = 0 + case s.Flags&blockGroupRaid6 != 0: + ratio = 0 + case s.Flags&blockGroupDup != 0: + ratio = 2 + case s.Flags&blockGroupRaid10 != 0: + ratio = 2 + } + if ratio > maxDataRatio { + maxDataRatio = ratio + } + if s.Flags&spaceInfoGlobalRsv != 0 { + u.GlobalReserve = s.TotalBytes + u.GlobalReserveUsed = s.UsedBytes + } + if s.Flags&(blockGroupData|blockGroupMetadata) == (blockGroupData | blockGroupMetadata) { + mixed = true + } + if s.Flags&blockGroupData != 0 { + u.RawDataUsed += s.UsedBytes * uint64(ratio) + u.RawDataChunks += s.TotalBytes * uint64(ratio) + u.LogicalDataChunks += s.TotalBytes + } + if s.Flags&blockGroupMetadata != 0 { + u.RawMetaUsed += s.UsedBytes * uint64(ratio) + u.RawMetaChunks += s.TotalBytes * uint64(ratio) + u.LogicalMetaChunks += s.TotalBytes + } + if s.Flags&blockGroupSystem != 0 { + u.SystemUsed += s.UsedBytes * uint64(ratio) + u.SystemChunks += s.TotalBytes * uint64(ratio) + } + } + u.TotalChunks = u.RawDataChunks + u.SystemChunks + u.TotalUsed = u.RawDataUsed + u.SystemUsed + if !mixed { + u.TotalChunks += u.RawMetaChunks + u.TotalUsed += u.RawMetaUsed + } + u.TotalUnused = u.Total - u.TotalChunks + + u.DataRatio = float64(u.RawDataChunks) / float64(u.LogicalDataChunks) + if mixed { + u.MetadataRatio = u.DataRatio + } else { + u.MetadataRatio = float64(u.RawMetaChunks) / float64(u.LogicalMetaChunks) + } + + // We're able to fill at least DATA for the unused space + // + // With mixed raid levels, this gives a rough estimate but more + // accurate than just counting the logical free space + // (l_data_chunks - l_data_used) + // + // In non-mixed case there's no difference. + u.FreeEstimated = uint64(float64(u.RawDataChunks-u.RawDataUsed) / u.DataRatio) + + // For mixed-bg the metadata are left out in calculations thus global + // reserve would be lost. Part of it could be permanently allocated, + // we have to subtract the used bytes so we don't go under zero free. + if mixed { + u.FreeEstimated -= u.GlobalReserve - u.GlobalReserveUsed + } + u.FreeMin = u.FreeEstimated + + // Chop unallocatable space + // FIXME: must be applied per device + if u.TotalUnused >= minUnallocatedThreshold { + u.FreeEstimated += uint64(float64(u.TotalUnused) / u.DataRatio) + // Match the calculation of 'df', use the highest raid ratio + u.FreeMin += u.TotalUnused / uint64(maxDataRatio) + } + return u, nil +} diff --git a/xattr.go b/xattr.go new file mode 100644 index 0000000..ae4b7d2 --- /dev/null +++ b/xattr.go @@ -0,0 +1,66 @@ +package btrfs + +import ( + "bytes" + "os" + "syscall" +) + +const ( + xattrPrefix = "btrfs." + xattrCompression = xattrPrefix + "compression" +) + +type Compression string + +const ( + CompressionNone = Compression("") + LZO = Compression("lzo") + ZLIB = Compression("zlib") +) + +func SetCompression(path string, v Compression) error { + var value []byte + if v != CompressionNone { + var err error + value, err = syscall.ByteSliceFromString(string(v)) + if err != nil { + return err + } + } + err := syscall.Setxattr(path, xattrCompression, value, 0) + if err != nil { + return &os.PathError{Op: "setxattr", Path: path, Err: err} + } + return nil +} + +func GetCompression(path string) (Compression, error) { + var buf []byte + for { + sz, err := syscall.Getxattr(path, xattrCompression, nil) + if err == syscall.ENODATA || sz == 0 { + return CompressionNone, nil + } else if err != nil { + return CompressionNone, &os.PathError{Op: "getxattr", Path: path, Err: err} + } + if cap(buf) < sz { + buf = make([]byte, sz) + } else { + buf = buf[:sz] + } + sz, err = syscall.Getxattr(path, xattrCompression, buf) + if err == syscall.ENODATA { + return CompressionNone, nil + } else if err == syscall.ERANGE { + // xattr changed by someone else, and is larger than our current buffer + continue + } else if err != nil { + return CompressionNone, &os.PathError{Op: "getxattr", Path: path, Err: err} + } + buf = buf[:sz] + break + } + buf = bytes.TrimSuffix(buf, []byte{0}) + return Compression(buf), nil +} From e52d936a58f9452f21cebabcfcb3bd1dde09ac77 Mon Sep 17 00:00:00 2001 From: Denys Smirnov Date: Mon, 17 Oct 2016 17:12:29 +0300 Subject: [PATCH 14/38] add helper to check for zero uuid (cherry picked from commit dennwc/btrfs@94acb6ee4ab21800c4fbd0d6d94dbfb15c686366) Signed-off-by: Akihiro Suda --- ioctl_h.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ioctl_h.go b/ioctl_h.go index 0691e89..e460c89 100644 --- a/ioctl_h.go +++ b/ioctl_h.go @@ -25,8 +25,9 @@ var zeroUUID UUID type UUID [UUIDSize]byte +func (id UUID) IsZero() bool { return id == zeroUUID } func (id UUID) String() string { - if id == zeroUUID { + if id.IsZero() { return "" } buf := make([]byte, UUIDSize*2+4) From 233b64696db7c6f25f7cc57a809c01a96b21dc67 Mon Sep 17 00:00:00 2001 From: Denys Smirnov Date: Thu, 20 Oct 2016 21:11:39 +0300 Subject: [PATCH 15/38] fix subvolume list (cherry picked from commit dennwc/btrfs@4ac498bb4d8462fc43d5a8898f8eb9cd28edc999) Signed-off-by: Akihiro Suda --- subvolume.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/subvolume.go b/subvolume.go index f8aee57..bbd8792 100644 --- a/subvolume.go +++ b/subvolume.go @@ -169,7 +169,7 @@ func listSubVolumes(f *os.File) (map[uint64]Subvolume, error) { // Set the min and max to backref keys. The search will // only send back this type of key now. - min_type: rootBackrefKey, + min_type: rootItemKey, max_type: rootBackrefKey, min_objectid: firstFreeObjectid, From d8ac265c9917e3cc57ac4800b877c9a75ce6e845 Mon Sep 17 00:00:00 2001 From: Denys Smirnov Date: Fri, 6 Jan 2017 14:02:41 +0200 Subject: [PATCH 16/38] expose file clone ioctl; add test (cherry picked from commit dennwc/btrfs@08f5a6b26a375e7b451bc74a28ba5d763af27283) Signed-off-by: Akihiro Suda --- btrfs.go | 4 ++++ btrfs_test.go | 39 +++++++++++++++++++++++++++++++++++++++ ioctl_h.go | 4 ++-- 3 files changed, 45 insertions(+), 2 deletions(-) diff --git a/btrfs.go b/btrfs.go index 4477f86..b5e8497 100644 --- a/btrfs.go +++ b/btrfs.go @@ -11,6 +11,10 @@ import ( const SuperMagic = 0x9123683E +func CloneFile(dst, src *os.File) error { + return iocClone(dst, src) +} + func Open(path string, ro bool) (*FS, error) { if ok, err := IsSubVolume(path); err != nil { return nil, err diff --git a/btrfs_test.go b/btrfs_test.go index e09abe1..067f4fc 100644 --- a/btrfs_test.go +++ b/btrfs_test.go @@ -2,6 +2,7 @@ package btrfs import ( "github.com/dennwc/btrfs/test" + "io" "os" "path/filepath" "testing" @@ -95,3 +96,41 @@ func TestCompression(t *testing.T) { t.Fatalf("unexpected compression returned: %q", string(c)) } } + +func TestCloneFile(t *testing.T) { + dir, closer := btrfstest.New(t, sizeDef) + defer closer() + + f1, err := os.Create(filepath.Join(dir, "1.dat")) + if err != nil { + t.Fatal(err) + } + defer f1.Close() + + const data = "btrfs_test" + _, err = f1.WriteString(data) + if err != nil { + t.Fatal(err) + } + + f2, err := os.Create(filepath.Join(dir, "2.dat")) + if err != nil { + t.Fatal(err) + } + defer f2.Close() + + err = CloneFile(f2, f1) + if err != nil { + t.Fatal(err) + } + + buf := make([]byte, len(data)) + n, err := f2.Read(buf) + if err != nil && err != io.EOF { + t.Fatal(err) + } + buf = buf[:n] + if string(buf) != data { + t.Fatalf("wrong data returned: %q", string(buf)) + } +} diff --git a/ioctl_h.go b/ioctl_h.go index e460c89..11021c5 100644 --- a/ioctl_h.go +++ b/ioctl_h.go @@ -631,8 +631,8 @@ func iocSync(f *os.File) error { return ioctl.Do(f, _BTRFS_IOC_SYNC, nil) } -func iocClone(f *os.File, out *int32) error { - return ioctl.Do(f, _BTRFS_IOC_CLONE, out) +func iocClone(dst, src *os.File) error { + return ioctl.Ioctl(dst, _BTRFS_IOC_CLONE, src.Fd()) } func iocAddDev(f *os.File, out *btrfs_ioctl_vol_args) error { From 01d467488b79adeb17d260f8755415175b375727 Mon Sep 17 00:00:00 2001 From: Denys Smirnov Date: Sat, 7 Jan 2017 22:55:54 +0200 Subject: [PATCH 17/38] decode root_item; more tests; uuid search wip (cherry picked from commit dennwc/btrfs@54573d1c9d8b7f096ba780086d6967289fbc5ef6) Signed-off-by: Akihiro Suda --- btrfs_test.go | 66 ++++++++++++++++ btrfs_tree.go | 206 ++++++++++++++++++++++++++++++++++++++++++++++++-- errors.go | 10 ++- size_test.go | 9 ++- subvolume.go | 79 ++++++++++++++++--- utils.go | 27 ++----- uuid_tree.go | 49 ++++++++++++ 7 files changed, 407 insertions(+), 39 deletions(-) create mode 100644 uuid_tree.go diff --git a/btrfs_test.go b/btrfs_test.go index 067f4fc..95f5a41 100644 --- a/btrfs_test.go +++ b/btrfs_test.go @@ -5,6 +5,8 @@ import ( "io" "os" "path/filepath" + "reflect" + "sort" "testing" ) @@ -74,6 +76,70 @@ func TestIsSubvolume(t *testing.T) { mksub("d1/v2/v3") } +func TestSubvolumes(t *testing.T) { + dir, closer := btrfstest.New(t, sizeDef) + defer closer() + fs, err := Open(dir, false) + if err != nil { + t.Fatal(err) + } + defer fs.Close() + + mksub := func(in string, path string) { + if in != "" { + path = filepath.Join(dir, in, path) + } else { + path = filepath.Join(dir, path) + } + if err := CreateSubVolume(path); err != nil { + t.Fatalf("cannot create subvolume %v: %v", path, err) + } + } + delsub := func(path string) { + path = filepath.Join(dir, path) + if err := DeleteSubVolume(path); err != nil { + t.Fatalf("cannot delete subvolume %v: %v", path, err) + } + } + expect := func(exp []string) { + subs, err := fs.ListSubvolumes(nil) + if err != nil { + t.Fatal(err) + } + var got []string + for _, s := range subs { + if s.UUID.IsZero() { + t.Fatalf("zero uuid in %+v", s) + } + if s.Name != "" { + got = append(got, s.Name) + } + } + sort.Strings(got) + sort.Strings(exp) + if !reflect.DeepEqual(got, exp) { + t.Fatalf("list failed:\ngot: %v\nvs\nexp: %v", got, exp) + } + } + + names := []string{"foo", "bar", "baz"} + for _, name := range names { + mksub("", name) + } + for _, name := range names { + mksub(names[0], name) + } + expect([]string{ + "foo", "bar", "baz", + "foo", "bar", "baz", + }) + delsub("foo/bar") + expect([]string{ + "foo", "bar", "baz", + "foo", "baz", + }) +} + func TestCompression(t *testing.T) { dir, closer := btrfstest.New(t, sizeDef) defer closer() diff --git a/btrfs_tree.go b/btrfs_tree.go index 5d0ebca..2a9cdb0 100644 --- a/btrfs_tree.go +++ b/btrfs_tree.go @@ -37,11 +37,6 @@ func asUint16(p []byte) uint16 { return *(*uint16)(unsafe.Pointer(&p[0])) } -func asTime(p []byte) time.Time { - sec, nsec := asUint64(p[0:]), asUint32(p[8:]) - return time.Unix(int64(sec), int64(nsec)) -} - func asRootRef(p []byte) rootRef { const sz = 18 // assuming that it is highly unsafe to have sizeof(struct) > len(data) @@ -55,3 +50,204 @@ func asRootRef(p []byte) rootRef { } return ref } + +// btrfs_disk_key_raw is a raw bytes for btrfs_disk_key structure +type btrfs_disk_key_raw [17]byte + +func (p btrfs_disk_key_raw) Decode() diskKey { + return diskKey{ + ObjectID: asUint64(p[0:]), + Type: p[8], + Offset: asUint64(p[9:]), + } +} + +type diskKey struct { + ObjectID uint64 + Type byte + Offset uint64 +} + +// btrfs_timespec_raw is a raw bytes for btrfs_timespec structure. +type btrfs_timespec_raw [12]byte + +func (t btrfs_timespec_raw) Decode() time.Time { + sec, nsec := asUint64(t[0:]), asUint32(t[8:]) + return time.Unix(int64(sec), int64(nsec)) +} + +// timeBlock is a raw set of bytes for 4 time fields: +// atime btrfs_timespec +// ctime btrfs_timespec +// mtime btrfs_timespec +// otime btrfs_timespec +// It is used to keep correct alignment when accessing structures from btrfs. +type timeBlock [4]btrfs_timespec_raw + +type btrfs_inode_item_raw struct { + generation uint64 + transid uint64 + size uint64 + nbytes uint64 + block_group uint64 + nlink uint32 + uid uint32 + gid uint32 + mode uint32 + rdev uint64 + flags uint64 + sequence uint64 + _ [4]uint64 // reserved + // atime btrfs_timespec + // ctime btrfs_timespec + // mtime btrfs_timespec + // otime btrfs_timespec + times timeBlock +} + +func (v btrfs_inode_item_raw) Decode() inodeItem { + return inodeItem{ + Gen: v.generation, + TransID: v.transid, + Size: v.size, + NBytes: v.nbytes, + BlockGroup: v.block_group, + NLink: v.nlink, + UID: v.uid, + GID: v.gid, + Mode: v.mode, + RDev: v.rdev, + Flags: v.flags, + Sequence: v.sequence, + ATime: v.times[0].Decode(), + CTime: v.times[1].Decode(), + MTime: v.times[2].Decode(), + OTime: v.times[3].Decode(), + } +} + +type inodeItem struct { + Gen uint64 // nfs style generation number + TransID uint64 // transid that last touched this inode + Size uint64 + NBytes uint64 + BlockGroup uint64 + NLink uint32 + UID uint32 + GID uint32 + Mode uint32 + RDev uint64 + Flags uint64 + Sequence uint64 // modification sequence number for NFS + ATime time.Time + CTime time.Time + MTime time.Time + OTime time.Time +} + +func asRootItem(p []byte) *btrfs_root_item_raw { + return (*btrfs_root_item_raw)(unsafe.Pointer(&p[0])) +} + +type btrfs_root_item_raw [439]byte + +func (p btrfs_root_item_raw) Decode() rootItem { + const ( + off2 = unsafe.Sizeof(btrfs_root_item_raw_p1{}) + off3 = off2 + 23 + ) + p1 := (*btrfs_root_item_raw_p1)(unsafe.Pointer(&p[0])) + p2 := p[off2 : off2+23] + p2_k := (*btrfs_disk_key_raw)(unsafe.Pointer(&p[off2+4])) + p2_b := p2[4+17:] + p3 := (*btrfs_root_item_raw_p3)(unsafe.Pointer(&p[off3])) + return rootItem{ + Inode: p1.inode.Decode(), + Gen: p1.generation, + RootDirID: p1.root_dirid, + ByteNr: p1.bytenr, + ByteLimit: p1.byte_limit, + BytesUsed: p1.bytes_used, + LastSnapshot: p1.last_snapshot, + Flags: p1.flags, + // from here, Go structure become misaligned with C structure + Refs: asUint32(p2[0:]), + DropProgress: p2_k.Decode(), + DropLevel: p2_b[0], + Level: p2_b[1], + // these fields are still misaligned by 1 bytes + // TODO(dennwc): it's a copy of Gen to check structure version; hide it maybe? + GenV2: p3.generation_v2, + UUID: p3.uuid, + ParentUUID: p3.parent_uuid, + ReceivedUUID: p3.received_uuid, + CTransID: p3.ctransid, + OTransID: p3.otransid, + STransID: p3.stransid, + RTransID: p3.rtransid, + CTime: p3.times[0].Decode(), + OTime: p3.times[1].Decode(), + STime: p3.times[2].Decode(), + RTime: p3.times[3].Decode(), + } +} + +type rootItem struct { + Inode inodeItem + Gen uint64 + RootDirID uint64 + ByteNr uint64 + ByteLimit uint64 + BytesUsed uint64 + LastSnapshot uint64 + Flags uint64 + Refs uint32 + DropProgress diskKey + DropLevel uint8 + Level uint8 + GenV2 uint64 + UUID UUID + ParentUUID UUID + ReceivedUUID UUID + CTransID uint64 + OTransID uint64 + STransID uint64 + RTransID uint64 + CTime time.Time + OTime time.Time + STime time.Time + RTime time.Time +} + +type btrfs_root_item_raw_p1 struct { + inode btrfs_inode_item_raw + generation uint64 + root_dirid uint64 + bytenr uint64 + byte_limit uint64 + bytes_used uint64 + last_snapshot uint64 + flags uint64 +} +type btrfs_root_item_raw_p2 struct { + refs uint32 + drop_progress btrfs_disk_key_raw + drop_level uint8 + level uint8 +} +type btrfs_root_item_raw_p3 struct { + generation_v2 uint64 + uuid UUID + parent_uuid UUID + received_uuid UUID + ctransid uint64 + otransid uint64 + stransid uint64 + rtransid uint64 + // ctime btrfs_timespec + // otime btrfs_timespec + // stime btrfs_timespec + // rtime btrfs_timespec + times timeBlock + _ [8]uint64 // reserved +} diff --git a/errors.go b/errors.go index cda99f8..b852bcf 100644 --- a/errors.go +++ b/errors.go @@ -1,6 +1,9 @@ package btrfs -import "fmt" +import ( + "errors" + "fmt" +) type ErrNotBtrfs struct { Path string @@ -42,3 +45,8 @@ var errorString = map[ErrCode]string{ ErrDevOnlyWritable: "unable to remove the only writeable device", ErrDevExclRunInProgress: "add/delete/balance/replace/resize operation in progress", } + +var ( + ErrNotFound = errors.New("not found") + errNotImplemented = errors.New("not implemented") +) diff --git a/size_test.go b/size_test.go index edc114a..01c813c 100644 --- a/size_test.go +++ b/size_test.go @@ -3,6 +3,7 @@ package btrfs import ( "reflect" "testing" + "unsafe" ) var caseSizes = []struct { @@ -48,15 +49,21 @@ var caseSizes = []struct { {obj: btrfs_ioctl_received_subvol_args{}, size: 200}, {obj: btrfs_ioctl_send_args{}, size: 72}, + //{obj:btrfs_timespec{},size:12}, //{obj:btrfs_root_ref{},size:18}, //{obj:btrfs_root_item{},size:439}, + {obj: btrfs_root_item_raw{}, size: 439}, + {obj: btrfs_root_item_raw_p1{}, size: 439 - 23 - int(unsafe.Sizeof(btrfs_root_item_raw_p3{}))}, + {obj: btrfs_root_item_raw_p3{}, size: 439 - 23 - int(unsafe.Sizeof(btrfs_root_item_raw_p1{}))}, //{obj:btrfs_inode_item{},size:160}, + {obj: btrfs_inode_item_raw{}, size: 160}, + {obj: timeBlock{}, size: 4 * 12}, } func TestSizes(t *testing.T) { for _, c := range caseSizes { if sz := int(reflect.ValueOf(c.obj).Type().Size()); sz != c.size { - t.Fatalf("unexpected size of %T: %d", c.obj, sz) + t.Errorf("unexpected size of %T: %d (exp: %d)", c.obj, sz, c.size) } } } diff --git a/subvolume.go b/subvolume.go index bbd8792..b268440 100644 --- a/subvolume.go +++ b/subvolume.go @@ -146,6 +146,23 @@ func SnapshotSubVolume(subvol, dst string, ro bool) error { return nil } +func IsReadOnly(path string) (bool, error) { + f, err := GetFlags(path) + if err != nil { + return false, err + } + return f.ReadOnly(), nil +} + +func GetFlags(path string) (SubvolFlags, error) { + fs, err := Open(path, true) + if err != nil { + return 0, err + } + defer fs.Close() + return fs.GetFlags() +} + type Subvolume struct { ObjectID uint64 TransID uint64 @@ -205,18 +222,15 @@ func listSubVolumes(f *os.File) (map[uint64]Subvolume, error) { o := m[obj.ObjectID] o.TransID = obj.TransID o.ObjectID = obj.ObjectID - // TODO: decode whole object? - o.Gen = asUint64(obj.Data[160:]) // size of btrfs_inode_item - o.Flags = asUint64(obj.Data[160+6*8:]) - const sz = 439 - const toff = sz - 8*8 - 4*12 - o.CTime = asTime(obj.Data[toff+0*12:]) - o.OTime = asTime(obj.Data[toff+1*12:]) - o.OGen = asUint64(obj.Data[toff-3*8:]) - const uoff = toff - 4*8 - 3*UUIDSize - copy(o.UUID[:], obj.Data[uoff+0*UUIDSize:]) - copy(o.ParentUUID[:], obj.Data[uoff+1*UUIDSize:]) - copy(o.ReceivedUUID[:], obj.Data[uoff+2*UUIDSize:]) + robj := asRootItem(obj.Data).Decode() + o.Gen = robj.Gen + o.Flags = robj.Flags + o.CTime = robj.CTime + o.OTime = robj.OTime + o.OGen = robj.GenV2 + o.UUID = robj.UUID + o.ParentUUID = robj.ParentUUID + o.ReceivedUUID = robj.ReceivedUUID m[obj.ObjectID] = o } } @@ -243,3 +257,44 @@ func listSubVolumes(f *os.File) (map[uint64]Subvolume, error) { } return m, nil } + +type subvolInfo struct { + RootID uint64 + + UUID UUID + ParentUUID UUID + ReceivedUUID UUID + + CTransID uint64 + OTransID uint64 + STransID uint64 + RTransID uint64 + + Path string +} + +func subvolSearchByUUID(mnt *os.File, uuid UUID) (*subvolInfo, error) { + id, err := lookupUUIDSubvolItem(mnt, uuid) + if err != nil { + return nil, err + } + return subvolSearchByRootID(mnt, id) +} + +func subvolSearchByReceivedUUID(mnt *os.File, uuid UUID) (*subvolInfo, error) { + id, err := lookupUUIDReceivedSubvolItem(mnt, uuid) + if err != nil { + return nil, err + } + return subvolSearchByRootID(mnt, id) +} + +func subvolSearchByPath(mnt *os.File, path string) (*subvolInfo, error) { + var id uint64 + panic("not implemented") + return subvolSearchByRootID(mnt, id) +} + +func subvolSearchByRootID(mnt *os.File, rootID uint64) (*subvolInfo, error) { + panic("not implemented") +} diff --git a/utils.go b/utils.go index b4ebeca..b4c9408 100644 --- a/utils.go +++ b/utils.go @@ -18,19 +18,6 @@ func isBtrfs(path string) (bool, error) { return stfs.Type == SuperMagic, nil } -func IsReadOnly(path string) (bool, error) { - fs, err := Open(path, true) - if err != nil { - return false, err - } - defer fs.Close() - f, err := fs.GetFlags() - if err != nil { - return false, err - } - return f.ReadOnly(), nil -} - func findMountRoot(path string) (string, error) { mounts, err := mtab.Mounts() if err != nil { @@ -77,7 +64,7 @@ func openDir(path string) (*os.File, error) { return file, nil } -type rawItem struct { +type searchResult struct { TransID uint64 ObjectID uint64 Type uint32 @@ -85,24 +72,24 @@ type rawItem struct { Data []byte } -func treeSearchRaw(f *os.File, key btrfs_ioctl_search_key) (out []rawItem, _ error) { +func treeSearchRaw(mnt *os.File, key btrfs_ioctl_search_key) (out []searchResult, _ error) { args := btrfs_ioctl_search_args{ key: key, } - if err := iocTreeSearch(f, &args); err != nil { + if err := iocTreeSearch(mnt, &args); err != nil { return nil, err } - out = make([]rawItem, 0, args.key.nr_items) + out = make([]searchResult, 0, args.key.nr_items) buf := args.buf[:] for i := 0; i < int(args.key.nr_items); i++ { h := (*btrfs_ioctl_search_header)(unsafe.Pointer(&buf[0])) buf = buf[unsafe.Sizeof(btrfs_ioctl_search_header{}):] - out = append(out, rawItem{ + out = append(out, searchResult{ TransID: h.transid, ObjectID: h.objectid, - Type: h.typ, Offset: h.offset, - Data: buf[:h.len], // TODO: reallocate? + Type: h.typ, + Data: buf[:h.len:h.len], // TODO: reallocate? }) buf = buf[h.len:] } diff --git a/uuid_tree.go b/uuid_tree.go new file mode 100644 index 0000000..d3a1425 --- /dev/null +++ b/uuid_tree.go @@ -0,0 +1,49 @@ +package btrfs + +import ( + "encoding/binary" + "fmt" + "os" +) + +func lookupUUIDSubvolItem(f *os.File, uuid UUID) (uint64, error) { + return uuidTreeLookupAny(f, uuid, uuidKeySubvol) +} + +func lookupUUIDReceivedSubvolItem(f *os.File, uuid UUID) (uint64, error) { + return uuidTreeLookupAny(f, uuid, uuidKeyReceivedSubvol) +} + +func (id UUID) toKey() (objID, off uint64) { + objID = binary.LittleEndian.Uint64(id[:8]) + off = binary.LittleEndian.Uint64(id[8:16]) + return +} + +// uuidTreeLookupAny searches uuid tree for a given uuid in specified field. +// It returns ErrNotFound if object was not found. +func uuidTreeLookupAny(f *os.File, uuid UUID, typ uint32) (uint64, error) { + objId, off := uuid.toKey() + args := btrfs_ioctl_search_key{ + tree_id: uuidTreeObjectid, + min_objectid: objId, + max_objectid: objId, + min_type: typ, + max_type: typ, + min_offset: off, + max_offset: off, + max_transid: maxUint64, + nr_items: 1, + } + res, err := treeSearchRaw(f, args) + if err != nil { + return 0, err + } else if len(res) < 1 { + return 0, ErrNotFound + } + out := res[0] + if len(out.Data) != 8 { + return 0, fmt.Errorf("btrfs: uuid item with illegal size %d", len(out.Data)) + } + return binary.LittleEndian.Uint64(out.Data), nil +} From f3c3c6aef69de4e3b81f70b825c77bd23dcbce2a Mon Sep 17 00:00:00 2001 From: Denys Smirnov Date: Sun, 8 Jan 2017 16:13:47 +0200 Subject: [PATCH 18/38] implement send for multiple snapshots (cherry picked from commit dennwc/btrfs@3b69894215b9a9d072bd36a001ab29e54c2fa5fe) Signed-off-by: Akihiro Suda --- btrfs_list.go | 11 ++- btrfs_test.go | 15 ++++ btrfs_tree.go | 11 +-- btrfs_tree_hc.go | 198 +++++++++++++++++++++++++---------------------- cmd/hgen.go | 58 +++++++++++++- headers.go | 2 +- ioctl_h.go | 43 ++++++---- receive.go | 2 +- send.go | 149 +++++++++++++++++++++++++++++++---- subvolume.go | 113 +++++++++++++++++++++++---- usage.go | 29 +++---- utils.go | 4 +- uuid_tree.go | 12 +-- 13 files changed, 476 insertions(+), 171 deletions(-) diff --git a/btrfs_list.go b/btrfs_list.go index 4b4a941..4929fe6 100644 --- a/btrfs_list.go +++ b/btrfs_list.go @@ -2,7 +2,7 @@ package btrfs import "os" -func getPathRootID(file *os.File) (uint64, error) { +func getFileRootID(file *os.File) (objectID, error) { args := btrfs_ioctl_ino_lookup_args{ objectid: firstFreeObjectid, } @@ -11,3 +11,12 @@ func getPathRootID(file *os.File) (uint64, error) { } return args.treeid, nil } + +func getPathRootID(path string) (objectID, error) { + fs, err := Open(path, true) + if err != nil { + return 0, err + } + defer fs.Close() + return getFileRootID(fs.f) +} diff --git a/btrfs_test.go b/btrfs_test.go index 95f5a41..2f5b611 100644 --- a/btrfs_test.go +++ b/btrfs_test.go @@ -138,6 +138,21 @@ func TestSubvolumes(t *testing.T) { "foo", "bar", "baz", "foo", "baz", }) + + path := filepath.Join(names[0], names[2]) + mksub(path, "new") + path = filepath.Join(path, "new") + + id, err := getPathRootID(filepath.Join(dir, path)) + if err != nil { + t.Fatal(err) + } + info, err := subvolSearchByRootID(fs.f, id, "") + if err != nil { + t.Fatal(err) + } else if info.Path != path { + t.Fatalf("wrong path returned: %v vs %v", info.Path, path) + } } func TestCompression(t *testing.T) { diff --git a/btrfs_tree.go b/btrfs_tree.go index 2a9cdb0..1f2f7ae 100644 --- a/btrfs_tree.go +++ b/btrfs_tree.go @@ -15,10 +15,11 @@ const ( blockGroupRaid6 | blockGroupDup | blockGroupRaid10) + _BTRFS_BLOCK_GROUP_MASK = _BTRFS_BLOCK_GROUP_TYPE_MASK | _BTRFS_BLOCK_GROUP_PROFILE_MASK ) type rootRef struct { - DirID uint64 + DirID objectID Sequence uint64 Name string } @@ -42,7 +43,7 @@ func asRootRef(p []byte) rootRef { // assuming that it is highly unsafe to have sizeof(struct) > len(data) // (*btrfs_root_ref)(unsafe.Pointer(&p[0])) and sizeof(btrfs_root_ref) == 24 ref := rootRef{ - DirID: asUint64(p[0:]), + DirID: objectID(asUint64(p[0:])), Sequence: asUint64(p[8:]), } if n := asUint16(p[16:]); n > 0 { @@ -76,11 +77,7 @@ func (t btrfs_timespec_raw) Decode() time.Time { return time.Unix(int64(sec), int64(nsec)) } -// timeBlock is a raw set of bytes for 4 time fields: -// atime btrfs_timespec -// ctime btrfs_timespec -// mtime btrfs_timespec -// otime btrfs_timespec +// timeBlock is a raw set of bytes for 4 time fields. // It is used to keep correct alignment when accessing structures from btrfs. type timeBlock [4]btrfs_timespec_raw diff --git a/btrfs_tree_hc.go b/btrfs_tree_hc.go index 75c392c..fd79932 100644 --- a/btrfs_tree_hc.go +++ b/btrfs_tree_hc.go @@ -2,6 +2,18 @@ package btrfs // This code was auto-generated; DO NOT EDIT! +type treeKeyType uint32 + +type objectID uint64 + +type fileType int + +type fileExtentType int + +type devReplaceItemState int + +type blockGroup uint64 + // This header contains the structure definitions and constants used // by file system objects that can be retrieved using // the BTRFS_IOC_SEARCH_TREE ioctl. That means basically anything that @@ -9,179 +21,179 @@ package btrfs const ( // Holds pointers to all of the tree roots - rootTreeObjectid = 1 + rootTreeObjectid objectID = 1 // Stores information about which extents are in use, and reference counts - extentTreeObjectid = 2 + extentTreeObjectid objectID = 2 // Chunk tree stores translations from logical -> physical block numbering // the super block points to the chunk tree - chunkTreeObjectid = 3 + chunkTreeObjectid objectID = 3 // Stores information about which areas of a given device are in use. // one per device. The tree of tree roots points to the device tree - devTreeObjectid = 4 + devTreeObjectid objectID = 4 // One per subvolume, storing files and directories - fsTreeObjectid = 5 + fsTreeObjectid objectID = 5 // Directory objectid inside the root tree - rootTreeDirObjectid = 6 + rootTreeDirObjectid objectID = 6 // Holds checksums of all the data extents - csumTreeObjectid = 7 + csumTreeObjectid objectID = 7 // Holds quota configuration and tracking - quotaTreeObjectid = 8 + quotaTreeObjectid objectID = 8 // For storing items that use the BTRFS_UUID_KEY* types - uuidTreeObjectid = 9 + uuidTreeObjectid objectID = 9 // Tracks free space in block groups. - freeSpaceTreeObjectid = 10 + freeSpaceTreeObjectid objectID = 10 // Device stats in the device tree - devStatsObjectid = 0 + devStatsObjectid objectID = 0 // For storing balance parameters in the root tree - balanceObjectid = (1<<64 - 4) + balanceObjectid objectID = (1<<64 - 4) // Orhpan objectid for tracking unlinked/truncated files - orphanObjectid = (1<<64 - 5) + orphanObjectid objectID = (1<<64 - 5) // Does write ahead logging to speed up fsyncs - treeLogObjectid = (1<<64 - 6) - treeLogFixupObjectid = (1<<64 - 7) + treeLogObjectid objectID = (1<<64 - 6) + treeLogFixupObjectid objectID = (1<<64 - 7) // For space balancing - treeRelocObjectid = (1<<64 - 8) - dataRelocTreeObjectid = (1<<64 - 9) + treeRelocObjectid objectID = (1<<64 - 8) + dataRelocTreeObjectid objectID = (1<<64 - 9) // Extent checksums all have this objectid // this allows them to share the logging tree // for fsyncs - extentCsumObjectid = (1<<64 - 10) + extentCsumObjectid objectID = (1<<64 - 10) // For storing free space cache - freeSpaceObjectid = (1<<64 - 11) + freeSpaceObjectid objectID = (1<<64 - 11) // The inode number assigned to the special inode for storing // free ino cache - freeInoObjectid = (1<<64 - 12) + freeInoObjectid objectID = (1<<64 - 12) // Dummy objectid represents multiple objectids multipleObjectids = (1<<64 - 255) // All files have objectids in this range. - firstFreeObjectid = 256 - lastFreeObjectid = (1<<64 - 256) - firstChunkTreeObjectid = 256 + firstFreeObjectid objectID = 256 + lastFreeObjectid objectID = (1<<64 - 256) + firstChunkTreeObjectid objectID = 256 // The device items go into the chunk tree. The key is in the form // [ 1 BTRFS_DEV_ITEM_KEY device_id ] - devItemsObjectid = 1 + devItemsObjectid objectID = 1 - btreeInodeObjectid = 1 + btreeInodeObjectid objectID = 1 - emptySubvolDirObjectid = 2 + emptySubvolDirObjectid objectID = 2 devReplaceDevid = 0 // Inode items have the data typically returned from stat and store other // info about object characteristics. There is one for every file and dir in // the FS - inodeItemKey = 1 - inodeRefKey = 12 - inodeExtrefKey = 13 - xattrItemKey = 24 - orphanItemKey = 48 + inodeItemKey treeKeyType = 1 + inodeRefKey treeKeyType = 12 + inodeExtrefKey treeKeyType = 13 + xattrItemKey treeKeyType = 24 + orphanItemKey treeKeyType = 48 // Reserve 2-15 close to the inode for later flexibility // Dir items are the name -> inode pointers in a directory. There is one // for every name in a directory. - dirLogItemKey = 60 - dirLogIndexKey = 72 - dirItemKey = 84 - dirIndexKey = 96 + dirLogItemKey treeKeyType = 60 + dirLogIndexKey treeKeyType = 72 + dirItemKey treeKeyType = 84 + dirIndexKey treeKeyType = 96 // Extent data is for file data - extentDataKey = 108 + extentDataKey treeKeyType = 108 // Extent csums are stored in a separate tree and hold csums for // an entire extent on disk. - extentCsumKey = 128 + extentCsumKey treeKeyType = 128 // Root items point to tree roots. They are typically in the root // tree used by the super block to find all the other trees - rootItemKey = 132 + rootItemKey treeKeyType = 132 // Root backrefs tie subvols and snapshots to the directory entries that // reference them - rootBackrefKey = 144 + rootBackrefKey treeKeyType = 144 // Root refs make a fast index for listing all of the snapshots and // subvolumes referenced by a given root. They point directly to the // directory item in the root that references the subvol - rootRefKey = 156 + rootRefKey treeKeyType = 156 // Extent items are in the extent map tree. These record which blocks // are used, and how many references there are to each block - extentItemKey = 168 + extentItemKey treeKeyType = 168 // The same as the BTRFS_EXTENT_ITEM_KEY, except it's metadata we already know // the length, so we save the level in key->offset instead of the length. - metadataItemKey = 169 + metadataItemKey treeKeyType = 169 - treeBlockRefKey = 176 + treeBlockRefKey treeKeyType = 176 - extentDataRefKey = 178 + extentDataRefKey treeKeyType = 178 - extentRefV0Key = 180 + extentRefV0Key treeKeyType = 180 - sharedBlockRefKey = 182 + sharedBlockRefKey treeKeyType = 182 - sharedDataRefKey = 184 + sharedDataRefKey treeKeyType = 184 // Block groups give us hints into the extent allocation trees. Which // blocks are free etc etc - blockGroupItemKey = 192 + blockGroupItemKey treeKeyType = 192 // Every block group is represented in the free space tree by a free space info // item, which stores some accounting information. It is keyed on // (block_group_start, FREE_SPACE_INFO, block_group_length). - freeSpaceInfoKey = 198 + freeSpaceInfoKey treeKeyType = 198 // A free space extent tracks an extent of space that is free in a block group. // It is keyed on (start, FREE_SPACE_EXTENT, length). - freeSpaceExtentKey = 199 + freeSpaceExtentKey treeKeyType = 199 // When a block group becomes very fragmented, we convert it to use bitmaps // instead of extents. A free space bitmap is keyed on // (start, FREE_SPACE_BITMAP, length); the corresponding item is a bitmap with // (length / sectorsize) bits. - freeSpaceBitmapKey = 200 + freeSpaceBitmapKey treeKeyType = 200 - devExtentKey = 204 - devItemKey = 216 - chunkItemKey = 228 + devExtentKey treeKeyType = 204 + devItemKey treeKeyType = 216 + chunkItemKey treeKeyType = 228 // Records the overall state of the qgroups. // There's only one instance of this key present, // (0, BTRFS_QGROUP_STATUS_KEY, 0) - qgroupStatusKey = 240 + qgroupStatusKey treeKeyType = 240 // Records the currently used space of the qgroup. // One key per qgroup, (0, BTRFS_QGROUP_INFO_KEY, qgroupid). - qgroupInfoKey = 242 + qgroupInfoKey treeKeyType = 242 // Contains the user configured limits for the qgroup. // One key per qgroup, (0, BTRFS_QGROUP_LIMIT_KEY, qgroupid). - qgroupLimitKey = 244 + qgroupLimitKey treeKeyType = 244 // Records the child-parent relationship of qgroups. For // each relation, 2 keys are present: // (childid, BTRFS_QGROUP_RELATION_KEY, parentid) // (parentid, BTRFS_QGROUP_RELATION_KEY, childid) - qgroupRelationKey = 246 + qgroupRelationKey treeKeyType = 246 // Obsolete name, see BTRFS_TEMPORARY_ITEM_KEY. - balanceItemKey = 248 + balanceItemKey treeKeyType = 248 // The key type for tree items that are stored persistently, but do not need to // exist for extended period of time. The items can exist in any tree. @@ -189,10 +201,10 @@ const ( // Existing items: // - balance status item // (BTRFS_BALANCE_OBJECTID, BTRFS_TEMPORARY_ITEM_KEY, 0) - temporaryItemKey = 248 + temporaryItemKey treeKeyType = 248 // Obsolete name, see BTRFS_PERSISTENT_ITEM_KEY - devStatsKey = 249 + devStatsKey treeKeyType = 249 // The key type for tree items that are stored persistently and usually exist // for a long period, eg. filesystem lifetime. The item kinds can be status @@ -202,11 +214,11 @@ const ( // - device statistics, store IO stats in the device tree, one key for all // stats // (BTRFS_DEV_STATS_OBJECTID, BTRFS_DEV_STATS_KEY, 0) - persistentItemKey = 249 + persistentItemKey treeKeyType = 249 // Persistantly stores the device replace state in the device tree. // The key is built like this: (0, BTRFS_DEV_REPLACE_KEY, 0). - devReplaceKey = 250 + devReplaceKey treeKeyType = 250 // Stores items that allow to quickly map UUIDs to something else. // These items are part of the filesystem UUID tree. @@ -217,7 +229,7 @@ const ( // String items are for debugging. They just store a short string of // data in the FS - stringItemKey = 253 + stringItemKey treeKeyType = 253 // 32 bytes in various csum fields csumSize = 32 @@ -228,16 +240,16 @@ const ( // Flags definitions for directory entry item type // Used by: // struct btrfs_dir_item.type - ftUnknown = 0 - ftRegFile = 1 - ftDir = 2 - ftChrdev = 3 - ftBlkdev = 4 - ftFifo = 5 - ftSock = 6 - ftSymlink = 7 - ftXattr = 8 - ftMax = 9 + ftUnknown fileType = 0 + ftRegFile fileType = 1 + ftDir fileType = 2 + ftChrdev fileType = 3 + ftBlkdev fileType = 4 + ftFifo fileType = 5 + ftSock fileType = 6 + ftSymlink fileType = 7 + ftXattr fileType = 8 + ftMax fileType = 9 // The key defines the order in the tree, and so it also defines (optimal) // block layout. @@ -392,9 +404,9 @@ const ( // resumed after crash or unmount // BTRFS_BALANCE_* - fileExtentInline = 0 - fileExtentReg = 1 - fileExtentPrealloc = 2 + fileExtentInline fileExtentType = 0 + fileExtentReg fileExtentType = 1 + fileExtentPrealloc fileExtentType = 2 // Transaction id that created this extent // Max number of bytes to hold this extent in ram @@ -425,27 +437,27 @@ const ( // Grow this item struct at the end for future enhancements and keep // the existing values unchanged - devReplaceItemContReadingFromSrcdevModeAlways = 0 - devReplaceItemContReadingFromSrcdevModeAvoid = 1 - devReplaceItemStateNeverStarted = 0 - devReplaceItemStateStarted = 1 - devReplaceItemStateSuspended = 2 - devReplaceItemStateFinished = 3 - devReplaceItemStateCanceled = 4 + devReplaceItemContReadingFromSrcdevModeAlways = 0 + devReplaceItemContReadingFromSrcdevModeAvoid = 1 + devReplaceItemStateNeverStarted devReplaceItemState = 0 + devReplaceItemStateStarted devReplaceItemState = 1 + devReplaceItemStateSuspended devReplaceItemState = 2 + devReplaceItemStateFinished devReplaceItemState = 3 + devReplaceItemStateCanceled devReplaceItemState = 4 // Grow this item struct at the end for future enhancements and keep // the existing values unchanged // Different types of block groups (and chunks) - blockGroupData = (1 << 0) - blockGroupSystem = (1 << 1) - blockGroupMetadata = (1 << 2) - blockGroupRaid0 = (1 << 3) - blockGroupRaid1 = (1 << 4) - blockGroupDup = (1 << 5) - blockGroupRaid10 = (1 << 6) - blockGroupRaid5 = (1 << 7) - blockGroupRaid6 = (1 << 8) + blockGroupData blockGroup = (1 << 0) + blockGroupSystem blockGroup = (1 << 1) + blockGroupMetadata blockGroup = (1 << 2) + blockGroupRaid0 blockGroup = (1 << 3) + blockGroupRaid1 blockGroup = (1 << 4) + blockGroupDup blockGroup = (1 << 5) + blockGroupRaid10 blockGroup = (1 << 6) + blockGroupRaid5 blockGroup = (1 << 7) + blockGroupRaid6 blockGroup = (1 << 8) // We need a bit for restriper to be able to tell when chunks of type // SINGLE are available. This "extended" profile format is used in diff --git a/cmd/hgen.go b/cmd/hgen.go index 89d2b61..0d79bd0 100644 --- a/cmd/hgen.go +++ b/cmd/hgen.go @@ -19,6 +19,9 @@ var ( f_unexport = flag.Bool("u", true, "make all definitions unexported") f_goname = flag.Bool("g", true, "rename symbols to follow Go conventions") f_trim = flag.String("t", "", "prefix to trim from names") + + f_constSuf = flag.String("cs", "", "comma-separated list of constant suffixes to create typed constants") + f_constPref = flag.String("cp", "", "comma-separated list of constant prefixes to create typed constants") ) var ( @@ -26,8 +29,30 @@ var ( reNegULL = regexp.MustCompile(`-(\d+)ULL`) ) +var ( + constTypes []constType +) + +type constType struct { + Name string + Type string + Suffix string + Prefix string +} + func constName(s string) string { s = strings.TrimPrefix(s, *f_trim) + typ := "" + for _, t := range constTypes { + if t.Suffix != "" && strings.HasSuffix(s, t.Suffix) { + //s = strings.TrimSuffix(s, t.Suffix) + typ = t.Name + break + } else if t.Prefix != "" && strings.HasPrefix(s, t.Prefix) { + typ = t.Name + break + } + } if *f_goname { buf := bytes.NewBuffer(nil) buf.Grow(len(s)) @@ -49,6 +74,9 @@ func constName(s string) string { } else if *f_unexport { s = "_" + s } + if typ != "" { + s += " " + typ + } return s } @@ -67,7 +95,6 @@ func process(w io.Writer, path string) error { ) nl := true - fmt.Fprint(w, "// This code was auto-generated; DO NOT EDIT!\n\n") defer fmt.Fprintln(w, ")") for { line, err := r.ReadBytes('\n') @@ -136,8 +163,31 @@ func process(w io.Writer, path string) error { } } +func regConstTypes(str string, fnc func(*constType, string)) { + for _, s := range strings.Split(str, ",") { + kv := strings.Split(s, "=") + if len(kv) != 2 { + continue + } + st := strings.Split(kv[0], ":") + typ := "int" + if len(st) > 1 { + typ = st[1] + } + t := constType{Name: st[0], Type: typ} + fnc(&t, kv[1]) + constTypes = append(constTypes, t) + } +} + func main() { flag.Parse() + if suf := *f_constSuf; suf != "" { + regConstTypes(suf, func(t *constType, v string) { t.Suffix = v }) + } + if pref := *f_constPref; pref != "" { + regConstTypes(pref, func(t *constType, v string) { t.Prefix = v }) + } var w io.Writer = os.Stdout if path := *f_out; path != "" && path != "-" { file, err := os.Create(path) @@ -148,7 +198,11 @@ func main() { w = file } - fmt.Fprintf(w, "package %s\n", *f_pkg) + fmt.Fprintf(w, "package %s\n\n", *f_pkg) + fmt.Fprint(w, "// This code was auto-generated; DO NOT EDIT!\n\n") + for _, t := range constTypes { + fmt.Fprintf(w, "type %s %s\n\n", t.Name, t.Type) + } for _, path := range flag.Args() { if err := process(w, path); err != nil { log.Fatal(err) diff --git a/headers.go b/headers.go index f0b0a0f..2ff5713 100644 --- a/headers.go +++ b/headers.go @@ -1,4 +1,4 @@ package btrfs -//go:generate go run ./cmd/hgen.go -u -g -t BTRFS_ -p btrfs -o btrfs_tree_hc.go btrfs_tree.h +//go:generate go run ./cmd/hgen.go -u -g -t BTRFS_ -p btrfs -cs=treeKeyType:uint32=_KEY,objectID:uint64=_OBJECTID -cp=fileType=FT_,fileExtentType=FILE_EXTENT_,devReplaceItemState=DEV_REPLACE_ITEM_STATE_,blockGroup:uint64=BLOCK_GROUP_ -o btrfs_tree_hc.go btrfs_tree.h //go:generate gofmt -l -w btrfs_tree_hc.go diff --git a/ioctl_h.go b/ioctl_h.go index 11021c5..0903585 100644 --- a/ioctl_h.go +++ b/ioctl_h.go @@ -305,16 +305,27 @@ type btrfs_ioctl_balance_args struct { const _BTRFS_INO_LOOKUP_PATH_MAX = 4080 type btrfs_ioctl_ino_lookup_args struct { - treeid uint64 - objectid uint64 + treeid objectID + objectid objectID name [_BTRFS_INO_LOOKUP_PATH_MAX]byte } +func (arg *btrfs_ioctl_ino_lookup_args) Name() string { + n := 0 + for i, b := range arg.name { + if b == '\x00' { + n = i + break + } + } + return string(arg.name[:n]) +} + type btrfs_ioctl_search_key struct { - tree_id uint64 // which root are we searching. 0 is the tree of tree roots + tree_id objectID // which root are we searching. 0 is the tree of tree roots // keys returned will be >= min and <= max - min_objectid uint64 - max_objectid uint64 + min_objectid objectID + max_objectid objectID // keys returned will be >= min and <= max min_offset uint64 max_offset uint64 @@ -322,8 +333,8 @@ type btrfs_ioctl_search_key struct { min_transid uint64 max_transid uint64 // keys returned will be >= min and <= max - min_type uint32 - max_type uint32 + min_type treeKeyType + max_type treeKeyType // how many items did userland ask for, and how many are we returning nr_items uint32 _ [36]byte @@ -331,9 +342,9 @@ type btrfs_ioctl_search_key struct { type btrfs_ioctl_search_header struct { transid uint64 - objectid uint64 + objectid objectID offset uint64 - typ uint32 + typ treeKeyType len uint32 } @@ -538,8 +549,8 @@ const ( type btrfs_ioctl_send_args struct { send_fd int64 // in clone_sources_count uint64 // in - clone_sources *uint64 // in - parent_root uint64 // in + clone_sources *objectID // in + parent_root objectID // in flags uint64 // in _ [4]uint64 // in } @@ -679,8 +690,14 @@ func iocDefaultSubvol(f *os.File, out *uint64) error { return ioctl.Do(f, _BTRFS_IOC_DEFAULT_SUBVOL, out) } +type spaceFlags uint64 + +func (f spaceFlags) BlockGroup() blockGroup { + return blockGroup(f) & _BTRFS_BLOCK_GROUP_MASK +} + type spaceInfo struct { - Flags uint64 + Flags spaceFlags TotalBytes uint64 UsedBytes uint64 } @@ -715,7 +732,7 @@ func iocSpaceInfo(f *os.File) ([]spaceInfo, error) { for i := 0; i < int(n); i++ { info := (*btrfs_ioctl_space_info)(unsafe.Pointer(ptr)) out[i] = spaceInfo{ - Flags: info.flags, + Flags: spaceFlags(info.flags), TotalBytes: info.total_bytes, UsedBytes: info.used_bytes, } diff --git a/receive.go b/receive.go index 1136f7b..9a7fe58 100644 --- a/receive.go +++ b/receive.go @@ -48,7 +48,7 @@ func Receive(r io.Reader, dstDir string) error { } // We want to resolve the path to the subvolume we're sitting in // so that we can adjust the paths of any subvols we want to receive in. - subvolID, err := getPathRootID(mnt) + subvolID, err := getFileRootID(mnt) if err != nil { return err } diff --git a/send.go b/send.go index 356cd3e..c6fe22b 100644 --- a/send.go +++ b/send.go @@ -5,6 +5,7 @@ import ( "io" "os" "path/filepath" + "unsafe" ) func Send(w io.Writer, parent string, subvols ...string) error { @@ -23,20 +24,15 @@ func Send(w io.Writer, parent string, subvols ...string) error { return err } var ( - cloneSrc []uint64 - parentID uint64 + cloneSrc []objectID + parentID objectID ) if parent != "" { parent, err = filepath.Abs(parent) if err != nil { return err } - f, err := os.Open(parent) - if err != nil { - return fmt.Errorf("cannot open parent: %v", err) - } - id, err := getPathRootID(f) - f.Close() + id, err := getPathRootID(parent) if err != nil { return fmt.Errorf("cannot get parent root id: %v", err) } @@ -53,7 +49,7 @@ func Send(w io.Writer, parent string, subvols ...string) error { paths = append(paths, sub) mount, err := findMountRoot(sub) if err != nil { - return err + return fmt.Errorf("cannot find mount root for %v: %v", sub, err) } else if mount != mountRoot { return fmt.Errorf("all subvolumes must be from the same filesystem (%s is not)", sub) } @@ -64,14 +60,29 @@ func Send(w io.Writer, parent string, subvols ...string) error { return fmt.Errorf("subvolume %s is not read-only", sub) } } - //full := len(cloneSrc) == 0 + mfs, err := Open(mountRoot, true) + if err != nil { + return err + } + defer mfs.Close() + full := len(cloneSrc) == 0 for i, sub := range paths { - //if len(cloneSrc) > 1 { - // // TODO: find_good_parent - //} - //if !full { // TODO - // cloneSrc = append(cloneSrc, ) - //} + var rootID objectID + if !full && parent != "" { + rel, err := filepath.Rel(mountRoot, sub) + if err != nil { + return err + } + si, err := subvolSearchByPath(mfs.f, rel) + if err != nil { + return fmt.Errorf("cannot find subvolume %s: %v", rel, err) + } + rootID = si.RootID + parentID, err = findGoodParent(mfs.f, rootID, cloneSrc) + if err != nil { + return fmt.Errorf("cannot find good parent for %v: %v", rel, err) + } + } fs, err := Open(sub, true) if err != nil { return err @@ -88,11 +99,14 @@ func Send(w io.Writer, parent string, subvols ...string) error { if err != nil { return fmt.Errorf("error sending %s: %v", sub, err) } + if !full && parent != "" { + cloneSrc = append(cloneSrc, rootID) + } } return nil } -func send(w io.Writer, subvol *os.File, parent uint64, sources []uint64, flags uint64) error { +func send(w io.Writer, subvol *os.File, parent objectID, sources []objectID, flags uint64) error { pr, pw, err := os.Pipe() if err != nil { return err @@ -123,3 +137,104 @@ func send(w io.Writer, subvol *os.File, parent uint64, sources []uint64, flags u } return wait() } + +// readRootItem reads a root item from the tree. +// +// TODO(dennwc): support older kernels: +// In case we detect a root item smaller then sizeof(root_item), +// we know it's an old version of the root structure and initialize all new fields to zero. +// The same happens if we detect mismatching generation numbers as then we know the root was +// once mounted with an older kernel that was not aware of the root item structure change. +func readRootItem(mnt *os.File, rootID objectID) (*rootItem, error) { + sk := btrfs_ioctl_search_key{ + tree_id: rootTreeObjectid, + // There may be more than one ROOT_ITEM key if there are + // snapshots pending deletion, we have to loop through them. + min_objectid: rootID, + max_objectid: rootID, + min_type: rootItemKey, + max_type: rootItemKey, + max_offset: maxUint64, + max_transid: maxUint64, + nr_items: 4096, + } + for ; sk.min_offset < maxUint64; sk.min_offset++ { + results, err := treeSearchRaw(mnt, sk) + if err != nil { + return nil, err + } else if len(results) == 0 { + break + } + for _, r := range results { + sk.min_objectid = r.ObjectID + sk.min_type = r.Type + sk.min_offset = r.Offset + if r.ObjectID > rootID { + break + } + if r.ObjectID == rootID && r.Type == rootItemKey { + const sz = int(unsafe.Sizeof(btrfs_root_item_raw{})) + if len(r.Data) > sz { + return nil, fmt.Errorf("btrfs_root_item is larger than expected; kernel is newer than the library") + } else if len(r.Data) < sz { // TODO + return nil, fmt.Errorf("btrfs_root_item is smaller then expected; kernel version is too old") + } + p := asRootItem(r.Data).Decode() + return &p, nil + } + } + results = nil + if sk.min_type != rootItemKey || sk.min_objectid != rootID { + break + } + } + return nil, ErrNotFound +} + +func getParent(mnt *os.File, rootID objectID) (*subvolInfo, error) { + st, err := subvolSearchByRootID(mnt, rootID, "") + if err != nil { + return nil, err + } + return subvolSearchByUUID(mnt, st.ParentUUID) +} + +func findGoodParent(mnt *os.File, rootID objectID, cloneSrc []objectID) (objectID, error) { + parent, err := getParent(mnt, rootID) + if err != nil { + return 0, fmt.Errorf("get parent failed: %v", err) + } + for _, id := range cloneSrc { + if id == parent.RootID { + return parent.RootID, nil + } + } + var ( + bestParent *subvolInfo + bestDiff uint64 = maxUint64 + ) + for _, id := range cloneSrc { + parent2, err := getParent(mnt, id) + if err != nil { + return 0, err + } + if parent2.RootID != parent.RootID { + continue + } + parent2, err = subvolSearchByRootID(mnt, id, "") + if err != nil { + return 0, err + } + diff := parent2.CTransID - parent.CTransID + if diff < 0 { + diff = -diff + } + if diff < bestDiff { + bestParent, bestDiff = parent2, diff + } + } + if bestParent == nil { + return 0, ErrNotFound + } + return bestParent.RootID, nil +} diff --git a/subvolume.go b/subvolume.go index b268440..962c2c9 100644 --- a/subvolume.go +++ b/subvolume.go @@ -19,7 +19,7 @@ func IsSubVolume(path string) (bool, error) { if err := syscall.Stat(path, &st); err != nil { return false, &os.PathError{Op: "stat", Path: path, Err: err} } - if st.Ino != firstFreeObjectid || + if objectID(st.Ino) != firstFreeObjectid || st.Mode&syscall.S_IFMT != syscall.S_IFDIR { return false, nil } @@ -164,11 +164,11 @@ func GetFlags(path string) (SubvolFlags, error) { } type Subvolume struct { - ObjectID uint64 + ObjectID objectID TransID uint64 Name string RefTree uint64 - DirID uint64 + DirID objectID Gen uint64 OGen uint64 Flags uint64 @@ -179,10 +179,10 @@ type Subvolume struct { CTime time.Time } -func listSubVolumes(f *os.File) (map[uint64]Subvolume, error) { +func listSubVolumes(f *os.File) (map[objectID]Subvolume, error) { sk := btrfs_ioctl_search_key{ // search in the tree of tree roots - tree_id: 1, + tree_id: rootTreeObjectid, // Set the min and max to backref keys. The search will // only send back this type of key now. @@ -199,7 +199,7 @@ func listSubVolumes(f *os.File) (map[uint64]Subvolume, error) { nr_items: 4096, // just a big number, doesn't matter much } - m := make(map[uint64]Subvolume) + m := make(map[objectID]Subvolume) for { out, err := treeSearchRaw(f, sk) if err != nil { @@ -259,12 +259,17 @@ func listSubVolumes(f *os.File) (map[uint64]Subvolume, error) { } type subvolInfo struct { - RootID uint64 + RootID objectID UUID UUID ParentUUID UUID ReceivedUUID UUID + CTime time.Time + OTime time.Time + STime time.Time + RTime time.Time + CTransID uint64 OTransID uint64 STransID uint64 @@ -278,7 +283,7 @@ func subvolSearchByUUID(mnt *os.File, uuid UUID) (*subvolInfo, error) { if err != nil { return nil, err } - return subvolSearchByRootID(mnt, id) + return subvolSearchByRootID(mnt, id, "") } func subvolSearchByReceivedUUID(mnt *os.File, uuid UUID) (*subvolInfo, error) { @@ -286,15 +291,95 @@ func subvolSearchByReceivedUUID(mnt *os.File, uuid UUID) (*subvolInfo, error) { if err != nil { return nil, err } - return subvolSearchByRootID(mnt, id) + return subvolSearchByRootID(mnt, id, "") } func subvolSearchByPath(mnt *os.File, path string) (*subvolInfo, error) { - var id uint64 - panic("not implemented") - return subvolSearchByRootID(mnt, id) + if !filepath.IsAbs(path) { + path = filepath.Join(mnt.Name(), path) + } + id, err := getPathRootID(path) + if err != nil { + return nil, err + } + return subvolSearchByRootID(mnt, id, path) +} + +func subvolidResolve(mnt *os.File, subvolID objectID) (string, error) { + return subvolidResolveSub(mnt, "", subvolID) } -func subvolSearchByRootID(mnt *os.File, rootID uint64) (*subvolInfo, error) { - panic("not implemented") +func subvolidResolveSub(mnt *os.File, path string, subvolID objectID) (string, error) { + if subvolID == fsTreeObjectid { + return "", nil + } + sk := btrfs_ioctl_search_key{ + tree_id: rootTreeObjectid, + min_objectid: subvolID, + max_objectid: subvolID, + min_type: rootBackrefKey, + max_type: rootBackrefKey, + max_offset: maxUint64, + max_transid: maxUint64, + nr_items: 1, + } + results, err := treeSearchRaw(mnt, sk) + if err != nil { + return "", err + } else if len(results) < 1 { + return "", ErrNotFound + } + res := results[0] + if objectID(res.Offset) != fsTreeObjectid { + spath, err := subvolidResolveSub(mnt, path, objectID(res.Offset)) + if err != nil { + return "", err + } + path = spath + "/" + } + backRef := asRootRef(res.Data) + if backRef.DirID != firstFreeObjectid { + arg := btrfs_ioctl_ino_lookup_args{ + treeid: objectID(res.Offset), + objectid: backRef.DirID, + } + if err := iocInoLookup(mnt, &arg); err != nil { + return "", err + } + path += arg.Name() + } + return path + backRef.Name, nil +} + +// subvolSearchByRootID +// +// Path is optional, and will be resolved automatically if not set. +func subvolSearchByRootID(mnt *os.File, rootID objectID, path string) (*subvolInfo, error) { + robj, err := readRootItem(mnt, rootID) + if err != nil { + return nil, err + } + info := &subvolInfo{ + RootID: rootID, + + UUID: robj.UUID, + ReceivedUUID: robj.ReceivedUUID, + ParentUUID: robj.ParentUUID, + + CTime: robj.CTime, + OTime: robj.OTime, + STime: robj.STime, + RTime: robj.RTime, + + CTransID: robj.CTransID, + OTransID: robj.OTransID, + STransID: robj.STransID, + RTransID: robj.RTransID, + + Path: path, + } + if path == "" { + info.Path, err = subvolidResolve(mnt, info.RootID) + } + return info, err } diff --git a/usage.go b/usage.go index 6f10f6a..792ea86 100644 --- a/usage.go +++ b/usage.go @@ -6,8 +6,8 @@ import ( "syscall" ) -func cmpChunkBlockGroup(f1, f2 uint64) int { - var mask uint64 +func cmpChunkBlockGroup(f1, f2 blockGroup) int { + var mask blockGroup if (f1 & _BTRFS_BLOCK_GROUP_TYPE_MASK) == (f2 & _BTRFS_BLOCK_GROUP_TYPE_MASK) { @@ -34,7 +34,7 @@ type spaceInfoByBlockGroup []spaceInfo func (a spaceInfoByBlockGroup) Len() int { return len(a) } func (a spaceInfoByBlockGroup) Swap(i, j int) { a[i], a[j] = a[j], a[i] } func (a spaceInfoByBlockGroup) Less(i, j int) bool { - return cmpChunkBlockGroup(a[i].Flags, a[j].Flags) < 0 + return cmpChunkBlockGroup(blockGroup(a[i].Flags), blockGroup(a[j].Flags)) < 0 } type UsageInfo struct { @@ -93,41 +93,42 @@ func spaceUsage(f *os.File) (UsageInfo, error) { ) for _, s := range spaces { ratio := 1 + bg := s.Flags.BlockGroup() switch { - case s.Flags&blockGroupRaid0 != 0: + case bg&blockGroupRaid0 != 0: ratio = 1 - case s.Flags&blockGroupRaid1 != 0: + case bg&blockGroupRaid1 != 0: ratio = 2 - case s.Flags&blockGroupRaid5 != 0: + case bg&blockGroupRaid5 != 0: ratio = 0 - case s.Flags&blockGroupRaid6 != 0: + case bg&blockGroupRaid6 != 0: ratio = 0 - case s.Flags&blockGroupDup != 0: + case bg&blockGroupDup != 0: ratio = 2 - case s.Flags&blockGroupRaid10 != 0: + case bg&blockGroupRaid10 != 0: ratio = 2 } if ratio > maxDataRatio { maxDataRatio = ratio } - if s.Flags&spaceInfoGlobalRsv != 0 { + if bg&spaceInfoGlobalRsv != 0 { u.GlobalReserve = s.TotalBytes u.GlobalReserveUsed = s.UsedBytes } - if s.Flags&(blockGroupData|blockGroupMetadata) == (blockGroupData | blockGroupMetadata) { + if bg&(blockGroupData|blockGroupMetadata) == (blockGroupData | blockGroupMetadata) { mixed = true } - if s.Flags&blockGroupData != 0 { + if bg&blockGroupData != 0 { u.RawDataUsed += s.UsedBytes * uint64(ratio) u.RawDataChunks += s.TotalBytes * uint64(ratio) u.LogicalDataChunks += s.TotalBytes } - if s.Flags&blockGroupMetadata != 0 { + if bg&blockGroupMetadata != 0 { u.RawMetaUsed += s.UsedBytes * uint64(ratio) u.RawMetaChunks += s.TotalBytes * uint64(ratio) u.LogicalMetaChunks += s.TotalBytes } - if s.Flags&blockGroupSystem != 0 { + if bg&blockGroupSystem != 0 { u.SystemUsed += s.UsedBytes * uint64(ratio) u.SystemChunks += s.TotalBytes * uint64(ratio) } diff --git a/utils.go b/utils.go index b4c9408..45aede7 100644 --- a/utils.go +++ b/utils.go @@ -66,8 +66,8 @@ func openDir(path string) (*os.File, error) { type searchResult struct { TransID uint64 - ObjectID uint64 - Type uint32 + ObjectID objectID + Type treeKeyType Offset uint64 Data []byte } diff --git a/uuid_tree.go b/uuid_tree.go index d3a1425..4906ad0 100644 --- a/uuid_tree.go +++ b/uuid_tree.go @@ -6,23 +6,23 @@ import ( "os" ) -func lookupUUIDSubvolItem(f *os.File, uuid UUID) (uint64, error) { +func lookupUUIDSubvolItem(f *os.File, uuid UUID) (objectID, error) { return uuidTreeLookupAny(f, uuid, uuidKeySubvol) } -func lookupUUIDReceivedSubvolItem(f *os.File, uuid UUID) (uint64, error) { +func lookupUUIDReceivedSubvolItem(f *os.File, uuid UUID) (objectID, error) { return uuidTreeLookupAny(f, uuid, uuidKeyReceivedSubvol) } -func (id UUID) toKey() (objID, off uint64) { - objID = binary.LittleEndian.Uint64(id[:8]) +func (id UUID) toKey() (objID objectID, off uint64) { + objID = objectID(binary.LittleEndian.Uint64(id[:8])) off = binary.LittleEndian.Uint64(id[8:16]) return } // uuidTreeLookupAny searches uuid tree for a given uuid in specified field. // It returns ErrNotFound if object was not found. -func uuidTreeLookupAny(f *os.File, uuid UUID, typ uint32) (uint64, error) { +func uuidTreeLookupAny(f *os.File, uuid UUID, typ treeKeyType) (objectID, error) { objId, off := uuid.toKey() args := btrfs_ioctl_search_key{ tree_id: uuidTreeObjectid, @@ -45,5 +45,5 @@ func uuidTreeLookupAny(f *os.File, uuid UUID, typ uint32) (uint64, error) { if len(out.Data) != 8 { return 0, fmt.Errorf("btrfs: uuid item with illegal size %d", len(out.Data)) } - return binary.LittleEndian.Uint64(out.Data), nil + return objectID(binary.LittleEndian.Uint64(out.Data)), nil } From eb1e931d4c55bacb4f8485ad97565afcc9ee8bca Mon Sep 17 00:00:00 2001 From: Denys Smirnov Date: Mon, 9 Jan 2017 15:31:52 +0200 Subject: [PATCH 19/38] expose balance ioc (cherry picked from commit dennwc/btrfs@51976cacd426b6471a8f0ccbc6f3c71754ace181) Signed-off-by: Akihiro Suda --- btrfs.go | 6 ++++++ btrfs_h.go | 17 ++++++++++++++++- ioctl_h.go | 47 +++++++++++++++++++++++++++-------------------- size_test.go | 2 +- 4 files changed, 50 insertions(+), 22 deletions(-) diff --git a/btrfs.go b/btrfs.go index b5e8497..651c1a2 100644 --- a/btrfs.go +++ b/btrfs.go @@ -223,3 +223,9 @@ func (f *FS) ListSubvolumes(filter func(Subvolume) bool) ([]Subvolume, error) { } func (f *FS) Usage() (UsageInfo, error) { return spaceUsage(f.f) } + +func (f *FS) Balance(flags BalanceFlags) (BalanceProgress, error) { + args := btrfs_ioctl_balance_args{flags: flags} + err := iocBalanceV2(f.f, &args) + return args.stat, err +} diff --git a/btrfs_h.go b/btrfs_h.go index b1af5c4..192af83 100644 --- a/btrfs_h.go +++ b/btrfs_h.go @@ -4,7 +4,7 @@ import "strings" const maxUint64 = 1<<64 - 1 -const BTRFS_LABEL_SIZE = 256 +const labelSize = 256 type FeatureFlags uint64 @@ -56,3 +56,18 @@ const ( FeatureIncompatSkinnyMetadata = IncompatFeatures(1 << 8) FeatureIncompatNoHoles = IncompatFeatures(1 << 9) ) + +// Flags definition for balance. +type BalanceFlags uint64 + +// Restriper's general type filter. +const ( + BalanceData = BalanceFlags(1 << 0) + BalanceSystem = BalanceFlags(1 << 1) + BalanceMetadata = BalanceFlags(1 << 2) + + BalanceMask = (BalanceData | BalanceSystem | BalanceMetadata) + + BalanceForce = BalanceFlags(1 << 3) + BalanceResume = BalanceFlags(1 << 4) +) diff --git a/ioctl_h.go b/ioctl_h.go index 0903585..d21d93a 100644 --- a/ioctl_h.go +++ b/ioctl_h.go @@ -277,29 +277,31 @@ type btrfs_balance_args struct { _ [48]byte } -// report balance progress to userspace -type btrfs_balance_progress struct { - expected uint64 // estimated # of chunks that will be relocated to fulfill the request - considered uint64 // # of chunks we have considered so far - completed uint64 // # of chunks relocated so far +// Report balance progress to userspace. +// +// btrfs_balance_progress +type BalanceProgress struct { + Expected uint64 // estimated # of chunks that will be relocated to fulfill the request + Considered uint64 // # of chunks we have considered so far + Completed uint64 // # of chunks relocated so far } -type balanceState uint64 +type BalanceState uint64 const ( - _BTRFS_BALANCE_STATE_RUNNING balanceState = (1 << 0) - _BTRFS_BALANCE_STATE_PAUSE_REQ balanceState = (1 << 1) - _BTRFS_BALANCE_STATE_CANCEL_REQ balanceState = (1 << 2) + BalanceStateRunning BalanceState = (1 << 0) + BalanceStatePauseReq BalanceState = (1 << 1) + BalanceStateCancelReq BalanceState = (1 << 2) ) type btrfs_ioctl_balance_args struct { - flags uint64 // in/out - state balanceState // out - data btrfs_balance_args // in/out - meta btrfs_balance_args // in/out - sys btrfs_balance_args // in/out - stat btrfs_balance_progress // out - _ [72 * 8]byte // pad to 1k + flags BalanceFlags // in/out + state BalanceState // out + data btrfs_balance_args // in/out + meta btrfs_balance_args // in/out + sys btrfs_balance_args // in/out + stat BalanceProgress // out + _ [72 * 8]byte // pad to 1k } const _BTRFS_INO_LOOKUP_PATH_MAX = 4080 @@ -586,6 +588,7 @@ var ( _BTRFS_IOC_SCRUB_PROGRESS = ioctl.IOWR(ioctlMagic, 29, unsafe.Sizeof(btrfs_ioctl_scrub_args{})) _BTRFS_IOC_DEV_INFO = ioctl.IOWR(ioctlMagic, 30, unsafe.Sizeof(btrfs_ioctl_dev_info_args{})) _BTRFS_IOC_FS_INFO = ioctl.IOR(ioctlMagic, 31, unsafe.Sizeof(btrfs_ioctl_fs_info_args{})) + _BTRFS_IOC_BALANCE_V2 = ioctl.IOWR(ioctlMagic, 32, unsafe.Sizeof(btrfs_ioctl_balance_args{})) _BTRFS_IOC_BALANCE_CTL = ioctl.IOW(ioctlMagic, 33, 4) // int32 _BTRFS_IOC_BALANCE_PROGRESS = ioctl.IOR(ioctlMagic, 34, unsafe.Sizeof(btrfs_ioctl_balance_args{})) _BTRFS_IOC_INO_PATHS = ioctl.IOWR(ioctlMagic, 35, unsafe.Sizeof(btrfs_ioctl_ino_path_args{})) @@ -600,8 +603,8 @@ var ( _BTRFS_IOC_QUOTA_RESCAN = ioctl.IOW(ioctlMagic, 44, unsafe.Sizeof(btrfs_ioctl_quota_rescan_args{})) _BTRFS_IOC_QUOTA_RESCAN_STATUS = ioctl.IOR(ioctlMagic, 45, unsafe.Sizeof(btrfs_ioctl_quota_rescan_args{})) _BTRFS_IOC_QUOTA_RESCAN_WAIT = ioctl.IO(ioctlMagic, 46) - _BTRFS_IOC_GET_FSLABEL = ioctl.IOR(ioctlMagic, 49, BTRFS_LABEL_SIZE) - _BTRFS_IOC_SET_FSLABEL = ioctl.IOW(ioctlMagic, 50, BTRFS_LABEL_SIZE) + _BTRFS_IOC_GET_FSLABEL = ioctl.IOR(ioctlMagic, 49, labelSize) + _BTRFS_IOC_SET_FSLABEL = ioctl.IOW(ioctlMagic, 50, labelSize) _BTRFS_IOC_GET_DEV_STATS = ioctl.IOWR(ioctlMagic, 52, unsafe.Sizeof(btrfs_ioctl_get_dev_stats{})) _BTRFS_IOC_DEV_REPLACE = ioctl.IOWR(ioctlMagic, 53, unsafe.Sizeof(btrfs_ioctl_dev_replace_args_u1{})) _BTRFS_IOC_FILE_EXTENT_SAME = ioctl.IOWR(ioctlMagic, 54, unsafe.Sizeof(btrfs_ioctl_same_args{})) @@ -783,6 +786,10 @@ func iocDevInfo(f *os.File, devid uint64, uuid UUID) (out btrfs_ioctl_dev_info_a return } +func iocBalanceV2(f *os.File, out *btrfs_ioctl_balance_args) error { + return ioctl.Do(f, _BTRFS_IOC_BALANCE_V2, out) +} + func iocBalanceCtl(f *os.File, out *int32) error { return ioctl.Do(f, _BTRFS_IOC_BALANCE_CTL, out) } @@ -839,11 +846,11 @@ func iocQuotaRescanWait(f *os.File) error { return ioctl.Do(f, _BTRFS_IOC_QUOTA_RESCAN_WAIT, nil) } -func iocGetFslabel(f *os.File, out *[BTRFS_LABEL_SIZE]byte) error { +func iocGetFslabel(f *os.File, out *[labelSize]byte) error { return ioctl.Do(f, _BTRFS_IOC_GET_FSLABEL, out) } -func iocSetFslabel(f *os.File, out *[BTRFS_LABEL_SIZE]byte) error { +func iocSetFslabel(f *os.File, out *[labelSize]byte) error { return ioctl.Do(f, _BTRFS_IOC_SET_FSLABEL, out) } diff --git a/size_test.go b/size_test.go index 01c813c..5c2c10d 100644 --- a/size_test.go +++ b/size_test.go @@ -25,7 +25,7 @@ var caseSizes = []struct { {obj: btrfs_ioctl_fs_info_args{}, size: 1024}, {obj: btrfs_ioctl_feature_flags{}, size: 24}, {obj: btrfs_balance_args{}, size: 136}, - {obj: btrfs_balance_progress{}, size: 24}, + {obj: BalanceProgress{}, size: 24}, {obj: btrfs_ioctl_balance_args{}, size: 1024}, {obj: btrfs_ioctl_ino_lookup_args{}, size: 4096}, {obj: btrfs_ioctl_search_key{}, size: 104}, From 14ca6274674600544f96d5da29fc35b7b47a88e2 Mon Sep 17 00:00:00 2001 From: Denys Smirnov Date: Mon, 9 Jan 2017 17:03:55 +0200 Subject: [PATCH 20/38] expose subvolume search functions; switch subvolume info structure (cherry picked from commit dennwc/btrfs@00defaf3f2472e61d5692ec87a366f253273c644) Signed-off-by: Akihiro Suda --- btrfs.go | 33 ++++++++++----- btrfs_test.go | 8 ++-- send.go | 4 +- subvolume.go | 112 ++++++++++++++++++++++++-------------------------- 4 files changed, 82 insertions(+), 75 deletions(-) diff --git a/btrfs.go b/btrfs.go index 651c1a2..63e1d87 100644 --- a/btrfs.go +++ b/btrfs.go @@ -203,25 +203,38 @@ func (f *FS) ReceiveTo(r io.Reader, mount string) error { return Receive(r, filepath.Join(f.f.Name(), mount)) } -func (f *FS) ListSubvolumes(filter func(Subvolume) bool) ([]Subvolume, error) { - //root, err := getPathRootID(f) - //if err != nil { - // return nil, fmt.Errorf("can't get rootid for '%s': %v", path, err) - //} - m, err := listSubVolumes(f.f) +func (f *FS) ListSubvolumes(filter func(SubvolInfo) bool) ([]SubvolInfo, error) { + m, err := listSubVolumes(f.f, filter) if err != nil { return nil, err } - out := make([]Subvolume, 0, len(m)) + out := make([]SubvolInfo, 0, len(m)) for _, v := range m { - if filter != nil && !filter(v) { - continue - } out = append(out, v) } return out, nil } +func (f *FS) SubvolumeByUUID(uuid UUID) (*SubvolInfo, error) { + id, err := lookupUUIDSubvolItem(f.f, uuid) + if err != nil { + return nil, err + } + return subvolSearchByRootID(f.f, id, "") +} + +func (f *FS) SubvolumeByReceivedUUID(uuid UUID) (*SubvolInfo, error) { + id, err := lookupUUIDReceivedSubvolItem(f.f, uuid) + if err != nil { + return nil, err + } + return subvolSearchByRootID(f.f, id, "") +} + +func (f *FS) SubvolumeByPath(path string) (*SubvolInfo, error) { + return subvolSearchByPath(f.f, path) +} + func (f *FS) Usage() (UsageInfo, error) { return spaceUsage(f.f) } func (f *FS) Balance(flags BalanceFlags) (BalanceProgress, error) { diff --git a/btrfs_test.go b/btrfs_test.go index 2f5b611..9e457ca 100644 --- a/btrfs_test.go +++ b/btrfs_test.go @@ -111,8 +111,8 @@ func TestSubvolumes(t *testing.T) { if s.UUID.IsZero() { t.Fatalf("zero uuid in %+v", s) } - if s.Name != "" { - got = append(got, s.Name) + if s.Path != "" { + got = append(got, s.Path) } } sort.Strings(got) @@ -131,12 +131,12 @@ func TestSubvolumes(t *testing.T) { } expect([]string{ "foo", "bar", "baz", - "foo", "bar", "baz", + "foo/foo", "foo/bar", "foo/baz", }) delsub("foo/bar") expect([]string{ "foo", "bar", "baz", - "foo", "baz", + "foo/foo", "foo/baz", }) path := filepath.Join(names[0], names[2]) diff --git a/send.go b/send.go index c6fe22b..e24ae4e 100644 --- a/send.go +++ b/send.go @@ -191,7 +191,7 @@ func readRootItem(mnt *os.File, rootID objectID) (*rootItem, error) { return nil, ErrNotFound } -func getParent(mnt *os.File, rootID objectID) (*subvolInfo, error) { +func getParent(mnt *os.File, rootID objectID) (*SubvolInfo, error) { st, err := subvolSearchByRootID(mnt, rootID, "") if err != nil { return nil, err @@ -210,7 +210,7 @@ func findGoodParent(mnt *os.File, rootID objectID, cloneSrc []objectID) (objectI } } var ( - bestParent *subvolInfo + bestParent *SubvolInfo bestDiff uint64 = maxUint64 ) for _, id := range cloneSrc { diff --git a/subvolume.go b/subvolume.go index 962c2c9..7fd4e82 100644 --- a/subvolume.go +++ b/subvolume.go @@ -163,23 +163,7 @@ func GetFlags(path string) (SubvolFlags, error) { return fs.GetFlags() } -type Subvolume struct { - ObjectID objectID - TransID uint64 - Name string - RefTree uint64 - DirID objectID - Gen uint64 - OGen uint64 - Flags uint64 - UUID UUID - ParentUUID UUID - ReceivedUUID UUID - OTime time.Time - CTime time.Time -} - -func listSubVolumes(f *os.File) (map[objectID]Subvolume, error) { +func listSubVolumes(f *os.File, filter func(SubvolInfo) bool) (map[objectID]SubvolInfo, error) { sk := btrfs_ioctl_search_key{ // search in the tree of tree roots tree_id: rootTreeObjectid, @@ -199,7 +183,7 @@ func listSubVolumes(f *os.File) (map[objectID]Subvolume, error) { nr_items: 4096, // just a big number, doesn't matter much } - m := make(map[objectID]Subvolume) + m := make(map[objectID]SubvolInfo) for { out, err := treeSearchRaw(f, sk) if err != nil { @@ -209,28 +193,20 @@ func listSubVolumes(f *os.File) (map[objectID]Subvolume, error) { } for _, obj := range out { switch obj.Type { - case rootBackrefKey: - ref := asRootRef(obj.Data) - o := m[obj.ObjectID] - o.TransID = obj.TransID - o.ObjectID = obj.ObjectID - o.RefTree = obj.Offset - o.DirID = ref.DirID - o.Name = ref.Name - m[obj.ObjectID] = o + //case rootBackrefKey: + // ref := asRootRef(obj.Data) + // o := m[obj.ObjectID] + // o.TransID = obj.TransID + // o.ObjectID = obj.ObjectID + // o.RefTree = obj.Offset + // o.DirID = ref.DirID + // o.Name = ref.Name + // m[obj.ObjectID] = o case rootItemKey: o := m[obj.ObjectID] - o.TransID = obj.TransID - o.ObjectID = obj.ObjectID + o.RootID = obj.ObjectID robj := asRootItem(obj.Data).Decode() - o.Gen = robj.Gen - o.Flags = robj.Flags - o.CTime = robj.CTime - o.OTime = robj.OTime - o.OGen = robj.GenV2 - o.UUID = robj.UUID - o.ParentUUID = robj.ParentUUID - o.ReceivedUUID = robj.ReceivedUUID + o.fillFromItem(&robj) m[obj.ObjectID] = o } } @@ -255,10 +231,26 @@ func listSubVolumes(f *os.File) (map[objectID]Subvolume, error) { break } } + // resolve paths + for id, v := range m { + if path, err := subvolidResolve(f, id); err == ErrNotFound { + delete(m, id) + continue + } else if err != nil { + return m, fmt.Errorf("cannot resolve path for %v: %v", id, err) + } else { + v.Path = path + m[id] = v + } + if filter != nil && !filter(v) { + delete(m, id) + } + } + return m, nil } -type subvolInfo struct { +type SubvolInfo struct { RootID objectID UUID UUID @@ -278,7 +270,23 @@ type subvolInfo struct { Path string } -func subvolSearchByUUID(mnt *os.File, uuid UUID) (*subvolInfo, error) { +func (s *SubvolInfo) fillFromItem(it *rootItem) { + s.UUID = it.UUID + s.ReceivedUUID = it.ReceivedUUID + s.ParentUUID = it.ParentUUID + + s.CTime = it.CTime + s.OTime = it.OTime + s.STime = it.STime + s.RTime = it.RTime + + s.CTransID = it.CTransID + s.OTransID = it.OTransID + s.STransID = it.STransID + s.RTransID = it.RTransID +} + +func subvolSearchByUUID(mnt *os.File, uuid UUID) (*SubvolInfo, error) { id, err := lookupUUIDSubvolItem(mnt, uuid) if err != nil { return nil, err @@ -286,7 +294,7 @@ func subvolSearchByUUID(mnt *os.File, uuid UUID) (*subvolInfo, error) { return subvolSearchByRootID(mnt, id, "") } -func subvolSearchByReceivedUUID(mnt *os.File, uuid UUID) (*subvolInfo, error) { +func subvolSearchByReceivedUUID(mnt *os.File, uuid UUID) (*SubvolInfo, error) { id, err := lookupUUIDReceivedSubvolItem(mnt, uuid) if err != nil { return nil, err @@ -294,7 +302,7 @@ func subvolSearchByReceivedUUID(mnt *os.File, uuid UUID) (*subvolInfo, error) { return subvolSearchByRootID(mnt, id, "") } -func subvolSearchByPath(mnt *os.File, path string) (*subvolInfo, error) { +func subvolSearchByPath(mnt *os.File, path string) (*SubvolInfo, error) { if !filepath.IsAbs(path) { path = filepath.Join(mnt.Name(), path) } @@ -354,30 +362,16 @@ func subvolidResolveSub(mnt *os.File, path string, subvolID objectID) (string, e // subvolSearchByRootID // // Path is optional, and will be resolved automatically if not set. -func subvolSearchByRootID(mnt *os.File, rootID objectID, path string) (*subvolInfo, error) { +func subvolSearchByRootID(mnt *os.File, rootID objectID, path string) (*SubvolInfo, error) { robj, err := readRootItem(mnt, rootID) if err != nil { return nil, err } - info := &subvolInfo{ + info := &SubvolInfo{ RootID: rootID, - - UUID: robj.UUID, - ReceivedUUID: robj.ReceivedUUID, - ParentUUID: robj.ParentUUID, - - CTime: robj.CTime, - OTime: robj.OTime, - STime: robj.STime, - RTime: robj.RTime, - - CTransID: robj.CTransID, - OTransID: robj.OTransID, - STransID: robj.STransID, - RTransID: robj.RTransID, - - Path: path, + Path: path, } + info.fillFromItem(robj) if path == "" { info.Path, err = subvolidResolve(mnt, info.RootID) } From 8dce338a994c6f37f8e96c86288b73811182bc54 Mon Sep 17 00:00:00 2001 From: Denys Smirnov Date: Mon, 9 Jan 2017 17:53:50 +0200 Subject: [PATCH 21/38] send: fix diff int overflow (cherry picked from commit dennwc/btrfs@cf180d91dccdbed6b4f2d9c1761271a1506e7f4c) Signed-off-by: Akihiro Suda --- send.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/send.go b/send.go index e24ae4e..be6b70b 100644 --- a/send.go +++ b/send.go @@ -225,12 +225,12 @@ func findGoodParent(mnt *os.File, rootID objectID, cloneSrc []objectID) (objectI if err != nil { return 0, err } - diff := parent2.CTransID - parent.CTransID + diff := int64(parent2.CTransID - parent.CTransID) if diff < 0 { diff = -diff } - if diff < bestDiff { - bestParent, bestDiff = parent2, diff + if uint64(diff) < bestDiff { + bestParent, bestDiff = parent2, uint64(diff) } } if bestParent == nil { From 434eac4fe969b137d7fa79c76bc9dfc277ae96ec Mon Sep 17 00:00:00 2001 From: Denys Smirnov Date: Tue, 10 Jan 2017 16:30:47 +0200 Subject: [PATCH 22/38] send: recursive best parent search (cherry picked from commit dennwc/btrfs@532eaaa4b741d00e8bf96a03b3d92dd301b409d4) Signed-off-by: Akihiro Suda --- send.go | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/send.go b/send.go index be6b70b..5038e9e 100644 --- a/send.go +++ b/send.go @@ -194,7 +194,7 @@ func readRootItem(mnt *os.File, rootID objectID) (*rootItem, error) { func getParent(mnt *os.File, rootID objectID) (*SubvolInfo, error) { st, err := subvolSearchByRootID(mnt, rootID, "") if err != nil { - return nil, err + return nil, fmt.Errorf("cannot find subvolume %d to determine parent: %v", rootID, err) } return subvolSearchByUUID(mnt, st.ParentUUID) } @@ -215,7 +215,9 @@ func findGoodParent(mnt *os.File, rootID objectID, cloneSrc []objectID) (objectI ) for _, id := range cloneSrc { parent2, err := getParent(mnt, id) - if err != nil { + if err == ErrNotFound { + continue + } else if err != nil { return 0, err } if parent2.RootID != parent.RootID { @@ -233,8 +235,11 @@ func findGoodParent(mnt *os.File, rootID objectID, cloneSrc []objectID) (objectI bestParent, bestDiff = parent2, uint64(diff) } } - if bestParent == nil { - return 0, ErrNotFound + if bestParent != nil { + return bestParent.RootID, nil + } + if !parent.ParentUUID.IsZero() { + return findGoodParent(mnt, parent.RootID, cloneSrc) } - return bestParent.RootID, nil + return 0, ErrNotFound } From a39a792d6e011dfa4b0edf29759637d5c7d11cb1 Mon Sep 17 00:00:00 2001 From: Denys Smirnov Date: Wed, 11 Jan 2017 18:53:23 +0200 Subject: [PATCH 23/38] expose resize (cherry picked from commit dennwc/btrfs@3db12b229437b25b099c3ae2f82a6bbb0678a445) Signed-off-by: Akihiro Suda --- btrfs.go | 20 ++++++++++++++++ btrfs_test.go | 60 +++++++++++++++++++++++++++++++++++++++++++++++ ioctl_h.go | 9 +++++-- test/btrfstest.go | 28 +++++++++++++--------- 4 files changed, 104 insertions(+), 13 deletions(-) diff --git a/btrfs.go b/btrfs.go index 63e1d87..fc2a483 100644 --- a/btrfs.go +++ b/btrfs.go @@ -6,6 +6,7 @@ import ( "io" "os" "path/filepath" + "strconv" "syscall" ) @@ -242,3 +243,22 @@ func (f *FS) Balance(flags BalanceFlags) (BalanceProgress, error) { err := iocBalanceV2(f.f, &args) return args.stat, err } + +func (f *FS) Resize(size int64) error { + amount := strconv.FormatInt(size, 10) + args := &btrfs_ioctl_vol_args{} + args.SetName(amount) + if err := iocResize(f.f, args); err != nil { + return fmt.Errorf("resize failed: %v", err) + } + return nil +} + +func (f *FS) ResizeToMax() error { + args := &btrfs_ioctl_vol_args{} + args.SetName("max") + if err := iocResize(f.f, args); err != nil { + return fmt.Errorf("resize failed: %v", err) + } + return nil +} diff --git a/btrfs_test.go b/btrfs_test.go index 9e457ca..f31b9c4 100644 --- a/btrfs_test.go +++ b/btrfs_test.go @@ -3,6 +3,7 @@ package btrfs import ( "github.com/dennwc/btrfs/test" "io" + "io/ioutil" "os" "path/filepath" "reflect" @@ -215,3 +216,62 @@ func TestCloneFile(t *testing.T) { t.Fatalf("wrong data returned: %q", string(buf)) } } + +func TestResize(t *testing.T) { + dir, err := ioutil.TempDir("", "btrfs_data_") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(dir) + fname := filepath.Join(dir, "data") + if err = btrfstest.Mkfs(fname, sizeDef); err != nil { + t.Fatal(err) + } + mnt := filepath.Join(dir, "mnt") + if err = os.MkdirAll(mnt, 0755); err != nil { + t.Fatal(err) + } + if err = btrfstest.Mount(mnt, fname); err != nil { + t.Fatal(err) + } + defer btrfstest.Unmount(mnt) + + fs, err := Open(mnt, false) + if err != nil { + t.Fatal(err) + } + st, err := fs.Usage() + fs.Close() + if err != nil { + t.Fatal(err) + } + + if err = btrfstest.Unmount(mnt); err != nil { + t.Fatal(err) + } + var newSize int64 = sizeDef + newSize = int64(float64(newSize) * 1.1) + if err = os.Truncate(fname, newSize); err != nil { + t.Fatal(err) + } + if err = btrfstest.Mount(mnt, fname); err != nil { + t.Fatal(err) + } + + fs, err = Open(mnt, false) + if err != nil { + t.Fatal(err) + } + defer fs.Close() + + if err = fs.ResizeToMax(); err != nil { + t.Fatal(err) + } + + st2, err := fs.Usage() + if err != nil { + t.Fatal(err) + } else if st.Total >= st2.Total { + t.Fatal("to resized:", st.Total, st2.Total) + } +} diff --git a/ioctl_h.go b/ioctl_h.go index d21d93a..a35a123 100644 --- a/ioctl_h.go +++ b/ioctl_h.go @@ -60,6 +60,11 @@ type btrfs_ioctl_vol_args struct { name [volNameMax + 1]byte } +func (arg *btrfs_ioctl_vol_args) SetName(name string) { + n := copy(arg.name[:], name) + arg.name[n] = 0 +} + type btrfs_qgroup_limit struct { flags uint64 max_referenced uint64 @@ -625,8 +630,8 @@ func iocDefrag(f *os.File, out *btrfs_ioctl_vol_args) error { return ioctl.Do(f, _BTRFS_IOC_DEFRAG, out) } -func iocResize(f *os.File, out *btrfs_ioctl_vol_args) error { - return ioctl.Do(f, _BTRFS_IOC_RESIZE, out) +func iocResize(f *os.File, in *btrfs_ioctl_vol_args) error { + return ioctl.Do(f, _BTRFS_IOC_RESIZE, in) } func iocScanDev(f *os.File, out *btrfs_ioctl_vol_args) error { diff --git a/test/btrfstest.go b/test/btrfstest.go index bb1cced..f85862d 100644 --- a/test/btrfstest.go +++ b/test/btrfstest.go @@ -52,6 +52,21 @@ func Mount(mount string, file string) error { return nil } +func Unmount(mount string) error { + for i := 0; i < 5; i++ { + if err := run("umount", mount); err == nil { + break + } else { + if strings.Contains(err.Error(), "busy") { + time.Sleep(time.Second) + } else { + break + } + } + } + return nil +} + func New(t testing.TB, size int64) (string, func()) { f, err := ioutil.TempFile("", "btrfs_vol") if err != nil { @@ -85,17 +100,8 @@ func New(t testing.TB, size int64) (string, func()) { if done { return } - for i := 0; i < 5; i++ { - if err := run("umount", mount); err == nil { - break - } else { - log.Println("umount failed:", err) - if strings.Contains(err.Error(), "busy") { - time.Sleep(time.Second) - } else { - break - } - } + if err := Unmount(mount); err != nil { + log.Println("umount failed:", err) } if err := os.Remove(mount); err != nil { log.Println("cleanup failed:", err) From 91f666b9f99711a808b3a43dc33d1d7f9f2e3cd2 Mon Sep 17 00:00:00 2001 From: Denys Smirnov Date: Mon, 13 Nov 2017 14:31:53 +0200 Subject: [PATCH 24/38] refactor send definitions (cherry picked from commit dennwc/btrfs@cdc1bf5c04a55fc2396b1733e508efddcdfe77c5) Signed-off-by: Akihiro Suda --- btrfs_tree.go | 49 ++++++++++ receive.go | 10 +- send_h.go | 251 +++++++++++++++++++++++++++++++++++--------------- subvolume.go | 2 +- 4 files changed, 230 insertions(+), 82 deletions(-) diff --git a/btrfs_tree.go b/btrfs_tree.go index 1f2f7ae..514e8cd 100644 --- a/btrfs_tree.go +++ b/btrfs_tree.go @@ -1,6 +1,7 @@ package btrfs import ( + "fmt" "time" "unsafe" ) @@ -52,6 +53,54 @@ func asRootRef(p []byte) rootRef { return ref } +var treeKeyNames = map[treeKeyType]string{ + inodeItemKey: "inodeItem", + inodeRefKey: "inodeRef", + inodeExtrefKey: "inodeExtref", + xattrItemKey: "xattrItemKey", + orphanItemKey: "orphanItem", + dirLogItemKey: "dirLogItem", + dirLogIndexKey: "dirLogIndex", + dirItemKey: "dirItem", + dirIndexKey: "dirIndex", + extentDataKey: "extentData", + extentCsumKey: "extentCsum", + rootItemKey: "rootItem", + rootBackrefKey: "rootBackref", + rootRefKey: "rootRef", + extentItemKey: "extentItem", + metadataItemKey: "metadataItem", + treeBlockRefKey: "treeBlockRef", + extentDataRefKey: "extentDataRef", + extentRefV0Key: "extentRefV0", + sharedBlockRefKey: "sharedBlockRef", + sharedDataRefKey: "sharedDataRef", + blockGroupItemKey: "blockGroupItem", + freeSpaceInfoKey: "freeSpaceInfo", + freeSpaceExtentKey: "freeSpaceExtent", + freeSpaceBitmapKey: "freeSpaceBitmap", + devExtentKey: "devExtent", + devItemKey: "devItem", + chunkItemKey: "chunkItem", + qgroupStatusKey: "qgroupStatus", + qgroupInfoKey: "qgroupInfo", + qgroupLimitKey: "qgroupLimit", + qgroupRelationKey: "qgroupRelation", + temporaryItemKey: "temporaryItem", + persistentItemKey: "persistentItem", + devReplaceKey: "devReplace", + uuidKeySubvol: "uuidKeySubvol", + uuidKeyReceivedSubvol: "uuidKeyReceivedSubvol", + stringItemKey: "stringItem", +} + +func (t treeKeyType) String() string { + if name, ok := treeKeyNames[t]; ok { + return name + } + return fmt.Sprintf("%#x", int(t)) +} + // btrfs_disk_key_raw is a raw bytes for btrfs_disk_key structure type btrfs_disk_key_raw [17]byte diff --git a/receive.go b/receive.go index 9a7fe58..dcc042d 100644 --- a/receive.go +++ b/receive.go @@ -66,7 +66,7 @@ type streamReader struct { buf *bytes.Buffer } type sendCommandArgs struct { - Type tlvType + Type sendCmdAttr Data []byte } type sendCommand struct { @@ -110,12 +110,12 @@ func (sr *streamReader) ReadCommand() (*sendCommand, error) { return nil, err } data = data[th.Size():] - if th.Type > _BTRFS_SEND_A_MAX { // || th.Len > _BTRFS_SEND_BUF_SIZE { + if sendCmdAttr(th.Type) > sendAttrMax { // || th.Len > _BTRFS_SEND_BUF_SIZE { return nil, fmt.Errorf("invalid tlv in cmd: %+v", th) } b := make([]byte, th.Len) copy(b, data) - cmd.Args = append(cmd.Args, sendCommandArgs{Type: th.Type, Data: b}) + cmd.Args = append(cmd.Args, sendCommandArgs{Type: sendCmdAttr(th.Type), Data: b}) } return &cmd, nil } @@ -125,11 +125,11 @@ func newStreamReader(r io.Reader) (*streamReader, error) { _, err := io.ReadFull(r, buf) if err != nil { return nil, err - } else if bytes.Compare(buf[:sendStreamMagicSize], []byte(_BTRFS_SEND_STREAM_MAGIC)) != 0 { + } else if bytes.Compare(buf[:sendStreamMagicSize], []byte(sendStreamMagic)) != 0 { return nil, errors.New("unexpected stream header") } version := binary.LittleEndian.Uint32(buf[sendStreamMagicSize:]) - if version > _BTRFS_SEND_STREAM_VERSION { + if version > sendStreamVersion { return nil, fmt.Errorf("stream version %d not supported", version) } return &streamReader{r: r}, nil diff --git a/send_h.go b/send_h.go index 9c93c92..64b5058 100644 --- a/send_h.go +++ b/send_h.go @@ -3,17 +3,20 @@ package btrfs import ( "encoding/binary" "io" + "strconv" ) +var sendEndianess = binary.LittleEndian + const ( - _BTRFS_SEND_STREAM_MAGIC = "btrfs-stream" - sendStreamMagicSize = len(_BTRFS_SEND_STREAM_MAGIC) - _BTRFS_SEND_STREAM_VERSION = 1 + sendStreamMagic = "btrfs-stream\x00" + sendStreamMagicSize = len(sendStreamMagic) + sendStreamVersion = 1 ) const ( - _BTRFS_SEND_BUF_SIZE = 64 * 1024 - _BTRFS_SEND_READ_SIZE = 48 * 1024 + sendBufSize = 64 * 1024 + sendReadSize = 48 * 1024 ) type tlvType uint16 @@ -30,115 +33,211 @@ const ( ) type streamHeader struct { - Magic [len(_BTRFS_SEND_STREAM_MAGIC)]byte + Magic [sendStreamMagicSize]byte Version uint32 } +const cmdHeaderSize = 10 + type cmdHeader struct { Len uint32 // len excluding the header - Cmd uint16 + Cmd sendCmd Crc uint32 // crc including the header with zero crc field } -func (h *cmdHeader) Size() int { return 10 } +func (h *cmdHeader) Size() int { return cmdHeaderSize } func (h *cmdHeader) Unmarshal(p []byte) error { - if len(p) < h.Size() { + if len(p) < cmdHeaderSize { return io.ErrUnexpectedEOF } - h.Len = binary.LittleEndian.Uint32(p[0:]) - h.Cmd = binary.LittleEndian.Uint16(p[4:]) - h.Crc = binary.LittleEndian.Uint32(p[6:]) + h.Len = sendEndianess.Uint32(p[0:]) + h.Cmd = sendCmd(sendEndianess.Uint16(p[4:])) + h.Crc = sendEndianess.Uint32(p[6:]) return nil } +const tlvHeaderSize = 4 + type tlvHeader struct { - Type tlvType + Type uint16 Len uint16 // len excluding the header } -func (h *tlvHeader) Size() int { return 4 } +func (h *tlvHeader) Size() int { return tlvHeaderSize } func (h *tlvHeader) Unmarshal(p []byte) error { - if len(p) < h.Size() { + if len(p) < tlvHeaderSize { return io.ErrUnexpectedEOF } - h.Type = tlvType(binary.LittleEndian.Uint16(p[0:])) - h.Len = binary.LittleEndian.Uint16(p[2:]) + h.Type = sendEndianess.Uint16(p[0:]) + h.Len = sendEndianess.Uint16(p[2:]) return nil } type sendCmd uint16 +func (c sendCmd) String() string { + var name string + if int(c) < len(sendCmdTypeNames) { + name = sendCmdTypeNames[int(c)] + } + if name != "" { + return name + } + return strconv.FormatInt(int64(c), 16) +} + +var sendCmdTypeNames = []string{ + "", + + "subvol", + "snapshot", + + "mkfile", + "mkdir", + "mknod", + "mkfifo", + "mksock", + "symlink", + + "rename", + "link", + "unlink", + "rmdir", + + "set_xattr", + "remove_xattr", + + "write", + "clone", + + "truncate", + "chmod", + "chown", + "utimes", + + "end", + "update_extent", + "", +} + const ( - _BTRFS_SEND_C_UNSPEC = sendCmd(iota) + sendCmdUnspec = sendCmd(iota) - _BTRFS_SEND_C_SUBVOL - _BTRFS_SEND_C_SNAPSHOT + sendCmdSubvol + sendCmdSnapshot - _BTRFS_SEND_C_MKFILE - _BTRFS_SEND_C_MKDIR - _BTRFS_SEND_C_MKNOD - _BTRFS_SEND_C_MKFIFO - _BTRFS_SEND_C_MKSOCK - _BTRFS_SEND_C_SYMLINK + sendCmdMkfile + sendCmdMkdir + sendCmdMknod + sendCmdMkfifo + sendCmdMksock + sendCmdSymlink - _BTRFS_SEND_C_RENAME - _BTRFS_SEND_C_LINK - _BTRFS_SEND_C_UNLINK - _BTRFS_SEND_C_RMDIR + sendCmdRename + sendCmdLink + sendCmdUnlink + sendCmdRmdir - _BTRFS_SEND_C_SET_XATTR - _BTRFS_SEND_C_REMOVE_XATTR + sendCmdSetXattr + sendCmdRemoveXattr - _BTRFS_SEND_C_WRITE - _BTRFS_SEND_C_CLONE + sendCmdWrite + sendCmdClone - _BTRFS_SEND_C_TRUNCATE - _BTRFS_SEND_C_CHMOD - _BTRFS_SEND_C_CHOWN - _BTRFS_SEND_C_UTIMES + sendCmdTruncate + sendCmdChmod + sendCmdChown + sendCmdUtimes - _BTRFS_SEND_C_END - _BTRFS_SEND_C_UPDATE_EXTENT - __BTRFS_SEND_C_MAX + sendCmdEnd + sendCmdUpdateExtent + _sendCmdMax ) -const _BTRFS_SEND_C_MAX = __BTRFS_SEND_C_MAX - 1 +const sendCmdMax = _sendCmdMax - 1 type sendCmdAttr uint16 +func (c sendCmdAttr) String() string { + var name string + if int(c) < len(sendAttrNames) { + name = sendAttrNames[int(c)] + } + if name != "" { + return name + } + return strconv.FormatInt(int64(c), 16) +} + const ( - _BTRFS_SEND_A_UNSPEC = iota - - _BTRFS_SEND_A_UUID - _BTRFS_SEND_A_CTRANSID - - _BTRFS_SEND_A_INO - _BTRFS_SEND_A_SIZE - _BTRFS_SEND_A_MODE - _BTRFS_SEND_A_UID - _BTRFS_SEND_A_GID - _BTRFS_SEND_A_RDEV - _BTRFS_SEND_A_CTIME - _BTRFS_SEND_A_MTIME - _BTRFS_SEND_A_ATIME - _BTRFS_SEND_A_OTIME - - _BTRFS_SEND_A_XATTR_NAME - _BTRFS_SEND_A_XATTR_DATA - - _BTRFS_SEND_A_PATH - _BTRFS_SEND_A_PATH_TO - _BTRFS_SEND_A_PATH_LINK - - _BTRFS_SEND_A_FILE_OFFSET - _BTRFS_SEND_A_DATA - - _BTRFS_SEND_A_CLONE_UUID - _BTRFS_SEND_A_CLONE_CTRANSID - _BTRFS_SEND_A_CLONE_PATH - _BTRFS_SEND_A_CLONE_OFFSET - _BTRFS_SEND_A_CLONE_LEN - - __BTRFS_SEND_A_MAX + sendAttrUnspec = sendCmdAttr(iota) + + sendAttrUuid + sendAttrCtransid + + sendAttrIno + sendAttrSize + sendAttrMode + sendAttrUid + sendAttrGid + sendAttrRdev + sendAttrCtime + sendAttrMtime + sendAttrAtime + sendAttrOtime + + sendAttrXattrName + sendAttrXattrData + + sendAttrPath + sendAttrPathTo + sendAttrPathLink + + sendAttrFileOffset + sendAttrData + + sendAttrCloneUuid + sendAttrCloneCtransid + sendAttrClonePath + sendAttrCloneOffset + sendAttrCloneLen + + _sendAttrMax ) -const _BTRFS_SEND_A_MAX = __BTRFS_SEND_A_MAX - 1 +const sendAttrMax = _sendAttrMax - 1 + +var sendAttrNames = []string{ + "", + + "uuid", + "ctransid", + + "ino", + "size", + "mode", + "uid", + "gid", + "rdev", + "ctime", + "mtime", + "atime", + "otime", + + "xattrname", + "xattrdata", + + "path", + "pathto", + "pathlink", + + "fileoffset", + "data", + + "cloneuuid", + "clonectransid", + "clonepath", + "cloneoffset", + "clonelen", + + "", +} diff --git a/subvolume.go b/subvolume.go index 7fd4e82..2e52886 100644 --- a/subvolume.go +++ b/subvolume.go @@ -141,7 +141,7 @@ func SnapshotSubVolume(subvol, dst string, ro bool) error { //} copy(args.name[:], newName) if err := iocSnapCreateV2(fdst, &args); err != nil { - return fmt.Errorf("ioc failed: %v", err) + return fmt.Errorf("snapshot create failed: %v", err) } return nil } From 022e86ba77fb0775fd0e56625794aa7280d8da64 Mon Sep 17 00:00:00 2001 From: Denys Smirnov Date: Mon, 27 Nov 2017 20:43:55 +0200 Subject: [PATCH 25/38] send: implement stream reader (cherry picked from commit dennwc/btrfs@75c27127c28e9fcb3f1d9ff479c5f3df70b9c3b8) Signed-off-by: Akihiro Suda --- receive.go | 88 +------ send/send.go | 484 ++++++++++++++++++++++++++++++++++++ send_h.go => send/send_h.go | 36 +-- 3 files changed, 498 insertions(+), 110 deletions(-) create mode 100644 send/send.go rename send_h.go => send/send_h.go (86%) diff --git a/receive.go b/receive.go index dcc042d..015714a 100644 --- a/receive.go +++ b/receive.go @@ -2,10 +2,7 @@ package btrfs import ( "bytes" - "encoding/binary" "errors" - "fmt" - "hash/crc32" "io" "os" "os/exec" @@ -52,85 +49,10 @@ func Receive(r io.Reader, dstDir string) error { if err != nil { return err } - sr, err := newStreamReader(r) - if err != nil { - return err - } - _, _, _ = dir, subvolID, sr + //sr, err := send.NewStreamReader(r) + //if err != nil { + // return err + //} + _, _ = dir, subvolID panic("not implemented") } - -type streamReader struct { - r io.Reader - hbuf []byte - buf *bytes.Buffer -} -type sendCommandArgs struct { - Type sendCmdAttr - Data []byte -} -type sendCommand struct { - Type sendCmd - Args []sendCommandArgs -} - -func (sr *streamReader) ReadCommand() (*sendCommand, error) { - sr.buf.Reset() - var h cmdHeader - if sr.hbuf == nil { - sr.hbuf = make([]byte, h.Size()) - } - if _, err := io.ReadFull(sr.r, sr.hbuf); err != nil { - return nil, err - } else if err = h.Unmarshal(sr.hbuf); err != nil { - return nil, err - } - if sr.buf == nil { - sr.buf = bytes.NewBuffer(nil) - } - if _, err := io.CopyN(sr.buf, sr.r, int64(h.Len)); err != nil { - return nil, err - } - tbl := crc32.MakeTable(0) - crc := crc32.Checksum(sr.buf.Bytes(), tbl) - if crc != h.Crc { - return nil, fmt.Errorf("crc missmatch in command: %x vs %x", crc, h.Crc) - } - cmd := sendCommand{Type: sendCmd(h.Cmd)} - var th tlvHeader - data := sr.buf.Bytes() - for { - if n := len(data); n < th.Size() { - if n != 0 { - return nil, io.ErrUnexpectedEOF - } - break - } - if err := th.Unmarshal(data); err != nil { - return nil, err - } - data = data[th.Size():] - if sendCmdAttr(th.Type) > sendAttrMax { // || th.Len > _BTRFS_SEND_BUF_SIZE { - return nil, fmt.Errorf("invalid tlv in cmd: %+v", th) - } - b := make([]byte, th.Len) - copy(b, data) - cmd.Args = append(cmd.Args, sendCommandArgs{Type: sendCmdAttr(th.Type), Data: b}) - } - return &cmd, nil -} - -func newStreamReader(r io.Reader) (*streamReader, error) { - buf := make([]byte, sendStreamMagicSize+4) - _, err := io.ReadFull(r, buf) - if err != nil { - return nil, err - } else if bytes.Compare(buf[:sendStreamMagicSize], []byte(sendStreamMagic)) != 0 { - return nil, errors.New("unexpected stream header") - } - version := binary.LittleEndian.Uint32(buf[sendStreamMagicSize:]) - if version > sendStreamVersion { - return nil, fmt.Errorf("stream version %d not supported", version) - } - return &streamReader{r: r}, nil -} diff --git a/send/send.go b/send/send.go new file mode 100644 index 0000000..f5f6315 --- /dev/null +++ b/send/send.go @@ -0,0 +1,484 @@ +package send + +import ( + "errors" + "fmt" + "github.com/dennwc/btrfs" + "io" + "io/ioutil" + "time" +) + +func NewStreamReader(r io.Reader) (*StreamReader, error) { + // read magic and version + buf := make([]byte, len(sendStreamMagic)+4) + _, err := io.ReadFull(r, buf) + if err != nil { + return nil, fmt.Errorf("cannot read magic: %v", err) + } else if string(buf[:sendStreamMagicSize]) != sendStreamMagic { + return nil, errors.New("unexpected stream header") + } + version := sendEndianess.Uint32(buf[sendStreamMagicSize:]) + if version != sendStreamVersion { + return nil, fmt.Errorf("stream version %d not supported", version) + } + return &StreamReader{r: r}, nil +} + +type StreamReader struct { + r io.Reader + buf [cmdHeaderSize]byte +} + +func (r *StreamReader) readCmdHeader() (h cmdHeader, err error) { + _, err = io.ReadFull(r.r, r.buf[:cmdHeaderSize]) + if err == io.EOF { + return + } else if err != nil { + err = fmt.Errorf("cannot read command header: %v", err) + return + } + err = h.Unmarshal(r.buf[:cmdHeaderSize]) + // TODO: check CRC + return +} + +type SendTLV struct { + Attr sendCmdAttr + Val interface{} +} + +func (r *StreamReader) readTLV(rd io.Reader) (*SendTLV, error) { + _, err := io.ReadFull(rd, r.buf[:tlvHeaderSize]) + if err == io.EOF { + return nil, err + } else if err != nil { + return nil, fmt.Errorf("cannot read tlv header: %v", err) + } + var h tlvHeader + if err = h.Unmarshal(r.buf[:tlvHeaderSize]); err != nil { + return nil, err + } + typ := sendCmdAttr(h.Type) + if sendCmdAttr(typ) > sendAttrMax { // || th.Len > _BTRFS_SEND_BUF_SIZE { + return nil, fmt.Errorf("invalid tlv in cmd: %q", typ) + } + buf := make([]byte, h.Len) + _, err = io.ReadFull(rd, buf) + if err != nil { + return nil, fmt.Errorf("cannot read tlv: %v", err) + } + var v interface{} + switch typ { + case sendAttrCtransid, sendAttrCloneCtransid, + sendAttrUid, sendAttrGid, sendAttrMode, + sendAttrIno, sendAttrFileOffset, sendAttrSize, + sendAttrCloneOffset, sendAttrCloneLen: + if len(buf) != 8 { + return nil, fmt.Errorf("unexpected int64 size: %v", h.Len) + } + v = sendEndianess.Uint64(buf[:8]) + case sendAttrPath, sendAttrPathTo, sendAttrClonePath, sendAttrXattrName: + v = string(buf) + case sendAttrData, sendAttrXattrData: + v = buf + case sendAttrUuid, sendAttrCloneUuid: + if h.Len != btrfs.UUIDSize { + return nil, fmt.Errorf("unexpected UUID size: %v", h.Len) + } + var u btrfs.UUID + copy(u[:], buf) + v = u + case sendAttrAtime, sendAttrMtime, sendAttrCtime, sendAttrOtime: + if h.Len != 12 { + return nil, fmt.Errorf("unexpected timestamp size: %v", h.Len) + } + v = time.Unix( // btrfs_timespec + int64(sendEndianess.Uint64(buf[:8])), + int64(sendEndianess.Uint32(buf[8:])), + ) + default: + return nil, fmt.Errorf("unsupported tlv type: %v (len: %v)", typ, h.Len) + } + return &SendTLV{Attr: typ, Val: v}, nil +} +func (r *StreamReader) ReadCommand() (_ Cmd, gerr error) { + h, err := r.readCmdHeader() + if err != nil { + return nil, err + } + var tlvs []SendTLV + rd := io.LimitReader(r.r, int64(h.Len)) + defer io.Copy(ioutil.Discard, rd) + for { + tlv, err := r.readTLV(rd) + if err == io.EOF { + break + } else if err != nil { + return nil, fmt.Errorf("command %v: %v", h.Cmd, err) + } + tlvs = append(tlvs, *tlv) + } + var c Cmd + switch h.Cmd { + case sendCmdEnd: + c = &StreamEnd{} + case sendCmdSubvol: + c = &SubvolCmd{} + case sendCmdSnapshot: + c = &SnapshotCmd{} + case sendCmdChown: + c = &ChownCmd{} + case sendCmdChmod: + c = &ChmodCmd{} + case sendCmdUtimes: + c = &UTimesCmd{} + case sendCmdMkdir: + c = &MkdirCmd{} + case sendCmdRename: + c = &RenameCmd{} + case sendCmdMkfile: + c = &MkfileCmd{} + case sendCmdWrite: + c = &WriteCmd{} + case sendCmdTruncate: + c = &TruncateCmd{} + } + if c == nil { + return &UnknownSendCmd{Kind: h.Cmd, Params: tlvs}, nil + } + if err := c.decode(tlvs); err != nil { + return nil, err + } + return c, nil +} + +type errUnexpectedAttrType struct { + Cmd CmdType + Val SendTLV +} + +func (e errUnexpectedAttrType) Error() string { + return fmt.Sprintf("unexpected type for %q (in %q): %T", + e.Val.Attr, e.Cmd, e.Val.Val) +} + +type errUnexpectedAttr struct { + Cmd CmdType + Val SendTLV +} + +func (e errUnexpectedAttr) Error() string { + return fmt.Sprintf("unexpected attr %q for %q (%T)", + e.Val.Attr, e.Cmd, e.Val.Val) +} + +type Cmd interface { + Type() CmdType + decode(tlvs []SendTLV) error +} + +type UnknownSendCmd struct { + Kind CmdType + Params []SendTLV +} + +func (c UnknownSendCmd) Type() CmdType { + return c.Kind +} +func (c *UnknownSendCmd) decode(tlvs []SendTLV) error { + c.Params = tlvs + return nil +} + +type StreamEnd struct{} + +func (c StreamEnd) Type() CmdType { + return sendCmdEnd +} +func (c *StreamEnd) decode(tlvs []SendTLV) error { + if len(tlvs) != 0 { + return fmt.Errorf("unexpected TLVs for stream end command: %#v", tlvs) + } + return nil +} + +type SubvolCmd struct { + Path string + UUID btrfs.UUID + CTransID uint64 +} + +func (c SubvolCmd) Type() CmdType { + return sendCmdSubvol +} +func (c *SubvolCmd) decode(tlvs []SendTLV) error { + for _, tlv := range tlvs { + var ok bool + switch tlv.Attr { + case sendAttrPath: + c.Path, ok = tlv.Val.(string) + case sendAttrUuid: + c.UUID, ok = tlv.Val.(btrfs.UUID) + case sendAttrCtransid: + c.CTransID, ok = tlv.Val.(uint64) + default: + return errUnexpectedAttr{Val: tlv, Cmd: c.Type()} + } + if !ok { + return errUnexpectedAttrType{Val: tlv, Cmd: c.Type()} + } + } + return nil +} + +type SnapshotCmd struct { + Path string + UUID btrfs.UUID + CTransID uint64 + CloneUUID btrfs.UUID + CloneTransID uint64 +} + +func (c SnapshotCmd) Type() CmdType { + return sendCmdSnapshot +} +func (c *SnapshotCmd) decode(tlvs []SendTLV) error { + for _, tlv := range tlvs { + var ok bool + switch tlv.Attr { + case sendAttrPath: + c.Path, ok = tlv.Val.(string) + case sendAttrUuid: + c.UUID, ok = tlv.Val.(btrfs.UUID) + case sendAttrCtransid: + c.CTransID, ok = tlv.Val.(uint64) + case sendAttrCloneUuid: + c.CloneUUID, ok = tlv.Val.(btrfs.UUID) + case sendAttrCloneCtransid: + c.CloneTransID, ok = tlv.Val.(uint64) + default: + return errUnexpectedAttr{Val: tlv, Cmd: c.Type()} + } + if !ok { + return errUnexpectedAttrType{Val: tlv, Cmd: c.Type()} + } + } + return nil +} + +type ChownCmd struct { + Path string + UID, GID uint64 +} + +func (c ChownCmd) Type() CmdType { + return sendCmdChown +} +func (c *ChownCmd) decode(tlvs []SendTLV) error { + for _, tlv := range tlvs { + var ok bool + switch tlv.Attr { + case sendAttrPath: + c.Path, ok = tlv.Val.(string) + case sendAttrUid: + c.UID, ok = tlv.Val.(uint64) + case sendAttrGid: + c.GID, ok = tlv.Val.(uint64) + default: + return errUnexpectedAttr{Val: tlv, Cmd: c.Type()} + } + if !ok { + return errUnexpectedAttrType{Val: tlv, Cmd: c.Type()} + } + } + return nil +} + +type ChmodCmd struct { + Path string + Mode uint64 +} + +func (c ChmodCmd) Type() CmdType { + return sendCmdChmod +} +func (c *ChmodCmd) decode(tlvs []SendTLV) error { + for _, tlv := range tlvs { + var ok bool + switch tlv.Attr { + case sendAttrPath: + c.Path, ok = tlv.Val.(string) + case sendAttrMode: + c.Mode, ok = tlv.Val.(uint64) + default: + return errUnexpectedAttr{Val: tlv, Cmd: c.Type()} + } + if !ok { + return errUnexpectedAttrType{Val: tlv, Cmd: c.Type()} + } + } + return nil +} + +type UTimesCmd struct { + Path string + ATime, MTime, CTime time.Time +} + +func (c UTimesCmd) Type() CmdType { + return sendCmdUtimes +} +func (c *UTimesCmd) decode(tlvs []SendTLV) error { + for _, tlv := range tlvs { + var ok bool + switch tlv.Attr { + case sendAttrPath: + c.Path, ok = tlv.Val.(string) + case sendAttrAtime: + c.ATime, ok = tlv.Val.(time.Time) + case sendAttrMtime: + c.MTime, ok = tlv.Val.(time.Time) + case sendAttrCtime: + c.CTime, ok = tlv.Val.(time.Time) + default: + return errUnexpectedAttr{Val: tlv, Cmd: c.Type()} + } + if !ok { + return errUnexpectedAttrType{Val: tlv, Cmd: c.Type()} + } + } + return nil +} + +type MkdirCmd struct { + Path string + Ino uint64 +} + +func (c MkdirCmd) Type() CmdType { + return sendCmdMkdir +} +func (c *MkdirCmd) decode(tlvs []SendTLV) error { + for _, tlv := range tlvs { + var ok bool + switch tlv.Attr { + case sendAttrPath: + c.Path, ok = tlv.Val.(string) + case sendAttrIno: + c.Ino, ok = tlv.Val.(uint64) + default: + return errUnexpectedAttr{Val: tlv, Cmd: c.Type()} + } + if !ok { + return errUnexpectedAttrType{Val: tlv, Cmd: c.Type()} + } + } + return nil +} + +type RenameCmd struct { + From, To string +} + +func (c RenameCmd) Type() CmdType { + return sendCmdRename +} +func (c *RenameCmd) decode(tlvs []SendTLV) error { + for _, tlv := range tlvs { + var ok bool + switch tlv.Attr { + case sendAttrPath: + c.From, ok = tlv.Val.(string) + case sendAttrPathTo: + c.To, ok = tlv.Val.(string) + default: + return errUnexpectedAttr{Val: tlv, Cmd: c.Type()} + } + if !ok { + return errUnexpectedAttrType{Val: tlv, Cmd: c.Type()} + } + } + return nil +} + +type MkfileCmd struct { + Path string + Ino uint64 +} + +func (c MkfileCmd) Type() CmdType { + return sendCmdMkfile +} +func (c *MkfileCmd) decode(tlvs []SendTLV) error { + for _, tlv := range tlvs { + var ok bool + switch tlv.Attr { + case sendAttrPath: + c.Path, ok = tlv.Val.(string) + case sendAttrIno: + c.Ino, ok = tlv.Val.(uint64) + default: + return errUnexpectedAttr{Val: tlv, Cmd: c.Type()} + } + if !ok { + return errUnexpectedAttrType{Val: tlv, Cmd: c.Type()} + } + } + return nil +} + +type WriteCmd struct { + Path string + Off uint64 + Data []byte +} + +func (c WriteCmd) Type() CmdType { + return sendCmdWrite +} +func (c *WriteCmd) decode(tlvs []SendTLV) error { + for _, tlv := range tlvs { + var ok bool + switch tlv.Attr { + case sendAttrPath: + c.Path, ok = tlv.Val.(string) + case sendAttrFileOffset: + c.Off, ok = tlv.Val.(uint64) + case sendAttrData: + c.Data, ok = tlv.Val.([]byte) + default: + return errUnexpectedAttr{Val: tlv, Cmd: c.Type()} + } + if !ok { + return errUnexpectedAttrType{Val: tlv, Cmd: c.Type()} + } + } + return nil +} + +type TruncateCmd struct { + Path string + Size uint64 +} + +func (c TruncateCmd) Type() CmdType { + return sendCmdTruncate +} +func (c *TruncateCmd) decode(tlvs []SendTLV) error { + for _, tlv := range tlvs { + var ok bool + switch tlv.Attr { + case sendAttrPath: + c.Path, ok = tlv.Val.(string) + case sendAttrSize: + c.Size, ok = tlv.Val.(uint64) + default: + return errUnexpectedAttr{Val: tlv, Cmd: c.Type()} + } + if !ok { + return errUnexpectedAttrType{Val: tlv, Cmd: c.Type()} + } + } + return nil +} diff --git a/send_h.go b/send/send_h.go similarity index 86% rename from send_h.go rename to send/send_h.go index 64b5058..63e3d32 100644 --- a/send_h.go +++ b/send/send_h.go @@ -1,4 +1,4 @@ -package btrfs +package send import ( "encoding/binary" @@ -19,29 +19,11 @@ const ( sendReadSize = 48 * 1024 ) -type tlvType uint16 - -const ( - tlvU8 = tlvType(iota) - tlvU16 - tlvU32 - tlvU64 - tlvBinary - tlvString - tlvUUID - tlvTimespec -) - -type streamHeader struct { - Magic [sendStreamMagicSize]byte - Version uint32 -} - const cmdHeaderSize = 10 type cmdHeader struct { Len uint32 // len excluding the header - Cmd sendCmd + Cmd CmdType Crc uint32 // crc including the header with zero crc field } @@ -51,7 +33,7 @@ func (h *cmdHeader) Unmarshal(p []byte) error { return io.ErrUnexpectedEOF } h.Len = sendEndianess.Uint32(p[0:]) - h.Cmd = sendCmd(sendEndianess.Uint16(p[4:])) + h.Cmd = CmdType(sendEndianess.Uint16(p[4:])) h.Crc = sendEndianess.Uint32(p[6:]) return nil } @@ -73,12 +55,12 @@ func (h *tlvHeader) Unmarshal(p []byte) error { return nil } -type sendCmd uint16 +type CmdType uint16 -func (c sendCmd) String() string { +func (c CmdType) String() string { var name string - if int(c) < len(sendCmdTypeNames) { - name = sendCmdTypeNames[int(c)] + if int(c) < len(cmdTypeNames) { + name = cmdTypeNames[int(c)] } if name != "" { return name @@ -86,7 +68,7 @@ func (c sendCmd) String() string { return strconv.FormatInt(int64(c), 16) } -var sendCmdTypeNames = []string{ +var cmdTypeNames = []string{ "", "subvol", @@ -121,7 +103,7 @@ var sendCmdTypeNames = []string{ } const ( - sendCmdUnspec = sendCmd(iota) + sendCmdUnspec = CmdType(iota) sendCmdSubvol sendCmdSnapshot From 0018d9b2578e76642b265600a0ea3c124b0877af Mon Sep 17 00:00:00 2001 From: Denys Smirnov Date: Sun, 21 Oct 2018 21:02:44 +0300 Subject: [PATCH 26/38] switch to dennwc/ioctl library (cherry picked from commit dennwc/btrfs@694b569856e3a51c9668fc42406ef0710785361e) Signed-off-by: Akihiro Suda --- btrfs.go | 2 +- ioctl/ioctl.go | 85 +++++++++++++++++---------------------------- ioctl/ioctl_test.go | 21 ----------- ioctl_h.go | 2 +- 4 files changed, 34 insertions(+), 76 deletions(-) delete mode 100644 ioctl/ioctl_test.go diff --git a/btrfs.go b/btrfs.go index fc2a483..e17f49b 100644 --- a/btrfs.go +++ b/btrfs.go @@ -2,7 +2,7 @@ package btrfs import ( "fmt" - "github.com/dennwc/btrfs/ioctl" + "github.com/dennwc/ioctl" "io" "os" "path/filepath" diff --git a/ioctl/ioctl.go b/ioctl/ioctl.go index 0571c5b..fe1d886 100644 --- a/ioctl/ioctl.go +++ b/ioctl/ioctl.go @@ -1,82 +1,61 @@ package ioctl import ( - "fmt" + "github.com/dennwc/ioctl" "os" - "reflect" - "syscall" ) const ( - nrBits = 8 - typeBits = 8 - sizeBits = 14 - dirBits = 2 -) - -const ( - nrMask = ((1 << nrBits) - 1) - typeMask = ((1 << typeBits) - 1) - sizeMask = ((1 << sizeBits) - 1) - dirMask = ((1 << dirBits) - 1) -) - -const ( - nrShift = 0 - typeShift = (nrShift + nrBits) - sizeShift = (typeShift + typeBits) - dirShift = (sizeShift + sizeBits) -) - -const ( - None = 0 - Write = 1 - Read = 2 + None = ioctl.None + Write = ioctl.Write + Read = ioctl.Read ) +// IOC +// +// Deprecated: use github/dennwc/ioctl func IOC(dir, typ, nr, size uintptr) uintptr { - return (dir << dirShift) | - (typ << typeShift) | - (nr << nrShift) | - (size << sizeShift) + return ioctl.IOC(dir, typ, nr, size) } +// IO +// +// Deprecated: use github/dennwc/ioctl func IO(typ, nr uintptr) uintptr { - return IOC(None, typ, nr, 0) + return ioctl.IO(typ, nr) } +// IOC +// +// Deprecated: use github/dennwc/ioctl func IOR(typ, nr, size uintptr) uintptr { - return IOC(Read, typ, nr, size) + return ioctl.IOR(typ, nr, size) } +// IOW +// +// Deprecated: use github/dennwc/ioctl func IOW(typ, nr, size uintptr) uintptr { - return IOC(Write, typ, nr, size) + return ioctl.IOW(typ, nr, size) } +// IOWR +// +// Deprecated: use github/dennwc/ioctl func IOWR(typ, nr, size uintptr) uintptr { - return IOC(Read|Write, typ, nr, size) + return ioctl.IOWR(typ, nr, size) } +// Ioctl +// +// Deprecated: use github/dennwc/ioctl func Ioctl(f *os.File, ioc uintptr, addr uintptr) error { - _, _, e := syscall.Syscall(syscall.SYS_IOCTL, f.Fd(), ioc, addr) - if e != 0 { - return e - } - return nil + return ioctl.Ioctl(f, ioc, addr) } +// Do +// +// Deprecated: use github/dennwc/ioctl func Do(f *os.File, ioc uintptr, arg interface{}) error { - var addr uintptr - if arg != nil { - v := reflect.ValueOf(arg) - switch v.Kind() { - case reflect.Ptr: - addr = v.Elem().UnsafeAddr() - case reflect.Slice: - addr = v.Index(0).UnsafeAddr() - default: - return fmt.Errorf("expected ptr or slice, got %T") - } - } - return Ioctl(f, ioc, addr) + return ioctl.Do(f, ioc, arg) } diff --git a/ioctl/ioctl_test.go b/ioctl/ioctl_test.go deleted file mode 100644 index 565fc83..0000000 --- a/ioctl/ioctl_test.go +++ /dev/null @@ -1,21 +0,0 @@ -package ioctl - -import ( - "testing" -) - -var casesIOC = []struct { - got uintptr - expect uintptr -}{ - {got: IOC(1, 2, 3, 4), expect: 0x40040203}, -} - -func TestIOC(t *testing.T) { - for i, c := range casesIOC { - if c.got != c.expect { - t.Errorf("unexpected ioc (case %d): %x(%b) vs %x(%b)", - i+1, c.got, c.got, c.expect, c.expect) - } - } -} diff --git a/ioctl_h.go b/ioctl_h.go index a35a123..769689b 100644 --- a/ioctl_h.go +++ b/ioctl_h.go @@ -3,7 +3,7 @@ package btrfs import ( "encoding/binary" "encoding/hex" - "github.com/dennwc/btrfs/ioctl" + "github.com/dennwc/ioctl" "os" "strconv" "strings" From dd114cfa6294752c13233818af8fe88736edd88d Mon Sep 17 00:00:00 2001 From: Denys Smirnov Date: Fri, 26 Apr 2019 17:22:05 +0300 Subject: [PATCH 27/38] support go modules (cherry picked from commit dennwc/btrfs@ae959341a5d5198290c53160e875df75864d944f) Signed-off-by: Akihiro Suda --- cmd/{ => gbtrfs}/gbtrfs.go | 5 +++-- cmd/gbtrfs/go.mod | 11 +++++++++++ cmd/gbtrfs/go.sum | 10 ++++++++++ go.mod | 5 +++++ go.sum | 2 ++ {cmd => internal/cmd}/hgen.go | 2 +- 6 files changed, 32 insertions(+), 3 deletions(-) rename cmd/{ => gbtrfs}/gbtrfs.go (99%) create mode 100644 cmd/gbtrfs/go.mod create mode 100644 cmd/gbtrfs/go.sum create mode 100644 go.mod create mode 100644 go.sum rename {cmd => internal/cmd}/hgen.go (99%) diff --git a/cmd/gbtrfs.go b/cmd/gbtrfs/gbtrfs.go similarity index 99% rename from cmd/gbtrfs.go rename to cmd/gbtrfs/gbtrfs.go index 03c3c6f..a14db69 100644 --- a/cmd/gbtrfs.go +++ b/cmd/gbtrfs/gbtrfs.go @@ -1,10 +1,11 @@ -package main +package gbtrfs import ( "fmt" + "os" + "github.com/dennwc/btrfs" "github.com/spf13/cobra" - "os" ) func init() { diff --git a/cmd/gbtrfs/go.mod b/cmd/gbtrfs/go.mod new file mode 100644 index 0000000..ceafb85 --- /dev/null +++ b/cmd/gbtrfs/go.mod @@ -0,0 +1,11 @@ +module github.com/dennwc/btrfs/cmd/gbtrfs + +go 1.12 + +require ( + github.com/dennwc/btrfs v0.0.0-20181021180244-694b569856e3 + github.com/dennwc/ioctl v1.0.0 // indirect + github.com/inconshreveable/mousetrap v1.0.0 // indirect + github.com/spf13/cobra v0.0.3 + github.com/spf13/pflag v1.0.3 // indirect +) diff --git a/cmd/gbtrfs/go.sum b/cmd/gbtrfs/go.sum new file mode 100644 index 0000000..87165a7 --- /dev/null +++ b/cmd/gbtrfs/go.sum @@ -0,0 +1,10 @@ +github.com/dennwc/btrfs v0.0.0-20181021180244-694b569856e3 h1:gvAC0SRt17o5OEwJU+0Iz298dfYF/aJlSfKf9NRay6c= +github.com/dennwc/btrfs v0.0.0-20181021180244-694b569856e3/go.mod h1:8k+PMLjFlirprJbTSZJbkj8SEkfTAn3b0JhgPPE78HI= +github.com/dennwc/ioctl v1.0.0 h1:DsWAAjIxRqNcLn9x6mwfuf2pet3iB7aK90K4tF16rLg= +github.com/dennwc/ioctl v1.0.0/go.mod h1:ellh2YB5ldny99SBU/VX7Nq0xiZbHphf1DrtHxxjMk0= +github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..1387a93 --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module github.com/dennwc/btrfs + +go 1.12 + +require github.com/dennwc/ioctl v1.0.0 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..b466485 --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/dennwc/ioctl v1.0.0 h1:DsWAAjIxRqNcLn9x6mwfuf2pet3iB7aK90K4tF16rLg= +github.com/dennwc/ioctl v1.0.0/go.mod h1:ellh2YB5ldny99SBU/VX7Nq0xiZbHphf1DrtHxxjMk0= diff --git a/cmd/hgen.go b/internal/cmd/hgen.go similarity index 99% rename from cmd/hgen.go rename to internal/cmd/hgen.go index 0d79bd0..95efeba 100644 --- a/cmd/hgen.go +++ b/internal/cmd/hgen.go @@ -1,4 +1,4 @@ -package main +package cmd import ( "bufio" From 4dd9ca333e7d1328dcf693c7eb7dce8541e3bb79 Mon Sep 17 00:00:00 2001 From: Denys Smirnov Date: Fri, 17 May 2019 20:53:32 +0300 Subject: [PATCH 28/38] expose method to get the subvolume id (cherry picked from commit dennwc/btrfs@e24c76dba44986967d637e8f1b4523dc9a6cff51) Signed-off-by: Akihiro Suda --- btrfs.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/btrfs.go b/btrfs.go index e17f49b..df0673c 100644 --- a/btrfs.go +++ b/btrfs.go @@ -60,6 +60,14 @@ type Info struct { CloneAlignment uint32 } +func (f *FS) SubVolumeID() (uint64, error) { + id, err := getFileRootID(f.f) + if err != nil { + return 0, err + } + return uint64(id), nil +} + func (f *FS) Info() (out Info, err error) { var arg btrfs_ioctl_fs_info_args arg, err = iocFsInfo(f.f) From c9f96813e9613ce71875de2816e591feee6f1afb Mon Sep 17 00:00:00 2001 From: Denys Smirnov Date: Fri, 17 May 2019 20:56:02 +0300 Subject: [PATCH 29/38] cli: fix the package name (cherry picked from commit dennwc/btrfs@a46fd0364e0523c1e437b31028ea43edaa45d096) Signed-off-by: Akihiro Suda --- cmd/gbtrfs/gbtrfs.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/gbtrfs/gbtrfs.go b/cmd/gbtrfs/gbtrfs.go index a14db69..a38865d 100644 --- a/cmd/gbtrfs/gbtrfs.go +++ b/cmd/gbtrfs/gbtrfs.go @@ -1,4 +1,4 @@ -package gbtrfs +package main import ( "fmt" From 0746c788595cffc38083caad4ee9dd8efb2238d0 Mon Sep 17 00:00:00 2001 From: Denys Smirnov Date: Fri, 17 May 2019 20:57:02 +0300 Subject: [PATCH 30/38] do not expose unexported objectid type to the user (cherry picked from commit dennwc/btrfs@d917b30ff03544db899b8c9bc14eda932341eb15) Signed-off-by: Akihiro Suda --- send.go | 10 +++++----- subvolume.go | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/send.go b/send.go index 5038e9e..0f324bb 100644 --- a/send.go +++ b/send.go @@ -77,7 +77,7 @@ func Send(w io.Writer, parent string, subvols ...string) error { if err != nil { return fmt.Errorf("cannot find subvolume %s: %v", rel, err) } - rootID = si.RootID + rootID = objectID(si.RootID) parentID, err = findGoodParent(mfs.f, rootID, cloneSrc) if err != nil { return fmt.Errorf("cannot find good parent for %v: %v", rel, err) @@ -205,8 +205,8 @@ func findGoodParent(mnt *os.File, rootID objectID, cloneSrc []objectID) (objectI return 0, fmt.Errorf("get parent failed: %v", err) } for _, id := range cloneSrc { - if id == parent.RootID { - return parent.RootID, nil + if id == objectID(parent.RootID) { + return objectID(parent.RootID), nil } } var ( @@ -236,10 +236,10 @@ func findGoodParent(mnt *os.File, rootID objectID, cloneSrc []objectID) (objectI } } if bestParent != nil { - return bestParent.RootID, nil + return objectID(bestParent.RootID), nil } if !parent.ParentUUID.IsZero() { - return findGoodParent(mnt, parent.RootID, cloneSrc) + return findGoodParent(mnt, objectID(parent.RootID), cloneSrc) } return 0, ErrNotFound } diff --git a/subvolume.go b/subvolume.go index 2e52886..0d2d808 100644 --- a/subvolume.go +++ b/subvolume.go @@ -204,7 +204,7 @@ func listSubVolumes(f *os.File, filter func(SubvolInfo) bool) (map[objectID]Subv // m[obj.ObjectID] = o case rootItemKey: o := m[obj.ObjectID] - o.RootID = obj.ObjectID + o.RootID = uint64(obj.ObjectID) robj := asRootItem(obj.Data).Decode() o.fillFromItem(&robj) m[obj.ObjectID] = o @@ -251,7 +251,7 @@ func listSubVolumes(f *os.File, filter func(SubvolInfo) bool) (map[objectID]Subv } type SubvolInfo struct { - RootID objectID + RootID uint64 UUID UUID ParentUUID UUID @@ -368,12 +368,12 @@ func subvolSearchByRootID(mnt *os.File, rootID objectID, path string) (*SubvolIn return nil, err } info := &SubvolInfo{ - RootID: rootID, + RootID: uint64(rootID), Path: path, } info.fillFromItem(robj) if path == "" { - info.Path, err = subvolidResolve(mnt, info.RootID) + info.Path, err = subvolidResolve(mnt, objectID(info.RootID)) } return info, err } From 7d268be8b0cf6ae6f494a5817b1577934f262a65 Mon Sep 17 00:00:00 2001 From: Akihiro Suda Date: Fri, 25 Jun 2021 15:45:42 +0900 Subject: [PATCH 31/38] Fix "constant 2435016766 overflows int32" on GOARCH=arm Fix #1 Signed-off-by: Akihiro Suda (cherry picked from commit dennwc/btrfs@ee67271a4cb46adc6cc1eab5eacb4561db6b9a54) Signed-off-by: Akihiro Suda --- utils.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils.go b/utils.go index 45aede7..2dbd66c 100644 --- a/utils.go +++ b/utils.go @@ -15,7 +15,7 @@ func isBtrfs(path string) (bool, error) { if err := syscall.Statfs(path, &stfs); err != nil { return false, &os.PathError{Op: "statfs", Path: path, Err: err} } - return stfs.Type == SuperMagic, nil + return int64(stfs.Type) == SuperMagic, nil } func findMountRoot(path string) (string, error) { From 7a5b584f58514e1c16b2649cac48710232f0f0b0 Mon Sep 17 00:00:00 2001 From: Denys Smirnov Date: Tue, 29 Jun 2021 19:57:49 +0000 Subject: [PATCH 32/38] Switch license to Apache 2 to be CNCF-friendly. (cherry picked from commit dennwc/btrfs@90a0320aae50c617db7a9c9d90072c37d1a81907) Signed-off-by: Akihiro Suda --- LICENSE | 201 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 201 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/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. From bf5ebcf6757b5ec2036a8c740a8ea535945f2b13 Mon Sep 17 00:00:00 2001 From: Akihiro Suda Date: Thu, 1 Jul 2021 15:16:15 +0900 Subject: [PATCH 33/38] dennwc/btrfs -> containerd/btrfs/v2 Temporarily delete cmd/gbtrfs/go.mod (added back in a separate commit) Signed-off-by: Akihiro Suda --- btrfs_test.go | 1 - cmd/gbtrfs/gbtrfs.go | 2 +- cmd/gbtrfs/go.mod | 11 -- cmd/gbtrfs/go.sum | 10 -- go.mod | 9 +- go.sum | 286 +++++++++++++++++++++++++++++++++++++++++++ send/send.go | 3 +- utils.go | 3 +- 8 files changed, 297 insertions(+), 28 deletions(-) delete mode 100644 cmd/gbtrfs/go.mod delete mode 100644 cmd/gbtrfs/go.sum diff --git a/btrfs_test.go b/btrfs_test.go index f31b9c4..3e183e6 100644 --- a/btrfs_test.go +++ b/btrfs_test.go @@ -1,7 +1,6 @@ package btrfs import ( - "github.com/dennwc/btrfs/test" "io" "io/ioutil" "os" diff --git a/cmd/gbtrfs/gbtrfs.go b/cmd/gbtrfs/gbtrfs.go index a38865d..0a92d7d 100644 --- a/cmd/gbtrfs/gbtrfs.go +++ b/cmd/gbtrfs/gbtrfs.go @@ -4,7 +4,7 @@ import ( "fmt" "os" - "github.com/dennwc/btrfs" + "github.com/containerd/btrfs/v2" "github.com/spf13/cobra" ) diff --git a/cmd/gbtrfs/go.mod b/cmd/gbtrfs/go.mod deleted file mode 100644 index ceafb85..0000000 --- a/cmd/gbtrfs/go.mod +++ /dev/null @@ -1,11 +0,0 @@ -module github.com/dennwc/btrfs/cmd/gbtrfs - -go 1.12 - -require ( - github.com/dennwc/btrfs v0.0.0-20181021180244-694b569856e3 - github.com/dennwc/ioctl v1.0.0 // indirect - github.com/inconshreveable/mousetrap v1.0.0 // indirect - github.com/spf13/cobra v0.0.3 - github.com/spf13/pflag v1.0.3 // indirect -) diff --git a/cmd/gbtrfs/go.sum b/cmd/gbtrfs/go.sum deleted file mode 100644 index 87165a7..0000000 --- a/cmd/gbtrfs/go.sum +++ /dev/null @@ -1,10 +0,0 @@ -github.com/dennwc/btrfs v0.0.0-20181021180244-694b569856e3 h1:gvAC0SRt17o5OEwJU+0Iz298dfYF/aJlSfKf9NRay6c= -github.com/dennwc/btrfs v0.0.0-20181021180244-694b569856e3/go.mod h1:8k+PMLjFlirprJbTSZJbkj8SEkfTAn3b0JhgPPE78HI= -github.com/dennwc/ioctl v1.0.0 h1:DsWAAjIxRqNcLn9x6mwfuf2pet3iB7aK90K4tF16rLg= -github.com/dennwc/ioctl v1.0.0/go.mod h1:ellh2YB5ldny99SBU/VX7Nq0xiZbHphf1DrtHxxjMk0= -github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8= -github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= -github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= -github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= diff --git a/go.mod b/go.mod index 1387a93..5b07d0c 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,8 @@ -module github.com/dennwc/btrfs +module github.com/containerd/btrfs/v2 -go 1.12 +go 1.16 -require github.com/dennwc/ioctl v1.0.0 +require ( + github.com/dennwc/ioctl v1.0.0 + github.com/spf13/cobra v1.1.3 +) diff --git a/go.sum b/go.sum index b466485..c887739 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,288 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dennwc/ioctl v1.0.0 h1:DsWAAjIxRqNcLn9x6mwfuf2pet3iB7aK90K4tF16rLg= github.com/dennwc/ioctl v1.0.0/go.mod h1:ellh2YB5ldny99SBU/VX7Nq0xiZbHphf1DrtHxxjMk0= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v1.1.3 h1:xghbfqPkxzxP3C/f3n5DdpAbdKLj4ZE4BWQI362l53M= +github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= diff --git a/send/send.go b/send/send.go index f5f6315..9673f98 100644 --- a/send/send.go +++ b/send/send.go @@ -3,10 +3,11 @@ package send import ( "errors" "fmt" - "github.com/dennwc/btrfs" "io" "io/ioutil" "time" + + "github.com/containerd/btrfs/v2" ) func NewStreamReader(r io.Reader) (*StreamReader, error) { diff --git a/utils.go b/utils.go index 2dbd66c..06028b5 100644 --- a/utils.go +++ b/utils.go @@ -2,12 +2,13 @@ package btrfs import ( "fmt" - "github.com/dennwc/btrfs/mtab" "os" "path/filepath" "strings" "syscall" "unsafe" + + "github.com/containerd/btrfs/v2/mtab" ) func isBtrfs(path string) (bool, error) { From 0ebc59a2215fd46e42f8a4ae92796e26cfa1d557 Mon Sep 17 00:00:00 2001 From: Akihiro Suda Date: Thu, 1 Jul 2021 15:30:02 +0900 Subject: [PATCH 34/38] go generate: fix generator path + regenerate Reran `go generate` with btrfs_tree.h from kernel 5.13 https://github.com/torvalds/linux/blob/v5.13/include/uapi/linux/btrfs_tree.h btrfs_tree.h is licensed under the terms of "GPL-2.0 WITH Linux-syscall-note": https://github.com/torvalds/linux/blob/v5.13/LICENSES/exceptions/Linux-syscall-note containerd/btrfs (dennwc/btrfs) shall be considered as "user programs that use kernel services by normal system calls" mentioned in the note above, and "does *not* fall under the heading of \"derived work\"" of the GPL-2.0 code. Signed-off-by: Akihiro Suda --- btrfs_tree_hc.go | 43 +++++++++++++++++++++++-------------------- headers.go | 10 +++++++++- internal/cmd/hgen.go | 2 +- 3 files changed, 33 insertions(+), 22 deletions(-) diff --git a/btrfs_tree_hc.go b/btrfs_tree_hc.go index fd79932..787bb4d 100644 --- a/btrfs_tree_hc.go +++ b/btrfs_tree_hc.go @@ -14,12 +14,15 @@ type devReplaceItemState int type blockGroup uint64 -// This header contains the structure definitions and constants used -// by file system objects that can be retrieved using -// the BTRFS_IOC_SEARCH_TREE ioctl. That means basically anything that -// is needed to describe a leaf node's key or item contents. +// SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note const ( + + // This header contains the structure definitions and constants used + // by file system objects that can be retrieved using + // the BTRFS_IOC_SEARCH_TREE ioctl. That means basically anything that + // is needed to describe a leaf node's key or item contents. + // Holds pointers to all of the tree roots rootTreeObjectid objectID = 1 @@ -231,15 +234,18 @@ const ( // data in the FS stringItemKey treeKeyType = 253 + // Maximum metadata block size (nodesize) + maxMetadataBlocksize = 65536 + // 32 bytes in various csum fields csumSize = 32 // Csum types - csumTypeCrc32 = 0 // Flags definitions for directory entry item type // Used by: // struct btrfs_dir_item.type + // Values 0..7 must match common file type values in fs_types.h. ftUnknown fileType = 0 ftRegFile fileType = 1 ftDir fileType = 2 @@ -318,8 +324,11 @@ const ( // Errors detected superFlagError = (1 << 2) - superFlagSeeding = (1 << 32) - superFlagMetadump = (1 << 33) + superFlagSeeding = (1 << 32) + superFlagMetadump = (1 << 33) + superFlagMetadumpV2 = (1 << 34) + superFlagChangingFsid = (1 << 35) + superFlagChangingFsidV2 = (1 << 36) // Items in the extent btree are used to record the objectid of the // owner of the block and the number of references @@ -336,8 +345,6 @@ const ( // it is only declared here to avoid collisions extentFlagSuper = (1 << 48) - // Old style backrefs item - // Dev extents record free space on individual devices. The owner // field points back to the chunk allocation mapping tree that allocated // the extent. The chunk tree uuid field is a way to double check the owner @@ -373,6 +380,9 @@ const ( // the offset of generation_v2 is also used as the start for the memset // when invalidating the fields. + // Btrfs root item used to be smaller than current size. The old format ends + // at where member generation_v2 is. + // This is used for both forward and backward root refs // Profiles to operate on, single is denoted by @@ -404,10 +414,6 @@ const ( // resumed after crash or unmount // BTRFS_BALANCE_* - fileExtentInline fileExtentType = 0 - fileExtentReg fileExtentType = 1 - fileExtentPrealloc fileExtentType = 2 - // Transaction id that created this extent // Max number of bytes to hold this extent in ram // when we split a compressed extent we can't know how big @@ -437,13 +443,8 @@ const ( // Grow this item struct at the end for future enhancements and keep // the existing values unchanged - devReplaceItemContReadingFromSrcdevModeAlways = 0 - devReplaceItemContReadingFromSrcdevModeAvoid = 1 - devReplaceItemStateNeverStarted devReplaceItemState = 0 - devReplaceItemStateStarted devReplaceItemState = 1 - devReplaceItemStateSuspended devReplaceItemState = 2 - devReplaceItemStateFinished devReplaceItemState = 3 - devReplaceItemStateCanceled devReplaceItemState = 4 + devReplaceItemContReadingFromSrcdevModeAlways = 0 + devReplaceItemContReadingFromSrcdevModeAvoid = 1 // Grow this item struct at the end for future enhancements and keep // the existing values unchanged @@ -458,6 +459,8 @@ const ( blockGroupRaid10 blockGroup = (1 << 6) blockGroupRaid5 blockGroup = (1 << 7) blockGroupRaid6 blockGroup = (1 << 8) + blockGroupRaid1c3 blockGroup = (1 << 9) + blockGroupRaid1c4 blockGroup = (1 << 10) // We need a bit for restriper to be able to tell when chunks of type // SINGLE are available. This "extended" profile format is used in diff --git a/headers.go b/headers.go index 2ff5713..02eea04 100644 --- a/headers.go +++ b/headers.go @@ -1,4 +1,12 @@ package btrfs -//go:generate go run ./cmd/hgen.go -u -g -t BTRFS_ -p btrfs -cs=treeKeyType:uint32=_KEY,objectID:uint64=_OBJECTID -cp=fileType=FT_,fileExtentType=FILE_EXTENT_,devReplaceItemState=DEV_REPLACE_ITEM_STATE_,blockGroup:uint64=BLOCK_GROUP_ -o btrfs_tree_hc.go btrfs_tree.h +//go:generate go run ./internal/cmd/hgen.go -u -g -t BTRFS_ -p btrfs -cs=treeKeyType:uint32=_KEY,objectID:uint64=_OBJECTID -cp=fileType=FT_,fileExtentType=FILE_EXTENT_,devReplaceItemState=DEV_REPLACE_ITEM_STATE_,blockGroup:uint64=BLOCK_GROUP_ -o btrfs_tree_hc.go btrfs_tree.h //go:generate gofmt -l -w btrfs_tree_hc.go + +/* +btrfs_tree.h can be found at https://github.com/torvalds/linux/blob/v5.13/include/uapi/linux/btrfs_tree.h +btrfs_tree.h is licensed under the terms of "GPL-2.0 WITH Linux-syscall-note": https://github.com/torvalds/linux/blob/v5.13/LICENSES/exceptions/Linux-syscall-note + +containerd/btrfs shall be considered as "user programs that use kernel services by normal system calls" mentioned in the note above, +and "does *not* fall under the heading of \"derived work\"" of the GPL-2.0 code. +*/ diff --git a/internal/cmd/hgen.go b/internal/cmd/hgen.go index 95efeba..0d79bd0 100644 --- a/internal/cmd/hgen.go +++ b/internal/cmd/hgen.go @@ -1,4 +1,4 @@ -package cmd +package main import ( "bufio" From c6b2e42e6330a16cd62f22ea86be0aaa34db1d2e Mon Sep 17 00:00:00 2001 From: Akihiro Suda Date: Thu, 1 Jul 2021 15:40:30 +0900 Subject: [PATCH 35/38] btrfs_test: fix import path Signed-off-by: Akihiro Suda --- btrfs_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/btrfs_test.go b/btrfs_test.go index 3e183e6..7a49b64 100644 --- a/btrfs_test.go +++ b/btrfs_test.go @@ -8,6 +8,8 @@ import ( "reflect" "sort" "testing" + + btrfstest "github.com/containerd/btrfs/v2/test" ) const sizeDef = 256 * 1024 * 1024 From 21aa2db69c4c26b19e29a987726e80564c99d037 Mon Sep 17 00:00:00 2001 From: Akihiro Suda Date: Thu, 1 Jul 2021 16:13:36 +0900 Subject: [PATCH 36/38] Add back containerd boilterplate (but as a non-core subproject) We would like to invite dennwc as a maintainer of this repo, so the status of the repo is now changed from core to non-core. Signed-off-by: Akihiro Suda --- .github/workflows/ci.yml | 45 ++++++++++++++++++++++++++++++++++++ MAINTAINERS | 8 +++++++ README.md | 50 ++++++++++++++++++++++++++++++++++++++++ btrfs.go | 16 +++++++++++++ btrfs_h.go | 16 +++++++++++++ btrfs_list.go | 16 +++++++++++++ btrfs_test.go | 16 +++++++++++++ btrfs_tree.go | 16 +++++++++++++ btrfs_tree_h.go | 16 +++++++++++++ btrfs_tree_hc.go | 18 ++++++++++++++- cmd/gbtrfs/gbtrfs.go | 16 +++++++++++++ errors.go | 16 +++++++++++++ headers.go | 18 +++++++++++++++ internal/cmd/hgen.go | 16 +++++++++++++ ioctl/ioctl.go | 16 +++++++++++++ ioctl_h.go | 16 +++++++++++++ license-templates/go.txt | 16 +++++++++++++ mtab/mtab.go | 16 +++++++++++++ receive.go | 16 +++++++++++++ send.go | 16 +++++++++++++ send/send.go | 16 +++++++++++++ send/send_h.go | 16 +++++++++++++ size_test.go | 16 +++++++++++++ subvolume.go | 16 +++++++++++++ test/btrfstest.go | 16 +++++++++++++ usage.go | 16 +++++++++++++ utils.go | 16 +++++++++++++ uuid_tree.go | 16 +++++++++++++ xattr.go | 16 +++++++++++++ 29 files changed, 522 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/ci.yml create mode 100644 MAINTAINERS create mode 100644 README.md create mode 100644 license-templates/go.txt diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..cf3c73c --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,45 @@ +name: CI + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + + build: + name: Btrfs CI + runs-on: ubuntu-18.04 + timeout-minutes: 5 + steps: + + - name: Set up Go + uses: actions/setup-go@v2 + with: + go-version: 1.16.x + id: go + + - name: Setup environment + shell: bash + run: | + echo "GOPATH=${{ github.workspace }}" >> $GITHUB_ENV + echo "${{ github.workspace }}/bin" >> $GITHUB_PATH + + - name: Check out code + uses: actions/checkout@v2 + with: + path: src/github.com/containerd/btrfs + fetch-depth: 25 + + - name: Project checks + uses: containerd/project-checks@v1 + # Disabled during repo migration + if: false + with: + working-directory: src/github.com/containerd/btrfs + + - name: Test + working-directory: src/github.com/containerd/btrfs + run: | + sudo go test -v ./... diff --git a/MAINTAINERS b/MAINTAINERS new file mode 100644 index 0000000..cffa015 --- /dev/null +++ b/MAINTAINERS @@ -0,0 +1,8 @@ +# containerd/btrfs maintainers +# +# As a containerd sub-project, containerd maintainers are also included from https://github.com/containerd/project/blob/main/MAINTAINERS. +# See https://github.com/containerd/project/blob/main/GOVERNANCE.md for description of maintainer role +# +# COMMITTERS +# GitHub ID, Name, Email address +"dennwc","Denys Smirnov","dennwc@pm.me" diff --git a/README.md b/README.md new file mode 100644 index 0000000..5feecb4 --- /dev/null +++ b/README.md @@ -0,0 +1,50 @@ +# go-btrfs (v2) + +[![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. + +* v1 (`release/1.0` branch): legacy version with cgo. Used in containerd 1.5 and before. +* v2 (`main` branch): modern pure-Go implementation, adopted from [dennwc/btrfs](https://github.com/dennwc/btrfs). + Planned to be used in containerd 1.6. + +# Status + +v2 codes 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 + +containerd/btrfs is a containerd **non-core** sub-project, licensed under the [Apache 2.0 license](./LICENSE). +As a containerd non-core sub-project, you will find the: + * [Project governance](https://github.com/containerd/project/blob/master/GOVERNANCE.md), + * [Maintainers](./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/btrfs.go b/btrfs.go index df0673c..24610ce 100644 --- a/btrfs.go +++ b/btrfs.go @@ -1,3 +1,19 @@ +/* + 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 ( diff --git a/btrfs_h.go b/btrfs_h.go index 192af83..6f1d1ed 100644 --- a/btrfs_h.go +++ b/btrfs_h.go @@ -1,3 +1,19 @@ +/* + 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 "strings" diff --git a/btrfs_list.go b/btrfs_list.go index 4929fe6..30c965a 100644 --- a/btrfs_list.go +++ b/btrfs_list.go @@ -1,3 +1,19 @@ +/* + 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 "os" diff --git a/btrfs_test.go b/btrfs_test.go index 7a49b64..cc0b68e 100644 --- a/btrfs_test.go +++ b/btrfs_test.go @@ -1,3 +1,19 @@ +/* + 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 ( diff --git a/btrfs_tree.go b/btrfs_tree.go index 514e8cd..8ffe676 100644 --- a/btrfs_tree.go +++ b/btrfs_tree.go @@ -1,3 +1,19 @@ +/* + 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 ( diff --git a/btrfs_tree_h.go b/btrfs_tree_h.go index d5ebd66..74499c5 100644 --- a/btrfs_tree_h.go +++ b/btrfs_tree_h.go @@ -1,3 +1,19 @@ +/* + 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 /* diff --git a/btrfs_tree_hc.go b/btrfs_tree_hc.go index 787bb4d..2745e01 100644 --- a/btrfs_tree_hc.go +++ b/btrfs_tree_hc.go @@ -1,3 +1,19 @@ +/* + 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 // This code was auto-generated; DO NOT EDIT! @@ -14,7 +30,7 @@ type devReplaceItemState int type blockGroup uint64 -// SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note +// SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note (see headers.go) const ( diff --git a/cmd/gbtrfs/gbtrfs.go b/cmd/gbtrfs/gbtrfs.go index 0a92d7d..5d41c73 100644 --- a/cmd/gbtrfs/gbtrfs.go +++ b/cmd/gbtrfs/gbtrfs.go @@ -1,3 +1,19 @@ +/* + 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 main import ( diff --git a/errors.go b/errors.go index b852bcf..06b9224 100644 --- a/errors.go +++ b/errors.go @@ -1,3 +1,19 @@ +/* + 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 ( diff --git a/headers.go b/headers.go index 02eea04..f175bf4 100644 --- a/headers.go +++ b/headers.go @@ -1,6 +1,24 @@ +/* + 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 //go:generate go run ./internal/cmd/hgen.go -u -g -t BTRFS_ -p btrfs -cs=treeKeyType:uint32=_KEY,objectID:uint64=_OBJECTID -cp=fileType=FT_,fileExtentType=FILE_EXTENT_,devReplaceItemState=DEV_REPLACE_ITEM_STATE_,blockGroup:uint64=BLOCK_GROUP_ -o btrfs_tree_hc.go btrfs_tree.h +//go:generate ltag -t ./license-templates btrfs_tree_hc.go +//go:generate sed -i -e "s/SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note/SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note (see headers.go)/" btrfs_tree_hc.go //go:generate gofmt -l -w btrfs_tree_hc.go /* diff --git a/internal/cmd/hgen.go b/internal/cmd/hgen.go index 0d79bd0..49bca85 100644 --- a/internal/cmd/hgen.go +++ b/internal/cmd/hgen.go @@ -1,3 +1,19 @@ +/* + 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 main import ( diff --git a/ioctl/ioctl.go b/ioctl/ioctl.go index fe1d886..6f0d66f 100644 --- a/ioctl/ioctl.go +++ b/ioctl/ioctl.go @@ -1,3 +1,19 @@ +/* + 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 ioctl import ( diff --git a/ioctl_h.go b/ioctl_h.go index 769689b..3f294c9 100644 --- a/ioctl_h.go +++ b/ioctl_h.go @@ -1,3 +1,19 @@ +/* + 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 ( diff --git a/license-templates/go.txt b/license-templates/go.txt new file mode 100644 index 0000000..bee278d --- /dev/null +++ b/license-templates/go.txt @@ -0,0 +1,16 @@ +/* + 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. +*/ + diff --git a/mtab/mtab.go b/mtab/mtab.go index e1651ed..b18b7dc 100644 --- a/mtab/mtab.go +++ b/mtab/mtab.go @@ -1,3 +1,19 @@ +/* + 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 mtab contains tools to work with /etc/mtab file. package mtab diff --git a/receive.go b/receive.go index 015714a..e114106 100644 --- a/receive.go +++ b/receive.go @@ -1,3 +1,19 @@ +/* + 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 ( diff --git a/send.go b/send.go index 0f324bb..a6708ea 100644 --- a/send.go +++ b/send.go @@ -1,3 +1,19 @@ +/* + 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 ( diff --git a/send/send.go b/send/send.go index 9673f98..e17d3dc 100644 --- a/send/send.go +++ b/send/send.go @@ -1,3 +1,19 @@ +/* + 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 send import ( diff --git a/send/send_h.go b/send/send_h.go index 63e3d32..d113a80 100644 --- a/send/send_h.go +++ b/send/send_h.go @@ -1,3 +1,19 @@ +/* + 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 send import ( diff --git a/size_test.go b/size_test.go index 5c2c10d..b883a4e 100644 --- a/size_test.go +++ b/size_test.go @@ -1,3 +1,19 @@ +/* + 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 ( diff --git a/subvolume.go b/subvolume.go index 0d2d808..b6fe015 100644 --- a/subvolume.go +++ b/subvolume.go @@ -1,3 +1,19 @@ +/* + 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 ( diff --git a/test/btrfstest.go b/test/btrfstest.go index f85862d..0162a44 100644 --- a/test/btrfstest.go +++ b/test/btrfstest.go @@ -1,3 +1,19 @@ +/* + 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 btrfstest import ( diff --git a/usage.go b/usage.go index 792ea86..dbac42d 100644 --- a/usage.go +++ b/usage.go @@ -1,3 +1,19 @@ +/* + 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 ( diff --git a/utils.go b/utils.go index 06028b5..2111f47 100644 --- a/utils.go +++ b/utils.go @@ -1,3 +1,19 @@ +/* + 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 ( diff --git a/uuid_tree.go b/uuid_tree.go index 4906ad0..7d23f08 100644 --- a/uuid_tree.go +++ b/uuid_tree.go @@ -1,3 +1,19 @@ +/* + 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 ( diff --git a/xattr.go b/xattr.go index ae4b7d2..fefab63 100644 --- a/xattr.go +++ b/xattr.go @@ -1,3 +1,19 @@ +/* + 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 ( From 38ecd2b98edb3c8cde967fc61a050c681f6f0942 Mon Sep 17 00:00:00 2001 From: Akihiro Suda Date: Thu, 1 Jul 2021 21:36:16 +0900 Subject: [PATCH 37/38] ioctl: import dennwc/ioctl Import dennwc/ioctl@01780425206817ff049d3eef4327d3f28e04450f Signed-off-by: Akihiro Suda --- btrfs.go | 3 +- go.mod | 5 +-- go.sum | 2 -- ioctl/ioctl.go | 85 ++++++++++++++++++++++++++++----------------- ioctl/ioctl_test.go | 37 ++++++++++++++++++++ ioctl_h.go | 3 +- 6 files changed, 95 insertions(+), 40 deletions(-) create mode 100644 ioctl/ioctl_test.go diff --git a/btrfs.go b/btrfs.go index 24610ce..7baba21 100644 --- a/btrfs.go +++ b/btrfs.go @@ -18,12 +18,13 @@ package btrfs import ( "fmt" - "github.com/dennwc/ioctl" "io" "os" "path/filepath" "strconv" "syscall" + + "github.com/containerd/btrfs/v2/ioctl" ) const SuperMagic = 0x9123683E diff --git a/go.mod b/go.mod index 5b07d0c..b36a5e2 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,4 @@ module github.com/containerd/btrfs/v2 go 1.16 -require ( - github.com/dennwc/ioctl v1.0.0 - github.com/spf13/cobra v1.1.3 -) +require github.com/spf13/cobra v1.1.3 diff --git a/go.sum b/go.sum index c887739..a759029 100644 --- a/go.sum +++ b/go.sum @@ -33,8 +33,6 @@ github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfc github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dennwc/ioctl v1.0.0 h1:DsWAAjIxRqNcLn9x6mwfuf2pet3iB7aK90K4tF16rLg= -github.com/dennwc/ioctl v1.0.0/go.mod h1:ellh2YB5ldny99SBU/VX7Nq0xiZbHphf1DrtHxxjMk0= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= diff --git a/ioctl/ioctl.go b/ioctl/ioctl.go index 6f0d66f..57479c2 100644 --- a/ioctl/ioctl.go +++ b/ioctl/ioctl.go @@ -17,61 +17,82 @@ package ioctl import ( - "github.com/dennwc/ioctl" + "fmt" "os" + "reflect" + "syscall" ) const ( - None = ioctl.None - Write = ioctl.Write - Read = ioctl.Read + nrBits = 8 + typeBits = 8 + sizeBits = 14 + dirBits = 2 +) + +const ( + nrMask = (1 << nrBits) - 1 + typeMask = (1 << typeBits) - 1 + sizeMask = (1 << sizeBits) - 1 + dirMask = (1 << dirBits) - 1 +) + +const ( + nrShift = 0 + typeShift = nrShift + nrBits + sizeShift = typeShift + typeBits + dirShift = sizeShift + sizeBits +) + +const ( + None = 0 + Write = 1 + Read = 2 ) -// IOC -// -// Deprecated: use github/dennwc/ioctl func IOC(dir, typ, nr, size uintptr) uintptr { - return ioctl.IOC(dir, typ, nr, size) + return (dir << dirShift) | + (typ << typeShift) | + (nr << nrShift) | + (size << sizeShift) } -// IO -// -// Deprecated: use github/dennwc/ioctl func IO(typ, nr uintptr) uintptr { - return ioctl.IO(typ, nr) + return IOC(None, typ, nr, 0) } -// IOC -// -// Deprecated: use github/dennwc/ioctl func IOR(typ, nr, size uintptr) uintptr { - return ioctl.IOR(typ, nr, size) + return IOC(Read, typ, nr, size) } -// IOW -// -// Deprecated: use github/dennwc/ioctl func IOW(typ, nr, size uintptr) uintptr { - return ioctl.IOW(typ, nr, size) + return IOC(Write, typ, nr, size) } -// IOWR -// -// Deprecated: use github/dennwc/ioctl func IOWR(typ, nr, size uintptr) uintptr { - return ioctl.IOWR(typ, nr, size) + return IOC(Read|Write, typ, nr, size) } -// Ioctl -// -// Deprecated: use github/dennwc/ioctl func Ioctl(f *os.File, ioc uintptr, addr uintptr) error { - return ioctl.Ioctl(f, ioc, addr) + _, _, e := syscall.Syscall(syscall.SYS_IOCTL, f.Fd(), ioc, addr) + if e != 0 { + return e + } + return nil } -// Do -// -// Deprecated: use github/dennwc/ioctl func Do(f *os.File, ioc uintptr, arg interface{}) error { - return ioctl.Do(f, ioc, arg) + var addr uintptr + if arg != nil { + v := reflect.ValueOf(arg) + switch v.Kind() { + case reflect.Ptr: + addr = v.Elem().UnsafeAddr() + case reflect.Slice: + addr = v.Index(0).UnsafeAddr() + default: + return fmt.Errorf("expected ptr or slice, got %T", arg) + } + } + return Ioctl(f, ioc, addr) } diff --git a/ioctl/ioctl_test.go b/ioctl/ioctl_test.go new file mode 100644 index 0000000..dff15ae --- /dev/null +++ b/ioctl/ioctl_test.go @@ -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. +*/ + +package ioctl + +import ( + "testing" +) + +var casesIOC = []struct { + got uintptr + expect uintptr +}{ + {got: IOC(1, 2, 3, 4), expect: 0x40040203}, +} + +func TestIOC(t *testing.T) { + for i, c := range casesIOC { + if c.got != c.expect { + t.Errorf("unexpected ioc (case %d): %x(%b) vs %x(%b)", + i+1, c.got, c.got, c.expect, c.expect) + } + } +} diff --git a/ioctl_h.go b/ioctl_h.go index 3f294c9..f297580 100644 --- a/ioctl_h.go +++ b/ioctl_h.go @@ -19,11 +19,12 @@ package btrfs import ( "encoding/binary" "encoding/hex" - "github.com/dennwc/ioctl" "os" "strconv" "strings" "unsafe" + + "github.com/containerd/btrfs/v2/ioctl" ) var order = binary.LittleEndian From 412d0f4b8785892d5ed2165822b22b30208b31f2 Mon Sep 17 00:00:00 2001 From: Akihiro Suda Date: Thu, 1 Jul 2021 21:38:54 +0900 Subject: [PATCH 38/38] add back cmd/gbtrfs/go.mod Signed-off-by: Akihiro Suda --- .github/workflows/ci.yml | 5 + cmd/gbtrfs/go.mod | 10 ++ cmd/gbtrfs/go.sum | 286 +++++++++++++++++++++++++++++++++++++++ go.mod | 2 - go.sum | 286 --------------------------------------- 5 files changed, 301 insertions(+), 288 deletions(-) create mode 100644 cmd/gbtrfs/go.mod create mode 100644 cmd/gbtrfs/go.sum diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cf3c73c..04ecce6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -43,3 +43,8 @@ jobs: working-directory: src/github.com/containerd/btrfs run: | sudo go test -v ./... + + - name: Build gbtrfs + working-directory: src/github.com/containerd/btrfs/cmd/gbtrfs + run: | + go build . diff --git a/cmd/gbtrfs/go.mod b/cmd/gbtrfs/go.mod new file mode 100644 index 0000000..ce0fe87 --- /dev/null +++ b/cmd/gbtrfs/go.mod @@ -0,0 +1,10 @@ +module github.com/containerd/btrfs/v2/cmd/gbtrfs + +go 1.16 + +replace github.com/containerd/btrfs/v2 => ../.. + +require ( + github.com/containerd/btrfs/v2 v2.0.0-00010101000000-000000000000 + github.com/spf13/cobra v1.1.3 +) diff --git a/cmd/gbtrfs/go.sum b/cmd/gbtrfs/go.sum new file mode 100644 index 0000000..a759029 --- /dev/null +++ b/cmd/gbtrfs/go.sum @@ -0,0 +1,286 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v1.1.3 h1:xghbfqPkxzxP3C/f3n5DdpAbdKLj4ZE4BWQI362l53M= +github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= diff --git a/go.mod b/go.mod index b36a5e2..dbe96fc 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,3 @@ module github.com/containerd/btrfs/v2 go 1.16 - -require github.com/spf13/cobra v1.1.3 diff --git a/go.sum b/go.sum index a759029..e69de29 100644 --- a/go.sum +++ b/go.sum @@ -1,286 +0,0 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= -github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= -github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= -github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= -github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= -github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= -github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= -github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= -github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= -github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= -github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= -github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= -github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= -github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= -github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= -github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= -github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= -github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= -github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= -github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= -github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= -github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v1.1.3 h1:xghbfqPkxzxP3C/f3n5DdpAbdKLj4ZE4BWQI362l53M= -github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= -github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= -github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= -go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= -gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=