Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/pkg
Path: blob/main/src/clean.c
2645 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
#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("Impossible 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("Impossible 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
if (link_len > 0 )
237
link_buf[link_len - 1] = '\0';
238
else
239
link_buf[0]='\0';
240
name = link_buf;
241
}
242
243
e = NULL;
244
if (extract_filename_sum(name, csum)) {
245
e = pkghash_get(*sumlist, csum);
246
}
247
if (e == NULL) {
248
added++;
249
*total += add_to_dellist(fd, dl, cachedir, path);
250
}
251
}
252
closedir(d);
253
return (nbfiles - added);
254
}
255
256
void
257
usage_clean(void)
258
{
259
fprintf(stderr, "Usage: pkg clean [-anqy]\n\n");
260
fprintf(stderr, "For more information see 'pkg help clean'.\n");
261
}
262
263
int
264
exec_clean(int argc, char **argv)
265
{
266
struct pkgdb *db = NULL;
267
pkghash *sumlist = NULL;
268
charv_t dl = vec_init();
269
const char *cachedir;
270
bool all = false;
271
int retcode;
272
int ch;
273
int cachefd = -1;
274
size_t total = 0;
275
char size[8];
276
#ifdef HAVE_CAPSICUM
277
cap_rights_t rights;
278
#endif
279
280
struct option longopts[] = {
281
{ "all", no_argument, NULL, 'a' },
282
{ "dry-run", no_argument, NULL, 'n' },
283
{ "quiet", no_argument, NULL, 'q' },
284
{ "yes", no_argument, NULL, 'y' },
285
{ NULL, 0, NULL, 0 },
286
};
287
288
while ((ch = getopt_long(argc, argv, "+anqy", longopts, NULL)) != -1) {
289
switch (ch) {
290
case 'a':
291
all = true;
292
break;
293
case 'n':
294
dry_run = true;
295
break;
296
case 'q':
297
quiet = true;
298
break;
299
case 'y':
300
yes = true;
301
break;
302
default:
303
usage_clean();
304
return (EXIT_FAILURE);
305
}
306
}
307
308
cachedir = pkg_get_cachedir();
309
cachefd = pkg_get_cachedirfd();
310
if (cachefd == -1) {
311
warn("Impossible to open %s", cachedir);
312
return (errno == ENOENT ? EXIT_SUCCESS : EXIT_FAILURE);
313
}
314
315
retcode = pkgdb_access(PKGDB_MODE_READ, PKGDB_DB_REPO);
316
317
if (retcode == EPKG_ENOACCESS) {
318
warnx("Insufficient privileges to clean old packages");
319
close(cachefd);
320
return (EXIT_FAILURE);
321
} else if (retcode == EPKG_ENODB) {
322
warnx("No package database installed. Nothing to do!");
323
close(cachefd);
324
return (EXIT_SUCCESS);
325
} else if (retcode != EPKG_OK) {
326
warnx("Error accessing the package database");
327
close(cachefd);
328
return (EXIT_FAILURE);
329
}
330
331
retcode = EXIT_FAILURE;
332
333
if (pkgdb_open(&db, PKGDB_REMOTE) != EPKG_OK) {
334
close(cachefd);
335
return (EXIT_FAILURE);
336
}
337
338
if (pkgdb_obtain_lock(db, PKGDB_LOCK_READONLY) != EPKG_OK) {
339
pkgdb_close(db);
340
close(cachefd);
341
warnx("Cannot get a read lock on a database, it is locked by "
342
"another process");
343
return (EXIT_FAILURE);
344
}
345
346
#ifdef HAVE_CAPSICUM
347
cap_rights_init(&rights, CAP_READ, CAP_LOOKUP, CAP_FSTATFS,
348
CAP_FSTAT, CAP_UNLINKAT);
349
if (cap_rights_limit(cachefd, &rights) < 0 && errno != ENOSYS ) {
350
warn("cap_rights_limit() failed");
351
close(cachefd);
352
return (EXIT_FAILURE);
353
}
354
355
#ifndef COVERAGE
356
if (cap_enter() < 0 && errno != ENOSYS) {
357
warn("cap_enter() failed");
358
close(cachefd);
359
return (EXIT_FAILURE);
360
}
361
#endif
362
#endif
363
364
/* Build the list of out-of-date or obsolete packages */
365
366
recursive_analysis(cachefd, db, cachedir, cachedir, &dl, &sumlist, all,
367
&total);
368
pkghash_destroy(sumlist);
369
370
if (dl.len == 0) {
371
if (!quiet)
372
printf("Nothing to do.\n");
373
retcode = EXIT_SUCCESS;
374
goto cleanup;
375
}
376
377
humanize_number(size, sizeof(size), total, "B",
378
HN_AUTOSCALE, HN_IEC_PREFIXES);
379
380
if (!quiet)
381
printf("The cleanup will free %s\n", size);
382
if (!dry_run) {
383
if (query_yesno(false,
384
"\nProceed with cleaning the cache? ")) {
385
retcode = delete_dellist(cachefd, cachedir, &dl);
386
}
387
} else {
388
retcode = EXIT_SUCCESS;
389
}
390
391
cleanup:
392
pkgdb_release_lock(db, PKGDB_LOCK_READONLY);
393
pkgdb_close(db);
394
vec_free_and_free(&dl, free);
395
396
if (cachefd != -1)
397
close(cachefd);
398
399
return (retcode);
400
}
401
402