chiark / gitweb /
1ddb043e2cd13ccc0bbf4a70984455752382d1fe
[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 int journal_directory_vacuum(
132                 const char *directory,
133                 uint64_t max_use,
134                 uint64_t min_free,
135                 usec_t max_retention_usec,
136                 usec_t *oldest_usec) {
137
138         _cleanup_closedir_ DIR *d = NULL;
139         int r = 0;
140         struct vacuum_info *list = NULL;
141         unsigned n_list = 0, i;
142         size_t n_allocated = 0;
143         uint64_t sum = 0;
144         usec_t retention_limit = 0;
145
146         assert(directory);
147
148         if (max_use <= 0 && min_free <= 0 && max_retention_usec <= 0)
149                 return 0;
150
151         if (max_retention_usec > 0) {
152                 retention_limit = now(CLOCK_REALTIME);
153                 if (retention_limit > max_retention_usec)
154                         retention_limit -= max_retention_usec;
155                 else
156                         max_retention_usec = retention_limit = 0;
157         }
158
159         d = opendir(directory);
160         if (!d)
161                 return -errno;
162
163         for (;;) {
164                 int k;
165                 struct dirent *de;
166                 union dirent_storage buf;
167                 size_t q;
168                 struct stat st;
169                 char *p;
170                 unsigned long long seqnum = 0, realtime;
171                 sd_id128_t seqnum_id;
172                 bool have_seqnum;
173
174                 k = readdir_r(d, &buf.de, &de);
175                 if (k != 0) {
176                         r = -k;
177                         goto finish;
178                 }
179
180                 if (!de)
181                         break;
182
183                 if (fstatat(dirfd(d), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0)
184                         continue;
185
186                 if (!S_ISREG(st.st_mode))
187                         continue;
188
189                 q = strlen(de->d_name);
190
191                 if (endswith(de->d_name, ".journal")) {
192
193                         /* Vacuum archived files */
194
195                         if (q < 1 + 32 + 1 + 16 + 1 + 16 + 8)
196                                 continue;
197
198                         if (de->d_name[q-8-16-1] != '-' ||
199                             de->d_name[q-8-16-1-16-1] != '-' ||
200                             de->d_name[q-8-16-1-16-1-32-1] != '@')
201                                 continue;
202
203                         p = strdup(de->d_name);
204                         if (!p) {
205                                 r = -ENOMEM;
206                                 goto finish;
207                         }
208
209                         de->d_name[q-8-16-1-16-1] = 0;
210                         if (sd_id128_from_string(de->d_name + q-8-16-1-16-1-32, &seqnum_id) < 0) {
211                                 free(p);
212                                 continue;
213                         }
214
215                         if (sscanf(de->d_name + q-8-16-1-16, "%16llx-%16llx.journal", &seqnum, &realtime) != 2) {
216                                 free(p);
217                                 continue;
218                         }
219
220                         have_seqnum = true;
221
222                 } else if (endswith(de->d_name, ".journal~")) {
223                         unsigned long long tmp;
224
225                         /* Vacuum corrupted files */
226
227                         if (q < 1 + 16 + 1 + 16 + 8 + 1)
228                                 continue;
229
230                         if (de->d_name[q-1-8-16-1] != '-' ||
231                             de->d_name[q-1-8-16-1-16-1] != '@')
232                                 continue;
233
234                         p = strdup(de->d_name);
235                         if (!p) {
236                                 r = -ENOMEM;
237                                 goto finish;
238                         }
239
240                         if (sscanf(de->d_name + q-1-8-16-1-16, "%16llx-%16llx.journal~", &realtime, &tmp) != 2) {
241                                 free(p);
242                                 continue;
243                         }
244
245                         have_seqnum = false;
246                 } else
247                         /* We do not vacuum active files or unknown files! */
248                         continue;
249
250                 patch_realtime(directory, de->d_name, &st, &realtime);
251
252                 GREEDY_REALLOC(list, n_allocated, n_list + 1);
253
254                 list[n_list].filename = p;
255                 list[n_list].usage = 512UL * (uint64_t) st.st_blocks;
256                 list[n_list].seqnum = seqnum;
257                 list[n_list].realtime = realtime;
258                 list[n_list].seqnum_id = seqnum_id;
259                 list[n_list].have_seqnum = have_seqnum;
260
261                 sum += list[n_list].usage;
262
263                 n_list ++;
264         }
265
266         if (n_list > 0)
267                 qsort(list, n_list, sizeof(struct vacuum_info), vacuum_compare);
268
269         for (i = 0; i < n_list; i++) {
270                 struct statvfs ss;
271
272                 if (fstatvfs(dirfd(d), &ss) < 0) {
273                         r = -errno;
274                         goto finish;
275                 }
276
277                 if ((max_retention_usec <= 0 || list[i].realtime >= retention_limit) &&
278                     (max_use <= 0 || sum <= max_use) &&
279                     (min_free <= 0 || (uint64_t) ss.f_bavail * (uint64_t) ss.f_bsize >= min_free))
280                         break;
281
282                 if (unlinkat(dirfd(d), list[i].filename, 0) >= 0) {
283                         log_debug("Deleted archived journal %s/%s.", directory, list[i].filename);
284
285                         if (list[i].usage < sum)
286                                 sum -= list[i].usage;
287                         else
288                                 sum = 0;
289
290                 } else if (errno != ENOENT)
291                         log_warning("Failed to delete %s/%s: %m", directory, list[i].filename);
292         }
293
294         if (oldest_usec && i < n_list && (*oldest_usec == 0 || list[i].realtime < *oldest_usec))
295                 *oldest_usec = list[i].realtime;
296
297 finish:
298         for (i = 0; i < n_list; i++)
299                 free(list[i].filename);
300         free(list);
301
302         return r;
303 }