summarylogtreecommitdiffstats
path: root/pkgcacheclean.c
diff options
context:
space:
mode:
Diffstat (limited to 'pkgcacheclean.c')
-rw-r--r--pkgcacheclean.c300
1 files changed, 300 insertions, 0 deletions
diff --git a/pkgcacheclean.c b/pkgcacheclean.c
new file mode 100644
index 000000000000..90c3d79de595
--- /dev/null
+++ b/pkgcacheclean.c
@@ -0,0 +1,300 @@
+#include <argp.h>
+#include <dirent.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <regex.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <alpm.h>
+
+#define ROOT "/"
+#define DBPATH "/var/lib/pacman/"
+#define CACHEDIR "/var/cache/pacman/pkg/"
+
+const char *argp_program_version = "pkgcacheclean "VERSION;
+const char *argp_program_bug_address = "auguste@gmail.com";
+
+static char doc[] =
+ "pkgcacheclean -- a simple utility to clean pacman cache.\v"
+ "For installed packages, preserve_number of versions are reserved. This\n"
+ "includes the current version and the newest (preserve_number - 1) of\n"
+ "the remaining. For uninstalled packages all versions are deleted.\n"
+ "The default number is 2.";
+static char args_doc[] = "[preserve_number]";
+
+static struct argp_option options[] =
+{
+ { .doc = "" },
+ { .name = "dry-run", .key = 'n', .doc = "Perform a trial run with no changes made" },
+ { .name = "cachedir", .key = 'd', .arg = "PATH",
+ .doc = "Set alternative (absolute) cache directory PATH. Default is "CACHEDIR },
+ { .name = "all-as-installed", .key = 'k',
+ .doc = "Treat not-installed packages as installed" },
+ { .name = "verbose", .key = 'v', .doc = "Verbose output" },
+ { .name = "quiet", .key = 'q', .doc = "Suppress output, default" },
+ { .doc = NULL }
+};
+
+static regex_t pkgnamesplit;
+static regex_t pkgnametest;
+
+struct pkginfo
+{
+ char *name;
+ char *version;
+ char *filename;
+};
+
+struct arguments
+{
+ int dry_run;
+ int preserve;
+ int keep;
+ int verbose;
+ char *cachedir;
+};
+
+static char *dupsubstr(const char *str, const int start, const int end)
+{
+ char *ret;
+ const size_t len = (size_t)(end - start);
+
+ ret = (char *)malloc(sizeof(char) * (len + 1));
+ strncpy(ret, str + start, len);
+ ret[len] = '\0';
+
+ return ret;
+}
+
+static struct pkginfo * get_pkginfo_from_filename(const char * const filename)
+{
+ struct pkginfo *ret;
+ static regmatch_t match[3];
+
+ ret = (struct pkginfo *)malloc(sizeof(struct pkginfo));
+ regexec(&pkgnamesplit, filename, 3, match, 0);
+ ret->name = dupsubstr(filename, match[1].rm_so, match[1].rm_eo);
+ ret->version = dupsubstr(filename, match[2].rm_so, match[2].rm_eo);
+ ret->filename = strdup(filename);
+
+ return ret;
+}
+
+static struct pkginfo * get_pkginfo_from_pmpkg(alpm_pkg_t *pmpkg)
+{
+ struct pkginfo *ret;
+
+ ret = (struct pkginfo *)malloc(sizeof(struct pkginfo));
+ ret->name = strdup(alpm_pkg_get_name(pmpkg));
+ ret->version = strdup(alpm_pkg_get_version(pmpkg));
+ ret->filename = NULL;
+
+ return ret;
+}
+
+static void free_pkginfo(struct pkginfo * pkg)
+{
+ free(pkg->name);
+ free(pkg->version);
+ free(pkg->filename);
+ free(pkg);
+}
+
+static int pkgcomp(const void *a, const void *b)
+{
+ struct pkginfo *ap = *(struct pkginfo **)a;
+ struct pkginfo *bp = *(struct pkginfo **)b;
+
+ int namecomp = strcmp(ap->name, bp->name);
+
+ return namecomp ? namecomp : -alpm_pkg_vercmp(ap->version,
+ bp->version);
+}
+
+static int pkgnamecomp(const void *a, const void *b)
+{
+ return strcmp((*(struct pkginfo **)a)->name,
+ (*(struct pkginfo **)b)->name);
+}
+
+static int ispackage(const struct dirent *file)
+{
+ return regexec(&pkgnametest, file->d_name, 0, NULL, 0) == 0;
+}
+
+static void free_pkginfo_array(struct pkginfo **pkgs, const size_t n)
+{
+ size_t i;
+
+ for (i = 0; i < n; i++)
+ free_pkginfo(pkgs[i]);
+ free(pkgs);
+}
+
+static off_t get_file_size(const char *filename)
+{
+ struct stat st;
+
+ stat(filename, &st);
+ return st.st_size;
+}
+
+static error_t parse_opt(int key, char *arg, struct argp_state *state)
+{
+ struct arguments *argument = (struct arguments *)(state -> input);
+
+ switch (key)
+ {
+ case 'n':
+ argument->dry_run = 1;
+ break;
+ case 'v':
+ argument->verbose = 1;
+ break;
+ case 'k':
+ argument->keep = 1;
+ case 'q':
+ argument->verbose = 0;
+ break;
+ case 'd':
+ argument->cachedir = arg;
+ break;
+ case ARGP_KEY_ARG:
+ if (argument->preserve)
+ return ARGP_ERR_UNKNOWN;
+ argument->preserve = atoi(arg);
+ if (argument->preserve <= 0)
+ return ARGP_ERR_UNKNOWN;
+ break;
+ default:
+ return ARGP_ERR_UNKNOWN;
+ }
+
+ return 0;
+}
+
+int main(const int argc, char ** __restrict__ argv)
+{
+ int count = 0;
+ size_t i, len, m = 0, n;
+ off_t total_size = 0;
+ alpm_handle_t *handle;
+ struct stat st;
+ struct dirent **dir;
+ enum _alpm_errno_t error;
+ struct pkginfo **cachepkg, **localpkg = NULL;
+ struct pkginfo **hit = NULL;
+ const char *current = "", *name;
+ char cachedir[PATH_MAX] = CACHEDIR;
+ struct argp arg_parser = { .options = options, .parser = parse_opt,
+ .args_doc = args_doc, .doc = doc };
+ struct arguments args = { .dry_run = 0, .preserve = 0, .keep = 0,
+ .verbose = 0, .cachedir = NULL };
+
+ argp_parse(&arg_parser, argc, argv, 0, NULL, &args);
+ if (!args.preserve)
+ args.preserve = 2;
+
+ if (!args.dry_run && getuid())
+ {
+ puts("please run as root.");
+ exit(EXIT_FAILURE);
+ }
+
+ if (args.cachedir)
+ strcpy(cachedir, args.cachedir);
+ if (!((stat(cachedir, &st) == 0) && ((st.st_mode & S_IFMT) == S_IFDIR)))
+ {
+ printf("Cache directory does not exist or is not a valid directory: "
+ "\"%s\".\n", cachedir);
+ exit(EXIT_FAILURE);
+ }
+ len = strlen(cachedir);
+ if (len && cachedir[len - 1] != '/')
+ {
+ cachedir[len] = '/';
+ cachedir[++len] = '\0';
+ }
+ regcomp(&pkgnamesplit, "^(.*)-([^-]*-[^-]*)-[^-]*$", REG_EXTENDED);
+ regcomp(&pkgnametest, "^.*-("CARCH"|any).[^-]*$", REG_EXTENDED);
+
+ handle = alpm_initialize(ROOT, DBPATH, &error);
+
+ n = (size_t)scandir(cachedir, &dir, ispackage, NULL);
+ cachepkg = (struct pkginfo **)malloc(sizeof(struct pkginfo *) * n);
+ for (i = 0; i < n; free(dir[i]), i++)
+ cachepkg[i] = get_pkginfo_from_filename(dir[i]->d_name);
+ free(dir);
+ qsort(cachepkg, n, sizeof(struct pkginfo *), pkgcomp);
+
+ if (!args.keep)
+ {
+ alpm_db_t *db;
+ alpm_list_t *pkglist;
+
+ db = alpm_get_localdb(handle);
+ pkglist = alpm_db_get_pkgcache(db);
+ m = alpm_list_count(pkglist);
+ localpkg = (struct pkginfo **)malloc(sizeof(struct pkginfo *) * m);
+ for (i = 0; i < m; i++, pkglist = alpm_list_next(pkglist))
+ localpkg[i] = get_pkginfo_from_pmpkg((alpm_pkg_t *)(pkglist->data));
+ qsort(localpkg, m, sizeof(struct pkginfo *), pkgnamecomp);
+ }
+
+ for (i = 0; i < n; i++)
+ {
+ name = cachepkg[i]->name;
+ if (strcmp(name, current))
+ {
+ current = name;
+ if (args.keep)
+ {
+ int j;
+ for (j = (int)i - 1; j >= 0; j--)
+ if (pkgnamecomp(cachepkg + i, cachepkg + j))
+ break;
+ hit = cachepkg + j + 1;
+ count = 0;
+ }
+ else
+ {
+ hit = (struct pkginfo **)bsearch(cachepkg + i, localpkg, m,
+ sizeof(struct pkginfo *), pkgnamecomp);
+ count = hit ? 0 : args.preserve;
+ }
+ }
+ if (!hit || alpm_pkg_vercmp((*hit)->version,
+ cachepkg[i]->version))
+ {
+ count++;
+ if (count >= args.preserve)
+ {
+ strcpy(cachedir + len, cachepkg[i]->filename);
+ if (args.verbose)
+ {
+ printf("remove: %s\n", cachepkg[i]->filename);
+ total_size += get_file_size(cachedir);
+ }
+ if (!args.dry_run)
+ unlink(cachedir);
+ }
+ }
+ }
+
+ if (args.verbose)
+ printf("\ntotal: %"PRIuMAX" bytes\n", (uintmax_t)total_size);
+
+ free_pkginfo_array(cachepkg, n);
+ if (!args.keep)
+ free_pkginfo_array(localpkg, m);
+
+ alpm_release(handle);
+ regfree(&pkgnametest);
+ regfree(&pkgnamesplit);
+
+ return EXIT_SUCCESS;
+}