New APIs: lvm-set-filter and lvm-clear-filter.
authorRichard Jones <rjones@redhat.com>
Fri, 16 Jul 2010 12:01:21 +0000 (13:01 +0100)
committerRichard Jones <rjones@redhat.com>
Fri, 16 Jul 2010 14:49:50 +0000 (15:49 +0100)
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
daemon/Makefile.am
daemon/lvm-filter.c [new file with mode: 0644]
po/POTFILES.in
regressions/Makefile.am
regressions/test-lvm-filtering.sh [new file with mode: 0755]
src/MAX_PROC_NR
src/generator.ml

index 15b496a..5583737 100644 (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
index 3fa2b31..cf9f7ca 100644 (file)
@@ -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 (file)
index 0000000..e487c6b
--- /dev/null
@@ -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 ();
+}
index bb53a76..62df1fd 100644 (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
index 385de45..ff00321 100644 (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 \
diff --git a/regressions/test-lvm-filtering.sh b/regressions/test-lvm-filtering.sh
new file mode 100755 (executable)
index 0000000..d7c4e7c
--- /dev/null
@@ -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
index f1aaa90..9183bf0 100644 (file)
@@ -1 +1 @@
-254
+256
index d427558..6ef85fd 100755 (executable)
@@ -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