LCOV - code coverage report
Current view: top level - journal - journal-vacuum.c (source / functions) Hit Total Coverage
Test: systemd_full.info Lines: 91 152 59.9 %
Date: 2019-08-23 13:36:53 Functions: 3 4 75.0 %
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 62 171 36.3 %

           Branch data     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                 :          4 : static void patch_realtime(
      51                 :            :                 int fd,
      52                 :            :                 const char *fn,
      53                 :            :                 const struct stat *st,
      54                 :            :                 unsigned long long *realtime) {
      55                 :            : 
      56                 :          4 :         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         [ -  + ]:          4 :         assert(fd >= 0);
      63         [ -  + ]:          4 :         assert(fn);
      64         [ -  + ]:          4 :         assert(st);
      65         [ -  + ]:          4 :         assert(realtime);
      66                 :            : 
      67                 :          4 :         x = timespec_load(&st->st_ctim);
      68   [ +  -  +  -  :          4 :         if (x > 0 && x != USEC_INFINITY && x < *realtime)
                   -  + ]
      69                 :          0 :                 *realtime = x;
      70                 :            : 
      71                 :          4 :         x = timespec_load(&st->st_atim);
      72   [ +  -  +  -  :          4 :         if (x > 0 && x != USEC_INFINITY && x < *realtime)
                   +  - ]
      73                 :          4 :                 *realtime = x;
      74                 :            : 
      75                 :          4 :         x = timespec_load(&st->st_mtim);
      76   [ +  -  +  -  :          4 :         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         [ +  - ]:          4 :         if (fd_getcrtime_at(fd, fn, &crtime, 0) >= 0) {
      85         [ +  - ]:          4 :                 if (crtime < *realtime)
      86                 :          4 :                         *realtime = crtime;
      87                 :            :         }
      88                 :          4 : }
      89                 :            : 
      90                 :          8 : static int journal_file_empty(int dir_fd, const char *name) {
      91                 :          8 :         _cleanup_close_ int fd;
      92                 :            :         struct stat st;
      93                 :            :         le64_t n_entries;
      94                 :            :         ssize_t n;
      95                 :            : 
      96                 :          8 :         fd = openat(dir_fd, name, O_RDONLY|O_CLOEXEC|O_NOFOLLOW|O_NONBLOCK|O_NOATIME);
      97         [ -  + ]:          8 :         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         [ -  + ]:          8 :         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         [ -  + ]:          8 :         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                 :          8 :         n = pread(fd, &n_entries, sizeof(n_entries), offsetof(Header, n_entries));
     113         [ -  + ]:          8 :         if (n < 0)
     114                 :          0 :                 return -errno;
     115         [ -  + ]:          8 :         if (n != sizeof(n_entries))
     116                 :          0 :                 return -EIO;
     117                 :            : 
     118                 :          8 :         return le64toh(n_entries) <= 0;
     119                 :            : }
     120                 :            : 
     121                 :         48 : 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                 :         48 :         uint64_t sum = 0, freed = 0, n_active_files = 0;
     130                 :         48 :         size_t n_list = 0, n_allocated = 0, i;
     131                 :         48 :         _cleanup_closedir_ DIR *d = NULL;
     132                 :         48 :         struct vacuum_info *list = NULL;
     133                 :         48 :         usec_t retention_limit = 0;
     134                 :            :         char sbytes[FORMAT_BYTES_MAX];
     135                 :            :         struct dirent *de;
     136                 :            :         int r;
     137                 :            : 
     138         [ -  + ]:         48 :         assert(directory);
     139                 :            : 
     140   [ -  +  #  #  :         48 :         if (max_use <= 0 && max_retention_usec <= 0 && n_max_files <= 0)
                   #  # ]
     141                 :          0 :                 return 0;
     142                 :            : 
     143         [ -  + ]:         48 :         if (max_retention_usec > 0)
     144                 :          0 :                 retention_limit = usec_sub_unsigned(now(CLOCK_REALTIME), max_retention_usec);
     145                 :            : 
     146                 :         48 :         d = opendir(directory);
     147         [ -  + ]:         48 :         if (!d)
     148                 :          0 :                 return -errno;
     149                 :            : 
     150   [ +  +  -  + ]:        224 :         FOREACH_DIRENT_ALL(de, d, r = -errno; goto finish) {
     151                 :            : 
     152                 :        176 :                 unsigned long long seqnum = 0, realtime;
     153      [ +  +  - ]:        176 :                 _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         [ -  + ]:        176 :                 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         [ +  + ]:        176 :                 if (!S_ISREG(st.st_mode))
     166                 :         96 :                         continue;
     167                 :            : 
     168                 :         80 :                 q = strlen(de->d_name);
     169                 :            : 
     170         [ +  - ]:         80 :                 if (endswith(de->d_name, ".journal")) {
     171                 :            : 
     172                 :            :                         /* Vacuum archived files. Active files are
     173                 :            :                          * left around */
     174                 :            : 
     175         [ +  + ]:         80 :                         if (q < 1 + 32 + 1 + 16 + 1 + 16 + 8) {
     176                 :         72 :                                 n_active_files++;
     177                 :         72 :                                 continue;
     178                 :            :                         }
     179                 :            : 
     180         [ +  - ]:          8 :                         if (de->d_name[q-8-16-1] != '-' ||
     181         [ +  - ]:          8 :                             de->d_name[q-8-16-1-16-1] != '-' ||
     182         [ -  + ]:          8 :                             de->d_name[q-8-16-1-16-1-32-1] != '@') {
     183                 :          0 :                                 n_active_files++;
     184                 :          0 :                                 continue;
     185                 :            :                         }
     186                 :            : 
     187                 :          8 :                         p = strdup(de->d_name);
     188         [ -  + ]:          8 :                         if (!p) {
     189                 :          0 :                                 r = -ENOMEM;
     190                 :          0 :                                 goto finish;
     191                 :            :                         }
     192                 :            : 
     193                 :          8 :                         de->d_name[q-8-16-1-16-1] = 0;
     194         [ -  + ]:          8 :                         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         [ -  + ]:          8 :                         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                 :          8 :                         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                 :          8 :                 size = 512UL * (uint64_t) st.st_blocks;
     241                 :            : 
     242                 :          8 :                 r = journal_file_empty(dirfd(d), p);
     243         [ -  + ]:          8 :                 if (r < 0) {
     244         [ #  # ]:          0 :                         log_debug_errno(r, "Failed check if %s is empty, ignoring: %m", p);
     245                 :          0 :                         continue;
     246                 :            :                 }
     247         [ +  + ]:          8 :                 if (r > 0) {
     248                 :            :                         /* Always vacuum empty non-online files. */
     249                 :            : 
     250                 :          4 :                         r = unlinkat_deallocate(dirfd(d), p, 0);
     251         [ +  - ]:          4 :                         if (r >= 0) {
     252                 :            : 
     253   [ +  -  +  - ]:          4 :                                 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                 :          4 :                                 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                 :          4 :                         continue;
     261                 :            :                 }
     262                 :            : 
     263                 :          4 :                 patch_realtime(dirfd(d), p, &st, &realtime);
     264                 :            : 
     265         [ -  + ]:          4 :                 if (!GREEDY_REALLOC(list, n_allocated, n_list + 1)) {
     266                 :          0 :                         r = -ENOMEM;
     267                 :          0 :                         goto finish;
     268                 :            :                 }
     269                 :            : 
     270                 :          8 :                 list[n_list++] = (struct vacuum_info) {
     271                 :          4 :                         .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                 :          4 :                 sum += size;
     280                 :            :         }
     281                 :            : 
     282                 :         48 :         typesafe_qsort(list, n_list, vacuum_compare);
     283                 :            : 
     284         [ +  + ]:         52 :         for (i = 0; i < n_list; i++) {
     285                 :            :                 uint64_t left;
     286                 :            : 
     287                 :          4 :                 left = n_active_files + n_list - i;
     288                 :            : 
     289   [ -  +  #  #  :          4 :                 if ((max_retention_usec <= 0 || list[i].realtime >= retention_limit) &&
                   +  - ]
     290   [ -  +  #  # ]:          4 :                     (max_use <= 0 || sum <= max_use) &&
     291         [ #  # ]:          0 :                     (n_max_files <= 0 || left <= n_max_files))
     292                 :            :                         break;
     293                 :            : 
     294                 :          4 :                 r = unlinkat_deallocate(dirfd(d), list[i].filename, 0);
     295         [ +  - ]:          4 :                 if (r >= 0) {
     296   [ +  -  +  - ]:          4 :                         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                 :          4 :                         freed += list[i].usage;
     298                 :            : 
     299         [ -  + ]:          4 :                         if (list[i].usage < sum)
     300                 :          0 :                                 sum -= list[i].usage;
     301                 :            :                         else
     302                 :          4 :                                 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   [ -  +  #  #  :         48 :         if (oldest_usec && i < n_list && (*oldest_usec == 0 || list[i].realtime < *oldest_usec))
             #  #  #  # ]
     309                 :          0 :                 *oldest_usec = list[i].realtime;
     310                 :            : 
     311                 :         48 :         r = 0;
     312                 :            : 
     313                 :         48 : finish:
     314         [ +  + ]:         52 :         for (i = 0; i < n_list; i++)
     315                 :          4 :                 free(list[i].filename);
     316                 :         48 :         free(list);
     317                 :            : 
     318   [ +  -  +  - ]:         48 :         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                 :         48 :         return r;
     321                 :            : }

Generated by: LCOV version 1.14