From 87ea7a04094d5ed40f3f047ff2b7a613d4d530d4 Mon Sep 17 00:00:00 2001 From: "Richard W.M. Jones" Date: Wed, 25 Apr 2012 16:35:12 +0100 Subject: [PATCH] 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 --- Makefile.am | 1 + configure.ac | 1 + daemon/btrfs.c | 396 ++++++++++++++++++++ generator/generator_actions.ml | 83 ++++ generator/generator_structs.ml | 8 + gobject/Makefile.inc | 2 + java/Makefile.inc | 1 + java/com/redhat/et/libguestfs/.gitignore | 1 + po/POTFILES.in | 1 + src/MAX_PROC_NR | 2 +- tests/btrfs/Makefile.am | 31 ++ tests/btrfs/test-btrfs-devices.sh | 57 +++ tests/btrfs/test-btrfs-subvolume-default.pl | 95 +++++ 13 files changed, 678 insertions(+), 1 deletion(-) create mode 100644 tests/btrfs/Makefile.am create mode 100755 tests/btrfs/test-btrfs-devices.sh create mode 100755 tests/btrfs/test-btrfs-subvolume-default.pl diff --git a/Makefile.am b/Makefile.am index 329998081..ae0ea3ff6 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 e16d6c8cc..fe603717b 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 60c863a47..8569173ef 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 " is the subvolume ID. "top level " is the top level + * subvolume ID. "path " 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 56af327ab..c237061a8 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."); + ("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. +The C argument is the destination directory and the name +of the snapshot, in the form C."); + + ("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 argument is the destination +directory and the name of the snapshot, in the form C."); + + ("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."); + + ("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 which will +be mounted by default. See 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."); + + ("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 +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 to the btrfs filesystem +mounted at C. If C 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 from the btrfs filesystem mounted at C. +If C 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 8c862ae3b..024bb3cda 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 ac65cd37a..69bb44bb3 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 64c6dcc21..efad2a0c8 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 c7cc3020f..9556d8169 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 01d260ab5..c5a87e5d0 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 3ae0b938f..db2cef56d 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 000000000..6d789ba73 --- /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 000000000..546ce7c57 --- /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 <$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: $!";