chiark / gitweb /
journal: downgrade vaccuum message to debug level
[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
28 #ifdef HAVE_XATTR
29 #include <attr/xattr.h>
30 #endif
31
32 #include "journal-def.h"
33 #include "journal-file.h"
34 #include "journal-vacuum.h"
35 #include "sd-id128.h"
36 #include "util.h"
37
38 struct vacuum_info {
39         uint64_t usage;
40         char *filename;
41
42         uint64_t realtime;
43         sd_id128_t seqnum_id;
44         uint64_t seqnum;
45
46         bool have_seqnum;
47 };
48
49 static int vacuum_compare(const void *_a, const void *_b) {
50         const struct vacuum_info *a, *b;
51
52         a = _a;
53         b = _b;
54
55         if (a->have_seqnum && b->have_seqnum &&
56             sd_id128_equal(a->seqnum_id, b->seqnum_id)) {
57                 if (a->seqnum < b->seqnum)
58                         return -1;
59                 else if (a->seqnum > b->seqnum)
60                         return 1;
61                 else
62                         return 0;
63         }
64
65         if (a->realtime < b->realtime)
66                 return -1;
67         else if (a->realtime > b->realtime)
68                 return 1;
69         else if (a->have_seqnum && b->have_seqnum)
70                 return memcmp(&a->seqnum_id, &b->seqnum_id, 16);
71         else
72                 return strcmp(a->filename, b->filename);
73 }
74
75 static void patch_realtime(
76                 const char *dir,
77                 const char *fn,
78                 const struct stat *st,
79                 unsigned long long *realtime) {
80
81         usec_t x;
82
83 #ifdef HAVE_XATTR
84         uint64_t crtime;
85         _cleanup_free_ const char *path = NULL;
86 #endif
87
88         /* The timestamp was determined by the file name, but let's
89          * see if the file might actually be older than the file name
90          * suggested... */
91
92         assert(dir);
93         assert(fn);
94         assert(st);
95         assert(realtime);
96
97         x = timespec_load(&st->st_ctim);
98         if (x > 0 && x != (usec_t) -1 && x < *realtime)
99                 *realtime = x;
100
101         x = timespec_load(&st->st_atim);
102         if (x > 0 && x != (usec_t) -1 && x < *realtime)
103                 *realtime = x;
104
105         x = timespec_load(&st->st_mtim);
106         if (x > 0 && x != (usec_t) -1 && x < *realtime)
107                 *realtime = x;
108
109 #ifdef HAVE_XATTR
110         /* Let's read the original creation time, if possible. Ideally
111          * we'd just query the creation time the FS might provide, but
112          * unfortunately there's currently no sane API to query
113          * it. Hence let's implement this manually... */
114
115         /* Unfortunately there is is not fgetxattrat(), so we need to
116          * go via path here. :-( */
117
118         path = strjoin(dir, "/", fn, NULL);
119         if (!path)
120                 return;
121
122         if (getxattr(path, "user.crtime_usec", &crtime, sizeof(crtime)) == sizeof(crtime)) {
123                 crtime = le64toh(crtime);
124
125                 if (crtime > 0 && crtime != (uint64_t) -1 && crtime < *realtime)
126                         *realtime = crtime;
127         }
128 #endif
129 }
130
131 static int journal_file_empty(int dir_fd, const char *name) {
132         int r;
133         le64_t n_entries;
134         _cleanup_close_ int fd;
135
136         fd = openat(dir_fd, name, O_RDONLY|O_CLOEXEC|O_NOFOLLOW|O_NONBLOCK);
137         if (fd < 0)
138                 return -errno;
139
140         if (lseek(fd, offsetof(Header, n_entries), SEEK_SET) < 0)
141                 return -errno;
142
143         r = read(fd, &n_entries, sizeof(n_entries));
144         if (r != sizeof(n_entries))
145                 return r == 0 ? -EINVAL : -errno;
146
147         return le64toh(n_entries) == 0;
148 }
149
150 int journal_directory_vacuum(
151                 const char *directory,
152                 uint64_t max_use,
153                 usec_t max_retention_usec,
154                 usec_t *oldest_usec) {
155
156         _cleanup_closedir_ DIR *d = NULL;
157         int r = 0;
158         struct vacuum_info *list = NULL;
159         unsigned n_list = 0, i;
160         size_t n_allocated = 0;
161         uint64_t sum = 0, freed = 0;
162         usec_t retention_limit = 0;
163
164         assert(directory);
165
166         if (max_use <= 0 && max_retention_usec <= 0)
167                 return 0;
168
169         if (max_retention_usec > 0) {
170                 retention_limit = now(CLOCK_REALTIME);
171                 if (retention_limit > max_retention_usec)
172                         retention_limit -= max_retention_usec;
173                 else
174                         max_retention_usec = retention_limit = 0;
175         }
176
177         d = opendir(directory);
178         if (!d)
179                 return -errno;
180
181         for (;;) {
182                 struct dirent *de;
183                 size_t q;
184                 struct stat st;
185                 char *p;
186                 unsigned long long seqnum = 0, realtime;
187                 sd_id128_t seqnum_id;
188                 bool have_seqnum;
189
190                 errno = 0;
191                 de = readdir(d);
192                 if (!de && errno != 0) {
193                         r = -errno;
194                         goto finish;
195                 }
196
197                 if (!de)
198                         break;
199
200                 if (fstatat(dirfd(d), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0)
201                         continue;
202
203                 if (!S_ISREG(st.st_mode))
204                         continue;
205
206                 q = strlen(de->d_name);
207
208                 if (endswith(de->d_name, ".journal")) {
209
210                         /* Vacuum archived files */
211
212                         if (q < 1 + 32 + 1 + 16 + 1 + 16 + 8)
213                                 continue;
214
215                         if (de->d_name[q-8-16-1] != '-' ||
216                             de->d_name[q-8-16-1-16-1] != '-' ||
217                             de->d_name[q-8-16-1-16-1-32-1] != '@')
218                                 continue;
219
220                         p = strdup(de->d_name);
221                         if (!p) {
222                                 r = -ENOMEM;
223                                 goto finish;
224                         }
225
226                         de->d_name[q-8-16-1-16-1] = 0;
227                         if (sd_id128_from_string(de->d_name + q-8-16-1-16-1-32, &seqnum_id) < 0) {
228                                 free(p);
229                                 continue;
230                         }
231
232                         if (sscanf(de->d_name + q-8-16-1-16, "%16llx-%16llx.journal", &seqnum, &realtime) != 2) {
233                                 free(p);
234                                 continue;
235                         }
236
237                         have_seqnum = true;
238
239                 } else if (endswith(de->d_name, ".journal~")) {
240                         unsigned long long tmp;
241
242                         /* Vacuum corrupted files */
243
244                         if (q < 1 + 16 + 1 + 16 + 8 + 1)
245                                 continue;
246
247                         if (de->d_name[q-1-8-16-1] != '-' ||
248                             de->d_name[q-1-8-16-1-16-1] != '@')
249                                 continue;
250
251                         p = strdup(de->d_name);
252                         if (!p) {
253                                 r = -ENOMEM;
254                                 goto finish;
255                         }
256
257                         if (sscanf(de->d_name + q-1-8-16-1-16, "%16llx-%16llx.journal~", &realtime, &tmp) != 2) {
258                                 free(p);
259                                 continue;
260                         }
261
262                         have_seqnum = false;
263                 } else
264                         /* We do not vacuum active files or unknown files! */
265                         continue;
266
267                 if (journal_file_empty(dirfd(d), p)) {
268                         /* Always vacuum empty non-online files. */
269
270                         uint64_t size = 512UL * (uint64_t) st.st_blocks;
271
272                         if (unlinkat(dirfd(d), p, 0) >= 0) {
273                                 log_info("Deleted empty journal %s/%s (%"PRIu64" bytes).",
274                                          directory, p, size);
275                                 freed += size;
276                         } else if (errno != ENOENT)
277                                 log_warning("Failed to delete %s/%s: %m", directory, p);
278
279                         free(p);
280
281                         continue;
282                 }
283
284                 patch_realtime(directory, p, &st, &realtime);
285
286                 GREEDY_REALLOC(list, n_allocated, n_list + 1);
287
288                 list[n_list].filename = p;
289                 list[n_list].usage = 512UL * (uint64_t) st.st_blocks;
290                 list[n_list].seqnum = seqnum;
291                 list[n_list].realtime = realtime;
292                 list[n_list].seqnum_id = seqnum_id;
293                 list[n_list].have_seqnum = have_seqnum;
294
295                 sum += list[n_list].usage;
296
297                 n_list ++;
298         }
299
300         qsort_safe(list, n_list, sizeof(struct vacuum_info), vacuum_compare);
301
302         for (i = 0; i < n_list; i++) {
303                 struct statvfs ss;
304
305                 if (fstatvfs(dirfd(d), &ss) < 0) {
306                         r = -errno;
307                         goto finish;
308                 }
309
310                 if ((max_retention_usec <= 0 || list[i].realtime >= retention_limit) &&
311                     (max_use <= 0 || sum <= max_use))
312                         break;
313
314                 if (unlinkat(dirfd(d), list[i].filename, 0) >= 0) {
315                         log_debug("Deleted archived journal %s/%s (%"PRIu64" bytes).",
316                                   directory, list[i].filename, list[i].usage);
317                         freed += list[i].usage;
318
319                         if (list[i].usage < sum)
320                                 sum -= list[i].usage;
321                         else
322                                 sum = 0;
323
324                 } else if (errno != ENOENT)
325                         log_warning("Failed to delete %s/%s: %m", directory, list[i].filename);
326         }
327
328         if (oldest_usec && i < n_list && (*oldest_usec == 0 || list[i].realtime < *oldest_usec))
329                 *oldest_usec = list[i].realtime;
330
331 finish:
332         for (i = 0; i < n_list; i++)
333                 free(list[i].filename);
334         free(list);
335
336         log_debug("Vacuuming done, freed %"PRIu64" bytes", freed);
337
338         return r;
339 }