New APIs: lvm-set-filter and lvm-clear-filter.

These APIs allow you to change the device filter, the list of
block devices that LVM "sees".  Either you can set it to a fixed
list of devices / partitions, or you can clear it so that LVM sees
everything.
This commit is contained in:
Richard Jones
2010-07-16 13:01:21 +01:00
parent 321ca1ef91
commit d2cf9a15a9
8 changed files with 382 additions and 1 deletions

1
.gitignore vendored
View File

@@ -201,6 +201,7 @@ python/guestfs-py.c
python/guestfs.pyc
regressions/rhbz501893
regressions/test1.img
regressions/test2.img
regressions/test.err
regressions/test.out
ruby/bindtests.rb

View File

@@ -99,6 +99,7 @@ guestfsd_SOURCES = \
link.c \
ls.c \
lvm.c \
lvm-filter.c \
mkfs.c \
mknod.c \
modprobe.c \

244
daemon/lvm-filter.c Normal file
View File

@@ -0,0 +1,244 @@
/* libguestfs - the guestfsd daemon
* Copyright (C) 2010 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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
#include <string.h>
#include <unistd.h>
#include "daemon.h"
#include "c-ctype.h"
#include "actions.h"
/* Does the current line match the regexp /^\s*filter\s*=/ */
static int
is_filter_line (const char *line)
{
while (*line && c_isspace (*line))
line++;
if (!*line)
return 0;
if (! STRPREFIX (line, "filter"))
return 0;
line += 6;
while (*line && c_isspace (*line))
line++;
if (!*line)
return 0;
if (*line != '=')
return 0;
return 1;
}
/* Rewrite the 'filter = [ ... ]' line in /etc/lvm/lvm.conf. */
static int
set_filter (const char *filter)
{
if (verbose)
fprintf (stderr, "LVM: setting device filter to %s\n", filter);
FILE *ifp = fopen ("/etc/lvm/lvm.conf", "r");
if (ifp == NULL) {
reply_with_perror ("open: /etc/lvm/lvm.conf");
return -1;
}
FILE *ofp = fopen ("/etc/lvm/lvm.conf.new", "w");
if (ofp == NULL) {
reply_with_perror ("open: /etc/lvm/lvm.conf.new");
fclose (ifp);
return -1;
}
char *line = NULL;
size_t len = 0;
while (getline (&line, &len, ifp) != -1) {
int r;
if (is_filter_line (line)) {
if (verbose)
fprintf (stderr, "LVM: replacing config line:\n%s", line);
r = fprintf (ofp, " filter = [ %s ]\n", filter);
} else {
r = fprintf (ofp, "%s", line);
}
if (r < 0) {
/* NB. fprintf doesn't set errno on error. */
reply_with_error ("/etc/lvm/lvm.conf.new: write failed");
fclose (ifp);
fclose (ofp);
free (line);
unlink ("/etc/lvm/lvm.conf.new");
return -1;
}
}
free (line);
if (fclose (ifp) == EOF) {
reply_with_perror ("/etc/lvm/lvm.conf.new");
unlink ("/etc/lvm/lvm.conf.new");
fclose (ofp);
return -1;
}
if (fclose (ofp) == EOF) {
reply_with_perror ("/etc/lvm/lvm.conf.new");
unlink ("/etc/lvm/lvm.conf.new");
return -1;
}
if (rename ("/etc/lvm/lvm.conf.new", "/etc/lvm/lvm.conf") == -1) {
reply_with_perror ("rename: /etc/lvm/lvm.conf");
unlink ("/etc/lvm/lvm.conf.new");
return -1;
}
return 0;
}
static int
vgchange (const char *vgchange_flag)
{
char *err;
int r = command (NULL, &err, "lvm", "vgchange", vgchange_flag, NULL);
if (r == -1) {
reply_with_error ("vgscan: %s", err);
free (err);
return -1;
}
free (err);
return 0;
}
/* Deactivate all VGs. */
static int
deactivate (void)
{
return vgchange ("-an");
}
/* Reactivate all VGs. */
static int
reactivate (void)
{
return vgchange ("-ay");
}
/* Clear the cache and rescan. */
static int
rescan (void)
{
unlink ("/etc/lvm/cache/.cache");
char *err;
int r = command (NULL, &err, "lvm", "vgscan", NULL);
if (r == -1) {
reply_with_error ("vgscan: %s", err);
free (err);
return -1;
}
free (err);
return 0;
}
/* Construct the new, specific filter string. We can assume that
* the 'devices' array does not contain any regexp metachars,
* because it's already been checked by the stub code.
*/
static char *
make_filter_string (char *const *devices)
{
size_t i;
size_t len = 64;
for (i = 0; devices[i] != NULL; ++i)
len += strlen (devices[i]) + 16;
char *filter = malloc (len);
if (filter == NULL) {
reply_with_perror ("malloc");
return NULL;
}
char *p = filter;
for (i = 0; devices[i] != NULL; ++i) {
/* Because of the way matching works in LVM, each match clause
* should be either:
* "a|^/dev/sda|", for whole block devices, or
* "a|^/dev/sda1$|", for single partitions
* (the assumption being we have <= 26 block devices XXX).
*/
size_t slen = strlen (devices[i]);
char str[slen+16];
if (c_isdigit (devices[i][slen-1]))
snprintf (str, slen+16, "\"a|^%s$|\", ", devices[i]);
else
snprintf (str, slen+16, "\"a|^%s|\", ", devices[i]);
strcpy (p, str);
p += strlen (str);
}
strcpy (p, "\"r|.*|\"");
return filter; /* Caller must free. */
}
int
do_lvm_set_filter (char *const *devices)
{
char *filter = make_filter_string (devices);
if (filter == NULL)
return -1;
if (deactivate () == -1) {
free (filter);
return -1;
}
int r = set_filter (filter);
free (filter);
if (r == -1)
return -1;
if (rescan () == -1)
return -1;
return reactivate ();
}
int
do_lvm_clear_filter (void)
{
if (deactivate () == -1)
return -1;
if (set_filter ("\"a/.*/\"") == -1)
return -1;
if (rescan () == -1)
return -1;
return reactivate ();
}

