LCOV - code coverage report
Current view: top level - journal - journal-vacuum.c (source / functions) Hit Total Coverage
Test: systemd test coverage Lines: 96 162 59.3 %
Date: 2015-07-29 18:47:03 Functions: 3 4 75.0 %

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

Generated by: LCOV version 1.11