322 lines
7.4 KiB
C
322 lines
7.4 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
// SPDX-FileCopyrightText: 2007-2010 Jan Engelhardt
|
|
/*
|
|
* graph-fanout.c - Fanout tree for Graphviz
|
|
*/
|
|
|
|
/*
|
|
* Assume you have a group of nodes X_1 ... X_n that all point to a next-node P
|
|
* (it is a directed graph). If n is within the threshold ("fan_height" in the
|
|
* source code), do nothing with this group.
|
|
*
|
|
* Insert intermediate parent nodes for X_1 ... X_n so that P and every
|
|
* intermediate only has at most n nodes pointing to it.
|
|
*
|
|
* (Just think of it like the n-level pagetables.)
|
|
*
|
|
* This is basically just a hack for graphviz because it tends to overlap edges
|
|
* when it has to give them greater curvature. (Make yourself a tree with 32
|
|
* nodes X_1...X_32 and one root node to which all 32 are attached.) The edges
|
|
* X_1-->P and X_32-->P will have a great 'curvature', while X_16-->P is pretty
|
|
* linear.
|
|
*
|
|
* The 'problem' (it's rather "we did not want to write code until the dawn of
|
|
* time just to avoid this problem") lies in graphviz's math parts to form the
|
|
* edge curvature -- so it happens that X_1-->P will overlap with X_4-->P. This
|
|
* is not so nice when you really want to follow all edges from your name to
|
|
* the root.
|
|
*/
|
|
#include <errno.h>
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <libHX/deque.h>
|
|
#include <libHX/init.h>
|
|
#include <libHX/map.h>
|
|
#include <libHX/option.h>
|
|
#include <libHX/string.h>
|
|
|
|
struct node {
|
|
char *name;
|
|
struct node *parent;
|
|
struct HXdeque *children;
|
|
};
|
|
|
|
static unsigned int fan_height = 8;
|
|
static unsigned int rt_node_counter;
|
|
static struct HXmap *nodename_map;
|
|
static struct HXmap *roots_map;
|
|
|
|
static void fanout_build(const char *child_name, const char *parent_name)
|
|
{
|
|
struct node *child_ptr, *parent_ptr;
|
|
int ret;
|
|
|
|
parent_ptr = HXmap_get(nodename_map, parent_name);
|
|
if (parent_ptr == NULL) {
|
|
struct node local_node = {.name = HX_strdup(parent_name)};
|
|
|
|
if (local_node.name == NULL) {
|
|
perror("strdup");
|
|
abort();
|
|
}
|
|
|
|
local_node.children = HXdeque_init();
|
|
if (local_node.children == NULL) {
|
|
perror("HXdeque_init");
|
|
abort();
|
|
}
|
|
|
|
parent_ptr = HX_memdup(&local_node, sizeof(local_node));
|
|
if (parent_ptr == NULL) {
|
|
perror("HX_memdup");
|
|
abort();
|
|
}
|
|
|
|
ret = HXmap_add(nodename_map, local_node.name, parent_ptr);
|
|
if (ret <= 0) {
|
|
fprintf(stderr, "HXmap_add: %s\n", strerror(-ret));
|
|
abort();
|
|
}
|
|
}
|
|
|
|
child_ptr = HXmap_get(nodename_map, child_name);
|
|
if (child_ptr == NULL) {
|
|
struct node local_node = {.name = HX_strdup(child_name)};
|
|
|
|
if (local_node.name == NULL) {
|
|
perror("strdup");
|
|
abort();
|
|
}
|
|
|
|
local_node.children = HXdeque_init();
|
|
if (local_node.children == NULL) {
|
|
perror("HXdeque_init");
|
|
abort();
|
|
}
|
|
|
|
child_ptr = HX_memdup(&local_node, sizeof(local_node));
|
|
if (child_ptr == NULL) {
|
|
perror("HX_memdup");
|
|
abort();
|
|
}
|
|
ret = HXmap_add(nodename_map, local_node.name, child_ptr);
|
|
if (ret <= 0) {
|
|
fprintf(stderr, "HXmap_add: %s\n", strerror(-ret));
|
|
abort();
|
|
}
|
|
}
|
|
|
|
HXdeque_push(parent_ptr->children, child_ptr);
|
|
child_ptr->parent = parent_ptr;
|
|
}
|
|
|
|
static void fanout_process(FILE *fp)
|
|
{
|
|
hxmc_t *source_name = NULL, *target_name = NULL;
|
|
|
|
while (HX_getl(&source_name, fp) != NULL) {
|
|
if (HX_getl(&target_name, fp) == NULL)
|
|
break;
|
|
|
|
HX_chomp(source_name);
|
|
HX_chomp(target_name);
|
|
fanout_build(source_name, target_name);
|
|
}
|
|
|
|
HXmc_free(source_name);
|
|
HXmc_free(target_name);
|
|
}
|
|
|
|
static void fanout_find_roots(void)
|
|
{
|
|
const struct HXmap_node *tree_node;
|
|
struct node *w;
|
|
struct HXmap_trav *trav;
|
|
|
|
trav = HXmap_travinit(nodename_map, 0);
|
|
while ((tree_node = HXmap_traverse(trav)) != NULL) {
|
|
w = tree_node->data;
|
|
for (; w->parent != NULL; w = w->parent)
|
|
;
|
|
HXmap_add(roots_map, w, NULL);
|
|
}
|
|
|
|
HXmap_travfree(trav);
|
|
}
|
|
|
|
static void fanout_one_node(struct node *current)
|
|
{
|
|
struct HXdeque *new_children;
|
|
struct node *rt_node = NULL, *child;
|
|
struct HXdeque_node *k;
|
|
unsigned int i = 0;
|
|
char buf[32];
|
|
|
|
if (current->children->items <= fan_height) {
|
|
for (k = current->children->first; k != NULL; k = k->next)
|
|
fanout_one_node(k->ptr);
|
|
return;
|
|
}
|
|
|
|
new_children = HXdeque_init();
|
|
if (new_children == NULL) {
|
|
perror("HXdeque_init");
|
|
abort();
|
|
}
|
|
|
|
for (k = current->children->first; k != NULL; k = k->next) {
|
|
if (i++ % fan_height == 0) {
|
|
rt_node = malloc(sizeof(struct node));
|
|
if (rt_node == NULL) {
|
|
perror("malloc");
|
|
abort();
|
|
}
|
|
|
|
snprintf(buf, sizeof(buf), "#%u", rt_node_counter++);
|
|
rt_node->name = HX_strdup(buf);
|
|
if (rt_node->name == NULL) {
|
|
perror("strdup");
|
|
abort();
|
|
}
|
|
|
|
rt_node->parent = current;
|
|
rt_node->children = HXdeque_init();
|
|
if (rt_node->children == NULL) {
|
|
perror("HXdeque_init");
|
|
abort();
|
|
}
|
|
HXdeque_push(new_children, rt_node);
|
|
HXmap_add(nodename_map, rt_node->name, rt_node);
|
|
}
|
|
|
|
child = k->ptr;
|
|
HXdeque_push(rt_node->children, child);
|
|
child->parent = rt_node;
|
|
fanout_one_node(child);
|
|
}
|
|
|
|
HXdeque_free(current->children);
|
|
current->children = new_children;
|
|
|
|
if (current->children->items > fan_height)
|
|
/* Still need to fan out into more levels */
|
|
fanout_one_node(current);
|
|
}
|
|
|
|
static void fanout_process_roots(struct HXmap *root_map)
|
|
{
|
|
const struct HXmap_node *tree_node;
|
|
struct HXmap_trav *trav;
|
|
|
|
trav = HXmap_travinit(root_map, 0);
|
|
while ((tree_node = HXmap_traverse(trav)) != NULL)
|
|
fanout_one_node(tree_node->key);
|
|
|
|
HXmap_travfree(trav);
|
|
}
|
|
|
|
static void fanout_dump_tree(const struct HXmap *tree)
|
|
{
|
|
const struct HXmap_node *tree_node;
|
|
const struct node *source;
|
|
struct HXmap_trav *trav;
|
|
|
|
trav = HXmap_travinit(tree, 0);
|
|
while ((tree_node = HXmap_traverse(trav)) != NULL) {
|
|
source = tree_node->data;
|
|
if (source->parent != NULL)
|
|
printf("%s\n%s\n",
|
|
source->name, source->parent->name);
|
|
}
|
|
|
|
HXmap_travfree(trav);
|
|
}
|
|
|
|
static void fanout_free_tree(struct HXmap *tree)
|
|
{
|
|
const struct HXmap_node *tree_node;
|
|
struct node *current;
|
|
struct HXmap_trav *trav;
|
|
|
|
trav = HXmap_travinit(tree, 0);
|
|
while ((tree_node = HXmap_traverse(trav)) != NULL) {
|
|
current = tree_node->data;
|
|
HXdeque_free(current->children);
|
|
free(current->name);
|
|
free(current);
|
|
}
|
|
|
|
HXmap_travfree(trav);
|
|
HXmap_free(tree);
|
|
}
|
|
|
|
static bool fanout_get_options(int *argc, const char ***argv)
|
|
{
|
|
struct HXoption options_table[] = {
|
|
{.sh = 'n', .type = HXTYPE_UINT, .ptr = &fan_height,
|
|
.help = "Maximum height per group", .htyp = "INT"},
|
|
HXOPT_AUTOHELP,
|
|
HXOPT_TABLEEND,
|
|
};
|
|
return HX_getopt(options_table, argc, argv, HXOPT_USAGEONERR) ==
|
|
HXOPT_ERR_SUCCESS;
|
|
}
|
|
|
|
static int main2(int argc, const char **argv)
|
|
{
|
|
const char **file;
|
|
FILE *fp;
|
|
|
|
nodename_map = HXmap_init(HXMAPT_DEFAULT, HXMAP_SKEY);
|
|
if (nodename_map == NULL) {
|
|
perror("HXmap_init");
|
|
abort();
|
|
}
|
|
|
|
roots_map = HXmap_init(HXMAPT_DEFAULT, HXMAP_SINGULAR);
|
|
if (roots_map == NULL) {
|
|
perror("HXbtree_init");
|
|
abort();
|
|
}
|
|
|
|
if (!fanout_get_options(&argc, &argv))
|
|
return EXIT_FAILURE;
|
|
|
|
if (argc == 1) {
|
|
fanout_process(stdin);
|
|
} else {
|
|
for (file = &argv[1]; *file != NULL; ++file) {
|
|
fp = fopen(*file, "r");
|
|
if (fp == NULL) {
|
|
fprintf(stderr, "%s: Could not open %s: %s\n",
|
|
*argv, *file, strerror(errno));
|
|
continue;
|
|
}
|
|
fanout_process(fp);
|
|
fclose(fp);
|
|
}
|
|
}
|
|
|
|
fanout_find_roots();
|
|
fanout_process_roots(roots_map);
|
|
fanout_dump_tree(nodename_map);
|
|
fanout_free_tree(nodename_map);
|
|
HXmap_free(roots_map);
|
|
return EXIT_SUCCESS;
|
|
}
|
|
|
|
int main(int argc, const char **argv)
|
|
{
|
|
int ret;
|
|
|
|
if ((ret = HX_init()) <= 0) {
|
|
fprintf(stderr, "HX_init: %s\n", strerror(-ret));
|
|
abort();
|
|
}
|
|
ret = main2(argc, argv);
|
|
HX_exit();
|
|
return ret;
|
|
}
|