rcorder.c revision 1.1 1 /* $NetBSD: rcorder.c,v 1.1 1999/11/23 05:28:22 mrg 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 } else if (new == 0) {
268 warnx("file `%s' provides `%s'.", fnode->filename, s);
269 warnx("\tpreviously seen in `%s'.", head->next->fnode->filename);
270 }
271
272 pnode = emalloc(sizeof(*pnode));
273 pnode->head = RESET;
274 pnode->in_progress = RESET;
275 pnode->fnode = fnode;
276 pnode->next = head->next;
277 pnode->last = head;
278 head->next = pnode;
279 if (pnode->next != NULL)
280 pnode->next->last = pnode;
281
282 f_pnode = emalloc(sizeof(*f_pnode));
283 f_pnode->pnode = pnode;
284 f_pnode->next = fnode->prov_list;
285 fnode->prov_list = f_pnode;
286 }
287
288 /*
289 * put the BEFORE: lines to a list and handle them later.
290 */
291 void
292 add_before(fnode, s)
293 filenode *fnode;
294 char *s;
295 {
296 beforelist *bf_ent;
297
298 bf_ent = emalloc(sizeof *bf_ent);
299 bf_ent->node = fnode;
300 bf_ent->s = s;
301 bf_ent->next = bl_list;
302 bl_list = bf_ent;
303 }
304
305 /*
306 * loop over the rest of a REQUIRE line, giving each word to
307 * add_require() to do the real work.
308 */
309 void
310 parse_require(node, buffer)
311 filenode *node;
312 char *buffer;
313 {
314 char *s;
315
316 while ((s = strsep(&buffer, " \t\n")) != NULL)
317 if (*s != '\0')
318 add_require(node, s);
319 }
320
321 /*
322 * loop over the rest of a PROVIDE line, giving each word to
323 * add_provide() to do the real work.
324 */
325 void
326 parse_provide(node, buffer)
327 filenode *node;
328 char *buffer;
329 {
330 char *s;
331
332 while ((s = strsep(&buffer, " \t\n")) != NULL)
333 if (*s != '\0')
334 add_provide(node, s);
335 }
336
337 /*
338 * loop over the rest of a BEFORE line, giving each word to
339 * add_before() to do the real work.
340 */
341 void
342 parse_before(node, buffer)
343 filenode *node;
344 char *buffer;
345 {
346 char *s;
347
348 while ((s = strsep(&buffer, " \t\n")) != NULL)
349 if (*s != '\0')
350 add_before(node, s);
351 }
352
353 /*
354 * given a file name, create a filenode for it, read in lines looking
355 * for provision and requirement lines, building the graphs as needed.
356 */
357 void
358 crunch_file(filename)
359 char *filename;
360 {
361 FILE *fp;
362 char *buf;
363 int require_flag, provide_flag, before_flag, directive_flag;
364 filenode *node;
365 char delims[3] = { '\\', '\\', '\0' };
366
367 directive_flag = 0;
368
369 if ((fp = fopen(filename, "r")) == NULL) {
370 warn("could not open %s", filename);
371 return;
372 }
373
374 node = filenode_new(filename);
375
376 /*
377 * we don't care about length, line number, don't want # for comments,
378 * and have no flags.
379 */
380 while ((buf = fparseln(fp, NULL, NULL, delims, 0))) {
381 require_flag = provide_flag = before_flag = 0;
382 if (strncmp(REQUIRE_STR, buf, REQUIRE_LEN) == 0)
383 require_flag = REQUIRE_LEN;
384 else if (strncmp(REQUIRES_STR, buf, REQUIRES_LEN) == 0)
385 require_flag = REQUIRES_LEN;
386 else if (strncmp(PROVIDE_STR, buf, PROVIDE_LEN) == 0)
387 provide_flag = PROVIDE_LEN;
388 else if (strncmp(PROVIDES_STR, buf, PROVIDES_LEN) == 0)
389 provide_flag = PROVIDES_LEN;
390 else if (strncmp(BEFORE_STR, buf, BEFORE_LEN) == 0)
391 before_flag = BEFORE_LEN;
392
393 if (require_flag)
394 parse_require(node, buf + require_flag);
395
396 if (provide_flag)
397 parse_provide(node, buf + provide_flag);
398
399 if (before_flag)
400 parse_before(node, buf + before_flag);
401 }
402 fclose(fp);
403 }
404
405 Hash_Entry *
406 make_fake_provision(node)
407 filenode *node;
408 {
409 Hash_Entry *entry;
410 f_provnode *f_pnode;
411 provnode *head, *pnode;
412 static int i = 0;
413 int new;
414 char buffer[30];
415
416 do {
417 snprintf(buffer, sizeof buffer, "fake_prov_%08d", i++);
418 entry = Hash_CreateEntry(provide_hash, buffer, &new);
419 } while (new == 0);
420 head = emalloc(sizeof(*head));
421 head->head = SET;
422 head->in_progress = RESET;
423 head->fnode = NULL;
424 head->last = head->next = NULL;
425 Hash_SetValue(entry, head);
426
427 pnode = emalloc(sizeof(*pnode));
428 pnode->head = RESET;
429 pnode->in_progress = RESET;
430 pnode->fnode = node;
431 pnode->next = head->next;
432 pnode->last = head;
433 head->next = pnode;
434 if (pnode->next != NULL)
435 pnode->next->last = pnode;
436
437 f_pnode = emalloc(sizeof(*f_pnode));
438 f_pnode->pnode = pnode;
439 f_pnode->next = node->prov_list;
440 node->prov_list = f_pnode;
441
442 return (entry);
443 }
444
445 /*
446 * go through the BEFORE list, inserting requirements into the graph(s)
447 * as required. in the before list, for each entry B, we have a file F
448 * and a string S. we create a "fake" provision (P) that F provides.
449 * for each entry in the provision list for S, add a requirement to
450 * that provisions filenode for P.
451 */
452 void
453 insert_before()
454 {
455 Hash_Entry *entry, *fake_prov_entry;
456 provnode *pnode;
457 f_reqnode *rnode;
458 beforelist *bl;
459 int new;
460
461 while (bl_list != NULL) {
462 bl = bl_list->next;
463
464 fake_prov_entry = make_fake_provision(bl_list->node);
465
466 entry = Hash_CreateEntry(provide_hash, bl_list->s, &new);
467 if (new == 1)
468 warnx("file `%s' is before unknown provision `%s'", bl_list->node->filename, bl_list->s);
469
470 for (pnode = Hash_GetValue(entry); pnode; pnode = pnode->next) {
471 if (pnode->head)
472 continue;
473
474 rnode = emalloc(sizeof(*rnode));
475 rnode->entry = fake_prov_entry;
476 rnode->next = pnode->fnode->req_list;
477 pnode->fnode->req_list = rnode;
478 }
479
480 free(bl_list);
481 bl_list = bl;
482 }
483 }
484
485 /*
486 * loop over all the files calling crunch_file() on them to do the
487 * real work. after we have built all the nodes, insert the BEFORE:
488 * lines into graph(s).
489 */
490 void
491 crunch_all_files()
492 {
493 int i;
494
495 for (i = 0; i < file_count; i++)
496 crunch_file(file_list[i]);
497 insert_before();
498 }
499
500 /*
501 * below are the functions that traverse the graphs we have built
502 * finding out the desired ordering, printing each file in turn.
503 * if missing requirements, or cyclic graphs are detected, a
504 * warning will be issued, and we will continue on..
505 */
506
507 /*
508 * given a requirement node (in a filename) we attempt to satisfy it.
509 * we do some sanity checking first, to ensure that we have providers,
510 * aren't already satisfied and aren't already being satisfied (ie,
511 * cyclic). if we pass all this, we loop over the provision list
512 * calling do_file() (enter recursion) for each filenode in this
513 * provision.
514 */
515 void
516 satisfy_req(rnode, filename)
517 f_reqnode *rnode;
518 char *filename;
519 {
520 Hash_Entry *entry;
521 provnode *head;
522
523 entry = rnode->entry;
524 head = Hash_GetValue(entry);
525
526 if (head == NULL) {
527 warnx("requirement `%s' in file `%s' has no providers.",
528 Hash_GetKey(entry), filename);
529 exit_code = 1;
530 return;
531 }
532
533 /* return if the requirement is already satisfied. */
534 if (head->next == NULL)
535 return;
536
537 /*
538 * if list is marked as in progress,
539 * print that there is a circular dependency on it and abort
540 */
541 if (head->in_progress == SET) {
542 warnx("Circular dependency on provision `%s' in file `%s'.",
543 Hash_GetKey(entry), filename);
544 exit_code = 1;
545 return;
546 }
547
548 head->in_progress = SET;
549
550 /*
551 * while provision_list is not empty
552 * do_file(first_member_of(provision_list));
553 */
554 while (head->next != NULL)
555 do_file(head->next->fnode);
556 }
557
558 /*
559 * given a filenode, we ensure we are not a cyclic graph. if this
560 * is ok, we loop over the filenodes requirements, calling satisfy_req()
561 * for each of them.. once we have done this, remove this filenode
562 * from each provision table, as we are now done.
563 */
564 void
565 do_file(fnode)
566 filenode *fnode;
567 {
568 f_reqnode *r, *r_tmp;
569 f_provnode *p, *p_tmp;
570 provnode *pnode;
571 int was_set;
572
573 DPRINTF((stderr, "do_file on %s.\n", fnode->filename));
574
575 /*
576 * if fnode is marked as in progress,
577 * print that fnode; is circularly depended upon and abort.
578 */
579 if (fnode->in_progress == SET) {
580 warnx("Circular dependency on file `%s'.",
581 fnode->filename);
582 was_set = exit_code = 1;
583 } else
584 was_set = 0;
585
586 /* mark fnode */
587 fnode->in_progress = SET;
588
589 /*
590 * for each requirement of fnode -> r
591 * satisfy_req(r, filename)
592 */
593 r = fnode->req_list;
594 while (r != NULL) {
595 r_tmp = r;
596 satisfy_req(r, fnode->filename);
597 r = r->next;
598 free(r_tmp);
599 }
600 fnode->req_list = NULL;
601
602 /*
603 * for each provision of fnode -> p
604 * remove fnode from provision list for p in hash table
605 */
606 p = fnode->prov_list;
607 while (p != NULL) {
608 p_tmp = p;
609 pnode = p->pnode;
610 if (pnode->next != NULL) {
611 pnode->next->last = pnode->last;
612 }
613 if (pnode->last != NULL) {
614 pnode->last->next = pnode->next;
615 }
616 free(pnode);
617 p = p->next;
618 free(p_tmp);
619 }
620 fnode->prov_list = NULL;
621
622 /* do_it(fnode) */
623 DPRINTF((stderr, "next do: "));
624
625 /* if we were already in progress, don't print again */
626 if (was_set == 0)
627 printf("%s\n", fnode->filename);
628
629 if (fnode->next != NULL) {
630 fnode->next->last = fnode->last;
631 }
632 if (fnode->last != NULL) {
633 fnode->last->next = fnode->next;
634 }
635
636 DPRINTF((stderr, "nuking %s\n", fnode->filename));
637 free(fnode->filename);
638 free(fnode);
639 }
640
641 void
642 generate_ordering()
643 {
644
645 /*
646 * while there remain undone files{f},
647 * pick an arbitrary f, and do_file(f)
648 * Note that the first file in the file list is perfectly
649 * arbitrary, and easy to find, so we use that.
650 */
651
652 /*
653 * N.B.: the file nodes "self delete" after they execute, so
654 * after each iteration of the loop, the head will be pointing
655 * to something totally different. The loop ends up being
656 * executed only once for every strongly connected set of
657 * nodes.
658 */
659 while (fn_head->next != NULL) {
660 DPRINTF((stderr, "generate on %s\n", fn_head->next->filename));
661 do_file(fn_head->next);
662 }
663 }
664