libalpm
Arch Linux Package Manager Library
|
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: */