/* Copyright (c) 2010-2014 Dave Reisner * * expac.c * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include "expac.h" #include "conf.h" #include "util.h" #define DEFAULT_DELIM "\n" #define DEFAULT_LISTDELIM " " #define DEFAULT_TIMEFMT "%c" #define FORMAT_TOKENS "BCDEGLMNOPRSVabdhmnprsuvw%" #define SIZE_TOKENS "BKMGTPEZY\0" #ifndef PATH_MAX #define PATH_MAX 4096 #endif static char const digits[] = "0123456789"; static char const printf_flags[] = "'-+ #0I"; alpm_list_t *targets = NULL; bool opt_readone = false; bool opt_verbose = false; char opt_humansize = 'B'; PackageCorpus opt_corpus = CORPUS_LOCAL; SearchWhat opt_what = 0; const char *opt_format = NULL; const char *opt_timefmt = DEFAULT_TIMEFMT; const char *opt_listdelim = DEFAULT_LISTDELIM; const char *opt_delim = DEFAULT_DELIM; const char *opt_config_file = "/etc/pacman.conf"; int opt_pkgcounter = 0; typedef const char *(*extractfn)(void*); static int is_valid_size_unit(char *u) { return u[0] != '\0' && u[1] == '\0' && memchr(SIZE_TOKENS, *u, strlen(SIZE_TOKENS)) != NULL; } static const char *alpm_backup_get_name(alpm_backup_t *bkup) { return bkup->name; } static double humanize_size(off_t bytes, const char target_unit, const char **label) { static const char *labels[] = {"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"}; static const int unitcount = sizeof(labels) / sizeof(labels[0]); double val = (double)bytes; int index; for(index = 0; index < unitcount - 1; index++) { if(target_unit != '\0' && labels[index][0] == target_unit) { break; } else if(target_unit == '\0' && val <= 2048.0 && val >= -2048.0) { break; } val /= 1024.0; } if(label) { *label = labels[index]; } return val; } static char *size_to_string(off_t pkgsize) { static char out[64]; if(opt_humansize == 'B') { snprintf(out, sizeof(out), "%jd", (intmax_t)pkgsize); } else { snprintf(out, sizeof(out), "%.2f %ciB", humanize_size(pkgsize, opt_humansize, NULL), opt_humansize); } return out; } static char *format_optdep(alpm_depend_t *optdep) { char *out; if (asprintf(&out, "%s: %s", optdep->name, optdep->desc) < 0) { return NULL; } return out; } static const char *alpm_dep_get_name(void *dep) { return ((alpm_depend_t*)dep)->name; } static void usage(void) { fprintf(stderr, "expac %s\n" "Usage: expac [options] target...\n\n", VERSION); fprintf(stderr, " Options:\n" " -Q, --local search local DB (default)\n" " -S, --sync search sync DBs\n" " -s, --search search for matching regex\n" " -g, --group return packages matching targets as groups\n" " -H, --humansize format package sizes in SI units (default: bytes)\n" " -1, --readone return only the first result of a sync search\n\n" " -d, --delim separator used between packages (default: \"\\n\")\n" " -l, --listdelim separator used between list elements (default: \" \")\n" " -p, --file query local files instead of the DB\n" " -t, --timefmt date format passed to strftime (default: \"%%c\")\n\n" " -v, --verbose be more verbose\n\n" " -h, --help display this help and exit\n\n" "For more details see expac(1).\n"); } static int parse_options(int argc, char *argv[]) { static struct option opts[] = { {"readone", no_argument, 0, '1'}, {"delim", required_argument, 0, 'd'}, {"listdelim", required_argument, 0, 'l'}, {"group", required_argument, 0, 'g'}, {"help", no_argument, 0, 'h'}, {"file", no_argument, 0, 'p'}, {"humansize", required_argument, 0, 'H'}, {"query", no_argument, 0, 'Q'}, {"sync", no_argument, 0, 'S'}, {"search", no_argument, 0, 's'}, {"timefmt", required_argument, 0, 't'}, {"verbose", no_argument, 0, 'v'}, {0, 0, 0, 0} }; for (;;) { int opt; opt = getopt_long(argc, argv, "1l:d:gH:hf:pQSst:v", opts, NULL); if (opt < 0) break; switch (opt) { case 'S': opt_corpus = CORPUS_SYNC; break; case 'Q': opt_corpus = CORPUS_LOCAL; break; case '1': opt_readone = true; break; case 'd': opt_delim = optarg; break; case 'g': opt_what |= SEARCH_GROUPS; break; case 'l': opt_listdelim = optarg; break; case 'H': if (!is_valid_size_unit(optarg)) { fprintf(stderr, "error: invalid SI size formatter: %s\n", optarg); return 1; } opt_humansize = *optarg; break; case 'h': usage(); return 1; case 'p': opt_corpus = CORPUS_FILE; break; case 's': opt_what |= SEARCH_REGEX; break; case 't': opt_timefmt = optarg; break; case 'v': opt_verbose = true; break; case '?': return -EINVAL; default: return -EINVAL; } } if (optind < argc) opt_format = argv[optind++]; else { fprintf(stderr, "error: missing format string (use -h for help)\n"); return -EINVAL; } while (optind < argc) targets = alpm_list_add(targets, argv[optind++]); return 0; } static int print_escaped(const char *delim) { const char *f; int out = 0; for (f = delim; *f != '\0'; f++) { if (*f == '\\') { switch (*++f) { case '\\': fputc('\\', stdout); break; case '"': fputc('\"', stdout); break; case 'a': fputc('\a', stdout); break; case 'b': fputc('\b', stdout); break; case 'e': /* \e is nonstandard */ fputc('\033', stdout); break; case 'n': fputc('\n', stdout); break; case 'r': fputc('\r', stdout); break; case 't': fputc('\t', stdout); break; case 'v': fputc('\v', stdout); break; case '0': fputc('\0', stdout); break; ++out; } } else { fputc(*f, stdout); ++out; } } return out; } static int print_list(alpm_list_t *list, extractfn fn) { alpm_list_t *i; int out = 0; if (!list) { if (opt_verbose) { out += printf("None"); } return out; } i = list; while (1) { char *item = (char*)(fn ? fn(i->data) : i->data); if (item == NULL) { continue; } out += printf("%s", item); if ((i = alpm_list_next(i))) { out += print_escaped(opt_listdelim); } else { break; } } return out; } static int print_allocated_list(alpm_list_t *list, extractfn fn) { int out = print_list(list, fn); alpm_list_free(list); return out; } static int print_time(time_t timestamp) { char buffer[64]; int out = 0; if (!timestamp) { if (opt_verbose) out += printf("None"); return out; } /* no overflow here, strftime prints a max of 64 including null */ strftime(&buffer[0], 64, opt_timefmt, localtime(×tamp)); out += printf("%s", buffer); return out; } static int print_filelist(alpm_filelist_t *filelist) { int out = 0; size_t i; for (i = 0; i < filelist->count; i++) { out += printf("%s", (filelist->files + i)->name); out += print_escaped(opt_listdelim); } return out; } static bool backup_file_is_modified(const alpm_backup_t *backup_file) { char fullpath[PATH_MAX]; _cleanup_free_ char *md5sum = NULL; bool modified; /* TODO: respect expac->dbroot */ snprintf(fullpath, sizeof(fullpath), "/%s", backup_file->name); md5sum = alpm_compute_md5sum(fullpath); if(md5sum == NULL) { return false; } modified = strcmp(md5sum, backup_file->hash) != 0; return modified; } static alpm_list_t *get_modified_files(alpm_pkg_t *pkg) { alpm_list_t *i, *modified_files = NULL; for(i = alpm_pkg_get_backup(pkg); i; i = i->next) { const alpm_backup_t *backup = i->data; if(backup->hash && backup_file_is_modified(backup)) { modified_files = alpm_list_add(modified_files, backup->name); } } return modified_files; } static alpm_list_t *get_validation_method(alpm_pkg_t *pkg) { alpm_list_t *validation = NULL; alpm_pkgvalidation_t v = alpm_pkg_get_validation(pkg); if(v == ALPM_PKG_VALIDATION_UNKNOWN) { return alpm_list_add(validation, "Unknown"); } if(v & ALPM_PKG_VALIDATION_NONE) { return alpm_list_add(validation, "None"); } if(v & ALPM_PKG_VALIDATION_MD5SUM) { validation = alpm_list_add(validation, "MD5 Sum"); } if(v & ALPM_PKG_VALIDATION_SHA256SUM) { validation = alpm_list_add(validation, "SHA256 Sum"); } if(v & ALPM_PKG_VALIDATION_SIGNATURE) { validation = alpm_list_add(validation, "Signature"); } return validation; } static int print_pkg(alpm_pkg_t *pkg, const char *format) { const char *f, *end; char fmt[64], buf[64]; int len, out = 0; end = format + strlen(format); for (f = format; f < end; f++) { len = 0; if (*f == '%') { len = strspn(f + 1 + len, printf_flags); len += strspn(f + 1 + len, digits); snprintf(fmt, len + 3, "%ss", f); fmt[len + 1] = 's'; f += len + 1; switch (*f) { /* simple attributes */ case 'f': /* filename */ out += printf(fmt, alpm_pkg_get_filename(pkg)); break; case 'n': /* package name */ out += printf(fmt, alpm_pkg_get_name(pkg)); break; case 'v': /* version */ out += printf(fmt, alpm_pkg_get_version(pkg)); break; case 'd': /* description */ out += printf(fmt, alpm_pkg_get_desc(pkg)); break; case 'u': /* project url */ out += printf(fmt, alpm_pkg_get_url(pkg)); break; case 'p': /* packager name */ out += printf(fmt, alpm_pkg_get_packager(pkg)); break; case 's': /* md5sum */ out += printf(fmt, alpm_pkg_get_md5sum(pkg)); break; case 'a': /* architecutre */ out += printf(fmt, alpm_pkg_get_arch(pkg)); break; case 'i': /* has install scriptlet? */ out += printf(fmt, alpm_pkg_has_scriptlet(pkg) ? "yes" : "no"); break; case 'r': /* repo */ out += printf(fmt, alpm_db_get_name(alpm_pkg_get_db(pkg))); break; case 'w': /* install reason */ out += printf(fmt, alpm_pkg_get_reason(pkg) ? "dependency" : "explicit"); break; case '!': /* result number */ snprintf(buf, sizeof(buf), "%d", opt_pkgcounter++); out += printf(fmt, buf); break; case 'g': /* base64 gpg sig */ out += printf(fmt, alpm_pkg_get_base64_sig(pkg)); break; case 'h': /* sha256sum */ out += printf(fmt, alpm_pkg_get_sha256sum(pkg)); break; /* times */ case 'b': /* build date */ out += print_time(alpm_pkg_get_builddate(pkg)); break; case 'l': /* install date */ out += print_time(alpm_pkg_get_installdate(pkg)); break; /* sizes */ case 'k': /* download size */ out += printf(fmt, size_to_string(alpm_pkg_get_size(pkg))); break; case 'm': /* install size */ out += printf(fmt, size_to_string(alpm_pkg_get_isize(pkg))); break; /* lists */ case 'F': /* files */ out += print_filelist(alpm_pkg_get_files(pkg)); break; case 'N': /* requiredby */ out += print_list(alpm_pkg_compute_requiredby(pkg), NULL); break; case 'L': /* licenses */ out += print_list(alpm_pkg_get_licenses(pkg), NULL); break; case 'G': /* groups */ out += print_list(alpm_pkg_get_groups(pkg), NULL); break; case 'E': /* depends (shortdeps) */ out += print_list(alpm_pkg_get_depends(pkg), (extractfn)alpm_dep_get_name); break; case 'D': /* depends */ out += print_list(alpm_pkg_get_depends(pkg), (extractfn)alpm_dep_compute_string); break; case 'O': /* optdepends */ out += print_list(alpm_pkg_get_optdepends(pkg), (extractfn)format_optdep); break; case 'o': /* optdepends (shortdeps) */ out += print_list(alpm_pkg_get_optdepends(pkg), (extractfn)alpm_dep_get_name); break; case 'C': /* conflicts */ out += print_list(alpm_pkg_get_conflicts(pkg), (extractfn)alpm_dep_get_name); break; case 'S': /* provides (shortdeps) */ out += print_list(alpm_pkg_get_provides(pkg), (extractfn)alpm_dep_get_name); break; case 'P': /* provides */ out += print_list(alpm_pkg_get_provides(pkg), (extractfn)alpm_dep_compute_string); break; case 'R': /* replaces */ out += print_list(alpm_pkg_get_replaces(pkg), (extractfn)alpm_dep_get_name); break; case 'B': /* backup */ out += print_list(alpm_pkg_get_backup(pkg), (extractfn)alpm_backup_get_name); break; case 'V': /* package validation */ out += print_allocated_list(get_validation_method(pkg), NULL); break; case 'M': /* modified */ out += print_allocated_list(get_modified_files(pkg), NULL); break; case '%': fputc('%', stdout); out++; break; default: fputc('?', stdout); out++; break; } } else if (*f == '\\') { char esc[3] = { f[0], f[1], '\0' }; out += print_escaped(esc); ++f; } else { fputc(*f, stdout); out++; } } /* only print a delimeter if any package data was outputted */ if (out > 0) print_escaped(opt_delim); return !out; } static alpm_list_t *all_packages(alpm_list_t *dbs) { alpm_list_t *i, *packages = NULL; for (i = dbs; i; i = i->next) { packages = alpm_list_join(packages, alpm_list_copy(alpm_db_get_pkgcache(i->data))); } return packages; } static alpm_list_t *search_packages(alpm_list_t *dbs, alpm_list_t *targets) { alpm_list_t *i, *packages = NULL; for (i = dbs; i; i = i->next) { packages = alpm_list_join(packages, alpm_db_search(i->data, targets)); } return packages; } static alpm_list_t *search_groups(alpm_list_t *dbs, alpm_list_t *groupnames) { alpm_list_t *i, *j, *packages = NULL; for (i = groupnames; i; i = i->next) { for (j = dbs; j; j = j->next) { alpm_group_t *grp = alpm_db_get_group(j->data, i->data); if (grp != NULL) { packages = alpm_list_join(packages, alpm_list_copy(grp->packages)); } } } return packages; } static alpm_list_t *search_exact(alpm_list_t *dblist, alpm_list_t *targets) { char *pkgname, *reponame; alpm_list_t *results = NULL; alpm_list_t *t; /* resolve each target individually from the repo pool */ for (t = targets; t; t = t->next) { alpm_pkg_t *pkg = NULL; alpm_list_t *r; int found = 0; pkgname = reponame = t->data; if (strchr(pkgname, '/')) strsep(&pkgname, "/"); else reponame = NULL; for (r = dblist; r; r = r->next) { alpm_db_t *repo = r->data; if (reponame && strcmp(reponame, alpm_db_get_name(repo)) != 0) continue; pkg = alpm_db_get_pkg(repo, pkgname); if (pkg == NULL) continue; found = 1; results = alpm_list_add(results, pkg); if (opt_readone) break; } if (!found && opt_verbose) fprintf(stderr, "error: package `%s' not found\n", pkgname); } return results; } static alpm_list_t *resolve_targets(alpm_list_t *dblist, alpm_list_t *targets) { if (targets == NULL) return all_packages(dblist); /* no method specified */ if ((opt_what & (_SEARCH_MAX - 1)) == 0) return search_exact(dblist, targets); if (opt_what & SEARCH_REGEX) return search_packages(dblist, targets); if (opt_what & SEARCH_GROUPS) return search_groups(dblist, targets); return NULL; } void expac_free(Expac *expac) { if (expac == NULL) return; alpm_release(expac->alpm); } int expac_new(Expac **expac, int argc, char **argv) { Expac *e; enum _alpm_errno_t alpm_errno = 0; config_t config = { NULL, 0, 0, NULL, NULL }; const char *dbroot = "/"; const char *dbpath = "/var/lib/pacman"; int r; r = parse_options(argc, argv); if (r < 0) return r; e = calloc(1, sizeof(*e)); if (e == NULL) return -ENOMEM; r = config_parse(&config, opt_config_file); if (r < 0) return r; if (config.dbpath) dbpath = config.dbpath; if (config.dbroot) dbroot = config.dbroot; e->alpm = alpm_initialize(dbroot, dbpath, &alpm_errno); if (!e->alpm) return -alpm_errno; e->db_local = alpm_get_localdb(e->alpm); for (int i = 0; i < config.size; ++i) alpm_register_syncdb(e->alpm, config.repos[i], 0); config_reset(&config); *expac = e; return 0; } alpm_list_t *expac_search_files(Expac *expac, alpm_list_t *targets) { alpm_list_t *i, *r = NULL; for (i = targets; i; i = i->next) { const char *path = i->data; alpm_pkg_t *pkg; if (alpm_pkg_load(expac->alpm, path, 0, 0, &pkg) != 0) { fprintf(stderr, "error: %s: %s\n", path, alpm_strerror(alpm_errno(expac->alpm))); continue; } r = alpm_list_add(r, pkg); } return r; } alpm_list_t *expac_search_local(Expac *expac, alpm_list_t *targets) { alpm_list_t *dblist, *r; dblist = alpm_list_add(NULL, alpm_get_localdb(expac->alpm)); r = resolve_targets(alpm_list_add(NULL, alpm_get_localdb(expac->alpm)), targets); alpm_list_free(dblist); return r; } alpm_list_t *expac_search_sync(Expac *expac, alpm_list_t *targets) { return resolve_targets(alpm_get_syncdbs(expac->alpm), targets); } alpm_list_t *expac_search(Expac *expac, PackageCorpus corpus, alpm_list_t *targets) { switch (corpus) { case CORPUS_LOCAL: return expac_search_local(expac, targets); case CORPUS_SYNC: return expac_search_sync(expac, targets); case CORPUS_FILE: return expac_search_files(expac, targets); } /* should be unreachable */ return NULL; } int main(int argc, char *argv[]) { alpm_list_t *results = NULL; Expac *expac; int r; r = expac_new(&expac, argc, argv); if (r < 0) return 1; results = expac_search(expac, opt_corpus, targets); if (results == NULL) return 1; for (alpm_list_t *i = results; i; i = i->next) print_pkg(i->data, opt_format); alpm_list_free(results); expac_free(expac); return 0; } /* vim: set et ts=2 sw=2: */