]> xenbits.xensource.com Git - libvirt.git/commitdiff
Added storage backend helper APIs
authorDaniel P. Berrange <berrange@redhat.com>
Wed, 20 Feb 2008 15:38:29 +0000 (15:38 +0000)
committerDaniel P. Berrange <berrange@redhat.com>
Wed, 20 Feb 2008 15:38:29 +0000 (15:38 +0000)
ChangeLog
configure.in
libvirt.spec.in
src/Makefile.am
src/storage_backend.c
src/storage_backend.h
tests/Makefile.am

index 179174ca6fcc8e8f0bc68d84acab5ffb90f03c12..b675cadedbccab86185aeaaf70179f8924dc9179 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,11 @@
+Wed Feb 20 10:32:27 EST 2008 Daniel P. Berrange <berrange@redhat.com>
+
+       * configure.in: Add check for selinux library
+       * libvirt.spec.in: Add BuildRequires on libselinux-devel
+       * src/Makefile.am, tests/Makefile.am: Add selinux build flags
+       * src/storage_backend.c, src/storage_backend.h: Add some
+       helper routines for storage backend impls
+
 Wed Feb 20 10:26:27 EST 2008 Daniel P. Berrange <berrange@redhat.com>
 
        * Makefile.maint: Add virStorageReportError to locale check rule
index 9155c77fccf5868a72db4170bfca02b0b4767efe..15c91aba315dbb496e7ef2dcc51aedb9cc41ac5d 100644 (file)
@@ -473,6 +473,40 @@ AM_CONDITIONAL(HAVE_AVAHI, [test "x$with_avahi" = "xyes"])
 AC_SUBST(AVAHI_CFLAGS)
 AC_SUBST(AVAHI_LIBS)
 
+dnl SELinux
+AC_ARG_WITH(selinux,
+  [  --with-selinux         use SELinux to manage security],
+  [],
+  [with_selinux=check])
+
+SELINUX_CFLAGS=
+SELINUX_LIBS=
+if test "$with_selinux" != "no"; then
+  old_cflags="$CFLAGS"
+  old_libs="$LIBS"
+  if test "$with_selinux" = "check"; then
+    AC_CHECK_HEADER([selinux/selinux.h],[],[with_selinux=no])
+    AC_CHECK_LIB(selinux, fgetfilecon,[],[with_selinux=no])
+    if test "$with_selinux" != "no"; then
+      with_selinux="yes"
+    fi
+  else
+    AC_CHECK_HEADER([selinux/selinux.h],[],
+       [AC_MSG_ERROR([You must install the SELinux development package in order to compile libvirt])])
+    AC_CHECK_LIB(selinux, fgetfilecon,[],
+       [AC_MSG_ERROR([You must install the SELinux development package in order to compile and run libvirt])])
+  fi
+  CFLAGS="$old_cflags"
+  LIBS="$old_libs"
+fi
+if test "$with_selinux" = "yes"; then
+  SELINUX_LIBS="-lselinux"
+  AC_DEFINE_UNQUOTED(HAVE_SELINUX, 1, [whether SELinux is available for security])
+fi
+AM_CONDITIONAL(HAVE_SELINUX, [test "$with_selinux" != "no"])
+AC_SUBST(SELINUX_CFLAGS)
+AC_SUBST(SELINUX_LIBS)
+
 dnl virsh libraries
 AC_CHECK_HEADERS([readline/readline.h])
 
@@ -745,6 +779,11 @@ AC_MSG_NOTICE([  polkit: $POLKIT_CFLAGS $POLKIT_LIBS])
 else
 AC_MSG_NOTICE([  polkit: no])
 fi
+if test "$with_selinux" = "yes" ; then
+AC_MSG_NOTICE([  selinux: $SELINUX_CFLAGS $SELINUX_LIBS])
+else
+AC_MSG_NOTICE([  selinux: no])
+fi
 AC_MSG_NOTICE([])
 AC_MSG_NOTICE([Miscellaneous])
 AC_MSG_NOTICE([])
