diff options
author | Omar Sandoval <osandov@fb.com> | 2018-01-18 14:23:23 -0800 |
---|---|---|
committer | David Sterba <dsterba@suse.com> | 2018-03-06 11:28:37 +0100 |
commit | cfa89b30821fedb48ca163a9c1128c4db596c911 (patch) | |
tree | bf04b519f0b9df71f542ec2ee8d9cbc2f4869dff /libbtrfsutil/python | |
parent | 0b8512b7f5a6106efcab371471c472572a55367e (diff) |
libbtrfsutil: add btrfs_util_create_snapshot()
Thanks to subvolume iterators, we can also implement recursive snapshot
fairly easily.
Signed-off-by: Omar Sandoval <osandov@fb.com>
Signed-off-by: David Sterba <dsterba@suse.com>
Diffstat (limited to 'libbtrfsutil/python')
-rw-r--r-- | libbtrfsutil/python/btrfsutilpy.h | 1 | ||||
-rw-r--r-- | libbtrfsutil/python/module.c | 11 | ||||
-rw-r--r-- | libbtrfsutil/python/subvolume.c | 49 | ||||
-rw-r--r-- | libbtrfsutil/python/tests/test_qgroup.py | 10 | ||||
-rw-r--r-- | libbtrfsutil/python/tests/test_subvolume.py | 55 |
5 files changed, 126 insertions, 0 deletions
diff --git a/libbtrfsutil/python/btrfsutilpy.h b/libbtrfsutil/python/btrfsutilpy.h index 79ac2eec..59c7e994 100644 --- a/libbtrfsutil/python/btrfsutilpy.h +++ b/libbtrfsutil/python/btrfsutilpy.h @@ -73,6 +73,7 @@ PyObject *set_subvolume_read_only(PyObject *self, PyObject *args, PyObject *kwds 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); void add_module_constants(PyObject *m); diff --git a/libbtrfsutil/python/module.c b/libbtrfsutil/python/module.c index 9a237142..3f0f6170 100644 --- a/libbtrfsutil/python/module.c +++ b/libbtrfsutil/python/module.c @@ -216,6 +216,17 @@ static PyMethodDef btrfsutil_methods[] = { "path -- string, bytes, or path-like object\n" "async -- create the subvolume without waiting for it to commit to\n" "disk and return the transaction ID"}, + {"create_snapshot", (PyCFunction)create_snapshot, + METH_VARARGS | METH_KEYWORDS, + "create_snapshot(source, path, recursive=False, read_only=False, async=False)\n\n" + "Create a new snapshot.\n\n" + "Arguments:\n" + "source -- string, bytes, path-like object, or open file descriptor\n" + "path -- string, bytes, or path-like object\n" + "recursive -- also snapshot child subvolumes\n" + "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"}, {}, }; diff --git a/libbtrfsutil/python/subvolume.c b/libbtrfsutil/python/subvolume.c index 6c384583..a158ade7 100644 --- a/libbtrfsutil/python/subvolume.c +++ b/libbtrfsutil/python/subvolume.c @@ -349,6 +349,55 @@ PyObject *create_subvolume(PyObject *self, PyObject *args, PyObject *kwds) Py_RETURN_NONE; } +PyObject *create_snapshot(PyObject *self, PyObject *args, PyObject *kwds) +{ + static char *keywords[] = { + "source", "path", "recursive", "read_only", "async", + "qgroup_inherit", NULL, + }; + struct path_arg src = {.allow_fd = true}, dst = {.allow_fd = false}; + enum btrfs_util_error err; + int recursive = 0, read_only = 0, async = 0; + int flags = 0; + QgroupInherit *inherit = NULL; + uint64_t transid; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O&O&|pppO!:create_snapshot", + keywords, &path_converter, &src, + &path_converter, &dst, &recursive, + &read_only, &async, + &QgroupInherit_type, &inherit)) + return NULL; + + if (recursive) + flags |= BTRFS_UTIL_CREATE_SNAPSHOT_RECURSIVE; + if (read_only) + flags |= BTRFS_UTIL_CREATE_SNAPSHOT_READ_ONLY; + + if (src.path) { + err = btrfs_util_create_snapshot(src.path, dst.path, flags, + async ? &transid : NULL, + inherit ? inherit->inherit : NULL); + } else { + err = btrfs_util_create_snapshot_fd(src.fd, dst.path, flags, + async ? &transid : NULL, + inherit ? inherit->inherit : NULL); + } + if (err) { + SetFromBtrfsUtilErrorWithPaths(err, &src, &dst); + path_cleanup(&src); + path_cleanup(&dst); + return NULL; + } + + path_cleanup(&src); + path_cleanup(&dst); + if (async) + return PyLong_FromUnsignedLongLong(transid); + else + Py_RETURN_NONE; +} + typedef struct { PyObject_HEAD struct btrfs_util_subvolume_iterator *iter; diff --git a/libbtrfsutil/python/tests/test_qgroup.py b/libbtrfsutil/python/tests/test_qgroup.py index 19e6b05a..74fc46b6 100644 --- a/libbtrfsutil/python/tests/test_qgroup.py +++ b/libbtrfsutil/python/tests/test_qgroup.py @@ -31,6 +31,16 @@ class TestQgroup(BtrfsTestCase): btrfsutil.create_subvolume(subvol, qgroup_inherit=inherit) + def test_snapshot_inherit(self): + subvol = os.path.join(self.mountpoint, 'subvol') + snapshot = os.path.join(self.mountpoint, 'snapshot') + + inherit = btrfsutil.QgroupInherit() + inherit.add_group(5) + + btrfsutil.create_subvolume(subvol) + btrfsutil.create_snapshot(subvol, snapshot, qgroup_inherit=inherit) + class TestQgroupInherit(unittest.TestCase): def test_new(self): diff --git a/libbtrfsutil/python/tests/test_subvolume.py b/libbtrfsutil/python/tests/test_subvolume.py index b43beca7..2951154e 100644 --- a/libbtrfsutil/python/tests/test_subvolume.py +++ b/libbtrfsutil/python/tests/test_subvolume.py @@ -129,6 +129,13 @@ class TestSubvolume(BtrfsTestCase): self.assertEqual(info.stime, 0) self.assertEqual(info.rtime, 0) + subvol_uuid = info.uuid + snapshot = os.path.join(self.mountpoint, 'snapshot') + btrfsutil.create_snapshot(subvol, snapshot) + + info = btrfsutil.subvolume_info(snapshot) + self.assertEqual(info.parent_uuid, subvol_uuid) + # TODO: test received_uuid, stransid, rtransid, stime, and rtime for arg in self.path_or_fd(self.mountpoint): @@ -215,6 +222,54 @@ class TestSubvolume(BtrfsTestCase): self.assertTrue(os.WIFEXITED(wstatus)) self.assertEqual(os.WEXITSTATUS(wstatus), 0) + def test_create_snapshot(self): + subvol = os.path.join(self.mountpoint, 'subvol') + + btrfsutil.create_subvolume(subvol) + os.mkdir(os.path.join(subvol, 'dir')) + + for i, arg in enumerate(self.path_or_fd(subvol)): + with self.subTest(type=type(arg)): + snapshots_dir = os.path.join(self.mountpoint, 'snapshots{}'.format(i)) + os.mkdir(snapshots_dir) + snapshot = os.path.join(snapshots_dir, 'snapshot') + + btrfsutil.create_snapshot(subvol, snapshot + '1') + self.assertTrue(btrfsutil.is_subvolume(snapshot + '1')) + self.assertTrue(os.path.exists(os.path.join(snapshot + '1', 'dir'))) + + btrfsutil.create_snapshot(subvol, (snapshot + '2').encode()) + self.assertTrue(btrfsutil.is_subvolume(snapshot + '2')) + self.assertTrue(os.path.exists(os.path.join(snapshot + '2', 'dir'))) + + if HAVE_PATH_LIKE: + btrfsutil.create_snapshot(subvol, PurePath(snapshot + '3')) + self.assertTrue(btrfsutil.is_subvolume(snapshot + '3')) + self.assertTrue(os.path.exists(os.path.join(snapshot + '3', 'dir'))) + + nested_subvol = os.path.join(subvol, 'nested') + more_nested_subvol = os.path.join(nested_subvol, 'more_nested') + btrfsutil.create_subvolume(nested_subvol) + btrfsutil.create_subvolume(more_nested_subvol) + os.mkdir(os.path.join(more_nested_subvol, 'nested_dir')) + + snapshot = os.path.join(self.mountpoint, 'snapshot') + + btrfsutil.create_snapshot(subvol, snapshot + '1') + # Dummy subvolume. + self.assertEqual(os.stat(os.path.join(snapshot + '1', 'nested')).st_ino, 2) + self.assertFalse(os.path.exists(os.path.join(snapshot + '1', 'nested', 'more_nested'))) + + btrfsutil.create_snapshot(subvol, snapshot + '2', recursive=True) + self.assertTrue(os.path.exists(os.path.join(snapshot + '2', 'nested/more_nested/nested_dir'))) + + transid = btrfsutil.create_snapshot(subvol, snapshot + '3', recursive=True, async=True) + self.assertTrue(os.path.exists(os.path.join(snapshot + '3', 'nested/more_nested/nested_dir'))) + self.assertGreater(transid, 0) + + btrfsutil.create_snapshot(subvol, snapshot + '4', read_only=True) + self.assertTrue(btrfsutil.get_subvolume_read_only(snapshot + '4')) + def test_subvolume_iterator(self): pwd = os.getcwd() try: |