summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile26
-rw-r--r--config.mk8
-rw-r--r--expac.c490
3 files changed, 524 insertions, 0 deletions
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..393e17f
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,26 @@
+include config.mk
+
+SRC = expac.c
+OBJ = ${SRC:.c=.o}
+
+all: expac
+
+.c.o:
+ ${CC} -c ${CFLAGS} $<
+
+expac: ${OBJ}
+ ${CC} -o $@ ${OBJ} ${LDFLAGS}
+
+install: expac
+ install -D -m755 expac ${DESTDIR}${PREFIX}/bin/expac
+
+dist: clean
+ mkdir expac-${VERSION}
+ cp Makefile expac.c expac-${VERSION}
+ sed "s/^VERSION = .*/VERSION = ${VERSION}/" config.mk > expac-${VERSION}/config.mk
+ tar cf - expac-${VERSION} | gzip -9 > expac-${VERSION}.tar.gz
+ rm -rf expac-${VERSION}
+
+clean:
+ $(RM) ${OBJ} expac
+
diff --git a/config.mk b/config.mk
new file mode 100644
index 0000000..a1ed858
--- /dev/null
+++ b/config.mk
@@ -0,0 +1,8 @@
+# version
+VERSION = $(shell git describe --always)
+
+# compiler flags
+CC = c99
+CPPFLAGS = -DVERSION=\"${VERSION}\"
+CFLAGS += -g -pedantic -Wall -Wextra ${CPPFLAGS}
+LDFLAGS += -lalpm
diff --git a/expac.c b/expac.c
new file mode 100644
index 0000000..840189a
--- /dev/null
+++ b/expac.c
@@ -0,0 +1,490 @@
+#define _GNU_SOURCE
+#include <alpm.h>
+#include <ctype.h>
+#include <getopt.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+
+#define FORMAT_TOKENS "BCDEFGLNOPRSabdfiklmnoprsuv%"
+#define ESCAPE_TOKENS "\"\\abefnrtv"
+
+alpm_list_t *dblist = NULL, *targets = NULL;
+pmdb_t *db_local;
+int verbose = 0;
+const char *format = NULL;
+const char *timefmt = NULL;
+const char *listdelim = NULL;
+const char *delim = NULL;
+
+typedef const char *(*extractfn)(void*);
+
+static char *strtrim(char *str) {
+ char *pch = str;
+
+ if (!str || *str == '\0') {
+ return(str);
+ }
+
+ while (isspace((unsigned char)*pch)) {
+ pch++;
+ }
+ if (pch != str) {
+ memmove(str, pch, (strlen(pch) + 1));
+ }
+
+ if (*str == '\0') {
+ return(str);
+ }
+
+ pch = (str + (strlen(str) - 1));
+ while (isspace((unsigned char)*pch)) {
+ pch--;
+ }
+ *++pch = '\0';
+
+ return(str);
+}
+
+static int alpm_init() {
+ int ret = 0;
+ FILE *fp;
+ char line[PATH_MAX];
+ char *ptr, *section = NULL;
+
+ ret = alpm_initialize();
+ if (ret != 0) {
+ return(ret);
+ }
+
+ ret = alpm_option_set_root("/");
+ if (ret != 0) {
+ return(ret);
+ }
+
+ ret = alpm_option_set_dbpath("/var/lib/pacman");
+ if (ret != 0) {
+ return(ret);
+ }
+
+ db_local = alpm_db_register_local();
+ if (!db_local) {
+ return(1);
+ }
+
+ fp = fopen("/etc/pacman.conf", "r");
+ if (!fp) {
+ return(1);
+ }
+
+ while (fgets(line, PATH_MAX, fp)) {
+ strtrim(line);
+
+ if (strlen(line) == 0 || line[0] == '#') {
+ continue;
+ }
+ if ((ptr = strchr(line, '#'))) {
+ *ptr = '\0';
+ }
+
+ if (line[0] == '[' && line[strlen(line) - 1] == ']') {
+ ptr = &line[1];
+ if (section) {
+ free(section);
+ }
+
+ section = strdup(ptr);
+ section[strlen(section) - 1] = '\0';
+
+ if (strcmp(section, "options") != 0) {
+ if (!alpm_db_register_sync(section)) {
+ ret = 1;
+ goto finish;
+ }
+ }
+ } else {
+ char *key;
+
+ key = ptr = line;
+ strsep(&ptr, "=");
+ strtrim(key);
+ strtrim(ptr);
+ if (strcmp(key, "RootDir") == 0) {
+ alpm_option_set_root(ptr);
+ } else if (strcmp(key, "DBPath") == 0) {
+ alpm_option_set_dbpath(ptr);
+ }
+ }
+ }
+
+finish:
+ free(section);
+ fclose(fp);
+ return(ret);
+}
+
+static void usage(void) {
+ fprintf(stderr, "expac %s\n"
+ "Usage: expac [options] <format> target...\n\n", VERSION);
+ fprintf(stderr,
+ " Options:\n"
+ " -Q, --local search local DB (default)\n"
+ " -S, --sync search sync DBs\n\n"
+ " -d, --delim <string> separator used between packages (default: \"\\n\")\n"
+ " -l, --listdelim <string> separator used between list elements (default: \" \")\n"
+ " -h, --help display this help and exit\n"
+ " -t, --timefmt <fmt> date format passed to strftime (default: \"%%c\")\n"
+ " -v, --verbose show errors while printing\n\n");
+}
+
+static int parse_options(int argc, char *argv[]) {
+ int opt, option_index = 0;
+
+ static struct option opts[] = {
+ {"delim", required_argument, 0, 'd'},
+ {"listdelim", required_argument, 0, 'l'},
+ {"help", no_argument, 0, 'h'},
+ {"local", no_argument, 0, 'Q'},
+ {"sync", no_argument, 0, 'S'},
+ {"timefmt", required_argument, 0, 't'},
+ {"verbose", no_argument, 0, 'v'},
+ {0, 0, 0, 0}
+ };
+
+ while (-1 != (opt = getopt_long(argc, argv, "l:d:hf:QSt:v", opts, &option_index))) {
+ switch (opt) {
+ case 'S':
+ if (dblist) {
+ fprintf(stderr, "error: can only select one repo option (use -h for help)\n");
+ return(1);
+ }
+ dblist = alpm_option_get_syncdbs();
+ break;
+ case 'Q':
+ if (dblist) {
+ fprintf(stderr, "error: can only select one repo option (use -h for help)\n");
+ return(1);
+ }
+ dblist = alpm_list_add(dblist, db_local);
+ break;
+ case 'd':
+ delim = optarg;
+ break;
+ case 'l':
+ listdelim = optarg;
+ break;
+ case 'h':
+ usage();
+ return(1);
+ case 't':
+ timefmt = optarg;
+ break;
+ case 'v':
+ verbose = 1;
+ break;
+
+ case '?':
+ return(1);
+ default:
+ return(1);
+ }
+ }
+
+ if (optind < argc) {
+ format = argv[optind++];
+ } else {
+ fprintf(stderr, "error: missing format string (use -h for help)\n");
+ return(1);
+ }
+
+ while (optind < argc) {
+ targets = alpm_list_add(targets, argv[optind++]);
+ }
+
+ return(0);
+}
+
+static void print_escaped(const char *delim) {
+ const char *f;
+
+ for (f = delim; *f != '\0'; f++) {
+ if (*f == '\\') {
+ switch (*++f) {
+ case '\\':
+ putchar('\\');
+ break;
+ case '"':
+ putchar('\"');
+ break;
+ case 'a':
+ putchar('\a');
+ break;
+ case 'b':
+ putchar('\b');
+ break;
+ case 'e': /* \e is nonstandard */
+ putchar('\033');
+ break;
+ case 'n':
+ putchar('\n');
+ break;
+ case 'r':
+ putchar('\r');
+ break;
+ case 't':
+ putchar('\t');
+ break;
+ case 'v':
+ putchar('\v');
+ break;
+ }
+ } else {
+ putchar(*f);
+ }
+ }
+}
+
+static void print_list(alpm_list_t *list, extractfn fn, bool shortdeps) {
+ alpm_list_t *i;
+
+ if (!list) {
+ if (verbose) {
+ printf("None");
+ }
+ return;
+ }
+
+ i = list;
+ while (1) {
+ char *item;
+
+ item = fn ? (char*)fn(alpm_list_getdata(i)) : (char*)alpm_list_getdata(i);
+
+ if (shortdeps) {
+ *(item + strcspn(item, "<>=")) = '\0';
+ }
+
+ printf("%s", item);
+
+ if ((i = alpm_list_next(i))) {
+ print_escaped(listdelim);
+ } else {
+ break;
+ }
+ }
+
+ return;
+}
+
+static void print_time(time_t timestamp) {
+ char buffer[64];
+
+ if (!timestamp) {
+ if (verbose) {
+ printf("None");
+ }
+ return;
+ }
+
+ strftime(&buffer[0], 64, timefmt, localtime(&timestamp));
+ printf("%s", buffer);
+}
+
+static int print_pkg(alpm_list_t *dblist, const char *p, const char *format) {
+ alpm_list_t *i;
+ pmpkg_t *pkg;
+ const char *f;
+ char *repo, *pkgname;
+
+ pkgname = repo = (char*)p;
+ if (strchr(pkgname, '/')) {
+ strsep(&pkgname, "/");
+ } else {
+ repo = NULL;
+ }
+
+ for (i = dblist; i; i = alpm_list_next(i)) {
+ pkg = alpm_db_get_pkg(alpm_list_getdata(i), pkgname);
+ if (repo && strcmp(repo, alpm_db_get_name(alpm_list_getdata(i))) != 0) {
+ continue;
+ }
+ if (pkg) {
+ break;
+ }
+ }
+
+ if (!pkg) {
+ if (verbose) {
+ fprintf(stderr, "error: package `%s' not found\n", pkgname);
+ }
+ return(1);
+ }
+
+ for (f = format; *f != '\0'; f++) {
+ bool shortdeps = false;
+ if (*f == '%') {
+ switch (*++f) {
+ /* simple attributes */
+ case 'f': /* filename */
+ printf("%s", alpm_pkg_get_filename(pkg));
+ break;
+ case 'n': /* package name */
+ printf("%s", alpm_pkg_get_name(pkg));
+ break;
+ case 'v': /* version */
+ printf("%s", alpm_pkg_get_version(pkg));
+ break;
+ case 'd': /* description */
+ printf("%s", alpm_pkg_get_desc(pkg));
+ break;
+ case 'u': /* project url */
+ printf("%s", alpm_pkg_get_url(pkg));
+ break;
+ case 'p': /* packager name */
+ printf("%s", alpm_pkg_get_packager(pkg));
+ break;
+ case 's': /* md5sum */
+ printf("%s", alpm_pkg_get_md5sum(pkg));
+ break;
+ case 'a': /* architecutre */
+ printf("%s", alpm_pkg_get_arch(pkg));
+ break;
+ case 'i': /* has install scriptlet? */
+ printf("%s", alpm_pkg_has_scriptlet(pkg) ? "yes" : "no");
+ break;
+ case 'r': /* repo */
+ printf("%s", alpm_db_get_name(alpm_pkg_get_db(pkg)));
+ break;
+
+ /* times */
+ case 'b': /* build date */
+ print_time(alpm_pkg_get_builddate(pkg));
+ break;
+ case 'l': /* install date */
+ print_time(alpm_pkg_get_installdate(pkg));
+ break;
+
+ /* sizes */
+ case 'k': /* download size */
+ printf("%.2f K", (float)alpm_pkg_get_size(pkg) / 1024.0);
+ break;
+ case 'm': /* install size */
+ printf("%.2f K", (float)alpm_pkg_get_isize(pkg) / 1024.0);
+ break;
+
+ /* lists */
+ case 'N': /* requiredby */
+ print_list(alpm_pkg_compute_requiredby(pkg), NULL, shortdeps);
+ break;
+ case 'L': /* licenses */
+ print_list(alpm_pkg_get_licenses(pkg), NULL, shortdeps);
+ break;
+ case 'G': /* groups */
+ print_list(alpm_pkg_get_groups(pkg), NULL, shortdeps);
+ break;
+ case 'E': /* depends (shortdeps) */
+ print_list(alpm_pkg_get_depends(pkg), (extractfn)alpm_dep_get_name, shortdeps);
+ break;
+ case 'D': /* depends */
+ print_list(alpm_pkg_get_depends(pkg), (extractfn)alpm_dep_compute_string, shortdeps);
+ break;
+ case 'O': /* optdepends */
+ print_list(alpm_pkg_get_optdepends(pkg), NULL, shortdeps);
+ break;
+ case 'C': /* conflicts */
+ print_list(alpm_pkg_get_conflicts(pkg), NULL, shortdeps);
+ break;
+ case 'S': /* provides (shortdeps) */
+ shortdeps = true;
+ case 'P': /* provides */
+ print_list(alpm_pkg_get_provides(pkg), NULL, shortdeps);
+ break;
+ case 'R': /* replaces */
+ print_list(alpm_pkg_get_replaces(pkg), NULL, shortdeps);
+ break;
+ case 'F': /* files */
+ print_list(alpm_pkg_get_files(pkg), NULL, shortdeps);
+ break;
+ case 'B': /* backup */
+ print_list(alpm_pkg_get_backup(pkg), NULL, shortdeps);
+ break;
+ case '%':
+ putchar('%');
+ break;
+ }
+ } else if (*f == '\\') {
+ char buf[3];
+ buf[0] = *f;
+ buf[1] = *++f;
+ buf[2] = '\0';
+ print_escaped(buf);
+ } else {
+ putchar(*f);
+ }
+ }
+
+ print_escaped(delim);
+
+ return(0);
+}
+
+int verify_format_string(const char *format) {
+ const char *p;
+
+ for (p = format; *p != '\0'; p++) {
+ if (*p == '%' && !strchr(FORMAT_TOKENS, *++p)) {
+ fprintf(stderr, "error: bad token in format string: %%%c\n", *p);
+ return(1);
+ } else if (*p == '\\' && !strchr(ESCAPE_TOKENS, *p)) {
+ fprintf(stderr, "error: bad token in format string: \\%c\n", *p);
+ return(1);
+ }
+ }
+
+ return(0);
+}
+
+int main(int argc, char *argv[]) {
+ int ret = 0, freelist = 0;
+ alpm_list_t *i;
+
+ ret = alpm_init();
+ if (ret != 0) {
+ return(1);
+ }
+
+ ret = parse_options(argc, argv);
+ if (ret != 0) {
+ goto finish;
+ }
+
+ /* default vals */
+ if (!dblist) {
+ dblist = alpm_list_add(dblist, db_local);
+ freelist = 1;
+ }
+ delim = delim ? delim : "\n";
+ listdelim = listdelim ? listdelim : " ";
+ timefmt = timefmt ? timefmt : "%c";
+
+ if (verify_format_string(format) != 0) {
+ return(1);
+ }
+
+ for (i = targets; i; i = alpm_list_next(i)) {
+ ret += print_pkg(dblist, alpm_list_getdata(i), format);
+ }
+ ret = !!ret;
+
+ if (freelist) {
+ alpm_list_free(dblist);
+ }
+
+finish:
+ alpm_list_free(targets);
+ alpm_release();
+ return(ret);
+}
+