libbtrfsutil: add btrfs_util_deleted_subvolumes()

Signed-off-by: Omar Sandoval <osandov@fb.com>
Signed-off-by: David Sterba <dsterba@suse.com>
This commit is contained in:
Omar Sandoval 2018-01-24 12:48:59 -08:00 committed by David Sterba
parent 678da5a7f7
commit f239180162
7 changed files with 183 additions and 15 deletions

View File

@ -580,6 +580,27 @@ enum btrfs_util_error btrfs_util_subvolume_iterator_next_info(struct btrfs_util_
char **path_ret,
struct btrfs_util_subvolume_info *subvol);
/**
* btrfs_util_deleted_subvolumes() - Get a list of subvolume which have been
* deleted but not yet cleaned up.
* @path: Path on a Btrfs filesystem.
* @ids: Returned array of subvolume IDs.
* @n: Returned number of IDs in the @ids array.
*
* 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_deleted_subvolumes(const char *path,
uint64_t **ids,
size_t *n);
/**
* btrfs_util_deleted_subvolumes_fd() - See btrfs_util_deleted_subvolumes().
*/
enum btrfs_util_error btrfs_util_deleted_subvolumes_fd(int fd, uint64_t **ids,
size_t *n);
/**
* btrfs_util_create_qgroup_inherit() - Create a qgroup inheritance specifier
* for btrfs_util_create_subvolume() or btrfs_util_create_snapshot().

View File

@ -54,6 +54,8 @@ struct path_arg {
int path_converter(PyObject *o, void *p);
void path_cleanup(struct path_arg *path);
PyObject *list_from_uint64_array(const uint64_t *arr, size_t n);
void SetFromBtrfsUtilError(enum btrfs_util_error err);
void SetFromBtrfsUtilErrorWithPath(enum btrfs_util_error err,
struct path_arg *path);
@ -75,6 +77,7 @@ PyObject *set_default_subvolume(PyObject *self, PyObject *args, PyObject *kwds);
PyObject *create_subvolume(PyObject *self, PyObject *args, PyObject *kwds);
PyObject *create_snapshot(PyObject *self, PyObject *args, PyObject *kwds);
PyObject *delete_subvolume(PyObject *self, PyObject *args, PyObject *kwds);
PyObject *deleted_subvolumes(PyObject *self, PyObject *args, PyObject *kwds);
void add_module_constants(PyObject *m);

View File

@ -125,6 +125,29 @@ err:
return 0;
}
PyObject *list_from_uint64_array(const uint64_t *arr, size_t n)
{
PyObject *ret;
size_t i;
ret = PyList_New(n);
if (!ret)
return NULL;
for (i = 0; i < n; i++) {
PyObject *tmp;
tmp = PyLong_FromUnsignedLongLong(arr[i]);
if (!tmp) {
Py_DECREF(ret);
return NULL;
}
PyList_SET_ITEM(ret, i, tmp);
}
return ret;
}
void path_cleanup(struct path_arg *path)
{
Py_CLEAR(path->object);
@ -235,6 +258,13 @@ static PyMethodDef btrfsutil_methods[] = {
"path -- string, bytes, or path-like object\n"
"recursive -- if the given subvolume has child subvolumes, delete\n"
"them instead of failing"},
{"deleted_subvolumes", (PyCFunction)deleted_subvolumes,
METH_VARARGS | METH_KEYWORDS,
"deleted_subvolumes(path)\n\n"
"Get the list of subvolume IDs which have been deleted but not yet\n"
"cleaned up\n\n"
"Arguments:\n"
"path -- string, bytes, path-like object, or open file descriptor"},
{},
};

View File

@ -55,25 +55,12 @@ static PyObject *QgroupInherit_getattro(QgroupInherit *self, PyObject *nameobj)
}
if (strcmp(name, "groups") == 0) {
PyObject *ret, *tmp;
const uint64_t *arr;
size_t n, i;
size_t n;
btrfs_util_qgroup_inherit_get_groups(self->inherit, &arr, &n);
ret = PyList_New(n);
if (!ret)
return NULL;
for (i = 0; i < n; i++) {
tmp = PyLong_FromUnsignedLongLong(arr[i]);
if (!tmp) {
Py_DECREF(ret);
return NULL;
}
PyList_SET_ITEM(ret, i, tmp);
}
return ret;
return list_from_uint64_array(arr, n);
} else {
return PyObject_GenericGetAttr((PyObject *)self, nameobj);
}

View File

@ -425,6 +425,36 @@ PyObject *delete_subvolume(PyObject *self, PyObject *args, PyObject *kwds)
Py_RETURN_NONE;
}
PyObject *deleted_subvolumes(PyObject *self, PyObject *args, PyObject *kwds)
{
static char *keywords[] = {"path", NULL};
struct path_arg path = {.allow_fd = true};
PyObject *ret;
uint64_t *ids;
size_t n;
enum btrfs_util_error err;
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O&:deleted_subvolumes",
keywords, &path_converter, &path))
return NULL;
if (path.path)
err = btrfs_util_deleted_subvolumes(path.path, &ids, &n);
else
err = btrfs_util_deleted_subvolumes_fd(path.fd, &ids, &n);
if (err) {
SetFromBtrfsUtilErrorWithPath(err, &path);
path_cleanup(&path);
return NULL;
}
path_cleanup(&path);
ret = list_from_uint64_array(ids, n);
free(ids);
return ret;
}
typedef struct {
PyObject_HEAD
struct btrfs_util_subvolume_iterator *iter;

View File

@ -318,6 +318,14 @@ class TestSubvolume(BtrfsTestCase):
btrfsutil.delete_subvolume(subvol + '5', recursive=True)
self.assertFalse(os.path.exists(subvol + '5'))
def test_deleted_subvolumes(self):
subvol = os.path.join(self.mountpoint, 'subvol')
btrfsutil.create_subvolume(subvol + '1')
btrfsutil.delete_subvolume(subvol + '1')
for arg in self.path_or_fd(self.mountpoint):
with self.subTest(type=type(arg)):
self.assertEqual(btrfsutil.deleted_subvolumes(arg), [256])
def test_subvolume_iterator(self):
pwd = os.getcwd()
try:

View File

@ -1279,3 +1279,92 @@ PUBLIC enum btrfs_util_error btrfs_util_subvolume_iterator_next_info(struct btrf
return btrfs_util_subvolume_info_fd(iter->fd, id, subvol);
}
PUBLIC enum btrfs_util_error btrfs_util_deleted_subvolumes(const char *path,
uint64_t **ids,
size_t *n)
{
enum btrfs_util_error err;
int fd;
fd = open(path, O_RDONLY);
if (fd == -1)
return BTRFS_UTIL_ERROR_OPEN_FAILED;
err = btrfs_util_deleted_subvolumes_fd(fd, ids, n);
SAVE_ERRNO_AND_CLOSE(fd);
return err;
}
PUBLIC enum btrfs_util_error btrfs_util_deleted_subvolumes_fd(int fd,
uint64_t **ids,
size_t *n)
{
size_t capacity = 0;
struct btrfs_ioctl_search_args search = {
.key = {
.tree_id = BTRFS_ROOT_TREE_OBJECTID,
.min_objectid = BTRFS_ORPHAN_OBJECTID,
.max_objectid = BTRFS_ORPHAN_OBJECTID,
.min_type = BTRFS_ORPHAN_ITEM_KEY,
.max_type = BTRFS_ORPHAN_ITEM_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;
int ret;
*ids = NULL;
*n = 0;
for (;;) {
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) {
err = BTRFS_UTIL_ERROR_SEARCH_FAILED;
goto out;
}
items_pos = 0;
buf_off = 0;
if (search.key.nr_items == 0)
break;
}
header = (struct btrfs_ioctl_search_header *)(search.buf + buf_off);
if (*n >= capacity) {
size_t new_capacity = capacity ? capacity * 2 : 1;
uint64_t *new_ids;
new_ids = reallocarray(*ids, new_capacity,
sizeof(**ids));
if (!new_ids)
return BTRFS_UTIL_ERROR_NO_MEMORY;
*ids = new_ids;
capacity = new_capacity;
}
(*ids)[(*n)++] = header->offset;
items_pos++;
buf_off += sizeof(*header) + header->len;
search.key.min_offset = header->offset + 1;
}
err = BTRFS_UTIL_OK;
out:
if (err) {
free(*ids);
*ids = NULL;
*n = 0;
}
return err;
}