chiark / gitweb /
journald: fix memory leak on error path
[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         usec_t x;
79         uint64_t crtime;
80         _cleanup_free_ const char *path = NULL;
81
82         /* The timestamp was determined by the file name, but let's
83          * see if the file might actually be older than the file name
84          * suggested... */
85
86         assert(dir);
87         assert(fn);
88         assert(st);
89         assert(realtime);
90
91         x = timespec_load(&st->st_ctim);
92         if (x > 0 && x != USEC_INFINITY && x < *realtime)
93                 *realtime = x;
94
95         x = timespec_load(&st->st_atim);
96         if (x > 0 && x != USEC_INFINITY && x < *realtime)
97                 *realtime = x;
98
99         x = timespec_load(&st->st_mtim);
100         if (x > 0 && x != USEC_INFINITY && x < *realtime)
101                 *realtime = x;
102
103         /* Let's read the original creation time, if possible. Ideally
104          * we'd just query the creation time the FS might provide, but
105          * unfortunately there's currently no sane API to query
106          * it. Hence let's implement this manually... */
107
108         /* Unfortunately there is is not fgetxattrat(), so we need to
109          * go via path here. :-( */
110
111         path = strjoin(dir, "/", fn, NULL);
112         if (!path)
113                 return;
114
115         if (getxattr(path, "user.crtime_usec", &crtime, sizeof(crtime)) == sizeof(crtime)) {
116                 crtime = le64toh(crtime);
117
118                 if (crtime > 0 && crtime != (uint64_t) -1 && crtime < *realtime)
119                         *realtime = crtime;
120         }
121 }
122
123 static int journal_file_empty(int dir_fd, const char *name) {
124         _cleanup_close_ int fd;
125         struct stat st;
126         le64_t n_entries;
127         ssize_t n;
128
129         fd = openat(dir_fd, name, O_RDONLY|O_CLOEXEC|O_NOFOLLOW|O_NONBLOCK);
130         if (fd < 0)
131                 return -errno;
132
133         if (fstat(fd, &st) < 0)
134                 return -errno;
135
136         /* If an offline file doesn't even have a header we consider it empty */
137         if (st.st_size < (off_t) sizeof(Header))
138                 return 1;
139
140         /* If the number of entries is empty, we consider it empty, too */
141         n = pread(fd, &n_entries, sizeof(n_entries), offsetof(Header, n_entries));
142         if (n < 0)
143                 return -errno;
144         if (n != sizeof(n_entries))
145                 return -EIO;
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                 bool verbose) {
156
157         _cleanup_closedir_ DIR *d = NULL;
158         int r = 0;
159         struct vacuum_info *list = NULL;
160         unsigned n_list = 0, i;
161         size_t n_allocated = 0;
162         uint64_t sum = 0, freed = 0;
163         usec_t retention_limit = 0;
164         char sbytes[FORMAT_BYTES_MAX];
165
166         assert(directory);
167
168         if (max_use <= 0 && max_retention_usec <= 0)
169                 return 0;
170
171         if (max_retention_usec > 0) {
172                 retention_limit = now(CLOCK_REALTIME);
173                 if (retention_limit > max_retention_usec)
174                         retention_limit -= max_retention_usec;
175                 else
176                         max_retention_usec = retention_limit = 0;
177         }
178
179         d = opendir(directory);
180         if (!d)
181                 return -errno;
182
183         for (;;) {
184                 struct dirent *de;
185                 size_t q;
186                 struct stat st;
187                 char *p;
188                 unsigned long long seqnum = 0, realtime;
189                 sd_id128_t seqnum_id;
190                 bool have_seqnum;
191
192                 errno = 0;
193                 de = readdir(d);
194                 if (!de && errno != 0) {
195                         r = -errno;
196                         goto finish;
197                 }
198
199                 if (!de)
200                         break;
201
202                 if (fstatat(dirfd(d), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0)
203                         continue;
204
205                 if (!S_ISREG(st.st_mode))
206                         continue;
207
208                 q = strlen(de->d_name);
209
210                 if (endswith(de->d_name, ".journal")) {
211
212                         /* Vacuum archived files */
213
214                         if (q < 1 + 32 + 1 + 16 + 1 + 16 + 8)
215                                 continue;
216
217                         if (de->d_name[q-8-16-1] != '-' ||
218                             de->d_name[q-8-16-1-16-1] != '-' ||
219                             de->d_name[q-8-16-1-16-1-32-1] != '@')
220                                 continue;
221
222                         p = strdup(de->d_name);
223                         if (!p) {
224                                 r = -ENOMEM;
225                                 goto finish;
226                         }
227
228                         de->d_name[q-8-16-1-16-1] = 0;
229                         if (sd_id128_from_string(de->d_name + q-8-16-1-16-1-32, &seqnum_id) < 0) {
230                                 free(p);
231                                 continue;
232                         }
233
234                         if (sscanf(de->d_name + q-8-16-1-16, "%16llx-%16llx.journal", &seqnum, &realtime) != 2) {
235                                 free(p);
236                                 continue;
237                         }
238
239                         have_seqnum = true;
240
241                 } else if (endswith(de->d_name, ".journal~")) {
242                         unsigned long long tmp;
243
244                         /* Vacuum corrupted files */
245
246                         if (q < 1 + 16 + 1 + 16 + 8 + 1)
247                                 continue;
248
249                         if (de->d_name[q-1-8-16-1] != '-' ||
250                             de->d_name[q-1-8-16-1-16-1] != '@')
251                                 continue;
252
253                         p = strdup(de->d_name);
254                         if (!p) {
255                                 r = -ENOMEM;
256                                 goto finish;
257                         }
258
259                         if (sscanf(de->d_name + q-1-8-16-1-16, "%16llx-%16llx.journal~", &realtime, &tmp) != 2) {
260                                 free(p);
261                                 continue;
262                         }
263
264                         have_seqnum = false;
265                 } else
266                         /* We do not vacuum active files or unknown files! */
267                         continue;
268
269                 if (journal_file_empty(dirfd(d), p)) {
270                         /* Always vacuum empty non-online files. */
271
272                         uint64_t size = 512UL * (uint64_t) st.st_blocks;
273
274                         if (unlinkat(dirfd(d), p, 0) >= 0) {
275                                 log_full(verbose ? LOG_INFO : LOG_DEBUG, "Deleted empty archived journal %s/%s (%s).", directory, p, format_bytes(sbytes, sizeof(sbytes), size));
276                                 freed += size;
277                         } else if (errno != ENOENT)
278                                 log_warning("Failed to delete empty archived journal %s/%s: %m", directory, p);
279
280                         free(p);
281                         continue;
282                 }
283
284                 patch_realtime(directory, p, &st, &realtime);
285
286                 if (!GREEDY_REALLOC(list, n_allocated, n_list + 1)) {
287                         free(p);
288                         r = -ENOMEM;
289                         goto finish;
290                 }
291
292                 list[n_list].filename = p;
293                 list[n_list].usage = 512UL * (uint64_t) st.st_blocks;
294                 list[n_list].seqnum = seqnum;
295                 list[n_list].realtime = realtime;
296                 list[n_list].seqnum_id = seqnum_id;
297                 list[n_list].have_seqnum = have_seqnum;
298
299                 sum += list[n_list].usage;
300
301                 n_list ++;
302         }
303
304         qsort_safe(list, n_list, sizeof(struct vacuum_info), vacuum_compare);
305
306         for (i = 0; i < n_list; i++) {
307                 if ((max_retention_usec <= 0 || list[i].realtime >= retention_limit) &&
308                     (max_use <= 0 || sum <= max_use))
309                         break;
310
311                 if (unlinkat(dirfd(d), list[i].filename, 0) >= 0) {
312                         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));
313                         freed += list[i].usage;
314
315                         if (list[i].usage < sum)
316                                 sum -= list[i].usage;
317                         else
318                                 sum = 0;
319
320                 } else if (errno != ENOENT)
321                         log_warning("Failed to delete archived journal %s/%s: %m", directory, list[i].filename);
322         }
323
324         if (oldest_usec && i < n_list && (*oldest_usec == 0 || list[i].realtime < *oldest_usec))
325                 *oldest_usec = list[i].realtime;
326
327 finish:
328         for (i = 0; i < n_list; i++)
329                 free(list[i].filename);
330         free(list);
331
332         log_full(verbose ? LOG_INFO : LOG_DEBUG, "Vacuuming done, freed %s of archived journals on disk.", format_bytes(sbytes, sizeof(sbytes), freed));
333
334         return r;
335 }