Richard W.M. Jones
2012-Mar-09 13:17 UTC
[Libguestfs] [PATCH 1/2] Close all file descriptors in the recovery process.
From: "Richard W.M. Jones" <rjones at redhat.com> If the parent process uses a pipe (or any fd, but pipes are a particular problem), then the recovery process would hold open the file descriptor(s) of the pipe, meaning that it could not be fully closed in the parent. Because the recovery process doesn't use exec(2), this wasn't avoidable even using FD_CLOEXEC. Avoid this by closing all file descriptors when starting the recovery process. --- src/launch.c | 12 ++++++++++++ 1 files changed, 12 insertions(+), 0 deletions(-) diff --git a/src/launch.c b/src/launch.c index 1dc23f4..a9af445 100644 --- a/src/launch.c +++ b/src/launch.c @@ -844,9 +844,21 @@ launch_appliance (guestfs_h *g) if (g->recovery_proc) { r = fork (); if (r == 0) { + int fd, max_fd; pid_t qemu_pid = g->pid; pid_t parent_pid = getppid (); + /* Close all other file descriptors. This ensures that we don't + * hold open (eg) pipes from the parent process. + */ + max_fd = sysconf (_SC_OPEN_MAX); + if (max_fd == -1) + max_fd = 1024; + if (max_fd > 65536) + max_fd = 65536; /* bound the amount of work we do here */ + for (fd = 0; fd < max_fd; ++fd) + close (fd); + /* It would be nice to be able to put this in the same process * group as qemu (ie. setpgid (0, qemu_pid)). However this is * not possible because we don't have any guarantee here that -- 1.7.9.1
Richard W.M. Jones
2012-Mar-09 13:17 UTC
[Libguestfs] [PATCH 2/2] example: Copying a directory between two guests using threads.
From: "Richard W.M. Jones" <rjones at redhat.com> --- .gitignore | 1 + examples/Makefile.am | 16 +++ examples/copy_over.c | 255 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 272 insertions(+), 0 deletions(-) create mode 100644 examples/copy_over.c diff --git a/.gitignore b/.gitignore index 3e37163..f6c42a2 100644 --- a/.gitignore +++ b/.gitignore @@ -73,6 +73,7 @@ erlang/examples/guestfs-erlang.3 erlang/examples/stamp-guestfs-erlang.pod erlang/guestfs.beam erlang/guestfs.erl +examples/copy_over examples/create_disk examples/display_icon examples/guestfs-examples.3 diff --git a/examples/Makefile.am b/examples/Makefile.am index ef4a581..137dfe6 100644 --- a/examples/Makefile.am +++ b/examples/Makefile.am @@ -27,10 +27,26 @@ CLEANFILES = \ stamp-guestfs-testing.pod noinst_PROGRAMS = create_disk display_icon inspect_vm +if HAVE_LIBVIRT +noinst_PROGRAMS += copy_over +endif if HAVE_HIVEX noinst_PROGRAMS += virt-dhcp-address endif +if HAVE_LIBVIRT +copy_over_SOURCES = copy_over.c +copy_over_CFLAGS = \ + -DGUESTFS_WARN_DEPRECATED=1 \ + -I$(top_srcdir)/src -I$(top_builddir)/src \ + $(LIBVIRT_CFLAGS) \ + -pthread \ + $(WARN_CFLAGS) $(WERROR_CFLAGS) +copy_over_LDADD = \ + $(top_builddir)/src/libguestfs.la \ + $(LIBVIRT_LIBS) +endif + create_disk_SOURCES = create_disk.c create_disk_CFLAGS = \ -DGUESTFS_WARN_DEPRECATED=1 \ diff --git a/examples/copy_over.c b/examples/copy_over.c new file mode 100644 index 0000000..e3d1865 --- /dev/null +++ b/examples/copy_over.c @@ -0,0 +1,255 @@ +/* Copy a directory from one libvirt guest to another. This + * demonstrates using multiple handles with threads, and connecting a + * pipe from one handle to another. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> + +#include <pthread.h> + +#include <guestfs.h> +#include <libvirt/libvirt.h> + +struct threaddata { + const char *src; + const char *srcdir; + int fd; + pthread_t mainthread; +}; + +static void *start_srcthread (void *); +static int open_guest (guestfs_h *g, const char *dom, int readonly); + +static void +usage (void) +{ + fprintf (stderr, + "Usage: copy_over source srcdir dest destdir\n" + "\n" + " source : the source domain (a libvirt guest name)\n" + " srcdir : the directory to copy from the source guest\n" + " dest : the destination domain (a libvirt guest name)\n" + " destdir : the destination directory (must exist at destination)\n" + "\n" + "eg: copy_over Src /home/rjones Dest /tmp\n" + "would copy /home/rjones from Src to /tmp/rjones on Dest\n" + "\n" + "The destination guest cannot be running.\n"); +} + +int +main (int argc, char *argv[]) +{ + const char *src, *srcdir, *dest, *destdir; + guestfs_h *destg; + int fd[2]; + pthread_t srcthread; + struct threaddata threaddata; + int err; + char fdname[128]; + + /* This is required when using libvirt from multiple threads. */ + virInitialize (); + + if (argc != 5) { + usage (); + exit (EXIT_FAILURE); + } + + src = argv[1]; + srcdir = argv[2]; + dest = argv[3]; + destdir = argv[4]; + + /* Instead of downloading to local disk and uploading, we are going + * to connect the source download and destination upload using a + * pipe. Create that pipe. + */ + if (pipe (fd) == -1) { + perror ("pipe"); + exit (EXIT_FAILURE); + } + + /* We don't want the pipe to be passed to / held open in subprocesses. */ + if (fcntl (fd[0], F_SETFD, FD_CLOEXEC) == -1 || + fcntl (fd[1], F_SETFD, FD_CLOEXEC) == -1) { + perror ("fcntl"); + exit (EXIT_FAILURE); + } + + /* The libguestfs API is synchronous, so if we want to use two + * handles concurrently, then we have to have two threads. In this + * case the main thread (this one) is handling the destination + * domain (uploading), and we create one more thread to handle the + * source domain (downloading). + */ + threaddata.src = src; + threaddata.srcdir = srcdir; + threaddata.fd = fd[1]; + threaddata.mainthread = pthread_self (); + err = pthread_create (&srcthread, NULL, start_srcthread, &threaddata); + if (err != 0) { + fprintf (stderr, "pthread_create: %s\n", strerror (err)); + exit (EXIT_FAILURE); + } + + /* Open the destination domain. */ + destg = guestfs_create (); + if (!destg) { + perror ("failed to create libguestfs handle"); + pthread_cancel (srcthread); + exit (EXIT_FAILURE); + } + if (open_guest (destg, dest, 0) == -1) { + pthread_cancel (srcthread); + exit (EXIT_FAILURE); + } + + /* Begin the upload. */ + snprintf (fdname, sizeof fdname, "/dev/fd/%d", fd[0]); + if (guestfs_tar_in (destg, fdname, destdir) == -1) { + pthread_cancel (srcthread); + exit (EXIT_FAILURE); + } + + /* Close our end of the pipe. The other thread will close the + * other side of the pipe. + */ + close (fd[0]); + + /* Wait for the other thread to finish. */ + err = pthread_join (srcthread, NULL); + if (err != 0) { + fprintf (stderr, "pthread_join: %s\n", strerror (err)); + exit (EXIT_FAILURE); + } + + /* Clean up. */ + if (guestfs_umount_all (destg) == -1) + exit (EXIT_FAILURE); + guestfs_close (destg); + + exit (EXIT_SUCCESS); +} + +static void * +start_srcthread (void *arg) +{ + struct threaddata *threaddata = arg; + guestfs_h *srcg; + char fdname[128]; + + /* Open the source domain. */ + srcg = guestfs_create (); + if (!srcg) { + perror ("failed to create libguestfs handle"); + pthread_cancel (threaddata->mainthread); + exit (EXIT_FAILURE); + } + if (open_guest (srcg, threaddata->src, 1) == -1) { + pthread_cancel (threaddata->mainthread); + exit (EXIT_FAILURE); + } + + /* Begin the download. */ + snprintf (fdname, sizeof fdname, "/dev/fd/%d", threaddata->fd); + if (guestfs_tar_out (srcg, threaddata->srcdir, fdname) == -1) { + pthread_cancel (threaddata->mainthread); + exit (EXIT_FAILURE); + } + + /* Close the pipe; this will cause the receiver to finish the upload. */ + if (close (threaddata->fd) == -1) { + pthread_cancel (threaddata->mainthread); + exit (EXIT_FAILURE); + } + + /* Clean up. */ + if (guestfs_umount_all (srcg) == -1) { + pthread_cancel (threaddata->mainthread); + exit (EXIT_FAILURE); + } + guestfs_close (srcg); + + return NULL; +} + +static int +compare_keys_len (const void *p1, const void *p2) +{ + const char *key1 = * (char * const *) p1; + const char *key2 = * (char * const *) p2; + return strlen (key1) - strlen (key2); +} + +static size_t +count_strings (char *const *argv) +{ + size_t c; + + for (c = 0; argv[c]; ++c) + ; + return c; +} + +/* This function deals with the complexity of adding the domain, + * launching the handle, and mounting up filesystems. See + * 'examples/inspect_vm.c' to understand how this works. + */ +static int +open_guest (guestfs_h *g, const char *dom, int readonly) +{ + char **roots, *root, **mountpoints; + size_t i; + + /* Use libvirt to find the guest disks and add them to the handle. */ + if (guestfs_add_domain (g, dom, + GUESTFS_ADD_DOMAIN_READONLY, readonly, + -1) == -1) + return -1; + + if (guestfs_launch (g) == -1) + return -1; + + /* Inspect the guest, looking for operating systems. */ + roots = guestfs_inspect_os (g); + if (roots == NULL) + return -1; + + if (roots[0] == NULL || roots[1] != NULL) { + fprintf (stderr, "copy_over: %s: no operating systems or multiple operating systems found\n", dom); + return -1; + } + + root = roots[0]; + + /* Mount up the filesystems (like 'guestfish -i'). */ + mountpoints = guestfs_inspect_get_mountpoints (g, root); + if (mountpoints == NULL) + return -1; + + qsort (mountpoints, count_strings (mountpoints) / 2, 2 * sizeof (char *), + compare_keys_len); + for (i = 0; mountpoints[i] != NULL; i += 2) { + /* Ignore failures from this call, since bogus entries can + * appear in the guest's /etc/fstab. + */ + (readonly ? guestfs_mount_ro : guestfs_mount) + (g, mountpoints[i+1], mountpoints[i]); + free (mountpoints[i]); + free (mountpoints[i+1]); + } + + free (mountpoints); + + free (root); + free (roots); + + /* Everything ready, no error. */ + return 0; +} -- 1.7.9.1