From 9521422ce60578f7196cc8b7977d998159238c19 Mon Sep 17 00:00:00 2001 From: "Richard W.M. Jones" Date: Mon, 7 Oct 2013 19:55:00 +0100 Subject: [PATCH] daemon: command: Copy appliance /etc/resolv.conf in before running commands. When you try to run commands for an Ubuntu guest, they fail because in Ubuntu /etc/resolv.conf is a symlink to /run/... and this turns out to be a dangling symlink when the Ubuntu guest is mounted up under the appliance. Therefore even if the network is enabled, any command which tries to do name resolution will fail. Ideally we would like to bind-mount the appliance /etc/resolv.conf into the sysroot. However this is not possible because mount is buggy (see comment). So instead we use a complex hack to achieve the same ends. Note this is only done if the network is enabled and if /etc in the guest actually exists. The original /etc/resolv.conf is restored as soon as the command has run. --- daemon/command.c | 108 ++++++++++++++++++++++++++++++++++++++++++++ daemon/daemon.h | 2 + daemon/guestfsd.c | 3 ++ daemon/guestfsd.pod | 5 ++ src/launch.c | 2 + 5 files changed, 120 insertions(+) diff --git a/daemon/command.c b/daemon/command.c index b100b145c..02e1dd42d 100644 --- a/daemon/command.c +++ b/daemon/command.c @@ -22,6 +22,7 @@ #include #include #include +#include #include "guestfs_protocol.h" #include "daemon.h" @@ -34,8 +35,10 @@ GUESTFSD_EXT_CMD(str_umount, umount); #ifdef HAVE_ATTRIBUTE_CLEANUP #define CLEANUP_BIND_STATE __attribute__((cleanup(free_bind_state))) +#define CLEANUP_RESOLVER_STATE __attribute__((cleanup(free_resolver_state))) #else #define CLEANUP_BIND_STATE +#define CLEANUP_RESOLVER_STATE #endif struct bind_state { @@ -47,6 +50,12 @@ struct bind_state { bool dev_ok, dev_pts_ok, proc_ok, sys_ok; }; +struct resolver_state { + bool mounted; + char *sysroot_etc_resolv_conf; + char *sysroot_etc_resolv_conf_old; +}; + /* While running the command, bind-mount /dev, /proc, /sys * into the chroot. However we must be careful to unmount them * afterwards because otherwise they would interfere with @@ -113,6 +122,98 @@ free_bind_state (struct bind_state *bs) } } +/* If the network is enabled, we want /etc/resolv.conf to + * reflect the contents of /etc/resolv.conf so that name resolution + * works. It would be nice to bind-mount the file (single file bind + * mounts are possible). However annoyingly that doesn't work for + * Ubuntu guests where the guest resolv.conf is a dangling symlink, + * and for reasons unknown mount tries to follow the symlink and + * fails (likely a bug). So this is a hack. Note we only invoke + * this if the network is enabled. + */ +static int +set_up_etc_resolv_conf (struct resolver_state *rs) +{ + struct stat statbuf; + + rs->sysroot_etc_resolv_conf_old = NULL; + + rs->sysroot_etc_resolv_conf = sysroot_path ("/etc/resolv.conf"); + + if (!rs->sysroot_etc_resolv_conf) { + reply_with_perror ("malloc"); + goto error; + } + + /* If /etc/resolv.conf exists, rename it to the backup file. Note + * that on Ubuntu it's a dangling symlink. + */ + if (lstat (rs->sysroot_etc_resolv_conf, &statbuf) == 0) { + size_t len = sysroot_len + 32; + char buf[len]; + + /* Make a random name for the backup file. */ + snprintf (buf, len, "%s/etc/XXXXXXXX", sysroot); + if (random_name (buf) == -1) { + reply_with_perror ("random_name"); + goto error; + } + rs->sysroot_etc_resolv_conf_old = strdup (buf); + if (!rs->sysroot_etc_resolv_conf_old) { + reply_with_perror ("strdup"); + goto error; + } + + if (verbose) + fprintf (stderr, "renaming %s to %s\n", rs->sysroot_etc_resolv_conf, + rs->sysroot_etc_resolv_conf_old); + + if (rename (rs->sysroot_etc_resolv_conf, + rs->sysroot_etc_resolv_conf_old) == -1) { + reply_with_perror ("rename: %s to %s", rs->sysroot_etc_resolv_conf, + rs->sysroot_etc_resolv_conf_old); + goto error; + } + } + + /* Now that the guest's /etc/resolv.conf is out the way, we + * can create our own copy of the appliance /etc/resolv.conf. + */ + ignore_value (command (NULL, NULL, "cp", "/etc/resolv.conf", + rs->sysroot_etc_resolv_conf, NULL)); + + rs->mounted = true; + return 0; + + error: + free (rs->sysroot_etc_resolv_conf); + free (rs->sysroot_etc_resolv_conf_old); + return -1; +} + +static void +free_resolver_state (struct resolver_state *rs) +{ + if (rs->mounted) { + unlink (rs->sysroot_etc_resolv_conf); + + if (rs->sysroot_etc_resolv_conf_old) { + if (verbose) + fprintf (stderr, "renaming %s to %s\n", rs->sysroot_etc_resolv_conf_old, + rs->sysroot_etc_resolv_conf); + + if (rename (rs->sysroot_etc_resolv_conf_old, + rs->sysroot_etc_resolv_conf) == -1) + perror ("error: could not restore /etc/resolv.conf"); + + free (rs->sysroot_etc_resolv_conf_old); + } + + free (rs->sysroot_etc_resolv_conf); + rs->mounted = false; + } +} + char * do_command (char *const *argv) { @@ -120,6 +221,8 @@ do_command (char *const *argv) CLEANUP_FREE char *err = NULL; int r; CLEANUP_BIND_STATE struct bind_state bind_state = { .mounted = false }; + CLEANUP_RESOLVER_STATE struct resolver_state resolver_state = + { .mounted = false }; /* We need a root filesystem mounted to do this. */ NEED_ROOT (, return NULL); @@ -135,12 +238,17 @@ do_command (char *const *argv) if (bind_mount (&bind_state) == -1) return NULL; + if (enable_network) { + if (set_up_etc_resolv_conf (&resolver_state) == -1) + return NULL; + } CHROOT_IN; r = commandv (&out, &err, (const char * const *) argv); CHROOT_OUT; free_bind_state (&bind_state); + free_resolver_state (&resolver_state); if (r == -1) { reply_with_error ("%s", err); diff --git a/daemon/daemon.h b/daemon/daemon.h index b3ee98461..78d5dea36 100644 --- a/daemon/daemon.h +++ b/daemon/daemon.h @@ -43,6 +43,8 @@ typedef struct { /*-- in guestfsd.c --*/ extern int verbose; +extern int enable_network; + extern int autosync_umount; extern const char *sysroot; diff --git a/daemon/guestfsd.c b/daemon/guestfsd.c index 4ef7d6f2f..2cf700f26 100644 --- a/daemon/guestfsd.c +++ b/daemon/guestfsd.c @@ -78,6 +78,7 @@ static char *read_cmdline (void); static dev_t root_device = 0; int verbose = 0; +int enable_network = 0; static void makeraw (const char *channel, int fd); static int print_shell_quote (FILE *stream, const struct printf_info *info, const void *const *args); @@ -222,6 +223,8 @@ main (int argc, char *argv[]) printf ("could not read linux command line\n"); } + enable_network = cmdline && strstr (cmdline, "guestfs_network=1") != NULL; + #ifndef WIN32 /* Make sure SIGPIPE doesn't kill us. */ struct sigaction sa; diff --git a/daemon/guestfsd.pod b/daemon/guestfsd.pod index 062de4b40..82f994110 100644 --- a/daemon/guestfsd.pod +++ b/daemon/guestfsd.pod @@ -105,6 +105,11 @@ default (which is C). This is used by the User-Mode Linux backend to use a regular emulated serial port instead of virtio-serial. +=item B + +This is set if the appliance network is enabled (see +C). + =back =back diff --git a/src/launch.c b/src/launch.c index 7cfcc90a0..48db1bf26 100644 --- a/src/launch.c +++ b/src/launch.c @@ -362,6 +362,7 @@ guestfs___appliance_command_line (guestfs_h *g, const char *appliance_dev, "%s" /* root=appliance_dev */ " %s" /* selinux */ "%s" /* verbose */ + "%s" /* network */ " TERM=%s" /* TERM environment variable */ "%s%s", /* append */ #ifdef __arm__ @@ -371,6 +372,7 @@ guestfs___appliance_command_line (guestfs_h *g, const char *appliance_dev, root, g->selinux ? "selinux=1 enforcing=0" : "selinux=0", g->verbose ? " guestfs_verbose=1" : "", + g->enable_network ? " guestfs_network=1" : "", term ? term : "linux", g->append ? " " : "", g->append ? g->append : "");