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 : "");