[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