btrfs: depend on kernel UAPI instead of libbtrfs
See containerd/btrfs PR 40 and moby/moby PR 44761. (Thanks to [@]neersighted.) The containerd/btrfs library now requires headers from kernel 4.12 or newer: - https://github.com/torvalds/linux/blob/master/include/uapi/linux/btrfs.h - https://github.com/torvalds/linux/blob/master/include/uapi/linux/btrfs_tree.h These files are licensed under the GPL-2.0 WITH Linux-syscall-note, so it should be compatible with the Apache License 2.0. https://spdx.org/licenses/Linux-syscall-note.html The dependency on the kernel headers only affects users building from source. Users on older kernels may opt to not compile this library (`BUILDTAGS=no_btfs`), or to provide headers from a newer kernel. Signed-off-by: Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp>
This commit is contained in:
		| @@ -11,6 +11,21 @@ Native Go bindings for btrfs. | ||||
| These are in the early stages. We will try to maintain stability, but please | ||||
| vendor if you are relying on these directly. | ||||
| 
 | ||||
| # Dependencies | ||||
| 
 | ||||
| ## v2.x | ||||
| 
 | ||||
| Headers from kernel 4.12 or newer. | ||||
| The package name is `linux-libc-dev` on Debian/Ubuntu, `kernel-headers` on Fedora and RHEL-like distros. | ||||
| 
 | ||||
| The headers are only required on compilation time, not on run time. | ||||
| 
 | ||||
| ## v1.x | ||||
| 
 | ||||
| libbtrfs headers. | ||||
| The package name is `libbtrfs-dev` on Debian/Ubuntu, `btrfs-progs-devel` on Fedora and CentOS 7. | ||||
| The package is not available for Rocky Linux and AlmaLinux. | ||||
| 
 | ||||
| # Contribute | ||||
| 
 | ||||
| This package may not cover all the use cases for btrfs. If something you need | ||||
| @@ -14,20 +14,16 @@ | ||||
|    limitations under the License. | ||||
| */ | ||||
| 
 | ||||
| #include <stddef.h> | ||||
| #include <linux/magic.h> | ||||
| #include <btrfs/ioctl.h> | ||||
| #include <btrfs/ctree.h> | ||||
| 
 | ||||
| #include <string.h> | ||||
| #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); | ||||
| 	dst->generation = src->generation; | ||||
| 	dst->otransid = src->otransid; | ||||
| 	dst->flags = src->flags; | ||||
| } | ||||
| 
 | ||||
| /* unpack_root_ref(struct gosafe_btrfs_root_ref* dst, struct btrfs_root_ref* src) { */ | ||||
| @@ -17,8 +17,7 @@ | ||||
| package btrfs | ||||
| 
 | ||||
