--- /dev/null
+/* 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 ();
+}
--- /dev/null
+#!/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
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