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