| /* | ||||
| #include <stddef.h> | ||||
| #include <btrfs/ioctl.h> | ||||
| #include <linux/magic.h> | ||||
| #include "btrfs.h" | ||||
| 
 | ||||
| static char* get_name_btrfs_ioctl_vol_args_v2(struct btrfs_ioctl_vol_args_v2* btrfs_struct) { | ||||
| @@ -28,13 +27,13 @@ static char* get_name_btrfs_ioctl_vol_args_v2(struct btrfs_ioctl_vol_args_v2* bt | ||||
| import "C" | ||||
| 
 | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 	"sort" | ||||
| 	"syscall" | ||||
| 	"unsafe" | ||||
| 
 | ||||
| 	"github.com/pkg/errors" | ||||
| ) | ||||
| 
 | ||||
| // maxByteSliceSize is the smallest size that Go supports on various platforms. | ||||
| @@ -99,7 +98,7 @@ func SubvolInfo(path string) (info Info, err error) { | ||||
| 		return *info, nil | ||||
| 	} | ||||
| 
 | ||||
| 	return info, errors.Errorf("%q not found", path) | ||||
| 	return info, fmt.Errorf("%q not found", path) | ||||
| } | ||||
| 
 | ||||
| func subvolMap(path string) (map[uint64]*Info, error) { | ||||
| @@ -154,13 +153,13 @@ func subvolMap(path string) (map[uint64]*Info, error) { | ||||
| 				// get an entry of the objectid, with name, but the parent is | ||||
| 				// the offset. | ||||
| 
 | ||||
| 				nname := C.btrfs_stack_root_ref_name_len(&rr) | ||||
| 				nname := le16ToNative(rr.name_len) | ||||
| 				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)) | ||||
| 				info.DirID = le64ToNative(rr.dirid) | ||||
| 
 | ||||
| 				subvolsByID[uint64(sh.objectid)] = info | ||||
| 			} else if sh._type == C.BTRFS_ROOT_ITEM_KEY && | ||||
| @@ -185,8 +184,8 @@ func subvolMap(path string) (map[uint64]*Info, error) { | ||||
| 				info.ParentUUID = uuidString(&gri.parent_uuid) | ||||
| 				info.ReceivedUUID = uuidString(&gri.received_uuid) | ||||
| 
 | ||||
| 				info.Generation = uint64(gri.gen) | ||||
| 				info.OriginalGeneration = uint64(gri.ogen) | ||||
| 				info.Generation = le64ToNative(gri.generation) | ||||
| 				info.OriginalGeneration = le64ToNative(gri.otransid) | ||||
| 
 | ||||
| 				subvolsByID[uint64(sh.objectid)] = info | ||||
| 			} | ||||
| @@ -273,13 +272,13 @@ func SubvolCreate(path string) error { | ||||
| 	args.fd = C.__s64(fp.Fd()) | ||||
| 
 | ||||
| 	if len(name) > C.BTRFS_PATH_NAME_MAX { | ||||
| 		return errors.Errorf("%q too long for subvolume", name) | ||||
| 		return fmt.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 fmt.Errorf("btrfs subvolume create failed: %w", err) | ||||
| 	} | ||||
| 
 | ||||
| 	return nil | ||||
| @@ -292,13 +291,13 @@ func SubvolSnapshot(dst, src string, readonly bool) error { | ||||
| 
 | ||||
| 	dstfp, err := openSubvolDir(dstdir) | ||||
| 	if err != nil { | ||||
| 		return errors.Wrapf(err, "opening snapshot destination subvolume failed") | ||||
| 		return fmt.Errorf("opening snapshot destination subvolume failed: %w", err) | ||||
| 	} | ||||
| 	defer dstfp.Close() | ||||
| 
 | ||||
| 	srcfp, err := openSubvolDir(src) | ||||
| 	if err != nil { | ||||
| 		return errors.Wrapf(err, "opening snapshot source subvolume failed") | ||||
| 		return fmt.Errorf("opening snapshot source subvolume failed: %w", err) | ||||
| 	} | ||||
| 	defer srcfp.Close() | ||||
| 
 | ||||
| @@ -308,7 +307,7 @@ func SubvolSnapshot(dst, src string, readonly bool) error { | ||||
| 	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) | ||||
| 		return fmt.Errorf("%q too long for subvolume", dstname) | ||||
| 	} | ||||
| 
 | ||||
| 	nameptr := (*[maxByteSliceSize]byte)(unsafe.Pointer(name))[:C.BTRFS_SUBVOL_NAME_MAX:C.BTRFS_SUBVOL_NAME_MAX] | ||||
| @@ -319,7 +318,7 @@ func SubvolSnapshot(dst, src string, readonly bool) error { | ||||
| 	} | ||||
| 
 | ||||
| 	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 fmt.Errorf("snapshot create failed: %w", err) | ||||
| 	} | ||||
| 
 | ||||
| 	return nil | ||||
| @@ -330,7 +329,7 @@ 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) | ||||
| 		return fmt.Errorf("failed opening %v: %w", path, err) | ||||
| 	} | ||||
| 	defer fp.Close() | ||||
| 
 | ||||
| @@ -341,7 +340,7 @@ func SubvolDelete(path string) error { | ||||
| 				return nil | ||||
| 			} | ||||
| 
 | ||||
| 			return errors.Wrapf(err, "failed walking subvolume %v", p) | ||||
| 			return fmt.Errorf("failed walking subvolume %v: %w", p, err) | ||||
| 		} | ||||
| 
 | ||||
| 		if !fi.IsDir() { | ||||
| @@ -357,7 +356,7 @@ func SubvolDelete(path string) error { | ||||
| 		} | ||||
| 
 | ||||
| 		if err := SubvolDelete(p); err != nil { | ||||
| 			return errors.Wrapf(err, "recursive delete of %v failed", p) | ||||
| 			return fmt.Errorf("recursive delete of %v failed: %w", p, err) | ||||
| 		} | ||||
| 
 | ||||
| 		return filepath.SkipDir // children get walked by call above. | ||||
| @@ -367,14 +366,14 @@ func SubvolDelete(path string) error { | ||||
| 
 | ||||
| 	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) | ||||
| 		return fmt.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 fmt.Errorf("failed removing subvolume %v: %w", path, err) | ||||
| 	} | ||||
| 
 | ||||
| 	return nil | ||||
| @@ -383,7 +382,7 @@ func SubvolDelete(path string) error { | ||||
| 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 nil, fmt.Errorf("opening %v as subvolume failed: %w", path, err) | ||||
| 	} | ||||
| 
 | ||||
| 	return fp, nil | ||||
| @@ -391,7 +390,7 @@ func openSubvolDir(path string) (*os.File, error) { | ||||
| 
 | ||||
| func isStatfsSubvol(statfs *syscall.Statfs_t) error { | ||||
| 	if int64(statfs.Type) != int64(C.BTRFS_SUPER_MAGIC) { | ||||
| 		return errors.Errorf("not a btrfs filesystem") | ||||
| 		return fmt.Errorf("not a btrfs filesystem") | ||||
| 	} | ||||
| 
 | ||||
| 	return nil | ||||
| @@ -399,13 +398,13 @@ func isStatfsSubvol(statfs *syscall.Statfs_t) error { | ||||
| 
 | ||||
| func isFileInfoSubvol(fi os.FileInfo) error { | ||||
| 	if !fi.IsDir() { | ||||
| 		errors.Errorf("must be a directory") | ||||
| 		return errors.New("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 fmt.Errorf("incorrect inode type") | ||||
| 	} | ||||
| 
 | ||||
| 	return nil | ||||
| @@ -14,23 +14,25 @@ | ||||
|    limitations under the License. | ||||
| */ | ||||
| 
 | ||||
