diff --git a/ChangeLog b/ChangeLog index 179174ca6f..b675cadedb 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,11 @@ +Wed Feb 20 10:32:27 EST 2008 Daniel P. Berrange + + * 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 * Makefile.maint: Add virStorageReportError to locale check rule diff --git a/configure.in b/configure.in index 9155c77fcc..15c91aba31 100644 --- a/configure.in +++ b/configure.in @@ -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([]) diff --git a/libvirt.spec.in b/libvirt.spec.in index 6481bb75a4..15f38c2c32 100644 --- a/libvirt.spec.in +++ b/libvirt.spec.in @@ -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 diff --git a/src/Makefile.am b/src/Makefile.am index 7c06f0363a..f9750e9e11 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -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 --git a/src/storage_backend.c b/src/storage_backend.c index 254d37f43d..b350b753c0 100644 --- a/src/storage_backend.c +++ b/src/storage_backend.c @@ -28,6 +28,14 @@ #include #include #include +#include +#include +#include +#include + +#if HAVE_SELINUX +#include +#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 --git a/src/storage_backend.h b/src/storage_backend.h index 0ad51526c1..1533e7e6be 100644 --- a/src/storage_backend.h +++ b/src/storage_backend.h @@ -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__ */ diff --git a/tests/Makefile.am b/tests/Makefile.am index 72d31a35f4..fb9bcff583 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -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 \