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