[libvirt] [PATCH 00/10] virCommand

This revives a couple of patch series from the past, and rebases them on top of the current tree. I've tested that the daemon hook on SIGHUP works with this rewrite, so I'm fairly confident that it is in decent shape, but there's still a lot more to do for both converting VIR_REALLOC_N to newer VIR_EXPAND_N or VIR_RESIZE_N, as well as converting virExec uses to virCommand. I wanted to get this posted for first reviews while I continue porting more instances. Daniel P. Berrange (5): Fix bug in setting up child stderr/out with /dev/null Remove bogus includes Introduce new APIs for spawning processes virCommand: docs for usage of new command APIs Port hooks and iptables code to new command execution APIs Eric Blake (5): memory: make it safer to expand arrays memory: make it easier to avoid quadratic scaling of arrays daemon: use safer memory growth macros capabilities, cpu: use new array API maint: tighten strncmp syntax check .x-sc_avoid_write | 1 + .x-sc_prohibit_strcmp_and_strncmp | 9 - .x-sc_prohibit_strncmp | 1 + HACKING | 59 ++- Makefile.am | 2 +- cfg.mk | 15 +- daemon/event.c | 44 +- daemon/libvirtd.c | 8 +- daemon/libvirtd.h | 11 +- docs/Makefile.am | 11 +- docs/hacking.html.in | 64 ++- docs/internals.html.in | 9 + docs/internals/command.html.in | 491 +++++++++++++++++++ docs/sitemap.html.in | 4 + docs/subsite.xsl | 25 + po/POTFILES.in | 1 + src/Makefile.am | 1 + src/conf/capabilities.c | 37 +- src/conf/capabilities.h | 20 +- src/conf/cpu_conf.c | 7 +- src/conf/cpu_conf.h | 5 +- src/conf/domain_conf.c | 1 - src/libvirt_private.syms | 32 ++ src/util/command.c | 960 +++++++++++++++++++++++++++++++++++++ src/util/command.h | 213 ++++++++ src/util/hooks.c | 217 +-------- src/util/iptables.c | 73 +--- src/util/memory.c | 94 ++++- src/util/memory.h | 77 +++- src/util/util.c | 2 +- tests/.gitignore | 4 + tests/Makefile.am | 18 +- tests/commanddata/test10.log | 14 + tests/commanddata/test11.log | 14 + tests/commanddata/test12.log | 12 + tests/commanddata/test13.log | 12 + tests/commanddata/test14.log | 12 + tests/commanddata/test15.log | 12 + tests/commanddata/test2.log | 12 + tests/commanddata/test3.log | 14 + tests/commanddata/test4.log | 12 + tests/commanddata/test5.log | 10 + tests/commanddata/test6.log | 6 + tests/commanddata/test7.log | 11 + tests/commanddata/test8.log | 7 + tests/commanddata/test9.log | 18 + tests/commandhelper.c | 136 ++++++ tests/commandtest.c | 572 ++++++++++++++++++++++ tests/testutilsqemu.c | 1 + 49 files changed, 3009 insertions(+), 382 deletions(-) delete mode 100644 .x-sc_prohibit_strcmp_and_strncmp create mode 100644 .x-sc_prohibit_strncmp create mode 100644 docs/internals/command.html.in create mode 100644 docs/subsite.xsl create mode 100644 src/util/command.c create mode 100644 src/util/command.h create mode 100644 tests/commanddata/test10.log create mode 100644 tests/commanddata/test11.log create mode 100644 tests/commanddata/test12.log create mode 100644 tests/commanddata/test13.log create mode 100644 tests/commanddata/test14.log create mode 100644 tests/commanddata/test15.log create mode 100644 tests/commanddata/test2.log create mode 100644 tests/commanddata/test3.log create mode 100644 tests/commanddata/test4.log create mode 100644 tests/commanddata/test5.log create mode 100644 tests/commanddata/test6.log create mode 100644 tests/commanddata/test7.log create mode 100644 tests/commanddata/test8.log create mode 100644 tests/commanddata/test9.log create mode 100644 tests/commandhelper.c create mode 100644 tests/commandtest.c -- 1.7.3.2

* src/util/memory.h (VIR_REALLOC_N): Update docs. (VIR_EXPAND_N, VIR_SHRINK_N): New macros. (virAlloc, virAllocN, virReallocN, virAllocVar, virFree): Add some gcc attributes. * src/util/memory.c (virExpandN, virShrinkN): New functions. (virReallocN): Update docs. * docs/hacking.html.in: Prefer newer interfaces over VIR_REALLOC_N, since uninitialized memory can bite us. * HACKING: Regenerate. * src/libvirt_private.syms: Export new helpers. --- HACKING | 24 +++++++++++------- docs/hacking.html.in | 25 +++++++++++------- src/libvirt_private.syms | 2 + src/util/memory.c | 59 +++++++++++++++++++++++++++++++++++++++++++++- src/util/memory.h | 51 +++++++++++++++++++++++++++++++++++---- 5 files changed, 134 insertions(+), 27 deletions(-) diff --git a/HACKING b/HACKING index 17ad344..2b19fe4 100644 --- a/HACKING +++ b/HACKING @@ -280,9 +280,9 @@ Low level memory management Use of the malloc/free/realloc/calloc APIs is deprecated in the libvirt codebase, because they encourage a number of serious coding bugs and do not enable compile time verification of checks for NULL. Instead of these -routines, use the macros from memory.h +routines, use the macros from memory.h. -- e.g. to allocate a single object: +- To allocate a single object: virDomainPtr domain; @@ -293,10 +293,10 @@ routines, use the macros from memory.h -- e.g. to allocate an array of objects +- To allocate an array of objects: virDomainPtr domains; - int ndomains = 10; + size_t ndomains = 10; if (VIR_ALLOC_N(domains, ndomains) < 0) { virReportOOMError(); @@ -305,7 +305,7 @@ routines, use the macros from memory.h -- e.g. to allocate an array of object pointers +- To allocate an array of object pointers: virDomainPtr *domains; int ndomains = 10; @@ -317,18 +317,22 @@ routines, use the macros from memory.h -- e.g. to re-allocate the array of domains to be longer - - ndomains = 20 +- To re-allocate the array of domains to be longer: - if (VIR_REALLOC_N(domains, ndomains) < 0) { + if (VIR_EXPAND_N(domains, ndomains, 10) < 0) { virReportOOMError(); return NULL; } -- e.g. to free the domain +- To trim an array of domains to have one less element: + + VIR_SHRINK_N(domains, ndomains, 1); + + + +- To free the domain: VIR_FREE(domain); diff --git a/docs/hacking.html.in b/docs/hacking.html.in index e1b5185..ffe6aea 100644 --- a/docs/hacking.html.in +++ b/docs/hacking.html.in @@ -354,11 +354,12 @@ Use of the malloc/free/realloc/calloc APIs is deprecated in the libvirt codebase, because they encourage a number of serious coding bugs and do not enable compile time verification of checks for NULL. Instead of these - routines, use the macros from memory.h + routines, use the macros from memory.h. </p> <ul> - <li><p>e.g. to allocate a single object:</p> + <li><p>To allocate a single object:</p> + <pre> virDomainPtr domain; @@ -369,10 +370,10 @@ </pre> </li> - <li><p>e.g. to allocate an array of objects</p> + <li><p>To allocate an array of objects:</p> <pre> virDomainPtr domains; - int ndomains = 10; + size_t ndomains = 10; if (VIR_ALLOC_N(domains, ndomains) < 0) { virReportOOMError(); @@ -381,7 +382,7 @@ </pre> </li> - <li><p>e.g. to allocate an array of object pointers</p> + <li><p>To allocate an array of object pointers:</p> <pre> virDomainPtr *domains; int ndomains = 10; @@ -393,18 +394,22 @@ </pre> </li> - <li><p>e.g. to re-allocate the array of domains to be longer</p> + <li><p>To re-allocate the array of domains to be longer:</p> <pre> - ndomains = 20 - - if (VIR_REALLOC_N(domains, ndomains) < 0) { + if (VIR_EXPAND_N(domains, ndomains, 10) < 0) { virReportOOMError(); return NULL; } </pre> </li> - <li><p>e.g. to free the domain</p> + <li><p>To trim an array of domains to have one less element:</p> + +<pre> + VIR_SHRINK_N(domains, ndomains, 1); +</pre></li> + + <li><p>To free the domain:</p> <pre> VIR_FREE(domain); </pre> diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 50eceba..796559c 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -503,8 +503,10 @@ virLogUnlock; # memory.h virAlloc; virAllocN; +virExpandN; virFree; virReallocN; +virShrinkN; # network.h diff --git a/src/util/memory.c b/src/util/memory.c index dd1216b..59685b3 100644 --- a/src/util/memory.c +++ b/src/util/memory.c @@ -1,6 +1,7 @@ /* * memory.c: safer memory allocation * + * Copyright (C) 2010 Red Hat, Inc. * Copyright (C) 2008 Daniel P. Berrange * * This library is free software; you can redistribute it and/or @@ -24,6 +25,7 @@ #include <stddef.h> #include "memory.h" +#include "ignore-value.h" #if TEST_OOM @@ -141,7 +143,7 @@ int virAllocN(void *ptrptr, size_t size, size_t count) * 'count' elements, each 'size' bytes in length. Update 'ptrptr' * with the address of the newly allocated memory. On failure, * 'ptrptr' is not changed and still points to the original memory - * block. The newly allocated memory is filled with zeros. + * block. Any newly allocated memory in 'ptrptr' is uninitialized. * * Returns -1 on failure to allocate, zero on success */ @@ -165,6 +167,61 @@ int virReallocN(void *ptrptr, size_t size, size_t count) } /** + * virExpandN: + * @ptrptr: pointer to pointer for address of allocated memory + * @size: number of bytes per element + * @countptr: pointer to number of elements in array + * @add: number of elements to add + * + * Resize the block of memory in 'ptrptr' to be an array of + * '*countptr' + 'add' elements, each 'size' bytes in length. + * Update 'ptrptr' and 'countptr' with the details of the newly + * allocated memory. On failure, 'ptrptr' and 'countptr' are not + * changed. Any newly allocated memory in 'ptrptr' is zero-filled. + * + * Returns -1 on failure to allocate, zero on success + */ +int virExpandN(void *ptrptr, size_t size, size_t *countptr, size_t add) +{ + int ret; + + if (*countptr + add < *countptr) { + errno = ENOMEM; + return -1; + } + ret = virReallocN(ptrptr, size, *countptr + add); + if (ret == 0) { + memset(*(char **)ptrptr + (size * *countptr), 0, size * add); + *countptr += add; + } + return ret; +} + +/** + * virShrinkN: + * @ptrptr: pointer to pointer for address of allocated memory + * @size: number of bytes per element + * @countptr: pointer to number of elements in array + * @remove: number of elements to remove + * + * Resize the block of memory in 'ptrptr' to be an array of + * '*countptr' - 'remove' elements, each 'size' bytes in length. + * Update 'ptrptr' and 'countptr' with the details of the newly + * allocated memory. If 'remove' is larger than 'countptr', free + * the entire array. + */ +void virShrinkN(void *ptrptr, size_t size, size_t *countptr, size_t remove) +{ + if (remove < *countptr) + ignore_value(virReallocN(ptrptr, size, *countptr -= remove)); + else { + virFree(ptrptr); + *countptr = 0; + } +} + + +/** * Vir_Alloc_Var: * @ptrptr: pointer to hold address of allocated memory * @struct_size: size of initial struct diff --git a/src/util/memory.h b/src/util/memory.h index 60e2be6..98ac2b3 100644 --- a/src/util/memory.h +++ b/src/util/memory.h @@ -46,14 +46,21 @@ /* Don't call these directly - use the macros below */ -int virAlloc(void *ptrptr, size_t size) ATTRIBUTE_RETURN_CHECK; -int virAllocN(void *ptrptr, size_t size, size_t count) ATTRIBUTE_RETURN_CHECK; -int virReallocN(void *ptrptr, size_t size, size_t count) ATTRIBUTE_RETURN_CHECK; +int virAlloc(void *ptrptr, size_t size) ATTRIBUTE_RETURN_CHECK + ATTRIBUTE_NONNULL(1); +int virAllocN(void *ptrptr, size_t size, size_t count) ATTRIBUTE_RETURN_CHECK + ATTRIBUTE_NONNULL(1); +int virReallocN(void *ptrptr, size_t size, size_t count) ATTRIBUTE_RETURN_CHECK + ATTRIBUTE_NONNULL(1); +int virExpandN(void *ptrptr, size_t size, size_t *count, size_t add) + ATTRIBUTE_RETURN_CHECK ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(3); +void virShrinkN(void *ptrptr, size_t size, size_t *count, size_t remove) + ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(3); int virAllocVar(void *ptrptr, size_t struct_size, size_t element_size, - size_t count) ATTRIBUTE_RETURN_CHECK; -void virFree(void *ptrptr); + size_t count) ATTRIBUTE_RETURN_CHECK ATTRIBUTE_NONNULL(1); +void virFree(void *ptrptr) ATTRIBUTE_NONNULL(1); /** * VIR_ALLOC: @@ -87,12 +94,44 @@ void virFree(void *ptrptr); * * Re-allocate an array of 'count' elements, each sizeof(*ptr) * bytes long and store the address of allocated memory in - * 'ptr'. Fill the newly allocated memory with zeros + * 'ptr'. If 'ptr' grew, the added memory is uninitialized. * * Returns -1 on failure, 0 on success */ # define VIR_REALLOC_N(ptr, count) virReallocN(&(ptr), sizeof(*(ptr)), (count)) +/** + * VIR_EXPAND_N: + * @ptr: pointer to hold address of allocated memory + * @count: variable tracking number of elements currently allocated + * @add: number of elements to add + * + * Re-allocate an array of 'count' elements, each sizeof(*ptr) + * bytes long, to be 'count' + 'add' elements long, then store the + * address of allocated memory in 'ptr' and the new size in 'count'. + * The new elements are filled with zero. + * + * Returns -1 on failure, 0 on success + */ +# define VIR_EXPAND_N(ptr, count, add) \ + virExpandN(&(ptr), sizeof(*(ptr)), &(count), add) + +/** + * VIR_SHRINK_N: + * @ptr: pointer to hold address of allocated memory + * @count: variable tracking number of elements currently allocated + * @remove: number of elements to remove + * + * Re-allocate an array of 'count' elements, each sizeof(*ptr) + * bytes long, to be 'count' - 'remove' elements long, then store the + * address of allocated memory in 'ptr' and the new size in 'count'. + * If 'count' <= 'remove', the entire array is freed. + * + * No return value. + */ +# define VIR_SHRINK_N(ptr, count, remove) \ + virShrinkN(&(ptr), sizeof(*(ptr)), &(count), remove) + /* * VIR_ALLOC_VAR_OVERSIZED: * @M: size of base structure -- 1.7.3.2

On Wed, Nov 17, 2010 at 09:28:53PM -0700, Eric Blake wrote:
* src/util/memory.h (VIR_REALLOC_N): Update docs. (VIR_EXPAND_N, VIR_SHRINK_N): New macros. (virAlloc, virAllocN, virReallocN, virAllocVar, virFree): Add some gcc attributes. * src/util/memory.c (virExpandN, virShrinkN): New functions. (virReallocN): Update docs. * docs/hacking.html.in: Prefer newer interfaces over VIR_REALLOC_N, since uninitialized memory can bite us. * HACKING: Regenerate. * src/libvirt_private.syms: Export new helpers. --- HACKING | 24 +++++++++++------- docs/hacking.html.in | 25 +++++++++++------- src/libvirt_private.syms | 2 + src/util/memory.c | 59 +++++++++++++++++++++++++++++++++++++++++++++- src/util/memory.h | 51 +++++++++++++++++++++++++++++++++++---- 5 files changed, 134 insertions(+), 27 deletions(-)
ACK, was just wondering where this patch had been ! Daniel -- |: Red Hat, Engineering, London -o- http://people.redhat.com/berrange/ :| |: http://libvirt.org -o- http://virt-manager.org -o- http://deltacloud.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: GnuPG: 7D3B9505 -o- F3C9 553F A1DA 4AC2 5648 23C1 B3DF F742 7D3B 9505 :|

At 2010-11-18 12:28, Eric Blake Write:
* src/util/memory.h (VIR_REALLOC_N): Update docs. (VIR_EXPAND_N, VIR_SHRINK_N): New macros. (virAlloc, virAllocN, virReallocN, virAllocVar, virFree): Add some gcc attributes. * src/util/memory.c (virExpandN, virShrinkN): New functions. (virReallocN): Update docs. * docs/hacking.html.in: Prefer newer interfaces over VIR_REALLOC_N, since uninitialized memory can bite us. * HACKING: Regenerate. * src/libvirt_private.syms: Export new helpers. --- HACKING | 24 +++++++++++------- docs/hacking.html.in | 25 +++++++++++------- src/libvirt_private.syms | 2 + src/util/memory.c | 59 +++++++++++++++++++++++++++++++++++++++++++++- src/util/memory.h | 51 +++++++++++++++++++++++++++++++++++---- 5 files changed, 134 insertions(+), 27 deletions(-)
There may be a bug in this patch. When I use the newest libvirt, I find the following problem: [root@localhost ~]# service libvirtd status libvirtd (pid 4541) is running... [root@localhost ~]# virsh start wency_vm1 error: cannot recv data: : Connection reset by peer error: failed to connect to the hypervisor [root@localhost ~]# service libvirtd status libvirtd dead but pid file exists [root@localhost ~]# Test the libvirtd without --dameon, I find that: [root@localhost newest]# libvirtd Segmentation fault (core dumped) [root@localhost newest]# The folling is the output of the command 'gdb libvirtd core': [root@localhost newest]# gdb /usr/sbin/libvirtd core.8996 <snip> Core was generated by `libvirtd'. Program terminated with signal 11, Segmentation fault. #0 0x000000000041a181 in qemudDispatchServer (server=0x209dcd0, sock=<value optimized out>) at libvirtd.c:1459 1459 server->clients[server->nclients++] = client; <snip> (gdb) bt #0 0x000000000041a181 in qemudDispatchServer (server=0x209dcd0, sock=<value optimized out>) at libvirtd.c:1459 #1 0x000000000041a6f1 in qemudDispatchServerEvent (watch=5, fd=8, events=1, opaque=0x209dcd0) at libvirtd.c:2225 #2 0x0000000000415b71 in virEventDispatchHandles () at event.c:467 #3 virEventRunOnce () at event.c:592 #4 0x00000000004180e9 in qemudOneLoop () at libvirtd.c:2234 #5 0x00000000004183db in qemudRunLoop (opaque=0x209dcd0) at libvirtd.c:2343 #6 0x0000003ffec077e1 in start_thread () from /lib64/libpthread.so.0 #7 0x0000003ffe4e153d in clone () from /lib64/libc.so.6 (gdb) p server->clients $2 = (struct qemud_client **) 0x0 I revert this patch and rebuild libvirt, the bug is not appreared.

On 11/19/2010 12:33 AM, Wen Congyang wrote:
At 2010-11-18 12:28, Eric Blake Write:
* src/util/memory.h (VIR_REALLOC_N): Update docs. (VIR_EXPAND_N, VIR_SHRINK_N): New macros. (virAlloc, virAllocN, virReallocN, virAllocVar, virFree): Add some gcc attributes.
There may be a bug in this patch.
Well, it would be patch 3 that touched the file where your backtrace points, so it would be the overall patch series and not this patch (patch 1) to blame.
Test the libvirtd without --dameon, I find that: [root@localhost newest]# libvirtd Segmentation fault (core dumped) [root@localhost newest]#
The folling is the output of the command 'gdb libvirtd core': [root@localhost newest]# gdb /usr/sbin/libvirtd core.8996 <snip> Core was generated by `libvirtd'. Program terminated with signal 11, Segmentation fault. #0 0x000000000041a181 in qemudDispatchServer (server=0x209dcd0, sock=<value optimized out>) at libvirtd.c:1459 1459 server->clients[server->nclients++] = client; <snip> (gdb) bt #0 0x000000000041a181 in qemudDispatchServer (server=0x209dcd0, sock=<value optimized out>) at libvirtd.c:1459 #1 0x000000000041a6f1 in qemudDispatchServerEvent (watch=5, fd=8, events=1, opaque=0x209dcd0) at libvirtd.c:2225 #2 0x0000000000415b71 in virEventDispatchHandles () at event.c:467 #3 virEventRunOnce () at event.c:592 #4 0x00000000004180e9 in qemudOneLoop () at libvirtd.c:2234 #5 0x00000000004183db in qemudRunLoop (opaque=0x209dcd0) at libvirtd.c:2343 #6 0x0000003ffec077e1 in start_thread () from /lib64/libpthread.so.0 #7 0x0000003ffe4e153d in clone () from /lib64/libc.so.6 (gdb) p server->clients $2 = (struct qemud_client **) 0x0
I'm having problems reproducing this, and don't see any obvious explanations for this in the code. qemuDispatchServer has: if (server->nclients >= max_clients) { VIR_ERROR(_("Too many active clients (%d), dropping connection from %s"), max_clients, addrstr); goto error; } if (VIR_RESIZE_N(server->clients, server->nclients_max, server->nclients, 1) < 0) { VIR_ERROR0(_("Out of memory allocating clients")); goto error; } ... server->clients[server->nclients++] = client; so the only way to get to the crashing line is to get through a successful VIR_RESIZE_N, but VIR_RESIZE_N is not successful unless it updates server->clients to be non-NULL. Can you do any further debugging that might explain why it is failing for you, and something I might have missed? -- Eric Blake eblake@redhat.com +1-801-349-2682 Libvirt virtualization library http://libvirt.org

* daemon/libvirtd.c (qemudRunLoop): Pass allocation size, not current count, to VIR_SHRINK_N. --- Found the cause of the crash; when the first loop completed, it was freeing the array but not reflecting that in the allocation count; the second time then saw the non-zero allocation count and didn't think it had to allocate anything. daemon/libvirtd.c | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) diff --git a/daemon/libvirtd.c b/daemon/libvirtd.c index e544c48..7f75096 100644 --- a/daemon/libvirtd.c +++ b/daemon/libvirtd.c @@ -2362,2 +2362,2 @@ static void *qemudRunLoop(void *opaque) { server->clients + i + 1, sizeof (*server->clients) * (server->nclients - i)); - VIR_SHRINK_N(server->clients, server->nclients, 0); + VIR_SHRINK_N(server->clients, server->nclients_max, 1); goto reprocess; } } -- 1.7.3.2

At 2010年11月23日 03:34, Eric Blake Write:
* daemon/libvirtd.c (qemudRunLoop): Pass allocation size, not current count, to VIR_SHRINK_N. ---
Found the cause of the crash; when the first loop completed, it was freeing the array but not reflecting that in the allocation count; the second time then saw the non-zero allocation count and didn't think it had to allocate anything.
This patch seems fine to me.
daemon/libvirtd.c | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-)
diff --git a/daemon/libvirtd.c b/daemon/libvirtd.c index e544c48..7f75096 100644 --- a/daemon/libvirtd.c +++ b/daemon/libvirtd.c @@ -2362,2 +2362,2 @@ static void *qemudRunLoop(void *opaque) { server->clients + i + 1, sizeof (*server->clients) * (server->nclients - i));
- VIR_SHRINK_N(server->clients, server->nclients, 0); + VIR_SHRINK_N(server->clients, server->nclients_max, 1); goto reprocess; } }

On 11/22/2010 02:34 PM, Eric Blake wrote:
* daemon/libvirtd.c (qemudRunLoop): Pass allocation size, not current count, to VIR_SHRINK_N. ---
Found the cause of the crash; when the first loop completed, it was freeing the array but not reflecting that in the allocation count; the second time then saw the non-zero allocation count and didn't think it had to allocate anything.
daemon/libvirtd.c | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-)
diff --git a/daemon/libvirtd.c b/daemon/libvirtd.c index e544c48..7f75096 100644 --- a/daemon/libvirtd.c +++ b/daemon/libvirtd.c @@ -2362,2 +2362,2 @@ static void *qemudRunLoop(void *opaque) { server->clients + i + 1, sizeof (*server->clients) * (server->nclients - i));
- VIR_SHRINK_N(server->clients, server->nclients, 0); + VIR_SHRINK_N(server->clients, server->nclients_max, 1); goto reprocess; } } Solves the problem for me.
ACK. Stefan

