chiark / gitweb /
man: netdev - add some minimal explanation to the different netdev kinds and modes
[elogind.git] / src / journal / journal-vacuum.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2011 Lennart Poettering
7
8   systemd is free software; you can redistribute it and/or modify it
9   under the terms of the GNU Lesser General Public License as published by
10   the Free Software Foundation; either version 2.1 of the License, or
11   (at your option) any later version.
12
13   systemd is distributed in the hope that it will be useful, but
14   WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16   Lesser General Public License for more details.
17
18   You should have received a copy of the GNU Lesser General Public License
19   along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <sys/types.h>
23 #include <fcntl.h>
24 #include <sys/stat.h>
25 #include <sys/statvfs.h>
26 #include <unistd.h>
27 #include <sys/xattr.h>
28
29 #include "journal-def.h"
30 #include "journal-file.h"
31 #include "journal-vacuum.h"
32 #include "sd-id128.h"
33 #include "util.h"
34
35 struct vacuum_info {
36         uint64_t usage;
37         char *filename;
38
39         uint64_t realtime;
40         sd_id128_t seqnum_id;
41         uint64_t seqnum;
42
43         bool have_seqnum;
44 };
45
46 static int vacuum_compare(const void *_a, const void *_b) {
47         const struct vacuum_info *a, *b;
48
49         a = _a;
50         b = _b;
51
52         if (a->have_seqnum && b->have_seqnum &&
53             sd_id128_equal(a->seqnum_id, b->seqnum_id)) {
54                 if (a->seqnum < b->seqnum)
55                         return -1;
56                 else if (a->seqnum > b->seqnum)
57                         return 1;
58                 else
59                         return 0;
60         }
61
62         if (a->realtime < b->realtime)
63                 return -1;
64         else if (a->realtime > b->realtime)
65                 return 1;
66         else if (a->have_seqnum && b->have_seqnum)
67                 return memcmp(&a->seqnum_id, &b->seqnum_id, 16);
68         else
69                 return strcmp(a->filename, b->filename);
70 }
71
72 static void patch_realtime(
73                 const char *dir,
74                 const char *fn,
75                 const struct stat *st,
76                 unsigned long long *realtime) {
77
78         _cleanup_free_ const char *path = NULL;
79         usec_t x, crtime;
80
81         /* The timestamp was determined by the file name, but let's
82          * see if the file might actually be older than the file name
83          * suggested... */
84
85         assert(dir);
86         assert(fn);
87         assert(st);
88         assert(realtime);
89
90         x = timespec_load(&st->st_ctim);
91         if (x > 0 && x != USEC_INFINITY && x < *realtime)
92                 *realtime = x;
93
94         x = timespec_load(&st->st_atim);
95         if (x > 0 && x != USEC_INFINITY && x < *realtime)
96                 *realtime = x;
97
98         x = timespec_load(&st->st_mtim);
99         if (x > 0 && x != USEC_INFINITY && x < *realtime)
100                 *realtime = x;
101
102         /* Let's read the original creation time, if possible. Ideally
103          * we'd just query the creation time the FS might provide, but
104          * unfortunately there's currently no sane API to query
105          * it. Hence let's implement this manually... */
106
107         /* Unfortunately there is is not fgetxattrat(), so we need to
108          * go via path here. :-( */
109
110         path = strjoin(dir, "/", fn, NULL);
111         if (!path)
112                 return;
113
114         if (path_getcrtime(path, &crtime) >= 0) {
115                 if (crtime < *realtime)
116                         *realtime = crtime;
117         }
118 }
119
120 static int journal_file_empty(int dir_fd, const char *name) {
121         _cleanup_close_ int fd;
122         struct stat st;
123         le64_t n_entries;
124         ssize_t n;
125
126         fd = openat(dir_fd, name, O_RDONLY|O_CLOEXEC|O_NOFOLLOW|O_NONBLOCK);
127         if (fd < 0)
128                 return -errno;
129
130         if (fstat(fd, &st) < 0)
131                 return -errno;
132
133         /* If an offline file doesn't even have a header we consider it empty */
134         if (st.st_size < (off_t) sizeof(Header))
135                 return 1;
136
137         /* If the number of entries is empty, we consider it empty, too */
138         n = pread(fd, &n_entries, sizeof(n_entries), offsetof(Header, n_entries));
139         if (n < 0)
140                 return -errno;
141         if (n != sizeof(n_entries))
142                 return -EIO;
143
144         return le64toh(n_entries) <= 0;
145 }
146
147 int journal_directory_vacuum(
148                 const char *directory,
149                 uint64_t max_use,
150                 usec_t max_retention_usec,
151                 usec_t *oldest_usec,
152                 bool verbose) {
153
154         _cleanup_closedir_ DIR *d = NULL;
155         int r = 0;
156         struct vacuum_info *list = NULL;
157         unsigned n_list = 0, i;
158         size_t n_allocated = 0;
159         uint64_t sum = 0, freed = 0;
160         usec_t retention_limit = 0;
161         char sbytes[FORMAT_BYTES_MAX];
162
163         assert(directory);
164
165         if (max_use <= 0 && max_retention_usec <= 0)
166                 return 0;
167
168         if (max_retention_usec > 0) {
169                 retention_limit = now(CLOCK_REALTIME);
170                 if (retention_limit > max_retention_usec)
171                         retention_limit -= max_retention_usec;
172                 else
173                         max_retention_usec = retention_limit = 0;
174         }
175
176         d = opendir(directory);
177         if (!d)
178                 return -errno;
179
180         for (;;) {
181                 struct dirent *de;
182                 size_t q;
183                 struct stat st;
184                 char *p;
185                 unsigned long long seqnum = 0, realtime;
186                 sd_id128_t seqnum_id;
187                 bool have_seqnum;
188
189                 errno = 0;
190                 de = readdir(d);
191                 if (!de && errno != 0) {
192                         r = -errno;
193                         goto finish;
194                 }
195
196                 if (!de)
197                         break;
198
199                 if (fstatat(dirfd(d), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0)
200                         continue;
201
202                 if (!S_ISREG(st.st_mode))
203                         continue;
204
205                 q = strlen(de->d_name);
206
207                 if (endswith(de->d_name, ".journal")) {
208
209                         /* Vacuum archived files */
210
211                         if (q < 1 + 32 + 1 + 16 + 1 + 16 + 8)
212                                 continue;
213
214                         if (de->d_name[q-8-16-1] != '-' ||
215                             de->d_name[q-8-16-1-16-1] != '-' ||
216                             de->d_name[q-8-16-1-16-1-32-1] != '@')
217                                 continue;
218
219                         p = strdup(de->d_name);
220                         if (!p) {
221                                 r = -ENOMEM;
222                                 goto finish;
223                         }
224
225                         de->d_name[q-8-16-1-16-1] = 0;
226                         if (sd_id128_from_string(de->d_name + q-8-16-1-16-1-32, &seqnum_id) < 0) {
227                                 free(p);
228                                 continue;
229                         }
230
231                         if (sscanf(de->d_name + q-8-16-1-16, "%16llx-%16llx.journal", &seqnum, &realtime) != 2) {
232                                 free(p);
233                                 continue;
234                         }
235
236                         have_seqnum = true;
237
238                 } else if (endswith(de->d_name, ".journal~")) {
239                         unsigned long long tmp;
240
241                         /* Vacuum corrupted files */
242
243                         if (q < 1 + 16 + 1 + 16 + 8 + 1)
244                                 continue;
245
246                         if (de->d_name[q-1-8-16-1] != '-' ||
247                             de->d_name[q-1-8-16-1-16-1] != '@')
248                                 continue;
249
250                         p = strdup(de->d_name);
251                         if (!p) {
252                                 r = -ENOMEM;
253                                 goto finish;
254                         }
255
256                         if (sscanf(de->d_name + q-1-8-16-1-16, "%16llx-%16llx.journal~", &realtime, &tmp) != 2) {
257                                 free(p);
258                                 continue;
259                         }
260
261                         have_seqnum = false;
262                 } else
263                         /* We do not vacuum active files or unknown files! */
264                         continue;
265
266                 if (journal_file_empty(dirfd(d), p)) {
267                         /* Always vacuum empty non-online files. */
268
269                         uint64_t size = 512UL * (uint64_t) st.st_blocks;
270
271                         if (unlinkat(dirfd(d), p, 0) >= 0) {
272                                 log_full(verbose ? LOG_INFO : LOG_DEBUG, "Deleted empty archived journal %s/%s (%s).", directory, p, format_bytes(sbytes, sizeof(sbytes), size));
273                                 freed += size;
274                         } else if (errno != ENOENT)
275                                 log_warning_errno(errno, "Failed to delete empty archived journal %s/%s: %m", directory, p);
276
277                         free(p);
278                         continue;
279                 }
280
281                 patch_realtime(directory, p, &st, &realtime);
282
283                 if (!GREEDY_REALLOC(list, n_allocated, n_list + 1)) {
284                         free(p);
285                         r = -ENOMEM;
286                         goto finish;
287                 }
288
289                 list[n_list].filename = p;
290                 list[n_list].usage = 512UL * (uint64_t) st.st_blocks;
291                 list[n_list].seqnum = seqnum;
292                 list[n_list].realtime = realtime;
293                 list[n_list].seqnum_id = seqnum_id;
294                 list[n_list].have_seqnum = have_seqnum;
295
296                 sum += list[n_list].usage;
297
298                 n_list ++;
299         }
300
301         qsort_safe(list, n_list, sizeof(struct vacuum_info), vacuum_compare);
302
303         for (i = 0; i < n_list; i++) {
304                 if ((max_retention_usec <= 0 || list[i].realtime >= retention_limit) &&
305                     (max_use <= 0 || sum <= max_use))
306                         break;
307
308                 if (unlinkat(dirfd(d), list[i].filename, 0) >= 0) {
309                         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));
310                         freed += list[i].usage;
311
312                         if (list[i].usage < sum)
313                                 sum -= list[i].usage;
314                         else
315                                 sum = 0;
316
317                 } else if (errno != ENOENT)
318                         log_warning_errno(errno, "Failed to delete archived journal %s/%s: %m", directory, list[i].filename);
319         }
320
321         if (oldest_usec && i < n_list && (*oldest_usec == 0 || list[i].realtime < *oldest_usec))
322                 *oldest_usec = list[i].realtime;
323
324 finish:
325         for (i = 0; i < n_list; i++)
326                 free(list[i].filename);
327         free(list);
328
329         log_full(verbose ? LOG_INFO : LOG_DEBUG, "Vacuuming done, freed %s of archived journals on disk.", format_bytes(sbytes, sizeof(sbytes), freed));
330
331         return r;
332 }