/* $OpenBSD: tsort.c,v 1.35 2016/01/05 16:10:57 espie Exp $ */ /* ex:ts=8 sw=4: * * Copyright (c) 1999-2004 Marc Espie * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include /* The complexity of topological sorting is O(e), where e is the * size of input. While reading input, vertices have to be identified, * thus add the complexity of e keys retrieval among v keys using * an appropriate data structure. This program uses open double hashing * for that purpose. See Knuth for the expected complexity of double * hashing (Brent variation should probably be used if v << e, as a user * option). * * The algorithm used for longest cycle reporting is accurate, but somewhat * expensive. It may need to build all free paths of the graph (a free * path is a path that never goes twice through the same node), whose * number can be as high as O(2^e). Usually, the number of free paths is * much smaller though. This program's author does not believe that a * significantly better worst-case complexity algorithm exists. * * In case of a hints file, the set of minimal nodes is maintained as a * heap. The resulting complexity is O(e+v log v) for the worst case. * The average should actually be near O(e). * * If the hints file is incomplete, there is some extra complexity incurred * by make_transparent, which does propagate order values to unmarked * nodes. In the worst case, make_transparent is O(e u), * where u is the number of originally unmarked nodes. * In practice, it is much faster. * * The simple topological sort algorithm detects cycles. This program * goes further, breaking cycles through the use of simple heuristics. * Each cycle break checks the whole set of nodes, hence if c cycles break * are needed, this is an extra cost of O(c v). * * Possible heuristics are as follows: * - break cycle at node with lowest number of predecessors (default case), * - break longest cycle at node with lowest number of predecessors, * - break cycle at next node from the hints file. * * Except for the hints file case, which sets an explicit constraint on * which cycle to break, those heuristics locally result in the smallest * number of broken edges. * * Those are admittedly greedy strategies, as is the selection of the next * node from the hints file amongst equivalent candidates that is used for * `stable' topological sorting. */ #ifdef __GNUC__ #define UNUSED __attribute__((unused)) #else #define UNUSED #endif struct node; /* The set of arcs from a given node is stored as a linked list. */ struct link { struct link *next; struct node *node; }; #define NO_ORDER UINT_MAX struct node { unsigned int refs; /* Number of arcs left, coming into this node. * Note that nodes with a null count can't * be part of cycles. */ struct link *arcs; /* List of forward arcs. */ unsigned int order; /* Order of nodes according to a hint file. */ /* Cycle detection algorithms build a free path of nodes. */ struct node *from; /* Previous node in the current path. */ unsigned int mark; /* Mark processed nodes in cycle discovery. */ struct link *traverse; /* Next link to traverse when backtracking. */ char k[1]; /* Name of this node. */ }; #define HASH_START 9 struct array { unsigned int entries; struct node **t; }; static void nodes_init(struct ohash *); static struct node *node_lookup(struct ohash *, const char *, const char *); static void usage(void); static struct node *new_node(const char *, const char *); static unsigned int read_pairs(FILE *, struct ohash *, int, const char *, unsigned int, int); static void split_nodes(struct ohash *, struct array *, struct array *); static void make_transparent(struct ohash *); static void insert_arc(struct node *, struct node *); #ifdef DEBUG static void dump_node(struct node *); static void dump_array(struct array *); static void dump_hash(struct ohash *); #endif static unsigned int read_hints(FILE *, struct ohash *, int, const char *, unsigned int); static struct node *find_smallest_node(struct array *); static struct node *find_good_cycle_break(struct array *); static void print_cycle(struct array *); static struct node *find_cycle_from(struct node *, struct array *); static struct node *find_predecessor(struct array *, struct node *); static unsigned int traverse_node(struct node *, unsigned int, struct array *); static struct node *find_longest_cycle(struct array *, struct array *); static struct node *find_normal_cycle(struct array *, struct array *); static void heap_down(struct array *, unsigned int); static void heapify(struct array *, int); static struct node *dequeue(struct array *); static void enqueue(struct array *, struct node *); static void *hash_calloc(size_t, size_t, void *); static void hash_free(void *, void *); static void* entry_alloc(size_t, void *); static void *ereallocarray(void *, size_t, size_t); static void *emem(void *); #define DEBUG_TRAVERSE 0 static struct ohash_info node_info = { offsetof(struct node, k), NULL, hash_calloc, hash_free, entry_alloc }; static void parse_args(int, char *[], struct ohash *); static int tsort(struct ohash *); static int quiet_flag, long_flag, warn_flag, hints_flag, verbose_flag; int main(int, char *[]); /*** *** Memory handling. ***/ static void * emem(void *p) { if (p) return p; else errx(1, "Memory exhausted"); } static void * hash_calloc(size_t n, size_t s, void *u UNUSED) { return emem(calloc(n, s)); } static void hash_free(void *p, void *u UNUSED) { free(p); } static void * entry_alloc(size_t s, void *u UNUSED) { return ereallocarray(NULL, 1, s); } static void * ereallocarray(void *p, size_t n, size_t s) { return emem(reallocarray(p, n, s)); } /*** *** Hash table. ***/ /* Inserting and finding nodes in the hash structure. * We handle interval strings for efficiency wrt fgetln. */ static struct node * new_node(const char *start, const char *end) { struct node *n; n = ohash_create_entry(&node_info, start, &end); n->from = NULL; n->arcs = NULL; n->refs = 0; n->mark = 0; n->order = NO_ORDER; n->traverse = NULL; return n; } static void nodes_init(struct ohash *h) { ohash_init(h, HASH_START, &node_info); } static struct node * node_lookup(struct ohash *h, const char *start, const char *end) { unsigned int i; struct node * n; i = ohash_qlookupi(h, start, &end); n = ohash_find(h, i); if (n == NULL) n = ohash_insert(h, i, new_node(start, end)); return n; } #ifdef DEBUG static void dump_node(struct node *n) { struct link *l; if (n->refs == 0) return; printf("%s (%u/%u): ", n->k, n->order, n->refs); for (l = n->arcs; l != NULL; l = l->next) if (n->refs != 0) printf("%s(%u/%u) ", l->node->k, l->node->order, l->node->refs); putchar('\n'); } static void dump_array(struct array *a) { unsigned int i; for (i = 0; i < a->entries; i++) dump_node(a->t[i]); } static void dump_hash(struct ohash *h) { unsigned int i; struct node *n; for (n = ohash_first(h, &i); n != NULL; n = ohash_next(h, &i)) dump_node(n); } #endif /*** *** Reading data. ***/ static void insert_arc(struct node *a, struct node *b) { struct link *l; /* Check that this arc is not already present. */ for (l = a->arcs; l != NULL; l = l->next) { if (l->node == b) return; } b->refs++; l = ereallocarray(NULL, 1, sizeof(struct link)); l->node = b; l->next = a->arcs; a->arcs = l; } static unsigned int read_pairs(FILE *f, struct ohash *h, int reverse, const char *name, unsigned int order, int hint) { int toggle; struct node *a; size_t size; char *str; toggle = 1; a = NULL; while ((str = fgetln(f, &size)) != NULL) { char *sentinel; sentinel = str + size; for (;;) { char *e; while (str < sentinel && isspace((unsigned char)*str)) str++; if (str == sentinel) break; for (e = str; e < sentinel && !isspace((unsigned char)*e); e++) continue; if (toggle) { a = node_lookup(h, str, e); if (a->order == NO_ORDER && hint) a->order = order++; } else { struct node *b; b = node_lookup(h, str, e); assert(a != NULL); if (b != a) { if (reverse) insert_arc(b, a); else insert_arc(a, b); } } toggle = !toggle; str = e; } } if (toggle == 0) errx(1, "odd number of node names in %s", name); if (!feof(f)) err(1, "error reading %s", name); return order; } static unsigned int read_hints(FILE *f, struct ohash *h, int quiet, const char *name, unsigned int order) { char *str; size_t size; while ((str = fgetln(f, &size)) != NULL) { char *sentinel; sentinel = str + size; for (;;) { char *e; struct node *a; while (str < sentinel && isspace((unsigned char)*str)) str++; if (str == sentinel) break; for (e = str; e < sentinel && !isspace((unsigned char)*e); e++) continue; a = node_lookup(h, str, e); if (a->order != NO_ORDER) { if (!quiet) warnx( "duplicate node %s in hints file %s", a->k, name); } else a->order = order++; str = e; } } if (!feof(f)) err(1, "error reading %s", name); return order; } /*** *** Standard heap handling routines. ***/ static void heap_down(struct array *h, unsigned int i) { unsigned int j; struct node *swap; for (; (j=2*i+1) < h->entries; i = j) { if (j+1 < h->entries && h->t[j+1]->order < h->t[j]->order) j++; if (h->t[i]->order <= h->t[j]->order) break; swap = h->t[i]; h->t[i] = h->t[j]; h->t[j] = swap; } } static void heapify(struct array *h, int verbose) { unsigned int i; for (i = h->entries; i != 0;) { if (h->t[--i]->order == NO_ORDER && verbose) warnx("node %s absent from hints file", h->t[i]->k); heap_down(h, i); } } #define DEQUEUE(h) ( hints_flag ? dequeue(h) : (h)->t[--(h)->entries] ) static struct node * dequeue(struct array *h) { struct node *n; if (h->entries == 0) n = NULL; else { n = h->t[0]; if (--h->entries != 0) { h->t[0] = h->t[h->entries]; heap_down(h, 0); } } return n; } #define ENQUEUE(h, n) do { \ if (hints_flag) \ enqueue((h), (n)); \ else \ (h)->t[(h)->entries++] = (n); \ } while(0); static void enqueue(struct array *h, struct node *n) { unsigned int i, j; struct node *swap; h->t[h->entries++] = n; for (i = h->entries-1; i > 0; i = j) { j = (i-1)/2; if (h->t[j]->order < h->t[i]->order) break; swap = h->t[j]; h->t[j] = h->t[i]; h->t[i] = swap; } } /* Nodes without order should not hinder direct dependencies. * Iterate until no nodes are left. */ static void make_transparent(struct ohash *hash) { struct node *n; unsigned int i; struct link *l; int adjusted; int bad; unsigned int min; /* first try to solve complete nodes */ do { adjusted = 0; bad = 0; for (n = ohash_first(hash, &i); n != NULL; n = ohash_next(hash, &i)) { if (n->order == NO_ORDER) { min = NO_ORDER; for (l = n->arcs; l != NULL; l = l->next) { /* unsolved node -> delay resolution */ if (l->node->order == NO_ORDER) { bad = 1; break; } else if (l->node->order < min) min = l->node->order; } if (min < NO_ORDER && l == NULL) { n->order = min; adjusted = 1; } } } } while (adjusted); /* then, if incomplete nodes are left, do them */ if (bad) do { adjusted = 0; for (n = ohash_first(hash, &i); n != NULL; n = ohash_next(hash, &i)) if (n->order == NO_ORDER) for (l = n->arcs; l != NULL; l = l->next) if (l->node->order < n->order) { n->order = l->node->order; adjusted = 1; } } while (adjusted); } /*** *** Search through hash array for nodes. ***/ /* Split nodes into unrefed nodes/live nodes. */ static void split_nodes(struct ohash *hash, struct array *heap, struct array *remaining) { struct node *n; unsigned int i; heap->t = ereallocarray(NULL, ohash_entries(hash), sizeof(struct node *)); remaining->t = ereallocarray(NULL, ohash_entries(hash), sizeof(struct node *)); heap->entries = 0; remaining->entries = 0; for (n = ohash_first(hash, &i); n != NULL; n = ohash_next(hash, &i)) { if (n->refs == 0) heap->t[heap->entries++] = n; else remaining->t[remaining->entries++] = n; } } /* Good point to break a cycle: live node with as few refs as possible. */ static struct node * find_good_cycle_break(struct array *h) { unsigned int i; unsigned int best; struct node *u; best = UINT_MAX; u = NULL; assert(h->entries != 0); for (i = 0; i < h->entries; i++) { struct node *n = h->t[i]; /* No need to look further. */ if (n->refs == 1) return n; if (n->refs != 0 && n->refs < best) { best = n->refs; u = n; } } assert(u != NULL); return u; } /* Retrieve the node with the smallest order. */ static struct node * find_smallest_node(struct array *h) { unsigned int i; unsigned int best; struct node *u; best = UINT_MAX; u = NULL; assert(h->entries != 0); for (i = 0; i < h->entries; i++) { struct node *n = h->t[i]; if (n->refs != 0 && n->order < best) { best = n->order; u = n; } } assert(u != NULL); return u; } /*** *** Graph algorithms. ***/ /* Explore the nodes reachable from i to find a cycle, store it in c. * This may fail. */ static struct node * find_cycle_from(struct node *i, struct array *c) { struct node *n; n = i; /* XXX Previous cycle findings may have left this pointer non-null. */ i->from = NULL; for (;;) { /* Note that all marks are reversed before this code exits. */ n->mark = 1; if (n->traverse) n->traverse = n->traverse->next; else n->traverse = n->arcs; /* Skip over dead nodes. */ while (n->traverse && n->traverse->node->refs == 0) n->traverse = n->traverse->next; if (n->traverse) { struct node *go = n->traverse->node; if (go->mark) { c->entries = 0; for (; n != NULL && n != go; n = n->from) { c->t[c->entries++] = n; n->mark = 0; } for (; n != NULL; n = n->from) n->mark = 0; c->t[c->entries++] = go; return go; } else { go->from = n; n = go; } } else { n->mark = 0; n = n->from; if (n == NULL) return NULL; } } } /* Find a live predecessor of node n. This is a slow routine, as it needs * to go through the whole array, but it is not needed often. */ static struct node * find_predecessor(struct array *a, struct node *n) { unsigned int i; for (i = 0; i < a->entries; i++) { struct node *m; m = a->t[i]; if (m->refs != 0) { struct link *l; for (l = m->arcs; l != NULL; l = l->next) if (l->node == n) return m; } } assert(1 == 0); return NULL; } /* Traverse all strongly connected components reachable from node n. Start numbering them at o. Return the maximum order reached. Update the largest cycle found so far. */ static unsigned int traverse_node(struct node *n, unsigned int o, struct array *c) { unsigned int min, max; n->from = NULL; min = o; max = ++o; for (;;) { n->mark = o; if (DEBUG_TRAVERSE) printf("%s(%u) ", n->k, n->mark); /* Find next arc to explore. */ if (n->traverse) n->traverse = n->traverse->next; else n->traverse = n->arcs; /* Skip over dead nodes. */ while (n->traverse && n->traverse->node->refs == 0) n->traverse = n->traverse->next; /* If arc left. */ if (n->traverse) { struct node *go; go = n->traverse->node; /* Optimisation: if go->mark < min, we already * visited this strongly-connected component in * a previous pass. Hence, this can yield no new * cycle. */ /* Not part of the current path: go for it. */ if (go->mark == 0 || go->mark == min) { go->from = n; n = go; o++; if (o > max) max = o; /* Part of the current path: check cycle length. */ } else if (go->mark > min) { if (DEBUG_TRAVERSE) printf("%d\n", o - go->mark + 1); if (o - go->mark + 1 > c->entries) { struct node *t; unsigned int i; c->entries = o - go->mark + 1; i = 0; c->t[i++] = go; for (t = n; t != go; t = t->from) c->t[i++] = t; } } /* No arc left: backtrack. */ } else { n->mark = min; n = n->from; if (!n) return max; o--; } } } static void print_cycle(struct array *c) { unsigned int i; /* Printing in reverse order, since cycle discoveries finds reverse * edges. */ for (i = c->entries; i != 0;) { i--; warnx("%s", c->t[i]->k); } } static struct node * find_longest_cycle(struct array *h, struct array *c) { unsigned int i; unsigned int o; unsigned int best; struct node *n; static int notfirst = 0; assert(h->entries != 0); /* No cycle found yet. */ c->entries = 0; /* Reset the set of marks, except the first time around. */ if (notfirst) { for (i = 0; i < h->entries; i++) h->t[i]->mark = 0; } else notfirst = 1; o = 0; /* Traverse the array. Each unmarked, live node heralds a * new set of strongly connected components. */ for (i = 0; i < h->entries; i++) { n = h->t[i]; if (n->refs != 0 && n->mark == 0) { /* Each call to traverse_node uses a separate * interval of numbers to mark nodes. */ o++; o = traverse_node(n, o, c); } } assert(c->entries != 0); n = c->t[0]; best = n->refs; for (i = 0; i < c->entries; i++) { if (c->t[i]->refs < best) { n = c->t[i]; best = n->refs; } } return n; } static struct node * find_normal_cycle(struct array *h, struct array *c) { struct node *b, *n; if (hints_flag) n = find_smallest_node(h); else n = find_good_cycle_break(h); while ((b = find_cycle_from(n, c)) == NULL) n = find_predecessor(h, n); return b; } #define plural(n) ((n) > 1 ? "s" : "") static void parse_args(int argc, char *argv[], struct ohash *pairs) { int c; unsigned int order; int reverse_flag; const char **files; int i, j; i = 0; reverse_flag = quiet_flag = long_flag = warn_flag = hints_flag = verbose_flag = 0; /* argc is good enough, as we start at argv[1] */ files = ereallocarray(NULL, argc, sizeof (char *)); while ((c = getopt(argc, argv, "h:flqrvw")) != -1) { switch(c) { case 'h': files[i++] = optarg; hints_flag = 1; break; /*FALLTHRU*/ case 'f': hints_flag = 2; break; case 'l': long_flag = 1; break; case 'q': quiet_flag = 1; break; case 'r': reverse_flag = 1; break; case 'v': verbose_flag = 1; break; case 'w': warn_flag = 1; break; default: usage(); } } argc -= optind; argv += optind; switch(argc) { case 1: files[i++] = argv[0]; break; case 0: break; default: usage(); } files[i] = NULL; /* if (pledge("stdio rpath", files) == -1) */ if (pledge("stdio rpath", NULL) == -1) err(1, "pledge"); nodes_init(pairs); order = 0; for (j = 0; j != i-argc; j++) { FILE *f; f = fopen(files[j], "r"); if (f == NULL) err(1, "Can't open hint file %s", files[i]); order = read_hints(f, pairs, quiet_flag, files[i], order); fclose(f); } free(files); if (argc == 1) { FILE *f; f = fopen(argv[0], "r"); if (f == NULL) err(1, "Can't open file %s", argv[0]); order = read_pairs(f, pairs, reverse_flag, argv[0], order, hints_flag == 2); fclose(f); } else { order = read_pairs(stdin, pairs, reverse_flag, "stdin", order, hints_flag == 2); } if (pledge("stdio", NULL) == -1) err(1, "pledge"); } static int tsort(struct ohash *pairs) { struct array aux; /* Unrefed nodes/cycle reporting. */ struct array remaining; unsigned int broken_arcs, broken_cycles; unsigned int left; broken_arcs = 0; broken_cycles = 0; if (hints_flag) make_transparent(pairs); split_nodes(pairs, &aux, &remaining); ohash_delete(pairs); if (hints_flag) heapify(&aux, verbose_flag); left = remaining.entries + aux.entries; while (left != 0) { /* Standard topological sort. */ while (aux.entries) { struct link *l; struct node *n; n = DEQUEUE(&aux); printf("%s\n", n->k); left--; /* We can't free nodes, as we don't know which * entry we can remove in the hash table. We * rely on refs == 0 to recognize live nodes. * Decrease ref count of live nodes, enter new * candidates into the unrefed list. */ for (l = n->arcs; l != NULL; l = l->next) if (l->node->refs != 0 && --l->node->refs == 0) { ENQUEUE(&aux, l->node); } } /* There are still cycles to break. */ if (left != 0) { struct node *n; broken_cycles++; /* XXX Simple cycle detection and long cycle * detection are mutually exclusive. */ if (long_flag) n = find_longest_cycle(&remaining, &aux); else n = find_normal_cycle(&remaining, &aux); if (!quiet_flag) { warnx("cycle in data"); print_cycle(&aux); } if (verbose_flag) warnx("%u edge%s broken", n->refs, plural(n->refs)); broken_arcs += n->refs; n->refs = 0; /* Reinitialization, cycle reporting uses aux. */ aux.t[0] = n; aux.entries = 1; } } if (verbose_flag && broken_cycles != 0) warnx("%u cycle%s broken, for a total of %u edge%s", broken_cycles, plural(broken_cycles), broken_arcs, plural(broken_arcs)); if (warn_flag) return (broken_cycles < 256 ? broken_cycles : 255); else return (0); } int main(int argc, char *argv[]) { struct ohash pairs; if (pledge("stdio rpath", NULL) == -1) err(1, "pledge"); parse_args(argc, argv, &pairs); return tsort(&pairs); } extern char *__progname; static void usage(void) { fprintf(stderr, "Usage: %s [-flqrvw] [-h file] [file]\n", __progname); exit(1); }