busybox/coreutils/tsort.c
<<
>>
Prefs
   1/* vi: set sw=4 ts=4: */
   2/*
   3 * tsort implementation for busybox
   4 *
   5 * public domain -- David Leonard, 2022
   6 */
   7//config:config TSORT
   8//config:       bool "tsort (0.7 kb)"
   9//config:       default y
  10//config:       help
  11//config:       tsort performs a topological sort.
  12
  13//applet:IF_TSORT(APPLET(tsort, BB_DIR_USR_BIN, BB_SUID_DROP))
  14
  15//kbuild:lib-$(CONFIG_TSORT) += tsort.o
  16
  17/* BB_AUDIT SUSv3 compliant */
  18/* http://www.opengroup.org/onlinepubs/007904975/utilities/tsort.html */
  19
  20//usage:#define tsort_trivial_usage
  21//usage:       "[FILE]"
  22//usage:#define tsort_full_usage "\n\n"
  23//usage:       "Topological sort"
  24//usage:#define tsort_example_usage
  25//usage:       "$ echo -e \"a b\\nb c\" | tsort\n"
  26//usage:       "a\n"
  27//usage:       "b\n"
  28//usage:       "c\n"
  29
  30#include "libbb.h"
  31#include "common_bufsiz.h"
  32
  33struct node {
  34        unsigned in_count;
  35        unsigned out_count;
  36        struct node **out;
  37        char name[1];
  38};
  39
  40struct globals {
  41        struct node **nodes;
  42        unsigned nodes_len;
  43};
  44#define G (*(struct globals*)bb_common_bufsiz1)
  45#define INIT_G() do { \
  46        setup_common_bufsiz(); \
  47        BUILD_BUG_ON(sizeof(G) > COMMON_BUFSIZE); \
  48        G.nodes = NULL; \
  49        G.nodes_len = 0; \
  50} while (0)
  51
  52static struct node *
  53get_node(const char *name)
  54{
  55        struct node *n;
  56        unsigned a = 0;
  57        unsigned b = G.nodes_len;
  58
  59        /* Binary search for name */
  60        while (a != b) {
  61                unsigned m = (a + b) / 2;
  62                int cmp = strcmp(name, G.nodes[m]->name);
  63                if (cmp == 0)
  64                        return G.nodes[m]; /* found */
  65                if (cmp < 0) {
  66                        b = m;
  67                } else {
  68                        a = m + 1;
  69                }
  70        }
  71
  72        /* Allocate new node */
  73        n = xzalloc(sizeof(*n) + strlen(name));
  74        //n->in_count = 0;
  75        //n->out_count = 0;
  76        //n->out = NULL;
  77        strcpy(n->name, name);
  78
  79        /* Insert to maintain sort */
  80        G.nodes = xrealloc(G.nodes, (G.nodes_len + 1) * sizeof(*G.nodes));
  81        memmove(&G.nodes[a + 1], &G.nodes[a],
  82                (G.nodes_len - a) * sizeof(*G.nodes));
  83        G.nodes[a] = n;
  84        G.nodes_len++;
  85        return n;
  86}
  87
  88static void
  89add_edge(struct node *a, struct node *b)
  90{
  91        a->out = xrealloc_vector(a->out, 6, a->out_count);
  92        a->out[a->out_count++] = b;
  93        b->in_count++;
  94}
  95
  96int tsort_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
  97int tsort_main(int argc UNUSED_PARAM, char **argv)
  98{
  99        char *line;
 100        size_t linesz;
 101        ssize_t len;
 102        struct node *a;
 103        int cycles;
 104
 105        INIT_G();
 106
 107        if (argv[1]) {
 108                if (argv[2])
 109                        bb_show_usage();
 110                if (NOT_LONE_DASH(argv[1])) {
 111                        close(STDIN_FILENO); /* == 0 */
 112                        xopen(argv[1], O_RDONLY); /* fd will be 0 */
 113                }
 114        }
 115
 116        /* Read in words separated by <blank>s */
 117        a = NULL;
 118        line = NULL;
 119        linesz = 0;
 120        while ((len = getline(&line, &linesz, stdin)) != -1) {
 121                char *s = line;
 122                while (*(s = skip_whitespace(s)) != '\0') {
 123                        struct node *b;
 124                        char *word;
 125
 126                        word = s;
 127                        s = skip_non_whitespace(s);
 128                        if (*s)
 129                                *s++ = '\0';
 130
 131                        /* Create nodes and edges for each word pair */
 132                        b = get_node(word);
 133                        if (!a) {
 134                                a = b;
 135                        } else {
 136                                if (a != b)
 137                                        add_edge(a, b);
 138                                a = NULL;
 139                        }
 140                }
 141        }
 142// Most other tools do not check for input read error (treat them as EOF)
 143//      die_if_ferror(in, input_filename);
 144        if (a)
 145                bb_simple_error_msg_and_die("odd input");
 146        free(line);
 147
 148        /*
 149         * Kahn's algorithm:
 150         *   - find a node that has no incoming edges, print and remove it
 151         *   - repeat until the graph is empty
 152         *   - if any nodes are left, they form cycles.
 153         */
 154        cycles = 0;
 155        while (G.nodes_len) {
 156                struct node *n;
 157                unsigned i;
 158
 159                /* Search for first node with no incoming edges */
 160                for (i = 0; i < G.nodes_len; i++) {
 161                        if (!G.nodes[i]->in_count)
 162                                break;
 163                }
 164                if (i == G.nodes_len) {
 165                        /* Must be a cycle; arbitraily break it at node 0 */
 166                        cycles++;
 167                        i = 0;
 168#ifndef TINY
 169                        bb_error_msg("cycle at %s", G.nodes[i]->name);
 170#endif
 171                }
 172
 173                /* Remove the node (need no longer maintain sort) */
 174                n = G.nodes[i];
 175                G.nodes[i] = G.nodes[--G.nodes_len];
 176
 177                /* And remove its outgoing edges */
 178                for (i = 0; i < n->out_count; i++)
 179                        n->out[i]->in_count--;
 180                free(n->out);
 181
 182                puts(n->name);
 183                free(n);
 184        }
 185        free(G.nodes);
 186
 187        fflush_stdout_and_exit(cycles ? 1 : 0);
 188}
 189