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
This commit is contained in:
Richard W.M. Jones
2012-04-25 16:35:12 +01:00
parent 3cc9703f90
commit 87ea7a0409
13 changed files with 678 additions and 1 deletions

View File

@@ -42,6 +42,7 @@ SUBDIRS += tests/lvm
SUBDIRS += tests/luks
SUBDIRS += tests/md
SUBDIRS += tests/ntfsclone
SUBDIRS += tests/btrfs
SUBDIRS += tests/regressions
endif

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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

View File

@@ -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 \

View File

@@ -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 \

View File

@@ -1,4 +1,5 @@
Application.java
BTRFSSubvolume.java
Dirent.java
INotifyEvent.java
ISOInfo.java

View File

@@ -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

View File

@@ -1 +1 @@
321
330

31
tests/btrfs/Makefile.am Normal file
View File

@@ -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)

View File

@@ -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

View File

@@ -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: $!";