From d2cf9a15a9f22623dbbed33fb66c5077f1275df2 Mon Sep 17 00:00:00 2001 From: Richard Jones Date: Fri, 16 Jul 2010 13:01:21 +0100 Subject: [PATCH] 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. --- .gitignore | 1 + daemon/Makefile.am | 1 + daemon/lvm-filter.c | 244 ++++++++++++++++++++++++++++++++++++++ po/POTFILES.in | 1 + regressions/Makefile.am | 1 + regressions/test-lvm-filtering.sh | 92 ++++++++++++++ src/MAX_PROC_NR | 2 +- src/generator.ml | 41 +++++++ 8 files changed, 382 insertions(+), 1 deletion(-) create mode 100644 daemon/lvm-filter.c create mode 100755 regressions/test-lvm-filtering.sh diff --git a/.gitignore b/.gitignore index 15b496a..5583737 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/daemon/Makefile.am b/daemon/Makefile.am index 3fa2b31..cf9f7ca 100644 --- a/daemon/Makefile.am +++ b/daemon/Makefile.am @@ -99,6 +99,7 @@ guestfsd_SOURCES = \ link.c \ ls.c \ lvm.c \ + lvm-filter.c \ mkfs.c \ mknod.c \ modprobe.c \ diff --git a/daemon/lvm-filter.c b/daemon/lvm-filter.c new file mode 100644 index 0000000..e487c6b --- /dev/null +++ b/daemon/lvm-filter.c @@ -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 + +#include +#include +#include +#include +#include + +#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 (); +} diff --git a/po/POTFILES.in b/po/POTFILES.in index bb53a76..62df1fd 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -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 diff --git a/regressions/Makefile.am b/regressions/Makefile.am index 385de45..ff00321 100644 --- a/regressions/Makefile.am +++ b/regressions/Makefile.am @@ -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 \ diff --git a/regressions/test-lvm-filtering.sh b/regressions/test-lvm-filtering.sh new file mode 100755 index 0000000..d7c4e7c --- /dev/null +++ b/regressions/test-lvm-filtering.sh @@ -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 diff --git a/src/MAX_PROC_NR b/src/MAX_PROC_NR index f1aaa90..9183bf0 100644 --- a/src/MAX_PROC_NR +++ b/src/MAX_PROC_NR @@ -1 +1 @@ -254 +256 diff --git a/src/generator.ml b/src/generator.ml index d427558..6ef85fd 100755 --- a/src/generator.ml +++ b/src/generator.ml @@ -4832,6 +4832,47 @@ C. 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, +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. 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 -- 1.8.3.1