| /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ |
| |
| /*** |
| This file is part of systemd. |
| |
| Copyright 2014 Lennart Poettering |
| |
| systemd is free software; you can redistribute it and/or modify it |
| under the terms of the GNU Lesser General Public License as published by |
| the Free Software Foundation; either version 2.1 of the License, or |
| (at your option) any later version. |
| |
| systemd is distributed in the hope that it will be useful, but |
| WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| Lesser General Public License for more details. |
| |
| You should have received a copy of the GNU Lesser General Public License |
| along with systemd; If not, see <http://www.gnu.org/licenses/>. |
| ***/ |
| |
| #include <sys/statvfs.h> |
| |
| #include "util.h" |
| #include "time-util.h" |
| #include "hashmap.h" |
| #include "macro.h" |
| |
| #include "coredump-vacuum.h" |
| |
| #define DEFAULT_MAX_USE_LOWER (off_t) (1ULL*1024ULL*1024ULL) /* 1 MiB */ |
| #define DEFAULT_MAX_USE_UPPER (off_t) (4ULL*1024ULL*1024ULL*1024ULL) /* 4 GiB */ |
| #define DEFAULT_KEEP_FREE_UPPER (off_t) (4ULL*1024ULL*1024ULL*1024ULL) /* 4 GiB */ |
| #define DEFAULT_KEEP_FREE (off_t) (1024ULL*1024ULL) /* 1 MB */ |
| |
| struct vacuum_candidate { |
| unsigned n_files; |
| char *oldest_file; |
| usec_t oldest_mtime; |
| }; |
| |
| static void vacuum_candidate_free(struct vacuum_candidate *c) { |
| if (!c) |
| return; |
| |
| free(c->oldest_file); |
| free(c); |
| } |
| |
| DEFINE_TRIVIAL_CLEANUP_FUNC(struct vacuum_candidate*, vacuum_candidate_free); |
| |
| static void vacuum_candidate_hasmap_free(Hashmap *h) { |
| struct vacuum_candidate *c; |
| |
| while ((c = hashmap_steal_first(h))) |
| vacuum_candidate_free(c); |
| |
| hashmap_free(h); |
| } |
| |
| DEFINE_TRIVIAL_CLEANUP_FUNC(Hashmap*, vacuum_candidate_hasmap_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(p, e-p); |
| return parse_uid(u, uid); |
| } |
| |
| static bool vacuum_necessary(int fd, off_t sum, off_t keep_free, off_t max_use) { |
| off_t fs_size = 0, fs_free = (off_t) -1; |
| 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 == (off_t) -1) { |
| |
| 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 == (off_t) -1) { |
| |
| 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, off_t keep_free, off_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) { |
| log_error("Failed to fstat(): %m"); |
| return -errno; |
| } |
| } |
| |
| /* 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; |
| |
| log_error("Can't open coredump directory: %m"); |
| return -errno; |
| } |
| |
| for (;;) { |
| _cleanup_(vacuum_candidate_hasmap_freep) Hashmap *h = NULL; |
| struct vacuum_candidate *worst = NULL; |
| struct dirent *de; |
| off_t sum = 0; |
| |
| rewinddir(d); |
| |
| FOREACH_DIRENT(de, d, goto fail) { |
| struct vacuum_candidate *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("Failed to stat /var/lib/systemd/coredump/%s", de->d_name); |
| continue; |
| } |
| |
| if (!S_ISREG(st.st_mode)) |
| continue; |
| |
| if (exclude_fd >= 0 && |
| exclude_st.st_dev == st.st_dev && |
| exclude_st.st_ino == st.st_ino) |
| continue; |
| |
| r = hashmap_ensure_allocated(&h, NULL); |
| if (r < 0) |
| return log_oom(); |
| |
| t = timespec_load(&st.st_mtim); |
| |
| c = hashmap_get(h, UINT32_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) struct vacuum_candidate *n = NULL; |
| |
| n = new0(struct vacuum_candidate, 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, UINT32_TO_PTR(uid), n); |
| if (r < 0) |
| return log_oom(); |
| |
| c = n; |
| n = NULL; |
| } |
| |
| 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; |
| |
| if (unlinkat(dirfd(d), worst->oldest_file, 0) < 0) { |
| |
| if (errno == ENOENT) |
| continue; |
| |
| log_error("Failed to remove file %s: %m", worst->oldest_file); |
| return -errno; |
| } else |
| log_info("Removed old coredump %s.", worst->oldest_file); |
| } |
| |
| return 0; |
| |
| fail: |
| log_error("Failed to read directory: %m"); |
| return -errno; |
| } |