Change network configuration to use macros.
[libguestfs.git] / src / guestfs.c
index 0e91b96..d68f12b 100644 (file)
@@ -33,6 +33,7 @@
 #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 */
+#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 "10.0.2.0/8"
+#define ROUTER "10.0.2.2"
 #define GUESTFWD_ADDR "10.0.2.4"
 #define GUESTFWD_PORT "6666"
 
@@ -389,7 +424,7 @@ guestfs_perrorf (guestfs_h *g, const char *fs, ...)
 
   if (err < 0) return;
 
-#ifndef _GNU_SOURCE
+#if !defined(_GNU_SOURCE) || defined(__APPLE__)
   char buf[256];
   strerror_r (errnum, buf, sizeof buf);
 #else
@@ -433,7 +468,11 @@ guestfs_safe_malloc (guestfs_h *g, size_t nbytes)
 
 /* 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.  */
@@ -791,9 +830,6 @@ int
 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;
@@ -804,7 +840,24 @@ guestfs__add_drive_ro_with_if (guestfs_h *g, const char *filename,
     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);
 }
@@ -870,8 +923,6 @@ dir_contains_files (const char *dir, ...)
 
 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);
 
@@ -1023,11 +1074,11 @@ guestfs__launch (guestfs_h *g)
     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.
@@ -1157,10 +1208,11 @@ guestfs__launch (guestfs_h *g)
      */
     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);
     }
 
@@ -1183,7 +1235,7 @@ guestfs__launch (guestfs_h *g)
       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");
 
@@ -1203,7 +1255,7 @@ guestfs__launch (guestfs_h *g)
       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;
     }
@@ -1225,10 +1277,12 @@ guestfs__launch (guestfs_h *g)
               "%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");
@@ -1255,7 +1309,7 @@ guestfs__launch (guestfs_h *g)
       if (dup (wfd[0]) == -1) {
       dup_failed:
         perror ("dup failed");
-        _exit (1);
+        _exit (EXIT_FAILURE);
       }
       if (dup (rfd[1]) == -1)
         goto dup_failed;
@@ -1275,7 +1329,7 @@ guestfs__launch (guestfs_h *g)
 
     execv (g->qemu, g->cmdline); /* Run qemu. */
     perror (g->qemu);
-    _exit (1);
+    _exit (EXIT_FAILURE);
   }
 
   /* Parent (library). */
@@ -1307,11 +1361,11 @@ guestfs__launch (guestfs_h *g)
        */
       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);
       }
@@ -1371,6 +1425,7 @@ guestfs__launch (guestfs_h *g)
                  "libguestfs: warning: unexpected connection from UID %d to port %d\n",
                  uid, null_vmchannel_sock);
         close (sock);
+        sock = -1;
         continue;
       }
     }
@@ -1465,7 +1520,7 @@ guestfs__launch (guestfs_h *g)
     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);
@@ -1537,8 +1592,9 @@ build_supermin_appliance (guestfs_h *g, const char *path,
 
   snprintf (cmd, sizeof cmd,
             "PATH='%s':$PATH "
-            "libguestfs-supermin-helper '%s' " host_cpu " " REPO " %s %s",
+            "libguestfs-supermin-helper%s '%s' " host_cpu " " REPO " %s %s",
             path,
+            g->verbose ? " --verbose" : "",
             path, *kernel, *initrd);
   if (g->verbose)
     print_timestamped_message (g, "%s", cmd);
@@ -1606,12 +1662,7 @@ test_qemu (guestfs_h *g)
   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
@@ -1633,7 +1684,8 @@ test_qemu (guestfs_h *g)
   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) {
@@ -1671,11 +1723,26 @@ read_all (guestfs_h *g, FILE *fp, char **ret)
 
 /* 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. */
@@ -1699,6 +1766,7 @@ is_openable (guestfs_h *g, const char *path, int flags)
 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;
 
@@ -1767,6 +1835,12 @@ check_peer_euid (guestfs_h *g, int sock, uid_t *rtn)
   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,
@@ -1794,7 +1868,7 @@ guestfs__kill_subprocess (guestfs_h *g)
   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;
@@ -1945,7 +2019,7 @@ child_cleanup (guestfs_h *g)
   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);
@@ -2312,8 +2386,16 @@ accept_from_daemon (guestfs_h *g)
   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;