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 : }
|