summaryrefslogtreecommitdiff
path: root/libbtrfsutil/python
diff options
context:
space:
mode:
authorOmar Sandoval <osandov@fb.com>2018-01-18 14:23:23 -0800
committerDavid Sterba <dsterba@suse.com>2018-03-06 11:28:37 +0100
commitcfa89b30821fedb48ca163a9c1128c4db596c911 (patch)
treebf04b519f0b9df71f542ec2ee8d9cbc2f4869dff /libbtrfsutil/python
parent0b8512b7f5a6106efcab371471c472572a55367e (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.h1
-rw-r--r--libbtrfsutil/python/module.c11
-rw-r--r--libbtrfsutil/python/subvolume.c49
-rw-r--r--libbtrfsutil/python/tests/test_qgroup.py10
-rw-r--r--libbtrfsutil/python/tests/test_subvolume.py55
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: