summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRichard W.M. Jones <rjones@redhat.com>2012-04-25 16:35:12 +0100
committerRichard W.M. Jones <rjones@redhat.com>2012-04-25 17:32:30 +0100
commit87ea7a04094d5ed40f3f047ff2b7a613d4d530d4 (patch)
tree458b3713c313d124c37b8de11e45d0b081f3fde6
parent3cc9703f901e85a589692b9d0bf5ef7cbf72ed73 (diff)
downloadlibguestfs-87ea7a04094d5ed40f3f047ff2b7a613d4d530d4.tar.gz
libguestfs-87ea7a04094d5ed40f3f047ff2b7a613d4d530d4.tar.xz
libguestfs-87ea7a04094d5ed40f3f047ff2b7a613d4d530d4.zip
New btrfs APIs.
Bind the easy parts of the 'btrfs' program. The new APIs are: btrfs-device-add: add devices to a btrfs filesystem btrfs-device-delete: remove devices from a btrfs filesystem btrfs-filesystem-sync: sync a btrfs filesystem btrfs-filesystem-balance: balance a btrfs filesystem btrfs-subvolume-create: create a btrfs snapshot btrfs-subvolume-delete: delete a btrfs snapshot btrfs-subvolume-list: list btrfs snapshots and subvolumes btrfs-subvolume-set-default: set default btrfs subvolume btrfs-subvolume-snapshot: create a writable btrfs snapshot
-rw-r--r--Makefile.am1
-rw-r--r--configure.ac1
-rw-r--r--daemon/btrfs.c396
-rw-r--r--generator/generator_actions.ml83
-rw-r--r--generator/generator_structs.ml8
-rw-r--r--gobject/Makefile.inc2
-rw-r--r--java/Makefile.inc1
-rw-r--r--java/com/redhat/et/libguestfs/.gitignore1
-rw-r--r--po/POTFILES.in1
-rw-r--r--src/MAX_PROC_NR2
-rw-r--r--tests/btrfs/Makefile.am31
-rwxr-xr-xtests/btrfs/test-btrfs-devices.sh57
-rwxr-xr-xtests/btrfs/test-btrfs-subvolume-default.pl95
13 files changed, 678 insertions, 1 deletions
diff --git a/Makefile.am b/Makefile.am
index 32999808..ae0ea3ff 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -42,6 +42,7 @@ SUBDIRS += tests/lvm
SUBDIRS += tests/luks
SUBDIRS += tests/md
SUBDIRS += tests/ntfsclone
+SUBDIRS += tests/btrfs
SUBDIRS += tests/regressions
endif
diff --git a/configure.ac b/configure.ac
index e16d6c8c..fe603717 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1229,6 +1229,7 @@ AC_CONFIG_FILES([Makefile
src/Makefile
sysprep/Makefile
test-tool/Makefile
+ tests/btrfs/Makefile
tests/c-api/Makefile
tests/data/Makefile
tests/extra/Makefile
diff --git a/daemon/btrfs.c b/daemon/btrfs.c
index 60c863a4..8569173e 100644
--- a/daemon/btrfs.c
+++ b/daemon/btrfs.c
@@ -202,3 +202,399 @@ do_mkfs_btrfs (char *const *devices,
free (err);
return 0;
}
+
+int
+do_btrfs_subvolume_snapshot (const char *source, const char *dest)
+{
+ const size_t MAX_ARGS = 64;
+ const char *argv[MAX_ARGS];
+ size_t i = 0;
+ char *source_buf, *dest_buf;
+ char *err;
+ int r;
+
+ source_buf = sysroot_path (source);
+ if (source_buf == NULL) {
+ reply_with_perror ("malloc");
+ return -1;
+ }
+ dest_buf = sysroot_path (dest);
+ if (dest_buf == NULL) {
+ reply_with_perror ("malloc");
+ free (source_buf);
+ return -1;
+ }
+
+ ADD_ARG (argv, i, "btrfs");
+ ADD_ARG (argv, i, "subvolume");
+ ADD_ARG (argv, i, "snapshot");
+ ADD_ARG (argv, i, source_buf);
+ ADD_ARG (argv, i, dest_buf);
+ ADD_ARG (argv, i, NULL);
+
+ r = commandv (NULL, &err, argv);
+ free (source_buf);
+ free (dest_buf);
+ if (r == -1) {
+ reply_with_error ("%s: %s: %s", source, dest, err);
+ free (err);
+ return -1;
+ }
+ free (err);
+
+ return 0;
+}
+
+int
+do_btrfs_subvolume_delete (const char *subvolume)
+{
+ const size_t MAX_ARGS = 64;
+ const char *argv[MAX_ARGS];
+ size_t i = 0;
+ char *subvolume_buf;
+ char *err;
+ int r;
+
+ subvolume_buf = sysroot_path (subvolume);
+ if (subvolume_buf == NULL) {
+ reply_with_perror ("malloc");
+ return -1;
+ }
+
+ ADD_ARG (argv, i, "btrfs");
+ ADD_ARG (argv, i, "subvolume");
+ ADD_ARG (argv, i, "delete");
+ ADD_ARG (argv, i, subvolume_buf);
+ ADD_ARG (argv, i, NULL);
+
+ r = commandv (NULL, &err, argv);
+ free (subvolume_buf);
+ if (r == -1) {
+ reply_with_error ("%s: %s", subvolume, err);
+ free (err);
+ return -1;
+ }
+ free (err);
+
+ return 0;
+}
+
+int
+do_btrfs_subvolume_create (const char *dest)
+{
+ const size_t MAX_ARGS = 64;
+ const char *argv[MAX_ARGS];
+ size_t i = 0;
+ char *dest_buf;
+ char *err;
+ int r;
+
+ dest_buf = sysroot_path (dest);
+ if (dest_buf == NULL) {
+ reply_with_perror ("malloc");
+ return -1;
+ }
+
+ ADD_ARG (argv, i, "btrfs");
+ ADD_ARG (argv, i, "subvolume");
+ ADD_ARG (argv, i, "create");
+ ADD_ARG (argv, i, dest_buf);
+ ADD_ARG (argv, i, NULL);
+
+ r = commandv (NULL, &err, argv);
+ free (dest_buf);
+ if (r == -1) {
+ reply_with_error ("%s: %s", dest, err);
+ free (err);
+ return -1;
+ }
+ free (err);
+
+ return 0;
+}
+
+guestfs_int_btrfssubvolume_list *
+do_btrfs_subvolume_list (const char *fs)
+{
+ const size_t MAX_ARGS = 64;
+ guestfs_int_btrfssubvolume_list *ret;
+ char *fs_buf;
+ const char *argv[MAX_ARGS];
+ size_t i = 0;
+ char *out, *err, **lines, *pos;
+ size_t nr_subvolumes;
+ int r;
+
+ fs_buf = sysroot_path (fs);
+ if (fs_buf == NULL) {
+ reply_with_perror ("malloc");
+ return NULL;
+ }
+
+ ADD_ARG (argv, i, "btrfs");
+ ADD_ARG (argv, i, "subvolume");
+ ADD_ARG (argv, i, "list");
+ ADD_ARG (argv, i, fs_buf);
+ ADD_ARG (argv, i, NULL);
+
+ r = commandv (&out, &err, argv);
+ free (fs_buf);
+ if (r == -1) {
+ reply_with_error ("%s: %s", fs, err);
+ free (err);
+ return NULL;
+ }
+ free (err);
+
+ lines = split_lines (out);
+ free (out);
+ if (!lines)
+ return NULL;
+
+ /* Output is:
+ *
+ * ID 256 top level 5 path test1
+ * ID 257 top level 5 path dir/test2
+ * ID 258 top level 5 path test3
+ *
+ * "ID <n>" is the subvolume ID. "top level <n>" is the top level
+ * subvolume ID. "path <str>" is the subvolume path, relative to
+ * the top of the filesystem.
+ */
+ nr_subvolumes = count_strings (lines);
+
+ ret = malloc (sizeof *ret);
+ if (!ret) {
+ reply_with_perror ("malloc");
+ free_stringslen (lines, nr_subvolumes);
+ return NULL;
+ }
+ ret->guestfs_int_btrfssubvolume_list_len = nr_subvolumes;
+ ret->guestfs_int_btrfssubvolume_list_val =
+ calloc (nr_subvolumes, sizeof (struct guestfs_int_btrfssubvolume));
+ if (ret->guestfs_int_btrfssubvolume_list_val == NULL) {
+ reply_with_perror ("malloc");
+ free (ret);
+ free_stringslen (lines, nr_subvolumes);
+ return NULL;
+ }
+
+ for (i = 0; i < nr_subvolumes; ++i) {
+ /* To avoid allocations, reuse the 'line' buffer to store the
+ * path. Thus we don't need to free 'line', since it will be
+ * freed by the calling (XDR) code.
+ */
+ char *line = lines[i];
+
+ if (sscanf (line, "ID %" SCNu64 " top level %" SCNu64 " path ",
+ &ret->guestfs_int_btrfssubvolume_list_val[i].btrfssubvolume_id,
+ &ret->guestfs_int_btrfssubvolume_list_val[i].btrfssubvolume_top_level_id) != 2) {
+ unexpected_output:
+ reply_with_error ("unexpected output from 'btrfs subvolume list' command: %s", line);
+ free_stringslen (lines, nr_subvolumes);
+ free (ret->guestfs_int_btrfssubvolume_list_val);
+ free (ret);
+ return NULL;
+ }
+
+ pos = strstr (line, " path ");
+ if (pos == NULL)
+ goto unexpected_output;
+ pos += 6;
+
+ memmove (line, pos, strlen (pos) + 1);
+ ret->guestfs_int_btrfssubvolume_list_val[i].btrfssubvolume_path = line;
+ }
+
+ return ret;
+}
+
+int
+do_btrfs_subvolume_set_default (int64_t id, const char *fs)
+{
+ const size_t MAX_ARGS = 64;
+ const char *argv[MAX_ARGS];
+ size_t i = 0;
+ char *fs_buf;
+ char buf[64];
+ char *err;
+ int r;
+
+ snprintf (buf, sizeof buf, "%" PRIi64, id);
+
+ fs_buf = sysroot_path (fs);
+ if (fs_buf == NULL) {
+ reply_with_perror ("malloc");
+ return -1;
+ }
+
+ ADD_ARG (argv, i, "btrfs");
+ ADD_ARG (argv, i, "subvolume");
+ ADD_ARG (argv, i, "set-default");
+ ADD_ARG (argv, i, buf);
+ ADD_ARG (argv, i, fs_buf);
+ ADD_ARG (argv, i, NULL);
+
+ r = commandv (NULL, &err, argv);
+ free (fs_buf);
+ if (r == -1) {
+ reply_with_error ("%s: %s", fs, err);
+ free (err);
+ return -1;
+ }
+ free (err);
+
+ return 0;
+}
+
+int
+do_btrfs_filesystem_sync (const char *fs)
+{
+ const size_t MAX_ARGS = 64;
+ const char *argv[MAX_ARGS];
+ size_t i = 0;
+ char *fs_buf;
+ char *err;
+ int r;
+
+ fs_buf = sysroot_path (fs);
+ if (fs_buf == NULL) {
+ reply_with_perror ("malloc");
+ return -1;
+ }
+
+ ADD_ARG (argv, i, "btrfs");
+ ADD_ARG (argv, i, "filesystem");
+ ADD_ARG (argv, i, "sync");
+ ADD_ARG (argv, i, fs_buf);
+ ADD_ARG (argv, i, NULL);
+
+ r = commandv (NULL, &err, argv);
+ free (fs_buf);
+ if (r == -1) {
+ reply_with_error ("%s: %s", fs, err);
+ free (err);
+ return -1;
+ }
+ free (err);
+
+ return 0;
+}
+
+int
+do_btrfs_filesystem_balance (const char *fs)
+{
+ const size_t MAX_ARGS = 64;
+ const char *argv[MAX_ARGS];
+ size_t i = 0;
+ char *fs_buf;
+ char *err;
+ int r;
+
+ fs_buf = sysroot_path (fs);
+ if (fs_buf == NULL) {
+ reply_with_perror ("malloc");
+ return -1;
+ }
+
+ ADD_ARG (argv, i, "btrfs");
+ ADD_ARG (argv, i, "filesystem");
+ ADD_ARG (argv, i, "balance");
+ ADD_ARG (argv, i, fs_buf);
+ ADD_ARG (argv, i, NULL);
+
+ r = commandv (NULL, &err, argv);
+ free (fs_buf);
+ if (r == -1) {
+ reply_with_error ("%s: %s", fs, err);
+ free (err);
+ return -1;
+ }
+ free (err);
+
+ return 0;
+}
+
+int
+do_btrfs_device_add (char *const *devices, const char *fs)
+{
+ size_t nr_devices = count_strings (devices);
+
+ if (nr_devices == 0)
+ return 0;
+
+ size_t MAX_ARGS = nr_devices + 8;
+ const char *argv[MAX_ARGS];
+ size_t i = 0, j;
+ char *fs_buf;
+ char *err;
+ int r;
+
+ fs_buf = sysroot_path (fs);
+ if (fs_buf == NULL) {
+ reply_with_perror ("malloc");
+ return -1;
+ }
+
+ ADD_ARG (argv, i, "btrfs");
+ ADD_ARG (argv, i, "device");
+ ADD_ARG (argv, i, "add");
+
+ for (j = 0; j < nr_devices; ++j)
+ ADD_ARG (argv, i, devices[j]);
+
+ ADD_ARG (argv, i, fs_buf);
+ ADD_ARG (argv, i, NULL);
+
+ r = commandv (NULL, &err, argv);
+ free (fs_buf);
+ if (r == -1) {
+ reply_with_error ("%s: %s", fs, err);
+ free (err);
+ return -1;
+ }
+ free (err);
+
+ return 0;
+}
+
+int
+do_btrfs_device_delete (char *const *devices, const char *fs)
+{
+ size_t nr_devices = count_strings (devices);
+
+ if (nr_devices == 0)
+ return 0;
+
+ size_t MAX_ARGS = nr_devices + 8;
+ const char *argv[MAX_ARGS];
+ size_t i = 0, j;
+ char *fs_buf;
+ char *err;
+ int r;
+
+ fs_buf = sysroot_path (fs);
+ if (fs_buf == NULL) {
+ reply_with_perror ("malloc");
+ return -1;
+ }
+
+ ADD_ARG (argv, i, "btrfs");
+ ADD_ARG (argv, i, "device");
+ ADD_ARG (argv, i, "delete");
+ for (j = 0; j < nr_devices; ++j)
+ ADD_ARG (argv, i, devices[j]);
+ ADD_ARG (argv, i, fs);
+ ADD_ARG (argv, i, NULL);
+
+ r = commandv (NULL, &err, argv);
+ free (fs_buf);
+ if (r == -1) {
+ reply_with_error ("%s: %s", fs, err);
+ free (err);
+ return -1;
+ }
+ free (err);
+
+ return 0;
+}
diff --git a/generator/generator_actions.ml b/generator/generator_actions.ml
index 56af327a..c237061a 100644
--- a/generator/generator_actions.ml
+++ b/generator/generator_actions.ml
@@ -7136,6 +7136,89 @@ This sets the ext2 file generation of a file.
See C<guestfs_get_e2generation>.");
+ ("btrfs_subvolume_snapshot", (RErr, [Pathname "source"; Pathname "dest"], []), 322, [Optional "btrfs"; CamelName "BTRFSSubvolumeSnapshot"],
+ [InitPartition, IfAvailable "btrfs", TestRun (
+ [["mkfs_btrfs"; "/dev/sda1"; ""; ""; "NOARG"; ""; "NOARG"; "NOARG"; ""; ""];
+ ["mount"; "/dev/sda1"; "/"];
+ ["mkdir"; "/dir"];
+ ["btrfs_subvolume_create"; "/test1"];
+ ["btrfs_subvolume_create"; "/test2"];
+ ["btrfs_subvolume_create"; "/dir/test3"];
+ ["btrfs_subvolume_snapshot"; "/dir/test3"; "/dir/test4"]])],
+ "create a writable btrfs snapshot",
+ "\
+Create a writable snapshot of the btrfs subvolume C<source>.
+The C<dest> argument is the destination directory and the name
+of the snapshot, in the form C</path/to/dest/name>.");
+
+ ("btrfs_subvolume_delete", (RErr, [Pathname "subvolume"], []), 323, [Optional "btrfs"; CamelName "BTRFSSubvolumeDelete"],
+ [InitPartition, IfAvailable "btrfs", TestRun (
+ [["mkfs_btrfs"; "/dev/sda1"; ""; ""; "NOARG"; ""; "NOARG"; "NOARG"; ""; ""];
+ ["mount"; "/dev/sda1"; "/"];
+ ["btrfs_subvolume_create"; "/test1"];
+ ["btrfs_subvolume_delete"; "/test1"]])],
+ "delete a btrfs snapshot",
+ "\
+Delete the named btrfs subvolume.");
+
+ ("btrfs_subvolume_create", (RErr, [Pathname "dest"], []), 324, [Optional "btrfs"; CamelName "BTRFSSubvolumeCreate"],
+ [], (* tested above *)
+ "create a btrfs snapshot",
+ "\
+Create a btrfs subvolume. The C<dest> argument is the destination
+directory and the name of the snapshot, in the form C</path/to/dest/name>.");
+
+ ("btrfs_subvolume_list", (RStructList ("subvolumes", "btrfssubvolume"), [Pathname "fs"], []), 325, [Optional "btrfs"; CamelName "BTRFSSubvolumeList"],
+ [], (* tested in tests/btrfs *)
+ "list btrfs snapshots and subvolumes",
+ "\
+List the btrfs snapshots and subvolumes of the btrfs filesystem
+which is mounted at C<fs>.");
+
+ ("btrfs_subvolume_set_default", (RErr, [Int64 "id"; Pathname "fs"], []), 326, [Optional "btrfs"; CamelName "BTRFSSubvolumeSetDefault"],
+ [], (* tested in tests/btrfs *)
+ "set default btrfs subvolume",
+ "\
+Set the subvolume of the btrfs filesystem C<fs> which will
+be mounted by default. See <guestfs_btrfs_subvolume_list> to
+get a list of subvolumes.");
+
+ ("btrfs_filesystem_sync", (RErr, [Pathname "fs"], []), 327, [Optional "btrfs"; CamelName "BTRFSFilesystemSync"],
+ [InitPartition, IfAvailable "btrfs", TestRun (
+ [["mkfs_btrfs"; "/dev/sda1"; ""; ""; "NOARG"; ""; "NOARG"; "NOARG"; ""; ""];
+ ["mount"; "/dev/sda1"; "/"];
+ ["btrfs_subvolume_create"; "/test1"];
+ ["btrfs_filesystem_sync"; "/test1"];
+ ["btrfs_filesystem_balance"; "/test1"]])],
+ "sync a btrfs filesystem",
+ "\
+Force sync on the btrfs filesystem mounted at C<fs>.");
+
+ ("btrfs_filesystem_balance", (RErr, [Pathname "fs"], []), 328, [Optional "btrfs"; CamelName "BTRFSFilesystemBalance"],
+ [], (* tested above *)
+ "balance a btrfs filesystem",
+ "\
+Balance the chunks in the btrfs filesystem mounted at C<fs>
+across the underlying devices.");
+
+ ("btrfs_device_add", (RErr, [DeviceList "devices"; Pathname "fs"], []), 329, [Optional "btrfs"; CamelName "BTRFSDeviceAdd"],
+ [], (* test disk isn't large enough to test this thoroughly, so there
+ * is an external test in 'tests/btrfs' directory.
+ *)
+ "add devices to a btrfs filesystem",
+ "\
+Add the list of device(s) in C<devices> to the btrfs filesystem
+mounted at C<fs>. If C<devices> is an empty list, this does nothing.");
+
+ ("btrfs_device_delete", (RErr, [DeviceList "devices"; Pathname "fs"], []), 330, [Optional "btrfs"; CamelName "BTRFSDeviceDelete"],
+ [], (* test disk isn't large enough to test this thoroughly, so there
+ * is an external test in 'tests/btrfs' directory.
+ *)
+ "remove devices from a btrfs filesystem",
+ "\
+Remove the C<devices> from the btrfs filesystem mounted at C<fs>.
+If C<devices> is an empty list, this does nothing.");
+
]
let all_functions = non_daemon_functions @ daemon_functions
diff --git a/generator/generator_structs.ml b/generator/generator_structs.ml
index 8c862ae3..024bb3cd 100644
--- a/generator/generator_structs.ml
+++ b/generator/generator_structs.ml
@@ -219,6 +219,13 @@ let structs = [
"mdstat_index", FInt32;
"mdstat_flags", FString;
];
+
+ (* btrfs subvolume list output *)
+ "btrfssubvolume", [
+ "btrfssubvolume_id", FUInt64;
+ "btrfssubvolume_top_level_id", FUInt64;
+ "btrfssubvolume_path", FString;
+ ];
] (* end of structs *)
(* For bindings which want camel case *)
@@ -237,6 +244,7 @@ let camel_structs = [
"application", "Application";
"isoinfo", "ISOInfo";
"mdstat", "MDStat";
+ "btrfssubvolume", "BTRFSSubvolume";
]
let camel_structs = List.sort (fun (_,a) (_,b) -> compare a b) camel_structs
diff --git a/gobject/Makefile.inc b/gobject/Makefile.inc
index ac65cd37..69bb44bb 100644
--- a/gobject/Makefile.inc
+++ b/gobject/Makefile.inc
@@ -37,6 +37,7 @@ guestfs_gobject_headers=\
guestfs-gobject-struct-application.h \
guestfs-gobject-struct-isoinfo.h \
guestfs-gobject-struct-mdstat.h \
+ guestfs-gobject-struct-btrfssubvolume.h \
guestfs-gobject-optargs-test0.h \
guestfs-gobject-optargs-add_drive_opts.h \
guestfs-gobject-optargs-add_domain.h \
@@ -78,6 +79,7 @@ guestfs_gobject_sources=\
guestfs-gobject-struct-application.c \
guestfs-gobject-struct-isoinfo.c \
guestfs-gobject-struct-mdstat.c \
+ guestfs-gobject-struct-btrfssubvolume.c \
guestfs-gobject-optargs-test0.c \
guestfs-gobject-optargs-add_drive_opts.c \
guestfs-gobject-optargs-add_domain.c \
diff --git a/java/Makefile.inc b/java/Makefile.inc
index 64c6dcc2..efad2a0c 100644
--- a/java/Makefile.inc
+++ b/java/Makefile.inc
@@ -21,6 +21,7 @@
java_built_sources = \
com/redhat/et/libguestfs/Application.java \
+ com/redhat/et/libguestfs/BTRFSSubvolume.java \
com/redhat/et/libguestfs/Dirent.java \
com/redhat/et/libguestfs/INotifyEvent.java \
com/redhat/et/libguestfs/ISOInfo.java \
diff --git a/java/com/redhat/et/libguestfs/.gitignore b/java/com/redhat/et/libguestfs/.gitignore
index c7cc3020..9556d816 100644
--- a/java/com/redhat/et/libguestfs/.gitignore
+++ b/java/com/redhat/et/libguestfs/.gitignore
@@ -1,4 +1,5 @@
Application.java
+BTRFSSubvolume.java
Dirent.java
INotifyEvent.java
ISOInfo.java
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 01d260ab..c5a87e5d 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -156,6 +156,7 @@ gobject/guestfs-gobject-optargs-tune2fs.c
gobject/guestfs-gobject-optargs-umount_local.c
gobject/guestfs-gobject-session.c
gobject/guestfs-gobject-struct-application.c
+gobject/guestfs-gobject-struct-btrfssubvolume.c
gobject/guestfs-gobject-struct-dirent.c
gobject/guestfs-gobject-struct-inotify_event.c
gobject/guestfs-gobject-struct-int_bool.c
diff --git a/src/MAX_PROC_NR b/src/MAX_PROC_NR
index 3ae0b938..db2cef56 100644
--- a/src/MAX_PROC_NR
+++ b/src/MAX_PROC_NR
@@ -1 +1 @@
-321
+330
diff --git a/tests/btrfs/Makefile.am b/tests/btrfs/Makefile.am
new file mode 100644
index 00000000..6d789ba7
--- /dev/null
+++ b/tests/btrfs/Makefile.am
@@ -0,0 +1,31 @@
+# libguestfs
+# Copyright (C) 2009-2012 Red Hat Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+include $(top_srcdir)/subdir-rules.mk
+
+TESTS = \
+ test-btrfs-devices.sh \
+ test-btrfs-subvolume-default.pl
+
+random_val := $(shell awk 'BEGIN{srand(); print 1+int(255*rand())}' < /dev/null)
+
+TESTS_ENVIRONMENT = \
+ MALLOC_PERTURB_=$(random_val) \
+ $(top_builddir)/run
+
+EXTRA_DIST = \
+ $(TESTS)
diff --git a/tests/btrfs/test-btrfs-devices.sh b/tests/btrfs/test-btrfs-devices.sh
new file mode 100755
index 00000000..546ce7c5
--- /dev/null
+++ b/tests/btrfs/test-btrfs-devices.sh
@@ -0,0 +1,57 @@
+#!/bin/bash -
+# libguestfs
+# Copyright (C) 2012 Red Hat Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+# Test btrfs adding/removing devices.
+
+set -e
+
+# XXX Not a very good test.
+if ! btrfs --help >/dev/null 2>&1; then
+ echo "$0: test skipped because no 'btrfs' utility"
+ exit 0
+fi
+
+rm -f test[1234].img
+
+../../fish/guestfish <<EOF
+# Add four empty disks
+sparse test1.img 1G
+sparse test2.img 1G
+sparse test3.img 1G
+sparse test4.img 1G
+run
+
+part-disk /dev/sda mbr
+part-disk /dev/sdb mbr
+part-disk /dev/sdc mbr
+part-disk /dev/sdd mbr
+
+mkfs-btrfs "/dev/sda1 /dev/sdb1"
+mount /dev/sda1 /
+
+mkdir /foo
+touch /foo/bar
+
+btrfs-device-add "/dev/sdc1 /dev/sdd1" /
+
+# I cannot get this to work, seems to be a btrfs bug:
+#btrfs-device-delete "/dev/sda1 /dev/sdb1" /
+
+EOF
+
+rm -f test[1234].img
diff --git a/tests/btrfs/test-btrfs-subvolume-default.pl b/tests/btrfs/test-btrfs-subvolume-default.pl
new file mode 100755
index 00000000..013291ba
--- /dev/null
+++ b/tests/btrfs/test-btrfs-subvolume-default.pl
@@ -0,0 +1,95 @@
+#!/usr/bin/perl
+# libguestfs
+# Copyright (C) 2012 Red Hat Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+# Test btrfs subvolume list and btrfs subvolume default-id.
+
+use strict;
+use warnings;
+
+use Sys::Guestfs;
+use Sys::Guestfs::Lib qw(feature_available);
+
+my $testimg = "test1.img";
+
+unlink $testimg;
+open FILE, ">$testimg" or die "$testimg: $!";
+truncate FILE, 1024*1024*1024 or die "$testimg: truncate: $!";
+close FILE or die "$testimg: $!";
+
+my $g = Sys::Guestfs->new ();
+
+$g->add_drive_opts ($testimg, format => "raw");
+$g->launch ();
+
+# If btrfs is not available, bail.
+unless (feature_available ($g, "btrfs")) {
+ warn "$0: skipping test because btrfs is not available\n";
+ exit 0;
+}
+
+$g->part_disk ("/dev/sda", "mbr");
+
+$g->mkfs_btrfs (["/dev/sda1"]);
+$g->mount ("/dev/sda1", "/");
+
+$g->btrfs_subvolume_create ("/test1");
+$g->mkdir ("/test1/foo");
+$g->btrfs_subvolume_create ("/test2");
+
+my @vols = $g->btrfs_subvolume_list ("/");
+
+# Check the subvolume list, and extract the subvolume ID of path 'test1',
+# and the top level ID (which should be the same for both subvolumes).
+die ("expected 2 subvolumes, but got ", 0+@vols, " instead") unless @vols == 2;
+
+my %ids;
+my $top_level_id;
+foreach (@vols) {
+ my $path = $_->{btrfssubvolume_path};
+ my $id = $_->{btrfssubvolume_id};
+ my $top = $_->{btrfssubvolume_top_level_id};
+
+ if (!defined $top_level_id) {
+ $top_level_id = $top;
+ } elsif ($top_level_id != $top) {
+ die "top_level_id fields are not all the same";
+ }
+
+ $ids{$path} = $id;
+}
+
+die "no subvolume path 'test1' found" unless exists $ids{test1};
+
+my $test1_id = $ids{test1};
+
+$g->btrfs_subvolume_set_default ($test1_id, "/");
+$g->umount ("/");
+$g->mount ("/dev/sda1", "/");
+# This was originally /test1/foo, but now that we changed the
+# default ID to 'test1', /test1 is mounted as /, so:
+$g->mkdir ("/foo/bar");
+
+$g->btrfs_subvolume_set_default ($top_level_id, "/");
+$g->umount ("/");
+$g->mount ("/dev/sda1", "/");
+# Now we're back to the original default volume, so this should work:
+$g->mkdir ("/test1/foo/bar/baz");
+
+$g->close ();
+
+unlink $testimg or die "$testimg: unlink: $!";