Systemd/src/journal/journal-vacuum.c
Zbigniew Jędrzejewski-Szmek 11a1589223 tree-wide: drop license boilerplate
Files which are installed as-is (any .service and other unit files, .conf
files, .policy files, etc), are left as is. My assumption is that SPDX
identifiers are not yet that well known, so it's better to retain the
extended header to avoid any doubt.

I also kept any copyright lines. We can probably remove them, but it'd nice to
obtain explicit acks from all involved authors before doing that.
2018-04-06 18:58:55 +02:00

340 lines
11 KiB
C

/* SPDX-License-Identifier: LGPL-2.1+ */
/***
This file is part of systemd.
Copyright 2011 Lennart Poettering
***/
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
#include "sd-id128.h"
#include "alloc-util.h"
#include "dirent-util.h"
#include "fd-util.h"
#include "fs-util.h"
#include "journal-def.h"
#include "journal-file.h"
#include "journal-vacuum.h"
#include "parse-util.h"
#include "string-util.h"
#include "util.h"
#include "xattr-util.h"
struct vacuum_info {
uint64_t usage;
char *filename;
uint64_t realtime;
sd_id128_t seqnum_id;
uint64_t seqnum;
bool have_seqnum;
};
static int vacuum_compare(const void *_a, const void *_b) {
const struct vacuum_info *a, *b;
a = _a;
b = _b;
if (a->have_seqnum && b->have_seqnum &&
sd_id128_equal(a->seqnum_id, b->seqnum_id)) {
if (a->seqnum < b->seqnum)
return -1;
else if (a->seqnum > b->seqnum)
return 1;
else
return 0;
}
if (a->realtime < b->realtime)
return -1;
else if (a->realtime > b->realtime)
return 1;
else if (a->have_seqnum && b->have_seqnum)
return memcmp(&a->seqnum_id, &b->seqnum_id, 16);
else
return strcmp(a->filename, b->filename);
}
static void patch_realtime(
int fd,
const char *fn,
const struct stat *st,
unsigned long long *realtime) {
usec_t x, crtime = 0;
/* The timestamp was determined by the file name, but let's
* see if the file might actually be older than the file name
* suggested... */
assert(fd >= 0);
assert(fn);
assert(st);
assert(realtime);
x = timespec_load(&st->st_ctim);
if (x > 0 && x != USEC_INFINITY && x < *realtime)
*realtime = x;
x = timespec_load(&st->st_atim);
if (x > 0 && x != USEC_INFINITY && x < *realtime)
*realtime = x;
x = timespec_load(&st->st_mtim);
if (x > 0 && x != USEC_INFINITY && x < *realtime)
*realtime = x;
/* Let's read the original creation time, if possible. Ideally
* we'd just query the creation time the FS might provide, but
* unfortunately there's currently no sane API to query
* it. Hence let's implement this manually... */
if (fd_getcrtime_at(fd, fn, &crtime, 0) >= 0) {
if (crtime < *realtime)
*realtime = crtime;
}
}
static int journal_file_empty(int dir_fd, const char *name) {
_cleanup_close_ int fd;
struct stat st;
le64_t n_entries;
ssize_t n;
fd = openat(dir_fd, name, O_RDONLY|O_CLOEXEC|O_NOFOLLOW|O_NONBLOCK|O_NOATIME);
if (fd < 0) {
/* Maybe failed due to O_NOATIME and lack of privileges? */
fd = openat(dir_fd, name, O_RDONLY|O_CLOEXEC|O_NOFOLLOW|O_NONBLOCK);
if (fd < 0)
return -errno;
}
if (fstat(fd, &st) < 0)
return -errno;
/* If an offline file doesn't even have a header we consider it empty */
if (st.st_size < (off_t) sizeof(Header))
return 1;
/* If the number of entries is empty, we consider it empty, too */
n = pread(fd, &n_entries, sizeof(n_entries), offsetof(Header, n_entries));
if (n < 0)
return -errno;
if (n != sizeof(n_entries))
return -EIO;
return le64toh(n_entries) <= 0;
}
int journal_directory_vacuum(
const char *directory,
uint64_t max_use,
uint64_t n_max_files,
usec_t max_retention_usec,
usec_t *oldest_usec,
bool verbose) {
_cleanup_closedir_ DIR *d = NULL;
struct vacuum_info *list = NULL;
unsigned n_list = 0, i, n_active_files = 0;
size_t n_allocated = 0;
uint64_t sum = 0, freed = 0;
usec_t retention_limit = 0;
char sbytes[FORMAT_BYTES_MAX];
struct dirent *de;
int r;
assert(directory);
if (max_use <= 0 && max_retention_usec <= 0 && n_max_files <= 0)
return 0;
if (max_retention_usec > 0) {
retention_limit = now(CLOCK_REALTIME);
if (retention_limit > max_retention_usec)
retention_limit -= max_retention_usec;
else
max_retention_usec = retention_limit = 0;
}
d = opendir(directory);
if (!d)
return -errno;
FOREACH_DIRENT_ALL(de, d, r = -errno; goto finish) {
unsigned long long seqnum = 0, realtime;
_cleanup_free_ char *p = NULL;
sd_id128_t seqnum_id;
bool have_seqnum;
uint64_t size;
struct stat st;
size_t q;
if (fstatat(dirfd(d), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) {
log_debug_errno(errno, "Failed to stat file %s while vacuuming, ignoring: %m", de->d_name);
continue;
}
if (!S_ISREG(st.st_mode))
continue;
q = strlen(de->d_name);
if (endswith(de->d_name, ".journal")) {
/* Vacuum archived files. Active files are
* left around */
if (q < 1 + 32 + 1 + 16 + 1 + 16 + 8) {
n_active_files++;
continue;
}
if (de->d_name[q-8-16-1] != '-' ||
de->d_name[q-8-16-1-16-1] != '-' ||
de->d_name[q-8-16-1-16-1-32-1] != '@') {
n_active_files++;
continue;
}
p = strdup(de->d_name);
if (!p) {
r = -ENOMEM;
goto finish;
}
de->d_name[q-8-16-1-16-1] = 0;
if (sd_id128_from_string(de->d_name + q-8-16-1-16-1-32, &seqnum_id) < 0) {
n_active_files++;
continue;
}
if (sscanf(de->d_name + q-8-16-1-16, "%16llx-%16llx.journal", &seqnum, &realtime) != 2) {
n_active_files++;
continue;
}
have_seqnum = true;
} else if (endswith(de->d_name, ".journal~")) {
unsigned long long tmp;
/* Vacuum corrupted files */
if (q < 1 + 16 + 1 + 16 + 8 + 1) {
n_active_files++;
continue;
}
if (de->d_name[q-1-8-16-1] != '-' ||
de->d_name[q-1-8-16-1-16-1] != '@') {
n_active_files++;
continue;
}
p = strdup(de->d_name);
if (!p) {
r = -ENOMEM;
goto finish;
}
if (sscanf(de->d_name + q-1-8-16-1-16, "%16llx-%16llx.journal~", &realtime, &tmp) != 2) {
n_active_files++;
continue;
}
have_seqnum = false;
} else {
/* We do not vacuum unknown files! */
log_debug("Not vacuuming unknown file %s.", de->d_name);
continue;
}
size = 512UL * (uint64_t) st.st_blocks;
r = journal_file_empty(dirfd(d), p);
if (r < 0) {
log_debug_errno(r, "Failed check if %s is empty, ignoring: %m", p);
continue;
}
if (r > 0) {
/* Always vacuum empty non-online files. */
r = unlinkat_deallocate(dirfd(d), p, 0);
if (r >= 0) {
log_full(verbose ? LOG_INFO : LOG_DEBUG,
"Deleted empty archived journal %s/%s (%s).", directory, p, format_bytes(sbytes, sizeof(sbytes), size));
freed += size;
} else if (r != -ENOENT)
log_warning_errno(r, "Failed to delete empty archived journal %s/%s: %m", directory, p);
continue;
}
patch_realtime(dirfd(d), p, &st, &realtime);
if (!GREEDY_REALLOC(list, n_allocated, n_list + 1)) {
r = -ENOMEM;
goto finish;
}
list[n_list].filename = TAKE_PTR(p);
list[n_list].usage = size;
list[n_list].seqnum = seqnum;
list[n_list].realtime = realtime;
list[n_list].seqnum_id = seqnum_id;
list[n_list].have_seqnum = have_seqnum;
n_list++;
sum += size;
}
qsort_safe(list, n_list, sizeof(struct vacuum_info), vacuum_compare);
for (i = 0; i < n_list; i++) {
unsigned left;
left = n_active_files + n_list - i;
if ((max_retention_usec <= 0 || list[i].realtime >= retention_limit) &&
(max_use <= 0 || sum <= max_use) &&
(n_max_files <= 0 || left <= n_max_files))
break;
r = unlinkat_deallocate(dirfd(d), list[i].filename, 0);
if (r >= 0) {
log_full(verbose ? LOG_INFO : LOG_DEBUG, "Deleted archived journal %s/%s (%s).", directory, list[i].filename, format_bytes(sbytes, sizeof(sbytes), list[i].usage));
freed += list[i].usage;
if (list[i].usage < sum)
sum -= list[i].usage;
else
sum = 0;
} else if (r != -ENOENT)
log_warning_errno(r, "Failed to delete archived journal %s/%s: %m", directory, list[i].filename);
}
if (oldest_usec && i < n_list && (*oldest_usec == 0 || list[i].realtime < *oldest_usec))
*oldest_usec = list[i].realtime;
r = 0;
finish:
for (i = 0; i < n_list; i++)
free(list[i].filename);
free(list);
log_full(verbose ? LOG_INFO : LOG_DEBUG, "Vacuuming done, freed %s of archived journals from %s.", format_bytes(sbytes, sizeof(sbytes), freed), directory);
return r;
}