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