mirror of
https://github.com/libguestfs/libguestfs.git
synced 2026-03-21 22:53:37 +00:00
Add experimental User-Mode Linux backend.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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 \
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -1406,8 +1406,8 @@ options into the C<args> 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</guestfs_set_backend>, or by
|
||||
setting the environment variable C<LIBGUESTFS_BACKEND>.
|
||||
@@ -1440,6 +1440,17 @@ with a URI would be C<libvirt:qemu:///session>
|
||||
The libvirt backend supports more features, including
|
||||
hotplugging (see L</HOTPLUGGING>) and sVirt.
|
||||
|
||||
=item C<uml>
|
||||
|
||||
Run the User-Mode Linux kernel. The location of the kernel is set
|
||||
using C<$LIBGUESTFS_QEMU> or using the L</guestfs_set_qemu> 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</USER-MODE LINUX BACKEND> below.
|
||||
|
||||
=item C<unix:I<path>>
|
||||
|
||||
Connect to the Unix domain socket I<path>.
|
||||
@@ -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<This is an experimental feature> 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<vmlinux> (or it may be called C<linux>) 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<vmlinux>, or you may need to compile it
|
||||
yourself from the kernel source (which is dead easy, follow the
|
||||
instructions here:
|
||||
L<http://user-mode-linux.sourceforge.net/source.html>).
|
||||
|
||||
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,
|
||||
|
||||
11
src/handle.c
11
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;
|
||||
|
||||
754
src/launch-uml.c
Normal file
754
src/launch-uml.c
Normal file
@@ -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 <config.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <inttypes.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <dirent.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sys/signal.h>
|
||||
|
||||
#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,
|
||||
};
|
||||
@@ -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 ();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user