]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - sbin/rcorder/rcorder.c
ident(1): Normalizing date format
[FreeBSD/FreeBSD.git] / sbin / rcorder / rcorder.c
1 # if 0
2 /*      $NetBSD: rcorder.c,v 1.7 2000/08/04 07:33:55 enami Exp $        */
3 #endif
4
5 /*-
6  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
7  *
8  * Copyright (c) 1998, 1999 Matthew R. Green
9  * All rights reserved.
10  * Copyright (c) 1998
11  *      Perry E. Metzger.  All rights reserved.
12  * Copyright (c) 2020
13  *     Boris N. Lytochkin. All rights reserved.
14  *
15  * Redistribution and use in source and binary forms, with or without
16  * modification, are permitted provided that the following conditions
17  * are met:
18  * 1. Redistributions of source code must retain the above copyright
19  *    notice, this list of conditions and the following disclaimer.
20  * 2. Redistributions in binary form must reproduce the above copyright
21  *    notice, this list of conditions and the following disclaimer in the
22  *    documentation and/or other materials provided with the distribution.
23  * 3. All advertising materials mentioning features or use of this software
24  *    must display the following acknowledgement:
25  *      This product includes software developed for the NetBSD Project
26  *      by Perry E. Metzger.
27  * 4. The name of the author may not be used to endorse or promote products
28  *    derived from this software without specific prior written permission.
29  *
30  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
31  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
32  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
33  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
34  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
35  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
36  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
37  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
38  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
39  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
40  */
41
42 #include <sys/types.h>
43 __FBSDID("$FreeBSD$");
44
45 #include <sys/stat.h>
46
47 #include <err.h>
48 #include <stdio.h>
49 #include <libutil.h>
50 #include <stdlib.h>
51 #include <string.h>
52 #include <unistd.h>
53 #include <libgen.h>
54 #include <stdbool.h>
55
56 #include "ealloc.h"
57 #include "sprite.h"
58 #include "hash.h"
59
60 #ifdef DEBUG
61 static int debug = 0;
62 # define        DPRINTF(args) if (debug) { fflush(stdout); fprintf args; }
63 #else
64 # define        DPRINTF(args)
65 #endif
66
67 #define REQUIRE_STR     "# REQUIRE:"
68 #define REQUIRE_LEN     (sizeof(REQUIRE_STR) - 1)
69 #define REQUIRES_STR    "# REQUIRES:"
70 #define REQUIRES_LEN    (sizeof(REQUIRES_STR) - 1)
71 #define PROVIDE_STR     "# PROVIDE:"
72 #define PROVIDE_LEN     (sizeof(PROVIDE_STR) - 1)
73 #define PROVIDES_STR    "# PROVIDES:"
74 #define PROVIDES_LEN    (sizeof(PROVIDES_STR) - 1)
75 #define BEFORE_STR      "# BEFORE:"
76 #define BEFORE_LEN      (sizeof(BEFORE_STR) - 1)
77 #define KEYWORD_STR     "# KEYWORD:"
78 #define KEYWORD_LEN     (sizeof(KEYWORD_STR) - 1)
79 #define KEYWORDS_STR    "# KEYWORDS:"
80 #define KEYWORDS_LEN    (sizeof(KEYWORDS_STR) - 1)
81
82 #define FAKE_PROV_NAME  "fake_prov_"
83
84 static int exit_code;
85 static int file_count;
86 static char **file_list;
87
88 #define TRUE 1
89 #define FALSE 0
90 typedef bool flag;
91 #define SET TRUE
92 #define RESET FALSE
93
94 static flag do_graphviz = false;
95 static flag do_parallel = false;
96
97 static Hash_Table provide_hash_s, *provide_hash;
98
99 typedef struct provnode provnode;
100 typedef struct filenode filenode;
101 typedef struct f_provnode f_provnode;
102 typedef struct f_reqnode f_reqnode;
103 typedef struct strnodelist strnodelist;
104
105 struct provnode {
106         flag            head;
107         flag            in_progress;
108         int             sequence;
109         filenode        *fnode;
110         provnode        *next, *last;
111 };
112
113 struct f_provnode {
114         provnode        *pnode;
115         Hash_Entry      *entry;
116         f_provnode      *next;
117 };
118
119 struct f_reqnode {
120         Hash_Entry      *entry;
121         f_reqnode       *next;
122 };
123
124 struct strnodelist {
125         filenode        *node;
126         strnodelist     *next;
127         char            s[1];
128 };
129
130 struct filenode {
131         char            *filename;
132         flag            in_progress;
133         filenode        *next, *last;
134         f_reqnode       *req_list;
135         f_provnode      *prov_list;
136         strnodelist     *keyword_list;
137         int             issues_count;
138         int             sequence;
139 };
140
141 static filenode fn_head_s, *fn_head, **fn_seqlist;
142 static int max_sequence = 0;
143
144 static strnodelist *bl_list;
145 static strnodelist *keep_list;
146 static strnodelist *skip_list;
147
148 static void do_file(filenode *fnode, strnodelist *);
149 static void strnode_add(strnodelist **, char *, filenode *);
150 static int skip_ok(filenode *fnode);
151 static int keep_ok(filenode *fnode);
152 static char *generate_loop_for_req(strnodelist *, provnode *, filenode *);
153 static void satisfy_req(f_reqnode *rnode, filenode *fnode, strnodelist *);
154 static void crunch_file(char *);
155 static void parse_require(filenode *, char *);
156 static void parse_provide(filenode *, char *);
157 static void parse_before(filenode *, char *);
158 static void parse_keywords(filenode *, char *);
159 static filenode *filenode_new(char *);
160 static void add_require(filenode *, char *);
161 static void add_provide(filenode *, char *);
162 static void add_before(filenode *, char *);
163 static void add_keyword(filenode *, char *);
164 static void insert_before(void);
165 static Hash_Entry *make_fake_provision(filenode *);
166 static void crunch_all_files(void);
167 static void initialize(void);
168 static void generate_graphviz_header(void);
169 static void generate_graphviz_footer(void);
170 static void generate_graphviz_file_links(Hash_Entry *, filenode *);
171 static void generate_graphviz_providers(void);
172 static inline int is_fake_prov(const char *);
173 static int sequence_cmp(const void *, const void *);
174 static void generate_ordering(void);
175
176 int
177 main(int argc, char *argv[])
178 {
179         int ch;
180
181         while ((ch = getopt(argc, argv, "dgk:ps:")) != -1)
182                 switch (ch) {
183                 case 'd':
184 #ifdef DEBUG
185                         debug = 1;
186 #else
187                         warnx("debugging not compiled in, -d ignored");
188 #endif
189                         break;
190                 case 'g':
191                         do_graphviz = true;
192                         break;
193                 case 'k':
194                         strnode_add(&keep_list, optarg, 0);
195                         break;
196                 case 'p':
197                         do_parallel = true;
198                         break;
199                 case 's':
200                         strnode_add(&skip_list, optarg, 0);
201                         break;
202                 default:
203                         /* XXX should crunch it? */
204                         break;
205                 }
206         argc -= optind;
207         argv += optind;
208
209         file_count = argc;
210         file_list = argv;
211
212         DPRINTF((stderr, "parse_args\n"));
213         initialize();
214         DPRINTF((stderr, "initialize\n"));
215         generate_graphviz_header();
216         crunch_all_files();
217         DPRINTF((stderr, "crunch_all_files\n"));
218         generate_graphviz_providers();
219         generate_ordering();
220         DPRINTF((stderr, "generate_ordering\n"));
221         generate_graphviz_footer();
222
223         exit(exit_code);
224 }
225
226 /*
227  * initialise various variables.
228  */
229 static void
230 initialize(void)
231 {
232
233         fn_head = &fn_head_s;
234
235         provide_hash = &provide_hash_s;
236         Hash_InitTable(provide_hash, file_count);
237 }
238
239 /* generic function to insert a new strnodelist element */
240 static void
241 strnode_add(strnodelist **listp, char *s, filenode *fnode)
242 {
243         strnodelist *ent;
244
245         ent = emalloc(sizeof *ent + strlen(s));
246         ent->node = fnode;
247         strcpy(ent->s, s);
248         ent->next = *listp;
249         *listp = ent;
250 }
251
252 /*
253  * below are the functions that deal with creating the lists
254  * from the filename's given dependencies and provisions
255  * in each of these files.  no ordering or checking is done here.
256  */
257
258 /*
259  * we have a new filename, create a new filenode structure.
260  * fill in the bits, and put it in the filenode linked list
261  */
262 static filenode *
263 filenode_new(char *filename)
264 {
265         filenode *temp;
266
267         temp = emalloc(sizeof(*temp));
268         memset(temp, 0, sizeof(*temp));
269         temp->filename = estrdup(filename);
270         temp->req_list = NULL;
271         temp->prov_list = NULL;
272         temp->keyword_list = NULL;
273         temp->in_progress = RESET;
274         /*
275          * link the filenode into the list of filenodes.
276          * note that the double linking means we can delete a
277          * filenode without searching for where it belongs.
278          */
279         temp->next = fn_head->next;
280         if (temp->next != NULL)
281                 temp->next->last = temp;
282         temp->last = fn_head;
283         fn_head->next = temp;
284         return (temp);
285 }
286
287 /*
288  * add a requirement to a filenode.
289  */
290 static void
291 add_require(filenode *fnode, char *s)
292 {
293         Hash_Entry *entry;
294         f_reqnode *rnode;
295         int new;
296
297         entry = Hash_CreateEntry(provide_hash, s, &new);
298         if (new)
299                 Hash_SetValue(entry, NULL);
300         rnode = emalloc(sizeof(*rnode));
301         rnode->entry = entry;
302         rnode->next = fnode->req_list;
303         fnode->req_list = rnode;
304 }
305
306 /*
307  * add a provision to a filenode.  if this provision doesn't
308  * have a head node, create one here.
309  */
310 static void
311 add_provide(filenode *fnode, char *s)
312 {
313         Hash_Entry *entry;
314         f_provnode *f_pnode;
315         provnode *pnode, *head;
316         int new;
317
318         entry = Hash_CreateEntry(provide_hash, s, &new);
319         head = Hash_GetValue(entry);
320
321         /* create a head node if necessary. */
322         if (head == NULL) {
323                 head = emalloc(sizeof(*head));
324                 head->head = SET;
325                 head->in_progress = RESET;
326                 head->fnode = NULL;
327                 head->sequence = 0;
328                 head->last = head->next = NULL;
329                 Hash_SetValue(entry, head);
330         }
331 #if 0
332         /*
333          * Don't warn about this.  We want to be able to support
334          * scripts that do two complex things:
335          *
336          *      - Two independent scripts which both provide the
337          *        same thing.  Both scripts must be executed in
338          *        any order to meet the barrier.  An example:
339          *
340          *              Script 1:
341          *
342          *                      PROVIDE: mail
343          *                      REQUIRE: LOGIN
344          *
345          *              Script 2:
346          *
347          *                      PROVIDE: mail
348          *                      REQUIRE: LOGIN
349          *
350          *      - Two interdependent scripts which both provide the
351          *        same thing.  Both scripts must be executed in
352          *        graph order to meet the barrier.  An example:
353          *
354          *              Script 1:
355          *
356          *                      PROVIDE: nameservice dnscache
357          *                      REQUIRE: SERVERS
358          *
359          *              Script 2:
360          *
361          *                      PROVIDE: nameservice nscd
362          *                      REQUIRE: dnscache
363          */
364         else if (new == 0) {
365                 warnx("file `%s' provides `%s'.", fnode->filename, s);
366                 warnx("\tpreviously seen in `%s'.",
367                     head->next->fnode->filename);
368         }
369 #endif
370
371         pnode = emalloc(sizeof(*pnode));
372         pnode->head = RESET;
373         pnode->in_progress = RESET;
374         pnode->fnode = fnode;
375         pnode->next = head->next;
376         pnode->last = head;
377         head->next = pnode;
378         if (pnode->next != NULL)
379                 pnode->next->last = pnode;
380
381         f_pnode = emalloc(sizeof(*f_pnode));
382         f_pnode->pnode = pnode;
383         f_pnode->entry = entry;
384         f_pnode->next = fnode->prov_list;
385         fnode->prov_list = f_pnode;
386 }
387
388 /*
389  * put the BEFORE: lines to a list and handle them later.
390  */
391 static void
392 add_before(filenode *fnode, char *s)
393 {
394         strnodelist *bf_ent;
395
396         bf_ent = emalloc(sizeof *bf_ent + strlen(s));
397         bf_ent->node = fnode;
398         strcpy(bf_ent->s, s);
399         bf_ent->next = bl_list;
400         bl_list = bf_ent;
401 }
402
403 /*
404  * add a key to a filenode.
405  */
406 static void
407 add_keyword(filenode *fnode, char *s)
408 {
409
410         strnode_add(&fnode->keyword_list, s, fnode);
411 }
412
413 /*
414  * loop over the rest of a REQUIRE line, giving each word to
415  * add_require() to do the real work.
416  */
417 static void
418 parse_require(filenode *node, char *buffer)
419 {
420         char *s;
421         
422         while ((s = strsep(&buffer, " \t\n")) != NULL)
423                 if (*s != '\0')
424                         add_require(node, s);
425 }
426
427 /*
428  * loop over the rest of a PROVIDE line, giving each word to
429  * add_provide() to do the real work.
430  */
431 static void
432 parse_provide(filenode *node, char *buffer)
433 {
434         char *s;
435         
436         while ((s = strsep(&buffer, " \t\n")) != NULL)
437                 if (*s != '\0')
438                         add_provide(node, s);
439 }
440
441 /*
442  * loop over the rest of a BEFORE line, giving each word to
443  * add_before() to do the real work.
444  */
445 static void
446 parse_before(filenode *node, char *buffer)
447 {
448         char *s;
449         
450         while ((s = strsep(&buffer, " \t\n")) != NULL)
451                 if (*s != '\0')
452                         add_before(node, s);
453 }
454
455 /*
456  * loop over the rest of a KEYWORD line, giving each word to
457  * add_keyword() to do the real work.
458  */
459 static void
460 parse_keywords(filenode *node, char *buffer)
461 {
462         char *s;
463         
464         while ((s = strsep(&buffer, " \t\n")) != NULL)
465                 if (*s != '\0')
466                         add_keyword(node, s);
467 }
468
469 /*
470  * given a file name, create a filenode for it, read in lines looking
471  * for provision and requirement lines, building the graphs as needed.
472  */
473 static void
474 crunch_file(char *filename)
475 {
476         FILE *fp;
477         char *buf;
478         int require_flag, provide_flag, before_flag, keywords_flag;
479         enum { BEFORE_PARSING, PARSING, PARSING_DONE } state;
480         filenode *node;
481         char delims[3] = { '\\', '\\', '\0' };
482         struct stat st;
483
484         if ((fp = fopen(filename, "r")) == NULL) {
485                 warn("could not open %s", filename);
486                 return;
487         }
488
489         if (fstat(fileno(fp), &st) == -1) {
490                 warn("could not stat %s", filename);
491                 fclose(fp);
492                 return;
493         }
494
495         if (!S_ISREG(st.st_mode)) {
496 #if 0
497                 warnx("%s is not a file", filename);
498 #endif
499                 fclose(fp);
500                 return;
501         }
502
503         node = filenode_new(filename);
504
505         /*
506          * we don't care about length, line number, don't want # for comments,
507          * and have no flags.
508          */
509         for (state = BEFORE_PARSING; state != PARSING_DONE &&
510             (buf = fparseln(fp, NULL, NULL, delims, 0)) != NULL; free(buf)) {
511                 require_flag = provide_flag = before_flag = keywords_flag = 0;
512                 if (strncmp(REQUIRE_STR, buf, REQUIRE_LEN) == 0)
513                         require_flag = REQUIRE_LEN;
514                 else if (strncmp(REQUIRES_STR, buf, REQUIRES_LEN) == 0)
515                         require_flag = REQUIRES_LEN;
516                 else if (strncmp(PROVIDE_STR, buf, PROVIDE_LEN) == 0)
517                         provide_flag = PROVIDE_LEN;
518                 else if (strncmp(PROVIDES_STR, buf, PROVIDES_LEN) == 0)
519                         provide_flag = PROVIDES_LEN;
520                 else if (strncmp(BEFORE_STR, buf, BEFORE_LEN) == 0)
521                         before_flag = BEFORE_LEN;
522                 else if (strncmp(KEYWORD_STR, buf, KEYWORD_LEN) == 0)
523                         keywords_flag = KEYWORD_LEN;
524                 else if (strncmp(KEYWORDS_STR, buf, KEYWORDS_LEN) == 0)
525                         keywords_flag = KEYWORDS_LEN;
526                 else {
527                         if (state == PARSING)
528                                 state = PARSING_DONE;
529                         continue;
530                 }
531
532                 state = PARSING;
533                 if (require_flag)
534                         parse_require(node, buf + require_flag);
535                 else if (provide_flag)
536                         parse_provide(node, buf + provide_flag);
537                 else if (before_flag)
538                         parse_before(node, buf + before_flag);
539                 else if (keywords_flag)
540                         parse_keywords(node, buf + keywords_flag);
541         }
542         fclose(fp);
543 }
544
545 static Hash_Entry *
546 make_fake_provision(filenode *node)
547 {
548         Hash_Entry *entry;
549         f_provnode *f_pnode;
550         provnode *head, *pnode;
551         static  int i = 0;
552         int     new;
553         char buffer[30];
554
555         do {
556                 snprintf(buffer, sizeof buffer, FAKE_PROV_NAME "%08d", i++);
557                 entry = Hash_CreateEntry(provide_hash, buffer, &new);
558         } while (new == 0);
559         head = emalloc(sizeof(*head));
560         head->head = SET;
561         head->in_progress = RESET;
562         head->fnode = NULL;
563         head->last = head->next = NULL;
564         Hash_SetValue(entry, head);
565
566         pnode = emalloc(sizeof(*pnode));
567         pnode->head = RESET;
568         pnode->in_progress = RESET;
569         pnode->fnode = node;
570         pnode->next = head->next;
571         pnode->last = head;
572         head->next = pnode;
573         if (pnode->next != NULL)
574                 pnode->next->last = pnode;
575
576         f_pnode = emalloc(sizeof(*f_pnode));
577         f_pnode->entry = entry;
578         f_pnode->pnode = pnode;
579         f_pnode->next = node->prov_list;
580         node->prov_list = f_pnode;
581
582         return (entry);
583 }
584
585 /*
586  * go through the BEFORE list, inserting requirements into the graph(s)
587  * as required.  in the before list, for each entry B, we have a file F
588  * and a string S.  we create a "fake" provision (P) that F provides.
589  * for each entry in the provision list for S, add a requirement to
590  * that provisions filenode for P.
591  */
592 static void
593 insert_before(void)
594 {
595         Hash_Entry *entry, *fake_prov_entry;
596         provnode *pnode;
597         f_reqnode *rnode;
598         strnodelist *bl;
599         int new;
600         
601         while (bl_list != NULL) {
602                 bl = bl_list->next;
603
604                 fake_prov_entry = make_fake_provision(bl_list->node);
605
606                 entry = Hash_CreateEntry(provide_hash, bl_list->s, &new);
607                 if (new == 1)
608                         warnx("file `%s' is before unknown provision `%s'", bl_list->node->filename, bl_list->s);
609
610                 if (new == 1 && do_graphviz == true)
611                         generate_graphviz_file_links(
612                             Hash_FindEntry(provide_hash, bl_list->s),
613                             bl_list->node);
614
615                 for (pnode = Hash_GetValue(entry); pnode; pnode = pnode->next) {
616                         if (pnode->head)
617                                 continue;
618
619                         rnode = emalloc(sizeof(*rnode));
620                         rnode->entry = fake_prov_entry;
621                         rnode->next = pnode->fnode->req_list;
622                         pnode->fnode->req_list = rnode;
623                 }
624
625                 free(bl_list);
626                 bl_list = bl;
627         }
628 }
629
630 /*
631  * loop over all the files calling crunch_file() on them to do the
632  * real work.  after we have built all the nodes, insert the BEFORE:
633  * lines into graph(s).
634  */
635 static void
636 crunch_all_files(void)
637 {
638         int i;
639         
640         for (i = 0; i < file_count; i++)
641                 crunch_file(file_list[i]);
642         insert_before();
643 }
644
645 static inline int
646 is_fake_prov(const char *name)
647 {
648
649         return (name == strstr(name, FAKE_PROV_NAME));
650 }
651
652 /* loop though provide list of vnode drawing all non-fake dependencies */
653 static void
654 generate_graphviz_file_links(Hash_Entry *entry, filenode *fnode)
655 {
656         char *dep_name, *fname;
657         provnode *head;
658         f_provnode *fpnode, *rfpnode;
659         int is_before = 0;
660
661         dep_name = Hash_GetKey(entry);
662         if (is_fake_prov(dep_name))
663                 is_before = 1;
664         head = Hash_GetValue(entry);
665
666         for (fpnode = fnode->prov_list; fpnode && fpnode->entry;
667             fpnode = fpnode->next) {
668                 fname = Hash_GetKey(fpnode->entry);
669                 if (is_fake_prov(fname))
670                         continue;
671                 rfpnode = NULL;
672                 do {
673                         if (rfpnode)
674                                 dep_name = Hash_GetKey(rfpnode->entry);
675                         else
676                                 dep_name = Hash_GetKey(entry);
677
678                         if (!is_fake_prov(dep_name)) {
679                                 printf("\"%s\" -> \"%s\" [%s%s];\n",
680                                     fname, dep_name,
681                                     /* edge style */
682                                     (is_before ? "style=dashed" : "style=solid"),
683                                     /* circular dep? */
684                                     ((head == NULL ||
685                                     (head->next && head->in_progress == SET)) ?
686                                     ", color=red, penwidth=4" : ""));
687                                 if (rfpnode == NULL)
688                                         break;
689                         }
690                         /* dependency is solved already */
691                         if (head == NULL || head->next == NULL)
692                                 break;
693
694                         if (rfpnode == NULL)
695                                 rfpnode = head->next->fnode->prov_list;
696                         else
697                                 rfpnode = rfpnode->next;
698                 } while (rfpnode);
699         }
700 }
701
702 /*
703  * Walk the stack, find the looping point and generate traceback.
704  * NULL is returned on failure, otherwize pointer to a buffer holding
705  * text representation is returned, caller must run free(3) for the
706  * pointer.
707  */
708 static char *
709 generate_loop_for_req(strnodelist *stack_tail, provnode *head,
710     filenode *fnode)
711 {
712         provnode *pnode;
713         strnodelist *stack_ptr, *loop_entry;
714         char *buf, **revstack;
715         size_t bufsize;
716         int i, stack_depth;
717
718         loop_entry = NULL;
719         /* fast forward stack to the component that is required now */
720         for (pnode = head->next; pnode; pnode = pnode->next) {
721                 if (loop_entry)
722                         break;
723                 stack_depth = 0;
724                 for (stack_ptr = stack_tail; stack_ptr;
725                     stack_ptr = stack_ptr->next) {
726                         stack_depth++;
727                         if (stack_ptr->node == pnode->fnode) {
728                                 loop_entry = stack_ptr;
729                                 break;
730                         }
731                 }
732         }
733
734         if (loop_entry == NULL)
735                 return (NULL);
736
737         stack_depth += 2; /* fnode + loop_entry */
738         revstack = emalloc(sizeof(char *) * stack_depth);
739         bzero(revstack, (sizeof(char *) * stack_depth));
740
741         /* reverse stack and estimate buffer size to allocate */
742         bufsize = 1; /* tralining \0 */
743
744         revstack[stack_depth - 1] = loop_entry->node->filename;
745         bufsize += strlen(revstack[stack_depth - 1]);
746
747         revstack[stack_depth - 2] = fnode->filename;
748         bufsize += strlen(revstack[stack_depth - 2]);
749         fnode->issues_count++;
750
751         stack_ptr = stack_tail;
752         for (i = stack_depth - 3; i >= 0; i--) {
753                 revstack[i] = stack_ptr->node->filename;
754                 stack_ptr->node->issues_count++;
755                 stack_ptr = stack_ptr->next;
756                 bufsize += strlen(revstack[i]);
757         }
758         bufsize += strlen(" -> ") * (stack_depth - 1);
759
760         buf = emalloc(bufsize);
761         bzero(buf, bufsize);
762
763         for (i = 0; i < stack_depth; i++) {
764                 strlcat(buf, revstack[i], bufsize);
765                 if (i < stack_depth - 1)
766                         strlcat(buf, " -> ", bufsize);
767         }
768
769         free(revstack);
770         return (buf);
771 }
772 /*
773  * below are the functions that traverse the graphs we have built
774  * finding out the desired ordering, printing each file in turn.
775  * if missing requirements, or cyclic graphs are detected, a
776  * warning will be issued, and we will continue on..
777  */
778
779 /*
780  * given a requirement node (in a filename) we attempt to satisfy it.
781  * we do some sanity checking first, to ensure that we have providers,
782  * aren't already satisfied and aren't already being satisfied (ie,
783  * cyclic).  if we pass all this, we loop over the provision list
784  * calling do_file() (enter recursion) for each filenode in this
785  * provision.
786  */
787 static void
788 satisfy_req(f_reqnode *rnode, filenode *fnode, strnodelist *stack_ptr)
789 {
790         Hash_Entry *entry;
791         provnode *head;
792         strnodelist stack_item;
793         char *buf;
794
795         entry = rnode->entry;
796         head = Hash_GetValue(entry);
797
798         if (do_graphviz == true)
799                 generate_graphviz_file_links(entry, fnode);
800
801         if (head == NULL) {
802                 warnx("requirement `%s' in file `%s' has no providers.",
803                     Hash_GetKey(entry), fnode->filename);
804                 exit_code = 1;
805                 return;
806         }
807
808         /* return if the requirement is already satisfied. */
809         if (head->next == NULL)
810                 return;
811
812         /* 
813          * if list is marked as in progress,
814          *      print that there is a circular dependency on it and abort
815          */
816         if (head->in_progress == SET) {
817                 exit_code = 1;
818                 buf = generate_loop_for_req(stack_ptr, head,
819                     fnode);
820
821                 if (buf == NULL) {
822                         warnx("Circular dependency on provision `%s' in "
823                             "file `%s' (tracing has failed).",
824                             Hash_GetKey(entry), fnode->filename);
825                         return;
826                 }
827
828                 warnx("Circular dependency on provision `%s': %s.",
829                     Hash_GetKey(entry), buf);
830                 free(buf);
831                 return;
832         }
833
834         head->in_progress = SET;
835         
836         stack_item.next = stack_ptr;
837         stack_item.node = fnode;
838
839         /*
840          * while provision_list is not empty
841          *      do_file(first_member_of(provision_list));
842          */
843         while (head->next != NULL)
844                 do_file(head->next->fnode, &stack_item);
845 }
846
847 static int
848 skip_ok(filenode *fnode)
849 {
850         strnodelist *s;
851         strnodelist *k;
852
853         for (s = skip_list; s; s = s->next)
854                 for (k = fnode->keyword_list; k; k = k->next)
855                         if (strcmp(k->s, s->s) == 0)
856                                 return (0);
857
858         return (1);
859 }
860
861 static int
862 keep_ok(filenode *fnode)
863 {
864         strnodelist *s;
865         strnodelist *k;
866
867         for (s = keep_list; s; s = s->next)
868                 for (k = fnode->keyword_list; k; k = k->next)
869                         if (strcmp(k->s, s->s) == 0)
870                                 return (1);
871
872         /* an empty keep_list means every one */
873         return (!keep_list);
874 }
875
876 /*
877  * given a filenode, we ensure we are not a cyclic graph.  if this
878  * is ok, we loop over the filenodes requirements, calling satisfy_req()
879  * for each of them.. once we have done this, remove this filenode
880  * from each provision table, as we are now done.
881  *
882  * NOTE: do_file() is called recursively from several places and cannot
883  * safely free() anything related to items that may be recursed on.
884  * Circular dependencies will cause problems if we do.
885  */
886 static void
887 do_file(filenode *fnode, strnodelist *stack_ptr)
888 {
889         f_reqnode *r;
890         f_provnode *p, *p_tmp;
891         provnode *pnode, *head;
892         int was_set;    
893         char *dep_name;
894
895         DPRINTF((stderr, "do_file on %s.\n", fnode->filename));
896
897         /*
898          * if fnode is marked as in progress,
899          *       print that fnode; is circularly depended upon and abort.
900          */
901         if (fnode->in_progress == SET) {
902                 warnx("Circular dependency on file `%s'.",
903                         fnode->filename);
904                 was_set = exit_code = 1;
905         } else
906                 was_set = 0;
907
908         /* mark fnode */
909         fnode->in_progress = SET;
910
911         /*
912          * for each requirement of fnode -> r
913          *      satisfy_req(r, filename)
914          */
915         r = fnode->req_list;
916         fnode->sequence = 0;
917         while (r != NULL) {
918                 satisfy_req(r, fnode, stack_ptr);
919                 /* find sequence number where all requirements are satisfied */
920                 head = Hash_GetValue(r->entry);
921                 if (head && head->sequence > fnode->sequence)
922                         fnode->sequence = head->sequence;
923                 r = r->next;
924         }
925         fnode->req_list = NULL;
926         fnode->sequence++;
927
928         /* if we've seen issues with this file - put it to the tail */
929         if (fnode->issues_count)
930                 fnode->sequence = max_sequence + 1;
931
932         if (max_sequence < fnode->sequence)
933                 max_sequence = fnode->sequence;
934
935         /*
936          * for each provision of fnode -> p
937          *      remove fnode from provision list for p in hash table
938          */
939         p = fnode->prov_list;
940         while (p != NULL) {
941                 /* mark all troublemakers on graphviz */
942                 if (do_graphviz == true && fnode->issues_count) {
943                         dep_name = Hash_GetKey(p->entry);
944                         if (!is_fake_prov(dep_name))
945                                 printf("\"%s\" [ color=red, penwidth=4 ];\n",
946                                     dep_name);
947                 }
948
949                 /* update sequence when provided requirements are satisfied */
950                 head = Hash_GetValue(p->entry);
951                 if (head->sequence < fnode->sequence)
952                         head->sequence = fnode->sequence;
953
954                 p_tmp = p;
955                 pnode = p->pnode;
956                 if (pnode->next != NULL) {
957                         pnode->next->last = pnode->last;
958                 }
959                 if (pnode->last != NULL) {
960                         pnode->last->next = pnode->next;
961                 }
962                 free(pnode);
963                 p = p->next;
964                 free(p_tmp);
965         }
966         fnode->prov_list = NULL;
967
968         /* do_it(fnode) */
969         DPRINTF((stderr, "next do: "));
970
971         /* if we were already in progress, don't print again */
972         if (do_graphviz != true && was_set == 0 && skip_ok(fnode) &&
973             keep_ok(fnode)) {
974                 *fn_seqlist = fnode;
975                 fn_seqlist++;
976         }
977         
978         if (fnode->next != NULL) {
979                 fnode->next->last = fnode->last;
980         }
981         if (fnode->last != NULL) {
982                 fnode->last->next = fnode->next;
983         }
984
985         if (fnode->issues_count)
986                 warnx("`%s' was seen in circular dependencies for %d times.",
987                     fnode->filename, fnode->issues_count);
988
989         DPRINTF((stderr, "nuking %s\n", fnode->filename));
990 }
991
992 static void
993 generate_graphviz_header(void)
994 {
995
996         if (do_graphviz != true)
997                 return;
998
999         printf("digraph rcorder {\n"
1000             "rankdir=\"BT\";\n"
1001             "node [style=rounded, shape=record];\n"
1002             "graph [overlap = false];\n");
1003 }
1004
1005 static void
1006 generate_graphviz_footer(void)
1007 {
1008
1009         if (do_graphviz == true)
1010                 printf("}\n");
1011 }
1012
1013 static void
1014 generate_graphviz_providers(void)
1015 {
1016         Hash_Entry *entry;
1017         Hash_Search psearch;
1018         provnode *head, *pnode;
1019         char *dep_name;
1020
1021         if (do_graphviz != true)
1022                 return;
1023
1024         entry = Hash_EnumFirst(provide_hash, &psearch);
1025         if (entry == NULL)
1026                 return;
1027
1028         do {
1029                 dep_name = Hash_GetKey(entry);
1030                 if (is_fake_prov(dep_name))
1031                         continue;
1032                 head = Hash_GetValue(entry);
1033                 /* no providers for this requirement */
1034                 if (head == NULL || head->next == NULL) {
1035                         printf("\"%s\" [label=\"{ %s | ENOENT }\", "
1036                             "style=\"rounded,filled\", color=red];\n",
1037                             dep_name, dep_name);
1038                         continue;
1039                 }
1040                 /* one PROVIDE word for one file that matches */
1041                 if (head->next->next == NULL &&
1042                     strcmp(dep_name,
1043                     basename(head->next->fnode->filename)) == 0) {
1044                         continue;
1045                 }
1046                 printf("\"%s\" [label=\"{ %s | ", dep_name, dep_name);
1047                 for (pnode = head->next; pnode; pnode = pnode->next)
1048                         printf("%s\\n", basename(pnode->fnode->filename));
1049
1050                 printf("}\"];\n");
1051         } while (NULL != (entry = Hash_EnumNext(&psearch)));
1052 }
1053
1054 static int
1055 sequence_cmp(const void *a, const void *b)
1056 {
1057         const filenode *fna = *((const filenode * const *)a);
1058         const filenode *fnb = *((const filenode * const *)b);
1059         int left, right;
1060
1061         /* push phantom files to the end */
1062         if (fna == NULL || fnb == NULL)
1063                 return ((fna < fnb) - (fna > fnb));
1064
1065         left =  fna->sequence;
1066         right = fnb->sequence;
1067
1068         return ((left > right) - (left < right));
1069 }
1070
1071 static void
1072 generate_ordering(void)
1073 {
1074         filenode **seqlist, **psl;
1075         int last_seq = 0;
1076
1077         /* Prepare order buffer, use an additional one as a list terminator */
1078         seqlist = emalloc(sizeof(filenode *) * (file_count + 1));
1079         bzero(seqlist, sizeof(filenode *) * (file_count + 1));
1080         fn_seqlist = seqlist;
1081
1082         /*
1083          * while there remain undone files{f},
1084          *      pick an arbitrary f, and do_file(f)
1085          * Note that the first file in the file list is perfectly
1086          * arbitrary, and easy to find, so we use that.
1087          */
1088
1089         /*
1090          * N.B.: the file nodes "self delete" after they execute, so
1091          * after each iteration of the loop, the head will be pointing
1092          * to something totally different. The loop ends up being
1093          * executed only once for every strongly connected set of
1094          * nodes.
1095          */
1096         while (fn_head->next != NULL) {
1097                 DPRINTF((stderr, "generate on %s\n", fn_head->next->filename));
1098                 do_file(fn_head->next, NULL);
1099         }
1100
1101         /* Sort filenode list based on sequence */
1102         qsort(seqlist, file_count, sizeof(filenode *), sequence_cmp);
1103
1104         for (psl = seqlist; *psl; psl++) {
1105                 printf("%s%s",
1106                     (last_seq == 0 ? "" :
1107                     (do_parallel != true || last_seq != (*psl)->sequence) ?
1108                     "\n" : " "),
1109                 (*psl)->filename);
1110                 last_seq = (*psl)->sequence;
1111                 free((*psl)->filename);
1112                 free(*psl);
1113         }
1114         if (last_seq)
1115                 printf("\n");
1116
1117         free(seqlist);
1118 }