1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2 
3 #include <fcntl.h>
4 #include <sys/stat.h>
5 #include <unistd.h>
6 
7 #include "sd-id128.h"
8 
9 #include "alloc-util.h"
10 #include "dirent-util.h"
11 #include "fd-util.h"
12 #include "format-util.h"
13 #include "fs-util.h"
14 #include "journal-def.h"
15 #include "journal-file.h"
16 #include "journal-vacuum.h"
17 #include "sort-util.h"
18 #include "string-util.h"
19 #include "time-util.h"
20 #include "xattr-util.h"
21 
22 struct vacuum_info {
23         uint64_t usage;
24         char *filename;
25 
26         uint64_t realtime;
27 
28         sd_id128_t seqnum_id;
29         uint64_t seqnum;
30         bool have_seqnum;
31 };
32 
vacuum_compare(const struct vacuum_info * a,const struct vacuum_info * b)33 static int vacuum_compare(const struct vacuum_info *a, const struct vacuum_info *b) {
34         int r;
35 
36         if (a->have_seqnum && b->have_seqnum &&
37             sd_id128_equal(a->seqnum_id, b->seqnum_id))
38                 return CMP(a->seqnum, b->seqnum);
39 
40         r = CMP(a->realtime, b->realtime);
41         if (r != 0)
42                 return r;
43 
44         if (a->have_seqnum && b->have_seqnum)
45                 return memcmp(&a->seqnum_id, &b->seqnum_id, 16);
46 
47         return strcmp(a->filename, b->filename);
48 }
49 
patch_realtime(int fd,const char * fn,const struct stat * st,unsigned long long * realtime)50 static void patch_realtime(
51                 int fd,
52                 const char *fn,
53                 const struct stat *st,
54                 unsigned long long *realtime) {
55 
56         usec_t x;
57 
58         /* The timestamp was determined by the file name, but let's see if the file might actually be older
59          * than the file name suggested... */
60 
61         assert(fd >= 0);
62         assert(fn);
63         assert(st);
64         assert(realtime);
65 
66         x = timespec_load(&st->st_ctim);
67         if (timestamp_is_set(x) && x < *realtime)
68                 *realtime = x;
69 
70         x = timespec_load(&st->st_atim);
71         if (timestamp_is_set(x) && x < *realtime)
72                 *realtime = x;
73 
74         x = timespec_load(&st->st_mtim);
75         if (timestamp_is_set(x) && x < *realtime)
76                 *realtime = x;
77 
78         /* Let's read the original creation time, if possible. Ideally we'd just query the creation time the
79          * FS might provide, but unfortunately there's currently no sane API to query it. Hence let's
80          * implement this manually... */
81 
82         if (fd_getcrtime_at(fd, fn, AT_SYMLINK_FOLLOW, &x) >= 0 && x < *realtime)
83                 *realtime = x;
84 }
85 
journal_file_empty(int dir_fd,const char * name)86 static int journal_file_empty(int dir_fd, const char *name) {
87         _cleanup_close_ int fd = -1;
88         struct stat st;
89         le64_t n_entries;
90         ssize_t n;
91 
92         fd = openat(dir_fd, name, O_RDONLY|O_CLOEXEC|O_NOFOLLOW|O_NONBLOCK|O_NOATIME);
93         if (fd < 0) {
94                 /* Maybe failed due to O_NOATIME and lack of privileges? */
95                 fd = openat(dir_fd, name, O_RDONLY|O_CLOEXEC|O_NOFOLLOW|O_NONBLOCK);
96                 if (fd < 0)
97                         return -errno;
98         }
99 
100         if (fstat(fd, &st) < 0)
101                 return -errno;
102 
103         /* If an offline file doesn't even have a header we consider it empty */
104         if (st.st_size < (off_t) sizeof(Header))
105                 return 1;
106 
107         /* If the number of entries is empty, we consider it empty, too */
108         n = pread(fd, &n_entries, sizeof(n_entries), offsetof(Header, n_entries));
109         if (n < 0)
110                 return -errno;
111         if (n != sizeof(n_entries))
112                 return -EIO;
113 
114         return le64toh(n_entries) <= 0;
115 }
116 
journal_directory_vacuum(const char * directory,uint64_t max_use,uint64_t n_max_files,usec_t max_retention_usec,usec_t * oldest_usec,bool verbose)117 int journal_directory_vacuum(
118                 const char *directory,
119                 uint64_t max_use,
120                 uint64_t n_max_files,
121                 usec_t max_retention_usec,
122                 usec_t *oldest_usec,
123                 bool verbose) {
124 
125         uint64_t sum = 0, freed = 0, n_active_files = 0;
126         size_t n_list = 0, i;
127         _cleanup_closedir_ DIR *d = NULL;
128         struct vacuum_info *list = NULL;
129         usec_t retention_limit = 0;
130         int r;
131 
132         assert(directory);
133 
134         if (max_use <= 0 && max_retention_usec <= 0 && n_max_files <= 0)
135                 return 0;
136 
137         if (max_retention_usec > 0)
138                 retention_limit = usec_sub_unsigned(now(CLOCK_REALTIME), max_retention_usec);
139 
140         d = opendir(directory);
141         if (!d)
142                 return -errno;
143 
144         FOREACH_DIRENT_ALL(de, d, r = -errno; goto finish) {
145                 unsigned long long seqnum = 0, realtime;
146                 _cleanup_free_ char *p = NULL;
147                 sd_id128_t seqnum_id;
148                 bool have_seqnum;
149                 uint64_t size;
150                 struct stat st;
151                 size_t q;
152 
153                 if (fstatat(dirfd(d), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) {
154                         log_debug_errno(errno, "Failed to stat file %s while vacuuming, ignoring: %m", de->d_name);
155                         continue;
156                 }
157 
158                 if (!S_ISREG(st.st_mode))
159                         continue;
160 
161                 q = strlen(de->d_name);
162 
163                 if (endswith(de->d_name, ".journal")) {
164 
165                         /* Vacuum archived files. Active files are
166                          * left around */
167 
168                         if (q < 1 + 32 + 1 + 16 + 1 + 16 + 8) {
169                                 n_active_files++;
170                                 continue;
171                         }
172 
173                         if (de->d_name[q-8-16-1] != '-' ||
174                             de->d_name[q-8-16-1-16-1] != '-' ||
175                             de->d_name[q-8-16-1-16-1-32-1] != '@') {
176                                 n_active_files++;
177                                 continue;
178                         }
179 
180                         p = strdup(de->d_name);
181                         if (!p) {
182                                 r = -ENOMEM;
183                                 goto finish;
184                         }
185 
186                         de->d_name[q-8-16-1-16-1] = 0;
187                         if (sd_id128_from_string(de->d_name + q-8-16-1-16-1-32, &seqnum_id) < 0) {
188                                 n_active_files++;
189                                 continue;
190                         }
191 
192                         if (sscanf(de->d_name + q-8-16-1-16, "%16llx-%16llx.journal", &seqnum, &realtime) != 2) {
193                                 n_active_files++;
194                                 continue;
195                         }
196 
197                         have_seqnum = true;
198 
199                 } else if (endswith(de->d_name, ".journal~")) {
200                         unsigned long long tmp;
201 
202                         /* seqnum_id won't be initialised before use below, so set to 0 */
203                         seqnum_id = SD_ID128_NULL;
204 
205                         /* Vacuum corrupted files */
206 
207                         if (q < 1 + 16 + 1 + 16 + 8 + 1) {
208                                 n_active_files++;
209                                 continue;
210                         }
211 
212                         if (de->d_name[q-1-8-16-1] != '-' ||
213                             de->d_name[q-1-8-16-1-16-1] != '@') {
214                                 n_active_files++;
215                                 continue;
216                         }
217 
218                         p = strdup(de->d_name);
219                         if (!p) {
220                                 r = -ENOMEM;
221                                 goto finish;
222                         }
223 
224                         if (sscanf(de->d_name + q-1-8-16-1-16, "%16llx-%16llx.journal~", &realtime, &tmp) != 2) {
225                                 n_active_files++;
226                                 continue;
227                         }
228 
229                         have_seqnum = false;
230                 } else {
231                         /* We do not vacuum unknown files! */
232                         log_debug("Not vacuuming unknown file %s.", de->d_name);
233                         continue;
234                 }
235 
236                 size = 512UL * (uint64_t) st.st_blocks;
237 
238                 r = journal_file_empty(dirfd(d), p);
239                 if (r < 0) {
240                         log_debug_errno(r, "Failed check if %s is empty, ignoring: %m", p);
241                         continue;
242                 }
243                 if (r > 0) {
244                         /* Always vacuum empty non-online files. */
245 
246                         r = unlinkat_deallocate(dirfd(d), p, 0);
247                         if (r >= 0) {
248 
249                                 log_full(verbose ? LOG_INFO : LOG_DEBUG,
250                                          "Deleted empty archived journal %s/%s (%s).", directory, p, FORMAT_BYTES(size));
251 
252                                 freed += size;
253                         } else if (r != -ENOENT)
254                                 log_warning_errno(r, "Failed to delete empty archived journal %s/%s: %m", directory, p);
255 
256                         continue;
257                 }
258 
259                 patch_realtime(dirfd(d), p, &st, &realtime);
260 
261                 if (!GREEDY_REALLOC(list, n_list + 1)) {
262                         r = -ENOMEM;
263                         goto finish;
264                 }
265 
266                 list[n_list++] = (struct vacuum_info) {
267                         .filename = TAKE_PTR(p),
268                         .usage = size,
269                         .seqnum = seqnum,
270                         .realtime = realtime,
271                         .seqnum_id = seqnum_id,
272                         .have_seqnum = have_seqnum,
273                 };
274 
275                 sum += size;
276         }
277 
278         typesafe_qsort(list, n_list, vacuum_compare);
279 
280         for (i = 0; i < n_list; i++) {
281                 uint64_t left;
282 
283                 left = n_active_files + n_list - i;
284 
285                 if ((max_retention_usec <= 0 || list[i].realtime >= retention_limit) &&
286                     (max_use <= 0 || sum <= max_use) &&
287                     (n_max_files <= 0 || left <= n_max_files))
288                         break;
289 
290                 r = unlinkat_deallocate(dirfd(d), list[i].filename, 0);
291                 if (r >= 0) {
292                         log_full(verbose ? LOG_INFO : LOG_DEBUG, "Deleted archived journal %s/%s (%s).",
293                                  directory, list[i].filename, FORMAT_BYTES(list[i].usage));
294                         freed += list[i].usage;
295 
296                         if (list[i].usage < sum)
297                                 sum -= list[i].usage;
298                         else
299                                 sum = 0;
300 
301                 } else if (r != -ENOENT)
302                         log_warning_errno(r, "Failed to delete archived journal %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         r = 0;
309 
310 finish:
311         for (i = 0; i < n_list; i++)
312                 free(list[i].filename);
313         free(list);
314 
315         log_full(verbose ? LOG_INFO : LOG_DEBUG, "Vacuuming done, freed %s of archived journals from %s.",
316                  FORMAT_BYTES(freed), directory);
317 
318         return r;
319 }
320