On 11/23/2010 04:45 AM, Stefan Berger wrote:
On 11/22/2010 02:34 PM, Eric Blake wrote:
* daemon/libvirtd.c (qemudRunLoop): Pass allocation size, not current count, to VIR_SHRINK_N. ---
Found the cause of the crash; when the first loop completed, it was freeing the array but not reflecting that in the allocation count; the second time then saw the non-zero allocation count and didn't think it had to allocate anything.
daemon/libvirtd.c | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-)
diff --git a/daemon/libvirtd.c b/daemon/libvirtd.c index e544c48..7f75096 100644 --- a/daemon/libvirtd.c +++ b/daemon/libvirtd.c @@ -2362,2 +2362,2 @@ static void *qemudRunLoop(void *opaque) { server->clients + i + 1, sizeof (*server->clients) * (server->nclients - i));
- VIR_SHRINK_N(server->clients, server->nclients, 0); + VIR_SHRINK_N(server->clients, server->nclients_max, 1); goto reprocess; } } Solves the problem for me.
ACK.
Thanks. On reflection, I'm actually going to squash this in before pushing, since it is a more accurate description of what is going on. diff --git i/daemon/libvirtd.c w/daemon/libvirtd.c index aa2f6ec..66f1388 100644 --- i/daemon/libvirtd.c +++ w/daemon/libvirtd.c @@ -2362,7 +2362,8 @@ static void *qemudRunLoop(void *opaque) { server->clients + i + 1, sizeof (*server->clients) * (server->nclients - i)); - VIR_SHRINK_N(server->clients, server->nclients_max, 1); + VIR_SHRINK_N(server->clients, server->nclients_max, + server->nclients_max - server->nclients); goto reprocess; } } diff --git i/docs/hacking.html.in w/docs/hacking.html.in index 890692f..ac16f41 100644 --- i/docs/hacking.html.in +++ w/docs/hacking.html.in @@ -426,14 +426,15 @@ </pre> </li> - <li><p>To trim an array of domains to have one less element:</p> + <li><p>To trim an array of domains from its allocated size down + to the actual used size:</p> <pre> virDomainPtr domains; size_t ndomains = x; size_t ndomains_max = y; - VIR_SHRINK_N(domains, ndomains_max, 1); + VIR_SHRINK_N(domains, ndomains_max, ndomains_max - ndomains); </pre></li> <li><p>To free an array of domains:</p> -- Eric Blake eblake@redhat.com +1-801-349-2682 Libvirt virtualization library http://libvirt.org

Le 19/11/2010 08:33, Wen Congyang a écrit :
p server->clients Sorry for the noise, but it's appear, that i encounter the same problem, compiling libvirt with the latest git snapshot.
Libivrft is compiled with the following options: CC="gcc -ggdb -O1 -Wall" ./configure --build=x86_64-linux-gnu --prefix=/usr --includedir="\${prefix}/include" --mandir="\${prefix}/share/man" --infodir="\${prefix}/share/info" --sysconfdir=/etc --localstatedir=/var --libexecdir="\${prefix}/lib/libvirt" --disable-maintainer-mode --disable-dependency-tracking --disable-silent-rules --srcdir=. --disable-rpath --disable-strip --with-qemu --with-qemu-user=libvirt-qemu --with-qemu-group=kvm --without-openvz --without-avahi --without-sasl --without-polkit --without-udev --without-hal --with-storage-fs --with-storage-lvm --without-storage-iscsi --with-storage-disk --with-init-scripts=none --without-numactl --without-selinux --without-esx --without-libssh2 --without-capng --without-macvtap --enable-debug --without-hal --without-xen --without-vbox --without-lxc Build64:/home/menil-jp/libvirt-0.8.5# /etc/init.d/libvirt-bin start Starting libvirt management daemon: libvirtd. Build64:/home/menil-jp/libvirt-0.8.5# virsh list ID Nom État ---------------------------------- Build64:/home/menil-jp/libvirt-0.8.5# virsh list erreur :cannot recv data: : Connection reset by peer erreur :impossible de se connecter à l'hyperviseur Build64:/home/menil-jp/libvirt-0.8.5# gdb /usr/sbin/libvirtd /core.15695 GNU gdb (GDB) 7.0.1-debian Copyright (C) 2009 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "x86_64-linux-gnu". For bug reporting instructions, please see: <http://www.gnu.org/software/gdb/bugs/>... Reading symbols from /usr/sbin/libvirtd...done. [New Thread 15702] [New Thread 15701] [New Thread 15699] [New Thread 15698] [New Thread 15697] [New Thread 15695] warning: Can't read pathname for load map: Input/output error. Reading symbols from /usr/lib/libvirt-qemu.so.0...done. Loaded symbols for /usr/lib/libvirt-qemu.so.0 Reading symbols from /usr/lib/libvirt.so.0...done. Loaded symbols for /usr/lib/libvirt.so.0 Reading symbols from /lib/libdevmapper.so.1.02.1...(no debugging symbols found)...done. Loaded symbols for /lib/libdevmapper.so.1.02.1 Reading symbols from /usr/lib/libgnutls.so.26...(no debugging symbols found)...done. Loaded symbols for /usr/lib/libgnutls.so.26 Reading symbols from /usr/lib/libgcrypt.so.11...(no debugging symbols found)...done. Loaded symbols for /usr/lib/libgcrypt.so.11 Reading symbols from /usr/lib/libpcap.so.0.8...(no debugging symbols found)...done. Loaded symbols for /usr/lib/libpcap.so.0.8 Reading symbols from /usr/lib/libxml2.so.2...(no debugging symbols found)...done. Loaded symbols for /usr/lib/libxml2.so.2 Reading symbols from /lib/libpthread.so.0...Reading symbols from /usr/lib/debug/lib/libpthread-2.11.2.so...done. (no debugging symbols found)...done. Loaded symbols for /lib/libpthread.so.0 Reading symbols from /lib/libc.so.6...Reading symbols from /usr/lib/debug/lib/libc-2.11.2.so...done. (no debugging symbols found)...done. Loaded symbols for /lib/libc.so.6 Reading symbols from /lib/libselinux.so.1...(no debugging symbols found)...done. Loaded symbols for /lib/libselinux.so.1 Reading symbols from /lib/libudev.so.0...(no debugging symbols found)...done. Loaded symbols for /lib/libudev.so.0 Reading symbols from /usr/lib/libtasn1.so.3...(no debugging symbols found)...done. Loaded symbols for /usr/lib/libtasn1.so.3 Reading symbols from /usr/lib/libz.so.1...(no debugging symbols found)...done. Loaded symbols for /usr/lib/libz.so.1 Reading symbols from /usr/lib/libgpg-error.so.0...(no debugging symbols found)...done. Loaded symbols for /usr/lib/libgpg-error.so.0 Reading symbols from /lib/libdl.so.2...Reading symbols from /usr/lib/debug/lib/libdl-2.11.2.so...done. (no debugging symbols found)...done. Loaded symbols for /lib/libdl.so.2 Reading symbols from /lib/libm.so.6...Reading symbols from /usr/lib/debug/lib/libm-2.11.2.so...done. (no debugging symbols found)...done. Loaded symbols for /lib/libm.so.6 Reading symbols from /lib64/ld-linux-x86-64.so.2...Reading symbols from /usr/lib/debug/lib/ld-2.11.2.so...done. (no debugging symbols found)...done. Loaded symbols for /lib64/ld-linux-x86-64.so.2 Reading symbols from /lib/libnss_compat.so.2...Reading symbols from /usr/lib/debug/lib/libnss_compat-2.11.2.so...done. (no debugging symbols found)...done. Loaded symbols for /lib/libnss_compat.so.2 Reading symbols from /lib/libnsl.so.1...Reading symbols from /usr/lib/debug/lib/libnsl-2.11.2.so...done. (no debugging symbols found)...done. Loaded symbols for /lib/libnsl.so.1 Reading symbols from /lib/libnss_nis.so.2...Reading symbols from /usr/lib/debug/lib/libnss_nis-2.11.2.so...done. (no debugging symbols found)...done. Loaded symbols for /lib/libnss_nis.so.2 Reading symbols from /lib/libnss_files.so.2...Reading symbols from /usr/lib/debug/lib/libnss_files-2.11.2.so...done. (no debugging symbols found)...done. Loaded symbols for /lib/libnss_files.so.2 Core was generated by `/usr/sbin/libvirtd -d'. Program terminated with signal 11, Segmentation fault. #0 0x00000000004185ea in qemudDispatchServer (server=0x116ba30, sock=<value optimized out>) at libvirtd.c:1459 1459 server->clients[server->nclients++] = client; (gdb) bt #0 0x00000000004185ea in qemudDispatchServer (server=0x116ba30, sock=<value optimized out>) at libvirtd.c:1459 #1 0x0000000000418b01 in qemudDispatchServerEvent (watch=5, fd=8, events=1, opaque=<value optimized out>) at libvirtd.c:2225 #2 0x00000000004165d1 in virEventDispatchHandles () at event.c:467 #3 virEventRunOnce () at event.c:592 #4 0x00000000004174e6 in qemudOneLoop () at libvirtd.c:2234 #5 0x00000000004177c9 in qemudRunLoop (opaque=<value optimized out>) at libvirtd.c:2343 #6 0x00007ff54ca668ba in start_thread (arg=<value optimized out>) at pthread_create.c:300 #7 0x00007ff54c7ce02d in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:112 #8 0x0000000000000000 in ?? () (gdb) p server->clients $1 = (struct qemud_client **) 0x0 Is there anything i can provide, to debug further? Regards.

