@@ -20,8 +20,10 @@
#ifndef BTRFS_UTIL_H
#define BTRFS_UTIL_H
+#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
+#include <sys/time.h>
#define BTRFS_UTIL_VERSION_MAJOR 1
#define BTRFS_UTIL_VERSION_MINOR 0
@@ -124,6 +126,115 @@ enum btrfs_util_error btrfs_util_subvolume_path(const char *path, uint64_t id,
enum btrfs_util_error btrfs_util_subvolume_path_fd(int fd, uint64_t id,
char **path_ret);
+/**
+ * struct btrfs_util_subvolume_info - Information about a Btrfs subvolume.
+ */
+struct btrfs_util_subvolume_info {
+ /** @id: ID of this subvolume, unique across the filesystem. */
+ uint64_t id;
+
+ /**
+ * @parent_id: ID of the subvolume which contains this subvolume, or
+ * zero for the root subvolume (BTRFS_FS_TREE_OBJECTID) or orphaned
+ * subvolumes (i.e., subvolumes which have been deleted but not yet
+ * cleaned up).
+ */
+ uint64_t parent_id;
+
+ /**
+ * @dir_id: Inode number of the directory containing this subvolume in
+ * the parent subvolume, or zero for the root subvolume
+ * (BTRFS_FS_TREE_OBJECTID) or orphaned subvolumes.
+ */
+ uint64_t dir_id;
+
+ /** @flags: On-disk root item flags. */
+ uint64_t flags;
+
+ /** @uuid: UUID of this subvolume. */
+ uint8_t uuid[16];
+
+ /**
+ * @parent_uuid: UUID of the subvolume this subvolume is a snapshot of,
+ * or all zeroes if this subvolume is not a snapshot.
+ */
+ uint8_t parent_uuid[16];
+
+ /**
+ * @received_uuid: UUID of the subvolume this subvolume was received
+ * from, or all zeroes if this subvolume was not received. Note that
+ * this field, @stransid, @rtransid, @stime, and @rtime are set manually
+ * by userspace after a subvolume is received.
+ */
+ uint8_t received_uuid[16];
+
+ /** @generation: Transaction ID of the subvolume root. */
+ uint64_t generation;
+
+ /**
+ * @ctransid: Transaction ID when an inode in this subvolume was last
+ * changed.
+ */
+ uint64_t ctransid;
+
+ /** @otransid: Transaction ID when this subvolume was created. */
+ uint64_t otransid;
+
+ /**
+ * @stransid: Transaction ID of the sent subvolume this subvolume was
+ * received from, or zero if this subvolume was not received. See the
+ * note on @received_uuid.
+ */
+ uint64_t stransid;
+
+ /**
+ * @rtransid: Transaction ID when this subvolume was received, or zero
+ * if this subvolume was not received. See the note on @received_uuid.
+ */
+ uint64_t rtransid;
+
+ /** @ctime: Time when an inode in this subvolume was last changed. */
+ struct timespec ctime;
+
+ /** @otime: Time when this subvolume was created. */
+ struct timespec otime;
+
+ /**
+ * @stime: Not well-defined, usually zero unless it was set otherwise.
+ * See the note on @received_uuid.
+ */
+ struct timespec stime;
+
+ /**
+ * @rtime: Time when this subvolume was received, or zero if this
+ * subvolume was not received. See the note on @received_uuid.
+ */
+ struct timespec rtime;
+};
+
+/**
+ * btrfs_util_subvolume_info() - Get information about a subvolume.
+ * @path: Path in a Btrfs filesystem. This may be any path in the filesystem; it
+ * does not have to refer to a subvolume unless @id is zero.
+ * @id: ID of subvolume to get information about. If zero is given, the
+ * subvolume ID of @path is used.
+ * @subvol: Returned subvolume information. This can be %NULL if you just want
+ * to check whether the subvolume exists; %BTRFS_UTIL_ERROR_SUBVOLUME_NOT_FOUND
+ * will be returned if it does not.
+ *
+ * This requires appropriate privilege (CAP_SYS_ADMIN).
+ *
+ * Return: %BTRFS_UTIL_OK on success, non-zero error code on failure.
+ */
+enum btrfs_util_error btrfs_util_subvolume_info(const char *path, uint64_t id,
+ struct btrfs_util_subvolume_info *subvol);
+
+/**
+ * btrfs_util_subvolume_info_fd() - See btrfs_util_subvolume_info().
+ */
+enum btrfs_util_error btrfs_util_subvolume_info_fd(int fd, uint64_t id,
+ struct btrfs_util_subvolume_info *subvol);
+
struct btrfs_util_qgroup_inherit;
/**
@@ -35,6 +35,8 @@ typedef struct {
} QgroupInherit;
extern PyTypeObject BtrfsUtilError_type;
+extern PyStructSequence_Desc SubvolumeInfo_desc;
+extern PyTypeObject SubvolumeInfo_type;
extern PyTypeObject QgroupInherit_type;
/*
@@ -61,6 +63,8 @@ void SetFromBtrfsUtilErrorWithPaths(enum btrfs_util_error err,
PyObject *is_subvolume(PyObject *self, PyObject *args, PyObject *kwds);
PyObject *subvolume_id(PyObject *self, PyObject *args, PyObject *kwds);
PyObject *subvolume_path(PyObject *self, PyObject *args, PyObject *kwds);
+PyObject *subvolume_info(PyObject *self, PyObject *args, PyObject *kwds);
+PyObject *create_subvolume(PyObject *self, PyObject *args, PyObject *kwds);
void add_module_constants(PyObject *m);
@@ -152,6 +152,14 @@ static PyMethodDef btrfsutil_methods[] = {
"path -- string, bytes, path-like object, or open file descriptor\n"
"id -- if not zero, instead of returning the subvolume path of the\n"
"given path, return the path of the subvolume with this ID"},
+ {"subvolume_info", (PyCFunction)subvolume_info,
+ METH_VARARGS | METH_KEYWORDS,
+ "subvolume_info(path, id=0) -> SubvolumeInfo\n\n"
+ "Get information about a subvolume.\n\n"
+ "Arguments:\n"
+ "path -- string, bytes, path-like object, or open file descriptor\n"
+ "id -- if not zero, instead of returning information about the\n"
+ "given path, return information about the subvolume with this ID"},
{"create_subvolume", (PyCFunction)create_subvolume,
METH_VARARGS | METH_KEYWORDS,
"create_subvolume(path, async=False)\n\n"
@@ -180,6 +188,9 @@ PyInit_btrfsutil(void)
if (PyType_Ready(&BtrfsUtilError_type) < 0)
return NULL;
+ if (PyStructSequence_InitType2(&SubvolumeInfo_type, &SubvolumeInfo_desc) < 0)
+ return NULL;
+
QgroupInherit_type.tp_new = PyType_GenericNew;
if (PyType_Ready(&QgroupInherit_type) < 0)
return NULL;
@@ -192,6 +203,9 @@ PyInit_btrfsutil(void)
PyModule_AddObject(m, "BtrfsUtilError",
(PyObject *)&BtrfsUtilError_type);
+ Py_INCREF(&SubvolumeInfo_type);
+ PyModule_AddObject(m, "SubvolumeInfo", (PyObject *)&SubvolumeInfo_type);
+
Py_INCREF(&QgroupInherit_type);
PyModule_AddObject(m, "QgroupInherit",
(PyObject *)&QgroupInherit_type);
@@ -102,6 +102,119 @@ PyObject *subvolume_path(PyObject *self, PyObject *args, PyObject *kwds)
return ret;
}
+static PyObject *subvolume_info_to_object(const struct btrfs_util_subvolume_info *subvol)
+{
+ PyObject *ret, *tmp;
+
+ ret = PyStructSequence_New(&SubvolumeInfo_type);
+ if (ret == NULL)
+ return NULL;
+
+#define SET_UINT64(i, field) \
+ tmp = PyLong_FromUnsignedLongLong(subvol->field); \
+ if (tmp == NULL) { \
+ Py_DECREF(ret); \
+ return ret; \
+ } \
+ PyStructSequence_SET_ITEM(ret, i, tmp);
+
+#define SET_UUID(i, field) \
+ tmp = PyBytes_FromStringAndSize((char *)subvol->field, 16); \
+ if (tmp == NULL) { \
+ Py_DECREF(ret); \
+ return ret; \
+ } \
+ PyStructSequence_SET_ITEM(ret, i, tmp);
+
+#define SET_TIME(i, field) \
+ tmp = PyFloat_FromDouble(subvol->field.tv_sec + \
+ subvol->field.tv_nsec / 1000000000); \
+ if (tmp == NULL) { \
+ Py_DECREF(ret); \
+ return ret; \
+ } \
+ PyStructSequence_SET_ITEM(ret, i, tmp);
+
+ SET_UINT64(0, id);
+ SET_UINT64(1, parent_id);
+ SET_UINT64(2, dir_id);
+ SET_UINT64(3, flags);
+ SET_UUID(4, uuid);
+ SET_UUID(5, parent_uuid);
+ SET_UUID(6, received_uuid);
+ SET_UINT64(7, generation);
+ SET_UINT64(8, ctransid);
+ SET_UINT64(9, otransid);
+ SET_UINT64(10, stransid);
+ SET_UINT64(11, rtransid);
+ SET_TIME(12, ctime);
+ SET_TIME(13, otime);
+ SET_TIME(14, stime);
+ SET_TIME(15, rtime);
+
+#undef SET_TIME
+#undef SET_UUID
+#undef SET_UINT64
+
+ return ret;
+}
+
+PyObject *subvolume_info(PyObject *self, PyObject *args, PyObject *kwds)
+{
+ static char *keywords[] = {"path", "id", NULL};
+ struct path_arg path = {.allow_fd = true};
+ struct btrfs_util_subvolume_info subvol;
+ enum btrfs_util_error err;
+ uint64_t id = 0;
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "O&|K:subvolume_info",
+ keywords, &path_converter, &path, &id))
+ return NULL;
+
+ if (path.path)
+ err = btrfs_util_subvolume_info(path.path, id, &subvol);
+ else
+ err = btrfs_util_subvolume_info_fd(path.fd, id, &subvol);
+ if (err) {
+ SetFromBtrfsUtilErrorWithPath(err, &path);
+ path_cleanup(&path);
+ return NULL;
+ }
+
+ path_cleanup(&path);
+
+ return subvolume_info_to_object(&subvol);
+}
+
+static PyStructSequence_Field SubvolumeInfo_fields[] = {
+ {"id", "int ID of this subvolume"},
+ {"parent_id", "int ID of the subvolume containing this subvolume"},
+ {"dir_id", "int inode number of the directory containing this subvolume"},
+ {"flags", "int root item flags"},
+ {"uuid", "bytes UUID of this subvolume"},
+ {"parent_uuid", "bytes UUID of the subvolume this is a snapshot of"},
+ {"received_uuid", "bytes UUID of the subvolume this was received from"},
+ {"generation", "int transaction ID of the subvolume root"},
+ {"ctransid", "int transaction ID when an inode was last changed"},
+ {"otransid", "int transaction ID when this subvolume was created"},
+ {"stransid", "int transaction ID of the sent subvolume this subvolume was received from"},
+ {"rtransid", "int transaction ID when this subvolume was received"},
+ {"ctime", "float time when an inode was last changed"},
+ {"otime", "float time when this subvolume was created"},
+ {"stime", "float time, usually zero"},
+ {"rtime", "float time when this subvolume was received"},
+ {},
+};
+
+PyStructSequence_Desc SubvolumeInfo_desc = {
+ "btrfsutil.SubvolumeInfo",
+ "Information about a Btrfs subvolume.",
+ SubvolumeInfo_fields,
+ 14,
+};
+
+PyTypeObject SubvolumeInfo_type;
+
PyObject *create_subvolume(PyObject *self, PyObject *args, PyObject *kwds)
{
static char *keywords[] = {"path", "async", "qgroup_inherit", NULL};
@@ -87,6 +87,56 @@ class TestSubvolume(BtrfsTestCase):
finally:
os.chdir(pwd)
+ def test_subvolume_info(self):
+ for arg in self.path_or_fd(self.mountpoint):
+ with self.subTest(type=type(arg)):
+ info = btrfsutil.subvolume_info(arg)
+ self.assertEqual(info.id, 5)
+ self.assertEqual(info.parent_id, 0)
+ self.assertEqual(info.dir_id, 0)
+ self.assertEqual(info.flags, 0)
+ self.assertEqual(info.uuid, bytes(16))
+ self.assertEqual(info.parent_uuid, bytes(16))
+ self.assertEqual(info.received_uuid, bytes(16))
+ self.assertNotEqual(info.generation, 0)
+ self.assertEqual(info.ctransid, 0)
+ self.assertEqual(info.otransid, 0)
+ self.assertEqual(info.stransid, 0)
+ self.assertEqual(info.rtransid, 0)
+ self.assertEqual(info.ctime, 0)
+ self.assertEqual(info.otime, 0)
+ self.assertEqual(info.stime, 0)
+ self.assertEqual(info.rtime, 0)
+
+ subvol = os.path.join(self.mountpoint, 'subvol')
+ btrfsutil.create_subvolume(subvol)
+
+ info = btrfsutil.subvolume_info(subvol)
+ self.assertEqual(info.id, 256)
+ self.assertEqual(info.parent_id, 5)
+ self.assertEqual(info.dir_id, 256)
+ self.assertEqual(info.flags, 0)
+ self.assertIsInstance(info.uuid, bytes)
+ self.assertEqual(info.parent_uuid, bytes(16))
+ self.assertEqual(info.received_uuid, bytes(16))
+ self.assertNotEqual(info.generation, 0)
+ self.assertNotEqual(info.ctransid, 0)
+ self.assertNotEqual(info.otransid, 0)
+ self.assertEqual(info.stransid, 0)
+ self.assertEqual(info.rtransid, 0)
+ self.assertNotEqual(info.ctime, 0)
+ self.assertNotEqual(info.otime, 0)
+ self.assertEqual(info.stime, 0)
+ self.assertEqual(info.rtime, 0)
+
+ # TODO: test received_uuid, stransid, rtransid, stime, and rtime
+
+ for arg in self.path_or_fd(self.mountpoint):
+ with self.subTest(type=type(arg)):
+ with self.assertRaises(btrfsutil.BtrfsUtilError) as e:
+ # BTRFS_EXTENT_TREE_OBJECTID
+ btrfsutil.subvolume_info(arg, 2)
+
def test_create_subvolume(self):
subvol = os.path.join(self.mountpoint, 'subvol')
@@ -253,6 +253,154 @@ PUBLIC enum btrfs_util_error btrfs_util_subvolume_path_fd(int fd, uint64_t id,
return BTRFS_UTIL_OK;
}
+static void copy_timespec(struct timespec *timespec,
+ const struct btrfs_timespec *btrfs_timespec)
+{
+ timespec->tv_sec = le64_to_cpu(btrfs_timespec->sec);
+ timespec->tv_nsec = le32_to_cpu(btrfs_timespec->nsec);
+}
+
+static void copy_root_item(struct btrfs_util_subvolume_info *subvol,
+ const struct btrfs_root_item *root)
+{
+ subvol->flags = le64_to_cpu(root->flags);
+ memcpy(subvol->uuid, root->uuid, sizeof(subvol->uuid));
+ memcpy(subvol->parent_uuid, root->parent_uuid,
+ sizeof(subvol->parent_uuid));
+ memcpy(subvol->received_uuid, root->received_uuid,
+ sizeof(subvol->received_uuid));
+ subvol->generation = le64_to_cpu(root->generation);
+ subvol->ctransid = le64_to_cpu(root->ctransid);
+ subvol->otransid = le64_to_cpu(root->otransid);
+ subvol->stransid = le64_to_cpu(root->stransid);
+ subvol->rtransid = le64_to_cpu(root->rtransid);
+ copy_timespec(&subvol->ctime, &root->ctime);
+ copy_timespec(&subvol->otime, &root->otime);
+ copy_timespec(&subvol->stime, &root->stime);
+ copy_timespec(&subvol->rtime, &root->rtime);
+}
+
+PUBLIC enum btrfs_util_error btrfs_util_subvolume_info(const char *path,
+ uint64_t id,
+ struct btrfs_util_subvolume_info *subvol)
+{
+ 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_info_fd(fd, id, subvol);
+ SAVE_ERRNO_AND_CLOSE(fd);
+ return err;
+}
+
+PUBLIC enum btrfs_util_error btrfs_util_subvolume_info_fd(int fd, uint64_t id,
+ struct btrfs_util_subvolume_info *subvol)
+{
+ struct btrfs_ioctl_search_args search = {
+ .key = {
+ .tree_id = BTRFS_ROOT_TREE_OBJECTID,
+ .min_type = BTRFS_ROOT_ITEM_KEY,
+ .max_type = BTRFS_ROOT_BACKREF_KEY,
+ .min_offset = 0,
+ .max_offset = UINT64_MAX,
+ .min_transid = 0,
+ .max_transid = UINT64_MAX,
+ .nr_items = 0,
+ },
+ };
+ enum btrfs_util_error err;
+ size_t items_pos = 0, buf_off = 0;
+ bool need_root_item = true, need_root_backref = true;
+ int ret;
+
+ if (id == 0) {
+ err = btrfs_util_is_subvolume_fd(fd);
+ if (err)
+ return err;
+
+ err = btrfs_util_subvolume_id_fd(fd, &id);
+ if (err)
+ return err;
+ }
+
+ if ((id < BTRFS_FIRST_FREE_OBJECTID && id != BTRFS_FS_TREE_OBJECTID) ||
+ id > BTRFS_LAST_FREE_OBJECTID) {
+ errno = ENOENT;
+ return BTRFS_UTIL_ERROR_SUBVOLUME_NOT_FOUND;
+ }
+
+ search.key.min_objectid = search.key.max_objectid = id;
+
+ if (subvol) {
+ subvol->id = id;
+ subvol->parent_id = 0;
+ subvol->dir_id = 0;
+ if (id == BTRFS_FS_TREE_OBJECTID)
+ need_root_backref = false;
+ } else {
+ /*
+ * We only need the backref for filling in the subvolume info.
+ */
+ need_root_backref = false;
+ }
+
+ /* Don't bother searching for the backref if we don't need it. */
+ if (!need_root_backref)
+ search.key.max_type = BTRFS_ROOT_ITEM_KEY;
+
+ while (need_root_item || need_root_backref) {
+ const struct btrfs_ioctl_search_header *header;
+
+ if (items_pos >= search.key.nr_items) {
+ search.key.nr_items = 4096;
+ ret = ioctl(fd, BTRFS_IOC_TREE_SEARCH, &search);
+ if (ret == -1)
+ return BTRFS_UTIL_ERROR_SEARCH_FAILED;
+ items_pos = 0;
+ buf_off = 0;
+
+ if (search.key.nr_items == 0) {
+ if (need_root_item) {
+ errno = ENOENT;
+ return BTRFS_UTIL_ERROR_SUBVOLUME_NOT_FOUND;
+ } else {
+ break;
+ }
+ }
+ }
+
+ header = (struct btrfs_ioctl_search_header *)(search.buf + buf_off);
+ if (header->type == BTRFS_ROOT_ITEM_KEY) {
+ if (subvol) {
+ const struct btrfs_root_item *root;
+
+ root = (const struct btrfs_root_item *)(header + 1);
+ copy_root_item(subvol, root);
+ }
+ need_root_item = false;
+ search.key.min_type = BTRFS_ROOT_BACKREF_KEY;
+ } else if (header->type == BTRFS_ROOT_BACKREF_KEY) {
+ if (subvol) {
+ const struct btrfs_root_ref *ref;
+
+ ref = (const struct btrfs_root_ref *)(header + 1);
+ subvol->parent_id = header->offset;
+ subvol->dir_id = le64_to_cpu(ref->dirid);
+ }
+ need_root_backref = false;
+ search.key.min_type = UINT32_MAX;
+ }
+
+ items_pos++;
+ buf_off += sizeof(*header) + header->len;
+ }
+
+ return BTRFS_UTIL_OK;
+}
+
static enum btrfs_util_error openat_parent_and_name(int dirfd, const char *path,
char *name, size_t name_len,
int *fd)