View File

@@ -33,6 +33,7 @@ daemon/initrd.c
daemon/inotify.c
daemon/link.c
daemon/ls.c
daemon/lvm-filter.c
daemon/lvm.c
daemon/mkfs.c
daemon/mknod.c

View File

@@ -34,6 +34,7 @@ TESTS = \
test-cancellation-download-librarycancels.sh \
test-cancellation-upload-daemoncancels.sh \
test-find0.sh \
test-lvm-filtering.sh \
test-lvm-mapping.pl \
test-noexec-stack.pl \
test-qemudie-killsub.sh \

View File

@@ -0,0 +1,92 @@
#!/bin/bash -
# libguestfs
# Copyright (C) 2010 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., 675 Mass Ave, Cambridge, MA 02139, USA.
# Test LVM device filtering.
set -e
rm -f test1.img test2.img
actual=$(../fish/guestfish <<'EOF'
sparse test1.img 1G
sparse test2.img 1G
run
part-disk /dev/sda mbr
part-disk /dev/sdb mbr
pvcreate /dev/sda1
pvcreate /dev/sdb1
vgcreate VG1 /dev/sda1
vgcreate VG2 /dev/sdb1
# Should see VG1 and VG2
vgs
# Should see just VG1
lvm-set-filter /dev/sda
vgs
lvm-set-filter /dev/sda1
vgs
# Should see just VG2
lvm-set-filter /dev/sdb
vgs
lvm-set-filter /dev/sdb1
vgs
# Should see VG1 and VG2
lvm-set-filter "/dev/sda /dev/sdb"
vgs
lvm-set-filter "/dev/sda1 /dev/sdb1"
vgs
lvm-set-filter "/dev/sda /dev/sdb1"
vgs
lvm-set-filter "/dev/sda1 /dev/sdb"
vgs
lvm-clear-filter
vgs
EOF
)
expected="VG1
VG2
VG1
VG1
VG2
VG2
VG1
VG2
VG1
VG2
VG1
VG2
VG1
VG2
VG1
VG2"
rm -f test1.img test2.img
if [ "$actual" != "$expected" ]; then
echo "LVM filter test failed. Actual output was:"
echo "$actual"
exit 1
fi

View File

@@ -1 +1 @@
254
256

View File

@@ -4832,6 +4832,47 @@ C<device>.
If the filesystem does not have a UUID, this returns the empty string.");
("lvm_set_filter", (RErr, [DeviceList "devices"]), 255, [Optional "lvm2"],
(* Can't be tested with the current framework because
* the VG is being used by the mounted filesystem, so
* the vgchange -an command we do first will fail.
*)
[],
"set LVM device filter",
"\
This sets the LVM device filter so that LVM will only be
able to \"see\" the block devices in the list C<devices>,
and will ignore all other attached block devices.
Where disk image(s) contain duplicate PVs or VGs, this
command is useful to get LVM to ignore the duplicates, otherwise
LVM can get confused. Note also there are two types
of duplication possible: either cloned PVs/VGs which have
identical UUIDs; or VGs that are not cloned but just happen
to have the same name. In normal operation you cannot
create this situation, but you can do it outside LVM, eg.
by cloning disk images or by bit twiddling inside the LVM
metadata.
This command also clears the LVM cache and performs a volume
group scan.
You can filter whole block devices or individual partitions.
You cannot use this if any VG is currently in use (eg.
contains a mounted filesystem), even if you are not
filtering out that VG.");
("lvm_clear_filter", (RErr, []), 256, [],
[], (* see note on lvm_set_filter *)
"clear LVM device filter",
"\
This undoes the effect of C<guestfs_lvm_set_filter>. LVM
will be able to see every block device.
This command also clears the LVM cache and performs a volume
group scan.");
]
let all_functions = non_daemon_functions @ daemon_functions