index 6481bb75a4c0e6dbef6964414a1638dc2b2c474d..15f38c2c325edc2a0ebdcc180ab27e833886196a 100644 (file)
@@ -41,6 +41,7 @@ BuildRequires: ncurses-devel
 BuildRequires: gettext
 BuildRequires: gnutls-devel
 BuildRequires: avahi-devel
+BuildRequires: libselinux-devel
 BuildRequires: dnsmasq
 BuildRequires: bridge-utils
 BuildRequires: qemu
index 7c06f0363ada969cca9f7a03cbc10b3e55fb6972..f9750e9e11bbb06d4c3df8215eddb20d1ecb9183 100644 (file)
@@ -8,6 +8,7 @@ INCLUDES = \
           $(LIBXML_CFLAGS) \
           $(GNUTLS_CFLAGS) \
           $(SASL_CFLAGS) \
+          $(SELINUX_CFLAGS) \
           -DBINDIR=\""$(libexecdir)"\" \
           -DSBINDIR=\""$(sbindir)"\" \
           -DSYSCONF_DIR="\"$(sysconfdir)\"" \
@@ -67,7 +68,7 @@ SERVER_SOURCES =                                              \
                ../qemud/remote_protocol.c ../qemud/remote_protocol.h
 
 libvirt_la_SOURCES = $(CLIENT_SOURCES) $(SERVER_SOURCES)
-libvirt_la_LIBADD = $(LIBXML_LIBS) $(GNUTLS_LIBS) $(SASL_LIBS) \
+libvirt_la_LIBADD = $(LIBXML_LIBS) $(GNUTLS_LIBS) $(SASL_LIBS) $(SELINUX_LIBS) \
                    @CYGWIN_EXTRA_LIBADD@ ../gnulib/lib/libgnu.la
 libvirt_la_LDFLAGS = -Wl,--version-script=$(srcdir)/libvirt_sym.version \
                      -version-info @LIBVIRT_VERSION_INFO@ \
index 254d37f43dfa2b4564696da517feaa4345d13f15..b350b753c095437c2f9bc2c6a2de37b61a6d49df 100644 (file)
 #include <sys/types.h>
 #include <sys/wait.h>
 #include <unistd.h>
+#include <fcntl.h>
+#include <stdint.h>
+#include <sys/stat.h>
+#include <dirent.h>
+
+#if HAVE_SELINUX
+#include <selinux/selinux.h>
+#endif
 
 #include "util.h"
 
@@ -73,6 +81,468 @@ virStorageBackendToString(int type) {
 }
 
 
