[libvirt] [PATCH sandbox 2/4] Sync and unmount filesystems during shutdown

Daniel P. Berrange berrange at redhat.com
Fri Sep 4 11:40:35 UTC 2015


To ensure that all pending I/O for filesytems backed by block
devices is flushed to disk, it is important to sync and unmount
the filesystems during shutdown. To avoid relying on the kernel
reboot-on-panic behaviour, we also explicitly call reboot to
power off the guest.

Signed-off-by: Daniel P. Berrange <berrange at redhat.com>
---
 libvirt-sandbox/libvirt-sandbox-init-common.c | 166 ++++++++++++++++++++++++++
 libvirt-sandbox/libvirt-sandbox-init-qemu.c   |   1 +
 2 files changed, 167 insertions(+)

diff --git a/libvirt-sandbox/libvirt-sandbox-init-common.c b/libvirt-sandbox/libvirt-sandbox-init-common.c
index 760509f..a3b4687 100644
--- a/libvirt-sandbox/libvirt-sandbox-init-common.c
+++ b/libvirt-sandbox/libvirt-sandbox-init-common.c
@@ -44,6 +44,8 @@
 #include <unistd.h>
 #include <limits.h>
 #include <grp.h>
+#include <mntent.h>
+#include <sys/reboot.h>
 
 #include "libvirt-sandbox-rpcpacket.h"
 
@@ -52,6 +54,8 @@ static gboolean verbose = FALSE;
 static int sigwrite;
 
 #define ATTR_UNUSED __attribute__((__unused__))
