updated on Wed Jan 18 04:00:29 UTC 2012
[aur-mirror.git] / pkgcacheclean / pkgcacheclean.c
blob13b0bf0eb36dc454afc96e9816711d9efaead234
1 #include <argp.h>
2 #include <dirent.h>
3 #include <inttypes.h>
4 #include <limits.h>
5 #include <regex.h>
6 #include <stdio.h>
7 #include <stdlib.h>
8 #include <string.h>
9 #include <sys/stat.h>
10 #include <unistd.h>
12 #include <alpm.h>
14 #define ROOT "/"
15 #define DBPATH "/var/lib/pacman/"
16 #define CACHEDIR "/var/cache/pacman/pkg/"
18 const char *argp_program_version = "pkgcacheclean "VERSION;
19 const char *argp_program_bug_address = "auguste@gmail.com";
21 static char doc[] =
22 "pkgcacheclean -- a simple utility to clean pacman cache.\v"
23 "For installed packages, preserve_number of versions are reserved. This\n"
24 "includes the current version and the newest (preserve_number - 1) of\n"
25 "the remaining. For uninstalled packages all versions are deleted.\n"
26 "The default number is 2.";
27 static char args_doc[] = "[preserve_number]";
29 static struct argp_option options[] =
31 { .doc = "" },
32 { .name = "dry-run", .key = 'n', .doc = "Perform a trial run with no changes made" },
33 { .name = "verbose", .key = 'v', .doc = "Verbose output" },
34 { .name = "quiet", .key = 'q', .doc = "Suppress output, default" },
35 { .doc = NULL }
38 static regex_t pkgnamesplit;
39 static regex_t pkgnametest;
41 struct pkginfo
43 char *name;
44 char *version;
45 char *filename;
48 struct arguments
50 int dry_run;
51 int preserve;
52 int verbose;
55 static char *dupsubstr(const char *str, const int start, const int end)
57 char *ret;
58 const int len = end - start;
60 ret = malloc(sizeof(char) * (len + 1));
61 strncpy(ret, str + start, len);
62 ret[len] = '\0';
64 return ret;
67 static struct pkginfo * get_pkginfo_from_filename(const char * const filename)
69 struct pkginfo *ret;
70 static regmatch_t match[3];
72 ret = malloc(sizeof(struct pkginfo));
73 regexec(&pkgnamesplit, filename, 3, match, 0);
74 ret->name = dupsubstr(filename, match[1].rm_so, match[1].rm_eo);
75 ret->version = dupsubstr(filename, match[2].rm_so, match[2].rm_eo);
76 ret->filename = strdup(filename);
78 return ret;
81 static struct pkginfo * get_pkginfo_from_pmpkg(alpm_pkg_t *pmpkg)
83 struct pkginfo *ret;
85 ret = malloc(sizeof(struct pkginfo));
86 ret->name = strdup(alpm_pkg_get_name(pmpkg));
87 ret->version = strdup(alpm_pkg_get_version(pmpkg));
88 ret->filename = NULL;
90 return ret;
93 static void free_pkginfo(struct pkginfo * pkg)
95 free(pkg->name);
96 free(pkg->version);
97 free(pkg->filename);
98 free(pkg);
101 static int pkgcomp(const void *a, const void *b)
103 struct pkginfo *ap = *(struct pkginfo **)a;
104 struct pkginfo *bp = *(struct pkginfo **)b;
106 int namecomp = strcmp(ap->name, bp->name);
108 return namecomp ? namecomp : -alpm_pkg_vercmp(ap->version,
109 bp->version);
112 static int pkgnamecomp(const void *a, const void *b)
114 return strcmp((*(struct pkginfo **)a)->name,
115 (*(struct pkginfo **)b)->name);
118 static int ispackage(const struct dirent *file)
120 return regexec(&pkgnametest, file->d_name, 0, NULL, 0) == 0;
123 static void free_pkginfo_array(struct pkginfo **pkgs, const int n)
125 int i;
127 for (i = 0; i < n; i++)
128 free_pkginfo(pkgs[i]);
129 free(pkgs);
132 static off_t get_file_size(const char *filename)
134 struct stat st;
136 stat(filename, &st);
137 return st.st_size;
140 static error_t parse_opt(int key, char *arg, struct argp_state *state)
142 struct arguments *argument = state -> input;
144 switch (key)
146 case 'n':
147 argument->dry_run = 1;
148 break;
149 case 'v':
150 argument->verbose = 1;
151 break;
152 case 'q':
153 argument->verbose = 0;
154 break;
155 case ARGP_KEY_ARG:
156 if (argument->preserve)
157 return ARGP_ERR_UNKNOWN;
158 argument->preserve = atoi(arg);
159 if (argument->preserve <= 0)
160 return ARGP_ERR_UNKNOWN;
161 break;
162 default:
163 return ARGP_ERR_UNKNOWN;
166 return 0;
169 int main(const int argc, char ** __restrict__ argv)
171 int i, n, m, len, count = 0;
172 off_t total_size = 0;
173 alpm_handle_t *handle;
174 alpm_db_t *db;
175 alpm_list_t *pkglist;
176 struct dirent **dir;
177 enum _alpm_errno_t error;
178 struct pkginfo **cachepkg, **localpkg;
179 struct pkginfo **hit = NULL;
180 const char *current = "", *name;
181 char cachedir[PATH_MAX] = CACHEDIR;
182 struct argp arg_parser = { .options = options, .parser = parse_opt,
183 .args_doc = args_doc, .doc = doc };
184 struct arguments args = { .dry_run = 0, .preserve = 0, .verbose = 0 };
186 argp_parse(&arg_parser, argc, argv, 0, NULL, &args);
187 if (!args.preserve)
188 args.preserve = 2;
190 if (!args.dry_run && getuid())
192 puts("please run as root.");
193 exit(EXIT_FAILURE);
196 len = strlen(cachedir);
197 regcomp(&pkgnamesplit, "^(.*)-([^-]*-[^-]*)-[^-]*$", REG_EXTENDED);
198 regcomp(&pkgnametest, "^.*-("CARCH"|any).[^-]*$", REG_EXTENDED);
200 handle = alpm_initialize(ROOT, DBPATH, &error);
202 n = scandir(cachedir, &dir, ispackage, NULL);
203 cachepkg = malloc(sizeof(struct pkginfo *) * n);
204 for (i = 0; i < n; free(dir[i]), i++)
205 cachepkg[i] = get_pkginfo_from_filename(dir[i]->d_name);
206 free(dir);
207 qsort(cachepkg, n, sizeof(struct pkginfo *), pkgcomp);
209 db = alpm_option_get_localdb(handle);
210 pkglist = alpm_db_get_pkgcache(db);
211 m = alpm_list_count(pkglist);
212 localpkg = malloc(sizeof(struct pkginfo *) * m);
213 for (i = 0; i < m; i++, pkglist = alpm_list_next(pkglist))
214 localpkg[i] = get_pkginfo_from_pmpkg(alpm_list_getdata(pkglist));
215 qsort(localpkg, m, sizeof(struct pkginfo *), pkgnamecomp);
216 alpm_list_free_inner(pkglist, (alpm_list_fn_free)alpm_pkg_free);
217 alpm_list_free(pkglist);
219 for (i = 0; i < n; i++)
221 name = cachepkg[i]->name;
222 if (strcmp(name, current))
224 current = name;
225 hit = bsearch(cachepkg + i, localpkg, m,
226 sizeof(struct pkginfo *), pkgnamecomp);
227 count = hit ? 0 : args.preserve;
229 if (!hit || alpm_pkg_vercmp((*hit)->version,
230 cachepkg[i]->version))
232 count++;
233 if (count >= args.preserve)
235 strcpy(cachedir + len, cachepkg[i]->filename);
236 if (args.verbose)
238 printf("remove: %s\n", cachepkg[i]->filename);
239 total_size += get_file_size(cachedir);
241 if (!args.dry_run)
242 unlink(cachedir);
247 if (args.verbose)
248 printf("\ntotal: %"PRIuMAX" bytes\n", (uintmax_t)total_size);
250 free_pkginfo_array(cachepkg, n);
251 free_pkginfo_array(localpkg, m);
253 alpm_release(handle);
254 regfree(&pkgnametest);
255 regfree(&pkgnamesplit);
257 return EXIT_SUCCESS;