rcorder.c revision 1.2 1 /* $NetBSD: rcorder.c,v 1.2 2000/04/26 05:12:06 thorpej Exp $ */
2
3 /*
4 * Copyright (c) 1998, 1999 Matthew R. Green
5 * All rights reserved.
6 * Copyright (c) 1998
7 * Perry E. Metzger. All rights reserved.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 * 1. Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in the
16 * documentation and/or other materials provided with the distribution.
17 * 3. All advertising materials mentioning features or use of this software
18 * must display the following acknowledgement:
19 * This product includes software developed for the NetBSD Project
20 * by Perry E. Metzger.
21 * 4. The name of the author may not be used to endorse or promote products
22 * derived from this software without specific prior written permission.
23 *
24 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
25 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
26 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
27 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
28 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
29 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
30 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
31 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
32 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
33 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34 */
35
36 #include <sys/types.h>
37
38 #include <err.h>
39 #include <stdio.h>
40 #include <stdlib.h>
41 #include <string.h>
42 #include <unistd.h>
43 #include <util.h>
44
45 #include "ealloc.h"
46 #include "sprite.h"
47 #include "hash.h"
48
49 #ifdef DEBUG
50 int debug = 0;
51 # define DPRINTF(args) if (debug) { fflush(stdout); fprintf args; }
52 #else
53 # define DPRINTF(args)
54 #endif
55
56 #define REQUIRE_STR "# REQUIRE:"
57 #define REQUIRE_LEN (sizeof(REQUIRE_STR) - 1)
58 #define REQUIRES_STR "# REQUIRES:"
59 #define REQUIRES_LEN (sizeof(REQUIRES_STR) - 1)
60 #define PROVIDE_STR "# PROVIDE:"
61 #define PROVIDE_LEN (sizeof(PROVIDE_STR) - 1)
62 #define PROVIDES_STR "# PROVIDES:"
63 #define PROVIDES_LEN (sizeof(PROVIDES_STR) - 1)
64 #define BEFORE_STR "# BEFORE:"
65 #define BEFORE_LEN (sizeof(BEFORE_STR) - 1)
66
67 int exit_code;
68 int file_count;
69 char **file_list;
70
71 typedef int bool;
72 #define TRUE 1
73 #define FALSE 0
74 typedef bool flag;
75 #define SET TRUE
76 #define RESET FALSE
77
78 Hash_Table provide_hash_s, *provide_hash;
79
80 typedef struct provnode provnode;
81 typedef struct filenode filenode;
82 typedef struct f_provnode f_provnode;
83 typedef struct f_reqnode f_reqnode;
84 typedef struct beforelist beforelist;
85
86 struct provnode {
87 flag head;
88 flag in_progress;
89 filenode *fnode;
90 provnode *next, *last;
91 };
92
93 struct f_provnode {
94 provnode *pnode;
95 f_provnode *next;
96 };
97
98 struct f_reqnode {
99 Hash_Entry *entry;
100 f_reqnode *next;
101 };
102
103 struct filenode {
104 char *filename;
105 flag in_progress;
106 filenode *next, *last;
107 f_reqnode *req_list;
108 f_provnode *prov_list;
109 };
110
111 struct beforelist {
112 filenode *node;
113 char *s;
114 beforelist *next;
115 } *bl_list = NULL;
116
117 filenode fn_head_s, *fn_head;
118
119 void do_file __P((filenode *fnode));
120 void satisfy_req __P((f_reqnode *rnode, char *filename));
121 void crunch_file __P((char *));
122 void parse_require __P((filenode *, char *));
123 void parse_provide __P((filenode *, char *));
124 void parse_before __P((filenode *, char *));
125 filenode *filenode_new __P((char *));
126 void add_require __P((filenode *, char *));
127 void add_provide __P((filenode *, char *));
128 void add_before __P((filenode *, char *));
129 void insert_before __P((void));
130 Hash_Entry *make_fake_provision __P((filenode *));
131 void crunch_all_files __P((void));
132 void initialize __P((void));
133 void generate_ordering __P((void));
134 int main __P((int, char *[]));
135
136 int
137 main(argc, argv)
138 int argc;
139 char *argv[];
140 {
141 int ch;
142
143 while ((ch = getopt(argc, argv, "d")) != -1)
144 switch (ch) {
145 case 'd':
146 #ifdef DEBUG
147 debug = 1;
148 #else
149 warnx("debugging not compiled in, -d ignored");
150 #endif
151 break;
152 default:
153 /* XXX should crunch it? */
154 break;
155 }
156 argc -= optind;
157 argv += optind;
158
159 file_count = argc;
160 file_list = argv;
161
162 DPRINTF((stderr, "parse_args\n"));
163 initialize();
164 DPRINTF((stderr, "initialize\n"));
165 crunch_all_files();
166 DPRINTF((stderr, "crunch_all_files\n"));
167 generate_ordering();
168 DPRINTF((stderr, "generate_ordering\n"));
169
170 exit(exit_code);
171 }
172
173 /*
174 * initialise various variables.
175 */
176 void
177 initialize()
178 {
179
180 fn_head = &fn_head_s;
181
182 provide_hash = &provide_hash_s;
183 Hash_InitTable(provide_hash, file_count);
184 }
185
186 /*
187 * below are the functions that deal with creating the lists
188 * from the filename's given and the dependancies and provisions
189 * in each of these files. no ordering or checking is done here.
190 */
191
192 /*
193 * we have a new filename, create a new filenode structure.
194 * fill in the bits, and put it in the filenode linked list
195 */
196 filenode *
197 filenode_new(filename)
198 char *filename;
199 {
200 filenode *temp;
201
202 temp = emalloc(sizeof(*temp));
203 memset(temp, 0, sizeof(*temp));
204 temp->filename = estrdup(filename);
205 temp->req_list = NULL;
206 temp->prov_list = NULL;
207 temp->in_progress = RESET;
208 /*
209 * link the filenode into the list of filenodes.
210 * note that the double linking means we can delete a
211 * filenode without searching for where it belongs.
212 */
213 temp->next = fn_head->next;
214 if (temp->next != NULL)
215 temp->next->last = temp;
216 temp->last = fn_head;
217 fn_head->next = temp;
218 return (temp);
219 }
220
221 /*
222 * add a requirement a filenode.
223 */
224 void
225 add_require(fnode, s)
226 filenode *fnode;
227 char *s;
228 {
229 Hash_Entry *entry;
230 f_reqnode *rnode;
231 int new;
232
233 entry = Hash_CreateEntry(provide_hash, s, &new);
234 if (new)
235 Hash_SetValue(entry, NULL);
236 rnode = emalloc(sizeof(*rnode));
237 rnode->entry = entry;
238 rnode->next = fnode->req_list;
239 fnode->req_list = rnode;
240 }
241
242 /*
243 * add a provision to a filenode. if this provision doesn't
244 * have a head node, create one here.
245 */
246 void
247 add_provide(fnode, s)
248 filenode *fnode;
249 char *s;
250 {
251 Hash_Entry *entry;
252 f_provnode *f_pnode;
253 provnode *pnode, *head;
254 int new;
255
256 entry = Hash_CreateEntry(provide_hash, s, &new);
257 head = Hash_GetValue(entry);
258
259 /* create a head node if necessary. */
260 if (head == NULL) {
261 head = emalloc(sizeof(*head));
262 head->head = SET;
263 head->in_progress = RESET;
264 head->fnode = NULL;
265 head->last = head->next = NULL;
266 Hash_SetValue(entry, head);
267 }
268 #if 0
269 /*
270 * Don't warn about this. We want to be able to support
271 * scripts that do two complex things:
272 *
273 * - Two independent scripts which both provide the
274 * same thing. Both scripts must be executed in
275 * any order to meet the barrier. An example:
276 *
277 * Script 1:
278 *
279 * PROVIDE: mail
280 * REQUIRE: LOGIN
281 *
282 * Script 2:
283 *
284 * PROVIDE: mail
285 * REQUIRE: LOGIN
286 *
287 * - Two interdependent scripts which both provide the
288 * same thing. Both scripts must be executed in
289 * graph order to meet the barrier. An example:
290 *
291 * Script 1:
292 *
293 * PROVIDE: nameservice dnscache
294 * REQUIRE: SERVERS
295 *
296 * Script 2:
297 *
298 * PROVIDE: nameservice nscd
299 * REQUIRE: dnscache
300 */
301 else if (new == 0) {
302 warnx("file `%s' provides `%s'.", fnode->filename, s);
303 warnx("\tpreviously seen in `%s'.",
304 head->next->fnode->filename);
305 }
306 #endif
307
308 pnode = emalloc(sizeof(*pnode));
309 pnode->head = RESET;
310 pnode->in_progress = RESET;
311 pnode->fnode = fnode;
312 pnode->next = head->next;
313 pnode->last = head;
314 head->next = pnode;
315 if (pnode->next != NULL)
316 pnode->next->last = pnode;
317
318 f_pnode = emalloc(sizeof(*f_pnode));
319 f_pnode->pnode = pnode;
320 f_pnode->next = fnode->prov_list;
321 fnode->prov_list = f_pnode;
322 }
323
324 /*
325 * put the BEFORE: lines to a list and handle them later.
326 */
327 void
328 add_before(fnode, s)
329 filenode *fnode;
330 char *s;
331 {
332 beforelist *bf_ent;
333
334 bf_ent = emalloc(sizeof *bf_ent);
335 bf_ent->node = fnode;
336 bf_ent->s = s;
337 bf_ent->next = bl_list;
338 bl_list = bf_ent;
339 }
340
341 /*
342 * loop over the rest of a REQUIRE line, giving each word to
343 * add_require() to do the real work.
344 */
345 void
346 parse_require(node, buffer)
347 filenode *node;
348 char *buffer;
349 {
350 char *s;
351
352 while ((s = strsep(&buffer, " \t\n")) != NULL)
353 if (*s != '\0')
354 add_require(node, s);
355 }
356
357 /*
358 * loop over the rest of a PROVIDE line, giving each word to
359 * add_provide() to do the real work.
360 */
361 void
362 parse_provide(node, buffer)
363 filenode *node;
364 char *buffer;
365 {
366 char *s;
367
368 while ((s = strsep(&buffer, " \t\n")) != NULL)
369 if (*s != '\0')
370 add_provide(node, s);
371 }
372
373 /*
374 * loop over the rest of a BEFORE line, giving each word to
375 * add_before() to do the real work.
376 */
377 void
378 parse_before(node, buffer)
379 filenode *node;
380 char *buffer;
381 {
382 char *s;
383
384 while ((s = strsep(&buffer, " \t\n")) != NULL)
385 if (*s != '\0')
386 add_before(node, s);
387 }
388
389 /*
390 * given a file name, create a filenode for it, read in lines looking
391 * for provision and requirement lines, building the graphs as needed.
392 */
393 void
394 crunch_file(filename)
395 char *filename;
396 {
397 FILE *fp;
398 char *buf;
399 int require_flag, provide_flag, before_flag, directive_flag;
400 filenode *node;
401 char delims[3] = { '\\', '\\', '\0' };
402
403 directive_flag = 0;
404
405 if ((fp = fopen(filename, "r")) == NULL) {
406 warn("could not open %s", filename);
407 return;
408 }
409
410 node = filenode_new(filename);
411
412 /*
413 * we don't care about length, line number, don't want # for comments,
414 * and have no flags.
415 */
416 while ((buf = fparseln(fp, NULL, NULL, delims, 0))) {
417 require_flag = provide_flag = before_flag = 0;
418 if (strncmp(REQUIRE_STR, buf, REQUIRE_LEN) == 0)
419 require_flag = REQUIRE_LEN;
420 else if (strncmp(REQUIRES_STR, buf, REQUIRES_LEN) == 0)
421 require_flag = REQUIRES_LEN;
422 else if (strncmp(PROVIDE_STR, buf, PROVIDE_LEN) == 0)
423 provide_flag = PROVIDE_LEN;
424 else if (strncmp(PROVIDES_STR, buf, PROVIDES_LEN) == 0)
425 provide_flag = PROVIDES_LEN;
426 else if (strncmp(BEFORE_STR, buf, BEFORE_LEN) == 0)
427 before_flag = BEFORE_LEN;
428
429 if (require_flag)
430 parse_require(node, buf + require_flag);
431
432 if (provide_flag)
433 parse_provide(node, buf + provide_flag);
434
435 if (before_flag)
436 parse_before(node, buf + before_flag);
437 }
438 fclose(fp);
439 }
440
441 Hash_Entry *
442 make_fake_provision(node)
443 filenode *node;
444 {
445 Hash_Entry *entry;
446 f_provnode *f_pnode;
447 provnode *head, *pnode;
448 static int i = 0;
449 int new;
450 char buffer[30];
451
452 do {
453 snprintf(buffer, sizeof buffer, "fake_prov_%08d", i++);
454 entry = Hash_CreateEntry(provide_hash, buffer, &new);
455 } while (new == 0);
456 head = emalloc(sizeof(*head));
457 head->head = SET;
458 head->in_progress = RESET;
459 head->fnode = NULL;
460 head->last = head->next = NULL;
461 Hash_SetValue(entry, head);
462
463 pnode = emalloc(sizeof(*pnode));
464 pnode->head = RESET;
465 pnode->in_progress = RESET;
466 pnode->fnode = node;
467 pnode->next = head->next;
468 pnode->last = head;
469 head->next = pnode;
470 if (pnode->next != NULL)
471 pnode->next->last = pnode;
472
473 f_pnode = emalloc(sizeof(*f_pnode));
474 f_pnode->pnode = pnode;
475 f_pnode->next = node->prov_list;
476 node->prov_list = f_pnode;
477
478 return (entry);
479 }
480
481 /*
482 * go through the BEFORE list, inserting requirements into the graph(s)
483 * as required. in the before list, for each entry B, we have a file F
484 * and a string S. we create a "fake" provision (P) that F provides.
485 * for each entry in the provision list for S, add a requirement to
486 * that provisions filenode for P.
487 */
488 void
489 insert_before()
490 {
491 Hash_Entry *entry, *fake_prov_entry;
492 provnode *pnode;
493 f_reqnode *rnode;
494 beforelist *bl;
495 int new;
496
497 while (bl_list != NULL) {
498 bl = bl_list->next;
499
500 fake_prov_entry = make_fake_provision(bl_list->node);
501
502 entry = Hash_CreateEntry(provide_hash, bl_list->s, &new);
503 if (new == 1)
504 warnx("file `%s' is before unknown provision `%s'", bl_list->node->filename, bl_list->s);
505
506 for (pnode = Hash_GetValue(entry); pnode; pnode = pnode->next) {
507 if (pnode->head)
508 continue;
509
510 rnode = emalloc(sizeof(*rnode));
511 rnode->entry = fake_prov_entry;
512 rnode->next = pnode->fnode->req_list;
513 pnode->fnode->req_list = rnode;
514 }
515
516 free(bl_list);
517 bl_list = bl;
518 }
519 }
520
521 /*
522 * loop over all the files calling crunch_file() on them to do the
523 * real work. after we have built all the nodes, insert the BEFORE:
524 * lines into graph(s).
525 */
526 void
527 crunch_all_files()
528 {
529 int i;
530
531 for (i = 0; i < file_count; i++)
532 crunch_file(file_list[i]);
533 insert_before();
534 }
535
536 /*
537 * below are the functions that traverse the graphs we have built
538 * finding out the desired ordering, printing each file in turn.
539 * if missing requirements, or cyclic graphs are detected, a
540 * warning will be issued, and we will continue on..
541 */
542
543 /*
544 * given a requirement node (in a filename) we attempt to satisfy it.
545 * we do some sanity checking first, to ensure that we have providers,
546 * aren't already satisfied and aren't already being satisfied (ie,
547 * cyclic). if we pass all this, we loop over the provision list
548 * calling do_file() (enter recursion) for each filenode in this
549 * provision.
550 */
551 void
552 satisfy_req(rnode, filename)
553 f_reqnode *rnode;
554 char *filename;
555 {
556 Hash_Entry *entry;
557 provnode *head;
558
559 entry = rnode->entry;
560 head = Hash_GetValue(entry);
561
562 if (head == NULL) {
563 warnx("requirement `%s' in file `%s' has no providers.",
564 Hash_GetKey(entry), filename);
565 exit_code = 1;
566 return;
567 }
568
569 /* return if the requirement is already satisfied. */
570 if (head->next == NULL)
571 return;
572
573 /*
574 * if list is marked as in progress,
575 * print that there is a circular dependency on it and abort
576 */
577 if (head->in_progress == SET) {
578 warnx("Circular dependency on provision `%s' in file `%s'.",
579 Hash_GetKey(entry), filename);
580 exit_code = 1;
581 return;
582 }
583
584 head->in_progress = SET;
585
586 /*
587 * while provision_list is not empty
588 * do_file(first_member_of(provision_list));
589 */
590 while (head->next != NULL)
591 do_file(head->next->fnode);
592 }
593
594 /*
595 * given a filenode, we ensure we are not a cyclic graph. if this
596 * is ok, we loop over the filenodes requirements, calling satisfy_req()
597 * for each of them.. once we have done this, remove this filenode
598 * from each provision table, as we are now done.
599 */
600 void
601 do_file(fnode)
602 filenode *fnode;
603 {
604 f_reqnode *r, *r_tmp;
605 f_provnode *p, *p_tmp;
606 provnode *pnode;
607 int was_set;
608
609 DPRINTF((stderr, "do_file on %s.\n", fnode->filename));
610
611 /*
612 * if fnode is marked as in progress,
613 * print that fnode; is circularly depended upon and abort.
614 */
615 if (fnode->in_progress == SET) {
616 warnx("Circular dependency on file `%s'.",
617 fnode->filename);
618 was_set = exit_code = 1;
619 } else
620 was_set = 0;
621
622 /* mark fnode */
623 fnode->in_progress = SET;
624
625 /*
626 * for each requirement of fnode -> r
627 * satisfy_req(r, filename)
628 */
629 r = fnode->req_list;
630 while (r != NULL) {
631 r_tmp = r;
632 satisfy_req(r, fnode->filename);
633 r = r->next;
634 free(r_tmp);
635 }
636 fnode->req_list = NULL;
637
638 /*
639 * for each provision of fnode -> p
640 * remove fnode from provision list for p in hash table
641 */
642 p = fnode->prov_list;
643 while (p != NULL) {
644 p_tmp = p;
645 pnode = p->pnode;
646 if (pnode->next != NULL) {
647 pnode->next->last = pnode->last;
648 }
649 if (pnode->last != NULL) {
650 pnode->last->next = pnode->next;
651 }
652 free(pnode);
653 p = p->next;
654 free(p_tmp);
655 }
656 fnode->prov_list = NULL;
657
658 /* do_it(fnode) */
659 DPRINTF((stderr, "next do: "));
660
661 /* if we were already in progress, don't print again */
662 if (was_set == 0)
663 printf("%s\n", fnode->filename);
664
665 if (fnode->next != NULL) {
666 fnode->next->last = fnode->last;
667 }
668 if (fnode->last != NULL) {
669 fnode->last->next = fnode->next;
670 }
671
672 DPRINTF((stderr, "nuking %s\n", fnode->filename));
673 free(fnode->filename);
674 free(fnode);
675 }
676
677 void
678 generate_ordering()
679 {
680
681 /*
682 * while there remain undone files{f},
683 * pick an arbitrary f, and do_file(f)
684 * Note that the first file in the file list is perfectly
685 * arbitrary, and easy to find, so we use that.
686 */
687
688 /*
689 * N.B.: the file nodes "self delete" after they execute, so
690 * after each iteration of the loop, the head will be pointing
691 * to something totally different. The loop ends up being
692 * executed only once for every strongly connected set of
693 * nodes.
694 */
695 while (fn_head->next != NULL) {
696 DPRINTF((stderr, "generate on %s\n", fn_head->next->filename));
697 do_file(fn_head->next);
698 }
699 }
700