LCOV - code coverage report
Current view: top level - src - clean.c (source / functions) Hit Total Coverage
Test: plop Lines: 106 208 51.0 %
Date: 2024-12-30 07:09:03 Functions: 5 7 71.4 %
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 58 127 45.7 %

           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 <tllist.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                 :            : #include "xmalloc.h"
      62                 :            : 
      63                 :            : typedef tll(char *) dl_list;
      64                 :            : 
      65                 :            : #define OUT_OF_DATE     (1U<<0)
      66                 :            : #define REMOVED         (1U<<1)
      67                 :            : #define CKSUM_MISMATCH  (1U<<2)
      68                 :            : #define SIZE_MISMATCH   (1U<<3)
      69                 :            : #define ALL             (1U<<4)
      70                 :            : 
      71                 :            : static size_t
      72                 :          2 : add_to_dellist(int fd, dl_list *dl, const char *cachedir, const char *path)
      73                 :            : {
      74                 :            :         static bool first_entry = true;
      75                 :            :         struct stat st;
      76                 :            :         char *store_path;
      77                 :            :         const char *relpath;
      78                 :          2 :         size_t sz = 0;
      79                 :            : 
      80         [ +  - ]:          2 :         assert(path != NULL);
      81                 :            : 
      82                 :          2 :         store_path = xstrdup(path);
      83                 :            : 
      84         [ -  + ]:          2 :         if (!quiet) {
      85         [ +  + ]:          2 :                 if (first_entry) {
      86                 :          1 :                         first_entry = false;
      87                 :          1 :                         printf("The following package files will be deleted:"
      88                 :            :                             "\n");
      89                 :          1 :                 }
      90                 :          2 :                 printf("\t%s\n", store_path);
      91                 :          2 :         }
      92                 :            : 
      93                 :          2 :         relpath = path + strlen(cachedir) + 1;
      94   [ +  -  -  + ]:          2 :         if (fstatat(fd, relpath, &st, AT_SYMLINK_NOFOLLOW) != -1 && S_ISREG(st.st_mode))
      95                 :          2 :                 sz = st.st_size;
      96   [ +  +  +  +  :          2 :         tll_push_back(*dl, store_path);
          +  -  -  +  +  
                      + ]
      97                 :            : 
      98                 :          2 :         return (sz);
      99                 :            : }
     100                 :            : 
     101                 :            : static int
     102                 :          0 : delete_dellist(int fd, const char *cachedir,  dl_list *dl, int total)
     103                 :            : {
     104                 :            :         struct stat st;
     105                 :          0 :         int retcode = EXIT_SUCCESS;
     106                 :          0 :         int flag = 0;
     107                 :          0 :         unsigned int count = 0, processed = 0;
     108                 :            :         char *file, *relpath;
     109                 :            : 
     110                 :          0 :         count = tll_length(*dl);
     111                 :          0 :         progressbar_start("Deleting files");
     112   [ #  #  #  #  :          0 :         tll_foreach(*dl, it) {
                   #  # ]
     113                 :          0 :                 flag = 0;
     114                 :          0 :                 relpath = file = it->item;
     115                 :          0 :                 relpath += strlen(cachedir) + 1;
     116         [ #  # ]:          0 :                 if (fstatat(fd, relpath, &st, AT_SYMLINK_NOFOLLOW) == -1) {
     117                 :          0 :                         ++processed;
     118                 :          0 :                         progressbar_tick(processed, total);
     119                 :          0 :                         warn("can't stat %s", file);
     120                 :          0 :                         continue;
     121                 :            :                 }
     122         [ #  # ]:          0 :                 if (S_ISDIR(st.st_mode))
     123                 :          0 :                         flag = AT_REMOVEDIR;
     124         [ #  # ]:          0 :                 if (unlinkat(fd, relpath, flag) == -1) {
     125                 :          0 :                         warn("unlink(%s)", file);
     126                 :          0 :                         retcode = EXIT_FAILURE;
     127                 :          0 :                 }
     128                 :          0 :                 free(file);
     129                 :          0 :                 it->item = NULL;
     130                 :          0 :                 ++processed;
     131                 :          0 :                 progressbar_tick(processed, total);
     132                 :          0 :         }
     133                 :          0 :         progressbar_tick(processed, total);
     134                 :            : 
     135         [ #  # ]:          0 :         if (!quiet) {
     136         [ #  # ]:          0 :                 if (retcode != EXIT_SUCCESS)
     137                 :          0 :                         printf("%d package%s could not be deleted\n",
     138                 :          0 :                               count, count > 1 ? "s" : "");
     139                 :          0 :         }
     140                 :          0 :         return (retcode);
     141                 :            : }
     142                 :            : 
     143                 :            : static pkghash *
     144                 :          1 : populate_sums(struct pkgdb *db)
     145                 :            : {
     146                 :          1 :         struct pkg *p = NULL;
     147                 :          1 :         struct pkgdb_it *it = NULL;
     148                 :            :         const char *sum;
     149                 :            :         char *cksum;
     150                 :            :         size_t slen;
     151                 :          1 :         pkghash *suml = NULL;
     152                 :            : 
     153                 :          1 :         suml = pkghash_new();
     154                 :          1 :         it = pkgdb_repo_search(db, "*", MATCH_GLOB, FIELD_NAME, FIELD_NONE, NULL);
     155         [ +  + ]:          3 :         while (pkgdb_it_next(it, &p, PKG_LOAD_BASIC) == EPKG_OK) {
     156                 :          2 :                 pkg_get(p, PKG_ATTR_CKSUM, &sum);
     157         [ -  + ]:          2 :                 slen = MIN(strlen(sum), PKG_FILE_CKSUM_CHARS);
     158                 :          2 :                 cksum = strndup(sum, slen);
     159   [ +  -  -  + ]:          4 :                 pkghash_safe_add(suml, cksum, NULL, NULL);
     160                 :          2 :                 free(cksum);
     161                 :            :         }
     162                 :          1 :         pkgdb_it_free(it);
     163                 :            :         
     164                 :          1 :         return (suml);
     165                 :            : }
     166                 :            : 
     167                 :            : /*
     168                 :            :  * Extract hash from filename in format <name>-<version>~<hash>.txz
     169                 :            :  */
     170                 :            : static bool
     171                 :          2 : extract_filename_sum(const char *fname, char sum[])
     172                 :            : {
     173                 :            :         const char *tilde_pos, *dot_pos;
     174                 :            : 
     175                 :          2 :         dot_pos = strrchr(fname, '.');
     176         [ +  - ]:          2 :         if (dot_pos == NULL)
     177                 :          0 :                 dot_pos = fname + strlen(fname);
     178                 :            : 
     179                 :          2 :         tilde_pos = strrchr(fname, '~');
     180                 :            :         /* XXX Legacy fallback; remove eventually. */
     181         [ -  + ]:          2 :         if (tilde_pos == NULL)
     182                 :          2 :                 tilde_pos = strrchr(fname, '-');
     183         [ +  - ]:          2 :         if (tilde_pos == NULL)
     184                 :          0 :                 return (false);
     185         [ +  - ]:          2 :         else if (dot_pos < tilde_pos)
     186                 :          0 :                 dot_pos = fname + strlen(fname);
     187                 :            : 
     188         [ +  - ]:          2 :         if (dot_pos - tilde_pos != PKG_FILE_CKSUM_CHARS + 1)
     189                 :          2 :                 return (false);
     190                 :            : 
     191                 :          0 :         strlcpy(sum, tilde_pos + 1, PKG_FILE_CKSUM_CHARS + 1);
     192                 :          0 :         return (true);
     193                 :          2 : }
     194                 :            : 
     195                 :            : static int
     196                 :          1 : recursive_analysis(int fd, struct pkgdb *db, const char *dir,
     197                 :            :     const char *cachedir, dl_list *dl, pkghash **sumlist, bool all,
     198                 :            :     size_t *total)
     199                 :            : {
     200                 :            :         DIR *d;
     201                 :            :         struct dirent *ent;
     202                 :            :         int newfd, tmpfd;
     203                 :            :         char path[MAXPATHLEN], csum[PKG_FILE_CKSUM_CHARS + 1],
     204                 :            :                 link_buf[MAXPATHLEN];
     205                 :            :         const char *name;
     206                 :            :         ssize_t link_len;
     207                 :          1 :         size_t nbfiles = 0, added = 0;
     208                 :            :         pkghash_entry *e;
     209                 :            : 
     210                 :          1 :         tmpfd = dup(fd);
     211                 :          1 :         d = fdopendir(tmpfd);
     212         [ +  - ]:          1 :         if (d == NULL) {
     213                 :          0 :                 close(tmpfd);
     214                 :          0 :                 warnx("Impossible to open the directory %s", dir);
     215                 :          0 :                 return (0);
     216                 :            :         }
     217                 :            : 
     218         [ +  + ]:          5 :         while ((ent = readdir(d)) != NULL) {
     219   [ +  +  +  + ]:          4 :                 if (STREQ(ent->d_name, ".") ||
     220                 :          3 :                     STREQ(ent->d_name, ".."))
     221                 :          2 :                         continue;
     222                 :          2 :                 snprintf(path, sizeof(path), "%s/%s", dir, ent->d_name);
     223         [ -  + ]:          2 :                 if (ent->d_type == DT_DIR) {
     224                 :          0 :                         nbfiles++;
     225                 :          0 :                         newfd = openat(fd, ent->d_name, O_DIRECTORY|O_CLOEXEC, 0);
     226         [ #  # ]:          0 :                         if (newfd == -1) {
     227                 :          0 :                                 warnx("Impossible to open the directory %s",
     228                 :          0 :                                     path);
     229                 :          0 :                                 continue;
     230                 :            :                         }
     231         [ #  # ]:          0 :                         if (recursive_analysis(newfd, db, path, cachedir, dl,
     232   [ #  #  #  # ]:          0 :                             sumlist, all, total) == 0 || all) {
     233                 :          0 :                                 add_to_dellist(fd, dl, cachedir, path);
     234                 :          0 :                                 added++;
     235                 :          0 :                         }
     236                 :          0 :                         close(newfd);
     237                 :          0 :                         continue;
     238                 :            :                 }
     239   [ +  -  +  - ]:          2 :                 if (ent->d_type != DT_LNK && ent->d_type != DT_REG)
     240                 :          0 :                         continue;
     241                 :          2 :                 nbfiles++;
     242         [ -  + ]:          2 :                 if (all) {
     243                 :          0 :                         *total += add_to_dellist(fd, dl, cachedir, path);
     244                 :          0 :                         continue;
     245                 :            :                 }
     246         [ +  + ]:          2 :                 if (*sumlist == NULL) {
     247                 :          1 :                         *sumlist = populate_sums(db);
     248                 :          1 :                 }
     249                 :          2 :                 name = ent->d_name;
     250         [ +  - ]:          2 :                 if (ent->d_type == DT_LNK) {
     251                 :            :                         /* Dereference the symlink and check it for being
     252                 :            :                          * recognized checksum file, or delete the symlink
     253                 :            :                          * later. */
     254   [ #  #  #  # ]:          0 :                         if ((link_len = readlinkat(fd, ent->d_name, link_buf,
     255                 :          0 :                             sizeof(link_buf))) == -1)
     256                 :          0 :                                 continue;
     257                 :          0 :                         link_buf[link_len - 1] = '\0';
     258                 :          0 :                         name = link_buf;
     259                 :          0 :                 }
     260                 :            : 
     261                 :          2 :                 e = NULL;
     262         [ +  - ]:          2 :                 if (extract_filename_sum(name, csum)) {
     263                 :          0 :                         e = pkghash_get(*sumlist, csum);
     264                 :          0 :                 }
     265         [ -  + ]:          2 :                 if (e == NULL) {
     266                 :          2 :                         added++;
     267                 :          2 :                         *total += add_to_dellist(fd, dl, cachedir, path);
     268                 :          2 :                 }
     269                 :            :         }
     270                 :          1 :         closedir(d);
     271                 :          1 :         return (nbfiles - added);
     272                 :          1 : }
     273                 :            : 
     274                 :            : void
     275                 :          0 : usage_clean(void)
     276                 :            : {
     277                 :          0 :         fprintf(stderr, "Usage: pkg clean [-anqy]\n\n");
     278                 :          0 :         fprintf(stderr, "For more information see 'pkg help clean'.\n");
     279                 :          0 : }
     280                 :            : 
     281                 :            : int
     282                 :          1 : exec_clean(int argc, char **argv)
     283                 :            : {
     284                 :          1 :         struct pkgdb    *db = NULL;
     285                 :          1 :         pkghash         *sumlist = NULL;
     286                 :          1 :         dl_list          dl = tll_init();
     287                 :            :         const char      *cachedir;
     288                 :          1 :         bool             all = false;
     289                 :            :         int              retcode;
     290                 :            :         int              ch;
     291                 :          1 :         int              cachefd = -1;
     292                 :          1 :         size_t           total = 0;
     293                 :            :         char             size[8];
     294                 :            : #ifdef HAVE_CAPSICUM
     295                 :            :         cap_rights_t rights;
     296                 :            : #endif
     297                 :            : 
     298                 :          1 :         struct option longopts[] = {
     299                 :            :                 { "all",      no_argument,    NULL,   'a' },
     300                 :            :                 { "dry-run",  no_argument,    NULL,   'n' },
     301                 :            :                 { "quiet",    no_argument,    NULL,   'q' },
     302                 :            :                 { "yes",      no_argument,    NULL,   'y' },
     303                 :            :                 { NULL,         0,              NULL,   0   },
     304                 :            :         };
     305                 :            : 
     306         [ +  + ]:          2 :         while ((ch = getopt_long(argc, argv, "+anqy", longopts, NULL)) != -1) {
     307   [ -  -  +  -  :          1 :                 switch (ch) {
                      - ]
     308                 :            :                 case 'a':
     309                 :          0 :                         all = true;
     310                 :          0 :                         break;
     311                 :            :                 case 'n':
     312                 :          1 :                         dry_run = true;
     313                 :          1 :                         break;
     314                 :            :                 case 'q':
     315                 :          0 :                         quiet = true;
     316                 :          0 :                         break;
     317                 :            :                 case 'y':
     318                 :          0 :                         yes = true;
     319                 :          0 :                         break;
     320                 :            :                 default:
     321                 :          0 :                         usage_clean();
     322                 :          0 :                         return (EXIT_FAILURE);
     323                 :            :                 }
     324                 :            :         }
     325                 :            : 
     326                 :          1 :         cachedir = pkg_get_cachedir();
     327                 :          1 :         cachefd = pkg_get_cachedirfd();
     328         [ +  - ]:          1 :         if (cachefd == -1) {
     329                 :          0 :                 warn("Impossible to open %s", cachedir);
     330                 :          0 :                 return (errno == ENOENT ? EXIT_SUCCESS : EXIT_FAILURE);
     331                 :            :         }
     332                 :            : 
     333                 :          1 :         retcode = pkgdb_access(PKGDB_MODE_READ, PKGDB_DB_REPO);
     334                 :            : 
     335         [ -  + ]:          1 :         if (retcode == EPKG_ENOACCESS) {
     336                 :          0 :                 warnx("Insufficient privileges to clean old packages");
     337                 :          0 :                 close(cachefd);
     338                 :          0 :                 return (EXIT_FAILURE);
     339         [ -  + ]:          1 :         } else if (retcode == EPKG_ENODB) {
     340                 :          0 :                 warnx("No package database installed.  Nothing to do!");
     341                 :          0 :                 close(cachefd);
     342                 :          0 :                 return (EXIT_SUCCESS);
     343         [ -  + ]:          1 :         } else if (retcode != EPKG_OK) {
     344                 :          0 :                 warnx("Error accessing the package database");
     345                 :          0 :                 close(cachefd);
     346                 :          0 :                 return (EXIT_FAILURE);
     347                 :            :         }
     348                 :            : 
     349                 :          1 :         retcode = EXIT_FAILURE;
     350                 :            : 
     351         [ -  + ]:          1 :         if (pkgdb_open(&db, PKGDB_REMOTE) != EPKG_OK) {
     352                 :          0 :                 close(cachefd);
     353                 :          0 :                 return (EXIT_FAILURE);
     354                 :            :         }
     355                 :            : 
     356         [ -  + ]:          1 :         if (pkgdb_obtain_lock(db, PKGDB_LOCK_READONLY) != EPKG_OK) {
     357                 :          0 :                 pkgdb_close(db);
     358                 :          0 :                 close(cachefd);
     359                 :          0 :                 warnx("Cannot get a read lock on a database, it is locked by "
     360                 :            :                     "another process");
     361                 :          0 :                 return (EXIT_FAILURE);
     362                 :            :         }
     363                 :            : 
     364                 :            : #ifdef HAVE_CAPSICUM
     365                 :            :                 cap_rights_init(&rights, CAP_READ, CAP_LOOKUP, CAP_FSTATFS,
     366                 :            :                     CAP_FSTAT, CAP_UNLINKAT);
     367                 :            :                 if (cap_rights_limit(cachefd, &rights) < 0 && errno != ENOSYS ) {
     368                 :            :                         warn("cap_rights_limit() failed");
     369                 :            :                         close(cachefd);
     370                 :            :                         return (EXIT_FAILURE);
     371                 :            :                 }
     372                 :            : 
     373                 :            : #ifndef PKG_COVERAGE
     374                 :            :                 if (cap_enter() < 0 && errno != ENOSYS) {
     375                 :            :                         warn("cap_enter() failed");
     376                 :            :                         close(cachefd);
     377                 :            :                         return (EXIT_FAILURE);
     378                 :            :                 }
     379                 :            : #endif
     380                 :            : #endif
     381                 :            : 
     382                 :            :         /* Build the list of out-of-date or obsolete packages */
     383                 :            : 
     384                 :          1 :         recursive_analysis(cachefd, db, cachedir, cachedir, &dl, &sumlist, all,
     385                 :            :             &total);
     386                 :          1 :         pkghash_destroy(sumlist);
     387                 :            : 
     388         [ +  - ]:          1 :         if (tll_length(dl) == 0) {
     389         [ #  # ]:          0 :                 if (!quiet)
     390                 :          0 :                         printf("Nothing to do.\n");
     391                 :          0 :                 retcode = EXIT_SUCCESS;
     392                 :          0 :                 goto cleanup;
     393                 :            :         }
     394                 :            : 
     395                 :          1 :         humanize_number(size, sizeof(size), total, "B",
     396                 :            :             HN_AUTOSCALE, HN_IEC_PREFIXES);
     397                 :            : 
     398         [ -  + ]:          1 :         if (!quiet)
     399                 :          1 :                 printf("The cleanup will free %s\n", size);
     400         [ -  + ]:          2 :         if (!dry_run) {
     401         [ #  # ]:          0 :                         if (query_yesno(false,
     402                 :            :                           "\nProceed with cleaning the cache? ")) {
     403                 :          0 :                                 retcode = delete_dellist(cachefd, cachedir, &dl, tll_length(dl));
     404                 :          0 :                         }
     405                 :          0 :         } else {
     406                 :          1 :                 retcode = EXIT_SUCCESS;
     407                 :            :         }
     408                 :            : 
     409                 :            : cleanup:
     410                 :          1 :         pkgdb_release_lock(db, PKGDB_LOCK_READONLY);
     411                 :          1 :         pkgdb_close(db);
     412   [ +  -  +  +  :          3 :         tll_free_and_free(dl, free);
                   +  + ]
     413                 :            : 
     414         [ -  + ]:          1 :         if (cachefd != -1)
     415                 :          1 :                 close(cachefd);
     416                 :            : 
     417                 :          1 :         return (retcode);
     418                 :          1 : }

Generated by: LCOV version 1.15