summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRichard Jones <rjones@redhat.com>2010-07-16 13:01:21 +0100
committerRichard Jones <rjones@redhat.com>2010-07-16 15:49:50 +0100
commitd2cf9a15a9f22623dbbed33fb66c5077f1275df2 (patch)
tree53dff5d18deb2180df1adb2c010341c5907b1b0c
parent321ca1ef91a90cec5b94058b84420e8018e3f1d8 (diff)
downloadlibguestfs-d2cf9a15a9f22623dbbed33fb66c5077f1275df2.tar.gz
libguestfs-d2cf9a15a9f22623dbbed33fb66c5077f1275df2.tar.xz
libguestfs-d2cf9a15a9f22623dbbed33fb66c5077f1275df2.zip
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.
-rw-r--r--.gitignore1
-rw-r--r--daemon/Makefile.am1
-rw-r--r--daemon/lvm-filter.c244
-rw-r--r--po/POTFILES.in1
-rw-r--r--regressions/Makefile.am1
-rwxr-xr-xregressions/test-lvm-filtering.sh92
-rw-r--r--src/MAX_PROC_NR2
-rwxr-xr-xsrc/generator.ml41
8 files changed, 382 insertions, 1 deletions
diff --git a/.gitignore b/.gitignore
index 15b496a7..55837373 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 3fa2b319..cf9f7ca3 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 00000000..e487c6be
--- /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 <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 ();
+}
diff --git a/po/POTFILES.in b/po/POTFILES.in
index bb53a76b..62df1fd5 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 385de458..ff003214 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 00000000..d7c4e7c3
--- /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 f1aaa905..9183bf03 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 d427558c..6ef85fd6 100755
--- a/src/generator.ml
+++ b/src/generator.ml
@@ -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