[Libvir] PATCH: 10/16: general purpose helper APIs
Daniel P. Berrange
berrange at redhat.com
Wed Feb 20 04:03:22 UTC 2008
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 -=|
More information about the libvir-list
mailing list