libalpm
Arch Linux Package Manager Library
pactree.c
Go to the documentation of this file.
00001 /*
00002  *  pactree.c - a simple dependency tree viewer
00003  *
00004  *  Copyright (c) 2010-2011 Pacman Development Team <pacman-dev@archlinux.org>
00005  *
00006  *  This program is free software; you can redistribute it and/or modify
00007  *  it under the terms of the GNU General Public License as published by
00008  *  the Free Software Foundation; either version 2 of the License, or
00009  *  (at your option) any later version.
00010  *
00011  *  This program is distributed in the hope that it will be useful,
00012  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00013  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00014  *  GNU General Public License for more details.
00015  *
00016  *  You should have received a copy of the GNU General Public License
00017  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
00018  */
00019 
00020 #include <ctype.h>
00021 #include <getopt.h>
00022 #include <stdio.h>
00023 #include <string.h>
00024 
00025 #include <alpm.h>
00026 #include <alpm_list.h>
00027 
00028 #define LINE_MAX     512
00029 
00030 /* output */
00031 struct graph_style {
00032     const char *provides;
00033     const char *tip1;
00034     const char *tip2;
00035     int indent;
00036 };
00037 
00038 static struct graph_style graph_default = {
00039     " provides",
00040     "|--",
00041     "+--",
00042     3
00043 };
00044 
00045 static struct graph_style graph_linear = {
00046     "",
00047     "",
00048     "",
00049     0
00050 };
00051 
00052 /* color choices */
00053 struct color_choices {
00054     const char *branch1;
00055     const char *branch2;
00056     const char *leaf1;
00057     const char *leaf2;
00058     const char *off;
00059 };
00060 
00061 static struct color_choices use_color = {
00062     "\033[0;33m", /* yellow */
00063     "\033[0;37m", /* white */
00064     "\033[1;32m", /* bold green */
00065     "\033[0;32m", /* green */
00066     "\033[0m"
00067 };
00068 
00069 static struct color_choices no_color = {
00070     "",
00071     "",
00072     "",
00073     "",
00074     ""
00075 };
00076 
00077 /* long operations */
00078 enum {
00079     OP_CONFIG = 1000
00080 };
00081 
00082 /* globals */
00083 alpm_handle_t *handle = NULL;
00084 alpm_list_t *walked = NULL;
00085 alpm_list_t *provisions = NULL;
00086 
00087 /* options */
00088 struct color_choices *color = &no_color;
00089 struct graph_style *style = &graph_default;
00090 int graphviz = 0;
00091 int max_depth = -1;
00092 int reverse = 0;
00093 int unique = 0;
00094 int searchsyncs = 0;
00095 const char *dbpath = DBPATH;
00096 const char *configfile = CONFFILE;
00097 
00098 #ifndef HAVE_STRNDUP
00099 /* A quick and dirty implementation derived from glibc */
00100 static size_t strnlen(const char *s, size_t max)
00101 {
00102     register const char *p;
00103     for(p = s; *p && max--; ++p);
00104     return (p - s);
00105 }
00106 
00107 char *strndup(const char *s, size_t n)
00108 {
00109   size_t len = strnlen(s, n);
00110   char *new = (char *) malloc(len + 1);
00111 
00112   if(new == NULL)
00113     return NULL;
00114 
00115   new[len] = '\0';
00116   return (char *)memcpy(new, s, len);
00117 }
00118 #endif
00119 
00120 static size_t strtrim(char *str)
00121 {
00122     char *end, *pch = str;
00123 
00124     if(str == NULL || *str == '\0') {
00125         /* string is empty, so we're done. */
00126         return 0;
00127     }
00128 
00129     while(isspace((unsigned char)*pch)) {
00130         pch++;
00131     }
00132     if(pch != str) {
00133         size_t len = strlen(pch);
00134         if(len) {
00135             memmove(str, pch, len + 1);
00136         } else {
00137             *str = '\0';
00138         }
00139     }
00140 
00141     /* check if there wasn't anything but whitespace in the string. */
00142     if(*str == '\0') {
00143         return 0;
00144     }
00145 
00146     end = (str + strlen(str) - 1);
00147     while(isspace((unsigned char)*end)) {
00148         end--;
00149     }
00150     *++end = '\0';
00151 
00152     return end - pch;
00153 }
00154 
00155 static int register_syncs(void) {
00156     FILE *fp;
00157     char *section = NULL;
00158     char line[LINE_MAX];
00159     const alpm_siglevel_t level = ALPM_SIG_DATABASE | ALPM_SIG_DATABASE_OPTIONAL;
00160 
00161     fp = fopen(configfile, "r");
00162     if(!fp) {
00163         fprintf(stderr, "error: config file %s could not be read\n", configfile);
00164         return 1;
00165     }
00166 
00167     while(fgets(line, LINE_MAX, fp)) {
00168         size_t linelen;
00169         char *ptr;
00170 
00171         linelen = strtrim(line);
00172 
00173         if(line[0] == '#' || !linelen) {
00174             continue;
00175         }
00176 
00177         if((ptr = strchr(line, '#'))) {
00178             *ptr = '\0';
00179             linelen = strtrim(line);
00180         }
00181 
00182         if(line[0] == '[' && line[linelen - 1] == ']') {
00183             free(section);
00184             section = strndup(&line[1], linelen - 2);
00185 
00186             if(section && strcmp(section, "options") != 0) {
00187                 alpm_db_register_sync(handle, section, level);
00188             }
00189         }
00190     }
00191 
00192     free(section);
00193     fclose(fp);
00194 
00195     return 0;
00196 }
00197 
00198 static int parse_options(int argc, char *argv[])
00199 {
00200     int opt, option_index = 0;
00201     char *endptr = NULL;
00202 
00203     static const struct option opts[] = {
00204         {"dbpath",  required_argument,    0, 'b'},
00205         {"color",   no_argument,          0, 'c'},
00206         {"depth",   required_argument,    0, 'd'},
00207         {"graph",   no_argument,          0, 'g'},
00208         {"help",    no_argument,          0, 'h'},
00209         {"linear",  no_argument,          0, 'l'},
00210         {"reverse", no_argument,          0, 'r'},
00211         {"sync",    no_argument,          0, 'S'},
00212         {"unique",  no_argument,          0, 'u'},
00213 
00214         {"config",  required_argument,    0, OP_CONFIG},
00215         {0, 0, 0, 0}
00216     };
00217 
00218     while((opt = getopt_long(argc, argv, "b:cd:ghlrsu", opts, &option_index))) {
00219         if(opt < 0) {
00220             break;
00221         }
00222 
00223         switch(opt) {
00224             case OP_CONFIG:
00225                 configfile = optarg;
00226                 break;
00227             case 'b':
00228                 dbpath = optarg;
00229                 break;
00230             case 'c':
00231                 color = &use_color;
00232                 break;
00233             case 'd':
00234                 /* validate depth */
00235                 max_depth = (int)strtol(optarg, &endptr, 10);
00236                 if(*endptr != '\0') {
00237                     fprintf(stderr, "error: invalid depth -- %s\n", optarg);
00238                     return 1;
00239                 }
00240                 break;
00241             case 'g':
00242                 graphviz = 1;
00243                 break;
00244             case 'l':
00245                 style = &graph_linear;
00246                 break;
00247             case 'r':
00248                 reverse = 1;
00249                 break;
00250             case 's':
00251                 searchsyncs = 1;
00252                 break;
00253             case 'u':
00254                 unique = 1;
00255                 style = &graph_linear;
00256                 break;
00257             case 'h':
00258             case '?':
00259             default:
00260                 return 1;
00261         }
00262     }
00263 
00264     if(!argv[optind]) {
00265         return 1;
00266     }
00267 
00268     return 0;
00269 }
00270 
00271 static void usage(void)
00272 {
00273     fprintf(stderr, "pactree v" PACKAGE_VERSION "\n"
00274             "Usage: pactree [options] PACKAGE\n\n"
00275             "  -b, --dbpath <path>  set an alternate database location\n"
00276             "  -c, --color          colorize output\n"
00277             "  -d, --depth <#>      limit the depth of recursion\n"
00278             "  -g, --graph          generate output for graphviz's dot\n"
00279             "  -h, --help           display this help message\n"
00280             "  -l, --linear         enable linear output\n"
00281             "  -r, --reverse        show reverse dependencies\n"
00282             "  -s, --sync           search sync DBs instead of local\n"
00283             "  -u, --unique         show dependencies with no duplicates (implies -l)\n"
00284             "      --config <path>  set an alternate configuration file\n");
00285 }
00286 
00287 static void cleanup(void)
00288 {
00289     alpm_list_free(walked);
00290     alpm_list_free(provisions);
00291     alpm_release(handle);
00292 }
00293 
00294 /* pkg provides provision */
00295 static void print_text(const char *pkg, const char *provision, int depth)
00296 {
00297     int indent_sz = (depth + 1) * style->indent;
00298 
00299     if(!pkg && !provision) {
00300         /* not much we can do */
00301         return;
00302     }
00303 
00304     if(!pkg && provision) {
00305         /* we failed to resolve provision */
00306         printf("%s%*s%s%s%s [unresolvable]%s\n", color->branch1, indent_sz,
00307                 style->tip1, color->leaf1, provision, color->branch1, color->off);
00308     } else if(provision && strcmp(pkg, provision) != 0) {
00309         /* pkg provides provision */
00310         printf("%s%*s%s%s%s%s %s%s%s\n", color->branch2, indent_sz, style->tip2,
00311                 color->leaf1, pkg, color->leaf2, style->provides, color->leaf1, provision,
00312                 color->off);
00313     } else {
00314         /* pkg is a normal package */
00315         printf("%s%*s%s%s%s\n", color->branch1, indent_sz, style->tip1, color->leaf1,
00316                 pkg, color->off);
00317     }
00318 }
00319 
00320 static void print_graph(const char *parentname, const char *pkgname, const char *depname)
00321 {
00322     if(depname) {
00323         printf("\"%s\" -> \"%s\" [color=chocolate4];\n", parentname, depname);
00324         if(pkgname && strcmp(depname, pkgname) != 0 && !alpm_list_find_str(provisions, depname)) {
00325             printf("\"%s\" -> \"%s\" [arrowhead=none, color=grey];\n", depname, pkgname);
00326             provisions = alpm_list_add(provisions, (char *)depname);
00327         }
00328     } else if(pkgname) {
00329         printf("\"%s\" -> \"%s\" [color=chocolate4];\n", parentname, pkgname);
00330     }
00331 }
00332 
00333 /* parent depends on dep which is satisfied by pkg */
00334 static void print(const char *parentname, const char *pkgname, const char *depname, int depth)
00335 {
00336     if(graphviz) {
00337         print_graph(parentname, pkgname, depname);
00338     } else {
00339         print_text(pkgname, depname, depth);
00340     }
00341 }
00342 
00343 static void print_start(const char *pkgname, const char *provname)
00344 {
00345     if(graphviz) {
00346         printf("digraph G { START [color=red, style=filled];\n"
00347                 "node [style=filled, color=green];\n"
00348                 " \"START\" -> \"%s\";\n", pkgname);
00349     } else {
00350         print_text(pkgname, provname, 0);
00351     }
00352 }
00353 
00354 static void print_end(void)
00355 {
00356     if(graphviz) {
00357         /* close graph output */
00358         printf("}\n");
00359     }
00360 }
00361 
00362 static alpm_pkg_t *get_pkg_from_dbs(alpm_list_t *dbs, const char *needle) {
00363     alpm_list_t *i;
00364     alpm_pkg_t *ret;
00365 
00366     for(i = dbs; i; i = alpm_list_next(i)) {
00367         ret = alpm_db_get_pkg(i->data, needle);
00368         if(ret) {
00369             return ret;
00370         }
00371     }
00372     return NULL;
00373 }
00374 
00375 /**
00376  * walk dependencies in reverse, showing packages which require the target
00377  */
00378 static void walk_reverse_deps(alpm_list_t *dblist, alpm_pkg_t *pkg, int depth)
00379 {
00380     alpm_list_t *required_by, *i;
00381 
00382     if(!pkg || ((max_depth >= 0) && (depth == max_depth + 1))) {
00383         return;
00384     }
00385 
00386     walked = alpm_list_add(walked, (void *)alpm_pkg_get_name(pkg));
00387     required_by = alpm_pkg_compute_requiredby(pkg);
00388 
00389     for(i = required_by; i; i = alpm_list_next(i)) {
00390         const char *pkgname = i->data;
00391 
00392         if(alpm_list_find_str(walked, pkgname)) {
00393             /* if we've already seen this package, don't print in "unique" output
00394              * and don't recurse */
00395             if(!unique) {
00396                 print(alpm_pkg_get_name(pkg), pkgname, NULL, depth);
00397             }
00398         } else {
00399             print(alpm_pkg_get_name(pkg), pkgname, NULL, depth);
00400             walk_reverse_deps(dblist, get_pkg_from_dbs(dblist, pkgname), depth + 1);
00401         }
00402     }
00403 
00404     FREELIST(required_by);
00405 }
00406 
00407 /**
00408  * walk dependencies, showing dependencies of the target
00409  */
00410 static void walk_deps(alpm_list_t *dblist, alpm_pkg_t *pkg, int depth)
00411 {
00412     alpm_list_t *i;
00413 
00414     if((max_depth >= 0) && (depth == max_depth + 1)) {
00415         return;
00416     }
00417 
00418     walked = alpm_list_add(walked, (void *)alpm_pkg_get_name(pkg));
00419 
00420     for(i = alpm_pkg_get_depends(pkg); i; i = alpm_list_next(i)) {
00421         alpm_depend_t *depend = i->data;
00422         alpm_pkg_t *provider = alpm_find_dbs_satisfier(handle, dblist, depend->name);
00423 
00424         if(provider) {
00425             const char *provname = alpm_pkg_get_name(provider);
00426 
00427             if(alpm_list_find_str(walked, provname)) {
00428                 /* if we've already seen this package, don't print in "unique" output
00429                  * and don't recurse */
00430                 if(!unique) {
00431                     print(alpm_pkg_get_name(pkg), provname, depend->name, depth);
00432                 }
00433             } else {
00434                 print(alpm_pkg_get_name(pkg), provname, depend->name, depth);
00435                 walk_deps(dblist, provider, depth + 1);
00436             }
00437         } else {
00438             /* unresolvable package */
00439             print(alpm_pkg_get_name(pkg), NULL, depend->name, depth);
00440         }
00441     }
00442 }
00443 
00444 int main(int argc, char *argv[])
00445 {
00446     int freelist = 0, ret = 0;
00447     alpm_errno_t err;
00448     const char *target_name;
00449     alpm_pkg_t *pkg;
00450     alpm_list_t *dblist = NULL;
00451 
00452     if(parse_options(argc, argv) != 0) {
00453         usage();
00454         ret = 1;
00455         goto finish;
00456     }
00457 
00458     handle = alpm_initialize(ROOTDIR, dbpath, &err);
00459     if(!handle) {
00460         fprintf(stderr, "error: cannot initialize alpm: %s\n",
00461                 alpm_strerror(err));
00462         ret = 1;
00463         goto finish;
00464     }
00465 
00466     if(searchsyncs) {
00467         if(register_syncs() != 0) {
00468             ret = 1;
00469             goto finish;
00470         }
00471         dblist = alpm_option_get_syncdbs(handle);
00472     } else {
00473         dblist = alpm_list_add(dblist, alpm_option_get_localdb(handle));
00474         freelist = 1;
00475     }
00476 
00477     /* we only care about the first non option arg for walking */
00478     target_name = argv[optind];
00479 
00480     pkg = alpm_find_dbs_satisfier(handle, dblist, target_name);
00481     if(!pkg) {
00482         fprintf(stderr, "error: package '%s' not found\n", target_name);
00483         ret = 1;
00484         goto finish;
00485     }
00486 
00487     print_start(alpm_pkg_get_name(pkg), target_name);
00488 
00489     if(reverse) {
00490         walk_reverse_deps(dblist, pkg, 1);
00491     } else {
00492         walk_deps(dblist, pkg, 1);
00493     }
00494 
00495     print_end();
00496 
00497     if(freelist) {
00498         alpm_list_free(dblist);
00499     }
00500 
00501 finish:
00502     cleanup();
00503     return ret;
00504 }
00505 
00506 /* vim: set ts=2 sw=2 noet: */