On Tue, Feb 12, 2008 at 04:37:08AM +0000, Daniel P. Berrange wrote:
This patch provides a couple of helper APIs used by the forthcoming
driver backends.
- virStorageBackendUpdateVolInfo - take a filename and virStorageVolPtr
and update the capacity/allocation information based on the file
stat() results. Also update the permissions data on owner/mode
and SELinux label.
- virStorageBackendUpdateVolInfoFD - same as above but with a pre-opened
filehandle
- virStorageBackendStablePath - given a /dev/XXX node, and a directory
of symlinks (eg /dev/disk/by-path), attempts to find a symlink pointing
to the desired file.
- virStorageBackendRunProgRegex - given one or more regexes and a command
line argv[], execute the program and attempt to match its STDOUT against
the regex. When complete matches are found invoke a callback.
- virStorageBackendRunProgNul - given a command line argv[] and an expected
number of fields per record, tokenize the STDOUT into records splitting
on NULL, and invoke the callback per record.
The SELinux code is optional, and can be replaced with calls to any other
library which can provide MAC file labels, or just disabled completely..
configure.in | 39 ++++
libvirt.spec.in | 1
src/Makefile.am | 3
src/storage_backend.c | 470 ++++++++++++++++++++++++++++++++++++++++++++++++++
src/storage_backend.h | 38 ++++
tests/Makefile.am | 2
6 files changed, 552 insertions(+), 1 deletion(-)
diff -r afe06dcc2b64 configure.in
--- a/configure.in Tue Feb 19 16:59:10 2008 -0500
+++ b/configure.in Tue Feb 19 17:04:59 2008 -0500
@@ -473,6 +473,40 @@ AC_SUBST(AVAHI_CFLAGS)
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 @@ else
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([])
diff -r afe06dcc2b64 libvirt.spec.in
--- a/libvirt.spec.in Tue Feb 19 16:59:10 2008 -0500
+++ b/libvirt.spec.in Tue Feb 19 17:04:59 2008 -0500
@@ -41,6 +41,7 @@ BuildRequires: gettext
BuildRequires: gettext
BuildRequires: gnutls-devel
BuildRequires: avahi-devel
+BuildRequires: libselinux-devel
BuildRequires: dnsmasq
BuildRequires: bridge-utils
BuildRequires: qemu
diff -r afe06dcc2b64 src/Makefile.am
--- a/src/Makefile.am Tue Feb 19 16:59:10 2008 -0500
+++ b/src/Makefile.am Tue Feb 19 17:04:59 2008 -0500
@@ -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@ \
diff -r afe06dcc2b64 src/storage_backend.c
--- a/src/storage_backend.c Tue Feb 19 16:59:10 2008 -0500
+++ b/src/storage_backend.c Tue Feb 19 17:04:59 2008 -0500
@@ -28,6 +28,14 @@
#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(®[i], regex[i], REG_EXTENDED);
+ if (err != 0) {
+ char error[100];
+ regerror(err, ®[i], error, sizeof(error));
+ virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR,
+ _("Failed to compile regex %s"), error);
+ for (j = 0 ; j <= i ; j++)
+ regfree(®[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(®[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(®[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:
diff -r afe06dcc2b64 src/storage_backend.h
--- a/src/storage_backend.h Tue Feb 19 16:59:10 2008 -0500
+++ b/src/storage_backend.h Tue Feb 19 17:04:59 2008 -0500
@@ -103,6 +103,44 @@ int virStorageBackendFromString(const ch
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__ */
diff -r afe06dcc2b64 tests/Makefile.am
--- a/tests/Makefile.am Tue Feb 19 16:59:10 2008 -0500
+++ b/tests/Makefile.am Tue Feb 19 17:04:59 2008 -0500
@@ -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 \
--
|=- Red Hat, Engineering, Emerging Technologies, Boston. +1 978 392 2496 -=|
|=- Perl modules:
http://search.cpan.org/~danberr/ -=|
|=- Projects:
http://freshmeat.net/~danielpb/ -=|
|=- GnuPG: 7D3B9505 F3C9 553F A1DA 4AC2 5648 23C1 B3DF F742 7D3B 9505 -=|