Each on_crash or watchdog-triggered dump writes a full memory dump
into auto_dump_path. A guest that keeps crashing and restarting (or
crashing and getting destroyed, then respawned by the mgmt app) can
fill the disk one dump at a time, with nothing to stop it.

Add auto_dump_max_size (qemu.conf), parsed via virConfGetValueBytes()
so it takes a plain byte count or a size with a unit suffix (e.g.
"10GiB"). After each dump attempt, the oldest files under
auto_dump_path are removed until the total fits the configured quota.
The dump that was just written is always kept by identity, not by
sort position: mtime is only second-granularity, so two dumps written
the same second would otherwise make the eviction order between them
arbitrary and could delete the one just written instead of an older
one. Defaults to 0, which keeps every dump forever, as before.

Signed-off-by: Denis V. Lunev <[email protected]>
---
 src/qemu/qemu.conf.in  | 16 +++++++
 src/qemu/qemu_conf.c   |  2 +
 src/qemu/qemu_conf.h   |  1 +
 src/qemu/qemu_driver.c | 97 ++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 116 insertions(+)

diff --git a/src/qemu/qemu.conf.in b/src/qemu/qemu.conf.in
index 97b0141cf6..3828af0fab 100644
--- a/src/qemu/qemu.conf.in
+++ b/src/qemu/qemu.conf.in
@@ -688,6 +688,22 @@
 #auto_dump_bypass_cache = 0
 
 
+# Total size that auto-triggered dumps (from on_crash and watchdog
+# handling) are allowed to occupy under auto_dump_path. After each new
+# dump is written, the oldest dumps are removed until the total fits
+# the quota again. The dump that was just written is never removed by
+# this, even if it alone exceeds the quota.
+#
+# The value is a plain byte count, or a byte count followed by a unit
+# suffix: bytes/b, KB/k/KiB, MB/M/MiB, GB/G/GiB, TB/T/TiB, PB/P/PiB, or
+# EB/E/EiB (decimal 'B' suffixes scale by 1000, binary 'iB' suffixes,
+# and their bare single-letter equivalents, scale by 1024).
+#
+# Defaults to 0, which disables the quota and keeps every dump forever.
+#
+#auto_dump_max_size = "10GiB"
+
+
 # When a domain is configured to be auto-started, enabling this flag
 # has the same effect as using the VIR_DOMAIN_START_BYPASS_CACHE flag
 # with the virDomainCreateWithFlags API.  That is, the system will
