[libvirt] Easy reproducer for multiple races and segfaults in libvirtd [UPDATED]
Richard W.M. Jones
rjones at redhat.com
Fri Nov 16 16:21:02 UTC 2012
- Move the socket close after virConnectClose. This was probably
what caused #877430, so that wasn't a real bug.
- Update some error messages.
- Turn off debugging by default. If it's working, it should be silent.
Rich.
--
Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones
New in Fedora 11: Fedora Windows cross-compiler. Compile Windows
programs, test, and build Windows installers. Over 70 libraries supprt'd
http://fedoraproject.org/wiki/MinGW http://www.annexia.org/fedora_mingw
-------------- next part --------------
/* gcc -Wall test-parallel.c -o test-parallel -lvirt -lpthread */
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
#include <error.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <pthread.h>
#include <libvirt/libvirt.h>
#include <libvirt/virterror.h>
#define TMPDIR "/tmp"
/* You have to download these from
* http://libguestfs.org/download/binaries/appliance/appliance-1.18.9.tar.xz
* and unpack in TMPDIR.
*/
#define KERNEL TMPDIR "/appliance/kernel"
#define INITRD TMPDIR "/appliance/initrd"
#define APPLIANCE TMPDIR "/appliance/root"
#define NR_THREADS 8
#define DEBUG 0
#define NOT_ROOT 1
#define LIBVIRT_URI "qemu:///session"
#define UNIX_PATH_MAX 108
#define ROOT_DEV "sda"
struct thread_state {
pthread_t thread; /* Thread handle. */
size_t thread_num; /* Thread number. */
int exit_status; /* Thread exit status. */
};
static struct thread_state threads[NR_THREADS];
static void *start_thread (void *) __attribute__((noreturn));
static volatile sig_atomic_t quit = 0;
static void
catch_sigint (int signal)
{
static char cleaning_up[] = "\ngot signal, cleaning up ...\n";
if (quit == 0) {
quit = 1;
write (2, cleaning_up, sizeof cleaning_up);
}
}
int
main (int argc, char *argv[])
{
struct sigaction sa;
int r;
size_t i, errors = 0;
void *status;
#if NOT_ROOT
if (geteuid () == 0) {
fprintf (stderr, "don't run this as root!\n");
exit (EXIT_FAILURE);
}
#endif
srandom (time (NULL));
virInitialize ();
memset (&sa, 0, sizeof sa);
sa.sa_handler = catch_sigint;
sa.sa_flags = SA_RESTART;
sigaction (SIGINT, &sa, NULL);
sigaction (SIGQUIT, &sa, NULL);
for (i = 0; i < NR_THREADS; ++i) {
threads[i].thread_num = i;
/* Start the thread. */
r = pthread_create (&threads[i].thread, NULL, start_thread,
&threads[i]);
if (r != 0)
error (EXIT_FAILURE, r, "pthread_create");
}
/* Wait for the threads to exit. */
for (i = 0; i < NR_THREADS; ++i) {
r = pthread_join (threads[i].thread, &status);
if (r != 0)
error (EXIT_FAILURE, r, "pthread_join");
if (*(int *)status != 0) {
fprintf (stderr, "%zu: thread returned an error\n", i);
errors++;
}
}
exit (errors == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
}
/* Run the test in a single thread. */
static void *
start_thread (void *statevp)
{
struct thread_state *state = statevp;
size_t i = 0;
int r, fd, fd2;
struct sockaddr_un addr;
struct sockaddr addr2;
char snapshot[256];
char sock[256];
char cmd[256];
char xml[2048];
virConnectPtr conn;
virDomainPtr dom;
char flag[4];
socklen_t addr_size;
snprintf (snapshot, sizeof snapshot, TMPDIR "/snapshot%zu.qcow2",
state->thread_num);
snprintf (sock, sizeof sock, TMPDIR "/sock%zu", state->thread_num);
/* Create the virtio socket. */
unlink (sock);
fd = socket (AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
if (fd == -1) {
perror ("socket");
goto error;
}
addr.sun_family = AF_UNIX;
strncpy (addr.sun_path, sock, UNIX_PATH_MAX);
addr.sun_path[UNIX_PATH_MAX-1] = '\0';
if (bind (fd, (struct sockaddr *) &addr, sizeof addr) == -1) {
perror ("bind");
goto error;
}
if (listen (fd, 1) == -1) {
perror ("listen");
goto error;
}
while (!quit) {
i++;
/* Create a snapshot of the appliance disk. */
snprintf (cmd, sizeof cmd,
"rm -f %s; "
"qemu-img create -f qcow2 -b %s -o backing_fmt=raw %s"
" >/dev/null 2>&1",
snapshot, APPLIANCE, snapshot);
if (system (cmd) != 0) {
fprintf (stderr, "command failed: %s\n", cmd);
goto error;
}
snprintf (xml, sizeof xml,
"<?xml version=\"1.0\"?>\n"
"<domain type=\"kvm\"\n"
" xmlns:qemu=\"http://libvirt.org/schemas/domain/qemu/1.0\">\n"
" <name>test_%zu_%zu</name>\n"
" <memory unit=\"MiB\">500</memory>\n"
" <currentMemory unit=\"MiB\">500</currentMemory>\n"
" <vcpu>1</vcpu>\n"
" <clock offset=\"utc\"/>\n"
" <os>\n"
" <type>hvm</type>\n"
" <kernel>%s</kernel>\n"
" <initrd>%s</initrd>\n"
" <cmdline>panic=1 console=ttyS0 udevtimeout=600 no_timer_check acpi=off printk.time=1 cgroup_disable=memory root=/dev/" ROOT_DEV " selinux=0 TERM=xterm-256color</cmdline>\n"
" </os>\n"
" <on_reboot>destroy</on_reboot>\n"
" <devices>\n"
" <controller type=\"scsi\" index=\"0\" model=\"virtio-scsi\"/>\n"
" <disk type=\"file\" device=\"disk\">\n"
" <source file=\"%s\"/>\n"
" <target dev=\"" ROOT_DEV "\" bus=\"scsi\"/>\n"
" <driver name=\"qemu\" type=\"qcow2\" cache=\"unsafe\"/>\n"
" <address type=\"drive\" controller=\"0\" bus=\"0\" target=\"0\" unit=\"0\"/>\n"
" <shareable/>\n"
" </disk>\n"
" <channel type=\"unix\">\n"
" <source mode=\"connect\" path=\"%s\"/>\n"
" <target type=\"virtio\" name=\"org.libguestfs.channel.0\"/>\n"
" </channel>\n"
" </devices>\n"
" <qemu:commandline>\n"
" <qemu:env name=\"TMPDIR\" value=\"" TMPDIR "\"/>\n"
" </qemu:commandline>\n"
"</domain>",
state->thread_num, i,
KERNEL, INITRD,
snapshot, sock);
/* Create the libvirt transient domain. */
#if DEBUG
printf ("%zu: opening libvirt\n", state->thread_num);
#endif
conn = virConnectOpen (LIBVIRT_URI);
if (!conn) {
fprintf (stderr, "%zu: virConnectOpen failed\n",
state->thread_num);
goto error;
}
#if DEBUG
printf ("%zu: starting domain\n", state->thread_num);
#endif
dom = virDomainCreateXML (conn, xml, VIR_DOMAIN_START_AUTODESTROY);
if (!dom) {
fprintf (stderr, "%zu: virDomainCreateXML failed\n",
state->thread_num);
goto error;
}
/* Wait for the launch flag to be received, which indicates that
* the daemon is running.
*/
again:
#if DEBUG
printf ("%zu: waiting for connection back from daemon\n",
state->thread_num);
#endif
addr_size = sizeof addr2;
fd2 = accept4 (fd, (struct sockaddr *) &addr2, &addr_size, SOCK_CLOEXEC);
if (fd2 == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK)
goto again;
perror ("accept4");
goto error;
}
r = read (fd2, flag, 4);
if (r == -1) {
perror ("read");
goto error;
}
if (r != 4) {
fprintf (stderr, "%zu: could not read launch flag: probably the transient domain failed to launch\n",
state->thread_num);
goto error;
}
/* Destroy the domain. */
#if DEBUG
printf ("%zu: destroying domain\n", state->thread_num);
#endif
if (virDomainDestroyFlags (dom, VIR_DOMAIN_DESTROY_GRACEFUL) == -1) {
fprintf (stderr, "%zu: virDomainDestroyFlags failed\n",
state->thread_num);
goto error;
}
virDomainFree (dom);
virConnectClose (conn);
close (fd2);
}
close (fd);
/* Test finished successfully. */
fprintf (stderr, "%zu: thread exiting successfully\n", state->thread_num);
state->exit_status = 0;
pthread_exit (&state->exit_status);
/* Test failed. */
error:
fprintf (stderr, "%zu: thread exiting on error\n", state->thread_num);
state->exit_status = 1;
pthread_exit (&state->exit_status);
}
More information about the libvir-list
mailing list