1 /* $NetBSD: rcorder.c,v 1.18 2016/09/05 01:09:57 sevan Exp $ */
4 * Copyright (c) 1998, 1999 Matthew R. Green
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
21 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 * Perry E. Metzger. All rights reserved.
32 * Redistribution and use in source and binary forms, with or without
33 * modification, are permitted provided that the following conditions
35 * 1. Redistributions of source code must retain the above copyright
36 * notice, this list of conditions and the following disclaimer.
37 * 2. Redistributions in binary form must reproduce the above copyright
38 * notice, this list of conditions and the following disclaimer in the
39 * documentation and/or other materials provided with the distribution.
40 * 3. All advertising materials mentioning features or use of this software
41 * must display the following acknowledgement:
42 * This product includes software developed for the NetBSD Project
43 * by Perry E. Metzger.
44 * 4. The name of the author may not be used to endorse or promote products
45 * derived from this software without specific prior written permission.
47 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
48 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
49 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
50 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
51 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
52 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
53 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
54 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
55 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
56 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
59 #include <sys/types.h>
73 # define DPRINTF(args) if (debug) { fflush(stdout); fprintf args; }
75 # define DPRINTF(args)
78 #define REQUIRE_STR "# REQUIRE:"
79 #define REQUIRE_LEN (sizeof(REQUIRE_STR) - 1)
80 #define REQUIRES_STR "# REQUIRES:"
81 #define REQUIRES_LEN (sizeof(REQUIRES_STR) - 1)
82 #define PROVIDE_STR "# PROVIDE:"
83 #define PROVIDE_LEN (sizeof(PROVIDE_STR) - 1)
84 #define PROVIDES_STR "# PROVIDES:"
85 #define PROVIDES_LEN (sizeof(PROVIDES_STR) - 1)
86 #define BEFORE_STR "# BEFORE:"
87 #define BEFORE_LEN (sizeof(BEFORE_STR) - 1)
88 #define KEYWORD_STR "# KEYWORD:"
89 #define KEYWORD_LEN (sizeof(KEYWORD_STR) - 1)
90 #define KEYWORDS_STR "# KEYWORDS:"
91 #define KEYWORDS_LEN (sizeof(KEYWORDS_STR) - 1)
102 Hash_Table provide_hash_s
, *provide_hash
;
104 typedef struct provnode provnode
;
105 typedef struct filenode filenode
;
106 typedef struct f_provnode f_provnode
;
107 typedef struct f_reqnode f_reqnode
;
108 typedef struct strnodelist strnodelist
;
114 provnode
*next
, *last
;
136 filenode
*next
, *last
;
138 f_provnode
*prov_list
;
139 strnodelist
*keyword_list
;
142 filenode fn_head_s
, *fn_head
;
144 strnodelist
*bl_list
;
145 strnodelist
*keep_list
;
146 strnodelist
*skip_list
;
148 void do_file(filenode
*fnode
);
149 void strnode_add(strnodelist
**, char *, filenode
*);
150 int skip_ok(filenode
*fnode
);
151 int keep_ok(filenode
*fnode
);
152 void satisfy_req(f_reqnode
*rnode
, char *);
153 void crunch_file(char *);
154 void parse_line(filenode
*, char *, void (*)(filenode
*, char *));
155 filenode
*filenode_new(char *);
156 void add_require(filenode
*, char *);
157 void add_provide(filenode
*, char *);
158 void add_before(filenode
*, char *);
159 void add_keyword(filenode
*, char *);
160 void insert_before(void);
161 Hash_Entry
*make_fake_provision(filenode
*);
162 void crunch_all_files(void);
163 void initialize(void);
164 void generate_ordering(void);
167 main(int argc
, char *argv
[])
171 while ((ch
= getopt(argc
, argv
, "dk:s:")) != -1)
177 warnx("debugging not compiled in, -d ignored");
181 strnode_add(&keep_list
, optarg
, 0);
184 strnode_add(&skip_list
, optarg
, 0);
187 /* XXX should crunch it? */
196 DPRINTF((stderr
, "parse_args\n"));
198 DPRINTF((stderr
, "initialize\n"));
200 DPRINTF((stderr
, "crunch_all_files\n"));
202 DPRINTF((stderr
, "generate_ordering\n"));
208 * initialise various variables.
214 fn_head
= &fn_head_s
;
216 provide_hash
= &provide_hash_s
;
217 Hash_InitTable(provide_hash
, file_count
);
220 /* generic function to insert a new strnodelist element */
222 strnode_add(strnodelist
**listp
, char *s
, filenode
*fnode
)
226 ent
= emalloc(sizeof *ent
+ strlen(s
));
234 * below are the functions that deal with creating the lists
235 * from the filename's given and the dependancies and provisions
236 * in each of these files. no ordering or checking is done here.
240 * we have a new filename, create a new filenode structure.
241 * fill in the bits, and put it in the filenode linked list
244 filenode_new(char *filename
)
248 temp
= emalloc(sizeof(*temp
));
249 memset(temp
, 0, sizeof(*temp
));
250 temp
->filename
= estrdup(filename
);
251 temp
->req_list
= NULL
;
252 temp
->prov_list
= NULL
;
253 temp
->keyword_list
= NULL
;
254 temp
->in_progress
= RESET
;
256 * link the filenode into the list of filenodes.
257 * note that the double linking means we can delete a
258 * filenode without searching for where it belongs.
260 temp
->next
= fn_head
->next
;
261 if (temp
->next
!= NULL
)
262 temp
->next
->last
= temp
;
263 temp
->last
= fn_head
;
264 fn_head
->next
= temp
;
269 * add a requirement to a filenode.
272 add_require(filenode
*fnode
, char *s
)
278 entry
= Hash_CreateEntry(provide_hash
, s
, &new);
280 Hash_SetValue(entry
, NULL
);
281 rnode
= emalloc(sizeof(*rnode
));
282 rnode
->entry
= entry
;
283 rnode
->next
= fnode
->req_list
;
284 fnode
->req_list
= rnode
;
288 * add a provision to a filenode. if this provision doesn't
289 * have a head node, create one here.
292 add_provide(filenode
*fnode
, char *s
)
296 provnode
*pnode
, *head
;
299 entry
= Hash_CreateEntry(provide_hash
, s
, &new);
300 head
= Hash_GetValue(entry
);
302 /* create a head node if necessary. */
304 head
= emalloc(sizeof(*head
));
306 head
->in_progress
= RESET
;
308 head
->last
= head
->next
= NULL
;
309 Hash_SetValue(entry
, head
);
313 * Don't warn about this. We want to be able to support
314 * scripts that do two complex things:
316 * - Two independent scripts which both provide the
317 * same thing. Both scripts must be executed in
318 * any order to meet the barrier. An example:
330 * - Two interdependent scripts which both provide the
331 * same thing. Both scripts must be executed in
332 * graph order to meet the barrier. An example:
336 * PROVIDE: nameservice dnscache
341 * PROVIDE: nameservice nscd
345 warnx("file `%s' provides `%s'.", fnode
->filename
, s
);
346 warnx("\tpreviously seen in `%s'.",
347 head
->next
->fnode
->filename
);
351 pnode
= emalloc(sizeof(*pnode
));
353 pnode
->in_progress
= RESET
;
354 pnode
->fnode
= fnode
;
355 pnode
->next
= head
->next
;
358 if (pnode
->next
!= NULL
)
359 pnode
->next
->last
= pnode
;
361 f_pnode
= emalloc(sizeof(*f_pnode
));
362 f_pnode
->pnode
= pnode
;
363 f_pnode
->next
= fnode
->prov_list
;
364 fnode
->prov_list
= f_pnode
;
368 * put the BEFORE: lines to a list and handle them later.
371 add_before(filenode
*fnode
, char *s
)
374 strnode_add(&bl_list
, s
, fnode
);
378 * add a key to a filenode.
381 add_keyword(filenode
*fnode
, char *s
)
384 strnode_add(&fnode
->keyword_list
, s
, fnode
);
388 * loop over the rest of a line, giving each word to
389 * add_func() to do the real work.
392 parse_line(filenode
*node
, char *buffer
, void (*add_func
)(filenode
*, char *))
396 while ((s
= strsep(&buffer
, " \t\n")) != NULL
)
398 (*add_func
)(node
, s
);
402 * given a file name, create a filenode for it, read in lines looking
403 * for provision and requirement lines, building the graphs as needed.
406 crunch_file(char *filename
)
410 int require_flag
, provide_flag
, before_flag
, keyword_flag
;
411 enum { BEFORE_PARSING
, PARSING
, PARSING_DONE
} state
;
413 char delims
[3] = { '\\', '\\', '\0' };
416 if ((fp
= fopen(filename
, "r")) == NULL
) {
417 warn("could not open %s", filename
);
421 if (fstat(fileno(fp
), &st
) == -1) {
422 warn("could not stat %s", filename
);
427 if (!S_ISREG(st
.st_mode
)) {
429 warnx("%s is not a file", filename
);
435 node
= filenode_new(filename
);
438 * we don't care about length, line number, don't want # for comments,
441 for (state
= BEFORE_PARSING
; state
!= PARSING_DONE
&&
442 (buf
= fparseln(fp
, NULL
, NULL
, delims
, 0)) != NULL
; free(buf
)) {
443 require_flag
= provide_flag
= before_flag
= keyword_flag
= 0;
444 if (strncmp(REQUIRE_STR
, buf
, REQUIRE_LEN
) == 0)
445 require_flag
= REQUIRE_LEN
;
446 else if (strncmp(REQUIRES_STR
, buf
, REQUIRES_LEN
) == 0)
447 require_flag
= REQUIRES_LEN
;
448 else if (strncmp(PROVIDE_STR
, buf
, PROVIDE_LEN
) == 0)
449 provide_flag
= PROVIDE_LEN
;
450 else if (strncmp(PROVIDES_STR
, buf
, PROVIDES_LEN
) == 0)
451 provide_flag
= PROVIDES_LEN
;
452 else if (strncmp(BEFORE_STR
, buf
, BEFORE_LEN
) == 0)
453 before_flag
= BEFORE_LEN
;
454 else if (strncmp(KEYWORD_STR
, buf
, KEYWORD_LEN
) == 0)
455 keyword_flag
= KEYWORD_LEN
;
456 else if (strncmp(KEYWORDS_STR
, buf
, KEYWORDS_LEN
) == 0)
457 keyword_flag
= KEYWORDS_LEN
;
459 if (state
== PARSING
)
460 state
= PARSING_DONE
;
466 parse_line(node
, buf
+ require_flag
, add_require
);
467 else if (provide_flag
)
468 parse_line(node
, buf
+ provide_flag
, add_provide
);
469 else if (before_flag
)
470 parse_line(node
, buf
+ before_flag
, add_before
);
471 else if (keyword_flag
)
472 parse_line(node
, buf
+ keyword_flag
, add_keyword
);
478 make_fake_provision(filenode
*node
)
482 provnode
*head
, *pnode
;
488 snprintf(buffer
, sizeof buffer
, "fake_prov_%08d", i
++);
489 entry
= Hash_CreateEntry(provide_hash
, buffer
, &new);
491 head
= emalloc(sizeof(*head
));
493 head
->in_progress
= RESET
;
495 head
->last
= head
->next
= NULL
;
496 Hash_SetValue(entry
, head
);
498 pnode
= emalloc(sizeof(*pnode
));
500 pnode
->in_progress
= RESET
;
502 pnode
->next
= head
->next
;
505 if (pnode
->next
!= NULL
)
506 pnode
->next
->last
= pnode
;
508 f_pnode
= emalloc(sizeof(*f_pnode
));
509 f_pnode
->pnode
= pnode
;
510 f_pnode
->next
= node
->prov_list
;
511 node
->prov_list
= f_pnode
;
517 * go through the BEFORE list, inserting requirements into the graph(s)
518 * as required. in the before list, for each entry B, we have a file F
519 * and a string S. we create a "fake" provision (P) that F provides.
520 * for each entry in the provision list for S, add a requirement to
521 * that provisions filenode for P.
526 Hash_Entry
*entry
, *fake_prov_entry
;
532 while (bl_list
!= NULL
) {
535 fake_prov_entry
= make_fake_provision(bl_list
->node
);
537 entry
= Hash_CreateEntry(provide_hash
, bl_list
->s
, &new);
539 warnx("file `%s' is before unknown provision `%s'",
540 bl_list
->node
->filename
, bl_list
->s
);
542 for (pnode
= Hash_GetValue(entry
); pnode
; pnode
= pnode
->next
) {
546 rnode
= emalloc(sizeof(*rnode
));
547 rnode
->entry
= fake_prov_entry
;
548 rnode
->next
= pnode
->fnode
->req_list
;
549 pnode
->fnode
->req_list
= rnode
;
558 * loop over all the files calling crunch_file() on them to do the
559 * real work. after we have built all the nodes, insert the BEFORE:
560 * lines into graph(s).
563 crunch_all_files(void)
567 for (i
= 0; i
< file_count
; i
++)
568 crunch_file(file_list
[i
]);
573 * below are the functions that traverse the graphs we have built
574 * finding out the desired ordering, printing each file in turn.
575 * if missing requirements, or cyclic graphs are detected, a
576 * warning will be issued, and we will continue on..
580 * given a requirement node (in a filename) we attempt to satisfy it.
581 * we do some sanity checking first, to ensure that we have providers,
582 * aren't already satisfied and aren't already being satisfied (ie,
583 * cyclic). if we pass all this, we loop over the provision list
584 * calling do_file() (enter recursion) for each filenode in this
588 satisfy_req(f_reqnode
*rnode
, char *filename
)
593 entry
= rnode
->entry
;
594 head
= Hash_GetValue(entry
);
597 warnx("requirement `%s' in file `%s' has no providers.",
598 Hash_GetKey(entry
), filename
);
603 /* return if the requirement is already satisfied. */
604 if (head
->next
== NULL
)
608 * if list is marked as in progress,
609 * print that there is a circular dependency on it and abort
611 if (head
->in_progress
== SET
) {
612 warnx("Circular dependency on provision `%s' in file `%s'.",
613 Hash_GetKey(entry
), filename
);
618 head
->in_progress
= SET
;
621 * while provision_list is not empty
622 * do_file(first_member_of(provision_list));
624 while (head
->next
!= NULL
)
625 do_file(head
->next
->fnode
);
629 skip_ok(filenode
*fnode
)
634 for (s
= skip_list
; s
; s
= s
->next
)
635 for (k
= fnode
->keyword_list
; k
; k
= k
->next
)
636 if (strcmp(k
->s
, s
->s
) == 0)
643 keep_ok(filenode
*fnode
)
648 for (s
= keep_list
; s
; s
= s
->next
)
649 for (k
= fnode
->keyword_list
; k
; k
= k
->next
)
650 if (strcmp(k
->s
, s
->s
) == 0)
653 /* an empty keep_list means every one */
658 * given a filenode, we ensure we are not a cyclic graph. if this
659 * is ok, we loop over the filenodes requirements, calling satisfy_req()
660 * for each of them.. once we have done this, remove this filenode
661 * from each provision table, as we are now done.
663 * NOTE: do_file() is called recursively from several places and cannot
664 * safely free() anything related to items that may be recursed on.
665 * Circular dependancies will cause problems if we do.
668 do_file(filenode
*fnode
)
671 f_provnode
*p
, *p_tmp
;
675 DPRINTF((stderr
, "do_file on %s.\n", fnode
->filename
));
678 * if fnode is marked as in progress,
679 * print that fnode; is circularly depended upon and abort.
681 if (fnode
->in_progress
== SET
) {
682 warnx("Circular dependency on file `%s'.",
684 was_set
= exit_code
= 1;
689 fnode
->in_progress
= SET
;
692 * for each requirement of fnode -> r
693 * satisfy_req(r, filename)
698 f_reqnode
*r_tmp
= r
;
700 satisfy_req(r
, fnode
->filename
);
706 fnode
->req_list
= NULL
;
709 * for each provision of fnode -> p
710 * remove fnode from provision list for p in hash table
712 p
= fnode
->prov_list
;
716 if (pnode
->next
!= NULL
) {
717 pnode
->next
->last
= pnode
->last
;
719 if (pnode
->last
!= NULL
) {
720 pnode
->last
->next
= pnode
->next
;
726 fnode
->prov_list
= NULL
;
729 DPRINTF((stderr
, "next do: "));
731 /* if we were already in progress, don't print again */
732 if (was_set
== 0 && skip_ok(fnode
) && keep_ok(fnode
))
733 printf("%s\n", fnode
->filename
);
735 if (fnode
->next
!= NULL
) {
736 fnode
->next
->last
= fnode
->last
;
738 if (fnode
->last
!= NULL
) {
739 fnode
->last
->next
= fnode
->next
;
742 DPRINTF((stderr
, "nuking %s\n", fnode
->filename
));
744 free(fnode
->filename
);
750 generate_ordering(void)
754 * while there remain undone files{f},
755 * pick an arbitrary f, and do_file(f)
756 * Note that the first file in the file list is perfectly
757 * arbitrary, and easy to find, so we use that.
761 * N.B.: the file nodes "self delete" after they execute, so
762 * after each iteration of the loop, the head will be pointing
763 * to something totally different. The loop ends up being
764 * executed only once for every strongly connected set of
767 while (fn_head
->next
!= NULL
) {
768 DPRINTF((stderr
, "generate on %s\n", fn_head
->next
->filename
));
769 do_file(fn_head
->next
);