add_drive_ro adds readonly=on option if available.
[libguestfs.git] / src / guestfs.c
index 1d6d40b..02d5fdb 100644 (file)
@@ -91,6 +91,7 @@ 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
 
@@ -748,7 +749,8 @@ guestfs__config (guestfs_h *g,
 }
 
 int
-guestfs__add_drive (guestfs_h *g, const char *filename)
+guestfs__add_drive_with_if (guestfs_h *g, const char *filename,
+                            const char *drive_if)
 {
   size_t len = strlen (filename) + 64;
   char buf[len];
@@ -771,12 +773,12 @@ guestfs__add_drive (guestfs_h *g, const char *filename)
   int fd = open (filename, O_RDONLY|O_DIRECT);
   if (fd >= 0) {
     close (fd);
-    snprintf (buf, len, "file=%s,cache=off,if=" DRIVE_IF, filename);
+    snprintf (buf, len, "file=%s,cache=off,if=%s", filename, drive_if);
   } else {
     fd = open (filename, O_RDONLY);
     if (fd >= 0) {
       close (fd);
-      snprintf (buf, len, "file=%s,if=" DRIVE_IF, filename);
+      snprintf (buf, len, "file=%s,if=%s", filename, drive_if);
     } else {
       perrorf (g, "%s", filename);
       return -1;
@@ -787,11 +789,9 @@ guestfs__add_drive (guestfs_h *g, const char *filename)
 }
 
 int
-guestfs__add_drive_ro (guestfs_h *g, const char *filename)
+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;
@@ -802,12 +802,41 @@ guestfs__add_drive_ro (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);
 }
 
 int
+guestfs__add_drive (guestfs_h *g, const char *filename)
+{
+  return guestfs__add_drive_with_if (g, filename, DRIVE_IF);
+}
+
+int
+guestfs__add_drive_ro (guestfs_h *g, const char *filename)
+{
+  return guestfs__add_drive_ro_with_if (g, filename, DRIVE_IF);
+}
+
+int
 guestfs__add_cdrom (guestfs_h *g, const char *filename)
 {
   if (strchr (filename, ',') != NULL) {
@@ -856,8 +885,7 @@ 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);
 
 static const char *kernel_name = "vmlinuz." REPO "." host_cpu;
@@ -1008,7 +1036,7 @@ 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. */
@@ -1091,6 +1119,22 @@ guestfs__launch (guestfs_h *g)
      */
     g->cmdline[0] = g->qemu;
 
+    /* qemu sometimes needs this option to enable hardware
+     * virtualization, but some versions of 'qemu-kvm' will use KVM
+     * regardless (even where this option appears in the help text).
+     * It is rumoured that there are versions of qemu where supplying
+     * this option when hardware virtualization is not available will
+     * cause qemu to fail, so we we have to check at least that
+     * /dev/kvm is openable.  That's not reliable, since /dev/kvm
+     * might be openable by qemu but not by us (think: SELinux) in
+     * which case the user would not get hardware virtualization,
+     * although at least shouldn't fail.  A giant clusterfuck with the
+     * qemu command line, again.
+     */
+    if (qemu_supports (g, "-enable-kvm") &&
+        is_openable (g, "/dev/kvm", O_RDWR))
+      add_cmdline (g, "-enable-kvm");
+
     /* Newer versions of qemu (from around 2009/12) changed the
      * behaviour of monitors so that an implicit '-monitor stdio' is
      * assumed if we are in -nographic mode and there is no other
@@ -1506,8 +1550,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);
@@ -1547,7 +1592,7 @@ print_timestamped_message (guestfs_h *g, const char *fs, ...)
   va_list args;
   char *msg;
   int err;
-  struct timeval tv, diff;
+  struct timeval tv;
 
   va_start (args, fs);
   err = vasprintf (&msg, fs, args);
@@ -1575,11 +1620,6 @@ 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);
 
   fp = popen (cmd, "r");
@@ -1640,11 +1680,40 @@ 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. */
+static int
+is_openable (guestfs_h *g, const char *path, int flags)
+{
+  int fd = open (path, flags);
+  if (fd == -1) {
+    if (g->verbose)
+      perror (path);
+    return 0;
+  }
+  close (fd);
+  return 1;
 }
 
 /* Check the peer effective UID for a TCP socket.  Ideally we'd like
@@ -2588,7 +2657,6 @@ guestfs___recv_file (guestfs_h *g, const char *filename)
 {
   void *buf;
   int fd, r;
-  size_t len;
 
   fd = open (filename, O_WRONLY|O_CREAT|O_TRUNC|O_NOCTTY, 0666);
   if (fd == -1) {