+int
+virStorageBackendUpdateVolInfo(virConnectPtr conn,
+                               virStorageVolDefPtr vol,
+                               int withCapacity)
+{
+    int ret, fd;
+
+    if ((fd = open(vol->target.path, O_RDONLY)) < 0) {
+        virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR,
+                              _("cannot open volume '%s': %s"),
+                              vol->target.path, strerror(errno));
+        return -1;
+    }
+
+    ret = virStorageBackendUpdateVolInfoFD(conn,
+                                           vol,
+                                           fd,
+                                           withCapacity);
+
+    close(fd);
+
+    return ret;
+}
+
+int
+virStorageBackendUpdateVolInfoFD(virConnectPtr conn,
+                                 virStorageVolDefPtr vol,
+                                 int fd,
+                                 int withCapacity)
+{
+    struct stat sb;
+#if HAVE_SELINUX
+    security_context_t filecon = NULL;
+#endif
+
+    if (fstat(fd, &sb) < 0) {
+        virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR,
+                              _("cannot stat file '%s': %s"),
+                              vol->target.path, strerror(errno));
+        return -1;
+    }
+
+    if (!S_ISREG(sb.st_mode) &&
+        !S_ISCHR(sb.st_mode) &&
+        !S_ISBLK(sb.st_mode))
+        return -2;
+
+    if (S_ISREG(sb.st_mode)) {
+        vol->allocation = (unsigned long long)sb.st_blocks *
+            (unsigned long long)sb.st_blksize;
+        /* Regular files may be sparse, so logical size (capacity) is not same
+         * as actual allocation above
+         */
+        if (withCapacity)
+            vol->capacity = sb.st_size;
+    } else {
+        off_t end;
+        /* XXX this is POSIX compliant, but doesn't work for for CHAR files,
+         * only BLOCK. There is a Linux specific ioctl() for getting
+         * size of both CHAR / BLOCK devices we should check for in
+         * configure
+         */
+        end = lseek(fd, 0, SEEK_END);
+        if (end == (off_t)-1) {
+            virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR,
+                                  _("cannot seek to end of file '%s':%s"),
+                                  vol->target.path, strerror(errno));
+            return -1;
+        }
+        vol->allocation = end;
+        if (withCapacity) vol->capacity = end;
+    }
+
+    vol->target.perms.mode = sb.st_mode;
+    vol->target.perms.uid = sb.st_uid;
+    vol->target.perms.gid = sb.st_gid;
+
+    free(vol->target.perms.label);
+    vol->target.perms.label = NULL;
+
+#if HAVE_SELINUX
+    if (fgetfilecon(fd, &filecon) == -1) {
+        virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR,
+                              _("cannot get file context of %s: %s"),
+                              vol->target.path, strerror(errno));
+        return -1;
+    }
+    vol->target.perms.label = strdup(filecon);
+    if (vol->target.perms.label == NULL) {
+        virStorageReportError(conn, VIR_ERR_NO_MEMORY, _("context"));
+        return -1;
+    }
+    freecon(filecon);
+#else
+    vol->target.perms.label = NULL;
+#endif
+
+    return 0;
+}
+
+/*
+ * Given a volume path directly in /dev/XXX, iterate over the
+ * entries in the directory pool->def->target.path and find the
+ * first symlink pointing to the volume path.
+ *
+ * If, the target.path is /dev/, then return the original volume
+ * path.
+ *
+ * If no symlink is found, then return the original volume path
+ *
+ * Typically target.path is one of the /dev/disk/by-XXX dirs
+ * with stable paths.
+ */
+char *
+virStorageBackendStablePath(virConnectPtr conn,
+                            virStoragePoolObjPtr pool,
+                            char *devpath)
+{
+    DIR *dh;
+    struct dirent *dent;
+
+    /* Short circuit if pool has no target, or if its /dev */
+    if (pool->def->target.path == NULL ||
+        STREQ(pool->def->target.path, "/dev") ||
+        STREQ(pool->def->target.path, "/dev/"))
+        return devpath;
+
+    /* The pool is pointing somewhere like /dev/disk/by-path
+     * or /dev/disk/by-id, so we need to check all symlinks in
+     * the target directory and figure out which one points
+     * to this device node
+     */
+    if ((dh = opendir(pool->def->target.path)) == NULL) {
+        virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR,
+                              _("cannot read dir %s: %s"),
+                              pool->def->target.path,
+                              strerror(errno));
+        return NULL;
+    }
+
+    while ((dent = readdir(dh)) != NULL) {
+        char *stablepath;
+        if (dent->d_name[0] == '.')
+            continue;
+
+        stablepath = malloc(strlen(pool->def->target.path) +
+                            1 + strlen(dent->d_name) + 1);
+        if (stablepath == NULL) {
+            virStorageReportError(conn, VIR_ERR_NO_MEMORY, _("path"));
+            closedir(dh);
+            return NULL;
+        }
+
+        strcpy(stablepath, pool->def->target.path);
+        strcat(stablepath, "/");
+        strcat(stablepath, dent->d_name);
+
+        if (virFileLinkPointsTo(stablepath, devpath)) {
+            closedir(dh);
+            return stablepath;
+        }
+
+        free(stablepath);
+    }
+
+    closedir(dh);
+
+    /* Couldn't find any matching stable link so give back
+     * the original non-stable dev path
+     */
+    return devpath;
+}
+
+/*
+ * Run an external program.
+ *
+ * Read its output and apply a series of regexes to each line
+ * When the entire set of regexes has matched consequetively
+ * then run a callback passing in all the matches
+ */
+int
+virStorageBackendRunProgRegex(virConnectPtr conn,
+                              virStoragePoolObjPtr pool,
+                              const char **prog,
+                              int nregex,
+                              const char **regex,
+                              int *nvars,
+                              virStorageBackendListVolRegexFunc func,
+                              void *data)
+{
+    int child = 0, fd = -1, exitstatus, err, failed = 1;
+    FILE *list = NULL;
+    regex_t *reg;
+    regmatch_t *vars = NULL;
+    char line[1024];
+    int maxReg = 0, i, j;
+    int totgroups = 0, ngroup = 0, maxvars = 0;
+    char **groups;
+
+    /* Compile all regular expressions */
+    if ((reg = calloc(nregex, sizeof(*reg))) == NULL) {
+        virStorageReportError(conn, VIR_ERR_NO_MEMORY, _("regex"));
+        return -1;
+    }
+
+    for (i = 0 ; i < nregex ; i++) {
+        err = regcomp(&reg[i], regex[i], REG_EXTENDED);
+        if (err != 0) {
+            char error[100];
+            regerror(err, &reg[i], error, sizeof(error));
+            virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR,
+                                  _("Failed to compile regex %s"), error);
+            for (j = 0 ; j <= i ; j++)
+                regfree(&reg[j]);
+            free(reg);
+            return -1;
+        }
+
+        totgroups += nvars[i];
+        if (nvars[i] > maxvars)
+            maxvars = nvars[i];
+
+    }
+
+    /* Storage for matched variables */
+    if ((groups = calloc(totgroups, sizeof(*groups))) == NULL) {
+        virStorageReportError(conn, VIR_ERR_NO_MEMORY,
+                              _("regex groups"));
+        goto cleanup;
+    }
+    if ((vars = calloc(maxvars+1, sizeof(*vars))) == NULL) {
+        virStorageReportError(conn, VIR_ERR_NO_MEMORY,
+                              _("regex groups"));
+        goto cleanup;
+    }
+
+
+    /* Run the program and capture its output */
+    if (virExec(conn, (char**)prog, &child, -1, &fd, NULL) < 0) {
+        goto cleanup;
+    }
+
+    if ((list = fdopen(fd, "r")) == NULL) {
+        virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR,
+                              _("cannot read fd"));
+        goto cleanup;
+    }
+
+    while (fgets(line, sizeof(line), list) != NULL) {
+        /* Strip trailing newline */
+        int len = strlen(line);
+        if (len && line[len-1] == '\n')
+            line[len-1] = '\0';
+
+        for (i = 0 ; i <= maxReg && i < nregex ; i++) {
+            if (regexec(&reg[i], line, nvars[i]+1, vars, 0) == 0) {
+                maxReg++;
+
+                if (i == 0)
+                    ngroup = 0;
+
+                /* NULL terminate each captured group in the line */
+                for (j = 0 ; j < nvars[i] ; j++) {
+                    /* NB vars[0] is the full pattern, so we offset j by 1 */
+                    line[vars[j+1].rm_eo] = '\0';
+                    if ((groups[ngroup++] =
+                         strdup(line + vars[j+1].rm_so)) == NULL) {
+                        virStorageReportError(conn, VIR_ERR_NO_MEMORY,
+                                              _("regex groups"));
+                        goto cleanup;
+                    }
+                }
+
+                /* We're matching on the last regex, so callback time */
+                if (i == (nregex-1)) {
+                    if (((*func)(conn, pool, groups, data)) < 0)
+                        goto cleanup;
+
+                    /* Release matches & restart to matching the first regex */
+                    for (j = 0 ; j < totgroups ; j++) {
+                        free(groups[j]);
+                        groups[j] = NULL;
+                    }
+                    maxReg = 0;
+                    ngroup = 0;
+                }
+            }
+        }
+    }
+
+    failed = 0;
+
+ cleanup:
+    if (groups) {
+        for (j = 0 ; j < totgroups ; j++)
+            free(groups[j]);
+        free(groups);
+    }
+    free(vars);
+
+    for (i = 0 ; i < nregex ; i++)
+        regfree(&reg[i]);
+
+    free(reg);
+
+    if (list)
+        fclose(list);
+    else
+        close(fd);
+
+    while ((err = waitpid(child, &exitstatus, 0) == -1) && errno == EINTR);
+
+    /* Don't bother checking exit status if we already failed */
+    if (failed)
+        return -1;
+
+    if (err == -1) {
+        virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR,
+                              _("failed to wait for command: %s"),
+                              strerror(errno));
+        return -1;
+    } else {
+        if (WIFEXITED(exitstatus)) {
+            if (WEXITSTATUS(exitstatus) != 0) {
+                virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR,
+                                      _("non-zero exit status from command %d"),
+                                      WEXITSTATUS(exitstatus));
+                return -1;
+            }
+        } else {
+            virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR,
+                                  _("command did not exit cleanly"));
+            return -1;
+        }
+    }
+
+    return 0;
+}
+
+/*
+ * Run an external program and read from its standard output
+ * a stream of tokens from IN_STREAM, applying FUNC to
+ * each successive sequence of N_COLUMNS tokens.
+ * If FUNC returns < 0, stop processing input and return -1.
+ * Return -1 if N_COLUMNS == 0.
+ * Return -1 upon memory allocation error.
+ * If the number of input tokens is not a multiple of N_COLUMNS,
+ * then the final FUNC call will specify a number smaller than N_COLUMNS.
+ * If there are no input tokens (empty input), call FUNC with N_COLUMNS == 0.
+ */
+int
+virStorageBackendRunProgNul(virConnectPtr conn,
+                            virStoragePoolObjPtr pool,
+                            const char **prog,
+                            size_t n_columns,
+                            virStorageBackendListVolNulFunc func,
+                            void *data)
+{
+    size_t n_tok = 0;
+    int child = 0, fd = -1, exitstatus;
+    FILE *fp = NULL;
+    char **v;
+    int err = -1;
+    int w_err;
+    int i;
+
+    if (n_columns == 0)
+        return -1;
+
+    if (n_columns > SIZE_MAX / sizeof *v
+        || (v = malloc (n_columns * sizeof *v)) == NULL) {
+        virStorageReportError(conn, VIR_ERR_NO_MEMORY,
+                              _("n_columns too large"));
+        return -1;
+    }
+    for (i = 0; i < n_columns; i++)
+        v[i] = NULL;
+
+    /* Run the program and capture its output */
+    if (virExec(conn, (char**)prog, &child, -1, &fd, NULL) < 0) {
+        goto cleanup;
+    }
+
+    if ((fp = fdopen(fd, "r")) == NULL) {
+        virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR,
+                              _("cannot read fd"));
+        goto cleanup;
+    }
+
+    while (1) {
+        char *buf = NULL;
+        size_t buf_len = 0;
+        /* Be careful: even when it returns -1,
+           this use of getdelim allocates memory.  */
+        ssize_t tok_len = getdelim (&buf, &buf_len, 0, fp);
+        v[n_tok] = buf;
+        if (tok_len < 0) {
+            /* Maybe EOF, maybe an error.
+               If n_tok > 0, then we know it's an error.  */
+            if (n_tok && func (conn, pool, n_tok, v, data) < 0)
+                goto cleanup;
+            break;
+        }
+        ++n_tok;
+        if (n_tok == n_columns) {
+            if (func (conn, pool, n_tok, v, data) < 0)
+                goto cleanup;
+            n_tok = 0;
+            for (i = 0; i < n_columns; i++) {
+                free (v[i]);
+                v[i] = NULL;
+            }
+        }
+    }
+
+    if (feof (fp))
+        err = 0;
+    else
+        virStorageReportError (conn, VIR_ERR_INTERNAL_ERROR,
+                               _("read error: %s"), strerror (errno));
+
+ cleanup:
+    for (i = 0; i < n_columns; i++)
+        free (v[i]);
+    free (v);
+
+    if (fp)
+        fclose (fp);
+    else
+        close (fd);
+
+    while ((w_err = waitpid (child, &exitstatus, 0) == -1) && errno == EINTR)
+        /* empty */ ;
+
+    /* Don't bother checking exit status if we already failed */
+    if (err < 0)
+        return -1;
+
+    if (w_err == -1) {
+        virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR,
+                              _("failed to wait for command: %s"),
+                              strerror(errno));
+        return -1;
+    } else {
+        if (WIFEXITED(exitstatus)) {
+            if (WEXITSTATUS(exitstatus) != 0) {
+                virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR,
+                                      _("non-zero exit status from command %d"),
+                                      WEXITSTATUS(exitstatus));
+                return -1;
+            }
+        } else {
+            virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR,
+                                  _("command did not exit cleanly"));
+            return -1;
+        }
+    }
+
+    return 0;
+}
+
+
 /*
  * vim: set tabstop=4:
  * vim: set shiftwidth=4:
index 0ad51526c17af9e6b0d7b602d111ee1790ed55a9..1533e7e6be8ef26cb0b0c28eff07acdc19ba8f82 100644 (file)
@@ -103,6 +103,44 @@ virStorageBackendVolOptionsPtr virStorageBackendVolOptionsForType(int type);
 int virStorageBackendFromString(const char *type);
 const char *virStorageBackendToString(int type);
 
+int virStorageBackendUpdateVolInfo(virConnectPtr conn,
+                                   virStorageVolDefPtr vol,
+                                   int withCapacity);
+
+int virStorageBackendUpdateVolInfoFD(virConnectPtr conn,
+                                     virStorageVolDefPtr vol,
+                                     int fd,
+                                     int withCapacity);
+
+char *virStorageBackendStablePath(virConnectPtr conn,
+                                  virStoragePoolObjPtr pool,
+                                  char *devpath);
+
+typedef int (*virStorageBackendListVolRegexFunc)(virConnectPtr conn,
+                                                 virStoragePoolObjPtr pool,
+                                                 char **const groups,
+                                                 void *data);
+typedef int (*virStorageBackendListVolNulFunc)(virConnectPtr conn,
+                                               virStoragePoolObjPtr pool,
+                                               size_t n_tokens,
+                                               char **const groups,
+                                               void *data);
+
+int virStorageBackendRunProgRegex(virConnectPtr conn,
+                                  virStoragePoolObjPtr pool,
+                                  const char **prog,
+                                  int nregex,
+                                  const char **regex,
+                                  int *nvars,
+                                  virStorageBackendListVolRegexFunc func,
+                                  void *data);
+
+int virStorageBackendRunProgNul(virConnectPtr conn,
+                                virStoragePoolObjPtr pool,
+                                const char **prog,
+                                size_t n_columns,
+                                virStorageBackendListVolNulFunc func,
+                                void *data);
 
 #endif /* __VIR_STORAGE_BACKEND_H__ */
 
index 72d31a35f4eff9821e086598b52e210a872f38f1..fb9bcff58394bcefcc218a3702d32b66dab70b90 100644 (file)
@@ -20,6 +20,7 @@ INCLUDES = \
        $(LIBXML_CFLAGS) \
        $(GNUTLS_CFLAGS) \
        $(SASL_CFLAGS) \
+       $(SELINUX_CFLAGS) \
         -D_XOPEN_SOURCE=600 -D_POSIX_C_SOURCE=199506L \
         -DGETTEXT_PACKAGE=\"$(PACKAGE)\" \
          $(COVERAGE_CFLAGS) \
@@ -31,6 +32,7 @@ LDADDS = \
        $(LIBXML_LIBS) \
         $(GNUTLS_LIBS) \
         $(SASL_LIBS) \
+        $(SELINUX_LIBS) \
         $(WARN_CFLAGS) \
        $(LIBVIRT) \
        ../gnulib/lib/libgnu.la \