From 92d4035074dd5234f1c3fd263947dde1f9bb288e Mon Sep 17 00:00:00 2001 From: Omar Sandoval Date: Thu, 18 Jan 2018 12:49:55 -0800 Subject: libbtrfsutil: add btrfs_util_is_subvolume() and btrfs_util_subvolume_id() These are the most trivial helpers in the library and will be used to implement several of the more involved functions. Signed-off-by: Omar Sandoval Signed-off-by: David Sterba --- Makefile | 2 +- libbtrfsutil/python/btrfsutilpy.h | 2 + libbtrfsutil/python/module.c | 12 +++ libbtrfsutil/python/setup.py | 1 + libbtrfsutil/python/subvolume.c | 73 ++++++++++++++++ libbtrfsutil/python/tests/test_subvolume.py | 57 +++++++++++++ libbtrfsutil/subvolume.c | 125 ++++++++++++++++++++++++++++ 7 files changed, 271 insertions(+), 1 deletion(-) create mode 100644 libbtrfsutil/python/subvolume.c create mode 100644 libbtrfsutil/python/tests/test_subvolume.py create mode 100644 libbtrfsutil/subvolume.c diff --git a/Makefile b/Makefile index 622095ba..24af13a4 100644 --- a/Makefile +++ b/Makefile @@ -136,7 +136,7 @@ libbtrfsutil_minor := $(shell sed -rn 's/^\#define BTRFS_UTIL_VERSION_MINOR ([0- libbtrfsutil_patch := $(shell sed -rn 's/^\#define BTRFS_UTIL_VERSION_PATCH ([0-9])+$$/\1/p' libbtrfsutil/btrfsutil.h) libbtrfsutil_version := $(libbtrfsutil_major).$(libbtrfsutil_minor).$(libbtrfsutil_patch) libbtrfsutil_objects = libbtrfsutil/errors.o libbtrfsutil/filesystem.o \ - libbtrfsutil/qgroup.o + libbtrfsutil/subvolume.o libbtrfsutil/qgroup.o convert_objects = convert/main.o convert/common.o convert/source-fs.o \ convert/source-ext2.o convert/source-reiserfs.o mkfs_objects = mkfs/main.o mkfs/common.o mkfs/rootdir.o diff --git a/libbtrfsutil/python/btrfsutilpy.h b/libbtrfsutil/python/btrfsutilpy.h index 02039ba1..47125d85 100644 --- a/libbtrfsutil/python/btrfsutilpy.h +++ b/libbtrfsutil/python/btrfsutilpy.h @@ -61,6 +61,8 @@ void SetFromBtrfsUtilErrorWithPaths(enum btrfs_util_error err, PyObject *filesystem_sync(PyObject *self, PyObject *args, PyObject *kwds); PyObject *start_sync(PyObject *self, PyObject *args, PyObject *kwds); PyObject *wait_sync(PyObject *self, PyObject *args, PyObject *kwds); +PyObject *is_subvolume(PyObject *self, PyObject *args, PyObject *kwds); +PyObject *subvolume_id(PyObject *self, PyObject *args, PyObject *kwds); void add_module_constants(PyObject *m); diff --git a/libbtrfsutil/python/module.c b/libbtrfsutil/python/module.c index 9c1a864a..18e8c5aa 100644 --- a/libbtrfsutil/python/module.c +++ b/libbtrfsutil/python/module.c @@ -153,6 +153,18 @@ static PyMethodDef btrfsutil_methods[] = { "path -- string, bytes, path-like object, or open file descriptor\n" "transid -- int transaction ID to wait for, or zero for the current\n" "transaction"}, + {"is_subvolume", (PyCFunction)is_subvolume, + METH_VARARGS | METH_KEYWORDS, + "is_subvolume(path) -> bool\n\n" + "Get whether a file is a subvolume.\n\n" + "Arguments:\n" + "path -- string, bytes, path-like object, or open file descriptor"}, + {"subvolume_id", (PyCFunction)subvolume_id, + METH_VARARGS | METH_KEYWORDS, + "subvolume_id(path) -> int\n\n" + "Get the ID of the subvolume containing a file.\n\n" + "Arguments:\n" + "path -- string, bytes, path-like object, or open file descriptor"}, {}, }; diff --git a/libbtrfsutil/python/setup.py b/libbtrfsutil/python/setup.py index df44809f..1cd3bc00 100755 --- a/libbtrfsutil/python/setup.py +++ b/libbtrfsutil/python/setup.py @@ -93,6 +93,7 @@ module = Extension( 'filesystem.c', 'module.c', 'qgroup.c', + 'subvolume.c', ], include_dirs=['..'], library_dirs=['../..'], diff --git a/libbtrfsutil/python/subvolume.c b/libbtrfsutil/python/subvolume.c new file mode 100644 index 00000000..4aab06a5 --- /dev/null +++ b/libbtrfsutil/python/subvolume.c @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2018 Facebook + * + * This file is part of libbtrfsutil. + * + * libbtrfsutil is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * libbtrfsutil is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with libbtrfsutil. If not, see . + */ + +#include "btrfsutilpy.h" + +PyObject *is_subvolume(PyObject *self, PyObject *args, PyObject *kwds) +{ + static char *keywords[] = {"path", NULL}; + struct path_arg path = {.allow_fd = true}; + enum btrfs_util_error err; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O&:is_subvolume", + keywords, &path_converter, &path)) + return NULL; + + if (path.path) + err = btrfs_util_is_subvolume(path.path); + else + err = btrfs_util_is_subvolume_fd(path.fd); + if (err == BTRFS_UTIL_OK) { + path_cleanup(&path); + Py_RETURN_TRUE; + } else if (err == BTRFS_UTIL_ERROR_NOT_BTRFS || + err == BTRFS_UTIL_ERROR_NOT_SUBVOLUME) { + path_cleanup(&path); + Py_RETURN_FALSE; + } else { + SetFromBtrfsUtilErrorWithPath(err, &path); + path_cleanup(&path); + return NULL; + } +} + +PyObject *subvolume_id(PyObject *self, PyObject *args, PyObject *kwds) +{ + static char *keywords[] = {"path", NULL}; + struct path_arg path = {.allow_fd = true}; + enum btrfs_util_error err; + uint64_t id; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O&:subvolume_id", + keywords, &path_converter, &path)) + return NULL; + + if (path.path) + err = btrfs_util_subvolume_id(path.path, &id); + else + err = btrfs_util_subvolume_id_fd(path.fd, &id); + if (err) { + SetFromBtrfsUtilErrorWithPath(err, &path); + path_cleanup(&path); + return NULL; + } + + path_cleanup(&path); + return PyLong_FromUnsignedLongLong(id); +} diff --git a/libbtrfsutil/python/tests/test_subvolume.py b/libbtrfsutil/python/tests/test_subvolume.py new file mode 100644 index 00000000..44b1d7f0 --- /dev/null +++ b/libbtrfsutil/python/tests/test_subvolume.py @@ -0,0 +1,57 @@ +# Copyright (C) 2018 Facebook +# +# This file is part of libbtrfsutil. +# +# libbtrfsutil is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# libbtrfsutil is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with libbtrfsutil. If not, see . + +import fcntl +import errno +import os +import os.path +from pathlib import PurePath +import traceback + +import btrfsutil +from tests import BtrfsTestCase + + +class TestSubvolume(BtrfsTestCase): + def test_is_subvolume(self): + dir = os.path.join(self.mountpoint, 'foo') + os.mkdir(dir) + + for arg in self.path_or_fd(self.mountpoint): + with self.subTest(type=type(arg)): + self.assertTrue(btrfsutil.is_subvolume(arg)) + for arg in self.path_or_fd(dir): + with self.subTest(type=type(arg)): + self.assertFalse(btrfsutil.is_subvolume(arg)) + + with self.assertRaises(btrfsutil.BtrfsUtilError) as e: + btrfsutil.is_subvolume(os.path.join(self.mountpoint, 'bar')) + # This is a bit of an implementation detail, but really this is testing + # that the exception is initialized correctly. + self.assertEqual(e.exception.btrfsutilerror, btrfsutil.ERROR_STATFS_FAILED) + self.assertEqual(e.exception.errno, errno.ENOENT) + + def test_subvolume_id(self): + dir = os.path.join(self.mountpoint, 'foo') + os.mkdir(dir) + + for arg in self.path_or_fd(self.mountpoint): + with self.subTest(type=type(arg)): + self.assertEqual(btrfsutil.subvolume_id(arg), 5) + for arg in self.path_or_fd(dir): + with self.subTest(type=type(arg)): + self.assertEqual(btrfsutil.subvolume_id(arg), 5) diff --git a/libbtrfsutil/subvolume.c b/libbtrfsutil/subvolume.c new file mode 100644 index 00000000..7ae8142c --- /dev/null +++ b/libbtrfsutil/subvolume.c @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2018 Facebook + * + * This file is part of libbtrfsutil. + * + * libbtrfsutil is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * libbtrfsutil is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with libbtrfsutil. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "btrfsutil_internal.h" + +/* + * This intentionally duplicates btrfs_util_is_subvolume_fd() instead of opening + * a file descriptor and calling it, because fstat() and fstatfs() don't accept + * file descriptors opened with O_PATH on old kernels (before v3.6 and before + * v3.12, respectively), but stat() and statfs() can be called on a path that + * the user doesn't have read or write permissions to. + */ +PUBLIC enum btrfs_util_error btrfs_util_is_subvolume(const char *path) +{ + struct statfs sfs; + struct stat st; + int ret; + + ret = statfs(path, &sfs); + if (ret == -1) + return BTRFS_UTIL_ERROR_STATFS_FAILED; + + if (sfs.f_type != BTRFS_SUPER_MAGIC) { + errno = EINVAL; + return BTRFS_UTIL_ERROR_NOT_BTRFS; + } + + ret = stat(path, &st); + if (ret == -1) + return BTRFS_UTIL_ERROR_STAT_FAILED; + + if (st.st_ino != BTRFS_FIRST_FREE_OBJECTID || !S_ISDIR(st.st_mode)) { + errno = EINVAL; + return BTRFS_UTIL_ERROR_NOT_SUBVOLUME; + } + + return BTRFS_UTIL_OK; +} + +PUBLIC enum btrfs_util_error btrfs_util_is_subvolume_fd(int fd) +{ + struct statfs sfs; + struct stat st; + int ret; + + ret = fstatfs(fd, &sfs); + if (ret == -1) + return BTRFS_UTIL_ERROR_STATFS_FAILED; + + if (sfs.f_type != BTRFS_SUPER_MAGIC) { + errno = EINVAL; + return BTRFS_UTIL_ERROR_NOT_BTRFS; + } + + ret = fstat(fd, &st); + if (ret == -1) + return BTRFS_UTIL_ERROR_STAT_FAILED; + + if (st.st_ino != BTRFS_FIRST_FREE_OBJECTID || !S_ISDIR(st.st_mode)) { + errno = EINVAL; + return BTRFS_UTIL_ERROR_NOT_SUBVOLUME; + } + + return BTRFS_UTIL_OK; +} + +PUBLIC enum btrfs_util_error btrfs_util_subvolume_id(const char *path, + uint64_t *id_ret) +{ + enum btrfs_util_error err; + int fd; + + fd = open(path, O_RDONLY); + if (fd == -1) + return BTRFS_UTIL_ERROR_OPEN_FAILED; + + err = btrfs_util_subvolume_id_fd(fd, id_ret); + SAVE_ERRNO_AND_CLOSE(fd); + return err; +} + +PUBLIC enum btrfs_util_error btrfs_util_subvolume_id_fd(int fd, + uint64_t *id_ret) +{ + struct btrfs_ioctl_ino_lookup_args args = { + .treeid = 0, + .objectid = BTRFS_FIRST_FREE_OBJECTID, + }; + int ret; + + ret = ioctl(fd, BTRFS_IOC_INO_LOOKUP, &args); + if (ret == -1) { + close(fd); + return BTRFS_UTIL_ERROR_INO_LOOKUP_FAILED; + } + + *id_ret = args.treeid; + + return BTRFS_UTIL_OK; +} -- cgit v1.2.3