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