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 : * All rights reserved.
6 : *
7 : * Redistribution and use in source and binary forms, with or without
8 : * modification, are permitted provided that the following conditions
9 : * are met:
10 : * 1. Redistributions of source code must retain the above copyright
11 : * notice, this list of conditions and the following disclaimer
12 : * in this position and unchanged.
13 : * 2. Redistributions in binary form must reproduce the above copyright
14 : * notice, this list of conditions and the following disclaimer in the
15 : * documentation and/or other materials provided with the distribution.
16 : *
17 : * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
18 : * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 : * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 : * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
21 : * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 : * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 : * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 : * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 : * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 : * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 : */
28 :
29 : #ifdef HAVE_CONFIG_H
30 : #include "pkg_config.h"
31 : #endif
32 :
33 : #include <sys/stat.h>
34 : /* For MIN */
35 : #include <sys/param.h>
36 :
37 : #include <assert.h>
38 : #include <err.h>
39 : #include <fts.h>
40 : #include <getopt.h>
41 : #ifdef HAVE_LIBUTIL_H
42 : #include <libutil.h>
43 : #endif
44 : #include <pkg.h>
45 : #include <stdbool.h>
46 : #include <string.h>
47 : #include <sysexits.h>
48 : #include <unistd.h>
49 : #include <khash.h>
50 : #include <kvec.h>
51 :
52 : #include <bsd_compat.h>
53 :
54 : #include "pkgcli.h"
55 :
56 0 : KHASH_MAP_INIT_STR(sum, char *);
57 : typedef kvec_t(char *) dl_list;
58 :
59 : #define OUT_OF_DATE (1U<<0)
60 : #define REMOVED (1U<<1)
61 : #define CKSUM_MISMATCH (1U<<2)
62 : #define SIZE_MISMATCH (1U<<3)
63 : #define ALL (1U<<4)
64 :
65 : static int
66 0 : add_to_dellist(dl_list *dl, const char *path)
67 : {
68 : static bool first_entry = true;
69 : char *store_path;
70 :
71 0 : assert(path != NULL);
72 :
73 0 : store_path = strdup(path);
74 :
75 0 : if (!quiet) {
76 0 : if (first_entry) {
77 0 : first_entry = false;
78 0 : printf("The following package files will be deleted:"
79 : "\n");
80 : }
81 0 : printf("\t%s\n", store_path);
82 : }
83 :
84 0 : kv_push(char *, *dl, store_path);
85 :
86 0 : return (EPKG_OK);
87 : }
88 :
89 : static void
90 0 : free_dellist(dl_list *dl)
91 : {
92 : unsigned int i;
93 :
94 0 : for (i = 0; i < kv_size(*dl); i++)
95 0 : free(kv_A(*dl, i));
96 0 : kv_destroy(*dl);
97 0 : }
98 :
99 : static int
100 0 : delete_dellist(dl_list *dl, int total)
101 : {
102 0 : int retcode = EX_OK;
103 0 : unsigned int count = 0, processed = 0;
104 : char *file;
105 :
106 0 : count = kv_size(*dl);
107 0 : progressbar_start("Deleting files");
108 0 : while (kv_size(*dl) > 0) {
109 0 : file = kv_pop(*dl);
110 0 : if (unlink(file) != 0) {
111 0 : warn("unlink(%s)", file);
112 0 : retcode = EX_SOFTWARE;
113 : }
114 0 : free(file);
115 0 : ++processed;
116 0 : progressbar_tick(processed, total);
117 : }
118 0 : progressbar_tick(processed, total);
119 :
120 0 : if (!quiet) {
121 0 : if (retcode == EX_OK)
122 0 : printf("All done\n");
123 : else
124 0 : printf("%d package%s could not be deleted\n",
125 : count, count > 1 ? "s" : "");
126 : }
127 0 : return (retcode);
128 : }
129 :
130 : /*
131 : * Extract hash from filename in format <name>-<version>-<hash>.txz
132 : */
133 : static bool
134 0 : extract_filename_sum(const char *fname, char sum[])
135 : {
136 : const char *dash_pos, *dot_pos;
137 :
138 0 : dot_pos = strrchr(fname, '.');
139 0 : if (dot_pos == NULL)
140 0 : dot_pos = fname + strlen(fname);
141 :
142 0 : dash_pos = strrchr(fname, '-');
143 0 : if (dash_pos == NULL)
144 0 : return (false);
145 0 : else if (dot_pos < dash_pos)
146 0 : dot_pos = fname + strlen(fname);
147 :
148 0 : if (dot_pos - dash_pos != PKG_FILE_CKSUM_CHARS + 1)
149 0 : return (false);
150 :
151 0 : strlcpy(sum, dash_pos + 1, PKG_FILE_CKSUM_CHARS + 1);
152 0 : return (true);
153 : }
154 :
155 : void
156 0 : usage_clean(void)
157 : {
158 0 : fprintf(stderr, "Usage: pkg clean [-anqy]\n\n");
159 0 : fprintf(stderr, "For more information see 'pkg help clean'.\n");
160 0 : }
161 :
162 : int
163 0 : exec_clean(int argc, char **argv)
164 : {
165 0 : struct pkgdb *db = NULL;
166 0 : struct pkgdb_it *it = NULL;
167 0 : struct pkg *p = NULL;
168 0 : kh_sum_t *sumlist = NULL;
169 0 : FTS *fts = NULL;
170 0 : FTSENT *ent = NULL;
171 : dl_list dl;
172 : const char *cachedir, *sum, *name;
173 : char *paths[2], csum[PKG_FILE_CKSUM_CHARS + 1],
174 : link_buf[MAXPATHLEN];
175 0 : bool all = false;
176 : int retcode, ret;
177 0 : int ch, cnt = 0;
178 0 : size_t total = 0, slen;
179 : ssize_t link_len;
180 : char size[8];
181 : char *cksum;
182 : khint_t k;
183 0 : struct pkg_manifest_key *keys = NULL;
184 :
185 0 : struct option longopts[] = {
186 : { "all", no_argument, NULL, 'a' },
187 : { "dry-run", no_argument, NULL, 'n' },
188 : { "quiet", no_argument, NULL, 'q' },
189 : { "yes", no_argument, NULL, 'y' },
190 : { NULL, 0, NULL, 0 },
191 : };
192 :
193 0 : while ((ch = getopt_long(argc, argv, "+anqy", longopts, NULL)) != -1) {
194 0 : switch (ch) {
195 : case 'a':
196 0 : all = true;
197 0 : break;
198 : case 'n':
199 0 : dry_run = true;
200 0 : break;
201 : case 'q':
202 0 : quiet = true;
203 0 : break;
204 : case 'y':
205 0 : yes = true;
206 0 : break;
207 : default:
208 0 : usage_clean();
209 0 : return (EX_USAGE);
210 : }
211 : }
212 0 : argc -= optind;
213 0 : argv += optind;
214 :
215 0 : cachedir = pkg_object_string(pkg_config_get("PKG_CACHEDIR"));
216 :
217 0 : paths[0] = __DECONST(char*, cachedir);
218 0 : paths[1] = NULL;
219 :
220 0 : retcode = pkgdb_access(PKGDB_MODE_READ, PKGDB_DB_REPO);
221 :
222 0 : if (retcode == EPKG_ENOACCESS) {
223 0 : warnx("Insufficient privileges to clean old packages");
224 0 : return (EX_NOPERM);
225 0 : } else if (retcode == EPKG_ENODB) {
226 0 : warnx("No package database installed. Nothing to do!");
227 0 : return (EX_OK);
228 0 : } else if (retcode != EPKG_OK) {
229 0 : warnx("Error accessing the package database");
230 0 : return (EX_SOFTWARE);
231 : }
232 :
233 0 : retcode = EX_SOFTWARE;
234 :
235 0 : if (pkgdb_open(&db, PKGDB_REMOTE) != EPKG_OK)
236 0 : return (EX_IOERR);
237 :
238 0 : if (pkgdb_obtain_lock(db, PKGDB_LOCK_READONLY) != EPKG_OK) {
239 0 : pkgdb_close(db);
240 0 : warnx("Cannot get a read lock on a database, it is locked by another process");
241 0 : return (EX_TEMPFAIL);
242 : }
243 :
244 0 : kv_init(dl);
245 0 : if ((fts = fts_open(paths, FTS_PHYSICAL, NULL)) == NULL) {
246 0 : warn("fts_open(%s)", cachedir);
247 0 : goto cleanup;
248 : }
249 :
250 : /* Build the list of out-of-date or obsolete packages */
251 :
252 0 : pkg_manifest_keys_new(&keys);
253 0 : while ((ent = fts_read(fts)) != NULL) {
254 0 : if (ent->fts_info != FTS_F && ent->fts_info != FTS_SL)
255 0 : continue;
256 :
257 0 : if (all) {
258 0 : retcode = add_to_dellist(&dl, ent->fts_path);
259 0 : if (retcode == EPKG_OK) {
260 0 : total += ent->fts_statp->st_size;
261 0 : ++cnt;
262 : }
263 0 : continue;
264 : }
265 :
266 0 : if (sumlist == NULL) {
267 0 : sumlist = kh_init_sum();
268 0 : it = pkgdb_repo_search(db, "*", MATCH_GLOB, FIELD_NAME, FIELD_NONE, NULL);
269 0 : while (pkgdb_it_next(it, &p, PKG_LOAD_BASIC) == EPKG_OK) {
270 0 : pkg_get(p, PKG_CKSUM, &sum);
271 0 : slen = MIN(strlen(sum), PKG_FILE_CKSUM_CHARS);
272 0 : cksum = strndup(sum, slen);
273 0 : k = kh_put_sum(sumlist, cksum, &ret);
274 0 : if (ret != 0)
275 0 : kh_value(sumlist, k) = cksum;
276 : }
277 : }
278 :
279 0 : if (ent->fts_info == FTS_SL) {
280 : /* Dereference the symlink and check it for being
281 : * recognized checksum file, or delete the symlink
282 : * later. */
283 0 : if ((link_len = readlink(ent->fts_name, link_buf,
284 : sizeof(link_buf))) == -1)
285 0 : continue;
286 0 : link_buf[link_len] = '\0';
287 0 : name = link_buf;
288 : } else
289 0 : name = ent->fts_name;
290 :
291 0 : k = kh_end(sumlist);
292 0 : if (extract_filename_sum(name, csum))
293 0 : k = kh_get_sum(sumlist, csum);
294 0 : if (k == kh_end(sumlist)) {
295 0 : retcode = add_to_dellist(&dl, ent->fts_path);
296 0 : if (retcode == EPKG_OK) {
297 0 : total += ent->fts_statp->st_size;
298 0 : ++cnt;
299 : }
300 0 : continue;
301 : }
302 : }
303 0 : if (sumlist != NULL) {
304 0 : kh_foreach_value(sumlist, cksum, free(cksum));
305 0 : kh_destroy_sum(sumlist);
306 : }
307 :
308 0 : if (kv_size(dl) == 0) {
309 0 : if (!quiet)
310 0 : printf("Nothing to do.\n");
311 0 : retcode = EX_OK;
312 0 : goto cleanup;
313 : }
314 :
315 0 : humanize_number(size, sizeof(size), total, "B",
316 : HN_AUTOSCALE, HN_IEC_PREFIXES);
317 :
318 0 : if (!quiet)
319 0 : printf("The cleanup will free %s\n", size);
320 0 : if (!dry_run) {
321 0 : if (query_yesno(false,
322 : "\nProceed with cleaning the cache? ")) {
323 0 : retcode = delete_dellist(&dl, cnt);
324 : }
325 : } else {
326 0 : retcode = EX_OK;
327 : }
328 :
329 : cleanup:
330 0 : pkgdb_release_lock(db, PKGDB_LOCK_READONLY);
331 0 : pkgdb_close(db);
332 0 : pkg_manifest_keys_free(keys);
333 0 : free_dellist(&dl);
334 :
335 0 : if (fts != NULL)
336 0 : fts_close(fts);
337 :
338 0 : return (retcode);
339 : }
|