chiark / gitweb /
sd-journal: properly parse cursor strings
[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 #include "journal-def.h"
29 #include "journal-file.h"
30 #include "journal-vacuum.h"
31 #include "sd-id128.h"
32 #include "util.h"
33
34 struct vacuum_info {
35         off_t usage;
36         char *filename;
37
38         uint64_t realtime;
39         sd_id128_t seqnum_id;
40         uint64_t seqnum;
41
42         bool have_seqnum;
43 };
44
45 static int vacuum_compare(const void *_a, const void *_b) {
46         const struct vacuum_info *a, *b;
47
48         a = _a;
49         b = _b;
50
51         if (a->have_seqnum && b->have_seqnum &&
52             sd_id128_equal(a->seqnum_id, b->seqnum_id)) {
53                 if (a->seqnum < b->seqnum)
54                         return -1;
55                 else if (a->seqnum > b->seqnum)
56                         return 1;
57                 else
58                         return 0;
59         }
60
61         if (a->realtime < b->realtime)
62                 return -1;
63         else if (a->realtime > b->realtime)
64                 return 1;
65         else if (a->have_seqnum && b->have_seqnum)
66                 return memcmp(&a->seqnum_id, &b->seqnum_id, 16);
67         else
68                 return strcmp(a->filename, b->filename);
69 }
70
71 int journal_directory_vacuum(const char *directory, uint64_t max_use, uint64_t min_free) {
72         DIR *d;
73         int r = 0;
74         struct vacuum_info *list = NULL;
75         unsigned n_list = 0, n_allocated = 0, i;
76         uint64_t sum = 0;
77
78         assert(directory);
79
80         if (max_use <= 0)
81                 return 0;
82
83         d = opendir(directory);
84         if (!d)
85                 return -errno;
86
87         for (;;) {
88                 int k;
89                 struct dirent *de;
90                 union dirent_storage buf;
91                 size_t q;
92                 struct stat st;
93                 char *p;
94                 unsigned long long seqnum = 0, realtime;
95                 sd_id128_t seqnum_id;
96                 bool have_seqnum;
97
98                 k = readdir_r(d, &buf.de, &de);
99                 if (k != 0) {
100                         r = -k;
101                         goto finish;
102                 }
103
104                 if (!de)
105                         break;
106
107                 if (fstatat(dirfd(d), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0)
108                         continue;
109
110                 if (!S_ISREG(st.st_mode))
111                         continue;
112
113                 q = strlen(de->d_name);
114
115                 if (endswith(de->d_name, ".journal")) {
116
117                         /* Vacuum archived files */
118
119                         if (q < 1 + 32 + 1 + 16 + 1 + 16 + 8)
120                                 continue;
121
122                         if (de->d_name[q-8-16-1] != '-' ||
123                             de->d_name[q-8-16-1-16-1] != '-' ||
124                             de->d_name[q-8-16-1-16-1-32-1] != '@')
125                                 continue;
126
127                         p = strdup(de->d_name);
128                         if (!p) {
129                                 r = -ENOMEM;
130                                 goto finish;
131                         }
132
133                         de->d_name[q-8-16-1-16-1] = 0;
134                         if (sd_id128_from_string(de->d_name + q-8-16-1-16-1-32, &seqnum_id) < 0) {
135                                 free(p);
136                                 continue;
137                         }
138
139                         if (sscanf(de->d_name + q-8-16-1-16, "%16llx-%16llx.journal", &seqnum, &realtime) != 2) {
140                                 free(p);
141                                 continue;
142                         }
143
144                         have_seqnum = true;
145
146                 } else if (endswith(de->d_name, ".journal~")) {
147                         unsigned long long tmp;
148
149                         /* Vacuum corrupted files */
150
151                         if (q < 1 + 16 + 1 + 16 + 8 + 1)
152                                 continue;
153
154                         if (de->d_name[q-1-8-16-1] != '-' ||
155                             de->d_name[q-1-8-16-1-16-1] != '@')
156                                 continue;
157
158                         p = strdup(de->d_name);
159                         if (!p) {
160                                 r = -ENOMEM;
161                                 goto finish;
162                         }
163
164                         if (sscanf(de->d_name + q-1-8-16-1-16, "%16llx-%16llx.journal~", &realtime, &tmp) != 2) {
165                                 free(p);
166                                 continue;
167                         }
168
169                         have_seqnum = false;
170                 } else
171                         continue;
172
173                 if (n_list >= n_allocated) {
174                         struct vacuum_info *j;
175
176                         n_allocated = MAX(n_allocated * 2U, 8U);
177                         j = realloc(list, n_allocated * sizeof(struct vacuum_info));
178                         if (!j) {
179                                 free(p);
180                                 r = -ENOMEM;
181                                 goto finish;
182                         }
183
184                         list = j;
185                 }
186
187                 list[n_list].filename = p;
188                 list[n_list].usage = 512UL * (uint64_t) st.st_blocks;
189                 list[n_list].seqnum = seqnum;
190                 list[n_list].realtime = realtime;
191                 list[n_list].seqnum_id = seqnum_id;
192                 list[n_list].have_seqnum = have_seqnum;
193
194                 sum += list[n_list].usage;
195
196                 n_list ++;
197         }
198
199         if (n_list > 0)
200                 qsort(list, n_list, sizeof(struct vacuum_info), vacuum_compare);
201
202         for(i = 0; i < n_list; i++) {
203                 struct statvfs ss;
204
205                 if (fstatvfs(dirfd(d), &ss) < 0) {
206                         r = -errno;
207                         goto finish;
208                 }
209
210                 if (sum <= max_use &&
211                     (uint64_t) ss.f_bavail * (uint64_t) ss.f_bsize >= min_free)
212                         break;
213
214                 if (unlinkat(dirfd(d), list[i].filename, 0) >= 0) {
215                         log_debug("Deleted archived journal %s/%s.", directory, list[i].filename);
216                         sum -= list[i].usage;
217                 } else if (errno != ENOENT)
218                         log_warning("Failed to delete %s/%s: %m", directory, list[i].filename);
219         }
220
221 finish:
222         for (i = 0; i < n_list; i++)
223                 free(list[i].filename);
224
225         free(list);
226
227         if (d)
228                 closedir(d);
229
230         return r;
231 }