2017-11-18 17:09:20 +01:00
|
|
|
/* SPDX-License-Identifier: LGPL-2.1+ */
|
2012-08-16 01:51:54 +02:00
|
|
|
|
|
|
|
#include <fcntl.h>
|
|
|
|
#include <sys/stat.h>
|
|
|
|
#include <unistd.h>
|
2012-10-16 22:58:07 +02:00
|
|
|
|
2015-10-24 22:58:24 +02:00
|
|
|
#include "sd-id128.h"
|
|
|
|
|
2015-10-27 03:01:06 +01:00
|
|
|
#include "alloc-util.h"
|
2015-10-26 20:07:55 +01:00
|
|
|
#include "dirent-util.h"
|
2015-10-25 13:14:12 +01:00
|
|
|
#include "fd-util.h"
|
2018-02-09 09:53:52 +01:00
|
|
|
#include "fs-util.h"
|
2012-08-16 01:51:54 +02:00
|
|
|
#include "journal-def.h"
|
|
|
|
#include "journal-file.h"
|
2015-10-25 13:14:12 +01:00
|
|
|
#include "journal-vacuum.h"
|
2015-10-26 16:18:16 +01:00
|
|
|
#include "parse-util.h"
|
2015-10-24 22:58:24 +02:00
|
|
|
#include "string-util.h"
|
2018-10-25 12:30:36 +02:00
|
|
|
#include "time-util.h"
|
2012-08-16 01:51:54 +02:00
|
|
|
#include "util.h"
|
2015-10-26 20:26:23 +01:00
|
|
|
#include "xattr-util.h"
|
2012-08-16 01:51:54 +02:00
|
|
|
|
|
|
|
struct vacuum_info {
|
2013-03-20 21:32:05 +01:00
|
|
|
uint64_t usage;
|
2012-08-16 01:51:54 +02:00
|
|
|
char *filename;
|
|
|
|
|
|
|
|
uint64_t realtime;
|
2015-10-02 22:31:32 +02:00
|
|
|
|
2012-08-16 01:51:54 +02:00
|
|
|
sd_id128_t seqnum_id;
|
|
|
|
uint64_t seqnum;
|
|
|
|
bool have_seqnum;
|
|
|
|
};
|
|
|
|
|
2018-09-18 01:39:24 +02:00
|
|
|
static int vacuum_compare(const struct vacuum_info *a, const struct vacuum_info *b) {
|
|
|
|
int r;
|
2012-08-16 01:51:54 +02:00
|
|
|
|
|
|
|
if (a->have_seqnum && b->have_seqnum &&
|
2018-09-18 01:39:24 +02:00
|
|
|
sd_id128_equal(a->seqnum_id, b->seqnum_id))
|
|
|
|
return CMP(a->seqnum, b->seqnum);
|
2012-08-16 01:51:54 +02:00
|
|
|
|
2018-09-18 01:39:24 +02:00
|
|
|
r = CMP(a->realtime, b->realtime);
|
|
|
|
if (r != 0)
|
|
|
|
return r;
|
|
|
|
|
|
|
|
if (a->have_seqnum && b->have_seqnum)
|
2012-08-16 01:51:54 +02:00
|
|
|
return memcmp(&a->seqnum_id, &b->seqnum_id, 16);
|
2018-09-18 01:39:24 +02:00
|
|
|
|
|
|
|
return strcmp(a->filename, b->filename);
|
2012-08-16 01:51:54 +02:00
|
|
|
}
|
|
|
|
|
2012-10-16 22:58:07 +02:00
|
|
|
static void patch_realtime(
|
2015-10-02 22:31:32 +02:00
|
|
|
int fd,
|
2012-10-16 22:58:07 +02:00
|
|
|
const char *fn,
|
|
|
|
const struct stat *st,
|
|
|
|
unsigned long long *realtime) {
|
|
|
|
|
2015-03-27 12:02:49 +01:00
|
|
|
usec_t x, crtime = 0;
|
2012-10-16 22:58:07 +02:00
|
|
|
|
|
|
|
/* The timestamp was determined by the file name, but let's
|
|
|
|
* see if the file might actually be older than the file name
|
|
|
|
* suggested... */
|
|
|
|
|
2015-10-02 22:31:32 +02:00
|
|
|
assert(fd >= 0);
|
2012-10-16 22:58:07 +02:00
|
|
|
assert(fn);
|
|
|
|
assert(st);
|
|
|
|
assert(realtime);
|
|
|
|
|
|
|
|
x = timespec_load(&st->st_ctim);
|
2014-07-29 12:23:31 +02:00
|
|
|
if (x > 0 && x != USEC_INFINITY && x < *realtime)
|
2012-10-16 22:58:07 +02:00
|
|
|
*realtime = x;
|
|
|
|
|
|
|
|
x = timespec_load(&st->st_atim);
|
2014-07-29 12:23:31 +02:00
|
|
|
if (x > 0 && x != USEC_INFINITY && x < *realtime)
|
2012-10-16 22:58:07 +02:00
|
|
|
*realtime = x;
|
|
|
|
|
|
|
|
x = timespec_load(&st->st_mtim);
|
2014-07-29 12:23:31 +02:00
|
|
|
if (x > 0 && x != USEC_INFINITY && x < *realtime)
|
2012-10-16 22:58:07 +02:00
|
|
|
*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... */
|
|
|
|
|
2015-10-02 22:31:32 +02:00
|
|
|
if (fd_getcrtime_at(fd, fn, &crtime, 0) >= 0) {
|
2014-12-24 16:39:55 +01:00
|
|
|
if (crtime < *realtime)
|
2012-10-16 22:58:07 +02:00
|
|
|
*realtime = crtime;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-06-25 03:00:28 +02:00
|
|
|
static int journal_file_empty(int dir_fd, const char *name) {
|
2013-09-04 14:12:44 +02:00
|
|
|
_cleanup_close_ int fd;
|
2014-11-03 23:10:21 +01:00
|
|
|
struct stat st;
|
|
|
|
le64_t n_entries;
|
|
|
|
ssize_t n;
|
2013-06-25 03:00:28 +02:00
|
|
|
|
2015-10-02 22:39:24 +02:00
|
|
|
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;
|
|
|
|
}
|
2013-06-25 03:00:28 +02:00
|
|
|
|
2014-11-03 23:10:21 +01:00
|
|
|
if (fstat(fd, &st) < 0)
|
2013-06-25 03:00:28 +02:00
|
|
|
return -errno;
|
|
|
|
|
2014-11-03 23:10:21 +01:00
|
|
|
/* 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;
|
2013-06-25 03:00:28 +02:00
|
|
|
|
2014-11-03 23:10:21 +01:00
|
|
|
return le64toh(n_entries) <= 0;
|
2013-06-25 03:00:28 +02:00
|
|
|
}
|
|
|
|
|
2012-10-16 22:58:07 +02:00
|
|
|
int journal_directory_vacuum(
|
|
|
|
const char *directory,
|
|
|
|
uint64_t max_use,
|
2015-10-02 23:21:59 +02:00
|
|
|
uint64_t n_max_files,
|
2012-10-16 22:58:07 +02:00
|
|
|
usec_t max_retention_usec,
|
2014-11-03 23:08:33 +01:00
|
|
|
usec_t *oldest_usec,
|
|
|
|
bool verbose) {
|
2012-10-16 22:58:07 +02:00
|
|
|
|
2018-10-25 17:40:41 +02:00
|
|
|
uint64_t sum = 0, freed = 0, n_active_files = 0;
|
|
|
|
size_t n_list = 0, n_allocated = 0, i;
|
2013-06-24 13:59:41 +02:00
|
|
|
_cleanup_closedir_ DIR *d = NULL;
|
2012-08-16 01:51:54 +02:00
|
|
|
struct vacuum_info *list = NULL;
|
2012-10-16 22:58:07 +02:00
|
|
|
usec_t retention_limit = 0;
|
2014-11-03 23:08:33 +01:00
|
|
|
char sbytes[FORMAT_BYTES_MAX];
|
2015-10-02 23:21:59 +02:00
|
|
|
struct dirent *de;
|
|
|
|
int r;
|
2012-08-16 01:51:54 +02:00
|
|
|
|
|
|
|
assert(directory);
|
|
|
|
|
2015-10-02 23:21:59 +02:00
|
|
|
if (max_use <= 0 && max_retention_usec <= 0 && n_max_files <= 0)
|
2012-08-16 01:51:54 +02:00
|
|
|
return 0;
|
|
|
|
|
2018-10-25 12:30:36 +02:00
|
|
|
if (max_retention_usec > 0)
|
|
|
|
retention_limit = usec_sub_unsigned(now(CLOCK_REALTIME), max_retention_usec);
|
2012-10-16 22:58:07 +02:00
|
|
|
|
2012-08-16 01:51:54 +02:00
|
|
|
d = opendir(directory);
|
|
|
|
if (!d)
|
|
|
|
return -errno;
|
|
|
|
|
2015-10-02 23:21:59 +02:00
|
|
|
FOREACH_DIRENT_ALL(de, d, r = -errno; goto finish) {
|
|
|
|
|
2012-08-16 01:51:54 +02:00
|
|
|
unsigned long long seqnum = 0, realtime;
|
2015-10-02 23:21:59 +02:00
|
|
|
_cleanup_free_ char *p = NULL;
|
2012-08-16 01:51:54 +02:00
|
|
|
sd_id128_t seqnum_id;
|
|
|
|
bool have_seqnum;
|
2015-10-02 23:21:59 +02:00
|
|
|
uint64_t size;
|
|
|
|
struct stat st;
|
|
|
|
size_t q;
|
2012-08-16 01:51:54 +02:00
|
|
|
|
2015-10-02 23:21:59 +02:00
|
|
|
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);
|
2012-08-16 01:51:54 +02:00
|
|
|
continue;
|
2015-10-02 23:21:59 +02:00
|
|
|
}
|
2012-08-16 01:51:54 +02:00
|
|
|
|
|
|
|
if (!S_ISREG(st.st_mode))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
q = strlen(de->d_name);
|
|
|
|
|
|
|
|
if (endswith(de->d_name, ".journal")) {
|
|
|
|
|
2015-10-02 23:21:59 +02:00
|
|
|
/* Vacuum archived files. Active files are
|
|
|
|
* left around */
|
2012-08-16 01:51:54 +02:00
|
|
|
|
2015-10-02 23:21:59 +02:00
|
|
|
if (q < 1 + 32 + 1 + 16 + 1 + 16 + 8) {
|
|
|
|
n_active_files++;
|
2012-08-16 01:51:54 +02:00
|
|
|
continue;
|
2015-10-02 23:21:59 +02:00
|
|
|
}
|
2012-08-16 01:51:54 +02:00
|
|
|
|
|
|
|
if (de->d_name[q-8-16-1] != '-' ||
|
|
|
|
de->d_name[q-8-16-1-16-1] != '-' ||
|
2015-10-02 23:21:59 +02:00
|
|
|
de->d_name[q-8-16-1-16-1-32-1] != '@') {
|
|
|
|
n_active_files++;
|
2012-08-16 01:51:54 +02:00
|
|
|
continue;
|
2015-10-02 23:21:59 +02:00
|
|
|
}
|
2012-08-16 01:51:54 +02:00
|
|
|
|
|
|
|
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) {
|
2015-10-02 23:21:59 +02:00
|
|
|
n_active_files++;
|
2012-08-16 01:51:54 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (sscanf(de->d_name + q-8-16-1-16, "%16llx-%16llx.journal", &seqnum, &realtime) != 2) {
|
2015-10-02 23:21:59 +02:00
|
|
|
n_active_files++;
|
2012-08-16 01:51:54 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
have_seqnum = true;
|
|
|
|
|
|
|
|
} else if (endswith(de->d_name, ".journal~")) {
|
|
|
|
unsigned long long tmp;
|
|
|
|
|
|
|
|
/* Vacuum corrupted files */
|
|
|
|
|
2015-10-02 23:21:59 +02:00
|
|
|
if (q < 1 + 16 + 1 + 16 + 8 + 1) {
|
2016-02-23 05:32:04 +01:00
|
|
|
n_active_files++;
|
2012-08-16 01:51:54 +02:00
|
|
|
continue;
|
2015-10-02 23:21:59 +02:00
|
|
|
}
|
2012-08-16 01:51:54 +02:00
|
|
|
|
|
|
|
if (de->d_name[q-1-8-16-1] != '-' ||
|
2015-10-02 23:21:59 +02:00
|
|
|
de->d_name[q-1-8-16-1-16-1] != '@') {
|
2016-02-23 05:32:04 +01:00
|
|
|
n_active_files++;
|
2012-08-16 01:51:54 +02:00
|
|
|
continue;
|
2015-10-02 23:21:59 +02:00
|
|
|
}
|
2012-08-16 01:51:54 +02:00
|
|
|
|
|
|
|
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) {
|
2016-02-23 05:32:04 +01:00
|
|
|
n_active_files++;
|
2012-08-16 01:51:54 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
have_seqnum = false;
|
2015-10-02 23:21:59 +02:00
|
|
|
} else {
|
|
|
|
/* We do not vacuum unknown files! */
|
|
|
|
log_debug("Not vacuuming unknown file %s.", de->d_name);
|
2012-08-16 01:51:54 +02:00
|
|
|
continue;
|
2015-10-02 23:21:59 +02:00
|
|
|
}
|
2012-08-16 01:51:54 +02:00
|
|
|
|
2015-10-02 23:21:59 +02:00
|
|
|
size = 512UL * (uint64_t) st.st_blocks;
|
2013-06-25 03:00:28 +02:00
|
|
|
|
2015-10-02 23:21:59 +02:00
|
|
|
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. */
|
2013-09-10 14:20:24 +02:00
|
|
|
|
2018-02-09 09:53:52 +01:00
|
|
|
r = unlinkat_deallocate(dirfd(d), p, 0);
|
|
|
|
if (r >= 0) {
|
2015-10-02 23:21:59 +02:00
|
|
|
|
|
|
|
log_full(verbose ? LOG_INFO : LOG_DEBUG,
|
|
|
|
"Deleted empty archived journal %s/%s (%s).", directory, p, format_bytes(sbytes, sizeof(sbytes), size));
|
|
|
|
|
2013-09-10 14:20:24 +02:00
|
|
|
freed += size;
|
2018-02-09 09:53:52 +01:00
|
|
|
} else if (r != -ENOENT)
|
|
|
|
log_warning_errno(r, "Failed to delete empty archived journal %s/%s: %m", directory, p);
|
2013-09-10 14:20:24 +02:00
|
|
|
|
2013-06-25 03:00:28 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2015-10-02 23:21:59 +02:00
|
|
|
patch_realtime(dirfd(d), p, &st, &realtime);
|
2012-10-16 22:58:07 +02:00
|
|
|
|
2014-11-03 23:10:34 +01:00
|
|
|
if (!GREEDY_REALLOC(list, n_allocated, n_list + 1)) {
|
|
|
|
r = -ENOMEM;
|
|
|
|
goto finish;
|
|
|
|
}
|
2012-08-16 01:51:54 +02:00
|
|
|
|
2018-10-25 12:30:48 +02:00
|
|
|
list[n_list++] = (struct vacuum_info) {
|
|
|
|
.filename = TAKE_PTR(p),
|
|
|
|
.usage = size,
|
|
|
|
.seqnum = seqnum,
|
|
|
|
.realtime = realtime,
|
|
|
|
.seqnum_id = seqnum_id,
|
|
|
|
.have_seqnum = have_seqnum,
|
|
|
|
};
|
2015-10-02 23:21:59 +02:00
|
|
|
|
|
|
|
sum += size;
|
2012-08-16 01:51:54 +02:00
|
|
|
}
|
|
|
|
|
2018-09-18 01:39:24 +02:00
|
|
|
typesafe_qsort(list, n_list, vacuum_compare);
|
2012-08-16 01:51:54 +02:00
|
|
|
|
2012-10-16 22:58:07 +02:00
|
|
|
for (i = 0; i < n_list; i++) {
|
2018-10-25 17:40:41 +02:00
|
|
|
uint64_t left;
|
2015-10-02 23:21:59 +02:00
|
|
|
|
|
|
|
left = n_active_files + n_list - i;
|
|
|
|
|
2012-10-16 22:58:07 +02:00
|
|
|
if ((max_retention_usec <= 0 || list[i].realtime >= retention_limit) &&
|
2015-10-02 23:21:59 +02:00
|
|
|
(max_use <= 0 || sum <= max_use) &&
|
|
|
|
(n_max_files <= 0 || left <= n_max_files))
|
2012-08-16 01:51:54 +02:00
|
|
|
break;
|
|
|
|
|
2018-02-09 09:53:52 +01:00
|
|
|
r = unlinkat_deallocate(dirfd(d), list[i].filename, 0);
|
|
|
|
if (r >= 0) {
|
2014-11-03 23:08:33 +01:00
|
|
|
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));
|
2013-09-10 14:20:24 +02:00
|
|
|
freed += list[i].usage;
|
2012-10-17 21:23:52 +02:00
|
|
|
|
2013-03-20 21:32:05 +01:00
|
|
|
if (list[i].usage < sum)
|
2012-10-17 21:23:52 +02:00
|
|
|
sum -= list[i].usage;
|
|
|
|
else
|
|
|
|
sum = 0;
|
|
|
|
|
2018-02-09 09:53:52 +01:00
|
|
|
} else if (r != -ENOENT)
|
|
|
|
log_warning_errno(r, "Failed to delete archived journal %s/%s: %m", directory, list[i].filename);
|
2012-08-16 01:51:54 +02:00
|
|
|
}
|
|
|
|
|
2012-10-16 22:58:07 +02:00
|
|
|
if (oldest_usec && i < n_list && (*oldest_usec == 0 || list[i].realtime < *oldest_usec))
|
|
|
|
*oldest_usec = list[i].realtime;
|
|
|
|
|
2015-10-02 23:21:59 +02:00
|
|
|
r = 0;
|
|
|
|
|
2012-08-16 01:51:54 +02:00
|
|
|
finish:
|
|
|
|
for (i = 0; i < n_list; i++)
|
|
|
|
free(list[i].filename);
|
|
|
|
free(list);
|
|
|
|
|
2016-10-12 20:23:43 +02:00
|
|
|
log_full(verbose ? LOG_INFO : LOG_DEBUG, "Vacuuming done, freed %s of archived journals from %s.", format_bytes(sbytes, sizeof(sbytes), freed), directory);
|
2013-09-10 14:20:24 +02:00
|
|
|
|
2012-08-16 01:51:54 +02:00
|
|
|
return r;
|
|
|
|
}
|