diff --git a/.hgignore b/.hgignore index 00159193..c664778c 100644 --- a/.hgignore +++ b/.hgignore @@ -2,9 +2,10 @@ syntax: glob config.h config.mk -xbps-bin -xbps-repo -xbps-uhelper +bin/xbps-bin/xbps-bin +bin/xbps-repo/xbps-repo +bin/xbps-uhelper/xbps-uhelper +bin/xbps-dgraph/xbps-dgraph *.static *.so* *.o diff --git a/NEWS b/NEWS index 78540bc3..b66a2eca 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,16 @@ +xbps-0.6.3 (?) + + * xbps-dgraph: new utility to generate graphviz' dot(1) graphs for package + metadata properties, such as dependencies, reverse dependencies, etc. + Extracts the info from installed package metadata plist files. + + * Performance improvements in libxbps and all utilities, by avoiding + unnecessary access(2) and chdir(2) calls while executing the + INSTALL/REMOVE scripts at pre/post (de)install time. + + * Fixed some memleaks on libxbps found while working on the xbps-dgraph + utility. + xbps-0.6.2 (2010-10-31): * libxbps: xbps_repository_unregister(): in remote repositories, also diff --git a/bin/Makefile b/bin/Makefile index a159e54d..f7f50693 100644 --- a/bin/Makefile +++ b/bin/Makefile @@ -3,6 +3,7 @@ SUBDIRS = xbps-uhelper SUBDIRS += xbps-repo SUBDIRS += xbps-bin +SUBDIRS += xbps-dgraph .PHONY: all all: diff --git a/bin/xbps-dgraph/Makefile b/bin/xbps-dgraph/Makefile new file mode 100644 index 00000000..dd2589aa --- /dev/null +++ b/bin/xbps-dgraph/Makefile @@ -0,0 +1,6 @@ +TOPDIR = ../.. +-include $(TOPDIR)/config.mk + +BIN = xbps-dgraph + +include $(TOPDIR)/prog.mk diff --git a/bin/xbps-dgraph/main.c b/bin/xbps-dgraph/main.c new file mode 100644 index 00000000..2cbf6583 --- /dev/null +++ b/bin/xbps-dgraph/main.c @@ -0,0 +1,528 @@ +/*- + * Copyright (c) 2010 Juan Romero Pardines. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include + +#include + +#ifndef __arraycount +# define __arraycount(a) (sizeof(a) / sizeof(*(a))) +#endif + +#define _DGRAPH_CFFILE "xbps-dgraph.conf" + +/* + * Object key for optional objects in package dictionary. + */ +static const char *optional_objs[] = { + "conflicts", "conf_files", "replaces", "run_depends", "preserve", + "requiredby" +}; + +/* + * Properties written to default configuration file. + */ +struct defprops { + const char *sect; + const char *prop; + const char *val; +} dfprops[] = { + { .sect = "graph", .prop = "rankdir", .val = "LR" }, + { .sect = "graph", .prop = "ranksep", .val = ".1" }, + { .sect = "graph", .prop = "nodesep", .val = ".1" }, + { .sect = "graph", .prop = "pack", .val = "true" }, + { .sect = "graph", .prop = "splines", .val = "polyline" }, + { .sect = "graph", .prop = "ratio", .val = "compress" }, + + { .sect = "edge", .prop = "constraint", .val = "true" }, + { .sect = "edge", .prop = "arrowhead", .val = "vee" }, + { .sect = "edge", .prop = "arrowsize", .val = ".4" }, + { .sect = "edge", .prop = "fontname", .val = "Sans" }, + { .sect = "edge", .prop = "fontsize", .val = "8" }, + + { .sect = "node", .prop = "height", .val = ".1" }, + { .sect = "node", .prop = "width", .val = ".1" }, + { .sect = "node", .prop = "shape", .val = "box" }, + { .sect = "node", .prop = "fontname", .val = "Sans" }, + { .sect = "node", .prop = "fontsize", .val = "8" }, + + { .sect = "node-sub", .prop = "main-style", .val = "filled" }, + { .sect = "node-sub", .prop = "main-fillcolor", .val = "darksalmon" }, + { .sect = "node-sub", .prop = "style", .val = "filled" }, + { .sect = "node-sub", .prop = "fillcolor", .val = "yellowgreen" }, + { .sect = "node-sub", .prop = "opt-style", .val = "filled" }, + { .sect = "node-sub", .prop = "opt-fillcolor", .val = "grey" } +}; + +static void +die(const char *fmt, ...) +{ + va_list ap; + int save_errno = errno; + + va_start(ap, fmt); + fprintf(stderr, "xbps-dgraph: ERROR "); + vfprintf(stderr, fmt, ap); + fprintf(stderr, " (%s)\n", strerror(save_errno)); + va_end(ap); + exit(EXIT_FAILURE); +} + +static void +usage(void) +{ + fprintf(stderr, + "Usage: xbps-dgraph [options] \n\n" + " Options\n" + " -c\t\tPath to configuration file\n" + " -g\t\tGenerate a default config file\n" + " -o\t\tOutput to this file (.dot set by default)\n" + " -R\t\tAlso generate reverse dependencies in the graph\n" + " -r\t\t\n\n"); + exit(EXIT_FAILURE); +} + +static const char * +convert_proptype_to_string(prop_object_t obj) +{ + switch (prop_object_type(obj)) { + case PROP_TYPE_ARRAY: + return "array"; + case PROP_TYPE_BOOL: + return "bool"; + case PROP_TYPE_DICTIONARY: + return "dictionary"; + case PROP_TYPE_DICT_KEYSYM: + return "dictionary key"; + case PROP_TYPE_NUMBER: + return "integer"; + case PROP_TYPE_STRING: + return "string"; + default: + return NULL; + } +} + +static void +generate_conf_file(void) +{ + prop_dictionary_t d, d2; + struct defprops *dfp; + size_t i; + const char *outfile = "xbps-dgraph.conf"; + + d = prop_dictionary_create(); + + d2 = prop_dictionary_create(); + prop_dictionary_set(d, "graph", d2); + prop_object_release(d2); + + d2 = prop_dictionary_create(); + prop_dictionary_set(d, "edge", d2); + prop_object_release(d2); + + d2 = prop_dictionary_create(); + prop_dictionary_set(d, "node", d2); + prop_object_release(d2); + + d2 = prop_dictionary_create(); + prop_dictionary_set(d, "node-sub", d2); + prop_object_release(d2); + + for (i = 0; i < __arraycount(dfprops); i++) { + dfp = &dfprops[i]; + d2 = prop_dictionary_get(d, dfp->sect); + prop_dictionary_set_cstring_nocopy(d2, dfp->prop, dfp->val); + } + + if (prop_dictionary_externalize_to_file(d, outfile) == false) { + prop_object_release(d); + die("couldn't write conf_file to %s", outfile); + } + prop_object_release(d); + printf("Wrote configuration file: %s\n", _DGRAPH_CFFILE); +} + +static void +write_conf_property_on_stream(FILE *f, + const char *section, + prop_dictionary_t confd) +{ + prop_array_t allkeys, allkeys2; + prop_dictionary_keysym_t dksym, dksym2; + prop_object_t keyobj, keyobj2; + size_t i, x; + const char *cf_val, *keyname, *keyname2; + + /* + * Iterate over the main dictionary. + */ + allkeys = prop_dictionary_all_keys(confd); + for (i = 0; i < prop_array_count(allkeys); i++) { + dksym = prop_array_get(allkeys, i); + keyname = prop_dictionary_keysym_cstring_nocopy(dksym); + keyobj = prop_dictionary_get_keysym(confd, dksym); + if (strcmp(keyname, section)) + continue; + + /* + * Iterate over the dictionary sections [edge/graph/node]. + */ + allkeys2 = prop_dictionary_all_keys(keyobj); + for (x = 0; x < prop_array_count(allkeys2); x++) { + dksym2 = prop_array_get(allkeys2, x); + keyname2 = prop_dictionary_keysym_cstring_nocopy(dksym2); + keyobj2 = prop_dictionary_get_keysym(keyobj, dksym2); + + cf_val = prop_string_cstring_nocopy(keyobj2); + fprintf(f, "%s=\"%s\"", keyname2, cf_val); + if (x + 1 >= prop_array_count(allkeys2)) + continue; + + fprintf(f, ","); + } + } +} + +static char * +strip_dashes_from_key(const char *str) +{ + char *p; + size_t i; + + p = strdup(str); + if (p == NULL) + die("%s alloc p", __func__); + + for (i = 0; i < strlen(p); i++) { + if (p[i] == '-') + p[i] = '_'; + } + return p; +} + +static void +parse_array_in_pkg_dictionary(FILE *f, prop_dictionary_t plistd, + prop_dictionary_t sub_confd, + prop_array_t allkeys, + bool parse_regpkgdb) +{ + prop_dictionary_keysym_t dksym; + prop_object_t keyobj, sub_keyobj; + size_t i, x; + const char *tmpkeyname, *cfprop, *optnodetmp; + char *optnode, *keyname; + + for (i = 0; i < prop_array_count(allkeys); i++) { + dksym = prop_array_get(allkeys, i); + tmpkeyname = prop_dictionary_keysym_cstring_nocopy(dksym); + + /* + * While parsing package's dictionary from regpkgdb, we are + * only interested in the "automatic-install" and "requiredby" + * objects. + */ + if (parse_regpkgdb && + (strcmp(tmpkeyname, "automatic-install")) && + (strcmp(tmpkeyname, "requiredby"))) + continue; + + keyobj = prop_dictionary_get_keysym(plistd, dksym); + keyname = strip_dashes_from_key(tmpkeyname); + optnodetmp = ""; + optnode = NULL; + + fprintf(f, " main -> %s [label=\"%s\"];\n", + keyname, convert_proptype_to_string(keyobj)); + + prop_dictionary_get_cstring_nocopy(sub_confd, "opt-style", &cfprop); + /* Check if object is optional and fill it in */ + for (x = 0; x < __arraycount(optional_objs); x++) { + if (strcmp(keyname, optional_objs[x]) == 0) { + optnode = xbps_xasprintf("[style=\"%s\"", + cfprop); + if (optnode == NULL) + die("alloc optnode"); + + break; + } + } + optnodetmp = optnode; + + /* + * We can assume that all arrays only contain strings, so + * this can be simplified. + */ + prop_dictionary_get_cstring_nocopy(sub_confd, "style", &cfprop); + if (prop_object_type(keyobj) == PROP_TYPE_ARRAY) { + if (optnodetmp) + fprintf(f, " %s %s];\n", keyname, + optnodetmp); + + for (x = 0; x < prop_array_count(keyobj); x++) { + sub_keyobj = prop_array_get(keyobj, x); + fprintf(f, " %s -> %s_%zu_string " + "[label=\"string\"];\n", + keyname, keyname, x); + prop_dictionary_get_cstring_nocopy(sub_confd, + "style", &cfprop); + fprintf(f, " %s_%zu_string [style=\"%s\",", + keyname, x, cfprop); + prop_dictionary_get_cstring_nocopy(sub_confd, + "fillcolor", &cfprop); + fprintf(f, "fillcolor=\"%s\"," + "label=\"%s\"];\n", cfprop, + prop_string_cstring_nocopy(sub_keyobj)); + } + if (optnode) + free(optnode); + free(keyname); + continue; + } + + if (optnodetmp) { + fprintf(f, " %s %s];\n", keyname, optnodetmp); + fprintf(f, " %s -> %s_value %s];\n", keyname, keyname, + optnode); + } else + fprintf(f, " %s -> %s_value;\n", keyname, keyname); + + prop_dictionary_get_cstring_nocopy(sub_confd, "style", &cfprop); + fprintf(f, " %s_value [style=\"%s\",", keyname, cfprop); + prop_dictionary_get_cstring_nocopy(sub_confd, + "fillcolor", &cfprop); + fprintf(f, "fillcolor=\"%s\"", cfprop); + + /* + * Process all other object types... + */ + switch (prop_object_type(keyobj)) { + case PROP_TYPE_BOOL: + fprintf(f, ",label=\"%s\"", + prop_bool_true(keyobj) ? "true" : "false"); + break; + case PROP_TYPE_NUMBER: + fprintf(f, ",label=\"%zu\"", + prop_number_unsigned_integer_value(keyobj)); + break; + case PROP_TYPE_STRING: + if (strcmp(keyname, "long_desc") == 0) { + /* + * Do not print this obj, too large! + */ + fprintf(f, ",label=\"...\""); + } else { + fprintf(f, ",label=\"%s\"", + prop_string_cstring_nocopy(keyobj)); + } + break; + default: + break; + } + fprintf(f, "];\n"); + + free(keyname); + if (optnode) + free(optnode); + } +} + +static void +create_dot_graph(FILE *f, + prop_dictionary_t plistd, + prop_dictionary_t confd, + bool revdeps) +{ + prop_dictionary_t sub_confd, regpkgd = NULL; + prop_array_t allkeys; + const char *pkgver, *pkgn, *cfprop; + + prop_dictionary_get_cstring_nocopy(plistd, "pkgver", &pkgver); + prop_dictionary_get_cstring_nocopy(plistd, "pkgname", &pkgn); + + /* + * Start filling the output file... + */ + fprintf(f, "/* Graph created for %s by xbps-graph %s */\n\n", + pkgver, XBPS_RELVER); + fprintf(f, "digraph pkg_dictionary {\n"); + + /* + * Process the graph section in config file. + */ + fprintf(f, " graph ["); + write_conf_property_on_stream(f, "graph", confd); + fprintf(f, ",label=\"[XBPS] %s metadata properties\"];\n", pkgver); + + /* + * Process the edge section in config file. + */ + fprintf(f, " edge ["); + write_conf_property_on_stream(f, "edge", confd); + fprintf(f, "];\n"); + + /* + * Process the node section in config file. + */ + fprintf(f, " node ["); + write_conf_property_on_stream(f, "node", confd); + fprintf(f, "];\n"); + + /* + * Process the node-sub section in config file. + */ + fprintf(f, " main ["); + sub_confd = prop_dictionary_get(confd, "node-sub"); + prop_dictionary_get_cstring_nocopy(sub_confd, "main-style", &cfprop); + if (cfprop) + fprintf(f, "style=%s,", cfprop); + prop_dictionary_get_cstring_nocopy(sub_confd, "main-fillcolor", &cfprop); + if (cfprop) + fprintf(f, "fillcolor=\"%s\",", cfprop); + fprintf(f, "label=\"Dictionary\"];\n"); + + /* + * Process all objects in package's dictionary from its metadata + * property list file, aka XBPS_META_PATH/metadata//XBPS_PKGPROPS. + */ + allkeys = prop_dictionary_all_keys(plistd); + parse_array_in_pkg_dictionary(f, plistd, sub_confd, allkeys, false); + + /* + * Process all objects in package's dictionary from regpkgdb property + * list file, aka XBPS_META_PATH/XBPS_REGPKGDB. + */ + if (revdeps) { + regpkgd = xbps_find_pkg_dict_installed(pkgn, false); + if (regpkgd == NULL) + die("cannot find '%s' dictionary on %s!", + pkgn, XBPS_REGPKGDB); + + allkeys = prop_dictionary_all_keys(regpkgd); + parse_array_in_pkg_dictionary(f, regpkgd, sub_confd, + allkeys, true); + } + /* + * Terminate the stream... + */ + fprintf(f, "}\n"); + fflush(f); + fclose(f); +} + +int +main(int argc, char **argv) +{ + prop_dictionary_t plistd, confd = NULL; + FILE *f = NULL; + char *outfile = NULL; + const char *conf_file = NULL; + int c; + bool revdeps = false; + + while ((c = getopt(argc, argv, "c:gRr:o:")) != -1) { + switch (c) { + case 'c': + /* Configuration file. */ + conf_file = optarg; + break; + case 'g': + /* Generate auto conf file. */ + generate_conf_file(); + exit(EXIT_SUCCESS); + case 'o': + /* Output to this file. */ + outfile = optarg; + break; + case 'R': + /* Also create graphs for reverse deps. */ + revdeps = true; + break; + case 'r': + /* Set different rootdir. */ + xbps_set_rootdir(optarg); + break; + case '?': + default: + usage(); + } + } + + argc -= optind; + argv += optind; + + if (argc != 1) + usage(); + + /* + * Output file will be .dot if not specified. + */ + if (outfile == NULL) { + outfile = xbps_xasprintf("%s.dot", argv[0]); + if (outfile == NULL) + die("alloc outfile"); + } + + /* + * If -c not set, try to read it from cwd. + */ + if (conf_file == NULL) + conf_file = _DGRAPH_CFFILE; + + /* + * Internalize the configuration file. + */ + confd = prop_dictionary_internalize_from_zfile(conf_file); + if (confd == NULL) + die("cannot read conf file `%s'", conf_file); + + /* + * Internalize the plist file of the target installed package. + */ + plistd = xbps_get_pkg_dict_from_metadata_plist(argv[0], XBPS_PKGPROPS); + if (plistd == NULL) + die("cannot internalize %s from %s", XBPS_PKGPROPS, argv[0]); + + /* + * Create the output FILE. + */ + if ((f = fopen(outfile, "w")) == NULL) + die("cannot create target file '%s'", outfile); + + /* + * Create the dot(1) graph! + */ + create_dot_graph(f, plistd, confd, revdeps); + + prop_object_release(plistd); + prop_object_release(confd); + + exit(EXIT_SUCCESS); +}