tsort.c 4.2 KB

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