LCOV - code coverage report
Current view: top level - journal - journal-vacuum.c (source / functions) Hit Total Coverage
Test: main_coverage.info Lines: 91 152 59.9 %
Date: 2019-08-22 15:41:25 Functions: 3 4 75.0 %

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

Generated by: LCOV version 1.14