libbtrfsutil: add btrfs_util_delete_subvolume()
authorOmar Sandoval <osandov@fb.com>
Thu, 18 Jan 2018 22:26:35 +0000 (14:26 -0800)
committerDavid Sterba <dsterba@suse.com>
Tue, 6 Mar 2018 10:28:37 +0000 (11:28 +0100)
We also support recursive deletion using a subvolume iterator.

Signed-off-by: Omar Sandoval <osandov@fb.com>
Signed-off-by: David Sterba <dsterba@suse.com>
libbtrfsutil/btrfsutil.h
libbtrfsutil/python/btrfsutilpy.h
libbtrfsutil/python/module.c
libbtrfsutil/python/subvolume.c
libbtrfsutil/python/tests/test_subvolume.py
libbtrfsutil/subvolume.c

index ccb2c17..6717b65 100644 (file)
@@ -450,6 +450,39 @@ enum btrfs_util_error btrfs_util_create_snapshot_fd2(int fd, int parent_fd,
                                                     uint64_t *async_transid,
                                                     struct btrfs_util_qgroup_inherit *qgroup_inherit);
 
+/**
+ * BTRFS_UTIL_DELETE_SUBVOLUME_RECURSIVE - Delete subvolumes beneath the given
+ * subvolume before attempting to delete the given subvolume.
+ *
+ * If this flag is not used, deleting a subvolume with child subvolumes is an
+ * error. Note that this is currently implemented in userspace non-atomically.
+ * It requires appropriate privilege (CAP_SYS_ADMIN).
+ */
+#define BTRFS_UTIL_DELETE_SUBVOLUME_RECURSIVE (1 << 0)
+#define BTRFS_UTIL_DELETE_SUBVOLUME_MASK ((1 << 1) - 1)
+
+/**
+ * btrfs_util_delete_subvolume() - Delete a subvolume or snapshot.
+ * @path: Path of the subvolume to delete.
+ * @flags: Bitmask of BTRFS_UTIL_DELETE_SUBVOLUME_* flags.
+ *
+ * Return: %BTRFS_UTIL_OK on success, non-zero error code on failure.
+ */
+enum btrfs_util_error btrfs_util_delete_subvolume(const char *path, int flags);
+
+/**
+ * btrfs_util_delete_subvolume_fd() - Delete a subvolume or snapshot given its
+ * parent and name.
+ * @parent_fd: File descriptor of the subvolume's parent directory.
+ * @name: Name of the subvolume.
+ * @flags: See btrfs_util_delete_subvolume().
+ *
+ * Return: %BTRFS_UTIL_OK on success, non-zero error code on failure.
+ */
+enum btrfs_util_error btrfs_util_delete_subvolume_fd(int parent_fd,
+                                                    const char *name,
+                                                    int flags);
+
 struct btrfs_util_subvolume_iterator;
 
 /**
index 59c7e99..0ee6afa 100644 (file)
@@ -74,6 +74,7 @@ PyObject *get_default_subvolume(PyObject *self, PyObject *args, PyObject *kwds);
 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);
 
 void add_module_constants(PyObject *m);
 
index 3f0f617..fe25f4b 100644 (file)
@@ -227,6 +227,14 @@ static PyMethodDef btrfsutil_methods[] = {
         "read_only -- create a read-only snapshot\n"
         "async -- create the subvolume without waiting for it to commit to\n"
         "disk and return the transaction ID"},
+       {"delete_subvolume", (PyCFunction)delete_subvolume,
+        METH_VARARGS | METH_KEYWORDS,
+        "delete_subvolume(path, recursive=False)\n\n"
+        "Delete a subvolume or snapshot.\n\n"
+        "Arguments:\n"
+        "path -- string, bytes, or path-like object\n"
+        "recursive -- if the given subvolume has child subvolumes, delete\n"
+        "them instead of failing"},
        {},
 };
 
index a158ade..eb3f6e2 100644 (file)
@@ -398,6 +398,33 @@ PyObject *create_snapshot(PyObject *self, PyObject *args, PyObject *kwds)
                Py_RETURN_NONE;
 }
 
+PyObject *delete_subvolume(PyObject *self, PyObject *args, PyObject *kwds)
+{
+       static char *keywords[] = {"path", "recursive", NULL};
+       struct path_arg path = {.allow_fd = false};
+       enum btrfs_util_error err;
+       int recursive = 0;
+       int flags = 0;
+
+       if (!PyArg_ParseTupleAndKeywords(args, kwds, "O&|p:delete_subvolume",
+                                        keywords, &path_converter, &path,
+                                        &recursive))
+               return NULL;
+
+       if (recursive)
+               flags |= BTRFS_UTIL_DELETE_SUBVOLUME_RECURSIVE;
+
+       err = btrfs_util_delete_subvolume(path.path, flags);
+       if (err) {
+               SetFromBtrfsUtilErrorWithPath(err, &path);
+               path_cleanup(&path);
+               return NULL;
+       }
+
+       path_cleanup(&path);
+       Py_RETURN_NONE;
+}
+
 typedef struct {
        PyObject_HEAD
        struct btrfs_util_subvolume_iterator *iter;
index 2951154..08083ab 100644 (file)
@@ -270,6 +270,54 @@ class TestSubvolume(BtrfsTestCase):
         btrfsutil.create_snapshot(subvol, snapshot + '4', read_only=True)
         self.assertTrue(btrfsutil.get_subvolume_read_only(snapshot + '4'))
 
+    def test_delete_subvolume(self):
+        subvol = os.path.join(self.mountpoint, 'subvol')
+        btrfsutil.create_subvolume(subvol + '1')
+        self.assertTrue(os.path.exists(subvol + '1'))
+        btrfsutil.create_subvolume(subvol + '2')
+        self.assertTrue(os.path.exists(subvol + '2'))
+        btrfsutil.create_subvolume(subvol + '3')
+        self.assertTrue(os.path.exists(subvol + '3'))
+
+        btrfsutil.delete_subvolume(subvol + '1')
+        self.assertFalse(os.path.exists(subvol + '1'))
+        btrfsutil.delete_subvolume((subvol + '2').encode())
+        self.assertFalse(os.path.exists(subvol + '2'))
+        if HAVE_PATH_LIKE:
+            btrfsutil.delete_subvolume(PurePath(subvol + '3'))
+            self.assertFalse(os.path.exists(subvol + '3'))
+
+        # Test deleting subvolumes under '/' in a chroot.
+        pid = os.fork()
+        if pid == 0:
+            try:
+                os.chroot(self.mountpoint)
+                os.chdir('/')
+                btrfsutil.create_subvolume('/subvol4')
+                self.assertTrue(os.path.exists('/subvol4'))
+                btrfsutil.delete_subvolume('/subvol4')
+                self.assertFalse(os.path.exists('/subvol4'))
+                with self.assertRaises(btrfsutil.BtrfsUtilError):
+                    btrfsutil.delete_subvolume('/')
+                os._exit(0)
+            except Exception:
+                traceback.print_exc()
+                os._exit(1)
+        wstatus = os.waitpid(pid, 0)[1]
+        self.assertTrue(os.WIFEXITED(wstatus))
+        self.assertEqual(os.WEXITSTATUS(wstatus), 0)
+
+        btrfsutil.create_subvolume(subvol + '5')
+        btrfsutil.create_subvolume(subvol + '5/foo')
+        btrfsutil.create_subvolume(subvol + '5/bar')
+        btrfsutil.create_subvolume(subvol + '5/bar/baz')
+        btrfsutil.create_subvolume(subvol + '5/bar/qux')
+        btrfsutil.create_subvolume(subvol + '5/quux')
+        with self.assertRaises(btrfsutil.BtrfsUtilError):
+            btrfsutil.delete_subvolume(subvol + '5')
+        btrfsutil.delete_subvolume(subvol + '5', recursive=True)
+        self.assertFalse(os.path.exists(subvol + '5'))
+
     def test_subvolume_iterator(self):
         pwd = os.getcwd()
         try:
index 28b38e5..56cd2c6 100644 (file)
@@ -1005,6 +1005,108 @@ PUBLIC enum btrfs_util_error btrfs_util_create_snapshot_fd2(int fd,
        return BTRFS_UTIL_OK;
 }
 
+static enum btrfs_util_error delete_subvolume_children(int parent_fd,
+                                                      const char *name)
+{
+       struct btrfs_util_subvolume_iterator *iter;
+       enum btrfs_util_error err;
+       int fd;
+
+       fd = openat(parent_fd, name, O_RDONLY);
+       if (fd == -1)
+               return BTRFS_UTIL_ERROR_OPEN_FAILED;
+
+       err = btrfs_util_create_subvolume_iterator_fd(fd, 0,
+                                                     BTRFS_UTIL_SUBVOLUME_ITERATOR_POST_ORDER,
+                                                     &iter);
+       if (err)
+               goto out;
+
+       for (;;) {
+               char child_name[BTRFS_PATH_NAME_MAX + 1];
+               char *child_path;
+               int child_parent_fd;
+
+               err = btrfs_util_subvolume_iterator_next(iter, &child_path,
+                                                        NULL);
+               if (err) {
+                       if (err == BTRFS_UTIL_ERROR_STOP_ITERATION)
+                               err = BTRFS_UTIL_OK;
+                       break;
+               }
+
+               err = openat_parent_and_name(fd, child_path, child_name,
+                                            sizeof(child_name),
+                                            &child_parent_fd);
+               free(child_path);
+               if (err)
+                       break;
+
+               err = btrfs_util_delete_subvolume_fd(child_parent_fd,
+                                                    child_name, 0);
+               SAVE_ERRNO_AND_CLOSE(child_parent_fd);
+               if (err)
+                       break;
+       }
+
+       btrfs_util_destroy_subvolume_iterator(iter);
+out:
+       SAVE_ERRNO_AND_CLOSE(fd);
+       return err;
+}
+
+PUBLIC enum btrfs_util_error btrfs_util_delete_subvolume(const char *path,
+                                                        int flags)
+{
+       char name[BTRFS_PATH_NAME_MAX + 1];
+       enum btrfs_util_error err;
+       int parent_fd;
+
+       err = openat_parent_and_name(AT_FDCWD, path, name, sizeof(name),
+                                    &parent_fd);
+       if (err)
+               return err;
+
+       err = btrfs_util_delete_subvolume_fd(parent_fd, name, flags);
+       SAVE_ERRNO_AND_CLOSE(parent_fd);
+       return err;
+}
+
+PUBLIC enum btrfs_util_error btrfs_util_delete_subvolume_fd(int parent_fd,
+                                                           const char *name,
+                                                           int flags)
+{
+       struct btrfs_ioctl_vol_args args = {};
+       enum btrfs_util_error err;
+       size_t len;
+       int ret;
+
+       if (flags & ~BTRFS_UTIL_DELETE_SUBVOLUME_MASK) {
+               errno = EINVAL;
+               return BTRFS_UTIL_ERROR_INVALID_ARGUMENT;
+       }
+
+       if (flags & BTRFS_UTIL_DELETE_SUBVOLUME_RECURSIVE) {
+               err = delete_subvolume_children(parent_fd, name);
+               if (err)
+                       return err;
+       }
+
+       len = strlen(name);
+       if (len >= sizeof(args.name)) {
+               errno = ENAMETOOLONG;
+               return BTRFS_UTIL_ERROR_INVALID_ARGUMENT;
+       }
+       memcpy(args.name, name, len);
+       args.name[len] = '\0';
+
+       ret = ioctl(parent_fd, BTRFS_IOC_SNAP_DESTROY, &args);
+       if (ret == -1)
+               return BTRFS_UTIL_ERROR_SNAP_DESTROY_FAILED;
+
+       return BTRFS_UTIL_OK;
+}
+
 PUBLIC void btrfs_util_destroy_subvolume_iterator(struct btrfs_util_subvolume_iterator *iter)
 {
        if (iter) {