File: [local] / src / usr.bin / tsort / tsort.c (download)
Revision 1.35, Tue Jan 5 16:10:57 2016 UTC (8 years, 5 months ago) by espie
Branch: MAIN
CVS Tags: OPENBSD_6_1_BASE, OPENBSD_6_1, OPENBSD_6_0_BASE, OPENBSD_6_0, OPENBSD_5_9_BASE, OPENBSD_5_9 Changes since 1.34: +3 -2 lines
stuff may still change, disable whitelist for now.
ok semarie@
|
/* $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 <espie@openbsd.org>
*
* 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 <assert.h>
#include <ctype.h>
#include <err.h>
#include <limits.h>
#include <stddef.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <ohash.h>
/* 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);
}