[libvirt] [PATCH 1/5] Enhance the streams helper to support plain file I/O

Eric Blake eblake at redhat.com
Tue Mar 15 15:53:40 UTC 2011


On 03/15/2011 06:30 AM, Daniel P. Berrange wrote:
> The O_NONBLOCK flag doesn't work as desired on plain files
> or block devices. Introduce an I/O helper program that does
> the blocking I/O operations, communicating over a pipe that
> can support O_NONBLOCK
> 
> * src/fdstream.c, src/fdstream.h: Add non-blocking I/O
>   on plain files/block devices
> * src/Makefile.am, src/util/iohelper.c: I/O helper program
> * src/qemu/qemu_driver.c, src/lxc/lxc_driver.c,
>   src/uml/uml_driver.c, src/xen/xen_driver.c: Update for
>   streams API change

> @@ -206,6 +212,25 @@ static int virFDStreamFree(struct virFDStreamData *fdst)
>  {
>      int ret;
>      ret = VIR_CLOSE(fdst->fd);
> +    if (fdst->cmd) {
> +        int status;
> +        if (virCommandWait(fdst->cmd, &status) < 0)
> +            ret = -1;
> +        if (status != 0) {
> +            ssize_t len = 1024;
> +            char buf[len];
> +            if ((len = saferead(fdst->errfd, buf, len)) < 0) {

Is this safe to read from errfd after you've already waited for the
child to exit?  After all, exit() is allowed to discard unread data from
a pipe, and once the child is exited, the parent may get EOF instead of
that data.  Conversely, can the child block trying to write into errfd,
and thus never reach the exit, so that you have a deadlock where the
parent is waiting without reading?  I think you have to read errfd prior
to calling virCommandWait.


> -    if ((fd  = open(path, flags)) < 0) {
> +    if (flags & O_CREAT)
> +        fd = open(path, flags, mode);
> +    else
> +        fd = open(path, flags);

Technically, it's safe to call fd = open(path, flags, mode) even when
flags does not contain O_CREAT, but I'm fine with keeping this if statement.

> +        const char *opname;
> +        int childfd;
> +        char offsetstr[100];
> +        char lengthstr[100];
> +
> +        snprintf(offsetstr, sizeof(offsetstr), "%llu", offset);
> +        snprintf(lengthstr, sizeof(lengthstr), "%llu", length);

100 is too big.  Use this for a tighter bound:

#include "intprops.h"

char offsetstr[INT_BUFSIZE_BOUND(offset)];

But see below for an alternate suggestion that loses these variables
altogether.

> +
> +        if (flags == O_RDONLY) {
> +            opname = "read";
> +        } else if (flags == O_WRONLY) {
> +            opname = "write";
> +        } else if (flags == (O_WRONLY|O_CREAT)) {
> +            opname = "create";
> +        } else {
> +            streamsReportError(VIR_ERR_INTERNAL_ERROR,
> +                               _("Non-blocking I/O is not supported on %s with flags %d"),

Okay, so you insist on a specific subset of flags.  But what about flags
like O_CLOEXEC?  Should you only be comparing against the bitmask of
flags that you actually care about, while allowing other flags as
needed?  Also, should you support O_EXCL?  I guess we can add support
for more flags at the point where we add more clients that want to use
those flags.

> +        cmd = virCommandNewArgList(LIBEXECDIR "/libvirt_iohelper",
> +                                   path,
> +                                   opname,
> +                                   offsetstr,
> +                                   lengthstr,
> +                                   NULL);

Rather than computing offsetstr and lengthstr into temporary strings
yourself, why not:

      cmd = virCommandNewArgList(LIBEXECDIR "/libvirt_iohelper",
                                 path,
                                 opname,
                                 NULL);
      virCommandAddArgFormat(cmd, "%llu", offset);
      virCommandAddArgFormat(cmd, "%llu", length);

> +
> +        if (!cmd)
> +            goto error;
> +
> +        //virCommandDaemonize(cmd);
> +        if (flags == O_RDONLY) {
> +            childfd = fds[1];
> +            fd = fds[0];
> +            virCommandSetOutputFD(cmd, &childfd);
> +        } else {
> +            childfd = fds[0];
> +            fd = fds[1];
> +            virCommandSetInputFD(cmd, childfd);

Should we also be setting FD_CLOEXEC on the side of the pipe not given
to the child?  (I really need to find time to work on O_CLOEXEC support
in gnulib, then do an audit over all of libvirt to take advantage of
atomic CLOEXEC support by using things like pipe2).

> +    } else {
> +#if 0
> +        if ((flags & O_CREAT) && length != 0) {
> +            if (ftruncate(fd, length) < 0) {

This almost sounds like you want to honor O_TRUNC as one of the
supported flags, rather than twisting O_CREAT.  It's in an #if 0; what
were your thoughts in coding this if we aren't going to use it?
> +++ b/src/qemu/qemu_driver.c
> @@ -4775,6 +4775,12 @@ static int qemuDomainGetMemoryParameters(virDomainPtr dom,
>          goto cleanup;
>      }
>  
> +    if (!virDomainObjIsActive (vm)) {
> +        qemuReportError(VIR_ERR_OPERATION_INVALID,
> +                        "%s", _("domain is not running"));
> +        goto cleanup;
> +    }

This hunk should be in a separate patch.

> @@ -4788,6 +4794,24 @@ static int qemuDomainGetMemoryParameters(virDomainPtr dom,
>          goto cleanup;
>      }
>  
> +    if (!virDomainObjIsActive (vm)) {
> +        qemuReportError(VIR_ERR_OPERATION_INVALID,
> +                        "%s", _("domain is not running"));
> +        goto cleanup;
> +    }
> +
> +    if (!virDomainObjIsActive (vm)) {
> +        qemuReportError(VIR_ERR_OPERATION_INVALID,
> +                        "%s", _("domain is not running"));
> +        goto cleanup;
> +    }
> +
> +    if (!virDomainObjIsActive (vm)) {
> +        qemuReportError(VIR_ERR_OPERATION_INVALID,
> +                        "%s", _("domain is not running"));
> +        goto cleanup;
> +    }

Yikes!  Rebase problems?

> +static int runIO(const char *path,
> +                 int flags,
> +                 int mode,
> +                 int otherfd,
> +                 const char *otherfdname,
> +                 unsigned long long offset,
> +                 unsigned long long length)
> +{
> +    char *buf = NULL;
> +    size_t buflen = 1024*1024;
> +    int fd;
> +    int ret = -1;
> +    int fdin, fdout;
> +    const char *fdinname, *fdoutname;
> +
> +    if (flags & O_CREAT) {
> +        fd = open(path, flags, mode);
> +    } else {
> +        fd = open(path, flags);
> +    }

Same comment about not strictly needing the if.

> +    if (fd < 0) {
> +        virReportSystemError(errno, _("Unable to open %s"), path);
> +        goto cleanup;
> +    }
> +
> +#if 0
> +    if (length && (flags & O_CREAT)) {
> +        if (ftruncate(fd, length) < 0) {

Same comment about O_TRUNC support.

> +
> +    if (argc != 5) {
> +        fprintf(stderr, _("%s: syntax FILENAME OPERATION OFFSET LENGTH\n"), argv[0]);
> +        exit(EXIT_FAILURE);
> +    }

Do we want --help/--version support (and a man page), or is this program
considered internal enough to not need the public-facing documentation?

> +    } else if (STREQ(op, "create")) {
> +        flags = O_WRONLY|O_CREAT;
> +        mode = 0644;

Hard-coded mode?

-- 
Eric Blake   eblake at redhat.com    +1-801-349-2682
Libvirt virtualization library http://libvirt.org

-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 619 bytes
Desc: OpenPGP digital signature
URL: <http://listman.redhat.com/archives/libvir-list/attachments/20110315/ad3ed07d/attachment-0001.sig>


More information about the libvir-list mailing list