| #include <stddef.h> | ||||
| #include <linux/magic.h> | ||||
| #include <btrfs/ioctl.h> | ||||
| #include <btrfs/ctree.h> | ||||
| #include <linux/version.h> | ||||
| #if LINUX_VERSION_CODE < KERNEL_VERSION(4,12,0) | ||||
| #error "Headers from kernel >= 4.12 are required on compilation time (not on run time)" | ||||
| #endif | ||||
| #include <linux/btrfs.h> | ||||
| #include <linux/btrfs_tree.h> | ||||
| 
 | ||||
| // 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]; | ||||
| 	__u8 uuid[BTRFS_UUID_SIZE]; | ||||
| 	__u8 parent_uuid[BTRFS_UUID_SIZE]; | ||||
| 	__u8 received_uuid[BTRFS_UUID_SIZE]; | ||||
| 
 | ||||
| 	u64 gen; | ||||
| 	u64 ogen; | ||||
| 	u64 flags; | ||||
| 	__le64 generation; | ||||
| 	__le64 otransid; | ||||
| 	__le64 flags; | ||||
| }; | ||||
| 
 | ||||
| void unpack_root_item(struct gosafe_btrfs_root_item* dst, struct btrfs_root_item* src); | ||||
| @@ -17,21 +17,20 @@ | ||||
| package btrfs | ||||
| 
 | ||||
| /* | ||||
| #include <stddef.h> | ||||
| #include <btrfs/ioctl.h> | ||||
| #include <btrfs/ctree.h> | ||||
| #include "btrfs.h" | ||||
| */ | ||||
| import "C" | ||||
| 
 | ||||
| import ( | ||||
| 	"bufio" | ||||
| 	"bytes" | ||||
| 	"encoding/binary" | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| 	"strings" | ||||
| 	"unsafe" | ||||
| 
 | ||||
| 	"github.com/pkg/errors" | ||||
| 	"golang.org/x/sys/cpu" | ||||
| ) | ||||
| 
 | ||||
| func subvolID(fd uintptr) (uint64, error) { | ||||
| @@ -50,7 +49,7 @@ var ( | ||||
| 	zeros     = zeroArray[:] | ||||
| ) | ||||
| 
 | ||||
| func uuidString(uuid *[C.BTRFS_UUID_SIZE]C.u8) string { | ||||
| 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) { | ||||
| @@ -60,6 +59,24 @@ func uuidString(uuid *[C.BTRFS_UUID_SIZE]C.u8) string { | ||||
| 	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 le16ToNative(le16 C.__le16) uint16 { | ||||
| 	if cpu.IsBigEndian { | ||||
| 		b := make([]byte, 2) | ||||
| 		binary.LittleEndian.PutUint16(b, uint16(le16)) | ||||
| 		return binary.BigEndian.Uint16(b) | ||||
| 	} | ||||
| 	return uint16(le16) | ||||
| } | ||||
| 
 | ||||
| func le64ToNative(le64 C.__le64) uint64 { | ||||
| 	if cpu.IsBigEndian { | ||||
| 		b := make([]byte, 8) | ||||
| 		binary.LittleEndian.PutUint64(b, uint64(le64)) | ||||
| 		return binary.BigEndian.Uint64(b) | ||||
| 	} | ||||
| 	return uint64(le64) | ||||
| } | ||||
| 
 | ||||
| func findMountPoint(path string) (string, error) { | ||||
| 	fp, err := os.Open("/proc/self/mounts") | ||||
| 	if err != nil { | ||||
| @@ -95,7 +112,7 @@ func findMountPoint(path string) (string, error) { | ||||
| 	} | ||||
| 
 | ||||
| 	if mount == "" { | ||||
| 		return "", errors.Errorf("mount point of %v not found", path) | ||||
| 		return "", fmt.Errorf("mount point of %v not found", path) | ||||
| 	} | ||||
| 
 | ||||
| 	return mount, nil | ||||
		Reference in New Issue
	
	Block a user
	 Akihiro Suda
					Akihiro Suda