On Tue, Jul 07, 2026 at 09:48:22AM +0200, Denis V. Lunev via Devel wrote: > 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.
This is a good idea, however, I wonder if we should also have a global cross-VM limit, as with cloud you might equally see a loop where a VM fails to spawn and automation spins up a new VM with different identity each time. Perhaps also "auto_dump_min_free_space" where we statfs the dump location to figure out current free space, subtract the VM RAM size (as a rough approximation for worst case dump size), and then reject the dump if the result would be under the limit. If you want to do that, it could be a separate followup patch so no need to repost this series just for that. > > 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 > With regards, Daniel -- |: https://berrange.com ~~ https://hachyderm.io/@berrange :| |: https://libvirt.org ~~ https://entangle-photo.org :| |: https://pixelfed.art/berrange ~~ https://fstop138.berrange.com :|
