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