[libvirt] [PATCHv2] Make domain save work on root-squash NFS

Daniel Veillard veillard at redhat.com
Wed Mar 3 16:14:13 UTC 2010


On Mon, Mar 01, 2010 at 11:13:26AM -0500, Laine Stump wrote:
> Move *all* file operations related to creation and writing of libvirt
> header to the domain save file into a hook function that is called by
> virFileOperation. First try to call virFileOperation as root. If that
> fails with EACCESS, and (in the case of Linux) statfs says that we're
> trying to save the file on an NFS share, rerun virFileOperation,
> telling it to fork a child process and setuid to the qemu user. This
> is the only way we can successfully create a file on a root-squashed
> NFS server.
> 
> This patch (along with setting dynamic_ownership=0 in qemu.conf)
> makes qemudDomainSave work on root-squashed NFS.
> ---
> 
> Note that this version of the patch avoids a problem the original had
> with NFS shares whose directories aren't even readable by root - in
> those cases, statfs would fail, so we would never learn that the file
> was on NFS, and just fail the entire operation. The solution is to
> repeatedly call statfs on a smaller and smaller part of the full path
> until it succeeds - this will at most continue until the mount point
> of the share, which will properly report the fstype for the share,
> rather than for the mount point itself.
> 
>  src/qemu/qemu_driver.c |  165 ++++++++++++++++++++++++++++++++++++++++--------
>  1 files changed, 139 insertions(+), 26 deletions(-)
> 
> diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c
> index 1e4b493..da92cf3 100644
> --- a/src/qemu/qemu_driver.c
> +++ b/src/qemu/qemu_driver.c
> @@ -47,6 +47,11 @@
>  #include <sys/ioctl.h>
>  #include <sys/un.h>
>  
> +#ifdef __linux__
> +#include <sys/vfs.h>
> +#include <linux/magic.h>
> +#endif
> +
>  #include "virterror_internal.h"
>  #include "logging.h"
>  #include "datatypes.h"
> @@ -3956,14 +3961,44 @@ struct qemud_save_header {
>      int unused[15];
>  };
>  
> +struct fileOpHookData {
> +    virDomainPtr dom;
> +    const char *path;
> +    char *xml;
> +    struct qemud_save_header *header;
> +};
> +
> +static int qemudDomainSaveFileOpHook(int fd, void *data) {
> +    struct fileOpHookData *hdata = data;
> +    int ret = 0;
> +
> +    if (safewrite(fd, hdata->header, sizeof(*hdata->header)) != sizeof(*hdata->header)) {
> +        ret = errno;
> +        qemuReportError(VIR_ERR_OPERATION_FAILED,
> +                         _("failed to write save header to '%s'"), hdata->path);
> +        goto endjob;
> +    }
> +
> +    if (safewrite(fd, hdata->xml, hdata->header->xml_len) != hdata->header->xml_len) {
> +        ret = errno;
> +        qemuReportError(VIR_ERR_OPERATION_FAILED,
> +                         _("failed to write xml to '%s'"), hdata->path);
> +        goto endjob;
> +    }
> +endjob:
> +    return ret;
> +}
> +
> +
>  static int qemudDomainSave(virDomainPtr dom,
>                             const char *path)
>  {
>      struct qemud_driver *driver = dom->conn->privateData;
>      virDomainObjPtr vm = NULL;
> -    int fd = -1;
>      char *xml = NULL;
>      struct qemud_save_header header;
> +    struct fileOpHookData hdata;
> +    int bypassSecurityDriver = 0;
>      int ret = -1;
>      int rc;
>      virDomainEventPtr event = NULL;
> @@ -4027,34 +4062,113 @@ static int qemudDomainSave(virDomainPtr dom,
>      }
>      header.xml_len = strlen(xml) + 1;
>  
> +    /* Setup hook data needed by virFileOperation hook function */
> +    hdata.dom = dom;
> +    hdata.path = path;
> +    hdata.xml = xml;
> +    hdata.header = &header;
> +
>      /* Write header to file, followed by XML */
> -    if ((fd = open(path, O_CREAT|O_TRUNC|O_WRONLY, S_IRUSR|S_IWUSR)) < 0) {
> -        qemuReportError(VIR_ERR_OPERATION_FAILED,
> -                        _("failed to create '%s'"), path);
> -        goto endjob;
> -    }
>  
> -    if (safewrite(fd, &header, sizeof(header)) != sizeof(header)) {
> -        qemuReportError(VIR_ERR_OPERATION_FAILED,
> -                        "%s", _("failed to write save header"));
> -        goto endjob;
> -    }
> +    /* First try creating the file as root */
> +    if ((rc = virFileOperation(path, O_CREAT|O_TRUNC|O_WRONLY,
> +                               S_IRUSR|S_IWUSR,
> +                               getuid(), getgid(),
> +                               qemudDomainSaveFileOpHook, &hdata,
> +                               0)) != 0) {
> +
> +        /* If we failed as root, and the error was permission-denied
> +           (EACCES), assume it's on a network-connected share where
> +           root access is restricted (eg, root-squashed NFS). If the
> +           qemu user (driver->user) is non-root, just set a flag to
> +           bypass security driver shenanigans, and retry the operation
> +           after doing setuid to qemu user */
> +
> +        if ((rc != EACCES) ||
> +            driver->user == getuid()) {
> +            virReportSystemError(rc, _("Failed to create domain save file '%s'"),
> +                                 path);
> +            goto endjob;
> +        }
>  
> -    if (safewrite(fd, xml, header.xml_len) != header.xml_len) {
> -        qemuReportError(VIR_ERR_OPERATION_FAILED,
> -                         "%s", _("failed to write xml"));
> -        goto endjob;
> -    }
> +#ifdef __linux__
> +        /* On Linux we can also verify the FS-type of the directory. */
> +        char *dirpath, *p;
> +        struct statfs st;
> +        int statfs_ret;
>  
> -    if (close(fd) < 0) {
> -        virReportSystemError(errno,
> -                             _("unable to save file %s"),
> -                             path);
> -        goto endjob;
> +        if ((dirpath = strdup(path)) == NULL) {
> +            virReportOOMError();
> +            goto endjob;
> +        }
> +
> +        do {
> +            // Try less and less of the path until we get to a
> +            // directory we can stat. Even if we don't have 'x'
> +            // permission on any directory in the path on the NFS
> +            // server (assuming it's NFS), we will be able to stat the
> +            // mount point, and that will properly tell us if the
> +            // fstype is NFS.
> +
> +            if ((p = strrchr(dirpath, '/')) == NULL) {
> +                qemuReportError(VIR_ERR_INVALID_ARG,
> +                                _("Invalid relative path '%s' for domain save file"),
> +                                path);
> +                VIR_FREE(dirpath);
> +                goto endjob;
> +            }
> +
> +            if (p == dirpath)
> +                *(p+1) = '\0';
> +            else
> +                *p = '\0';
> +
> +            statfs_ret = statfs(dirpath, &st);
> +
> +        } while ((statfs_ret == -1) && (p != dirpath));
> +
> +        if (statfs_ret == -1) {
> +            virReportSystemError(errno,
> +                                 _("Failed to create domain save file '%s'"
> +                                   " statfs of all elements of path failed."),
> +                                 path);
> +            VIR_FREE(dirpath);
> +            goto endjob;
> +        }
> +
> +        if (st.f_type != NFS_SUPER_MAGIC) {
> +            virReportSystemError(rc,
> +                                 _("Failed to create domain save file '%s'"
> +                                   " (fstype of '%s' is 0x%X"),
> +                                 path, dirpath, st.f_type);
> +            VIR_FREE(dirpath);
> +            goto endjob;
> +        }
> +        VIR_FREE(dirpath);
> +#endif
> +
> +        /* Retry creating the file as driver->user */
> +
> +        if ((rc = virFileOperation(path, O_CREAT|O_TRUNC|O_WRONLY,
> +                                   S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP,
> +                                   driver->user, driver->group,
> +                                   qemudDomainSaveFileOpHook, &hdata,
> +                                   VIR_FILE_OP_AS_UID)) != 0) {
> +            virReportSystemError(rc, _("Error from child process creating '%s'"),
> +                                 path);
> +            goto endjob;
> +        }
> +
> +        /* Since we had to setuid to create the file, and the fstype
> +           is NFS, we assume it's a root-squashing NFS share, and that
> +           the security driver stuff would have failed anyway */
> +
> +        bypassSecurityDriver = 1;
>      }
> -    fd = -1;
>  
> -    if (driver->securityDriver &&
> +
> +    if ((!bypassSecurityDriver) &&
> +        driver->securityDriver &&
>          driver->securityDriver->domainSetSavedStateLabel &&
>          driver->securityDriver->domainSetSavedStateLabel(vm, path) == -1)
>          goto endjob;
> @@ -4081,7 +4195,8 @@ static int qemudDomainSave(virDomainPtr dom,
>      if (rc < 0)
>          goto endjob;
>  
> -    if (driver->securityDriver &&
> +    if ((!bypassSecurityDriver) &&
> +        driver->securityDriver &&
>          driver->securityDriver->domainRestoreSavedStateLabel &&
>          driver->securityDriver->domainRestoreSavedStateLabel(vm, path) == -1)
>          goto endjob;
> @@ -4106,8 +4221,6 @@ endjob:
>          vm = NULL;
>  
>  cleanup:
> -    if (fd != -1)
> -        close(fd);
>      VIR_FREE(xml);
>      if (ret != 0)
>          unlink(path);

  ACK, we need this one too for root-squash NFS save, I just rebased it
a bit, the cleanup part of the patch and the virReportSystemError
raised a small cast problem for %X,

  Pushed too,

   thanks !

Daniel

-- 
Daniel Veillard      | libxml Gnome XML XSLT toolkit  http://xmlsoft.org/
daniel at veillard.com  | Rpmfind RPM search engine http://rpmfind.net/
http://veillard.com/ | virtualization library  http://libvirt.org/




More information about the libvir-list mailing list