LCOV - code coverage report
Current view: top level - src - clean.c (source / functions) Hit Total Coverage
Test: rapport Lines: 115 221 52.0 %
Date: 2021-12-10 16:22:55 Functions: 6 8 75.0 %
Branches: 51 117 43.6 %

           Branch data     Line data    Source code
       1                 :            : /*-
       2                 :            :  * Copyright (c) 2011-2012 Julien Laffaye <jlaffaye@FreeBSD.org>
       3                 :            :  * Copyright (c) 2013-2014 Matthew Seaman <matthew@FreeBSD.org>
       4                 :            :  * Copyright (c) 2014 Vsevolod Stakhov <vsevolod@FreeBSD.org>
       5                 :            :  * Copyright (c) 2016 Baptiste Daroussin <bapt@FreeBSD.org>
       6                 :            :  * All rights reserved.
       7                 :            :  *
       8                 :            :  * Redistribution and use in source and binary forms, with or without
       9                 :            :  * modification, are permitted provided that the following conditions
      10                 :            :  * are met:
      11                 :            :  * 1. Redistributions of source code must retain the above copyright
      12                 :            :  *    notice, this list of conditions and the following disclaimer
      13                 :            :  *    in this position and unchanged.
      14                 :            :  * 2. Redistributions in binary form must reproduce the above copyright
      15                 :            :  *    notice, this list of conditions and the following disclaimer in the
      16                 :            :  *    documentation and/or other materials provided with the distribution.
      17                 :            :  *
      18                 :            :  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
      19                 :            :  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
      20                 :            :  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
      21                 :            :  * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
      22                 :            :  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
      23                 :            :  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
      24                 :            :  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
      25                 :            :  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
      26                 :            :  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
      27                 :            :  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
      28                 :            :  */
      29                 :            : 
      30                 :            : #ifdef HAVE_CONFIG_H
      31                 :            : #include "pkg_config.h"
      32                 :            : #endif
      33                 :            : 
      34                 :            : #include <sys/stat.h>
      35                 :            : /* For MIN */
      36                 :            : #include <sys/param.h>
      37                 :            : 
      38                 :            : #ifdef HAVE_CAPSICUM
      39                 :            : #include <sys/capsicum.h>
      40                 :            : #endif
      41                 :            : 
      42                 :            : #include <assert.h>
      43                 :            : #include <err.h>
      44                 :            : #include <getopt.h>
      45                 :            : #ifdef HAVE_LIBUTIL_H
      46                 :            : #include <libutil.h>
      47                 :            : #endif
      48                 :            : #include <pkg.h>
      49                 :            : #include <stdbool.h>
      50                 :            : #include <string.h>
      51                 :            : #include <unistd.h>
      52                 :            : #include <kvec.h>
      53                 :            : #include <fcntl.h>
      54                 :            : #include <dirent.h>
      55                 :            : #include <errno.h>
      56                 :            : 
      57                 :            : #include <bsd_compat.h>
      58                 :            : 
      59                 :            : #include "pkgcli.h"
      60                 :            : #include "pkghash.h"
      61                 :            : 
      62                 :            : typedef kvec_t(char *) dl_list;
      63                 :            : 
      64                 :            : #define OUT_OF_DATE     (1U<<0)
      65                 :            : #define REMOVED         (1U<<1)
      66                 :            : #define CKSUM_MISMATCH  (1U<<2)
      67                 :            : #define SIZE_MISMATCH   (1U<<3)
      68                 :            : #define ALL             (1U<<4)
      69                 :            : 
      70                 :            : static size_t
      71                 :          8 : add_to_dellist(int fd, dl_list *dl, const char *cachedir, const char *path)
      72                 :            : {
      73                 :            :         static bool first_entry = true;
      74                 :            :         struct stat st;
      75                 :            :         char *store_path;
      76                 :            :         const char *relpath;
      77                 :          8 :         size_t sz = 0;
      78                 :            : 
      79         [ +  - ]:          8 :         assert(path != NULL);
      80                 :            : 
      81                 :          8 :         store_path = strdup(path);
      82                 :            : 
      83         [ -  + ]:          8 :         if (!quiet) {
      84         [ +  + ]:          8 :                 if (first_entry) {
      85                 :          4 :                         first_entry = false;
      86                 :          4 :                         printf("The following package files will be deleted:"
      87                 :            :                             "\n");
      88                 :          4 :                 }
      89                 :          8 :                 printf("\t%s\n", store_path);
      90                 :          8 :         }
      91                 :            : 
      92                 :          8 :         relpath = path + strlen(cachedir) + 1;
      93   [ +  -  -  + ]:          8 :         if (fstatat(fd, relpath, &st, AT_SYMLINK_NOFOLLOW) != -1 && S_ISREG(st.st_mode))
      94                 :          8 :                 sz = st.st_size;
      95   [ +  +  -  + ]:          8 :         kv_push(char *, *dl, store_path);
      96                 :            : 
      97                 :          8 :         return (sz);
      98                 :            : }
      99                 :            : 
     100                 :            : static void
     101                 :          4 : free_dellist(dl_list *dl)
     102                 :            : {
     103                 :            :         unsigned int i;
     104                 :            : 
     105         [ +  + ]:         12 :         for (i = 0; i < kv_size(*dl); i++)
     106                 :          8 :                 free(kv_A(*dl, i));
     107                 :          4 :         kv_destroy(*dl);
     108                 :          4 : }
     109                 :            : 
     110                 :            : static int
     111                 :          0 : delete_dellist(int fd, const char *cachedir,  dl_list *dl, int total)
     112                 :            : {
     113                 :            :         struct stat st;
     114                 :          0 :         int retcode = EXIT_SUCCESS;
     115                 :          0 :         int flag = 0;
     116                 :            :         size_t i;
     117                 :          0 :         unsigned int count = 0, processed = 0;
     118                 :            :         char *file, *relpath;
     119                 :            : 
     120                 :          0 :         count = kv_size(*dl);
     121                 :          0 :         progressbar_start("Deleting files");
     122         [ #  # ]:          0 :         for (i = 0; i < kv_size(*dl); i++) {
     123                 :          0 :                 flag = 0;
     124                 :          0 :                 relpath = file = kv_A(*dl, i);
     125                 :          0 :                 relpath += strlen(cachedir) + 1;
     126         [ #  # ]:          0 :                 if (fstatat(fd, relpath, &st, AT_SYMLINK_NOFOLLOW) == -1) {
     127                 :          0 :                         ++processed;
     128                 :          0 :                         progressbar_tick(processed, total);
     129                 :          0 :                         warn("can't stat %s", file);
     130                 :          0 :                         continue;
     131                 :            :                 }
     132         [ #  # ]:          0 :                 if (S_ISDIR(st.st_mode))
     133                 :          0 :                         flag = AT_REMOVEDIR;
     134         [ #  # ]:          0 :                 if (unlinkat(fd, relpath, flag) == -1) {
     135                 :          0 :                         warn("unlink(%s)", file);
     136                 :          0 :                         retcode = EXIT_FAILURE;
     137                 :          0 :                 }
     138                 :          0 :                 free(file);
     139                 :          0 :                 kv_A(*dl, i) = NULL;
     140                 :          0 :                 ++processed;
     141                 :          0 :                 progressbar_tick(processed, total);
     142                 :          0 :         }
     143                 :          0 :         progressbar_tick(processed, total);
     144                 :            : 
     145         [ #  # ]:          0 :         if (!quiet) {
     146         [ #  # ]:          0 :                 if (retcode == EXIT_SUCCESS)
     147                 :          0 :                         printf("All done\n");
     148                 :            :                 else
     149                 :          0 :                         printf("%d package%s could not be deleted\n",
     150                 :          0 :                               count, count > 1 ? "s" : "");
     151                 :          0 :         }
     152                 :          0 :         return (retcode);
     153                 :            : }
     154                 :            : 
     155                 :            : static pkghash *
     156                 :          4 : populate_sums(struct pkgdb *db)
     157                 :            : {
     158                 :          4 :         struct pkg *p = NULL;
     159                 :          4 :         struct pkgdb_it *it = NULL;
     160                 :            :         const char *sum;
     161                 :            :         char *cksum;
     162                 :            :         size_t slen;
     163                 :          4 :         pkghash *suml = NULL;
     164                 :            : 
     165                 :          4 :         suml = pkghash_new();
     166                 :          4 :         it = pkgdb_repo_search(db, "*", MATCH_GLOB, FIELD_NAME, FIELD_NONE, NULL);
     167         [ +  + ]:         12 :         while (pkgdb_it_next(it, &p, PKG_LOAD_BASIC) == EPKG_OK) {
     168                 :          8 :                 pkg_get(p, PKG_CKSUM, &sum);
     169         [ -  + ]:          8 :                 slen = MIN(strlen(sum), PKG_FILE_CKSUM_CHARS);
     170                 :          8 :                 cksum = strndup(sum, slen);
     171   [ +  -  -  + ]:         16 :                 pkghash_safe_add(suml, cksum, NULL, NULL);
     172                 :          8 :                 free(cksum);
     173                 :            :         }
     174                 :            : 
     175                 :          4 :         return (suml);
     176                 :            : }
     177                 :            : 
     178                 :            : /*
     179                 :            :  * Extract hash from filename in format <name>-<version>~<hash>.txz
     180                 :            :  */
     181                 :            : static bool
     182                 :          8 : extract_filename_sum(const char *fname, char sum[])
     183                 :            : {
     184                 :            :         const char *tilde_pos, *dot_pos;
     185                 :            : 
     186                 :          8 :         dot_pos = strrchr(fname, '.');
     187         [ +  - ]:          8 :         if (dot_pos == NULL)
     188                 :          0 :                 dot_pos = fname + strlen(fname);
     189                 :            : 
     190                 :          8 :         tilde_pos = strrchr(fname, '~');
     191                 :            :         /* XXX Legacy fallback; remove eventually. */
     192         [ -  + ]:          8 :         if (tilde_pos == NULL)
     193                 :          8 :                 tilde_pos = strrchr(fname, '-');
     194         [ +  - ]:          8 :         if (tilde_pos == NULL)
     195                 :          0 :                 return (false);
     196         [ +  - ]:          8 :         else if (dot_pos < tilde_pos)
     197                 :          0 :                 dot_pos = fname + strlen(fname);
     198                 :            : 
     199         [ +  - ]:          8 :         if (dot_pos - tilde_pos != PKG_FILE_CKSUM_CHARS + 1)
     200                 :          8 :                 return (false);
     201                 :            : 
     202                 :          0 :         strlcpy(sum, tilde_pos + 1, PKG_FILE_CKSUM_CHARS + 1);
     203                 :          0 :         return (true);
     204                 :          8 : }
     205                 :            : 
     206                 :            : static int
     207                 :          4 : recursive_analysis(int fd, struct pkgdb *db, const char *dir,
     208                 :            :     const char *cachedir, dl_list *dl, pkghash **sumlist, bool all,
     209                 :            :     size_t *total)
     210                 :            : {
     211                 :            :         DIR *d;
     212                 :            :         struct dirent *ent;
     213                 :            :         int newfd, tmpfd;
     214                 :            :         char path[MAXPATHLEN], csum[PKG_FILE_CKSUM_CHARS + 1],
     215                 :            :                 link_buf[MAXPATHLEN];
     216                 :            :         const char *name;
     217                 :            :         ssize_t link_len;
     218                 :          4 :         size_t nbfiles = 0, added = 0;
     219                 :            :         pkghash_entry *e;
     220                 :            : 
     221                 :          4 :         tmpfd = dup(fd);
     222                 :          4 :         d = fdopendir(tmpfd);
     223         [ +  - ]:          4 :         if (d == NULL) {
     224                 :          0 :                 close(tmpfd);
     225                 :          0 :                 warnx("Impossible to open the directory %s", dir);
     226                 :          0 :                 return (0);
     227                 :            :         }
     228                 :            : 
     229         [ +  + ]:         20 :         while ((ent = readdir(d)) != NULL) {
     230   [ +  +  +  + ]:         16 :                 if (strcmp(ent->d_name, ".") == 0 ||
     231                 :         12 :                     strcmp(ent->d_name, "..") == 0)
     232                 :          8 :                         continue;
     233                 :          8 :                 snprintf(path, sizeof(path), "%s/%s", dir, ent->d_name);
     234         [ -  + ]:          8 :                 if (ent->d_type == DT_DIR) {
     235                 :          0 :                         nbfiles++;
     236                 :          0 :                         newfd = openat(fd, ent->d_name, O_DIRECTORY|O_CLOEXEC, 0);
     237         [ #  # ]:          0 :                         if (newfd == -1) {
     238                 :          0 :                                 warnx("Impossible to open the directory %s",
     239                 :          0 :                                     path);
     240                 :          0 :                                 continue;
     241                 :            :                         }
     242         [ #  # ]:          0 :                         if (recursive_analysis(newfd, db, path, cachedir, dl,
     243   [ #  #  #  # ]:          0 :                             sumlist, all, total) == 0 || all) {
     244                 :          0 :                                 add_to_dellist(fd, dl, cachedir, path);
     245                 :          0 :                                 added++;
     246                 :          0 :                         }
     247                 :          0 :                         close(newfd);
     248                 :          0 :                         continue;
     249                 :            :                 }
     250   [ +  -  +  - ]:          8 :                 if (ent->d_type != DT_LNK && ent->d_type != DT_REG)
     251                 :          0 :                         continue;
     252                 :          8 :                 nbfiles++;
     253         [ -  + ]:          8 :                 if (all) {
     254                 :          0 :                         *total += add_to_dellist(fd, dl, cachedir, path);
     255                 :          0 :                         continue;
     256                 :            :                 }
     257         [ +  + ]:          8 :                 if (*sumlist == NULL) {
     258                 :          4 :                         *sumlist = populate_sums(db);
     259                 :          4 :                 }
     260                 :          8 :                 name = ent->d_name;
     261         [ +  - ]:          8 :                 if (ent->d_type == DT_LNK) {
     262                 :            :                         /* Dereference the symlink and check it for being
     263                 :            :                          * recognized checksum file, or delete the symlink
     264                 :            :                          * later. */
     265   [ #  #  #  # ]:          0 :                         if ((link_len = readlinkat(fd, ent->d_name, link_buf,
     266                 :          0 :                             sizeof(link_buf))) == -1)
     267                 :          0 :                                 continue;
     268                 :          0 :                         link_buf[link_len - 1] = '\0';
     269                 :          0 :                         name = link_buf;
     270                 :          0 :                 }
     271                 :            : 
     272                 :          8 :                 e = NULL;
     273         [ +  - ]:          8 :                 if (extract_filename_sum(name, csum)) {
     274                 :          0 :                         e = pkghash_get(*sumlist, csum);
     275                 :          0 :                 }
     276         [ -  + ]:          8 :                 if (e == NULL) {
     277                 :          8 :                         added++;
     278                 :          8 :                         *total += add_to_dellist(fd, dl, cachedir, path);
     279                 :          8 :                 }
     280                 :            :         }
     281                 :          4 :         closedir(d);
     282                 :          4 :         return (nbfiles - added);
     283                 :          4 : }
     284                 :            : 
     285                 :            : void
     286                 :          0 : usage_clean(void)
     287                 :            : {
     288                 :          0 :         fprintf(stderr, "Usage: pkg clean [-anqy]\n\n");
     289                 :          0 :         fprintf(stderr, "For more information see 'pkg help clean'.\n");
     290                 :          0 : }
     291                 :            : 
     292                 :            : int
     293                 :          4 : exec_clean(int argc, char **argv)
     294                 :            : {
     295                 :          4 :         struct pkgdb    *db = NULL;
     296                 :          4 :         pkghash         *sumlist = NULL;
     297                 :            :         dl_list          dl;
     298                 :            :         const char      *cachedir;
     299                 :          4 :         bool             all = false;
     300                 :            :         int              retcode;
     301                 :            :         int              ch;
     302                 :          4 :         int              cachefd = -1;
     303                 :          4 :         size_t           total = 0;
     304                 :            :         char             size[8];
     305                 :          4 :         struct pkg_manifest_key *keys = NULL;
     306                 :            : #ifdef HAVE_CAPSICUM
     307                 :            :         cap_rights_t rights;
     308                 :            : #endif
     309                 :            : 
     310                 :          4 :         struct option longopts[] = {
     311                 :            :                 { "all",      no_argument,    NULL,   'a' },
     312                 :            :                 { "dry-run",  no_argument,    NULL,   'n' },
     313                 :            :                 { "quiet",    no_argument,    NULL,   'q' },
     314                 :            :                 { "yes",      no_argument,    NULL,   'y' },
     315                 :            :                 { NULL,         0,              NULL,   0   },
     316                 :            :         };
     317                 :            : 
     318         [ +  + ]:          8 :         while ((ch = getopt_long(argc, argv, "+anqy", longopts, NULL)) != -1) {
     319   [ -  +  -  -  :          4 :                 switch (ch) {
                      - ]
     320                 :            :                 case 'a':
     321                 :          0 :                         all = true;
     322                 :          0 :                         break;
     323                 :            :                 case 'n':
     324                 :          4 :                         dry_run = true;
     325                 :          4 :                         break;
     326                 :            :                 case 'q':
     327                 :          0 :                         quiet = true;
     328                 :          0 :                         break;
     329                 :            :                 case 'y':
     330                 :          0 :                         yes = true;
     331                 :          0 :                         break;
     332                 :            :                 default:
     333                 :          0 :                         usage_clean();
     334                 :          0 :                         return (EXIT_FAILURE);
     335                 :            :                 }
     336                 :            :         }
     337                 :            : 
     338                 :          4 :         cachedir = pkg_get_cachedir();
     339                 :          4 :         cachefd = pkg_get_cachedirfd();
     340         [ +  - ]:          4 :         if (cachefd == -1) {
     341                 :          0 :                 warn("Impossible to open %s", cachedir);
     342                 :          0 :                 return (errno == ENOENT ? EXIT_SUCCESS : EXIT_FAILURE);
     343                 :            :         }
     344                 :            : 
     345                 :          4 :         retcode = pkgdb_access(PKGDB_MODE_READ, PKGDB_DB_REPO);
     346                 :            : 
     347         [ -  + ]:          4 :         if (retcode == EPKG_ENOACCESS) {
     348                 :          0 :                 warnx("Insufficient privileges to clean old packages");
     349                 :          0 :                 close(cachefd);
     350                 :          0 :                 return (EXIT_FAILURE);
     351         [ -  + ]:          4 :         } else if (retcode == EPKG_ENODB) {
     352                 :          0 :                 warnx("No package database installed.  Nothing to do!");
     353                 :          0 :                 close(cachefd);
     354                 :          0 :                 return (EXIT_SUCCESS);
     355         [ -  + ]:          4 :         } else if (retcode != EPKG_OK) {
     356                 :          0 :                 warnx("Error accessing the package database");
     357                 :          0 :                 close(cachefd);
     358                 :          0 :                 return (EXIT_FAILURE);
     359                 :            :         }
     360                 :            : 
     361                 :          4 :         retcode = EXIT_FAILURE;
     362                 :            : 
     363         [ -  + ]:          4 :         if (pkgdb_open(&db, PKGDB_REMOTE) != EPKG_OK) {
     364                 :          0 :                 close(cachefd);
     365                 :          0 :                 return (EXIT_FAILURE);
     366                 :            :         }
     367                 :            : 
     368         [ -  + ]:          4 :         if (pkgdb_obtain_lock(db, PKGDB_LOCK_READONLY) != EPKG_OK) {
     369                 :          0 :                 pkgdb_close(db);
     370                 :          0 :                 close(cachefd);
     371                 :          0 :                 warnx("Cannot get a read lock on a database, it is locked by "
     372                 :            :                     "another process");
     373                 :          0 :                 return (EXIT_FAILURE);
     374                 :            :         }
     375                 :            : 
     376                 :            : #ifdef HAVE_CAPSICUM
     377                 :          4 :                 cap_rights_init(&rights, CAP_READ, CAP_LOOKUP, CAP_FSTATFS,
     378                 :            :                     CAP_FSTAT, CAP_UNLINKAT);
     379   [ -  +  #  # ]:          4 :                 if (cap_rights_limit(cachefd, &rights) < 0 && errno != ENOSYS ) {
     380                 :          0 :                         warn("cap_rights_limit() failed");
     381                 :          0 :                         close(cachefd);
     382                 :          0 :                         return (EXIT_FAILURE);
     383                 :            :                 }
     384                 :            : 
     385                 :            : #ifndef PKG_COVERAGE
     386                 :            :                 if (cap_enter() < 0 && errno != ENOSYS) {
     387                 :            :                         warn("cap_enter() failed");
     388                 :            :                         close(cachefd);
     389                 :            :                         return (EXIT_FAILURE);
     390                 :            :                 }
     391                 :            : #endif
     392                 :            : #endif
     393                 :            : 
     394                 :          4 :         kv_init(dl);
     395                 :            : 
     396                 :            :         /* Build the list of out-of-date or obsolete packages */
     397                 :            : 
     398                 :          4 :         pkg_manifest_keys_new(&keys);
     399                 :          4 :         recursive_analysis(cachefd, db, cachedir, cachedir, &dl, &sumlist, all,
     400                 :            :             &total);
     401                 :          4 :         pkghash_destroy(sumlist);
     402                 :            : 
     403         [ +  - ]:          4 :         if (kv_size(dl) == 0) {
     404         [ #  # ]:          0 :                 if (!quiet)
     405                 :          0 :                         printf("Nothing to do.\n");
     406                 :          0 :                 retcode = EXIT_SUCCESS;
     407                 :          0 :                 goto cleanup;
     408                 :            :         }
     409                 :            : 
     410                 :          4 :         humanize_number(size, sizeof(size), total, "B",
     411                 :            :             HN_AUTOSCALE, HN_IEC_PREFIXES);
     412                 :            : 
     413         [ -  + ]:          4 :         if (!quiet)
     414                 :          4 :                 printf("The cleanup will free %s\n", size);
     415         [ -  + ]:          8 :         if (!dry_run) {
     416         [ #  # ]:          0 :                         if (query_yesno(false,
     417                 :            :                           "\nProceed with cleaning the cache? ")) {
     418                 :          0 :                                 retcode = delete_dellist(cachefd, cachedir, &dl, kv_size(dl));
     419                 :          0 :                         }
     420                 :          0 :         } else {
     421                 :          4 :                 retcode = EXIT_SUCCESS;
     422                 :            :         }
     423                 :            : 
     424                 :            : cleanup:
     425                 :          4 :         pkgdb_release_lock(db, PKGDB_LOCK_READONLY);
     426                 :          4 :         pkgdb_close(db);
     427                 :          4 :         pkg_manifest_keys_free(keys);
     428                 :          4 :         free_dellist(&dl);
     429                 :            : 
     430         [ -  + ]:          4 :         if (cachefd != -1)
     431                 :          4 :                 close(cachefd);
     432                 :            : 
     433                 :          4 :         return (retcode);
     434                 :          4 : }

Generated by: LCOV version 1.15