diff --git a/src/qemu/qemu_conf.c b/src/qemu/qemu_conf.c
index e30b146634..6d67939b6e 100644
--- a/src/qemu/qemu_conf.c
+++ b/src/qemu/qemu_conf.c
@@ -748,6 +748,8 @@ virQEMUDriverConfigLoadSaveEntry(virQEMUDriverConfig *cfg,
         return -1;
     if (virConfGetValueBool(conf, "auto_dump_bypass_cache", 
&cfg->autoDumpBypassCache) < 0)
         return -1;
+    if (virConfGetValueBytes(conf, "auto_dump_max_size", 
&cfg->autoDumpMaxSize) < 0)
+        return -1;
     if (virConfGetValueBool(conf, "auto_start_bypass_cache", 
&cfg->autoStartBypassCache) < 0)
         return -1;
     if (virConfGetValueUInt(conf, "auto_start_delay", &cfg->autoStartDelayMS) 
< 0)
diff --git a/src/qemu/qemu_conf.h b/src/qemu/qemu_conf.h
index 1d29f35c5d..9faf6db206 100644
--- a/src/qemu/qemu_conf.h
+++ b/src/qemu/qemu_conf.h
@@ -226,6 +226,7 @@ struct _virQEMUDriverConfig {
 
     char *autoDumpPath;
     bool autoDumpBypassCache;
+    unsigned long long autoDumpMaxSize;
     bool autoStartBypassCache;
     unsigned int autoStartDelayMS;
     virDomainDriverAutoShutdownConfig autoShutdown;
diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c
index b59a714a81..3f021bb7c5 100644
--- a/src/qemu/qemu_driver.c
+++ b/src/qemu/qemu_driver.c
@@ -3557,6 +3557,101 @@ qemuDomainGetAutoDumpFormat(virDomainObj *vm)
 }
 
 
+typedef struct _qemuAutoDumpFile qemuAutoDumpFile;
+struct _qemuAutoDumpFile {
+    char *path;
+    unsigned long long size;
+    long long mtime;
+};
+
+static void
+qemuAutoDumpFileFree(void *opaque)
+{
+    qemuAutoDumpFile *file = opaque;
+
+    g_free(file->path);
+    g_free(file);
+}
+
+
+static gint
+qemuAutoDumpFileCompare(gconstpointer a,
+                        gconstpointer b)
+{
+    qemuAutoDumpFile *fa = *(qemuAutoDumpFile **) a;
+    qemuAutoDumpFile *fb = *(qemuAutoDumpFile **) b;
+
+    return fa->mtime < fb->mtime ? -1 : fa->mtime > fb->mtime;
+}
+
+
+/* Removes the oldest files under autoDumpPath until the total size fits
+ * autoDumpMaxSize. KEEP (the dump just written) is never removed, even
+ * alone over quota: mtime alone can't protect it, since it is only
+ * second-granularity and ties with another dump written the same
+ * second would make the eviction order among them arbitrary. */
+static void
+qemuPruneAutoDumpPath(virQEMUDriverConfig *cfg,
+                      const char *keep)
+{
+    g_autoptr(DIR) dir = NULL;
+    struct dirent *entry;
+    g_autoptr(GPtrArray) files = NULL;
+    unsigned long long total = 0;
+    size_t i;
+    int rc;
+
+    if (cfg->autoDumpMaxSize == 0)
+        return;
+
+    if (virDirOpenQuiet(&dir, cfg->autoDumpPath) < 0)
+        return;
+
+    files = g_ptr_array_new_with_free_func(qemuAutoDumpFileFree);
+
+    while ((rc = virDirRead(dir, &entry, NULL)) > 0) {
+        g_autofree char *path = g_strdup_printf("%s/%s", cfg->autoDumpPath,
+                                                entry->d_name);
+        GStatBuf sb;
+        qemuAutoDumpFile *file;
+
+        if (g_stat(path, &sb) < 0 || !S_ISREG(sb.st_mode))
+            continue;
+
+        total += sb.st_size;
+
+        if (STREQ(path, keep))
+            continue;
+
+        file = g_new0(qemuAutoDumpFile, 1);
+        file->path = g_steal_pointer(&path);
+        file->size = sb.st_size;
+        file->mtime = sb.st_mtime;
+
+        g_ptr_array_add(files, file);
+    }
+
+    if (rc < 0)
+        return;
+
+    g_ptr_array_sort(files, qemuAutoDumpFileCompare);
+
+    for (i = 0; i < files->len && total > cfg->autoDumpMaxSize; i++) {
+        qemuAutoDumpFile *file = g_ptr_array_index(files, i);
+
+        if (unlink(file->path) < 0 && errno != ENOENT) {
+            VIR_WARN("Failed to prune old dump %s: %s",
+                     file->path, g_strerror(errno));
+            continue;
+        }
+
+        VIR_INFO("Pruned old dump %s to satisfy auto_dump_max_size quota",
+                 file->path);
+        total -= file->size;
+    }
+}
+
+
 static void
 processWatchdogEvent(virQEMUDriver *driver,
                      virDomainObj *vm,
@@ -3586,6 +3681,7 @@ processWatchdogEvent(virQEMUDriver *driver,
                               qemuDomainGetAutoDumpFormat(vm))) < 0)
             virReportError(VIR_ERR_OPERATION_FAILED,
                            "%s", _("Dump failed"));
+        qemuPruneAutoDumpPath(cfg, dumpfile);
 
         ret = qemuProcessStartCPUs(driver, vm,
                                    VIR_DOMAIN_RUNNING_UNPAUSED,
@@ -3621,6 +3717,7 @@ doCoreDumpToAutoDumpPath(virQEMUDriver *driver,
                           qemuDomainGetAutoDumpFormat(vm))) < 0)
         virReportError(VIR_ERR_OPERATION_FAILED,
                        "%s", _("Dump failed"));
+    qemuPruneAutoDumpPath(cfg, dumpfile);
     return ret;
 }
 
-- 
2.53.0

Reply via email to