#include <sys/stat.h>
#include <sys/select.h>
#include <dirent.h>
+#include <signal.h>
#include <rpc/types.h>
#include <rpc/xdr.h>
#define safe_strdup guestfs_safe_strdup
//#define safe_memdup guestfs_safe_memdup
+#ifdef __linux__
+#define CAN_CHECK_PEER_EUID 1
+#else
+#define CAN_CHECK_PEER_EUID 0
+#endif
+
static void default_error_cb (guestfs_h *g, void *data, const char *msg);
static int send_to_daemon (guestfs_h *g, const void *v_buf, size_t n);
static int recv_from_daemon (guestfs_h *g, uint32_t *size_rtn, void **buf_rtn);
static int accept_from_daemon (guestfs_h *g);
static int check_peer_euid (guestfs_h *g, int sock, uid_t *rtn);
static void close_handles (void);
+static int qemu_supports (guestfs_h *g, const char *option);
#define UNIX_PATH_MAX 108
-/* Also in guestfsd.c */
-#define GUESTFWD_ADDR "10.0.2.4"
+#ifndef MAX
+#define MAX(a,b) ((a)>(b)?(a):(b))
+#endif
+
+#ifdef __APPLE__
+#define xdr_uint32_t xdr_u_int32_t
+#endif
+
+/* Network configuration of the appliance. Note these addresses are
+ * only meaningful within the context of the running appliance. QEMU
+ * translates network connections to these magic addresses into
+ * userspace calls on the host (eg. connect(2)). qemu-doc has a nice
+ * diagram which is also useful to refer to.
+ *
+ * NETWORK: The network.
+ *
+ * ROUTER: The address of the "host", ie. this library.
+ *
+ * [Note: If you change NETWORK and ROUTER then you also have to
+ * change the network configuration in appliance/init].
+ *
+ * GUESTFWD_ADDR, GUESTFWD_PORT: The guestfwd feature of qemu
+ * magically connects this pseudo-address to the guestfwd channel. In
+ * typical Linux configurations of libguestfs, guestfwd is not
+ * actually used any more.
+ */
+#define NETWORK "169.254.0.0/16"
+#define ROUTER "169.254.2.2"
+#define GUESTFWD_ADDR "169.254.2.4"
#define GUESTFWD_PORT "6666"
/* GuestFS handle and connection. */
if (err < 0) return;
-#ifndef _GNU_SOURCE
+#if !defined(_GNU_SOURCE) || defined(__APPLE__)
char buf[256];
strerror_r (errnum, buf, sizeof buf);
#else
/* Technically we should add an autoconf test for this, testing for the desired
functionality, like what's done in gnulib, but for now, this is fine. */
+#if defined(__GLIBC__)
#define HAVE_GNU_CALLOC (__GLIBC__ >= 2)
+#else
+#define HAVE_GNU_CALLOC 0
+#endif
/* Allocate zeroed memory for N elements of S bytes, with error
checking. S must be nonzero. */
guestfs__add_drive_ro_with_if (guestfs_h *g, const char *filename,
const char *drive_if)
{
- size_t len = strlen (filename) + 64;
- char buf[len];
-
if (strchr (filename, ',') != NULL) {
error (g, _("filename cannot contain ',' (comma) character"));
return -1;
return -1;
}
- snprintf (buf, len, "file=%s,snapshot=on,if=%s", filename, drive_if);
+ if (qemu_supports (g, NULL) == -1)
+ return -1;
+
+ /* Only SCSI and virtio drivers support readonly mode.
+ * This is only supported as a QEMU feature since 2010/01.
+ */
+ int supports_ro = 0;
+ if ((STREQ (drive_if, "scsi") || STREQ (drive_if, "virtio")) &&
+ qemu_supports (g, "readonly=on"))
+ supports_ro = 1;
+
+ size_t len = strlen (filename) + 100;
+ char buf[len];
+
+ snprintf (buf, len, "file=%s,snapshot=on,%sif=%s",
+ filename,
+ supports_ro ? "readonly=on," : "",
+ drive_if);
return guestfs__config (g, "-drive", buf);
}
static void print_timestamped_message (guestfs_h *g, const char *fs, ...);
static int build_supermin_appliance (guestfs_h *g, const char *path, char **kernel, char **initrd);
-static int test_qemu (guestfs_h *g);
-static int qemu_supports (guestfs_h *g, const char *option);
static int is_openable (guestfs_h *g, const char *path, int flags);
static void print_cmdline (guestfs_h *g);
static const char *kernel_name = "vmlinuz." REPO "." host_cpu;
static const char *initrd_name = "initramfs." REPO "." host_cpu ".img";
-static const char *supermin_name =
- "initramfs." REPO "." host_cpu ".supermin.img";
-static const char *supermin_hostfiles_name =
- "initramfs." REPO "." host_cpu ".supermin.hostfiles";
int
guestfs__launch (guestfs_h *g)
char unixsock[256];
struct sockaddr_un addr;
+ /* Configured? */
+ if (!g->cmdline) {
+ error (g, _("you must call guestfs_add_drive before guestfs_launch"));
+ return -1;
+ }
+
+ if (g->state != CONFIG) {
+ error (g, _("the libguestfs handle has already been launched"));
+ return -1;
+ }
+
/* Start the clock ... */
gettimeofday (&g->launch_t, NULL);
+ /* Make the temporary directory. */
#ifdef P_tmpdir
tmpdir = P_tmpdir;
#else
tmpdir = getenv ("TMPDIR") ? : tmpdir;
snprintf (dir_template, sizeof dir_template, "%s/libguestfsXXXXXX", tmpdir);
- /* Configured? */
- if (!g->cmdline) {
- error (g, _("you must call guestfs_add_drive before guestfs_launch"));
- return -1;
- }
-
- if (g->state != CONFIG) {
- error (g, _("qemu has already been launched"));
- return -1;
- }
-
- /* Make the temporary directory. */
if (!g->tmpdir) {
g->tmpdir = safe_strdup (g, dir_template);
if (mkdtemp (g->tmpdir) == NULL) {
fprintf (stderr,
"looking for supermin appliance in current directory\n");
if (dir_contains_files (".",
- supermin_name, supermin_hostfiles_name,
- "kmod.whitelist", NULL)) {
+ "supermin.d", "kmod.whitelist", NULL)) {
if (build_supermin_appliance (g, ".", &kernel, &initrd) == -1)
return -1;
break;
fprintf (stderr, "looking for supermin appliance in %s\n", pelem);
if (dir_contains_files (pelem,
- supermin_name, supermin_hostfiles_name,
- "kmod.whitelist", NULL)) {
+ "supermin.d", "kmod.whitelist", NULL)) {
if (build_supermin_appliance (g, pelem, &kernel, &initrd) == -1)
return -1;
break;
print_timestamped_message (g, "begin testing qemu features");
/* Get qemu help text and version. */
- if (test_qemu (g) == -1)
+ if (qemu_supports (g, NULL) == -1)
goto cleanup0;
/* Choose which vmchannel implementation to use. */
- if (qemu_supports (g, "-net user")) {
+ if (CAN_CHECK_PEER_EUID && qemu_supports (g, "-net user")) {
/* The "null vmchannel" implementation. Requires SLIRP (user mode
* networking in qemu) but no other vmchannel support. The daemon
* will connect back to a random port number on localhost.
*/
if (null_vmchannel_sock) {
add_cmdline (g, "-net");
- add_cmdline (g, "user,vlan=0,net=10.0.2.0/8");
+ add_cmdline (g, "user,vlan=0,net=" NETWORK);
snprintf (buf, sizeof buf,
- "guestfs_vmchannel=tcp:10.0.2.2:%d", null_vmchannel_sock);
+ "guestfs_vmchannel=tcp:" ROUTER ":%d",
+ null_vmchannel_sock);
vmchannel = strdup (buf);
}
add_cmdline (g, buf);
snprintf (buf, sizeof buf,
- "user,vlan=0,net=10.0.2.0/8,"
+ "user,vlan=0,net=" NETWORK ","
"guestfwd=tcp:" GUESTFWD_ADDR ":" GUESTFWD_PORT
"-chardev:guestfsvmc");
add_cmdline (g, "-net");
add_cmdline (g, buf);
add_cmdline (g, "-net");
- add_cmdline (g, "user,vlan=0,net=10.0.2.0/8");
+ add_cmdline (g, "user,vlan=0,net=" NETWORK);
vmchannel = "guestfs_vmchannel=tcp:" GUESTFWD_ADDR ":" GUESTFWD_PORT;
}
"%s " /* (selinux) */
"%s " /* (vmchannel) */
"%s " /* (verbose) */
+ "TERM=%s " /* (TERM environment variable) */
"%s", /* (append) */
g->selinux ? "selinux=1 enforcing=0" : "selinux=0",
vmchannel ? vmchannel : "",
g->verbose ? "guestfs_verbose=1" : "",
+ getenv ("TERM") ? : "linux",
g->append ? g->append : "");
add_cmdline (g, "-kernel");
if (dup (wfd[0]) == -1) {
dup_failed:
perror ("dup failed");
- _exit (1);
+ _exit (EXIT_FAILURE);
}
if (dup (rfd[1]) == -1)
goto dup_failed;
execv (g->qemu, g->cmdline); /* Run qemu. */
perror (g->qemu);
- _exit (1);
+ _exit (EXIT_FAILURE);
}
/* Parent (library). */
*/
for (;;) {
if (kill (qemu_pid, 0) == -1) /* qemu's gone away, we aren't needed */
- _exit (0);
+ _exit (EXIT_SUCCESS);
if (kill (parent_pid, 0) == -1) {
/* Parent's gone away, qemu still around, so kill qemu. */
kill (qemu_pid, 9);
- _exit (0);
+ _exit (EXIT_SUCCESS);
}
sleep (2);
}
"libguestfs: warning: unexpected connection from UID %d to port %d\n",
uid, null_vmchannel_sock);
close (sock);
+ sock = -1;
continue;
}
}
close (wfd[1]);
close (rfd[0]);
}
- kill (g->pid, 9);
+ if (g->pid > 0) kill (g->pid, 9);
if (g->recoverypid > 0) kill (g->recoverypid, 9);
waitpid (g->pid, NULL, 0);
if (g->recoverypid > 0) waitpid (g->recoverypid, NULL, 0);
snprintf (*initrd, len+8, "%s/initrd", g->tmpdir);
snprintf (cmd, sizeof cmd,
- "PATH='%s':$PATH "
- "libguestfs-supermin-helper '%s' " host_cpu " " REPO " %s %s",
+ "febootstrap-supermin-helper%s "
+ "-k '%s/kmod.whitelist' "
+ "'%s/supermin.d' "
+ host_cpu " "
+ "%s %s",
+ g->verbose ? " --verbose" : "",
+ path,
path,
- path, *kernel, *initrd);
+ *kernel, *initrd);
if (g->verbose)
print_timestamped_message (g, "%s", cmd);
va_list args;
char *msg;
int err;
- struct timeval tv, diff;
+ struct timeval tv;
va_start (args, fs);
err = vasprintf (&msg, fs, args);
char cmd[1024];
FILE *fp;
- free (g->qemu_help);
- free (g->qemu_version);
- g->qemu_help = NULL;
- g->qemu_version = NULL;
-
- snprintf (cmd, sizeof cmd, "LC_ALL=C '%s' -help", g->qemu);
+ snprintf (cmd, sizeof cmd, "LC_ALL=C '%s' -nographic -help", g->qemu);
fp = popen (cmd, "r");
/* qemu -help should always work (qemu -version OTOH wasn't
if (pclose (fp) == -1)
goto error;
- snprintf (cmd, sizeof cmd, "LC_ALL=C '%s' -version 2>/dev/null", g->qemu);
+ snprintf (cmd, sizeof cmd, "LC_ALL=C '%s' -nographic -version 2>/dev/null",
+ g->qemu);
fp = popen (cmd, "r");
if (fp) {
/* Test if option is supported by qemu command line (just by grepping
* the help text).
+ *
+ * The first time this is used, it has to run the external qemu
+ * binary. If that fails, it returns -1.
+ *
+ * To just do the first-time run of the qemu binary, call this with
+ * option == NULL, in which case it will return -1 if there was an
+ * error doing that.
*/
static int
qemu_supports (guestfs_h *g, const char *option)
{
- return g->qemu_help && strstr (g->qemu_help, option) != NULL;
+ if (!g->qemu_help) {
+ if (test_qemu (g) == -1)
+ return -1;
+ }
+
+ if (option == NULL)
+ return 1;
+
+ return strstr (g->qemu_help, option) != NULL;
}
/* Check if a file can be opened. */
static int
check_peer_euid (guestfs_h *g, int sock, uid_t *rtn)
{
+#if CAN_CHECK_PEER_EUID
struct sockaddr_in peer;
socklen_t addrlen = sizeof peer;
error (g, "check_peer_euid: no matching TCP connection found in /proc/net/tcp");
fclose (fp);
return -1;
+#else /* !CAN_CHECK_PEER_EUID */
+ /* This function exists but should never be called in this
+ * configuration.
+ */
+ abort ();
+#endif /* !CAN_CHECK_PEER_EUID */
}
/* You had to call this function after launch in versions <= 1.0.70,
if (g->verbose)
fprintf (stderr, "sending SIGTERM to process %d\n", g->pid);
- kill (g->pid, SIGTERM);
+ if (g->pid > 0) kill (g->pid, SIGTERM);
if (g->recoverypid > 0) kill (g->recoverypid, 9);
return 0;
if (g->verbose)
fprintf (stderr, "child_cleanup: %p: child process died\n", g);
- /*kill (g->pid, SIGTERM);*/
+ /*if (g->pid > 0) kill (g->pid, SIGTERM);*/
if (g->recoverypid > 0) kill (g->recoverypid, 9);
waitpid (g->pid, NULL, 0);
if (g->recoverypid > 0) waitpid (g->recoverypid, NULL, 0);
}
static int
-read_log_message_or_eof (guestfs_h *g, int fd)
+read_log_message_or_eof (guestfs_h *g, int fd, int error_if_eof)
{
char buf[BUFSIZ];
int n;
if (n == 0) {
/* Hopefully this indicates the qemu child process has died. */
child_cleanup (g);
+
+ if (error_if_eof) {
+ /* We weren't expecting eof here (called from launch) so place
+ * something in the error buffer. RHBZ#588851.
+ */
+ error (g, "child process died unexpectedly");
+ }
return -1;
}
}
if (FD_ISSET (g->fd[1], &rset2)) {
- if (read_log_message_or_eof (g, g->fd[1]) == -1)
+ if (read_log_message_or_eof (g, g->fd[1], 0) == -1)
return -1;
}
if (FD_ISSET (g->sock, &rset2)) {
}
if (FD_ISSET (g->fd[1], &rset2)) {
- if (read_log_message_or_eof (g, g->fd[1]) == -1) {
+ if (read_log_message_or_eof (g, g->fd[1], 0) == -1) {
free (*buf_rtn);
*buf_rtn = NULL;
return -1;
int sock = -1;
while (sock == -1) {
+ /* If the qemu process has died, clean up the zombie (RHBZ#579155).
+ * By partially polling in the select below we ensure that this
+ * function will be called eventually.
+ */
+ waitpid (g->pid, NULL, WNOHANG);
+
rset2 = rset;
- int r = select (max_fd+1, &rset2, NULL, NULL, NULL);
+
+ struct timeval tv = { .tv_sec = 1, .tv_usec = 0 };
+ int r = select (max_fd+1, &rset2, NULL, NULL, &tv);
if (r == -1) {
if (errno == EINTR || errno == EAGAIN)
continue;
}
if (FD_ISSET (g->fd[1], &rset2)) {
- if (read_log_message_or_eof (g, g->fd[1]) == -1)
+ if (read_log_message_or_eof (g, g->fd[1], 1) == -1)
return -1;
}
if (FD_ISSET (g->sock, &rset2)) {
{
void *buf;
int fd, r;
- size_t len;
fd = open (filename, O_WRONLY|O_CREAT|O_TRUNC|O_NOCTTY, 0666);
if (fd == -1) {