libbtrfsutil: add btrfs_util_is_subvolume() and btrfs_util_subvolume_id()
authorOmar Sandoval <osandov@fb.com>
Thu, 18 Jan 2018 20:49:55 +0000 (12:49 -0800)
committerDavid Sterba <dsterba@suse.com>
Tue, 6 Mar 2018 10:28:36 +0000 (11:28 +0100)
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 <osandov@fb.com>
Signed-off-by: David Sterba <dsterba@suse.com>
Makefile
libbtrfsutil/python/btrfsutilpy.h
libbtrfsutil/python/module.c
libbtrfsutil/python/setup.py
libbtrfsutil/python/subvolume.c [new file with mode: 0644]
libbtrfsutil/python/tests/test_subvolume.py [new file with mode: 0644]
libbtrfsutil/subvolume.c [new file with mode: 0644]

index 622095b..24af13a 100644 (file)
--- 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
index 02039ba..47125d8 100644 (file)
@@ -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);
 
index 9c1a864..18e8c5a 100644 (file)
@@ -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"},
        {},
 };
 
index df44809..1cd3bc0 100755 (executable)
@@ -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 (file)
index 0000000..4aab06a
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+ */
+
+#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 (file)
index 0000000..44b1d7f
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+
+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 (file)
index 0000000..7ae8142
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/vfs.h>
+#include <linux/magic.h>
+
+#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;
+}