diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 15:35:18 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 15:35:18 +0000 |
commit | b750101eb236130cf056c675997decbac904cc49 (patch) | |
tree | a5df1a06754bdd014cb975c051c83b01c9a97532 /src/coredump/coredump-vacuum.c | |
parent | Initial commit. (diff) | |
download | systemd-b750101eb236130cf056c675997decbac904cc49.tar.xz systemd-b750101eb236130cf056c675997decbac904cc49.zip |
Adding upstream version 252.22.upstream/252.22
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/coredump/coredump-vacuum.c')
-rw-r--r-- | src/coredump/coredump-vacuum.c | 245 |
1 files changed, 245 insertions, 0 deletions
diff --git a/src/coredump/coredump-vacuum.c b/src/coredump/coredump-vacuum.c new file mode 100644 index 0000000..c6e201e --- /dev/null +++ b/src/coredump/coredump-vacuum.c @@ -0,0 +1,245 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <sys/statvfs.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include "alloc-util.h" +#include "coredump-vacuum.h" +#include "dirent-util.h" +#include "fd-util.h" +#include "fs-util.h" +#include "hashmap.h" +#include "macro.h" +#include "memory-util.h" +#include "stat-util.h" +#include "string-util.h" +#include "time-util.h" +#include "user-util.h" + +#define DEFAULT_MAX_USE_LOWER (uint64_t) (1ULL*1024ULL*1024ULL) /* 1 MiB */ +#define DEFAULT_MAX_USE_UPPER (uint64_t) (4ULL*1024ULL*1024ULL*1024ULL) /* 4 GiB */ +#define DEFAULT_KEEP_FREE_UPPER (uint64_t) (4ULL*1024ULL*1024ULL*1024ULL) /* 4 GiB */ +#define DEFAULT_KEEP_FREE (uint64_t) (1024ULL*1024ULL) /* 1 MB */ + +typedef struct VacuumCandidate { + unsigned n_files; + char *oldest_file; + usec_t oldest_mtime; +} VacuumCandidate; + +static VacuumCandidate* vacuum_candidate_free(VacuumCandidate *c) { + if (!c) + return NULL; + + free(c->oldest_file); + return mfree(c); +} +DEFINE_TRIVIAL_CLEANUP_FUNC(VacuumCandidate*, vacuum_candidate_free); + +static Hashmap* vacuum_candidate_hashmap_free(Hashmap *h) { + return hashmap_free_with_destructor(h, vacuum_candidate_free); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(Hashmap*, vacuum_candidate_hashmap_free); + +static int uid_from_file_name(const char *filename, uid_t *uid) { + const char *p, *e, *u; + + p = startswith(filename, "core."); + if (!p) + return -EINVAL; + + /* Skip the comm field */ + p = strchr(p, '.'); + if (!p) + return -EINVAL; + p++; + + /* Find end up UID */ + e = strchr(p, '.'); + if (!e) + return -EINVAL; + + u = strndupa_safe(p, e - p); + return parse_uid(u, uid); +} + +static bool vacuum_necessary(int fd, uint64_t sum, uint64_t keep_free, uint64_t max_use) { + uint64_t fs_size = 0, fs_free = UINT64_MAX; + struct statvfs sv; + + assert(fd >= 0); + + if (fstatvfs(fd, &sv) >= 0) { + fs_size = sv.f_frsize * sv.f_blocks; + fs_free = sv.f_frsize * sv.f_bfree; + } + + if (max_use == UINT64_MAX) { + + if (fs_size > 0) { + max_use = PAGE_ALIGN(fs_size / 10); /* 10% */ + + if (max_use > DEFAULT_MAX_USE_UPPER) + max_use = DEFAULT_MAX_USE_UPPER; + + if (max_use < DEFAULT_MAX_USE_LOWER) + max_use = DEFAULT_MAX_USE_LOWER; + } else + max_use = DEFAULT_MAX_USE_LOWER; + } else + max_use = PAGE_ALIGN(max_use); + + if (max_use > 0 && sum > max_use) + return true; + + if (keep_free == UINT64_MAX) { + + if (fs_size > 0) { + keep_free = PAGE_ALIGN((fs_size * 3) / 20); /* 15% */ + + if (keep_free > DEFAULT_KEEP_FREE_UPPER) + keep_free = DEFAULT_KEEP_FREE_UPPER; + } else + keep_free = DEFAULT_KEEP_FREE; + } else + keep_free = PAGE_ALIGN(keep_free); + + if (keep_free > 0 && fs_free < keep_free) + return true; + + return false; +} + +int coredump_vacuum(int exclude_fd, uint64_t keep_free, uint64_t max_use) { + _cleanup_closedir_ DIR *d = NULL; + struct stat exclude_st; + int r; + + if (keep_free == 0 && max_use == 0) + return 0; + + if (exclude_fd >= 0) { + if (fstat(exclude_fd, &exclude_st) < 0) + return log_error_errno(errno, "Failed to fstat(): %m"); + } + + /* This algorithm will keep deleting the oldest file of the + * user with the most coredumps until we are back in the size + * limits. Note that vacuuming for journal files is different, + * because we rely on rate-limiting of the messages there, + * to avoid being flooded. */ + + d = opendir("/var/lib/systemd/coredump"); + if (!d) { + if (errno == ENOENT) + return 0; + + return log_error_errno(errno, "Can't open coredump directory: %m"); + } + + for (;;) { + _cleanup_(vacuum_candidate_hashmap_freep) Hashmap *h = NULL; + VacuumCandidate *worst = NULL; + uint64_t sum = 0; + + rewinddir(d); + + FOREACH_DIRENT(de, d, goto fail) { + VacuumCandidate *c; + struct stat st; + uid_t uid; + usec_t t; + + r = uid_from_file_name(de->d_name, &uid); + if (r < 0) + continue; + + if (fstatat(dirfd(d), de->d_name, &st, AT_NO_AUTOMOUNT|AT_SYMLINK_NOFOLLOW) < 0) { + if (errno == ENOENT) + continue; + + log_warning_errno(errno, "Failed to stat /var/lib/systemd/coredump/%s: %m", de->d_name); + continue; + } + + if (!S_ISREG(st.st_mode)) + continue; + + if (exclude_fd >= 0 && stat_inode_same(&exclude_st, &st)) + continue; + + r = hashmap_ensure_allocated(&h, NULL); + if (r < 0) + return log_oom(); + + t = timespec_load(&st.st_mtim); + + c = hashmap_get(h, UID_TO_PTR(uid)); + if (c) { + + if (t < c->oldest_mtime) { + char *n; + + n = strdup(de->d_name); + if (!n) + return log_oom(); + + free(c->oldest_file); + c->oldest_file = n; + c->oldest_mtime = t; + } + + } else { + _cleanup_(vacuum_candidate_freep) VacuumCandidate *n = NULL; + + n = new0(VacuumCandidate, 1); + if (!n) + return log_oom(); + + n->oldest_file = strdup(de->d_name); + if (!n->oldest_file) + return log_oom(); + + n->oldest_mtime = t; + + r = hashmap_put(h, UID_TO_PTR(uid), n); + if (r < 0) + return log_oom(); + + c = TAKE_PTR(n); + } + + c->n_files++; + + if (!worst || + worst->n_files < c->n_files || + (worst->n_files == c->n_files && c->oldest_mtime < worst->oldest_mtime)) + worst = c; + + sum += st.st_blocks * 512; + } + + if (!worst) + break; + + r = vacuum_necessary(dirfd(d), sum, keep_free, max_use); + if (r <= 0) + return r; + + r = unlinkat_deallocate(dirfd(d), worst->oldest_file, 0); + if (r == -ENOENT) + continue; + if (r < 0) + return log_error_errno(r, "Failed to remove file %s: %m", worst->oldest_file); + + log_info("Removed old coredump %s.", worst->oldest_file); + } + + return 0; + +fail: + return log_error_errno(errno, "Failed to read directory: %m"); +} |