* src/util/memory.h (VIR_RESIZE_N): New macro. * src/util/memory.c (virResizeN): New function. * docs/hacking.html.in: Document it. * HACKING: Regenerate. * src/libvirt_private.syms: Export new helper. --- HACKING | 43 +++++++++++++++++++++++++++++++++++++---- docs/hacking.html.in | 47 ++++++++++++++++++++++++++++++++++++++++----- src/libvirt_private.syms | 1 + src/util/memory.c | 35 ++++++++++++++++++++++++++++++++++ src/util/memory.h | 26 +++++++++++++++++++++++++ 5 files changed, 141 insertions(+), 11 deletions(-) diff --git a/HACKING b/HACKING index 2b19fe4..e54af05 100644 --- a/HACKING +++ b/HACKING @@ -308,7 +308,7 @@ routines, use the macros from memory.h. - To allocate an array of object pointers: virDomainPtr *domains; - int ndomains = 10; + size_t ndomains = 10; if (VIR_ALLOC_N(domains, ndomains) < 0) { virReportOOMError(); @@ -317,24 +317,57 @@ routines, use the macros from memory.h. -- To re-allocate the array of domains to be longer: +- To re-allocate the array of domains to be 1 element longer (however, note that +repeatedly expanding an array by 1 scales quadratically, so this is +recommended only for smaller arrays): + + virDomainPtr domains; + size_t ndomains = 0; - if (VIR_EXPAND_N(domains, ndomains, 10) < 0) { + if (VIR_EXPAND_N(domains, ndomains, 1) < 0) { virReportOOMError(); return NULL; } + domains[ndomains - 1] = domain; + + + +- To ensure an array has room to hold at least one more element (this approach +scales better, but requires tracking allocation separately from usage) + + virDomainPtr domains; + size_t ndomains = 0; + size_t ndomains_max = 0; + + if (VIR_RESIZE_N(domains, ndomains_max, ndomains, 1) < 0) { + virReportOOMError(); + return NULL; + } + domains[ndomains++] = domain; - To trim an array of domains to have one less element: + virDomainPtr domains; + size_t ndomains = x; + size_t ndomains_max = y; + VIR_SHRINK_N(domains, ndomains, 1); -- To free the domain: +- To free an array of domains: - VIR_FREE(domain); + virDomainPtr domains; + size_t ndomains = x; + size_t ndomains_max = y; + size_t i; + + for (i = 0; i < ndomains; i++) + VIR_FREE(domains[i]); + VIR_FREE(domains) + ndomains_max = ndomains = 0; diff --git a/docs/hacking.html.in b/docs/hacking.html.in index ffe6aea..138a1ef 100644 --- a/docs/hacking.html.in +++ b/docs/hacking.html.in @@ -385,7 +385,7 @@ <li><p>To allocate an array of object pointers:</p> <pre> virDomainPtr *domains; - int ndomains = 10; + size_t ndomains = 10; if (VIR_ALLOC_N(domains, ndomains) < 0) { virReportOOMError(); @@ -394,26 +394,61 @@ </pre> </li> - <li><p>To re-allocate the array of domains to be longer:</p> + <li><p>To re-allocate the array of domains to be 1 element + longer (however, note that repeatedly expanding an array by 1 + scales quadratically, so this is recommended only for smaller + arrays):</p> <pre> - if (VIR_EXPAND_N(domains, ndomains, 10) < 0) { + virDomainPtr domains; + size_t ndomains = 0; + + if (VIR_EXPAND_N(domains, ndomains, 1) < 0) { virReportOOMError(); return NULL; } + domains[ndomains - 1] = domain; +</pre></li> + + <li><p>To ensure an array has room to hold at least one more + element (this approach scales better, but requires tracking + allocation separately from usage)</p> + +<pre> + virDomainPtr domains; + size_t ndomains = 0; + size_t ndomains_max = 0; + + if (VIR_RESIZE_N(domains, ndomains_max, ndomains, 1) < 0) { + virReportOOMError(); + return NULL; + } + domains[ndomains++] = domain; </pre> </li> <li><p>To trim an array of domains to have one less element:</p> <pre> + virDomainPtr domains; + size_t ndomains = x; + size_t ndomains_max = y; + VIR_SHRINK_N(domains, ndomains, 1); </pre></li> - <li><p>To free the domain:</p> + <li><p>To free an array of domains:</p> <pre> - VIR_FREE(domain); + virDomainPtr domains; + size_t ndomains = x; + size_t ndomains_max = y; + size_t i; + + for (i = 0; i < ndomains; i++) + VIR_FREE(domains[i]); + VIR_FREE(domains) + ndomains_max = ndomains = 0; </pre> - </li> + </li> </ul> <h2><a name="file_handling">File handling</a></h2> diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 796559c..8bf1028 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -506,6 +506,7 @@ virAllocN; virExpandN; virFree; virReallocN; +virResizeN; virShrinkN; diff --git a/src/util/memory.c b/src/util/memory.c index 59685b3..d1237e3 100644 --- a/src/util/memory.c +++ b/src/util/memory.c @@ -198,6 +198,41 @@ int virExpandN(void *ptrptr, size_t size, size_t *countptr, size_t add) } /** + * virResizeN: + * @ptrptr: pointer to pointer for address of allocated memory + * @size: number of bytes per element + * @allocptr: pointer to number of elements allocated in array + * @count: number of elements currently used in array + * @add: minimum number of additional elements to support in array + * + * If 'count' + 'add' is larger than '*allocptr', then Resize the + * block of memory in 'ptrptr' to be an array of at least 'count' + + * 'add' elements, each 'size' bytes in length. Update 'ptrptr' and + * 'allocptr' with the details of the newly allocated memory. On + * failure, 'ptrptr' and 'allocptr' are not changed. Any newly + * allocated memory in 'ptrptr' is zero-filled. + * + * Returns -1 on failure to allocate, zero on success + */ +int virResizeN(void *ptrptr, size_t size, size_t *allocptr, size_t count, + size_t add) +{ + size_t delta; + + if (count + add < count) { + errno = ENOMEM; + return -1; + } + if (count + add <= *allocptr) + return 0; + + delta = count + add - *allocptr; + if (delta < *allocptr / 2) + delta = *allocptr / 2; + return virExpandN(ptrptr, size, allocptr, delta); +} + +/** * virShrinkN: * @ptrptr: pointer to pointer for address of allocated memory * @size: number of bytes per element diff --git a/src/util/memory.h b/src/util/memory.h index 98ac2b3..2b39393 100644 --- a/src/util/memory.h +++ b/src/util/memory.h @@ -54,6 +54,9 @@ int virReallocN(void *ptrptr, size_t size, size_t count) ATTRIBUTE_RETURN_CHECK ATTRIBUTE_NONNULL(1); int virExpandN(void *ptrptr, size_t size, size_t *count, size_t add) ATTRIBUTE_RETURN_CHECK ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(3); +int virResizeN(void *ptrptr, size_t size, size_t *alloc, size_t count, + size_t desired) + ATTRIBUTE_RETURN_CHECK ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(3); void virShrinkN(void *ptrptr, size_t size, size_t *count, size_t remove) ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(3); int virAllocVar(void *ptrptr, @@ -117,6 +120,29 @@ void virFree(void *ptrptr) ATTRIBUTE_NONNULL(1); virExpandN(&(ptr), sizeof(*(ptr)), &(count), add) /** + * VIR_RESIZE_N: + * @ptr: pointer to hold address of allocated memory + * @alloc: variable tracking number of elements currently allocated + * @count: number of elements currently in use + * @add: minimum number of elements to additionally support + * + * Blindly using VIR_EXPAND_N(array, alloc, 1) in a loop scales + * quadratically, because every iteration must copy contents from + * all prior iterations. But amortized linear scaling can be achieved + * by tracking allocation size separately from the number of used + * elements, and growing geometrically only as needed. + * + * If 'count' + 'add' is larger than 'count', then geometrically reallocate + * the array of 'alloc' elements, each sizeof(*ptr) bytes long, and store + * the address of allocated memory in 'ptr' and the new size in 'alloc'. + * The new elements are filled with zero. + * + * Returns -1 on failure, 0 on success + */ +# define VIR_RESIZE_N(ptr, alloc, count, add) \ + virResizeN(&(ptr), sizeof(*(ptr)), &(alloc), count, add) + +/** * VIR_SHRINK_N: * @ptr: pointer to hold address of allocated memory * @count: variable tracking number of elements currently allocated -- 1.7.3.2

On Wed, Nov 17, 2010 at 09:28:54PM -0700, Eric Blake wrote:
* src/util/memory.h (VIR_RESIZE_N): New macro. * src/util/memory.c (virResizeN): New function. * docs/hacking.html.in: Document it. * HACKING: Regenerate. * src/libvirt_private.syms: Export new helper. --- HACKING | 43 +++++++++++++++++++++++++++++++++++++---- docs/hacking.html.in | 47 ++++++++++++++++++++++++++++++++++++++++----- src/libvirt_private.syms | 1 + src/util/memory.c | 35 ++++++++++++++++++++++++++++++++++ src/util/memory.h | 26 +++++++++++++++++++++++++ 5 files changed, 141 insertions(+), 11 deletions(-)
ACK Daniel -- |: Red Hat, Engineering, London -o- http://people.redhat.com/berrange/ :| |: http://libvirt.org -o- http://virt-manager.org -o- http://deltacloud.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: GnuPG: 7D3B9505 -o- F3C9 553F A1DA 4AC2 5648 23C1 B3DF F742 7D3B 9505 :|

* daemon/libvirtd.h (qemud_server): Change types of members tracking array sizes, and add allocation trackers. * daemon/event.c (virEventLoop): Likewise. (virEventAddHandleImpl, virEventAddTimeoutImpl) (virEventCleanupTimeouts, virEventCleanupHandles): Use VIR_RESIZE_N instead of VIR_REALLOC_N. Tweak debug messages to match type changes. * daemon/libvirtd.c (qemudDispatchServer, qemudRunLoop): Likewise. --- daemon/event.c | 44 +++++++++++++++++++------------------------- daemon/libvirtd.c | 8 +++----- daemon/libvirtd.h | 11 ++++++----- 3 files changed, 28 insertions(+), 35 deletions(-) diff --git a/daemon/event.c b/daemon/event.c index 06f9aad..a983b35 100644 --- a/daemon/event.c +++ b/daemon/event.c @@ -73,11 +73,11 @@ struct virEventLoop { int running; virThread leader; int wakeupfd[2]; - int handlesCount; - int handlesAlloc; + size_t handlesCount; + size_t handlesAlloc; struct virEventHandle *handles; - int timeoutsCount; - int timeoutsAlloc; + size_t timeoutsCount; + size_t timeoutsAlloc; struct virEventTimeout *timeouts; }; @@ -103,14 +103,13 @@ int virEventAddHandleImpl(int fd, int events, EVENT_DEBUG("Add handle fd=%d events=%d cb=%p opaque=%p", fd, events, cb, opaque); virMutexLock(&eventLoop.lock); if (eventLoop.handlesCount == eventLoop.handlesAlloc) { - EVENT_DEBUG("Used %d handle slots, adding %d more", + EVENT_DEBUG("Used %zu handle slots, adding at least %d more", eventLoop.handlesAlloc, EVENT_ALLOC_EXTENT); - if (VIR_REALLOC_N(eventLoop.handles, - (eventLoop.handlesAlloc + EVENT_ALLOC_EXTENT)) < 0) { + if (VIR_RESIZE_N(eventLoop.handles, eventLoop.handlesAlloc, + eventLoop.handlesCount, EVENT_ALLOC_EXTENT) < 0) { virMutexUnlock(&eventLoop.lock); return -1; } - eventLoop.handlesAlloc += EVENT_ALLOC_EXTENT; } watch = nextWatch++; @@ -204,14 +203,13 @@ int virEventAddTimeoutImpl(int frequency, virMutexLock(&eventLoop.lock); if (eventLoop.timeoutsCount == eventLoop.timeoutsAlloc) { - EVENT_DEBUG("Used %d timeout slots, adding %d more", + EVENT_DEBUG("Used %zu timeout slots, adding at least %d more", eventLoop.timeoutsAlloc, EVENT_ALLOC_EXTENT); - if (VIR_REALLOC_N(eventLoop.timeouts, - (eventLoop.timeoutsAlloc + EVENT_ALLOC_EXTENT)) < 0) { + if (VIR_RESIZE_N(eventLoop.timeouts, eventLoop.timeoutsAlloc, + eventLoop.timeoutsCount, EVENT_ALLOC_EXTENT) < 0) { virMutexUnlock(&eventLoop.lock); return -1; } - eventLoop.timeoutsAlloc += EVENT_ALLOC_EXTENT; } eventLoop.timeouts[eventLoop.timeoutsCount].timer = nextTimer++; @@ -301,7 +299,7 @@ int virEventRemoveTimeoutImpl(int timer) { static int virEventCalculateTimeout(int *timeout) { unsigned long long then = 0; int i; - EVENT_DEBUG("Calculate expiry of %d timers", eventLoop.timeoutsCount); + EVENT_DEBUG("Calculate expiry of %zu timers", eventLoop.timeoutsCount); /* Figure out if we need a timeout */ for (i = 0 ; i < eventLoop.timeoutsCount ; i++) { if (eventLoop.timeouts[i].frequency < 0) @@ -482,7 +480,7 @@ static int virEventDispatchHandles(int nfds, struct pollfd *fds) { */ static int virEventCleanupTimeouts(void) { int i; - DEBUG("Cleanup %d", eventLoop.timeoutsCount); + DEBUG("Cleanup %zu", eventLoop.timeoutsCount); /* Remove deleted entries, shuffling down remaining * entries as needed to form contiguous series @@ -507,12 +505,10 @@ static int virEventCleanupTimeouts(void) { /* Release some memory if we've got a big chunk free */ if ((eventLoop.timeoutsAlloc - EVENT_ALLOC_EXTENT) > eventLoop.timeoutsCount) { - EVENT_DEBUG("Releasing %d out of %d timeout slots used, releasing %d", + EVENT_DEBUG("Releasing %zu out of %zu timeout slots used, releasing %d", eventLoop.timeoutsCount, eventLoop.timeoutsAlloc, EVENT_ALLOC_EXTENT); - if (VIR_REALLOC_N(eventLoop.timeouts, - (eventLoop.timeoutsAlloc - EVENT_ALLOC_EXTENT)) < 0) - return -1; - eventLoop.timeoutsAlloc -= EVENT_ALLOC_EXTENT; + VIR_SHRINK_N(eventLoop.timeouts, eventLoop.timeoutsAlloc, + EVENT_ALLOC_EXTENT); } return 0; } @@ -523,7 +519,7 @@ static int virEventCleanupTimeouts(void) { */ static int virEventCleanupHandles(void) { int i; - DEBUG("Cleanupo %d", eventLoop.handlesCount); + DEBUG("Cleanup %zu", eventLoop.handlesCount); /* Remove deleted entries, shuffling down remaining * entries as needed to form contiguous series @@ -547,12 +543,10 @@ static int virEventCleanupHandles(void) { /* Release some memory if we've got a big chunk free */ if ((eventLoop.handlesAlloc - EVENT_ALLOC_EXTENT) > eventLoop.handlesCount) { - EVENT_DEBUG("Releasing %d out of %d handles slots used, releasing %d", + EVENT_DEBUG("Releasing %zu out of %zu handles slots used, releasing %d", eventLoop.handlesCount, eventLoop.handlesAlloc, EVENT_ALLOC_EXTENT); - if (VIR_REALLOC_N(eventLoop.handles, - (eventLoop.handlesAlloc - EVENT_ALLOC_EXTENT)) < 0) - return -1; - eventLoop.handlesAlloc -= EVENT_ALLOC_EXTENT; + VIR_SHRINK_N(eventLoop.handles, eventLoop.handlesAlloc, + EVENT_ALLOC_EXTENT); } return 0; } diff --git a/daemon/libvirtd.c b/daemon/libvirtd.c index 1673e91..e544c48 100644 --- a/daemon/libvirtd.c +++ b/daemon/libvirtd.c @@ -1333,7 +1333,8 @@ static int qemudDispatchServer(struct qemud_server *server, struct qemud_socket goto error; } - if (VIR_REALLOC_N(server->clients, server->nclients+1) < 0) { + if (VIR_RESIZE_N(server->clients, server->nclients_max, + server->nclients, 1) < 0) { VIR_ERROR0(_("Out of memory allocating clients")); goto error; } @@ -2361,10 +2362,7 @@ static void *qemudRunLoop(void *opaque) { server->clients + i + 1, sizeof (*server->clients) * (server->nclients - i)); - if (VIR_REALLOC_N(server->clients, - server->nclients) < 0) { - ; /* ignore */ - } + VIR_SHRINK_N(server->clients, server->nclients, 0); goto reprocess; } } diff --git a/daemon/libvirtd.h b/daemon/libvirtd.h index 785ac07..af20e56 100644 --- a/daemon/libvirtd.h +++ b/daemon/libvirtd.h @@ -1,7 +1,7 @@ /* * libvirtd.h: daemon data structure definitions * - * Copyright (C) 2006-2009 Red Hat, Inc. + * Copyright (C) 2006-2010 Red Hat, Inc. * Copyright (C) 2006 Daniel P. Berrange * * This library is free software; you can redistribute it and/or @@ -283,12 +283,13 @@ struct qemud_server { int privileged; - int nworkers; - int nactiveworkers; + size_t nworkers; + size_t nactiveworkers; struct qemud_worker *workers; - int nsockets; + size_t nsockets; struct qemud_socket *sockets; - int nclients; + size_t nclients; + size_t nclients_max; struct qemud_client **clients; int sigread; -- 1.7.3.2

On Wed, Nov 17, 2010 at 09:28:55PM -0700, Eric Blake wrote:
* daemon/libvirtd.h (qemud_server): Change types of members tracking array sizes, and add allocation trackers. * daemon/event.c (virEventLoop): Likewise. (virEventAddHandleImpl, virEventAddTimeoutImpl) (virEventCleanupTimeouts, virEventCleanupHandles): Use VIR_RESIZE_N instead of VIR_REALLOC_N. Tweak debug messages to match type changes. * daemon/libvirtd.c (qemudDispatchServer, qemudRunLoop): Likewise. --- daemon/event.c | 44 +++++++++++++++++++------------------------- daemon/libvirtd.c | 8 +++----- daemon/libvirtd.h | 11 ++++++----- 3 files changed, 28 insertions(+), 35 deletions(-)
ACK Daniel -- |: Red Hat, Engineering, London -o- http://people.redhat.com/berrange/ :| |: http://libvirt.org -o- http://virt-manager.org -o- http://deltacloud.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: GnuPG: 7D3B9505 -o- F3C9 553F A1DA 4AC2 5648 23C1 B3DF F742 7D3B 9505 :|

* src/conf/capabilities.h (_virCaps, _virCapsHost, _virCapsGuest) (_virCapsGuestArch): Add additional fields. * src/conf/cpu_conf.h (_virCPUDef): Likewise. * src/conf/capabilities.c (virCapabilitiesFormatXML): Reflect updated type. (virCapabilitiesAddGuest, virCapabilitiesAddHostFeature) (virCapabilitiesAddHostMigrateTransport) (virCapabilitiesAddHostNUMACell, virCapabilitiesAddGuestFeature) (virCapabilitiesAddGuestDomain): Use new array APIs. * src/conf/cpu_conf.c (virCPUDefAddFeature, virCPUDefCopy) (virCPUDefParseXML): Likewise. * tests/testutilsqemu.c (testQemuCapsInit): Adjust test. --- src/conf/capabilities.c | 37 +++++++++++++++++-------------------- src/conf/capabilities.h | 20 +++++++++++++------- src/conf/cpu_conf.c | 7 +++++-- src/conf/cpu_conf.h | 5 +++-- tests/testutilsqemu.c | 1 + 5 files changed, 39 insertions(+), 31 deletions(-) diff --git a/src/conf/capabilities.c b/src/conf/capabilities.c index 4478d85..99d5a56 100644 --- a/src/conf/capabilities.c +++ b/src/conf/capabilities.c @@ -1,7 +1,7 @@ /* * capabilities.c: hypervisor capabilities * - * Copyright (C) 2006-2008 Red Hat, Inc. + * Copyright (C) 2006-2008, 2010 Red Hat, Inc. * Copyright (C) 2006-2008 Daniel P. Berrange * * This library is free software; you can redistribute it and/or @@ -190,8 +190,8 @@ int virCapabilitiesAddHostFeature(virCapsPtr caps, const char *name) { - if (VIR_REALLOC_N(caps->host.features, - caps->host.nfeatures + 1) < 0) + if (VIR_RESIZE_N(caps->host.features, caps->host.nfeatures_max, + caps->host.nfeatures, 1) < 0) return -1; if ((caps->host.features[caps->host.nfeatures] = strdup(name)) == NULL) @@ -213,8 +213,8 @@ int virCapabilitiesAddHostMigrateTransport(virCapsPtr caps, const char *name) { - if (VIR_REALLOC_N(caps->host.migrateTrans, - caps->host.nmigrateTrans + 1) < 0) + if (VIR_RESIZE_N(caps->host.migrateTrans, caps->host.nmigrateTrans_max, + caps->host.nmigrateTrans, 1) < 0) return -1; if ((caps->host.migrateTrans[caps->host.nmigrateTrans] = strdup(name)) == NULL) @@ -243,8 +243,8 @@ virCapabilitiesAddHostNUMACell(virCapsPtr caps, { virCapsHostNUMACellPtr cell; - if (VIR_REALLOC_N(caps->host.numaCell, - caps->host.nnumaCell + 1) < 0) + if (VIR_RESIZE_N(caps->host.numaCell, caps->host.nnumaCell_max, + caps->host.nnumaCell, 1) < 0) return -1; if (VIR_ALLOC(cell) < 0) @@ -261,8 +261,7 @@ virCapabilitiesAddHostNUMACell(virCapsPtr caps, cell->ncpus = ncpus; cell->num = num; - caps->host.numaCell[caps->host.nnumaCell] = cell; - caps->host.nnumaCell++; + caps->host.numaCell[caps->host.nnumaCell++] = cell; return 0; } @@ -380,11 +379,10 @@ virCapabilitiesAddGuest(virCapsPtr caps, (guest->arch.defaultInfo.loader = strdup(loader)) == NULL) goto no_memory; - if (VIR_REALLOC_N(caps->guests, - caps->nguests + 1) < 0) + if (VIR_RESIZE_N(caps->guests, caps->nguests_max, + caps->nguests, 1) < 0) goto no_memory; - caps->guests[caps->nguests] = guest; - caps->nguests++; + caps->guests[caps->nguests++] = guest; if (nmachines) { guest->arch.defaultInfo.nmachines = nmachines; @@ -434,8 +432,8 @@ virCapabilitiesAddGuestDomain(virCapsGuestPtr guest, (dom->info.loader = strdup(loader)) == NULL) goto no_memory; - if (VIR_REALLOC_N(guest->arch.domains, - guest->arch.ndomains + 1) < 0) + if (VIR_RESIZE_N(guest->arch.domains, guest->arch.ndomains_max, + guest->arch.ndomains, 1) < 0) goto no_memory; guest->arch.domains[guest->arch.ndomains] = dom; guest->arch.ndomains++; @@ -478,11 +476,10 @@ virCapabilitiesAddGuestFeature(virCapsGuestPtr guest, feature->defaultOn = defaultOn; feature->toggle = toggle; - if (VIR_REALLOC_N(guest->features, - guest->nfeatures + 1) < 0) + if (VIR_RESIZE_N(guest->features, guest->nfeatures_max, + guest->nfeatures, 1) < 0) goto no_memory; - guest->features[guest->nfeatures] = feature; - guest->nfeatures++; + guest->features[guest->nfeatures++] = feature; return feature; @@ -706,7 +703,7 @@ virCapabilitiesFormatXML(virCapsPtr caps) if (caps->host.nnumaCell) { virBufferAddLit(&xml, " <topology>\n"); - virBufferVSprintf(&xml, " <cells num='%d'>\n", + virBufferVSprintf(&xml, " <cells num='%zu'>\n", caps->host.nnumaCell); for (i = 0 ; i < caps->host.nnumaCell ; i++) { virBufferVSprintf(&xml, " <cell id='%d'>\n", diff --git a/src/conf/capabilities.h b/src/conf/capabilities.h index f41be1c..759265d 100644 --- a/src/conf/capabilities.h +++ b/src/conf/capabilities.h @@ -1,7 +1,7 @@ /* * capabilities.h: hypervisor capabilities * - * Copyright (C) 2006-2008 Red Hat, Inc. + * Copyright (C) 2006-2008, 2010 Red Hat, Inc. * Copyright (C) 2006-2008 Daniel P. Berrange * * This library is free software; you can redistribute it and/or @@ -71,7 +71,8 @@ struct _virCapsGuestArch { char *name; int wordsize; virCapsGuestDomainInfo defaultInfo; - int ndomains; + size_t ndomains; + size_t ndomains_max; virCapsGuestDomainPtr *domains; }; @@ -80,7 +81,8 @@ typedef virCapsGuest *virCapsGuestPtr; struct _virCapsGuest { char *ostype; virCapsGuestArch arch; - int nfeatures; + size_t nfeatures; + size_t nfeatures_max; virCapsGuestFeaturePtr *features; }; @@ -102,13 +104,16 @@ typedef struct _virCapsHost virCapsHost; typedef virCapsHost *virCapsHostPtr; struct _virCapsHost { char *arch; - int nfeatures; + size_t nfeatures; + size_t nfeatures_max; char **features; int offlineMigrate; int liveMigrate; - int nmigrateTrans; + size_t nmigrateTrans; + size_t nmigrateTrans_max; char **migrateTrans; - int nnumaCell; + size_t nnumaCell; + size_t nnumaCell_max; virCapsHostNUMACellPtr *numaCell; virCapsHostSecModel secModel; virCPUDefPtr cpu; @@ -134,7 +139,8 @@ typedef struct _virCaps virCaps; typedef virCaps* virCapsPtr; struct _virCaps { virCapsHost host; - int nguests; + size_t nguests; + size_t nguests_max; virCapsGuestPtr *guests; unsigned char macPrefix[VIR_MAC_PREFIX_BUFLEN]; unsigned int emulatorRequired : 1; diff --git a/src/conf/cpu_conf.c b/src/conf/cpu_conf.c index f8d006d..7f03eaa 100644 --- a/src/conf/cpu_conf.c +++ b/src/conf/cpu_conf.c @@ -83,6 +83,7 @@ virCPUDefCopy(const virCPUDefPtr cpu) || (cpu->vendor && !(copy->vendor = strdup(cpu->vendor))) || VIR_ALLOC_N(copy->features, cpu->nfeatures) < 0) goto no_memory; + copy->nfeatures_max = cpu->nfeatures; copy->type = cpu->type; copy->match = cpu->match; @@ -234,7 +235,8 @@ virCPUDefParseXML(const xmlNodePtr node, goto error; } - if (VIR_ALLOC_N(def->features, n) < 0) + if (VIR_RESIZE_N(def->features, def->nfeatures_max, + def->nfeatures, n) < 0) goto no_memory; def->nfeatures = n; } @@ -425,7 +427,8 @@ virCPUDefAddFeature(virCPUDefPtr def, } } - if (VIR_REALLOC_N(def->features, def->nfeatures + 1) < 0) + if (VIR_RESIZE_N(def->features, def->nfeatures_max, + def->nfeatures, 1) < 0) goto no_memory; if (def->type == VIR_CPU_TYPE_HOST) diff --git a/src/conf/cpu_conf.h b/src/conf/cpu_conf.h index 6151683..055887c 100644 --- a/src/conf/cpu_conf.h +++ b/src/conf/cpu_conf.h @@ -1,7 +1,7 @@ /* * cpu_conf.h: CPU XML handling * - * Copyright (C) 2009 Red Hat, Inc. + * Copyright (C) 2009, 2010 Red Hat, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -74,7 +74,8 @@ struct _virCPUDef { unsigned int sockets; unsigned int cores; unsigned int threads; - unsigned int nfeatures; + size_t nfeatures; + size_t nfeatures_max; virCPUFeatureDefPtr features; }; diff --git a/tests/testutilsqemu.c b/tests/testutilsqemu.c index dd33b62..72fc8aa 100644 --- a/tests/testutilsqemu.c +++ b/tests/testutilsqemu.c @@ -89,6 +89,7 @@ virCapsPtr testQemuCapsInit(void) { 2, /* cores */ 1, /* threads */ ARRAY_CARDINALITY(host_cpu_features), /* nfeatures */ + ARRAY_CARDINALITY(host_cpu_features), /* nfeatures_max */ host_cpu_features /* features */ }; -- 1.7.3.2

On Wed, Nov 17, 2010 at 09:28:56PM -0700, Eric Blake wrote:
* src/conf/capabilities.h (_virCaps, _virCapsHost, _virCapsGuest) (_virCapsGuestArch): Add additional fields. * src/conf/cpu_conf.h (_virCPUDef): Likewise. * src/conf/capabilities.c (virCapabilitiesFormatXML): Reflect updated type. (virCapabilitiesAddGuest, virCapabilitiesAddHostFeature) (virCapabilitiesAddHostMigrateTransport) (virCapabilitiesAddHostNUMACell, virCapabilitiesAddGuestFeature) (virCapabilitiesAddGuestDomain): Use new array APIs. * src/conf/cpu_conf.c (virCPUDefAddFeature, virCPUDefCopy) (virCPUDefParseXML): Likewise. * tests/testutilsqemu.c (testQemuCapsInit): Adjust test. --- src/conf/capabilities.c | 37 +++++++++++++++++-------------------- src/conf/capabilities.h | 20 +++++++++++++------- src/conf/cpu_conf.c | 7 +++++-- src/conf/cpu_conf.h | 5 +++-- tests/testutilsqemu.c | 1 + 5 files changed, 39 insertions(+), 31 deletions(-)
ACK Daniel -- |: Red Hat, Engineering, London -o- http://people.redhat.com/berrange/ :| |: http://libvirt.org -o- http://virt-manager.org -o- http://deltacloud.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: GnuPG: 7D3B9505 -o- F3C9 553F A1DA 4AC2 5648 23C1 B3DF F742 7D3B 9505 :|

From: Daniel P. Berrange <berrange@redhat.com> --- src/util/util.c | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) diff --git a/src/util/util.c b/src/util/util.c index 2d6485f..f2fe58a 100644 --- a/src/util/util.c +++ b/src/util/util.c @@ -463,7 +463,7 @@ __virExec(const char *const*argv, int childerr = -1; int tmpfd; - if ((null = open("/dev/null", O_RDONLY)) < 0) { + if ((null = open("/dev/null", O_RDWR)) < 0) { virReportSystemError(errno, _("cannot open %s"), "/dev/null"); -- 1.7.3.2

On Wed, Nov 17, 2010 at 09:28:57PM -0700, Eric Blake wrote:
From: Daniel P. Berrange <berrange@redhat.com>
--- src/util/util.c | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-)
diff --git a/src/util/util.c b/src/util/util.c index 2d6485f..f2fe58a 100644 --- a/src/util/util.c +++ b/src/util/util.c @@ -463,7 +463,7 @@ __virExec(const char *const*argv, int childerr = -1; int tmpfd;
- if ((null = open("/dev/null", O_RDONLY)) < 0) { + if ((null = open("/dev/null", O_RDWR)) < 0) { virReportSystemError(errno, _("cannot open %s"), "/dev/null");
ACK Daniel -- |: Red Hat, Engineering, London -o- http://people.redhat.com/berrange/ :| |: http://libvirt.org -o- http://virt-manager.org -o- http://deltacloud.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: GnuPG: 7D3B9505 -o- F3C9 553F A1DA 4AC2 5648 23C1 B3DF F742 7D3B 9505 :|

Using 'int ret = strcmp(a, b)' in a qsort function is a valid use of str[n]cmp that should _not_ be turned to STREQ, but it was falling foul of our specific syntax-check. Meanwhile, gnulib's maint.mk already has a tighter bound for strcmp, so we can copy that regex and just check for strncmp, which results in fewer false positives that require exceptions. * cfg.mk (sc_prohibit_strcmp_and_strncmp): Rename... (sc_prohibit_strncmp): ...to this, and tighten, to mirror maint.mk's sc_prohibit_strcmp's better regex. * Makefile.am (syntax_check_exceptions): Update exception rule. * .x-sc_prohibit_strcmp_and_strncmp: Rename... * .x-sc_prohibit_strncmp: ...and trim. --- .x-sc_prohibit_strcmp_and_strncmp | 9 --------- .x-sc_prohibit_strncmp | 1 + Makefile.am | 2 +- cfg.mk | 14 ++++++++------ 4 files changed, 10 insertions(+), 16 deletions(-) delete mode 100644 .x-sc_prohibit_strcmp_and_strncmp create mode 100644 .x-sc_prohibit_strncmp diff --git a/.x-sc_prohibit_strcmp_and_strncmp b/.x-sc_prohibit_strcmp_and_strncmp deleted file mode 100644 index 77c3ee6..0000000 --- a/.x-sc_prohibit_strcmp_and_strncmp +++ /dev/null @@ -1,9 +0,0 @@ -^gnulib/ -^ChangeLog-old$ -^docs/ -^examples/domain-events/events-c/event-test\.c$ -^src/internal\.h$ -^src/lxc/lxc_container\.c$ -^src/node_device/node_device_devkit\.c$ -^src/node_device/node_device_hal\.c$ -^src/storage/parthelper\.c$ diff --git a/.x-sc_prohibit_strncmp b/.x-sc_prohibit_strncmp new file mode 100644 index 0000000..8be2055 --- /dev/null +++ b/.x-sc_prohibit_strncmp @@ -0,0 +1 @@ +^src/internal\.h$ diff --git a/Makefile.am b/Makefile.am index d34313a..efdc204 100644 --- a/Makefile.am +++ b/Makefile.am @@ -33,7 +33,7 @@ syntax_check_exceptions = \ .x-sc_prohibit_readlink \ .x-sc_prohibit_sprintf \ .x-sc_prohibit_strcmp \ - .x-sc_prohibit_strcmp_and_strncmp \ + .x-sc_prohibit_strncmp \ .x-sc_prohibit_strncpy \ .x-sc_prohibit_test_minus_ao \ .x-sc_prohibit_VIR_ERR_NO_MEMORY \ diff --git a/cfg.mk b/cfg.mk index 19a4622..1863bf1 100644 --- a/cfg.mk +++ b/cfg.mk @@ -231,12 +231,14 @@ sc_avoid_write: halt='consider using safewrite instead of write' \ $(_sc_search_regexp) -# Use STREQ rather than comparing strcmp == 0, or != 0. -# Similarly, use STREQLEN or STRPREFIX rather than strncmp. -sc_prohibit_strcmp_and_strncmp: - @prohibit='strn?cmp *\(' \ - halt='use STREQ() in place of the above uses of str[n]cmp' \ - $(_sc_search_regexp) +# Similar to the gnulib maint.mk rule for sc_prohibit_strcmp +# Use STREQLEN or STRPREFIX rather than comparing strncmp == 0, or != 0. +sc_prohibit_strncmp: + @grep -nE '! *str''ncmp *\(|\<str''ncmp *\([^)]+\) *==' \ + $$($(VC_LIST_EXCEPT)) \ + | grep -vE ':# *define STREQ\(' && \ + { echo '$(ME): use STREQLEN or STRPREFIX instead of str''ncmp' \ + 1>&2; exit 1; } || : # Use virAsprintf rather than as'printf since *strp is undefined on error. sc_prohibit_asprintf: -- 1.7.3.2

On Wed, Nov 17, 2010 at 09:28:58PM -0700, Eric Blake wrote:
Using 'int ret = strcmp(a, b)' in a qsort function is a valid use of str[n]cmp that should _not_ be turned to STREQ, but it was falling foul of our specific syntax-check. Meanwhile, gnulib's maint.mk already has a tighter bound for strcmp, so we can copy that regex and just check for strncmp, which results in fewer false positives that require exceptions.
* cfg.mk (sc_prohibit_strcmp_and_strncmp): Rename... (sc_prohibit_strncmp): ...to this, and tighten, to mirror maint.mk's sc_prohibit_strcmp's better regex. * Makefile.am (syntax_check_exceptions): Update exception rule. * .x-sc_prohibit_strcmp_and_strncmp: Rename... * .x-sc_prohibit_strncmp: ...and trim. --- .x-sc_prohibit_strcmp_and_strncmp | 9 --------- .x-sc_prohibit_strncmp | 1 + Makefile.am | 2 +- cfg.mk | 14 ++++++++------ 4 files changed, 10 insertions(+), 16 deletions(-) delete mode 100644 .x-sc_prohibit_strcmp_and_strncmp create mode 100644 .x-sc_prohibit_strncmp
ACK Daniel -- |: Red Hat, Engineering, London -o- http://people.redhat.com/berrange/ :| |: http://libvirt.org -o- http://virt-manager.org -o- http://deltacloud.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: GnuPG: 7D3B9505 -o- F3C9 553F A1DA 4AC2 5648 23C1 B3DF F742 7D3B 9505 :|

On 11/18/2010 02:51 AM, Daniel P. Berrange wrote:
On Wed, Nov 17, 2010 at 09:28:58PM -0700, Eric Blake wrote:
Using 'int ret = strcmp(a, b)' in a qsort function is a valid use of str[n]cmp that should _not_ be turned to STREQ, but it was falling foul of our specific syntax-check. Meanwhile, gnulib's maint.mk already has a tighter bound for strcmp, so we can copy that regex and just check for strncmp, which results in fewer false positives that require exceptions.
* cfg.mk (sc_prohibit_strcmp_and_strncmp): Rename... (sc_prohibit_strncmp): ...to this, and tighten, to mirror maint.mk's sc_prohibit_strcmp's better regex. * Makefile.am (syntax_check_exceptions): Update exception rule. * .x-sc_prohibit_strcmp_and_strncmp: Rename... * .x-sc_prohibit_strncmp: ...and trim. --- .x-sc_prohibit_strcmp_and_strncmp | 9 --------- .x-sc_prohibit_strncmp | 1 + Makefile.am | 2 +- cfg.mk | 14 ++++++++------ 4 files changed, 10 insertions(+), 16 deletions(-) delete mode 100644 .x-sc_prohibit_strcmp_and_strncmp create mode 100644 .x-sc_prohibit_strncmp
ACK
1-6 are now pushed after fixing some typos I noticed in the comments of 1 and 2; I'll respond to your comments on 7-8 and rebase 9-10 for another round of review before pushing those. -- Eric Blake eblake@redhat.com +1-801-349-2682 Libvirt virtualization library http://libvirt.org

From: Daniel P. Berrange <berrange@redhat.com> --- src/conf/domain_conf.c | 1 - src/util/hooks.c | 1 - 2 files changed, 0 insertions(+), 2 deletions(-) diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c index 30c27db..dc6f198 100644 --- a/src/conf/domain_conf.c +++ b/src/conf/domain_conf.c @@ -42,7 +42,6 @@ #include "c-ctype.h" #include "logging.h" #include "network.h" -#include "macvtap.h" #include "nwfilter_conf.h" #include "ignore-value.h" #include "storage_file.h" diff --git a/src/util/hooks.c b/src/util/hooks.c index 8e24564..2a9df20 100644 --- a/src/util/hooks.c +++ b/src/util/hooks.c @@ -33,7 +33,6 @@ #include "virterror_internal.h" #include "hooks.h" #include "util.h" -#include "conf/domain_conf.h" #include "logging.h" #include "memory.h" #include "files.h" -- 1.7.3.2

On Wed, Nov 17, 2010 at 09:28:59PM -0700, Eric Blake wrote:
From: Daniel P. Berrange <berrange@redhat.com>
--- src/conf/domain_conf.c | 1 - src/util/hooks.c | 1 - 2 files changed, 0 insertions(+), 2 deletions(-)
diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c index 30c27db..dc6f198 100644 --- a/src/conf/domain_conf.c +++ b/src/conf/domain_conf.c @@ -42,7 +42,6 @@ #include "c-ctype.h" #include "logging.h" #include "network.h" -#include "macvtap.h" #include "nwfilter_conf.h" #include "ignore-value.h" #include "storage_file.h" diff --git a/src/util/hooks.c b/src/util/hooks.c index 8e24564..2a9df20 100644 --- a/src/util/hooks.c +++ b/src/util/hooks.c @@ -33,7 +33,6 @@ #include "virterror_internal.h" #include "hooks.h" #include "util.h" -#include "conf/domain_conf.h" #include "logging.h" #include "memory.h" #include "files.h"
You might want to re-check this one - when I re-tested with macvtap enabled, it caused a build failure. Daniel -- |: Red Hat, Engineering, London -o- http://people.redhat.com/berrange/ :| |: http://libvirt.org -o- http://virt-manager.org -o- http://deltacloud.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: GnuPG: 7D3B9505 -o- F3C9 553F A1DA 4AC2 5648 23C1 B3DF F742 7D3B 9505 :|

On 11/18/2010 02:51 AM, Daniel P. Berrange wrote:
On Wed, Nov 17, 2010 at 09:28:59PM -0700, Eric Blake wrote:
From: Daniel P. Berrange <berrange@redhat.com>
--- src/conf/domain_conf.c | 1 - src/util/hooks.c | 1 - 2 files changed, 0 insertions(+), 2 deletions(-)
You might want to re-check this one - when I re-tested with macvtap enabled, it caused a build failure.
What failure did you see? From my config.log: configure:42373: ===================== configure:42375: configure:42377: Drivers configure:42379: configure:42381: Xen: yes configure:42383: QEMU: yes configure:42385: UML: yes configure:42387: OpenVZ: yes configure:42389: VBox: yes configure:42391: XenAPI: yes configure:42393: LXC: yes configure:42395: PHYP: yes configure:42397: ONE: yes configure:42399: ESX: yes configure:42401: Test: yes configure:42403: Remote: yes configure:42405: Network: yes configure:42407: Libvirtd: yes configure:42409: netcf: yes configure:42411: macvtap: yes configure:42413: virtport: yes so I'm compiling with macvtap, and not seeing any failures. The original patch from you that I amended had one more file touched for a removed include, but that removal did cause compilation failures. -- Eric Blake eblake@redhat.com +1-801-349-2682 Libvirt virtualization library http://libvirt.org

From: Daniel P. Berrange <berrange@redhat.com> This introduces a new set of APIs in src/util/command.h to use for invoking commands. This is intended to replace all current usage of virRun and virExec variants, with a more flexible and less error prone API. * src/util/command.c: New file. * src/util/command.h: New header. * src/Makefile.am (UTIL_SOURCES): Build it. * src/libvirt_private.syms: Export symbols internally. * tests/commandtest.c: New test. * tests/Makefile.am (check_PROGRAMS): Run it. * tests/commandhelper.c: Auxiliary program. * tests/commanddata/test2.log - test15.log: New expected outputs. * cfg.mk (useless_free_options): Add virCommandFree. * po/POTFILES.in: New translation. * .x-sc_avoid_write: Add exemption. * tests/.gitignore: Ignore new built file. --- .x-sc_avoid_write | 1 + cfg.mk | 1 + po/POTFILES.in | 1 + src/Makefile.am | 1 + src/libvirt_private.syms | 29 ++ src/util/command.c | 960 ++++++++++++++++++++++++++++++++++++++++++ src/util/command.h | 213 ++++++++++ tests/.gitignore | 4 + tests/Makefile.am | 18 +- tests/commanddata/test10.log | 14 + tests/commanddata/test11.log | 14 + tests/commanddata/test12.log | 12 + tests/commanddata/test13.log | 12 + tests/commanddata/test14.log | 12 + tests/commanddata/test15.log | 12 + tests/commanddata/test2.log | 12 + tests/commanddata/test3.log | 14 + tests/commanddata/test4.log | 12 + tests/commanddata/test5.log | 10 + tests/commanddata/test6.log | 6 + tests/commanddata/test7.log | 11 + tests/commanddata/test8.log | 7 + tests/commanddata/test9.log | 18 + tests/commandhelper.c | 136 ++++++ tests/commandtest.c | 572 +++++++++++++++++++++++++ 25 files changed, 2101 insertions(+), 1 deletions(-) create mode 100644 src/util/command.c create mode 100644 src/util/command.h create mode 100644 tests/commanddata/test10.log create mode 100644 tests/commanddata/test11.log create mode 100644 tests/commanddata/test12.log create mode 100644 tests/commanddata/test13.log create mode 100644 tests/commanddata/test14.log create mode 100644 tests/commanddata/test15.log create mode 100644 tests/commanddata/test2.log create mode 100644 tests/commanddata/test3.log create mode 100644 tests/commanddata/test4.log create mode 100644 tests/commanddata/test5.log create mode 100644 tests/commanddata/test6.log create mode 100644 tests/commanddata/test7.log create mode 100644 tests/commanddata/test8.log create mode 100644 tests/commanddata/test9.log create mode 100644 tests/commandhelper.c create mode 100644 tests/commandtest.c diff --git a/.x-sc_avoid_write b/.x-sc_avoid_write index 232504f..f6fc1b2 100644 --- a/.x-sc_avoid_write +++ b/.x-sc_avoid_write @@ -1,6 +1,7 @@ ^src/libvirt\.c$ ^src/fdstream\.c$ ^src/qemu/qemu_monitor\.c$ +^src/util/command\.c$ ^src/util/util\.c$ ^src/xen/xend_internal\.c$ ^daemon/libvirtd.c$ diff --git a/cfg.mk b/cfg.mk index 1863bf1..3b29ca1 100644 --- a/cfg.mk +++ b/cfg.mk @@ -77,6 +77,7 @@ useless_free_options = \ --name=virCapabilitiesFreeHostNUMACell \ --name=virCapabilitiesFreeMachines \ --name=virCgroupFree \ + --name=virCommandFree \ --name=virConfFreeList \ --name=virConfFreeValue \ --name=virDomainChrDefFree \ diff --git a/po/POTFILES.in b/po/POTFILES.in index 2820ac1..e7be0d3 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -78,6 +78,7 @@ src/uml/uml_driver.c src/util/authhelper.c src/util/bridge.c src/util/cgroup.c +src/util/command.c src/util/conf.c src/util/dnsmasq.c src/util/hooks.c diff --git a/src/Makefile.am b/src/Makefile.am index a9a1986..0923d60 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -48,6 +48,7 @@ UTIL_SOURCES = \ util/bitmap.c util/bitmap.h \ util/bridge.c util/bridge.h \ util/buf.c util/buf.h \ + util/command.c util/command.h \ util/conf.c util/conf.h \ util/cgroup.c util/cgroup.h \ util/event.c util/event.h \ diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 8bf1028..7a7ede6 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -82,6 +82,35 @@ virCgroupSetMemorySoftLimit; virCgroupSetSwapHardLimit; +# command.h +virCommandAddArg; +virCommandAddArgList; +virCommandAddArgPair; +virCommandAddArgSet; +virCommandAddEnvPair; +virCommandAddEnvPass; +virCommandAddEnvPassCommon; +virCommandAddEnvString; +virCommandClearCaps; +virCommandDaemonize; +virCommandFree; +virCommandNew; +virCommandNewArgs; +virCommandPreserveFD; +virCommandRun; +virCommandRunAsync; +virCommandSetErrorBuffer; +virCommandSetErrorFD; +virCommandSetInputBuffer; +virCommandSetInputFD; +virCommandSetOutputBuffer; +virCommandSetOutputFD; +virCommandSetPidFile; +virCommandSetPreExecHook; +virCommandSetWorkingDirectory; +virCommandWait; + + # conf.h virConfFree; virConfFreeValue; diff --git a/src/util/command.c b/src/util/command.c new file mode 100644 index 0000000..71db2a1 --- /dev/null +++ b/src/util/command.c @@ -0,0 +1,960 @@ +/* + * command.c: Child command execution + * + * Copyright (C) 2010 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <config.h> + +#include <poll.h> +#include <stdarg.h> +#include <stdbool.h> +#include <stdlib.h> +#include <sys/wait.h> + +#include "command.h" +#include "memory.h" +#include "virterror_internal.h" +#include "util.h" +#include "logging.h" +#include "files.h" + +#define VIR_FROM_THIS VIR_FROM_NONE + +#define virCommandError(code, ...) \ + virReportErrorHelper(NULL, VIR_FROM_NONE, code, __FILE__, \ + __FUNCTION__, __LINE__, __VA_ARGS__) + +struct _virCommand { + int has_error; /* ENOMEM on allocation failure, -1 for anything else. */ + + char **args; + size_t nargs; + size_t maxargs; + + char **env; + size_t nenv; + size_t maxenv; + + char *pwd; + + /* XXX Use int[] if we ever need to support more than FD_SETSIZE fd's. */ + fd_set preserve; + unsigned int flags; + + char *inbuf; + char **outbuf; + char **errbuf; + + int infd; + int inpipe; + int outfd; + int errfd; + int *outfdptr; + int *errfdptr; + + virExecHook hook; + void *opaque; + + pid_t pid; + char *pidfile; +}; + +/* + * Create a new command for named binary + */ +virCommandPtr +virCommandNew(const char *binary) +{ + const char *const args[] = { binary, NULL }; + + return virCommandNewArgs(args); +} + +/* + * Create a new command with a NULL terminated + * set of args, taking binary from argv[0] + */ +virCommandPtr +virCommandNewArgs(const char *const*args) +{ + virCommandPtr cmd; + + if (VIR_ALLOC(cmd) < 0) + return NULL; + + FD_ZERO(&cmd->preserve); + cmd->infd = cmd->outfd = cmd->errfd = -1; + cmd->inpipe = -1; + cmd->pid = -1; + + virCommandAddArgSet(cmd, args); + + if (cmd->has_error) { + virCommandFree(cmd); + return NULL; + } + + return cmd; +} + + +/* + * Preserve the specified file descriptor in the child, instead of + * closing it. FD must not be one of the three standard streams. + */ +void +virCommandPreserveFD(virCommandPtr cmd, int fd) +{ + if (!cmd || cmd->has_error) + return; + + if (fd <= STDERR_FILENO || FD_SETSIZE <= fd) { + cmd->has_error = -1; + VIR_DEBUG("cannot preserve %d", fd); + return; + } + + FD_SET(fd, &cmd->preserve); +} + +/* + * Save the child PID in a pidfile + */ +void +virCommandSetPidFile(virCommandPtr cmd, const char *pidfile) +{ + if (!cmd || cmd->has_error) + return; + + VIR_FREE(cmd->pidfile); + if (!(cmd->pidfile = strdup(pidfile))) { + cmd->has_error = ENOMEM; + } +} + + +/* + * Remove all capabilities from the child + */ +void +virCommandClearCaps(virCommandPtr cmd) +{ + if (!cmd || cmd->has_error) + return; + + cmd->flags |= VIR_EXEC_CLEAR_CAPS; +} + +#if 0 /* XXX Enable if we have a need for capability management. */ + +/* + * Re-allow a specific capability + */ +void +virCommandAllowCap(virCommandPtr cmd, + int capability ATTRIBUTE_UNUSED) +{ + if (!cmd || cmd->has_error) + return; + + /* XXX ? */ +} + +#endif /* 0 */ + + +/* + * Daemonize the child process + */ +void +virCommandDaemonize(virCommandPtr cmd) +{ + if (!cmd || cmd->has_error) + return; + + cmd->flags |= VIR_EXEC_DAEMON; +} + +/* + * Add an environment variable to the child + * using separate name & value strings + */ +void +virCommandAddEnvPair(virCommandPtr cmd, const char *name, const char *value) +{ + char *env; + + if (!cmd || cmd->has_error) + return; + + if (virAsprintf(&env, "%s=%s", name, value ? value : "") < 0) { + cmd->has_error = ENOMEM; + return; + } + + /* env plus trailing NULL */ + if (VIR_RESIZE_N(cmd->env, cmd->maxenv, cmd->nenv, 1 + 1) < 0) { + VIR_FREE(env); + cmd->has_error = ENOMEM; + return; + } + + cmd->env[cmd->nenv++] = env; +} + + +/* + * Add an environment variable to the child + * using a preformatted env string FOO=BAR + */ +void +virCommandAddEnvString(virCommandPtr cmd, const char *str) +{ + if (!cmd || cmd->has_error) + return; + + char *env; + + if (!cmd || cmd->has_error) + return; + + if (!(env = strdup(str))) { + cmd->has_error = ENOMEM; + return; + } + + /* env plus trailing NULL */ + if (VIR_RESIZE_N(cmd->env, cmd->maxenv, cmd->nenv, 1 + 1) < 0) { + VIR_FREE(env); + cmd->has_error = ENOMEM; + return; + } + + cmd->env[cmd->nenv++] = env; +} + + +/* + * Pass an environment variable to the child + * using current process' value + */ +void +virCommandAddEnvPass(virCommandPtr cmd, const char *name) +{ + char *value; + if (!cmd || cmd->has_error) + return; + + value = getenv(name); + if (value) + virCommandAddEnvPair(cmd, name, value); +} + + +/* + * Set LC_ALL to C, and propagate other essential environment + * variables from the parent process. + */ +void +virCommandAddEnvPassCommon(virCommandPtr cmd) +{ + /* Attempt to Pre-allocate; allocation failure will be detected + * later during virCommandAdd*. */ + ignore_value(VIR_RESIZE_N(cmd->env, cmd->maxenv, cmd->nenv, 9)); + + virCommandAddEnvPair(cmd, "LC_ALL", "C"); + + virCommandAddEnvPass(cmd, "LD_PRELOAD"); + virCommandAddEnvPass(cmd, "LD_LIBRARY_PATH"); + virCommandAddEnvPass(cmd, "PATH"); + virCommandAddEnvPass(cmd, "HOME"); + virCommandAddEnvPass(cmd, "USER"); + virCommandAddEnvPass(cmd, "LOGNAME"); + virCommandAddEnvPass(cmd, "TMPDIR"); +} + +/* + * Add a command line argument to the child + */ +void +virCommandAddArg(virCommandPtr cmd, const char *val) +{ + char *arg; + + if (!cmd || cmd->has_error) + return; + + if (!(arg = strdup(val))) { + cmd->has_error = ENOMEM; + return; + } + + /* Arg plus trailing NULL. */ + if (VIR_RESIZE_N(cmd->args, cmd->maxargs, cmd->nargs, 1 + 1) < 0) { + VIR_FREE(arg); + cmd->has_error = ENOMEM; + return; + } + + cmd->args[cmd->nargs++] = arg; +} + + +/* + * Add "NAME=VAL" as a single command line argument to the child + */ +void +virCommandAddArgPair(virCommandPtr cmd, const char *name, const char *val) +{ + char *arg; + + if (!cmd || cmd->has_error) + return; + + if (virAsprintf(&arg, "%s=%s", name, val) < 0) { + cmd->has_error = ENOMEM; + return; + } + + /* Arg plus trailing NULL. */ + if (VIR_RESIZE_N(cmd->args, cmd->maxargs, cmd->nargs, 1 + 1) < 0) { + VIR_FREE(arg); + cmd->has_error = ENOMEM; + return; + } + + cmd->args[cmd->nargs++] = arg; +} + +/* + * Add a NULL terminated list of args + */ +void +virCommandAddArgSet(virCommandPtr cmd, const char *const*vals) +{ + int narg = 0; + + if (!cmd || cmd->has_error) + return; + + while (vals[narg] != NULL) + narg++; + + /* narg plus trailing NULL. */ + if (VIR_RESIZE_N(cmd->args, cmd->maxargs, cmd->nargs, narg + 1) < 0) { + cmd->has_error = ENOMEM; + return; + } + + narg = 0; + while (vals[narg] != NULL) { + char *arg = strdup(vals[narg++]); + if (!arg) { + cmd->has_error = ENOMEM; + return; + } + cmd->args[cmd->nargs++] = arg; + } +} + +/* + * Add a NULL terminated list of args + */ +void +virCommandAddArgList(virCommandPtr cmd, ...) +{ + va_list list; + int narg = 0; + + if (!cmd || cmd->has_error) + return; + + va_start(list, cmd); + while (va_arg(list, const char *) != NULL) + narg++; + va_end(list); + + /* narg plus trailing NULL. */ + if (VIR_RESIZE_N(cmd->args, cmd->maxargs, cmd->nargs, narg + 1) < 0) { + cmd->has_error = ENOMEM; + return; + } + + va_start(list, cmd); + while (1) { + char *arg = va_arg(list, char *); + if (!arg) + break; + arg = strdup(arg); + if (!arg) { + cmd->has_error = ENOMEM; + va_end(list); + return; + } + cmd->args[cmd->nargs++] = arg; + } + va_end(list); +} + +/* + * Set the working directory of a non-daemon child process, rather + * than the parent's working directory. Daemons automatically get / + * without using this call. + */ +void +virCommandSetWorkingDirectory(virCommandPtr cmd, const char *pwd) +{ + if (!cmd || cmd->has_error) + return; + + if (cmd->pwd) { + cmd->has_error = -1; + VIR_DEBUG0("cannot set directory twice"); + } else { + cmd->pwd = strdup(pwd); + if (!cmd->pwd) + cmd->has_error = ENOMEM; + } +} + + +/* + * Feed the child's stdin from a string buffer + */ +void +virCommandSetInputBuffer(virCommandPtr cmd, const char *inbuf) +{ + if (!cmd || cmd->has_error) + return; + + if (cmd->infd != -1 || cmd->inbuf) { + cmd->has_error = -1; + VIR_DEBUG0("cannot specify input twice"); + return; + } + + cmd->inbuf = strdup(inbuf); + if (!cmd->inbuf) + cmd->has_error = ENOMEM; +} + + +/* + * Capture the child's stdout to a string buffer + */ +void +virCommandSetOutputBuffer(virCommandPtr cmd, char **outbuf) +{ + if (!cmd || cmd->has_error) + return; + + if (cmd->outfd != -1) { + cmd->has_error = -1; + VIR_DEBUG0("cannot specify output twice"); + return; + } + + cmd->outbuf = outbuf; + cmd->outfdptr = &cmd->outfd; +} + + +/* + * Capture the child's stderr to a string buffer + */ +void +virCommandSetErrorBuffer(virCommandPtr cmd, char **errbuf) +{ + if (!cmd || cmd->has_error) + return; + + if (cmd->errfd != -1) { + cmd->has_error = -1; + VIR_DEBUG0("cannot specify stderr twice"); + return; + } + + cmd->errbuf = errbuf; + cmd->errfdptr = &cmd->errfd; +} + + +/* + * Attach a file descriptor to the child's stdin + */ +void +virCommandSetInputFD(virCommandPtr cmd, int infd) +{ + if (!cmd || cmd->has_error) + return; + + if (infd < 0 || cmd->inbuf) { + cmd->has_error = -1; + VIR_DEBUG0("cannot specify input twice"); + return; + } + + cmd->infd = infd; +} + + +/* + * Attach a file descriptor to the child's stdout + */ +void +virCommandSetOutputFD(virCommandPtr cmd, int *outfd) +{ + if (!cmd || cmd->has_error) + return; + + if (!outfd || cmd->outfd != -1) { + cmd->has_error = -1; + VIR_DEBUG0("cannot specify output twice"); + return; + } + + cmd->outfdptr = outfd; +} + + +/* + * Attach a file descriptor to the child's stderr + */ +void +virCommandSetErrorFD(virCommandPtr cmd, int *errfd) +{ + if (!cmd || cmd->has_error) + return; + + if (!errfd || cmd->errfd != -1) { + cmd->has_error = -1; + VIR_DEBUG0("cannot specify stderr twice"); + return; + } + + cmd->errfdptr = errfd; +} + + +/* + * Run HOOK(OPAQUE) in the child as the last thing before changing + * directories, dropping capabilities, and executing the new process. + * Force the child to fail if HOOK does not return zero. + */ +void +virCommandSetPreExecHook(virCommandPtr cmd, virExecHook hook, void *opaque) +{ + if (!cmd || cmd->has_error) + return; + + cmd->hook = hook; + cmd->opaque = opaque; +} + + +/* + * Manage input and output to the child process. + */ +static int +virCommandProcessIO(virCommandPtr cmd) +{ + int infd = -1, outfd = -1, errfd = -1; + size_t inlen = 0, outlen = 0, errlen = 0; + size_t inoff = 0; + + /* With an input buffer, feed data to child + * via pipe */ + if (cmd->inbuf) { + inlen = strlen(cmd->inbuf); + infd = cmd->inpipe; + } + + /* With out/err buffer, the outfd/errfd + * have been filled with an FD for us */ + if (cmd->outbuf) + outfd = cmd->outfd; + if (cmd->errbuf) + errfd = cmd->errfd; + + for (;;) { + int i; + struct pollfd fds[3]; + int nfds = 0; + + if (infd != -1) { + fds[nfds].fd = infd; + fds[nfds].events = POLLOUT; + nfds++; + } + if (outfd != -1) { + fds[nfds].fd = outfd; + fds[nfds].events = POLLIN; + nfds++; + } + if (errfd != -1) { + fds[nfds].fd = errfd; + fds[nfds].events = POLLIN; + nfds++; + } + + if (nfds == 0) + break; + + if (poll(fds,nfds, -1) < 0) { + if ((errno == EAGAIN) || (errno == EINTR)) + continue; + virReportSystemError(errno, "%s", + _("unable to poll on child")); + return -1; + } + + for (i = 0; i < nfds ; i++) { + if (fds[i].fd == errfd || + fds[i].fd == outfd) { + char data[1024]; + char **buf; + size_t *len; + int done; + if (fds[i].fd == outfd) { + buf = cmd->outbuf; + len = &outlen; + } else { + buf = cmd->errbuf; + len = &errlen; + } + + done = read(fds[i].fd, data, sizeof(data)); + if (done < 0) { + if (errno != EINTR && + errno != EAGAIN) { + virReportSystemError(errno, "%s", + _("unable to write to child input")); + return -1; + } + } else if (done == 0) { + if (fds[i].fd == outfd) + outfd = -1; + else + errfd = -1; + } else { + if (VIR_REALLOC_N(*buf, *len + done + 1) < 0) { + virReportOOMError(); + return -1; + } + memcpy(*buf + *len, data, done); + *len += done; + (*buf)[*len] = '\0'; + } + } else { + int done; + + done = write(infd, cmd->inbuf + inoff, + inlen - inoff); + if (done < 0) { + if (errno != EINTR && + errno != EAGAIN) { + virReportSystemError(errno, "%s", + _("unable to write to child input")); + return -1; + } + } else { + inoff += done; + if (inoff == inlen) { + int tmpfd = infd; + if (VIR_CLOSE(infd) < 0) + VIR_DEBUG("ignoring failed close on fd %d", tmpfd); + } + } + } + + } + } + + return 0; +} + + +/* + * Run the command and wait for completion. + * Returns -1 on any error executing the + * command. Returns 0 if the command executed, + * with the exit status set + */ +int +virCommandRun(virCommandPtr cmd, int *exitstatus) +{ + int ret = 0; + char *outbuf = NULL; + char *errbuf = NULL; + int infd[2]; + + if (!cmd || cmd->has_error == -1) { + virCommandError(VIR_ERR_INTERNAL_ERROR, "%s", + _("invalid use of command API")); + return -1; + } + if (cmd->has_error == ENOMEM) { + virReportOOMError(); + return -1; + } + + /* If we have an input buffer, we need + * a pipe to feed the data to the child */ + if (cmd->inbuf) { + if (pipe(infd) < 0) { + virReportSystemError(errno, "%s", + _("unable to open pipe")); + return -1; + } + cmd->infd = infd[0]; + cmd->inpipe = infd[1]; + } + + if (virCommandRunAsync(cmd, NULL) < 0) { + if (cmd->inbuf) { + int tmpfd = infd[0]; + if (VIR_CLOSE(infd[0]) < 0) + VIR_DEBUG("ignoring failed close on fd %d", tmpfd); + tmpfd = infd[1]; + if (VIR_CLOSE(infd[1]) < 0) + VIR_DEBUG("ignoring failed close on fd %d", tmpfd); + } + return -1; + } + + /* If caller hasn't requested capture of + * stdout/err, then capture it ourselves + * so we can log it + */ + if (!cmd->outbuf && + !cmd->outfdptr) { + cmd->outfd = -1; + cmd->outfdptr = &cmd->outfd; + cmd->outbuf = &outbuf; + } + if (!cmd->errbuf && + !cmd->errfdptr) { + cmd->errfd = -1; + cmd->errfdptr = &cmd->errfd; + cmd->errbuf = &errbuf; + } + + if (cmd->inbuf || cmd->outbuf || cmd->errbuf) + ret = virCommandProcessIO(cmd); + + if (virCommandWait(cmd, exitstatus) < 0) + ret = -1; + + VIR_DEBUG("Result stdout: '%s' stderr: '%s'", + NULLSTR(*cmd->outbuf), + NULLSTR(*cmd->errbuf)); + + /* Reset any capturing, in case caller runs + * this identical command again */ + if (cmd->inbuf) { + int tmpfd = infd[0]; + if (VIR_CLOSE(infd[0]) < 0) + VIR_DEBUG("ignoring failed close on fd %d", tmpfd); + tmpfd = infd[1]; + if (VIR_CLOSE(infd[1]) < 0) + VIR_DEBUG("ignoring failed close on fd %d", tmpfd); + } + if (cmd->outbuf == &outbuf) { + int tmpfd = cmd->outfd; + if (VIR_CLOSE(cmd->outfd) < 0) + VIR_DEBUG("ignoring failed close on fd %d", tmpfd); + cmd->outfdptr = NULL; + cmd->outbuf = NULL; + } + if (cmd->errbuf == &errbuf) { + int tmpfd = cmd->errfd; + if (VIR_CLOSE(cmd->errfd) < 0) + VIR_DEBUG("ignoring failed close on fd %d", tmpfd); + cmd->errfdptr = NULL; + cmd->errbuf = NULL; + } + + return ret; +} + + +/* + * Perform all virCommand-specific actions, along with the user hook. + */ +static int +virCommandHook(void *data) +{ + virCommandPtr cmd = data; + int res = 0; + + if (cmd->hook) + res = cmd->hook(cmd->opaque); + if (res == 0 && cmd->pwd) { + VIR_DEBUG("Running child in %s", cmd->pwd); + res = chdir(cmd->pwd); + } + return res; +} + + +/* + * Run the command asynchronously + * Returns -1 on any error executing the + * command. Returns 0 if the command executed. + */ +int +virCommandRunAsync(virCommandPtr cmd, pid_t *pid) +{ + int ret; + char *str; + + if (!cmd || cmd->has_error == -1) { + virCommandError(VIR_ERR_INTERNAL_ERROR, "%s", + _("invalid use of command API")); + return -1; + } + if (cmd->has_error == ENOMEM) { + virReportOOMError(); + return -1; + } + + if (cmd->pid != -1) { + virCommandError(VIR_ERR_INTERNAL_ERROR, + _("command is already running as pid %d"), + cmd->pid); + return -1; + } + + if (cmd->pwd && (cmd->flags & VIR_EXEC_DAEMON)) { + virCommandError(VIR_ERR_INTERNAL_ERROR, + _("daemonized command cannot set working directory %s"), + cmd->pwd); + return -1; + } + + + str = virArgvToString((const char *const *)cmd->args); + VIR_DEBUG("About to run %s", str ? str : cmd->args[0]); + VIR_FREE(str); + + ret = virExecWithHook((const char *const *)cmd->args, + (const char *const *)cmd->env, + &cmd->preserve, + &cmd->pid, + cmd->infd, + cmd->outfdptr, + cmd->errfdptr, + cmd->flags, + virCommandHook, + cmd, + cmd->pidfile); + + VIR_DEBUG("Command result %d, with PID %d", + ret, (int)cmd->pid); + + if (ret == 0 && pid) + *pid = cmd->pid; + + return ret; +} + + +/* + * Wait for the async command to complete. + * Return -1 on any error waiting for + * completion. Returns 0 if the command + * finished with the exit status set + */ +int +virCommandWait(virCommandPtr cmd, int *exitstatus) +{ + int ret; + int status; + + if (!cmd || cmd->has_error == -1) { + virCommandError(VIR_ERR_INTERNAL_ERROR, "%s", + _("invalid use of command API")); + return -1; + } + if (cmd->has_error == ENOMEM) { + virReportOOMError(); + return -1; + } + + if (cmd->pid == -1) { + virCommandError(VIR_ERR_INTERNAL_ERROR, "%s", + _("command is not yet running")); + return -1; + } + + + /* Wait for intermediate process to exit */ + while ((ret = waitpid(cmd->pid, &status, 0)) == -1 && + errno == EINTR); + + if (ret == -1) { + virReportSystemError(errno, _("unable to wait for process %d"), + cmd->pid); + return -1; + } + + cmd->pid = -1; + + if (exitstatus == NULL) { + if (status != 0) { + virCommandError(VIR_ERR_INTERNAL_ERROR, + _("Intermediate daemon process exited with status %d."), + WEXITSTATUS(status)); + return -1; + } + } else { + *exitstatus = status; + } + + return 0; +} + + +/* + * Release all resources + */ +void +virCommandFree(virCommandPtr cmd) +{ + int i; + if (!cmd) + return; + + VIR_FORCE_CLOSE(cmd->outfd); + VIR_FORCE_CLOSE(cmd->errfd); + + for (i = 0 ; i < cmd->nargs ; i++) + VIR_FREE(cmd->args[i]); + VIR_FREE(cmd->args); + + for (i = 0 ; i < cmd->nenv ; i++) + VIR_FREE(cmd->env[i]); + VIR_FREE(cmd->env); + + VIR_FREE(cmd->pwd); + + VIR_FREE(cmd->pidfile); + + VIR_FREE(cmd); +} diff --git a/src/util/command.h b/src/util/command.h new file mode 100644 index 0000000..ca24869 --- /dev/null +++ b/src/util/command.h @@ -0,0 +1,213 @@ +/* + * command.h: Child command execution + * + * Copyright (C) 2010 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef __VIR_COMMAND_H__ +# define __VIR_COMMAND_H__ + +# include "internal.h" +# include "util.h" + +typedef struct _virCommand virCommand; +typedef virCommand *virCommandPtr; + +/* + * Create a new command for named binary + */ +virCommandPtr virCommandNew(const char *binary) ATTRIBUTE_NONNULL(1); + +/* + * Create a new command with a NULL terminated + * set of args, taking binary from argv[0] + */ +virCommandPtr virCommandNewArgs(const char *const*args) ATTRIBUTE_NONNULL(1); + +/* All error report from these setup APIs is + * delayed until the Run/Exec/Wait methods + */ + +/* + * Preserve the specified file descriptor + * in the child, instead of closing it + */ +void virCommandPreserveFD(virCommandPtr cmd, + int fd); + +/* + * Save the child PID in a pidfile + */ +void virCommandSetPidFile(virCommandPtr cmd, + const char *pidfile) ATTRIBUTE_NONNULL(2); + +/* + * Remove all capabilities from the child + */ +void virCommandClearCaps(virCommandPtr cmd); + +# if 0 +/* + * Re-allow a specific capability + */ +void virCommandAllowCap(virCommandPtr cmd, + int capability); +# endif + +/* + * Daemonize the child process + */ +void virCommandDaemonize(virCommandPtr cmd); + + +/* + * Add an environment variable to the child + * using separate name & value strings + */ +void virCommandAddEnvPair(virCommandPtr cmd, + const char *name, + const char *value) ATTRIBUTE_NONNULL(2); + +/* + * Add an environemnt variable to the child + * using a preformated env string FOO=BAR + */ +void virCommandAddEnvString(virCommandPtr cmd, + const char *str) ATTRIBUTE_NONNULL(2); +/* + * Pass an environment variable to the child + * using current process' value + */ +void virCommandAddEnvPass(virCommandPtr cmd, + const char *name) ATTRIBUTE_NONNULL(2); +/* + * Pass a common set of environment variables + * to the child using current process' values + */ +void virCommandAddEnvPassCommon(virCommandPtr cmd); + +/* + * Add a command line argument to the child + */ +void virCommandAddArg(virCommandPtr cmd, + const char *val) ATTRIBUTE_NONNULL(2); +/* + * Add a command line argument to the child + */ +void virCommandAddArgPair(virCommandPtr cmd, + const char *name, + const char *val) + ATTRIBUTE_NONNULL(2) ATTRIBUTE_NONNULL(3); +/* + * Add a NULL terminated array of args + */ +void virCommandAddArgSet(virCommandPtr cmd, + const char *const*vals) ATTRIBUTE_NONNULL(2); +/* + * Add a NULL terminated list of args + */ +void virCommandAddArgList(virCommandPtr cmd, + ... /* const char *arg, ..., NULL */) + ATTRIBUTE_SENTINEL; + +/* + * Set the working directory of a non-daemon child process, rather + * than the parent's working directory. Daemons automatically get / + * without using this call. + */ +void virCommandSetWorkingDirectory(virCommandPtr cmd, + const char *pwd) ATTRIBUTE_NONNULL(2); + +/* + * Feed the child's stdin from a string buffer. + * + * NB: Only works with virCommandRun() + */ +void virCommandSetInputBuffer(virCommandPtr cmd, + const char *inbuf) ATTRIBUTE_NONNULL(2); +/* + * Capture the child's stdout to a string buffer + * + * NB: Only works with virCommandRun() + */ +void virCommandSetOutputBuffer(virCommandPtr cmd, + char **outbuf) ATTRIBUTE_NONNULL(2); +/* + * Capture the child's stderr to a string buffer + * + * NB: Only works with virCommandRun() + */ +void virCommandSetErrorBuffer(virCommandPtr cmd, + char **errbuf) ATTRIBUTE_NONNULL(2); + +/* + * Set a file descriptor as the child's stdin + */ +void virCommandSetInputFD(virCommandPtr cmd, + int infd); +/* + * Set a file descriptor as the child's stdout + */ +void virCommandSetOutputFD(virCommandPtr cmd, + int *outfd) ATTRIBUTE_NONNULL(2); +/* + * Set a file descriptor as the child's stderr + */ +void virCommandSetErrorFD(virCommandPtr cmd, + int *errfd) ATTRIBUTE_NONNULL(2); + +/* + * A hook function to run between fork + exec + */ +void virCommandSetPreExecHook(virCommandPtr cmd, + virExecHook hook, + void *opaque) ATTRIBUTE_NONNULL(2); + +/* + * Run the command and wait for completion. + * Returns -1 on any error executing the + * command. Returns 0 if the command executed, + * with the exit status set + */ +int virCommandRun(virCommandPtr cmd, + int *exitstatus) ATTRIBUTE_RETURN_CHECK; + +/* + * Run the command asynchronously + * Returns -1 on any error executing the + * command. Returns 0 if the command executed. + */ +int virCommandRunAsync(virCommandPtr cmd, + pid_t *pid) ATTRIBUTE_RETURN_CHECK; + +/* + * Wait for the async command to complete. + * Return -1 on any error waiting for + * completion. Returns 0 if the command + * finished with the exit status set + */ +int virCommandWait(virCommandPtr cmd, + int *exitstatus) ATTRIBUTE_RETURN_CHECK; + +/* + * Release all resources + */ +void virCommandFree(virCommandPtr cmd); + + +#endif /* __VIR_COMMAND_H__ */ diff --git a/tests/.gitignore b/tests/.gitignore index 8ad3e98..e3906f0 100644 --- a/tests/.gitignore +++ b/tests/.gitignore @@ -1,6 +1,10 @@ *.exe .deps .libs +commandhelper +commandhelper.log +commandhelper.pid +commandtest conftest esxutilstest eventtest diff --git a/tests/Makefile.am b/tests/Makefile.am index 77b6fb9..ff3f135 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -72,7 +72,8 @@ EXTRA_DIST = \ qemuhelpdata check_PROGRAMS = virshtest conftest sockettest \ - nodeinfotest qparamtest virbuftest + nodeinfotest qparamtest virbuftest \ + commandtest commandhelper if WITH_XEN check_PROGRAMS += xml2sexprtest sexpr2xmltest \ @@ -154,6 +155,7 @@ TESTS = virshtest \ qparamtest \ virbuftest \ sockettest \ + commandtest \ $(test_scripts) if WITH_XEN @@ -339,6 +341,20 @@ nodeinfotest_SOURCES = \ nodeinfotest.c testutils.h testutils.c nodeinfotest_LDADD = $(LDADDS) +commandtest_SOURCES = \ + commandtest.c testutils.h testutils.c +commandtest_CFLAGS = -Dabs_builddir="\"$(abs_builddir)\"" +commandtest_LDADD = $(LDADDS) + +commandhelper_SOURCES = \ + commandhelper.c +commandhelper_CFLAGS = -Dabs_builddir="\"$(abs_builddir)\"" +commandhelper_LDADD = $(LDADDS) + +statstest_SOURCES = \ + statstest.c testutils.h testutils.c +statstest_LDADD = $(LDADDS) + if WITH_SECDRIVER_SELINUX seclabeltest_SOURCES = \ seclabeltest.c diff --git a/tests/commanddata/test10.log b/tests/commanddata/test10.log new file mode 100644 index 0000000..e1d6092 --- /dev/null +++ b/tests/commanddata/test10.log @@ -0,0 +1,14 @@ +ARG:-version +ARG:-log=bar.log +ENV:DISPLAY=:0.0 +ENV:HOME=/home/test +ENV:HOSTNAME=test +ENV:LANG=C +ENV:LOGNAME=testTMPDIR=/tmp +ENV:PATH=/usr/bin:/bin +ENV:USER=test +FD:0 +FD:1 +FD:2 +DAEMON:no +CWD:/tmp diff --git a/tests/commanddata/test11.log b/tests/commanddata/test11.log new file mode 100644 index 0000000..e1d6092 --- /dev/null +++ b/tests/commanddata/test11.log @@ -0,0 +1,14 @@ +ARG:-version +ARG:-log=bar.log +ENV:DISPLAY=:0.0 +ENV:HOME=/home/test +ENV:HOSTNAME=test +ENV:LANG=C +ENV:LOGNAME=testTMPDIR=/tmp +ENV:PATH=/usr/bin:/bin +ENV:USER=test +FD:0 +FD:1 +FD:2 +DAEMON:no +CWD:/tmp diff --git a/tests/commanddata/test12.log b/tests/commanddata/test12.log new file mode 100644 index 0000000..1b59206 --- /dev/null +++ b/tests/commanddata/test12.log @@ -0,0 +1,12 @@ +ENV:DISPLAY=:0.0 +ENV:HOME=/home/test +ENV:HOSTNAME=test +ENV:LANG=C +ENV:LOGNAME=testTMPDIR=/tmp +ENV:PATH=/usr/bin:/bin +ENV:USER=test +FD:0 +FD:1 +FD:2 +DAEMON:no +CWD:/tmp diff --git a/tests/commanddata/test13.log b/tests/commanddata/test13.log new file mode 100644 index 0000000..1b59206 --- /dev/null +++ b/tests/commanddata/test13.log @@ -0,0 +1,12 @@ +ENV:DISPLAY=:0.0 +ENV:HOME=/home/test +ENV:HOSTNAME=test +ENV:LANG=C +ENV:LOGNAME=testTMPDIR=/tmp +ENV:PATH=/usr/bin:/bin +ENV:USER=test +FD:0 +FD:1 +FD:2 +DAEMON:no +CWD:/tmp diff --git a/tests/commanddata/test14.log b/tests/commanddata/test14.log new file mode 100644 index 0000000..1b59206 --- /dev/null +++ b/tests/commanddata/test14.log @@ -0,0 +1,12 @@ +ENV:DISPLAY=:0.0 +ENV:HOME=/home/test +ENV:HOSTNAME=test +ENV:LANG=C +ENV:LOGNAME=testTMPDIR=/tmp +ENV:PATH=/usr/bin:/bin +ENV:USER=test +FD:0 +FD:1 +FD:2 +DAEMON:no +CWD:/tmp diff --git a/tests/commanddata/test15.log b/tests/commanddata/test15.log new file mode 100644 index 0000000..f439a85 --- /dev/null +++ b/tests/commanddata/test15.log @@ -0,0 +1,12 @@ +ENV:DISPLAY=:0.0 +ENV:HOME=/home/test +ENV:HOSTNAME=test +ENV:LANG=C +ENV:LOGNAME=testTMPDIR=/tmp +ENV:PATH=/usr/bin:/bin +ENV:USER=test +FD:0 +FD:1 +FD:2 +DAEMON:no +CWD:.../commanddata diff --git a/tests/commanddata/test2.log b/tests/commanddata/test2.log new file mode 100644 index 0000000..1b59206 --- /dev/null +++ b/tests/commanddata/test2.log @@ -0,0 +1,12 @@ +ENV:DISPLAY=:0.0 +ENV:HOME=/home/test +ENV:HOSTNAME=test +ENV:LANG=C +ENV:LOGNAME=testTMPDIR=/tmp +ENV:PATH=/usr/bin:/bin +ENV:USER=test +FD:0 +FD:1 +FD:2 +DAEMON:no +CWD:/tmp diff --git a/tests/commanddata/test3.log b/tests/commanddata/test3.log new file mode 100644 index 0000000..6bd7974 --- /dev/null +++ b/tests/commanddata/test3.log @@ -0,0 +1,14 @@ +ENV:DISPLAY=:0.0 +ENV:HOME=/home/test +ENV:HOSTNAME=test +ENV:LANG=C +ENV:LOGNAME=testTMPDIR=/tmp +ENV:PATH=/usr/bin:/bin +ENV:USER=test +FD:0 +FD:1 +FD:2 +FD:3 +FD:5 +DAEMON:no +CWD:/tmp diff --git a/tests/commanddata/test4.log b/tests/commanddata/test4.log new file mode 100644 index 0000000..1876685 --- /dev/null +++ b/tests/commanddata/test4.log @@ -0,0 +1,12 @@ +ENV:DISPLAY=:0.0 +ENV:HOME=/home/test +ENV:HOSTNAME=test +ENV:LANG=C +ENV:LOGNAME=testTMPDIR=/tmp +ENV:PATH=/usr/bin:/bin +ENV:USER=test +FD:0 +FD:1 +FD:2 +DAEMON:yes +CWD:/ diff --git a/tests/commanddata/test5.log b/tests/commanddata/test5.log new file mode 100644 index 0000000..f745c3f --- /dev/null +++ b/tests/commanddata/test5.log @@ -0,0 +1,10 @@ +ENV:HOME=/home/test +ENV:LC_ALL=C +ENV:LOGNAME=testTMPDIR=/tmp +ENV:PATH=/usr/bin:/bin +ENV:USER=test +FD:0 +FD:1 +FD:2 +DAEMON:no +CWD:/tmp diff --git a/tests/commanddata/test6.log b/tests/commanddata/test6.log new file mode 100644 index 0000000..5394428 --- /dev/null +++ b/tests/commanddata/test6.log @@ -0,0 +1,6 @@ +ENV:DISPLAY=:0.0 +FD:0 +FD:1 +FD:2 +DAEMON:no +CWD:/tmp diff --git a/tests/commanddata/test7.log b/tests/commanddata/test7.log new file mode 100644 index 0000000..cdfe445 --- /dev/null +++ b/tests/commanddata/test7.log @@ -0,0 +1,11 @@ +ENV:DISPLAY=:0.0 +ENV:HOME=/home/test +ENV:LC_ALL=C +ENV:LOGNAME=testTMPDIR=/tmp +ENV:PATH=/usr/bin:/bin +ENV:USER=test +FD:0 +FD:1 +FD:2 +DAEMON:no +CWD:/tmp diff --git a/tests/commanddata/test8.log b/tests/commanddata/test8.log new file mode 100644 index 0000000..87874fd --- /dev/null +++ b/tests/commanddata/test8.log @@ -0,0 +1,7 @@ +ENV:LANG=C +ENV:USER=test +FD:0 +FD:1 +FD:2 +DAEMON:no +CWD:/tmp diff --git a/tests/commanddata/test9.log b/tests/commanddata/test9.log new file mode 100644 index 0000000..2607530 --- /dev/null +++ b/tests/commanddata/test9.log @@ -0,0 +1,18 @@ +ARG:-version +ARG:-log=bar.log +ARG:arg1 +ARG:arg2 +ARG:arg3 +ARG:arg4 +ENV:DISPLAY=:0.0 +ENV:HOME=/home/test +ENV:HOSTNAME=test +ENV:LANG=C +ENV:LOGNAME=testTMPDIR=/tmp +ENV:PATH=/usr/bin:/bin +ENV:USER=test +FD:0 +FD:1 +FD:2 +DAEMON:no +CWD:/tmp diff --git a/tests/commandhelper.c b/tests/commandhelper.c new file mode 100644 index 0000000..dc8998f --- /dev/null +++ b/tests/commandhelper.c @@ -0,0 +1,136 @@ +/* + * commandhelper.c: Auxiliary program for commandtest + * + * Copyright (C) 2010 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <config.h> + +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <fcntl.h> +#include <string.h> + +#include "internal.h" +#include "util.h" +#include "memory.h" + + +static int envsort(const void *a, const void *b) { + const char *const*astrptr = a; + const char *const*bstrptr = b; + const char *astr = *astrptr; + const char *bstr = *bstrptr; + char *aeq = strchr(astr, '='); + char *beq = strchr(bstr, '='); + char *akey = strndup(astr, aeq - astr); + char *bkey = strndup(bstr, beq - bstr); + int ret = strcmp(akey, bkey); + free(akey); + free(bkey); + return ret; +} + +int main(int argc, char **argv) { + int i, n; + char **origenv; + char **newenv; + FILE *log = fopen(abs_builddir "/commandhelper.log", "w"); + + if (!log) + goto error; + + for (i = 1 ; i < argc ; i++) { + fprintf(log, "ARG:%s\n", argv[i]); + } + + origenv = environ; + n = 0; + while (*origenv != NULL) { + n++; + origenv++; + } + + if (VIR_ALLOC_N(newenv, n) < 0) { + exit(EXIT_FAILURE); + } + + origenv = environ; + n = i = 0; + while (*origenv != NULL) { + newenv[i++] = *origenv; + n++; + origenv++; + } + qsort(newenv, n, sizeof(newenv[0]), envsort); + + for (i = 0 ; i < n ; i++) { + fprintf(log, "ENV:%s\n", newenv[i]); + } + + for (i = 0 ; i < sysconf(_SC_OPEN_MAX) ; i++) { + int f; + int closed; + if (i == fileno(log)) + continue; + closed = fcntl(i, F_GETFD, &f) == -1 && + errno == EBADF; + if (!closed) + fprintf(log, "FD:%d\n", i); + } + + fprintf(log, "DAEMON:%s\n", getppid() == 1 ? "yes" : "no"); + char cwd[1024]; + getcwd(cwd, sizeof(cwd)); + if (strlen(cwd) > strlen("/commanddata") && + STREQ(cwd + strlen(cwd) - strlen("/commanddata"), "/commanddata")) + strcpy(cwd, ".../commanddata"); + fprintf(log, "CWD:%s\n", cwd); + + fclose(log); + + char buf[1024]; + ssize_t got; + + fprintf(stdout, "BEGIN STDOUT\n"); + fflush(stdout); + fprintf(stderr, "BEGIN STDERR\n"); + fflush(stderr); + + for (;;) { + got = read(STDIN_FILENO, buf, sizeof(buf)); + if (got < 0) + goto error; + if (got == 0) + break; + if (safewrite(STDOUT_FILENO, buf, got) != got) + goto error; + if (safewrite(STDERR_FILENO, buf, got) != got) + goto error; + } + + fprintf(stdout, "END STDOUT\n"); + fflush(stdout); + fprintf(stderr, "END STDERR\n"); + fflush(stderr); + + return EXIT_SUCCESS; + +error: + return EXIT_FAILURE; +} diff --git a/tests/commandtest.c b/tests/commandtest.c new file mode 100644 index 0000000..5479bfc --- /dev/null +++ b/tests/commandtest.c @@ -0,0 +1,572 @@ +/* + * commandtest.c: Test the libCommand API + * + * Copyright (C) 2010 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <signal.h> + +#include "testutils.h" +#include "internal.h" +#include "nodeinfo.h" +#include "util.h" +#include "memory.h" +#include "command.h" + +#ifndef __linux__ + +static int +mymain(int argc ATTRIBUTE_UNUSED, char **argv ATTRIBUTE_UNUSED) +{ + exit (EXIT_AM_SKIP); +} + +#else + +static char *progname; +static char *abs_srcdir; + + +static int checkoutput(const char *testname) { + int ret = -1; + char cwd[1024]; + char *expectname = NULL; + char *expectlog = NULL; + char *actualname = NULL; + char *actuallog = NULL; + + if (!getcwd(cwd, sizeof(cwd))) + return -1; + + if (virAsprintf(&expectname, "%s/commanddata/%s.log", abs_srcdir, + testname) < 0) + goto cleanup; + if (virAsprintf(&actualname, "%s/commandhelper.log", abs_builddir) < 0) + goto cleanup; + + if (virFileReadAll(expectname, 1024*64, &expectlog) < 0) { + fprintf(stderr, "cannot read %s\n", expectname); + goto cleanup; + } + + if (virFileReadAll(actualname, 1024*64, &actuallog) < 0) { + fprintf(stderr, "cannot read %s\n", actualname); + goto cleanup; + } + + if (STRNEQ(expectlog, actuallog)) { + virtTestDifference(stderr, expectlog, actuallog); + goto cleanup; + } + + + ret = 0; + +cleanup: + unlink(actuallog); + VIR_FREE(actuallog); + VIR_FREE(actualname); + VIR_FREE(expectlog); + VIR_FREE(expectname); + return ret; +} + +/* + * Run program, no args, inherit all ENV, keep CWD. + * Only stdin/out/err open + * No slot for return status must log error. + */ +static int test0(const void *unused ATTRIBUTE_UNUSED) { + virCommandPtr cmd; + char *log; + int ret = -1; + + free(virtTestLogContentAndReset()); + cmd = virCommandNew(abs_builddir "/commandhelper-doesnotexist"); + if (virCommandRun(cmd, NULL) == 0) + goto cleanup; + if ((log = virtTestLogContentAndReset()) == NULL) + goto cleanup; + if (strstr(log, ": error :") == NULL) + goto cleanup; + virResetLastError(); + ret = 0; + +cleanup: + virCommandFree(cmd); + return ret; +} + +/* + * Run program, no args, inherit all ENV, keep CWD. + * Only stdin/out/err open + * Capturing return status must not log error. + */ +static int test1(const void *unused ATTRIBUTE_UNUSED) { + virCommandPtr cmd; + int ret = -1; + int status; + + cmd = virCommandNew(abs_builddir "/commandhelper-doesnotexist"); + if (virCommandRun(cmd, &status) < 0) + goto cleanup; + if (status == 0) + goto cleanup; + ret = 0; + +cleanup: + virCommandFree(cmd); + return ret; +} + +/* + * Run program, no args, inherit all ENV, keep CWD. + * Only stdin/out/err open + */ +static int test2(const void *unused ATTRIBUTE_UNUSED) { + virCommandPtr cmd = virCommandNew(abs_builddir "/commandhelper"); + + if (virCommandRun(cmd, NULL) < 0) { + virErrorPtr err = virGetLastError(); + printf("Cannot run child %s\n", err->message); + return -1; + } + + virCommandFree(cmd); + + return checkoutput("test2"); +} + +/* + * Run program, no args, inherit all ENV, keep CWD. + * stdin/out/err + two extra FD open + */ +static int test3(const void *unused ATTRIBUTE_UNUSED) { + virCommandPtr cmd = virCommandNew(abs_builddir "/commandhelper"); + int newfd1 = dup(STDERR_FILENO); + int newfd2 = dup(STDERR_FILENO); + int newfd3 = dup(STDERR_FILENO); + close(newfd2); + + virCommandPreserveFD(cmd, newfd1); + virCommandPreserveFD(cmd, newfd3); + + if (virCommandRun(cmd, NULL) < 0) { + virErrorPtr err = virGetLastError(); + printf("Cannot run child %s\n", err->message); + return -1; + } + + virCommandFree(cmd); + + return checkoutput("test3"); +} + + +/* + * Run program, no args, inherit all ENV, CWD is / + * Only stdin/out/err open. + * Daemonized + */ +static int test4(const void *unused ATTRIBUTE_UNUSED) { + virCommandPtr cmd = virCommandNew(abs_builddir "/commandhelper"); + pid_t pid; + char *pidfile = virFilePid(abs_builddir, "commandhelper"); + + virCommandSetPidFile(cmd, pidfile); + virCommandDaemonize(cmd); + + if (virCommandRun(cmd, NULL) < 0) { + virErrorPtr err = virGetLastError(); + printf("Cannot run child %s\n", err->message); + return -1; + } + + if (virFileReadPid(abs_builddir, "commandhelper", &pid) != 0) { + printf("cannot read pidfile\n"); + return -1; + } + while (kill(pid, 0) != -1) + usleep(100*1000); + + virCommandFree(cmd); + + VIR_FREE(pidfile); + + return checkoutput("test4"); +} + + +/* + * Run program, no args, inherit filtered ENV, keep CWD. + * Only stdin/out/err open + */ +static int test5(const void *unused ATTRIBUTE_UNUSED) { + virCommandPtr cmd = virCommandNew(abs_builddir "/commandhelper"); + + virCommandAddEnvPassCommon(cmd); + + if (virCommandRun(cmd, NULL) < 0) { + virErrorPtr err = virGetLastError(); + printf("Cannot run child %s\n", err->message); + return -1; + } + + virCommandFree(cmd); + + return checkoutput("test5"); +} + + +/* + * Run program, no args, inherit filtered ENV, keep CWD. + * Only stdin/out/err open + */ +static int test6(const void *unused ATTRIBUTE_UNUSED) { + virCommandPtr cmd = virCommandNew(abs_builddir "/commandhelper"); + + virCommandAddEnvPass(cmd, "DISPLAY"); + virCommandAddEnvPass(cmd, "DOESNOTEXIST"); + + if (virCommandRun(cmd, NULL) < 0) { + virErrorPtr err = virGetLastError(); + printf("Cannot run child %s\n", err->message); + return -1; + } + + virCommandFree(cmd); + + return checkoutput("test6"); +} + + +/* + * Run program, no args, inherit filtered ENV, keep CWD. + * Only stdin/out/err open + */ +static int test7(const void *unused ATTRIBUTE_UNUSED) { + virCommandPtr cmd = virCommandNew(abs_builddir "/commandhelper"); + + virCommandAddEnvPassCommon(cmd); + virCommandAddEnvPass(cmd, "DISPLAY"); + virCommandAddEnvPass(cmd, "DOESNOTEXIST"); + + if (virCommandRun(cmd, NULL) < 0) { + virErrorPtr err = virGetLastError(); + printf("Cannot run child %s\n", err->message); + return -1; + } + + virCommandFree(cmd); + + return checkoutput("test7"); +} + +/* + * Run program, no args, inherit filtered ENV, keep CWD. + * Only stdin/out/err open + */ +static int test8(const void *unused ATTRIBUTE_UNUSED) { + virCommandPtr cmd = virCommandNew(abs_builddir "/commandhelper"); + + virCommandAddEnvString(cmd, "LANG=C"); + virCommandAddEnvPair(cmd, "USER", "test"); + + if (virCommandRun(cmd, NULL) < 0) { + virErrorPtr err = virGetLastError(); + printf("Cannot run child %s\n", err->message); + return -1; + } + + virCommandFree(cmd); + + return checkoutput("test8"); +} + + +/* + * Run program, some args, inherit all ENV, keep CWD. + * Only stdin/out/err open + */ +static int test9(const void *unused ATTRIBUTE_UNUSED) { + virCommandPtr cmd = virCommandNew(abs_builddir "/commandhelper"); + const char* const args[] = { "arg1", "arg2", NULL }; + + virCommandAddArg(cmd, "-version"); + virCommandAddArgPair(cmd, "-log", "bar.log"); + virCommandAddArgSet(cmd, args); + virCommandAddArgList(cmd, "arg3", "arg4", NULL); + + if (virCommandRun(cmd, NULL) < 0) { + virErrorPtr err = virGetLastError(); + printf("Cannot run child %s\n", err->message); + return -1; + } + + virCommandFree(cmd); + + return checkoutput("test9"); +} + + +/* + * Run program, some args, inherit all ENV, keep CWD. + * Only stdin/out/err open + */ +static int test10(const void *unused ATTRIBUTE_UNUSED) { + virCommandPtr cmd = virCommandNew(abs_builddir "/commandhelper"); + const char *const args[] = { + "-version", "-log=bar.log", NULL, + }; + + virCommandAddArgSet(cmd, args); + + if (virCommandRun(cmd, NULL) < 0) { + virErrorPtr err = virGetLastError(); + printf("Cannot run child %s\n", err->message); + return -1; + } + + virCommandFree(cmd); + + return checkoutput("test10"); +} + +/* + * Run program, some args, inherit all ENV, keep CWD. + * Only stdin/out/err open + */ +static int test11(const void *unused ATTRIBUTE_UNUSED) { + const char *args[] = { + abs_builddir "/commandhelper", + "-version", "-log=bar.log", NULL, + }; + virCommandPtr cmd = virCommandNewArgs(args); + + if (virCommandRun(cmd, NULL) < 0) { + virErrorPtr err = virGetLastError(); + printf("Cannot run child %s\n", err->message); + return -1; + } + + virCommandFree(cmd); + + return checkoutput("test11"); +} + +/* + * Run program, no args, inherit all ENV, keep CWD. + * Only stdin/out/err open. Set stdin data + */ +static int test12(const void *unused ATTRIBUTE_UNUSED) { + virCommandPtr cmd = virCommandNew(abs_builddir "/commandhelper"); + + virCommandSetInputBuffer(cmd, "Hello World\n"); + + if (virCommandRun(cmd, NULL) < 0) { + virErrorPtr err = virGetLastError(); + printf("Cannot run child %s\n", err->message); + return -1; + } + + virCommandFree(cmd); + + return checkoutput("test12"); +} + +/* + * Run program, no args, inherit all ENV, keep CWD. + * Only stdin/out/err open. Set stdin data + */ +static int test13(const void *unused ATTRIBUTE_UNUSED) { + virCommandPtr cmd = virCommandNew(abs_builddir "/commandhelper"); + char *outactual = NULL; + const char *outexpect = "BEGIN STDOUT\n" + "Hello World\n" + "END STDOUT\n"; + int ret = -1; + + virCommandSetInputBuffer(cmd, "Hello World\n"); + virCommandSetOutputBuffer(cmd, &outactual); + + if (virCommandRun(cmd, NULL) < 0) { + virErrorPtr err = virGetLastError(); + printf("Cannot run child %s\n", err->message); + return -1; + } + + virCommandFree(cmd); + + if (!STREQ(outactual, outexpect)) { + virtTestDifference(stderr, outactual, outexpect); + goto cleanup; + } + + if (checkoutput("test13") < 0) + goto cleanup; + + ret = 0; + +cleanup: + VIR_FREE(outactual); + return ret; +} + +/* + * Run program, no args, inherit all ENV, keep CWD. + * Only stdin/out/err open. Set stdin data + */ +static int test14(const void *unused ATTRIBUTE_UNUSED) { + virCommandPtr cmd = virCommandNew(abs_builddir "/commandhelper"); + char *outactual = NULL; + const char *outexpect = "BEGIN STDOUT\n" + "Hello World\n" + "END STDOUT\n"; + char *erractual = NULL; + const char *errexpect = "BEGIN STDERR\n" + "Hello World\n" + "END STDERR\n"; + int ret = -1; + + virCommandSetInputBuffer(cmd, "Hello World\n"); + virCommandSetOutputBuffer(cmd, &outactual); + virCommandSetErrorBuffer(cmd, &erractual); + + if (virCommandRun(cmd, NULL) < 0) { + virErrorPtr err = virGetLastError(); + printf("Cannot run child %s\n", err->message); + return -1; + } + + virCommandFree(cmd); + + if (!STREQ(outactual, outexpect)) { + virtTestDifference(stderr, outactual, outexpect); + goto cleanup; + } + if (!STREQ(erractual, errexpect)) { + virtTestDifference(stderr, erractual, errexpect); + goto cleanup; + } + + if (checkoutput("test14") < 0) + goto cleanup; + + ret = 0; + +cleanup: + VIR_FREE(outactual); + VIR_FREE(erractual); + return ret; +} + + +/* + * Run program, no args, inherit all ENV, change CWD. + * Only stdin/out/err open + */ +static int test15(const void *unused ATTRIBUTE_UNUSED) { + virCommandPtr cmd = virCommandNew(abs_builddir "/commandhelper"); + + virCommandSetWorkingDirectory(cmd, abs_builddir "/commanddata"); + + if (virCommandRun(cmd, NULL) < 0) { + virErrorPtr err = virGetLastError(); + printf("Cannot run child %s\n", err->message); + return -1; + } + + virCommandFree(cmd); + + return checkoutput("test15"); +} + +static int +mymain(int argc, char **argv) +{ + int ret = 0; + char cwd[PATH_MAX]; + + abs_srcdir = getenv("abs_srcdir"); + if (!abs_srcdir) + abs_srcdir = getcwd(cwd, sizeof(cwd)); + + progname = argv[0]; + + if (argc > 1) { + fprintf(stderr, "Usage: %s\n", progname); + return(EXIT_FAILURE); + } + + if (chdir("/tmp") < 0) + return(EXIT_FAILURE); + + virInitialize(); + + const char *const newenv[] = { + "PATH=/usr/bin:/bin", + "HOSTNAME=test", + "LANG=C", + "HOME=/home/test", + "USER=test", + "LOGNAME=test" + "TMPDIR=/tmp", + "DISPLAY=:0.0", + NULL + }; + environ = (char **)newenv; + +# define DO_TEST(NAME) \ + if (virtTestRun("Command Exec " #NAME " test", \ + 1, NAME, NULL) < 0) \ + ret = -1 + + char *actualname; + if (virAsprintf(&actualname, "%s/commandhelper.log", abs_builddir) < 0) + return EXIT_FAILURE; + unlink(actualname); + VIR_FREE(actualname); + + DO_TEST(test0); + DO_TEST(test1); + DO_TEST(test2); + DO_TEST(test3); + DO_TEST(test4); + DO_TEST(test5); + DO_TEST(test6); + DO_TEST(test7); + DO_TEST(test8); + DO_TEST(test9); + DO_TEST(test10); + DO_TEST(test11); + DO_TEST(test12); + DO_TEST(test13); + DO_TEST(test14); + DO_TEST(test15); + + return(ret==0 ? EXIT_SUCCESS : EXIT_FAILURE); +} + +#endif /* __linux__ */ + +VIRT_TEST_MAIN(mymain) -- 1.7.3.2

On Wed, Nov 17, 2010 at 09:29:00PM -0700, Eric Blake wrote:
From: Daniel P. Berrange <berrange@redhat.com>
This introduces a new set of APIs in src/util/command.h to use for invoking commands. This is intended to replace all current usage of virRun and virExec variants, with a more flexible and less error prone API.
* src/util/command.c: New file. * src/util/command.h: New header. * src/Makefile.am (UTIL_SOURCES): Build it. * src/libvirt_private.syms: Export symbols internally. * tests/commandtest.c: New test. * tests/Makefile.am (check_PROGRAMS): Run it. * tests/commandhelper.c: Auxiliary program. * tests/commanddata/test2.log - test15.log: New expected outputs. * cfg.mk (useless_free_options): Add virCommandFree. * po/POTFILES.in: New translation. * .x-sc_avoid_write: Add exemption. * tests/.gitignore: Ignore new built file.
+struct _virCommand { + int has_error; /* ENOMEM on allocation failure, -1 for anything else. */ + + char **args; + size_t nargs; + size_t maxargs; + + char **env; + size_t nenv; + size_t maxenv; + + char *pwd; + + /* XXX Use int[] if we ever need to support more than FD_SETSIZE fd's. */ + fd_set preserve; + unsigned int flags; + + char *inbuf; + char **outbuf; + char **errbuf; + + int infd; + int inpipe; + int outfd; + int errfd; + int *outfdptr; + int *errfdptr; + + virExecHook hook; + void *opaque; + + pid_t pid; + char *pidfile; +}; +/* + * Release all resources + */ +void +virCommandFree(virCommandPtr cmd) +{ + int i; + if (!cmd) + return; + + VIR_FORCE_CLOSE(cmd->outfd); + VIR_FORCE_CLOSE(cmd->errfd); + + for (i = 0 ; i < cmd->nargs ; i++) + VIR_FREE(cmd->args[i]); + VIR_FREE(cmd->args); + + for (i = 0 ; i < cmd->nenv ; i++) + VIR_FREE(cmd->env[i]); + VIR_FREE(cmd->env); + + VIR_FREE(cmd->pwd); + + VIR_FREE(cmd->pidfile); + + VIR_FREE(cmd); +}
My code forgot to ever close() the fds in cmd->preserve. We definitely need todo it in virCommandFree(), but there's a small argument to say we should also do it in virCommandRun/virCommandRunAsync so that if the caller keeps the virCommandPtr alive for a long time, we don't have the open FDs. It would also be useful to have a generic API for logging info about the command to an FD (to let us remove that logging code from UML and QEMU & LXC drivers). eg +void virCommandWriteArgLog(virCommandPtr cmd, int logfd) +{ + int ioError = 0; + int i; + + for (i = 0 ; i < cmd->nenv ; i++) { + if (safewrite(logfd, cmd->env[i], strlen(cmd->env[i])) < 0) + ioError = errno; + if (safewrite(logfd, " ", 1) < 0) + ioError = errno; + } + for (i = 0 ; i < cmd->nargs ; i++) { + if (safewrite(logfd, cmd->args[i], strlen(cmd->args[i])) < 0) + ioError = errno; + if (safewrite(logfd, " ", 1) < 0) + ioError = errno; + } + + if (ioError) { + char ebuf[1024]; + VIR_WARN("Unable to write command %s args to logfile: %s", + cmd->args[0], virStrerror(ioError, ebuf, sizeof ebuf)); + } +} Regards, Daniel -- |: Red Hat, Engineering, London -o- http://people.redhat.com/berrange/ :| |: http://libvirt.org -o- http://virt-manager.org -o- http://deltacloud.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: GnuPG: 7D3B9505 -o- F3C9 553F A1DA 4AC2 5648 23C1 B3DF F742 7D3B 9505 :|

On 11/18/2010 02:55 AM, Daniel P. Berrange wrote:
On Wed, Nov 17, 2010 at 09:29:00PM -0700, Eric Blake wrote:
From: Daniel P. Berrange <berrange@redhat.com>
This introduces a new set of APIs in src/util/command.h to use for invoking commands. This is intended to replace all current usage of virRun and virExec variants, with a more flexible and less error prone API.
My code forgot to ever close() the fds in cmd->preserve. We definitely need todo it in virCommandFree(), but there's a small argument to say we should also do it in virCommandRun/virCommandRunAsync so that if the caller keeps the virCommandPtr alive for a long time, we don't have the open FDs.
I'll look into this more.
It would also be useful to have a generic API for logging info about the command to an FD (to let us remove that logging code from UML and QEMU & LXC drivers).
eg
+void virCommandWriteArgLog(virCommandPtr cmd, int logfd)
Would this be called before virCommandRun and do the logging immediately at that point (or, if something has gone wrong such as allocation failure, then nothing is logged, and the user will eventually learn that nothing was logged when virCommandRun fails), or is it something where you call it once to register the logfd, then calling virCommandRun is what actually writes to the registered fd at the time the child is actually spawned? -- Eric Blake eblake@redhat.com +1-801-349-2682 Libvirt virtualization library http://libvirt.org

On 11/19/2010 05:16 PM, Eric Blake wrote:
On 11/18/2010 02:55 AM, Daniel P. Berrange wrote:
On Wed, Nov 17, 2010 at 09:29:00PM -0700, Eric Blake wrote:
From: Daniel P. Berrange <berrange@redhat.com>
This introduces a new set of APIs in src/util/command.h to use for invoking commands. This is intended to replace all current usage of virRun and virExec variants, with a more flexible and less error prone API.
My code forgot to ever close() the fds in cmd->preserve. We definitely need todo it in virCommandFree(), but there's a small argument to say we should also do it in virCommandRun/virCommandRunAsync so that if the caller keeps the virCommandPtr alive for a long time, we don't have the open FDs.
I'll look into this more.
On thinking about this more, I'm thinking that the user should be able to request whether the fd remains open after the command has been executed (for example, the client may want to share an fd among multiple child processes, in which case each virCommandRun must not close the fd); doable by adding another argument to virCommandPreserveFD.
It would also be useful to have a generic API for logging info about the command to an FD (to let us remove that logging code from UML and QEMU & LXC drivers).
eg
+void virCommandWriteArgLog(virCommandPtr cmd, int logfd)
Okay, I see how you added that in your variant in today's locking series, along with virCommandToString; now folded into my v2. Here's the interdiff of what will be in my v2: diff --git c/src/util/command.c w/src/util/command.c index 71db2a1..4e1071d 100644 --- c/src/util/command.c +++ w/src/util/command.c @@ -33,6 +33,7 @@ #include "util.h" #include "logging.h" #include "files.h" +#include "buf.h" #define VIR_FROM_THIS VIR_FROM_NONE @@ -54,7 +55,9 @@ struct _virCommand { char *pwd; /* XXX Use int[] if we ever need to support more than FD_SETSIZE fd's. */ - fd_set preserve; + fd_set preserve; /* FDs to pass to child. */ + fd_set transfer; /* FDs to close in parent. */ + unsigned int flags; char *inbuf; @@ -99,6 +102,7 @@ virCommandNewArgs(const char *const*args) return NULL; FD_ZERO(&cmd->preserve); + FD_ZERO(&cmd->transfer); cmd->infd = cmd->outfd = cmd->errfd = -1; cmd->inpipe = -1; cmd->pid = -1; @@ -117,20 +121,25 @@ virCommandNewArgs(const char *const*args) /* * Preserve the specified file descriptor in the child, instead of * closing it. FD must not be one of the three standard streams. + * If transfer is true, then fd will be closed in the parent after + * a call to Run/RunAsync, otherwise caller is still responsible for fd. */ void -virCommandPreserveFD(virCommandPtr cmd, int fd) +virCommandPreserveFD(virCommandPtr cmd, int fd, bool transfer) { - if (!cmd || cmd->has_error) + if (!cmd) return; if (fd <= STDERR_FILENO || FD_SETSIZE <= fd) { - cmd->has_error = -1; + if (!cmd->has_error) + cmd->has_error = -1; VIR_DEBUG("cannot preserve %d", fd); return; } FD_SET(fd, &cmd->preserve); + if (transfer) + FD_SET(fd, &cmd->transfer); } /* @@ -569,6 +578,89 @@ virCommandSetPreExecHook(virCommandPtr cmd, virExecHook hook, void *opaque) /* + * Call after adding all arguments and environment settings, but before + * Run/RunAsync, to immediately output the environment and arguments of + * cmd to logfd. If virCommandRun cannot succeed (because of an + * out-of-memory condition while building cmd), nothing will be logged. + */ +void +virCommandWriteArgLog(virCommandPtr cmd, int logfd) +{ + int ioError = 0; + size_t i; + + /* Any errors will be reported later by virCommandRun, which means + * no command will be run, so there is nothing to log. */ + if (!cmd || cmd->has_error) + return; + + for (i = 0 ; i < cmd->nenv ; i++) { + if (safewrite(logfd, cmd->env[i], strlen(cmd->env[i])) < 0) + ioError = errno; + if (safewrite(logfd, " ", 1) < 0) + ioError = errno; + } + for (i = 0 ; i < cmd->nargs ; i++) { + if (safewrite(logfd, cmd->args[i], strlen(cmd->args[i])) < 0) + ioError = errno; + if (safewrite(logfd, i == cmd->nargs - 1 ? "\n" : " ", 1) < 0) + ioError = errno; + } + + if (ioError) { + char ebuf[1024]; + VIR_WARN("Unable to write command %s args to logfile: %s", + cmd->args[0], virStrerror(ioError, ebuf, sizeof ebuf)); + } +} + + +/* + * Call after adding all arguments and environment settings, but before + * Run/RunAsync, to return a string representation of the environment and + * arguments of cmd. If virCommandRun cannot succeed (because of an + * out-of-memory condition while building cmd), NULL will be returned. + * Caller is responsible for freeing the resulting string. + */ +char * +virCommandToString(virCommandPtr cmd) +{ + size_t i; + virBuffer buf = VIR_BUFFER_INITIALIZER; + + /* Cannot assume virCommandRun will be called; so report the error + * now. If virCommandRun is called, it will report the same error. */ + if (!cmd || cmd->has_error == -1) { + virCommandError(VIR_ERR_INTERNAL_ERROR, "%s", + _("invalid use of command API")); + return NULL; + } + if (cmd->has_error == ENOMEM) { + virReportOOMError(); + return NULL; + } + + for (i = 0; i < cmd->nenv; i++) { + virBufferAdd(&buf, cmd->env[i], strlen(cmd->env[i])); + virBufferAddChar(&buf, ' '); + } + virBufferAdd(&buf, cmd->args[0], strlen(cmd->args[0])); + for (i = 1; i < cmd->nargs; i++) { + virBufferAddChar(&buf, ' '); + virBufferAdd(&buf, cmd->args[i], strlen(cmd->args[i])); + } + + if (virBufferError(&buf)) { + virBufferFreeAndReset(&buf); + virReportOOMError(); + return NULL; + } + + return virBufferContentAndReset(&buf); +} + + +/* * Manage input and output to the child process. */ static int @@ -823,6 +915,7 @@ virCommandRunAsync(virCommandPtr cmd, pid_t *pid) { int ret; char *str; + int i; if (!cmd || cmd->has_error == -1) { virCommandError(VIR_ERR_INTERNAL_ERROR, "%s", @@ -868,6 +961,13 @@ virCommandRunAsync(virCommandPtr cmd, pid_t *pid) VIR_DEBUG("Command result %d, with PID %d", ret, (int)cmd->pid); + for (i = STDERR_FILENO + 1; i < FD_SETSIZE; i++) { + if (FD_ISSET(i, &cmd->transfer)) { + int tmpfd = i; + VIR_FORCE_CLOSE(tmpfd); + } + } + if (ret == 0 && pid) *pid = cmd->pid; diff --git c/src/util/command.h w/src/util/command.h index ca24869..a419140 100644 --- c/src/util/command.h +++ w/src/util/command.h @@ -22,6 +22,8 @@ #ifndef __VIR_COMMAND_H__ # define __VIR_COMMAND_H__ +# include <stdbool.h> + # include "internal.h" # include "util.h" @@ -40,15 +42,18 @@ virCommandPtr virCommandNew(const char *binary) ATTRIBUTE_NONNULL(1); virCommandPtr virCommandNewArgs(const char *const*args) ATTRIBUTE_NONNULL(1); /* All error report from these setup APIs is - * delayed until the Run/Exec/Wait methods + * delayed until the Run/RunAsync methods */ /* * Preserve the specified file descriptor - * in the child, instead of closing it + * in the child, instead of closing it. + * If transfer is true, then fd will be closed in the parent after + * a call to Run/RunAsync, otherwise caller is still responsible for fd. */ void virCommandPreserveFD(virCommandPtr cmd, - int fd); + int fd, + bool transfer); /* * Save the child PID in a pidfile @@ -179,6 +184,24 @@ void virCommandSetPreExecHook(virCommandPtr cmd, void *opaque) ATTRIBUTE_NONNULL(2); /* + * Call after adding all arguments and environment settings, but before + * Run/RunAsync, to immediately output the environment and arguments of + * cmd to logfd. If virCommandRun cannot succeed (because of an + * out-of-memory condition while building cmd), nothing will be logged. + */ +void virCommandWriteArgLog(virCommandPtr cmd, + int logfd); + +/* + * Call after adding all arguments and environment settings, but before + * Run/RunAsync, to return a string representation of the environment and + * arguments of cmd. If virCommandRun cannot succeed (because of an + * out-of-memory condition while building cmd), NULL will be returned. + * Caller is responsible for freeing the resulting string. + */ +char *virCommandToString(virCommandPtr cmd) ATTRIBUTE_RETURN_CHECK; + +/* * Run the command and wait for completion. * Returns -1 on any error executing the * command. Returns 0 if the command executed, diff --git c/tests/commanddata/test16.log w/tests/commanddata/test16.log new file mode 100644 index 0000000..2433a4d --- /dev/null +++ w/tests/commanddata/test16.log @@ -0,0 +1 @@ +A=B /bin/true C diff --git c/tests/commandhelper.c w/tests/commandhelper.c index dc8998f..2ee9153 100644 --- c/tests/commandhelper.c +++ w/tests/commandhelper.c @@ -29,6 +29,7 @@ #include "internal.h" #include "util.h" #include "memory.h" +#include "files.h" static int envsort(const void *a, const void *b) { @@ -102,7 +103,7 @@ int main(int argc, char **argv) { strcpy(cwd, ".../commanddata"); fprintf(log, "CWD:%s\n", cwd); - fclose(log); + VIR_FORCE_FCLOSE(log); char buf[1024]; ssize_t got; diff --git c/tests/commandtest.c w/tests/commandtest.c index 5479bfc..de1597d 100644 --- c/tests/commandtest.c +++ w/tests/commandtest.c @@ -25,6 +25,8 @@ #include <string.h> #include <unistd.h> #include <signal.h> +#include <sys/stat.h> +#include <fcntl.h> #include "testutils.h" #include "internal.h" @@ -32,6 +34,7 @@ #include "util.h" #include "memory.h" #include "command.h" +#include "files.h" #ifndef __linux__ @@ -166,10 +169,9 @@ static int test3(const void *unused ATTRIBUTE_UNUSED) { int newfd1 = dup(STDERR_FILENO); int newfd2 = dup(STDERR_FILENO); int newfd3 = dup(STDERR_FILENO); - close(newfd2); - virCommandPreserveFD(cmd, newfd1); - virCommandPreserveFD(cmd, newfd3); + virCommandPreserveFD(cmd, newfd1, false); + virCommandPreserveFD(cmd, newfd3, true); if (virCommandRun(cmd, NULL) < 0) { virErrorPtr err = virGetLastError(); @@ -177,7 +179,17 @@ static int test3(const void *unused ATTRIBUTE_UNUSED) { return -1; } + if (fcntl(newfd1, F_GETFL) < 0 || + fcntl(newfd2, F_GETFL) < 0 || + fcntl(newfd3, F_GETFL) >= 0) { + puts("fds in wrong state"); + return -1; + } + virCommandFree(cmd); + VIR_FORCE_CLOSE(newfd1); + VIR_FORCE_CLOSE(newfd2); return checkoutput("test3"); } @@ -501,6 +513,52 @@ static int test15(const void *unused ATTRIBUTE_UNUSED) { return checkoutput("test15"); } +/* + * Don't run program; rather, log what would be run. + */ +static int test16(const void *unused ATTRIBUTE_UNUSED) { + virCommandPtr cmd = virCommandNew("/bin/true"); + char *outactual = NULL; + const char *outexpect = "A=B /bin/true C"; + int ret = -1; + int fd = -1; + + virCommandAddEnvPair(cmd, "A", "B"); + virCommandAddArg(cmd, "C"); + + if ((outactual = virCommandToString(cmd)) == NULL) { + virErrorPtr err = virGetLastError(); + printf("Cannot convert to string: %s\n", err->message); + return -1; + } + if ((fd = open(abs_builddir "/commandhelper.log", + O_CREAT | O_TRUNC | O_WRONLY, 0600)) < 0) { + printf("Cannot open log file: %s\n", strerror (errno)); + goto cleanup; + } + virCommandWriteArgLog(cmd, fd); + if (VIR_CLOSE(fd) < 0) { + printf("Cannot close log file: %s\n", strerror (errno)); + goto cleanup; + } + + virCommandFree(cmd); + + if (checkoutput("test16") < 0) + goto cleanup; + + if (!STREQ(outactual, outexpect)) { + virtTestDifference(stderr, outactual, outexpect); + goto cleanup; + } + ret = 0; + +cleanup: + VIR_FORCE_CLOSE(fd); + VIR_FREE(outactual); + return ret; +} + static int mymain(int argc, char **argv) { @@ -563,6 +621,7 @@ mymain(int argc, char **argv) DO_TEST(test13); DO_TEST(test14); DO_TEST(test15); + DO_TEST(test16); return(ret==0 ? EXIT_SUCCESS : EXIT_FAILURE); } -- Eric Blake eblake@redhat.com +1-801-349-2682 Libvirt virtualization library http://libvirt.org

On Mon, Nov 22, 2010 at 03:30:35PM -0700, Eric Blake wrote:
On 11/19/2010 05:16 PM, Eric Blake wrote:
On 11/18/2010 02:55 AM, Daniel P. Berrange wrote:
On Wed, Nov 17, 2010 at 09:29:00PM -0700, Eric Blake wrote:
From: Daniel P. Berrange <berrange@redhat.com>
This introduces a new set of APIs in src/util/command.h to use for invoking commands. This is intended to replace all current usage of virRun and virExec variants, with a more flexible and less error prone API.
My code forgot to ever close() the fds in cmd->preserve. We definitely need todo it in virCommandFree(), but there's a small argument to say we should also do it in virCommandRun/virCommandRunAsync so that if the caller keeps the virCommandPtr alive for a long time, we don't have the open FDs.
I'll look into this more.
On thinking about this more, I'm thinking that the user should be able to request whether the fd remains open after the command has been executed (for example, the client may want to share an fd among multiple child processes, in which case each virCommandRun must not close the fd); doable by adding another argument to virCommandPreserveFD.
It would also be useful to have a generic API for logging info about the command to an FD (to let us remove that logging code from UML and QEMU & LXC drivers).
eg
+void virCommandWriteArgLog(virCommandPtr cmd, int logfd)
Okay, I see how you added that in your variant in today's locking series, along with virCommandToString; now folded into my v2.
Ah yes, I forgot to mention that - it came in useful when converting the QEMU driver to the new APIs.
@@ -117,20 +121,25 @@ virCommandNewArgs(const char *const*args) /* * Preserve the specified file descriptor in the child, instead of * closing it. FD must not be one of the three standard streams. + * If transfer is true, then fd will be closed in the parent after + * a call to Run/RunAsync, otherwise caller is still responsible for fd. */ void -virCommandPreserveFD(virCommandPtr cmd, int fd) +virCommandPreserveFD(virCommandPtr cmd, int fd, bool transfer)
How about having two separate methods ? virCommandPreserveFD virCommandTransferFD Means you don't have to remember whether true means transfer or don't transfer
@@ -868,6 +961,13 @@ virCommandRunAsync(virCommandPtr cmd, pid_t *pid) VIR_DEBUG("Command result %d, with PID %d", ret, (int)cmd->pid);
+ for (i = STDERR_FILENO + 1; i < FD_SETSIZE; i++) { + if (FD_ISSET(i, &cmd->transfer)) { + int tmpfd = i; + VIR_FORCE_CLOSE(tmpfd); + } + } + if (ret == 0 && pid) *pid = cmd->pid;
I think we also need duplicate this in virCommandFree(), because there may be scenarios where we get 1/2 way through populating a virCommandPtr instance, and then some other error means we have to stop & free it, without ever getting to virCommandRunAsync. Daniel

From: Daniel P. Berrange <berrange@redhat.com> --- docs/Makefile.am | 11 +- docs/internals.html.in | 9 + docs/internals/command.html.in | 491 ++++++++++++++++++++++++++++++++++++++++ docs/sitemap.html.in | 4 + docs/subsite.xsl | 25 ++ 5 files changed, 539 insertions(+), 1 deletions(-) create mode 100644 docs/internals/command.html.in create mode 100644 docs/subsite.xsl diff --git a/docs/Makefile.am b/docs/Makefile.am index b28e04e..ce0b391 100644 --- a/docs/Makefile.am +++ b/docs/Makefile.am @@ -60,7 +60,8 @@ gif = \ architecture.gif \ node.gif -dot_html_in = $(notdir $(wildcard $(srcdir)/*.html.in)) todo.html.in +dot_html_in = $(notdir $(wildcard $(srcdir)/*.html.in)) todo.html.in \ + $(patsubst $(srcdir)/%,%,$(wildcard $(srcdir)/internals/*.html.in)) dot_html = $(dot_html_in:%.html.in=%.html) patches = $(wildcard api_extension/*.patch) @@ -113,6 +114,14 @@ todo: %.png: %.fig convert -rotate 90 $< $@ +internals/%.html.tmp: internals/%.html.in subsite.xsl page.xsl sitemap.html.in + @if [ -x $(XSLTPROC) ] ; then \ + echo "Generating $@"; \ + name=`echo $@ | sed -e 's/.tmp//'`; \ + $(XSLTPROC) --stringparam pagename $$name --nonet --html \ + $(top_srcdir)/docs/subsite.xsl $< > $@ \ + || { rm $@ && exit 1; }; fi + %.html.tmp: %.html.in site.xsl page.xsl sitemap.html.in @if [ -x $(XSLTPROC) ] ; then \ echo "Generating $@"; \ diff --git a/docs/internals.html.in b/docs/internals.html.in index d39098e..dc88eab 100644 --- a/docs/internals.html.in +++ b/docs/internals.html.in @@ -7,5 +7,14 @@ internals, adding new public APIs, new hypervisor drivers or extending the libvirtd daemon code. </p> + + <ul> + <li>Introduction to basic rules and guidelines for <a href="hacking.html">hacking<a> + on libvirt code</li> + <li>Guide to adding <a href="api_extension.html">public APIs<a></li> + <li>Approach for <a href="internals/command.html">spawning commands</a> from + libvirt driver code</li> + </ul> + </body> </html> diff --git a/docs/internals/command.html.in b/docs/internals/command.html.in new file mode 100644 index 0000000..3d28cd4 --- /dev/null +++ b/docs/internals/command.html.in @@ -0,0 +1,491 @@ +<html> + <body> + <h1>Spawning processes / commands from libvirt drivers</h1> + + <ul id="toc"></ul> + + <p> + This page describes the usage of libvirt APIs for + spawning processes / commands from libvirt drivers. + All code is required to use these APIs + </p> + + <h2><a name="posix">Problems with standard POSIX APIs</a></h2> + + <p> + The POSIX specification includes a number of APIs for + spawning processes / commands, but they suffer from + a number of flaws + </p> + + <ul> + <li><code>fork+exec</code>: The lowest & most flexible + level, but very hard to use correctly / safely. It + is easy to leak file descriptors, have unexpected + signal handler behaviour and not handle edge cases. + Furthermore, it is not portable to mingw. + </li> + <li><code>system</code>: Convenient if you don't care + about capturing command output, but has the serious + downside that the command string is interpreted by + the shell. This makes it very dangerous to use, because + improperly validated user input can lead to exploits + via shell meta characters. + </li> + <li><code>popen</code>: Inherits the flaws of + <code>system</code>, and has no option for bi-directional + communication. + </li> + <li><code>posix_spawn</code>: A half-way house between + simplicity of system() and the flexibility of fork+exec. + It does not allow for a couple of important features + though, such as running a hook between the fork+exec + stage, or closing all open file descriptors.</li> + </ul> + + <p> + Due to the problems mentioned with each of these, + libvirt driver code <strong>must not use</strong> any + of the above APIs. Historically libvirt provided a + higher level API known as virExec. This was wrapper + around fork+exec, in a similar style to posix_spawn, + but with a few more features. + </p> + + <p> + This wrapper still suffered from a number of problems. + Handling command cleanup via waitpid() is overly + complex & error prone for most usage. Building up the + argv[] + env[] string arrays is quite cumbersome and + error prone, particularly wrt memory leak / OOM handling. + </p> + + <h2><a name="api">The libvirt command execution API</a></h2> + + <p> + There is now a high level API that provides a safe and + flexible way to spawn commands, which prevents the most + common errors & is easy to code against. This + code is provided in the <code>src/util/command.h</code> + header which can be imported using <code>#include "command.h"</code> + </p> + + <h3><a name="initial">Defining commands in libvirt</a></h3> + + <p> + The first step is to declare what command is to be + executed. The command name can be either a fully + qualified path, or a bare command name. In the latter + case it will be resolved wrt the <code>$PATH</code> + environment variable. + </p> + +<pre> + virCommandPtr cmd = virCommandNew("/usr/bin/dnsmasq"); +</pre> + + <p> + There is no need to check for allocation failure after + <code>virCommandNew</code>. This will be detected and + reported at a later time. + </p> + + <h3><a name="args">Adding arguments to the command</a></h3> + + <p> + There are a number of APIs for adding arguments to a + command. To add a direct string arg + </p> + +<pre> + virCommandAddArg(cmd, "-strict-order"); +</pre> + + <p> + If an argument takes an attached value of the form + <code>-arg=val</code>, then this can be done using + </p> + +<pre> + virCommandAddArgPair(cmd, "--conf-file", "/etc/dnsmasq.conf"); +</pre> + + <p> + To add a entire NULL terminated array of arguments in one go, + there are two options. + </p> + +<pre> + const char *const args[] = { + "--strict-order", "--except-interface", "lo", NULL + }; + virCommandAddArgSet(cmd, args); + virCommandAddArgList(cmd, "--domain", "localdomain", NULL); +</pre> + + <p> + This latter option can also be used at time of initial + construction of the <code>virCommandPtr</code> object + </p> + +<pre> + const char *const args[] = { + "/usr/bin/dnsmasq", + "--strict-order", "--except-interface", + "lo", "--domain", "localdomain", NULL + }; + virCommandPtr cmd = virCommandNewArgs(cmd, args); +</pre> + + <h3><a name="env">Setting up the environment</a></h3> + + <p> + By default a command will inherit all environment variables + from the current process. Generally this is not desirable + and a customized environment will be more suitable. Any + customization done via the following APIs will prevent + inheritance of any existing environment variables unless + explicitly allowed. The first step is usually to pass through + a small number of variables from the current process. + </p> + +<pre> + virCommandAddEnvPassCommon(cmd); +</pre> + + <p> + This has now set up a clean environment for the child, passing + through <code>PATH</code>, <code>LD_PRELOAD</code>, + <code>LD_LIBRARY_PATH</code>, <code>HOME</code>, + <code>USER</code>, <code>LOGNAME</code> and <code>TMPDIR</code>. + Furthermore it will explicitly set <code>LC_ALL=C</code> to + avoid unexpected localization of command output. Further + variables can be passed through from parent explicitly: + </p> + +<pre> + virCommandAddEnvPass(cmd, "DISPLAY"); + virCommandAddEnvPass(cmd, "XAUTHORITY"); +</pre> + + <p> + To define an environment variable in the child with an + separate key / value: + </p> + +<pre> + virCommandAddEnvPair(cmd, "TERM", "xterm"); +</pre> + + <p> + If the key/value pair is pre-formatted in the right + format, it can be set directly + </p> + +<pre> + virCommandAddEnvString(cmd, "TERM=xterm"); +</pre> + + <h3><a name="misc">Miscellaneous other options</a></h3> + + <p> + Normally the spawned command will retain the current + process and process group as its parent. If the current + process dies, the child will then (usually) be terminated + too. If this cleanup is not desired, then the command + should be marked as daemonized: + </p> + +<pre> + virCommandDaemonize(cmd); +</pre> + + <p> + When daemonizing a command, the PID visible from the + caller will be that of the intermediate process, not + the actual damonized command. If the PID of the real + command is required then a pidfile can be requested + </p> + +<pre> + virCommandSetPidFile(cmd, "/var/run/dnsmasq.pid"); +</pre> + + <p> + This PID file is guaranteed to be written before + the intermediate process exits. + </p> + + <h3><a name="privs">Reducing command privileges</a></h3> + + <p> + Normally a command will inherit all privileges of + the current process. To restrict what a command can + do, it is possible to request that all its capabilities + are cleared. With this done it will only be able to + access resources for which it has explicit DAC permissions + </p> + +<pre> + virCommandClearCaps(cmd); +</pre> + + <h3><a name="fds">Managing file handles</a></h3> + + <p> + To prevent unintended resource leaks to child processes, + all open file handles will be closed in the child, and + its stdin/out/err all connected to <code>/dev/null</code>. + It is possible to allow an open file handle to be passed + into the child: + </p> + +<pre> + virCommandPreserveFD(cmd, 5); +</pre> + + <p> + With this file descriptor 5 in the current process remains + open as file descriptor 5 in the child. For stdin/out/err + it is usually necessary to map a file handle. To attach + file descriptor 7 in the current process to stdin in the + child: + </p> + +<pre> + virCommandSetInputFD(cmd, 7); +</pre> + + <p> + Equivalently to redirect stdout or stderr in the child, + pass in a pointer to the desired handle + </p> + +<pre> + int outfd = open("out.log", "w+"); + int errfd = open("err.log", "w+"); + virCommandSetOutputFD(cmd, &outfd); + virCommandSetErrorFD(cmd, &errfd); +</pre> + + <p> + Alternatively it is possible to request that a pipe be + created to fetch stdout/err in the parent, by initializing + the FD to -1. + </p> + +<pre> + int outfd = -1; + int errfd = -1 + virCommandSetOutputFD(cmd, &outfd); + virCommandSetErrorFD(cmd, &errfd); +</pre> + + <p> + Once the command is running, <code>outfd</code> + and <code>errfd</code> will be initialized with + valid file handles that can be read from. + </p> + + <h3><a name="buffers">Feeding & capturing strings to/from the child</a></h3> + + <p> + Often dealing with file handles for stdin/out/err + is unnecessarily complex. It is possible to specify + a string buffer to act as the data source for the + child's stdin, if there are no embedded NUL bytes + </p> + +<pre> + const char *input = "Hello World\n"; + virCommandSetInputBuffer(cmd, input); +</pre> + + <p> + Similarly it is possible to request that the child's + stdout/err be redirected into a string buffer, if the + output is not expected to contain NUL bytes + </p> + +<pre> + char *output = NULL, *errors = NULL; + virCommandSetOutputBuffer(cmd, &output); + virCommandSetErrorBuffer(cmd, &errors); +</pre> + + <p> + Once the command has finished executing, these buffers + will contain the output. It is the callers responsibility + to free these buffers. + </p> + + <h3><a name="directory">Setting working directory</a></h3> + + <p> + Daemonized commands are always run with "/" as the current + working directory. All other commands default to running in the + same working directory as the parent process, but an alternate + directory can be specified: + </p> + +<pre> + virCommandSetWorkingDirectory(cmd, LOCALSTATEDIR); +</pre> + + <h3><a name="hooks">Any additional hooks</a></h3> + + <p> + If anything else is needed, it is possible to request a hook + function that is called in the child after the fork, as the + last thing before changing directories, dropping capabilities, + and executing the new process. If hook(opaque) returns + non-zero, then the child process will not be run. + </p> + +<pre> + virCommandSetPreExecHook(cmd, hook, opaque); +</pre> + + <h3><a name="sync">Running commands synchronously</a></h3> + + <p> + For most commands, the desired behaviour is to spawn + the command, wait for it to complete & exit and then + check that its exit status is zero + </p> + +<pre> + if (virCommandRun(cmd, NULL) < 0) + return -1; +</pre> + + <p> + <strong>Note:</strong> if the command has been daemonized + this will only block & wait for the intermediate process, + not the real command. <code>virCommandRun</code> will + report on any errors that have occured upon this point + with all previous API calls. If the command fails to + run, or exits with non-zero status an error will be + reported via normal libvirt error infrastructure. If a + non-zero exit status can represent a success condition, + it is possible to request the exit status and perform + that check manually instead of letting <code>virCommandRun</code> + raise the error + </p> + +<pre> + int status; + if (virCommandRun(cmd, &status) < 0) + return -1; + + if (WEXITSTATUS(status) ...) { + ...do stuff... + } +</pre> + + <h3><a name="async">Running commands asynchronously</a></h3> + + <p> + In certain complex scenarios, particularly special + I/O handling is required for the child's stdin/err/out + it will be necessary to run the command asynchronously + and wait for completion separately. + </p> + +<pre> + pid_t pid; + if (virCommandRunAsync(cmd, &pid) < 0) + return -1; + + ... do something while pid is running ... + + int status; + if (virCommandWait(cmd, &status) < 0) + return -1; + + if (WEXITSTATUS(status)...) { + ..do stuff.. + } +</pre> + + <p> + As with <code>virCommandRun</code>, the <code>status</code> + arg for <code>virCommandWait</code> can be omitted, in which + case it will validate that exit status is zero and raise an + error if not. + </p> + + + <h3><a name="release">Releasing resources</a></h3> + + <p> + Once the command has been executed, or if execution + has been abandoned, it is necessary to release + resources associated with the <code>virCommandPtr</code> + object. This is done with: + </p> + +<pre> + virCommandFree(cmd); +</pre> + + <p> + There is no need to check if <code>cmd</code> is NULL + before calling <code>virCommandFree</code>. This scenario + is handled automatically. If the command is still running, + it will be forcably killed and cleaned up (via waitpid). + </p> + + <h2><a name="example">Complete examples</a></h2> + + <p> + This shows a complete example usage of the APIs roughly + using the libvirt source src/util/hooks.c + </p> + +<pre> +int runhook(const char *drvstr, const char *id, + const char *opstr, const char *subopstr, + const char *extra) { + int ret; + char *path; + virCommandPtr cmd; + + ret = virBuildPath(&path, LIBVIRT_HOOK_DIR, drvstr); + if ((ret < 0) || (path == NULL)) { + virHookReportError(VIR_ERR_INTERNAL_ERROR, + _("Failed to build path for %s hook"), + drvstr); + return -1; + } + + cmd = virCommandNew(path); + VIR_FREE(path); + + virCommandAddEnvPassCommon(cmd); + + virCommandAddArgList(cmd, id, opstr, subopstr, extra, NULL); + + virCommandSetInputBuffer(cmd, input); + + ret = virCommandRun(cmd, NULL); + + virCommandFree(cmd); + + return ret; +} +</pre> + + <p> + In this example, the command is being run synchronously. + A pre-formatted string is being fed to the command as + its stdin. The command takes four arguments, and has a + minimal set of environment variables passed down. In + this example, the code does not require any error checking. + All errors are reported by the <code>virCommandRun</code> + method, and the exit status from this is returned to + the caller to handle as desired. + </p> + + </body> +</html> diff --git a/docs/sitemap.html.in b/docs/sitemap.html.in index 692da29..7db59a1 100644 --- a/docs/sitemap.html.in +++ b/docs/sitemap.html.in @@ -260,6 +260,10 @@ <a href="api_extension.html">API extensions</a> <span>Adding new public libvirt APIs</span> </li> + <li> + <a href="internals/command.html">Spawning commands</a> + <span>Spawning commands from libvirt driver code</span> + </li> </ul> </li> <li> diff --git a/docs/subsite.xsl b/docs/subsite.xsl new file mode 100644 index 0000000..108d0d8 --- /dev/null +++ b/docs/subsite.xsl @@ -0,0 +1,25 @@ +<?xml version="1.0"?> +<xsl:stylesheet + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:exsl="http://exslt.org/common" + exclude-result-prefixes="xsl exsl" + version="1.0"> + + <xsl:import href="page.xsl"/> + + <xsl:output + method="xml" + encoding="UTF-8" + indent="yes" + doctype-public="-//W3C//DTD XHTML 1.0 Strict//EN" + doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"/> + + <xsl:variable name="href_base" select="'../'"/> + + <xsl:template match="/"> + <xsl:apply-templates select="." mode="page"> + <xsl:with-param name="pagename" select="$pagename"/> + </xsl:apply-templates> + </xsl:template> + +</xsl:stylesheet> -- 1.7.3.2

On Wed, Nov 17, 2010 at 09:29:01PM -0700, Eric Blake wrote:
From: Daniel P. Berrange <berrange@redhat.com>
--- docs/Makefile.am | 11 +- docs/internals.html.in | 9 + docs/internals/command.html.in | 491 ++++++++++++++++++++++++++++++++++++++++ docs/sitemap.html.in | 4 + docs/subsite.xsl | 25 ++ 5 files changed, 539 insertions(+), 1 deletions(-) create mode 100644 docs/internals/command.html.in create mode 100644 docs/subsite.xsl
ACK Daniel -- |: Red Hat, Engineering, London -o- http://people.redhat.com/berrange/ :| |: http://libvirt.org -o- http://virt-manager.org -o- http://deltacloud.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: GnuPG: 7D3B9505 -o- F3C9 553F A1DA 4AC2 5648 23C1 B3DF F742 7D3B 9505 :|

From: Daniel P. Berrange <berrange@redhat.com> This proof of concept shows how two existing uses of virExec and virRun can be ported to the new virCommand APIs, and how much simpler the code becomes --- src/util/hooks.c | 216 +++------------------------------------------------ src/util/iptables.c | 73 +++-------------- 2 files changed, 24 insertions(+), 265 deletions(-) diff --git a/src/util/hooks.c b/src/util/hooks.c index 2a9df20..1139d88 100644 --- a/src/util/hooks.c +++ b/src/util/hooks.c @@ -37,6 +37,7 @@ #include "memory.h" #include "files.h" #include "configmake.h" +#include "command.h" #define VIR_FROM_THIS VIR_FROM_HOOK @@ -189,36 +190,15 @@ virHookPresent(int driver) { * Returns: 0 if the execution succeeded, 1 if the script was not found or * invalid parameters, and -1 if script returned an error */ -#ifdef WIN32 -int -virHookCall(int driver ATTRIBUTE_UNUSED, - const char *id ATTRIBUTE_UNUSED, - int op ATTRIBUTE_UNUSED, - int sub_op ATTRIBUTE_UNUSED, - const char *extra ATTRIBUTE_UNUSED, - const char *input ATTRIBUTE_UNUSED) { - virReportSystemError(ENOSYS, "%s", - _("spawning hooks not supported on this platform")); - return -1; -} -#else int virHookCall(int driver, const char *id, int op, int sub_op, const char *extra, const char *input) { - int ret, waitret, exitstatus, i; + int ret; char *path; - int argc = 0, arga = 0; - const char **argv = NULL; - int envc = 0, enva = 0; - const char **env = NULL; + virCommandPtr cmd; const char *drvstr; const char *opstr; const char *subopstr; - pid_t pid; - int outfd = -1, errfd = -1; - int pipefd[2] = { -1, -1}; - char *outbuf = NULL; - char *errbuf = NULL; if ((driver < VIR_HOOK_DRIVER_DAEMON) || (driver >= VIR_HOOK_DRIVER_LAST)) @@ -270,192 +250,20 @@ virHookCall(int driver, const char *id, int op, int sub_op, const char *extra, return(-1); } - /* - * Convenience macros borrowed from qemudBuildCommandLine() - */ -# define ADD_ARG_SPACE \ - do { \ - if (argc == arga) { \ - arga += 10; \ - if (VIR_REALLOC_N(argv, arga) < 0) \ - goto no_memory; \ - } \ - } while (0) - -# define ADD_ARG(thisarg) \ - do { \ - ADD_ARG_SPACE; \ - argv[argc++] = thisarg; \ - } while (0) - -# define ADD_ARG_LIT(thisarg) \ - do { \ - ADD_ARG_SPACE; \ - if ((argv[argc++] = strdup(thisarg)) == NULL) \ - goto no_memory; \ - } while (0) - -# define ADD_ENV_SPACE \ - do { \ - if (envc == enva) { \ - enva += 10; \ - if (VIR_REALLOC_N(env, enva) < 0) \ - goto no_memory; \ - } \ - } while (0) - -# define ADD_ENV(thisarg) \ - do { \ - ADD_ENV_SPACE; \ - env[envc++] = thisarg; \ - } while (0) - -# define ADD_ENV_LIT(thisarg) \ - do { \ - ADD_ENV_SPACE; \ - if ((env[envc++] = strdup(thisarg)) == NULL) \ - goto no_memory; \ - } while (0) - -# define ADD_ENV_PAIR(envname, val) \ - do { \ - char *envval; \ - ADD_ENV_SPACE; \ - if (virAsprintf(&envval, "%s=%s", envname, val) < 0) \ - goto no_memory; \ - env[envc++] = envval; \ - } while (0) - -# define ADD_ENV_COPY(envname) \ - do { \ - char *val = getenv(envname); \ - if (val != NULL) { \ - ADD_ENV_PAIR(envname, val); \ - } \ - } while (0) - - ADD_ENV_LIT("LC_ALL=C"); - - ADD_ENV_COPY("LD_PRELOAD"); - ADD_ENV_COPY("LD_LIBRARY_PATH"); - ADD_ENV_COPY("PATH"); - ADD_ENV_COPY("HOME"); - ADD_ENV_COPY("USER"); - ADD_ENV_COPY("LOGNAME"); - ADD_ENV_COPY("TMPDIR"); - ADD_ENV(NULL); - - ADD_ARG_LIT(path); - ADD_ARG_LIT(id); - ADD_ARG_LIT(opstr); - ADD_ARG_LIT(subopstr); - - ADD_ARG_LIT(extra); - ADD_ARG(NULL); - - /* pass any optional input on the script stdin */ - if (input != NULL) { - if (pipe(pipefd) < -1) { - virReportSystemError(errno, "%s", - _("unable to create pipe for hook input")); - ret = 1; - goto cleanup; - } - if (safewrite(pipefd[1], input, strlen(input)) < 0) { - virReportSystemError(errno, "%s", - _("unable to write to pipe for hook input")); - ret = 1; - goto cleanup; - } - ret = virExec(argv, env, NULL, &pid, pipefd[0], &outfd, &errfd, - VIR_EXEC_NONE | VIR_EXEC_NONBLOCK); - if (VIR_CLOSE(pipefd[1]) < 0) { - virReportSystemError(errno, "%s", - _("unable to close pipe for hook input")); - } - } else { - ret = virExec(argv, env, NULL, &pid, -1, &outfd, &errfd, - VIR_EXEC_NONE | VIR_EXEC_NONBLOCK); - } - if (ret < 0) { - virHookReportError(VIR_ERR_HOOK_SCRIPT_FAILED, - _("Failed to execute %s hook script"), - path); - ret = 1; - goto cleanup; - } + cmd = virCommandNew(path); - /* - * we are interested in the error log if any and make sure the - * script doesn't block on stdout/stderr descriptors being full - * stdout can be useful for debug too. - */ - if (virPipeReadUntilEOF(outfd, errfd, &outbuf, &errbuf) < 0) { - virReportSystemError(errno, _("cannot wait for '%s'"), path); - while (waitpid(pid, &exitstatus, 0) == -1 && errno == EINTR) - ; - ret = 1; - goto cleanup; - } + virCommandAddEnvPassCommon(cmd); - if (outbuf) - VIR_DEBUG("Command stdout: %s", outbuf); - if (errbuf) - VIR_DEBUG("Command stderr: %s", errbuf); - - while ((waitret = waitpid(pid, &exitstatus, 0) == -1) && - (errno == EINTR)); - if (waitret == -1) { - virReportSystemError(errno, _("Failed to wait for '%s'"), path); - ret = 1; - goto cleanup; - } - if (exitstatus != 0) { - virHookReportError(VIR_ERR_HOOK_SCRIPT_FAILED, - _("Hook script %s %s failed with error code %d:%s"), - path, drvstr, exitstatus, errbuf); - ret = -1; - } + virCommandAddArgList(cmd, id, opstr, subopstr, extra, NULL); -cleanup: - if (VIR_CLOSE(pipefd[0]) < 0) { - virReportSystemError(errno, "%s", - _("unable to close pipe for hook input")); - ret = 1; - } - if (VIR_CLOSE(pipefd[1]) < 0) { - virReportSystemError(errno, "%s", - _("unable to close pipe for hook input")); - ret = 1; - } - if (argv) { - for (i = 0 ; i < argc ; i++) - VIR_FREE((argv)[i]); - VIR_FREE(argv); - } - if (env) { - for (i = 0 ; i < envc ; i++) - VIR_FREE((env)[i]); - VIR_FREE(env); - } - VIR_FREE(outbuf); - VIR_FREE(errbuf); - VIR_FREE(path); + if (input) + virCommandSetInputBuffer(cmd, input); - return(ret); + ret = virCommandRun(cmd, NULL); -no_memory: - virReportOOMError(); + virCommandFree(cmd); - goto cleanup; + VIR_FREE(path); -# undef ADD_ARG -# undef ADD_ARG_LIT -# undef ADD_ARG_SPACE -# undef ADD_USBDISK -# undef ADD_ENV -# undef ADD_ENV_COPY -# undef ADD_ENV_LIT -# undef ADD_ENV_SPACE + return ret; } -#endif diff --git a/src/util/iptables.c b/src/util/iptables.c index fe78d1c..effd81b 100644 --- a/src/util/iptables.c +++ b/src/util/iptables.c @@ -39,7 +39,7 @@ #include "internal.h" #include "iptables.h" -#include "util.h" +#include "command.h" #include "memory.h" #include "virterror_internal.h" #include "logging.h" @@ -102,72 +102,23 @@ static int ATTRIBUTE_SENTINEL iptablesAddRemoveRule(iptRules *rules, int action, const char *arg, ...) { va_list args; - int retval = ENOMEM; - const char **argv; + int ret; + virCommandPtr cmd; const char *s; - int n; - - n = 1 + /* /sbin/iptables */ - 2 + /* --table foo */ - 2 + /* --insert bar */ - 1; /* arg */ - - va_start(args, arg); - while (va_arg(args, const char *)) - n++; - - va_end(args); - - if (VIR_ALLOC_N(argv, n + 1) < 0) - goto error; - - n = 0; - - if (!(argv[n++] = strdup(IPTABLES_PATH))) - goto error; - - if (!(argv[n++] = strdup("--table"))) - goto error; - - if (!(argv[n++] = strdup(rules->table))) - goto error; - - if (!(argv[n++] = strdup(action == ADD ? "--insert" : "--delete"))) - goto error; - - if (!(argv[n++] = strdup(rules->chain))) - goto error; - if (!(argv[n++] = strdup(arg))) - goto error; + cmd = virCommandNew(IPTABLES_PATH); + virCommandAddArgList(cmd, "--table", rules->table, + action == ADD ? "--insert" : "--delete", + rules->chain, arg, NULL); va_start(args, arg); - - while ((s = va_arg(args, const char *))) { - if (!(argv[n++] = strdup(s))) { - va_end(args); - goto error; - } - } - + while ((s = va_arg(args, const char *))) + virCommandAddArg(cmd, s); va_end(args); - if (virRun(argv, NULL) < 0) { - retval = errno; - goto error; - } - - retval = 0; - - error: - if (argv) { - n = 0; - while (argv[n]) - VIR_FREE(argv[n++]); - VIR_FREE(argv); - } - - return retval; + ret = virCommandRun(cmd, NULL); + virCommandFree(cmd); + return ret; } /** -- 1.7.3.2

On Wed, Nov 17, 2010 at 09:29:02PM -0700, Eric Blake wrote:
From: Daniel P. Berrange <berrange@redhat.com>
This proof of concept shows how two existing uses of virExec and virRun can be ported to the new virCommand APIs, and how much simpler the code becomes --- src/util/hooks.c | 216 +++------------------------------------------------ src/util/iptables.c | 73 +++-------------- 2 files changed, 24 insertions(+), 265 deletions(-)
ACK Daniel -- |: Red Hat, Engineering, London -o- http://people.redhat.com/berrange/ :| |: http://libvirt.org -o- http://virt-manager.org -o- http://deltacloud.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: GnuPG: 7D3B9505 -o- F3C9 553F A1DA 4AC2 5648 23C1 B3DF F742 7D3B 9505 :|
participants (5)
-
Daniel P. Berrange
-
Eric Blake
-
Jean-Philippe Menil
-
Stefan Berger
-
Wen Congyang