diff --git a/po/POTFILES b/po/POTFILES index 2e9a00baf..e8f01fd11 100644 --- a/po/POTFILES +++ b/po/POTFILES @@ -280,6 +280,7 @@ src/inspect.c src/journal.c src/launch-direct.c src/launch-libvirt.c +src/launch-uml.c src/launch-unix.c src/launch.c src/libvirt-auth.c diff --git a/src/Makefile.am b/src/Makefile.am index 9e13c784b..b36f59dc0 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -108,6 +108,7 @@ libguestfs_la_SOURCES = \ launch.c \ launch-direct.c \ launch-libvirt.c \ + launch-uml.c \ launch-unix.c \ libvirt-auth.c \ libvirt-domain.c \ diff --git a/src/guestfs-internal.h b/src/guestfs-internal.h index 28e40a291..7572e4ca9 100644 --- a/src/guestfs-internal.h +++ b/src/guestfs-internal.h @@ -97,6 +97,7 @@ enum state { CONFIG = 0, LAUNCHING = 1, READY = 2, enum backend { BACKEND_DIRECT, BACKEND_LIBVIRT, + BACKEND_UML, BACKEND_UNIX, }; @@ -209,6 +210,7 @@ struct backend_ops { }; extern struct backend_ops backend_ops_direct; extern struct backend_ops backend_ops_libvirt; +extern struct backend_ops backend_ops_uml; extern struct backend_ops backend_ops_unix; /* Connection module. A 'connection' represents the appliance console @@ -432,6 +434,17 @@ struct guestfs_h char *virt_selinux_label; char *virt_selinux_imagelabel; bool virt_selinux_norelabel_disks; + + struct { /* Used only by src/launch-uml.c. */ + pid_t pid; /* vmlinux PID. */ + pid_t recoverypid; /* Recovery process PID. */ + +#define UML_UMID_LEN 16 + char umid[UML_UMID_LEN+1]; /* umid=<...> unique ID. */ + + char **cmdline; /* Only used in child, does not need freeing. */ + size_t cmdline_size; + } uml; }; /* Per-filesystem data stored for inspect_os. */ diff --git a/src/guestfs.pod b/src/guestfs.pod index 93c7cd4a2..2bccdbeb0 100644 --- a/src/guestfs.pod +++ b/src/guestfs.pod @@ -1406,8 +1406,8 @@ options into the C array. The backend (previously known as the "attach method") controls how libguestfs creates and/or connects to the backend daemon, eg. by -starting qemu directly, or by using libvirt to manage an appliance, or -connecting to an already running daemon. +starting qemu directly, or using libvirt to manage an appliance, +running User-Mode Linux, or connecting to an already running daemon. You can set the backend by calling L, or by setting the environment variable C. @@ -1440,6 +1440,17 @@ with a URI would be C The libvirt backend supports more features, including hotplugging (see L) and sVirt. +=item C + +Run the User-Mode Linux kernel. The location of the kernel is set +using C<$LIBGUESTFS_QEMU> or using the L API (note +that qemu is not involved, we just reuse the same variable in the +handle for convenience). + +User-Mode Linux can be much faster, simpler and more lightweight than +using a full-blown virtual machine, but it also has some shortcomings. +See L below. + =item C> Connect to the Unix domain socket I. @@ -1520,6 +1531,43 @@ The virtual machine needs to have been set up beforehand so that it has the virtio-serial channel and so that guestfsd is running inside it. +=head2 USER-MODE LINUX BACKEND + +B which you should use with care. + +Setting the following environment variables (or the equivalent in the +API) selects the User-Mode Linux backend: + + export LIBGUESTFS_BACKEND=uml + export LIBGUESTFS_QEMU=/path/to/vmlinux + +C (or it may be called C) is the Linux binary, +compiled to run as a userspace process. Note that we reuse the qemu +variable in the handle for convenience; qemu is not involved. Your +Linux distro may provide C, or you may need to compile it +yourself from the kernel source (which is dead easy, follow the +instructions here: +L). + +User-Mode Linux can be faster and more lightweight than running a +full-blown virtual machine as the backend (especially if you are +already running libguestfs in a virtual machine or cloud instance), +but it also has some important shortcomings. The main ones are: + +=over 4 + +=item UML only supports raw-format images + +Only plain raw-format images will work. No qcow2, no backing files. + +=item UML does not support any remote drives + +No NBD, etc. + +=item UML only works on ix86 and x86-64 + +=back + =head2 ABI GUARANTEE We guarantee the libguestfs ABI (binary interface), for public, diff --git a/src/handle.c b/src/handle.c index dc2b15bab..698ca173c 100644 --- a/src/handle.c +++ b/src/handle.c @@ -604,6 +604,13 @@ parse_backend (guestfs_h *g, const char *method) return 0; } + if (STREQ (method, "uml")) { + g->backend = BACKEND_UML; + free (g->backend_arg); + g->backend_arg = NULL; + return 0; + } + if (STRPREFIX (method, "unix:") && strlen (method) > 5) { g->backend = BACKEND_UNIX; free (g->backend_arg); @@ -649,6 +656,10 @@ guestfs__get_backend (guestfs_h *g) ret = safe_asprintf (g, "libvirt:%s", g->backend_arg); break; + case BACKEND_UML: + ret = safe_strdup (g, "uml"); + break; + case BACKEND_UNIX: ret = safe_asprintf (g, "unix:%s", g->backend_arg); break; diff --git a/src/launch-uml.c b/src/launch-uml.c new file mode 100644 index 000000000..336d7b3ce --- /dev/null +++ b/src/launch-uml.c @@ -0,0 +1,754 @@ +/* libguestfs + * Copyright (C) 2009-2013 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cloexec.h" +#include "c-ctype.h" + +#include "guestfs.h" +#include "guestfs-internal.h" +#include "guestfs-internal-actions.h" +#include "guestfs_protocol.h" + +static void print_vmlinux_command_line (guestfs_h *g, char **argv); +static char *make_cow_overlay (guestfs_h *g, const char *original); +static int kill_vmlinux (guestfs_h *g, int signum); + +/* Functions to build up the vmlinux command line. These are only run + * in the child process so no clean-up is required. + */ +static void +alloc_cmdline (guestfs_h *g) +{ + g->uml.cmdline_size = 1; + g->uml.cmdline = safe_malloc (g, sizeof (char *)); + g->uml.cmdline[0] = g->qemu; +} + +static void +incr_cmdline_size (guestfs_h *g) +{ + g->uml.cmdline_size++; + g->uml.cmdline = + safe_realloc (g, g->uml.cmdline, sizeof (char *) * g->uml.cmdline_size); +} + +static void +add_cmdline (guestfs_h *g, const char *str) +{ + incr_cmdline_size (g); + g->uml.cmdline[g->uml.cmdline_size-1] = safe_strdup (g, str); +} + +/* Test for features which are not supported by the UML backend. + * Possibly some of these should just be warnings, not errors. + */ +static bool +uml_supported (guestfs_h *g) +{ + size_t i; + struct drive *drv; + + if (g->enable_network) { + error (g, _("uml backend does not support networking")); + return false; + } + if (g->smp > 1) { + error (g, _("uml backend does not support SMP")); + return false; + } + + ITER_DRIVES (g, i, drv) { + if (drv->src.protocol != drive_protocol_file) { + error (g, _("uml backend does not support remote drives")); + return false; + } + if (drv->format && STRNEQ (drv->format, "raw")) { + error (g, _("uml backend does not support non-raw-format drives")); + return false; + } + if (drv->iface) { + error (g, + _("uml backend does not support drives with 'iface' parameter")); + return false; + } + if (drv->disk_label) { + error (g, + _("uml backend does not support drives with 'label' parameter")); + return false; + } + } + + return true; +} + +static int +launch_uml (guestfs_h *g, const char *arg) +{ + int console_sock = -1, daemon_sock = -1; + int r; + int csv[2], dsv[2]; + CLEANUP_FREE char *kernel = NULL, *initrd = NULL, *appliance = NULL; + int has_appliance_drive; + CLEANUP_FREE char *appliance_cow = NULL; + uint32_t size; + CLEANUP_FREE void *buf = NULL; + struct drive *drv; + size_t i; + + if (!uml_supported (g)) + return -1; + + if (!g->nr_drives) { + error (g, _("you must call guestfs_add_drive before guestfs_launch")); + return -1; + } + + /* Assign a random unique ID to this run. */ + if (guestfs___random_string (g->uml.umid, UML_UMID_LEN) == -1) { + perrorf (g, "guestfs___random_string"); + return -1; + } + + /* Locate and/or build the appliance. */ + if (guestfs___build_appliance (g, &kernel, &initrd, &appliance) == -1) + return -1; + has_appliance_drive = appliance != NULL; + + /* Create COW overlays for any readonly drives, and for the root. + * Note that the documented syntax ubd0=cow,orig does not work since + * kernel 3.3. See: + * http://thread.gmane.org/gmane.linux.uml.devel/13556 + */ + ITER_DRIVES (g, i, drv) { + if (drv->readonly) { + drv->priv = make_cow_overlay (g, drv->src.u.path); + if (!drv->priv) + goto cleanup0; + drv->free_priv = free; + } + } + + if (has_appliance_drive) { + appliance_cow = make_cow_overlay (g, appliance); + if (!appliance_cow) + goto cleanup0; + } + + /* The socket that the daemon will talk to us on. + */ + if (socketpair (AF_LOCAL, SOCK_STREAM|SOCK_CLOEXEC, 0, dsv) == -1) { + perrorf (g, "socketpair"); + goto cleanup0; + } + + /* The console socket. */ + if (!g->direct_mode) { + if (socketpair (AF_LOCAL, SOCK_STREAM|SOCK_CLOEXEC, 0, csv) == -1) { + perrorf (g, "socketpair"); + close (dsv[0]); + close (dsv[1]); + goto cleanup0; + } + } + + r = fork (); + if (r == -1) { + perrorf (g, "fork"); + if (!g->direct_mode) { + close (csv[0]); + close (csv[1]); + } + close (dsv[0]); + close (dsv[1]); + goto cleanup0; + } + + if (r == 0) { /* Child (vmlinux). */ + char *buf; + struct qemu_param *qp; + char *term = getenv ("TERM"); + + /* Set up the full command line. Do this in the subprocess so we + * don't need to worry about cleaning up. + */ + alloc_cmdline (g); + + /* UMID: *NB* This must be the first parameter on the command line + * because of kill_vmlinux below. + */ + buf = safe_asprintf (g, "umid=%s", g->uml.umid); + add_cmdline (g, buf); + free (buf); + + /* Set memory size. */ + buf = safe_asprintf (g, "mem=%dM", g->memsize); + add_cmdline (g, buf); + free (buf); + + /* vmlinux appears to ignore this, but let's add it anyway. */ + buf = safe_asprintf (g, "initrd=%s", initrd); + add_cmdline (g, buf); + free (buf); + + /* Make sure our appliance init script runs first. */ + add_cmdline (g, "init=/init"); + + /* This tells the /init script not to reboot at the end. */ + add_cmdline (g, "guestfs_noreboot=1"); + + /* Root filesystem should be mounted read-write (default seems to + * be "ro"). + */ + add_cmdline (g, "rw"); + + /* See also guestfs___appliance_command_line. */ + if (g->verbose) + add_cmdline (g, "guestfs_verbose=1"); + + add_cmdline (g, "panic=1"); + + buf = safe_asprintf (g, "TERM=%s", term ? term : "linux"); + add_cmdline (g, buf); + free (buf); + + if (g->selinux) + add_cmdline (g, "selinux=1 enforcing=0"); + else + add_cmdline (g, "selinux=0"); + + /* XXX This isn't quite right. Multiple append args won't work. */ + if (g->append) + add_cmdline (g, g->append); + + /* Add the drives. */ + ITER_DRIVES (g, i, drv) { + if (!drv->readonly) + buf = safe_asprintf (g, "ubd%zu=%s", i, drv->src.u.path); + else + buf = safe_asprintf (g, "ubd%zu=%s", i, (char *) drv->priv); + add_cmdline (g, buf); + free (buf); + } + + /* Add the ext2 appliance drive (after all the drives). */ + if (has_appliance_drive) { + char drv_name[64] = "ubd"; + guestfs___drive_name (g->nr_drives, &drv_name[3]); + + buf = safe_asprintf (g, "ubd%zu=%s", g->nr_drives, appliance_cow); + add_cmdline (g, buf); + free (buf); + buf = safe_asprintf (g, "root=/dev/%s", drv_name); + add_cmdline (g, buf); + free (buf); + } + + /* Create the daemon socket. */ + close (dsv[0]); + set_cloexec_flag (dsv[1], 0); /* so it doesn't close across exec */ + buf = safe_asprintf (g, "ssl3=fd:%d", dsv[1]); + add_cmdline (g, buf); + free (buf); + add_cmdline (g, "guestfs_channel=/dev/ttyS3"); + +#if 0 /* XXX This could be made to work. */ +#ifdef VALGRIND_DAEMON + /* Set up virtio-serial channel for valgrind messages. */ + add_cmdline (g, "-chardev"); + snprintf (buf, sizeof buf, "file,path=%s/valgrind.log.%d,id=valgrind", + VALGRIND_LOG_PATH, getpid ()); + add_cmdline (g, buf); + add_cmdline (g, "-device"); + add_cmdline (g, "virtserialport,chardev=valgrind,name=org.libguestfs.valgrind"); +#endif +#endif + + /* Add any vmlinux parameters. */ + for (qp = g->qemu_params; qp; qp = qp->next) { + add_cmdline (g, qp->qemu_param); + if (qp->qemu_value) + add_cmdline (g, qp->qemu_value); + } + + /* Finish off the command line. */ + incr_cmdline_size (g); + g->uml.cmdline[g->uml.cmdline_size-1] = NULL; + + if (!g->direct_mode) { + /* Set up stdin, stdout, stderr. */ + close (0); + close (1); + close (csv[0]); + + /* We set the FD_CLOEXEC flag on the socket above, but now (in + * the child) it's safe to unset this flag so vmlinux can use the + * socket. + */ + set_cloexec_flag (csv[1], 0); + + /* Stdin. */ + if (dup (csv[1]) == -1) { + dup_failed: + perror ("dup failed"); + _exit (EXIT_FAILURE); + } + /* Stdout. */ + if (dup (csv[1]) == -1) + goto dup_failed; + + /* Send stderr to the pipe as well. */ + close (2); + if (dup (csv[1]) == -1) + goto dup_failed; + + close (csv[1]); + } + + /* Dump the command line (after setting up stderr above). */ + if (g->verbose) + print_vmlinux_command_line (g, g->uml.cmdline); + + /* Put vmlinux in a new process group. */ + if (g->pgroup) + setpgid (0, 0); + + setenv ("LC_ALL", "C", 1); + + execv (g->qemu, g->uml.cmdline); /* Run vmlinux. */ + perror (g->qemu); + _exit (EXIT_FAILURE); + } + + /* Parent (library). */ + g->uml.pid = r; + + /* Fork the recovery process off which will kill vmlinux if the + * parent process fails to do so (eg. if the parent segfaults). + */ + g->uml.recoverypid = -1; + if (g->recovery_proc) { + r = fork (); + if (r == 0) { + int i, fd, max_fd; + struct sigaction sa; + pid_t vmlinux_pid = g->uml.pid; + pid_t parent_pid = getppid (); + + /* Remove all signal handlers. See the justification here: + * https://www.redhat.com/archives/libvir-list/2008-August/msg00303.html + * We don't mask signal handlers yet, so this isn't completely + * race-free, but better than not doing it at all. + */ + memset (&sa, 0, sizeof sa); + sa.sa_handler = SIG_DFL; + sa.sa_flags = 0; + sigemptyset (&sa.sa_mask); + for (i = 1; i < NSIG; ++i) + sigaction (i, &sa, NULL); + + /* 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 vmlinux (ie. setpgid (0, vmlinux_pid)). However + * this is not possible because we don't have any guarantee here + * that the vmlinux process has started yet. + */ + if (g->pgroup) + setpgid (0, 0); + + /* Writing to argv is hideously complicated and error prone. See: + * http://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/utils/misc/ps_status.c;hb=HEAD + */ + + /* Loop around waiting for one or both of the other processes to + * disappear. It's fair to say this is very hairy. The PIDs that + * we are looking at might be reused by another process. We are + * effectively polling. Is the cure worse than the disease? + */ + for (;;) { + if (kill (vmlinux_pid, 0) == -1) + /* vmlinux's gone away, we aren't needed */ + _exit (EXIT_SUCCESS); + if (kill (parent_pid, 0) == -1) { + /* Parent's gone away, vmlinux still around, so kill vmlinux. */ + kill_vmlinux (g, SIGKILL); + _exit (EXIT_SUCCESS); + } + sleep (2); + } + } + + /* Don't worry, if the fork failed, this will be -1. The recovery + * process isn't essential. + */ + g->uml.recoverypid = r; + } + + if (!g->direct_mode) { + /* Close the other end of the console socketpair. */ + close (csv[1]); + + console_sock = csv[0]; /* stdin of child */ + csv[0] = -1; + } + + daemon_sock = dsv[0]; + close (dsv[1]); + dsv[0] = -1; + + g->state = LAUNCHING; + + /* Wait for vmlinux to start and to connect back to us via + * virtio-serial and send the GUESTFS_LAUNCH_FLAG message. + */ + g->conn = + guestfs___new_conn_socket_connected (g, daemon_sock, console_sock); + if (!g->conn) + goto cleanup1; + + /* g->conn now owns these sockets. */ + daemon_sock = console_sock = -1; + + /* We now have to wait for vmlinux to start up, the daemon to start + * running, and for it to send the GUESTFS_LAUNCH_FLAG to us. + */ + r = guestfs___recv_from_daemon (g, &size, &buf); + + if (r == -1) { + guestfs___launch_failed_error (g); + goto cleanup1; + } + + if (size != GUESTFS_LAUNCH_FLAG) { + guestfs___launch_failed_error (g); + goto cleanup1; + } + + if (g->verbose) + guestfs___print_timestamped_message (g, "appliance is up"); + + /* This is possible in some really strange situations, such as + * guestfsd starts up OK but then vmlinux immediately exits. Check + * for it because the caller is probably expecting to be able to + * send commands after this function returns. + */ + if (g->state != READY) { + error (g, _("vmlinux launched and contacted daemon, but state != READY")); + goto cleanup1; + } + + if (has_appliance_drive) + guestfs___add_dummy_appliance_drive (g); + + return 0; + + cleanup1: + if (!g->direct_mode && csv[0] >= 0) + close (csv[0]); + if (dsv[0] >= 0) + close (dsv[0]); + if (g->uml.pid > 0) kill_vmlinux (g, SIGKILL); + if (g->uml.recoverypid > 0) kill (g->uml.recoverypid, SIGKILL); + if (g->uml.pid > 0) waitpid (g->uml.pid, NULL, 0); + if (g->uml.recoverypid > 0) waitpid (g->uml.recoverypid, NULL, 0); + g->uml.pid = 0; + g->uml.recoverypid = 0; + memset (&g->launch_t, 0, sizeof g->launch_t); + + cleanup0: + if (daemon_sock >= 0) + close (daemon_sock); + if (console_sock >= 0) + close (console_sock); + if (g->conn) { + g->conn->ops->free_connection (g, g->conn); + g->conn = NULL; + } + g->state = CONFIG; + return -1; +} + +static bool +is_numeric (const char *name) +{ + if (!*name) + return false; + + while (*name) { + if (!c_isdigit (*name)) + return false; + name++; + } + + return true; +} + +/* You can't just kill the parent vmlinux PID (g->uml.pid) and have + * the whole process go away. You have to kill all related vmlinux + * PIDs. I think this is related to a 'FIXME' in the UML code (in + * function 'kill_off_processes') which seems to indicate that it + * doesn't kill any UML-userspace processes. + * + * We use the 'umid=' on the command line (by reading /proc/PID/cmdline) + * to identify the related PIDs so we can kill them too. + * + * XXX Possibly insecure if a process spoofs umid= argument for some + * reason. + */ +static int +kill_vmlinux (guestfs_h *g, int signum) +{ + DIR *dir; + + kill (g->uml.pid, signum); + + /* Find the related processes. */ + dir = opendir ("/proc"); + if (dir == NULL) { + perrorf (g, "opendir: /proc"); + return -1; + } + + for (;;) { + pid_t pid; + struct dirent *d; + FILE *fp; + char procname[64]; + size_t i; + char umid[UML_UMID_LEN+1]; + + errno = 0; + d = readdir (dir); + if (d == NULL) break; + + /* Ignore anything which is not numeric. */ + if (! is_numeric (d->d_name)) + continue; + + if (sscanf (d->d_name, "%d", &pid) != 1) + continue; + + if (pid <= 0) + continue; + + snprintf (procname, sizeof procname, "/proc/%d/cmdline", pid); + fp = fopen (procname, "r"); + if (fp == NULL) + continue; /* Ignore it, there are many reasons + * this could legitimately fail. + */ + + /* /proc/PID/cmdline is argv[0].. with each string separated by a \0. + * As umid= parameter is always argv[1], search for the first \0 + * followed by 'u' 'm' 'i' 'd' '='. + */ + for (;;) { + int c = fgetc (fp); + if (c == 0) + goto found_0; + if (c == EOF) + goto not_found; + } + + found_0: + if (fgetc (fp) != 'u' || + fgetc (fp) != 'm' || + fgetc (fp) != 'i' || + fgetc (fp) != 'd' || + fgetc (fp) != '=') + goto not_found; + + for (i = 0; i < UML_UMID_LEN; ++i) { + int c = fgetc (fp); + if (c == 0 || c == EOF) + goto not_found; + umid[i] = c; + } + umid[i] = '\0'; + + if (STRNEQ (umid, g->uml.umid)) + goto not_found; + + /* Found a related process - kill it! */ + kill (pid, signum); + + not_found: + fclose (fp); + } + + if (errno != 0) { + perrorf (g, "readdir: /proc"); + closedir (dir); + return -1; + } + + if (closedir (dir) == -1) { + perrorf (g, "closedir: /proc"); + return -1; + } + + return 0; +} + +/* Run uml_mkcow to create a COW overlay. This works around a kernel + * bug in UML option parsing. + */ +static char * +make_cow_overlay (guestfs_h *g, const char *original) +{ + CLEANUP_CMD_CLOSE struct command *cmd = guestfs___new_command (g); + char *cow; + int r; + + cow = safe_asprintf (g, "%s/cow%d", g->tmpdir, g->unique++); + + guestfs___cmd_add_arg (cmd, "uml_mkcow"); + guestfs___cmd_add_arg (cmd, cow); + guestfs___cmd_add_arg (cmd, original); + r = guestfs___cmd_run (cmd); + if (r == -1) { + free (cow); + return NULL; + } + if (!WIFEXITED (r) || WEXITSTATUS (r) != 0) { + guestfs___external_command_failed (g, r, "uml_mkcow", original); + free (cow); + return NULL; + } + + return cow; /* caller must free */ +} + +/* This is called from the forked subprocess just before vmlinux runs, + * so it can just print the message straight to stderr, where it will + * be picked up and funnelled through the usual appliance event API. + */ +static void +print_vmlinux_command_line (guestfs_h *g, char **argv) +{ + size_t i = 0; + int needs_quote; + + struct timeval tv; + gettimeofday (&tv, NULL); + fprintf (stderr, "[%05" PRIi64 "ms] ", + guestfs___timeval_diff (&g->launch_t, &tv)); + + while (argv[i]) { + if (i > 0) fputc (' ', stderr); + + /* Does it need shell quoting? This only deals with simple cases. */ + needs_quote = strcspn (argv[i], " ") != strlen (argv[i]); + + if (needs_quote) fputc ('\'', stderr); + fprintf (stderr, "%s", argv[i]); + if (needs_quote) fputc ('\'', stderr); + i++; + } + + fputc ('\n', stderr); +} + +static int +shutdown_uml (guestfs_h *g, int check_for_errors) +{ + int ret = 0; + int status; + + /* Signal vmlinux to shutdown cleanly, and kill the recovery process. */ + if (g->uml.pid > 0) { + debug (g, "sending SIGTERM to process %d", g->uml.pid); + kill_vmlinux (g, SIGTERM); + } + if (g->uml.recoverypid > 0) kill (g->uml.recoverypid, 9); + + /* Wait for subprocess(es) to exit. */ + if (g->uml.pid > 0) { + if (waitpid (g->uml.pid, &status, 0) == -1) { + perrorf (g, "waitpid (vmlinux)"); + ret = -1; + } + /* Note it's normal for the vmlinux process to exit with status + * "killed by signal 15" (where 15 == SIGTERM). So don't consider + * that to be an error. + */ + else if (!(WIFSIGNALED (status) && WTERMSIG (status) == SIGTERM) && + !(WIFEXITED (status) && WEXITSTATUS (status) == 0)) { + guestfs___external_command_failed (g, status, g->qemu, NULL); + ret = -1; + } + } + if (g->uml.recoverypid > 0) waitpid (g->uml.recoverypid, NULL, 0); + + g->uml.pid = g->uml.recoverypid = 0; + + return ret; +} + +static int +get_pid_uml (guestfs_h *g) +{ + if (g->uml.pid > 0) + return g->uml.pid; + else { + error (g, "get_pid: no vmlinux subprocess"); + return -1; + } +} + +/* XXX This is a guess. UML appears to use a single major, and puts + * ubda at minor 0 with each partition at minors 1-15, ubdb at minor + * 16, etc, so my guess is that the maximum is 256/16. + */ +static int +max_disks_uml (guestfs_h *g) +{ + return 256/16; +} + +struct backend_ops backend_ops_uml = { + .launch = launch_uml, + .shutdown = shutdown_uml, + .get_pid = get_pid_uml, + .max_disks = max_disks_uml, +}; diff --git a/src/launch.c b/src/launch.c index 415edc2f6..80a9a6e2e 100644 --- a/src/launch.c +++ b/src/launch.c @@ -44,6 +44,7 @@ get_backend_ops (guestfs_h *g) switch (g->backend) { case BACKEND_DIRECT: return &backend_ops_direct; case BACKEND_LIBVIRT: return &backend_ops_libvirt; + case BACKEND_UML: return &backend_ops_uml; case BACKEND_UNIX: return &backend_ops_unix; default: abort (); }