+static void sync_data(void);
+static void umount_fs(void);
 
 static void sig_child(int sig ATTR_UNUSED)
 {
@@ -598,6 +602,14 @@ static GVirSandboxRPCPacket *gvir_sandbox_encode_exit(int status,
     pkt->header.type = GVIR_SANDBOX_PROTOCOL_TYPE_MESSAGE;
     pkt->header.serial = serial;
 
+    /* The host may destroy the guest any time after receiving
+     * the exit code messages. So although the main() has code
+     * to sync + unmount we can't rely on that running. So we
+     * opportunistically sync + unmount here too.
+     */
+    sync_data();
+    umount_fs();
+
     if (!gvir_sandbox_rpcpacket_encode_header(pkt, error))
         goto error;
     if (!gvir_sandbox_rpcpacket_encode_payload_msg(pkt,
@@ -613,7 +625,151 @@ static GVirSandboxRPCPacket *gvir_sandbox_encode_exit(int status,
     return NULL;
 }
 
+/* Copied & adapted from libguestfs daemon/sync.c under LGPLv2+ */
+static void sync_data(void)
+{
+  DIR *dir;
+  struct dirent *d;
+  char *dev_path;
+  int fd;
+
+  if (debug)
+      fprintf(stderr, "Syncing data\n");
+  sync();
+
+  /* On Linux, sync(2) doesn't perform a barrier, so qemu (which may
+   * have a writeback cache, even with cache=none) will still have
+   * some unwritten data.  Force the data out of any qemu caches, by
+   * calling fsync on all block devices.  Note we still need the
+   * call to sync above in order to schedule the writes.
+   * Thanks to: Avi Kivity, Kevin Wolf.
+   */
+
+  if (!(dir = opendir("/dev"))) {
+      fprintf(stderr, "opendir: /dev failed %s\n", strerror(errno));
+      return;
+  }
+
+  for (;;) {
+      errno = 0;
+      d = readdir(dir);
+      if (!d)
+          break;
+
+      if (!(g_str_has_prefix(d->d_name, "sd") ||
+            g_str_has_prefix(d->d_name, "hd") ||
+            g_str_has_prefix(d->d_name, "ubd") ||
+            g_str_has_prefix(d->d_name, "vd") ||
+            g_str_has_prefix(d->d_name, "sr"))) {
+          continue;
+      }
+
+      dev_path = g_strdup_printf("/dev/%s", d->d_name);
+
+      if (debug)
+          fprintf(stderr, "Syncing fd %s\n", dev_path);
+      if ((fd = open(dev_path, O_RDONLY)) < 0) {
+          fprintf(stderr, "cannot open %s: %s\n", dev_path,
+                  strerror(errno));
+          g_free(dev_path);
+          continue;
+      }
+
+      /* fsync the device. */
+      if (debug) {
+          fprintf(stderr, "fsync %s\n", dev_path);
+      }
+
+      if (fsync(fd) < 0) {
+          fprintf(stderr, "failed to fsync %s: %s\n",
+                  dev_path, strerror(errno));
+      }
+      if (close(fd) < 0) {
+          fprintf(stderr, "failed to close %s: %s\n",
+                  dev_path, strerror(errno));
+      }
+      g_free(dev_path);
+  }
+
+  /* Check readdir didn't fail */
+  if (errno != 0) {
+      fprintf(stderr, "Failed to read /dev: %s\n",
+              strerror(errno));
+  }
+
+  /* Close the directory handle */
+  if (closedir(dir) < 0) {
+      fprintf(stderr, "Failed to block /dev: %s\n",
+              strerror(errno));
+  }
+
+  if (debug)
+      fprintf(stderr, "Syncing complete\n");
+}
+
+
+static int
+compare_longest_first (gconstpointer vp1, gconstpointer vp2)
+{
+    int n1 = strlen(vp1);
+    int n2 = strlen(vp2);
+    return n2 - n1;
+}
+
+
+/* Copied & adapted from libguestfs daemon/sync.c under LGPLv2+ */
+static void umount_fs(void)
+{
+    FILE *fp;
+    struct mntent *m;
+    GList *mounts = NULL, *tmp;
+
+    if (debug)
+        fprintf(stderr, "Unmounting all filesystems\n");
+    if (!(fp = setmntent ("/proc/mounts", "r"))) {
+        fprintf(stderr, "Failed to open /proc/mounts: %s\n",
+                strerror(errno));
+        return;
+    }
+
+    while ((m = getmntent (fp)) != NULL) {
+        if (debug)
+            fprintf(stderr, "Got fsname=%s dir=%s type=%s opts=%s freq=%d passno=%d\n",
+                    m->mnt_fsname, m->mnt_dir, m->mnt_type, m->mnt_opts,
+                    m->mnt_freq, m->mnt_passno);
+
+        mounts = g_list_append(mounts, g_strdup(m->mnt_dir));
+    }
 
+    endmntent(fp);
+
+    mounts = g_list_sort(mounts, compare_longest_first);
+
+    /* Unmount them. */
+    tmp = mounts;
+    while (tmp) {
+        char *dir = tmp->data;
+
+        if (debug)
+            fprintf(stderr, "Unmounting %s\n", dir);
+        if (umount(dir) < 0) {
+            /* We expect some failures, so don't pollute
+             * logs with them uneccessarily
+             */
+            if (debug || errno != EBUSY)
+                fprintf(stderr, "cannot unmount %s: %s\n",
+                        dir, strerror(errno));
+            /* ignore failure */
+        }
+        g_free(dir);
+
+        tmp = tmp->next;
+    }
+
+    g_list_free(mounts);
+    if (debug)
+        fprintf(stderr, "Unmounting complete\n");
+}
 
 
 static gssize read_data(int fd, char *buf, size_t len)
@@ -1204,6 +1360,7 @@ static void libvirt_sandbox_version(void)
 
 int main(int argc, char **argv) {
     gchar *configfile = NULL;
+    gboolean poweroff = FALSE;
     GError *error = NULL;
     GOptionContext *context;
     GOptionEntry options[] = {
@@ -1215,6 +1372,8 @@ int main(int argc, char **argv) {
           N_("display debugging information"), NULL },
         { "config", 'c', 0, G_OPTION_ARG_STRING, &configfile,
           N_("config file path"), "URI"},
+        { "poweroff", 'p', 0, G_OPTION_ARG_NONE, &poweroff,
+          N_("clean power off when exiting"), NULL},
         { NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, NULL }
     };
     const char *help_msg = N_("Run '" PACKAGE " --help' to see a full list of available command line options");
@@ -1290,6 +1449,13 @@ int main(int argc, char **argv) {
     if (error)
         g_error_free(error);
 
+    sync_data();
+
+    if (poweroff) {
+        umount_fs();
+        reboot(RB_POWER_OFF);
+        /* Should not be reached, but if it is, kernel will panic anyway */
+    }
     return ret;
 
  error:
diff --git a/libvirt-sandbox/libvirt-sandbox-init-qemu.c b/libvirt-sandbox/libvirt-sandbox-init-qemu.c
index cd6055a..8bde224 100644
--- a/libvirt-sandbox/libvirt-sandbox-init-qemu.c
+++ b/libvirt-sandbox/libvirt-sandbox-init-qemu.c
@@ -424,6 +424,7 @@ main(int argc ATTR_UNUSED, char **argv ATTR_UNUSED)
 
     args[narg++] = SANDBOXCONFIGDIR "/.libs/ld.so";
     args[narg++] = SANDBOXCONFIGDIR "/.libs/libvirt-sandbox-init-common";
+    args[narg++] = "--poweroff";
     if (debug)
         args[narg++] = "-d";
 
-- 
2.4.3




More information about the libvir-list mailing list