mirror of
https://gitlab.com/libvirt/libvirt.git
synced 2025-02-08 04:31:33 +00:00
command: improve behavior on no output
Guarantee that outbuf/errbuf are allocated on success, even if to the empty string. Caller always has to free the result, and empty output check requires checking if *outbuf=='\0'. Makes the API easier to use safely. Failure is best effort allocation (some paths, like out-of-memory, cannot allocate a buffer, but most do), so caller must free buffer on failure. * docs/internals/command.html.in: Update documentation. * src/util/command.c (virCommandSetOutputBuffer) (virCommandSetErrorBuffer, virCommandProcessIO) Guarantee empty string on no output. * tests/commandtest.c (test17): New test.
This commit is contained in:
parent
ee11729d7f
commit
cc5e2a849c
@ -365,9 +365,15 @@
|
|||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
Once the command has finished executing, these buffers
|
Once the command has finished executing, these buffers will
|
||||||
will contain the output. It is the callers responsibility
|
contain the output. Allocation is guaranteed if virCommandRun
|
||||||
to free these buffers.
|
or virCommandWait succeed (if there was no output, then the
|
||||||
|
buffer will contain an allocated empty string); if the command
|
||||||
|
failed, then the buffers usually contain a best-effort
|
||||||
|
allocation of collected information (however, on an
|
||||||
|
out-of-memory condition, the buffer may still be NULL). The
|
||||||
|
caller is responsible for freeing registered buffers, since the
|
||||||
|
buffers are designed to persist beyond virCommandFree.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h3><a name="directory">Setting working directory</a></h3>
|
<h3><a name="directory">Setting working directory</a></h3>
|
||||||
|
@ -533,11 +533,15 @@ virCommandSetInputBuffer(virCommandPtr cmd, const char *inbuf)
|
|||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Capture the child's stdout to a string buffer
|
* Capture the child's stdout to a string buffer. *outbuf is
|
||||||
|
* guaranteed to be allocated after successful virCommandRun or
|
||||||
|
* virCommandWait, and is best-effort allocated after failed
|
||||||
|
* virCommandRun; caller is responsible for freeing *outbuf.
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
virCommandSetOutputBuffer(virCommandPtr cmd, char **outbuf)
|
virCommandSetOutputBuffer(virCommandPtr cmd, char **outbuf)
|
||||||
{
|
{
|
||||||
|
*outbuf = NULL;
|
||||||
if (!cmd || cmd->has_error)
|
if (!cmd || cmd->has_error)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@ -553,11 +557,15 @@ virCommandSetOutputBuffer(virCommandPtr cmd, char **outbuf)
|
|||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Capture the child's stderr to a string buffer
|
* Capture the child's stderr to a string buffer. *errbuf is
|
||||||
|
* guaranteed to be allocated after successful virCommandRun or
|
||||||
|
* virCommandWait, and is best-effort allocated after failed
|
||||||
|
* virCommandRun; caller is responsible for freeing *errbuf.
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
virCommandSetErrorBuffer(virCommandPtr cmd, char **errbuf)
|
virCommandSetErrorBuffer(virCommandPtr cmd, char **errbuf)
|
||||||
{
|
{
|
||||||
|
*errbuf = NULL;
|
||||||
if (!cmd || cmd->has_error)
|
if (!cmd || cmd->has_error)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@ -747,6 +755,7 @@ virCommandProcessIO(virCommandPtr cmd)
|
|||||||
int infd = -1, outfd = -1, errfd = -1;
|
int infd = -1, outfd = -1, errfd = -1;
|
||||||
size_t inlen = 0, outlen = 0, errlen = 0;
|
size_t inlen = 0, outlen = 0, errlen = 0;
|
||||||
size_t inoff = 0;
|
size_t inoff = 0;
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
/* With an input buffer, feed data to child
|
/* With an input buffer, feed data to child
|
||||||
* via pipe */
|
* via pipe */
|
||||||
@ -755,12 +764,27 @@ virCommandProcessIO(virCommandPtr cmd)
|
|||||||
infd = cmd->inpipe;
|
infd = cmd->inpipe;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* With out/err buffer, the outfd/errfd
|
/* With out/err buffer, the outfd/errfd have been filled with an
|
||||||
* have been filled with an FD for us */
|
* FD for us. Guarantee an allocated string with partial results
|
||||||
if (cmd->outbuf)
|
* even if we encounter a later failure, as well as freeing any
|
||||||
|
* results accumulated over a prior run of the same command. */
|
||||||
|
if (cmd->outbuf) {
|
||||||
outfd = cmd->outfd;
|
outfd = cmd->outfd;
|
||||||
if (cmd->errbuf)
|
if (VIR_REALLOC_N(*cmd->outbuf, 1) < 0) {
|
||||||
|
virReportOOMError();
|
||||||
|
ret = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (cmd->errbuf) {
|
||||||
errfd = cmd->errfd;
|
errfd = cmd->errfd;
|
||||||
|
if (VIR_REALLOC_N(*cmd->errbuf, 1) < 0) {
|
||||||
|
virReportOOMError();
|
||||||
|
ret = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ret == -1)
|
||||||
|
goto cleanup;
|
||||||
|
ret = -1;
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
int i;
|
int i;
|
||||||
@ -791,7 +815,7 @@ virCommandProcessIO(virCommandPtr cmd)
|
|||||||
continue;
|
continue;
|
||||||
virReportSystemError(errno, "%s",
|
virReportSystemError(errno, "%s",
|
||||||
_("unable to poll on child"));
|
_("unable to poll on child"));
|
||||||
return -1;
|
goto cleanup;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (i = 0; i < nfds ; i++) {
|
for (i = 0; i < nfds ; i++) {
|
||||||
@ -815,7 +839,7 @@ virCommandProcessIO(virCommandPtr cmd)
|
|||||||
errno != EAGAIN) {
|
errno != EAGAIN) {
|
||||||
virReportSystemError(errno, "%s",
|
virReportSystemError(errno, "%s",
|
||||||
_("unable to write to child input"));
|
_("unable to write to child input"));
|
||||||
return -1;
|
goto cleanup;
|
||||||
}
|
}
|
||||||
} else if (done == 0) {
|
} else if (done == 0) {
|
||||||
if (fds[i].fd == outfd)
|
if (fds[i].fd == outfd)
|
||||||
@ -825,11 +849,10 @@ virCommandProcessIO(virCommandPtr cmd)
|
|||||||
} else {
|
} else {
|
||||||
if (VIR_REALLOC_N(*buf, *len + done + 1) < 0) {
|
if (VIR_REALLOC_N(*buf, *len + done + 1) < 0) {
|
||||||
virReportOOMError();
|
virReportOOMError();
|
||||||
return -1;
|
goto cleanup;
|
||||||
}
|
}
|
||||||
memcpy(*buf + *len, data, done);
|
memcpy(*buf + *len, data, done);
|
||||||
*len += done;
|
*len += done;
|
||||||
(*buf)[*len] = '\0';
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
int done;
|
int done;
|
||||||
@ -841,7 +864,7 @@ virCommandProcessIO(virCommandPtr cmd)
|
|||||||
errno != EAGAIN) {
|
errno != EAGAIN) {
|
||||||
virReportSystemError(errno, "%s",
|
virReportSystemError(errno, "%s",
|
||||||
_("unable to write to child input"));
|
_("unable to write to child input"));
|
||||||
return -1;
|
goto cleanup;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
inoff += done;
|
inoff += done;
|
||||||
@ -856,7 +879,13 @@ virCommandProcessIO(virCommandPtr cmd)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
ret = 0;
|
||||||
|
cleanup:
|
||||||
|
if (*cmd->outbuf)
|
||||||
|
(*cmd->outbuf)[outlen] = '\0';
|
||||||
|
if (*cmd->errbuf)
|
||||||
|
(*cmd->errbuf)[errlen] = '\0';
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -610,6 +610,63 @@ cleanup:
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Test string handling when no output is present.
|
||||||
|
*/
|
||||||
|
static int test17(const void *unused ATTRIBUTE_UNUSED)
|
||||||
|
{
|
||||||
|
virCommandPtr cmd = virCommandNew("/bin/true");
|
||||||
|
int ret = -1;
|
||||||
|
char *outbuf;
|
||||||
|
char *errbuf;
|
||||||
|
|
||||||
|
virCommandSetOutputBuffer(cmd, &outbuf);
|
||||||
|
if (outbuf != NULL) {
|
||||||
|
puts("buffer not sanitized at registration");
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (virCommandRun(cmd, NULL) < 0) {
|
||||||
|
virErrorPtr err = virGetLastError();
|
||||||
|
printf("Cannot run child %s\n", err->message);
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!outbuf || *outbuf) {
|
||||||
|
puts("output buffer is not an allocated empty string");
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
VIR_FREE(outbuf);
|
||||||
|
if ((outbuf = strdup("should not be leaked")) == NULL) {
|
||||||
|
puts("test framework failure");
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
virCommandSetErrorBuffer(cmd, &errbuf);
|
||||||
|
if (errbuf != NULL) {
|
||||||
|
puts("buffer not sanitized at registration");
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (virCommandRun(cmd, NULL) < 0) {
|
||||||
|
virErrorPtr err = virGetLastError();
|
||||||
|
printf("Cannot run child %s\n", err->message);
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!outbuf || *outbuf || !errbuf || *errbuf) {
|
||||||
|
puts("output buffers are not allocated empty strings");
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = 0;
|
||||||
|
cleanup:
|
||||||
|
virCommandFree(cmd);
|
||||||
|
VIR_FREE(outbuf);
|
||||||
|
VIR_FREE(errbuf);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
mymain(int argc, char **argv)
|
mymain(int argc, char **argv)
|
||||||
{
|
{
|
||||||
@ -673,6 +730,7 @@ mymain(int argc, char **argv)
|
|||||||
DO_TEST(test14);
|
DO_TEST(test14);
|
||||||
DO_TEST(test15);
|
DO_TEST(test15);
|
||||||
DO_TEST(test16);
|
DO_TEST(test16);
|
||||||
|
DO_TEST(test17);
|
||||||
|
|
||||||
return(ret==0 ? EXIT_SUCCESS : EXIT_FAILURE);
|
return(ret==0 ? EXIT_SUCCESS : EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user