1 /* $NetBSD: rcorder.c,v 1.7 2000/08/04 07:33:55 enami 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 #include <sys/stat.h> 38 39 #include <err.h> 40 #include <stdio.h> 41 #include <stdlib.h> 42 #include <string.h> 43 #include <unistd.h> 44 #include <util.h> 45 46 #include "ealloc.h" 47 #include "sprite.h" 48 #include "hash.h" 49 50 #ifdef DEBUG 51 int debug = 0; 52 # define DPRINTF(args) if (debug) { fflush(stdout); fprintf args; } 53 #else 54 # define DPRINTF(args) 55 #endif 56 57 #define REQUIRE_STR "# REQUIRE:" 58 #define REQUIRE_LEN (sizeof(REQUIRE_STR) - 1) 59 #define REQUIRES_STR "# REQUIRES:" 60 #define REQUIRES_LEN (sizeof(REQUIRES_STR) - 1) 61 #define PROVIDE_STR "# PROVIDE:" 62 #define PROVIDE_LEN (sizeof(PROVIDE_STR) - 1) 63 #define PROVIDES_STR "# PROVIDES:" 64 #define PROVIDES_LEN (sizeof(PROVIDES_STR) - 1) 65 #define BEFORE_STR "# BEFORE:" 66 #define BEFORE_LEN (sizeof(BEFORE_STR) - 1) 67 #define KEYWORD_STR "# KEYWORD:" 68 #define KEYWORD_LEN (sizeof(KEYWORD_STR) - 1) 69 #define KEYWORDS_STR "# KEYWORDS:" 70 #define KEYWORDS_LEN (sizeof(KEYWORDS_STR) - 1) 71 72 int exit_code; 73 int file_count; 74 char **file_list; 75 76 typedef int bool; 77 #define TRUE 1 78 #define FALSE 0 79 typedef bool flag; 80 #define SET TRUE 81 #define RESET FALSE 82 83 Hash_Table provide_hash_s, *provide_hash; 84 85 typedef struct provnode provnode; 86 typedef struct filenode filenode; 87 typedef struct f_provnode f_provnode; 88 typedef struct f_reqnode f_reqnode; 89 typedef struct strnodelist strnodelist; 90 91 struct provnode { 92 flag head; 93 flag in_progress; 94 filenode *fnode; 95 provnode *next, *last; 96 }; 97 98 struct f_provnode { 99 provnode *pnode; 100 f_provnode *next; 101 }; 102 103 struct f_reqnode { 104 Hash_Entry *entry; 105 f_reqnode *next; 106 }; 107 108 struct strnodelist { 109 filenode *node; 110 strnodelist *next; 111 char s[1]; 112 }; 113 114 struct filenode { 115 char *filename; 116 flag in_progress; 117 filenode *next, *last; 118 f_reqnode *req_list; 119 f_provnode *prov_list; 120 strnodelist *keyword_list; 121 }; 122 123 filenode fn_head_s, *fn_head; 124 125 strnodelist *bl_list; 126 strnodelist *keep_list; 127 strnodelist *skip_list; 128 129 void do_file __P((filenode *fnode)); 130 void strnode_add __P((strnodelist **, char *, filenode *)); 131 int skip_ok __P((filenode *fnode)); 132 int keep_ok __P((filenode *fnode)); 133 void satisfy_req __P((f_reqnode *rnode, char *filename)); 134 void crunch_file __P((char *)); 135 void parse_require __P((filenode *, char *)); 136 void parse_provide __P((filenode *, char *)); 137 void parse_before __P((filenode *, char *)); 138 void parse_keywords __P((filenode *, char *)); 139 filenode *filenode_new __P((char *)); 140 void add_require __P((filenode *, char *)); 141 void add_provide __P((filenode *, char *)); 142 void add_before __P((filenode *, char *)); 143 void add_keyword __P((filenode *, char *)); 144 void insert_before __P((void)); 145 Hash_Entry *make_fake_provision __P((filenode *)); 146 void crunch_all_files __P((void)); 147 void initialize __P((void)); 148 void generate_ordering __P((void)); 149 int main __P((int, char *[])); 150 151 int 152 main(argc, argv) 153 int argc; 154 char *argv[]; 155 { 156 int ch; 157 158 while ((ch = getopt(argc, argv, "dk:s:")) != -1) 159 switch (ch) { 160 case 'd': 161 #ifdef DEBUG 162 debug = 1; 163 #else 164 warnx("debugging not compiled in, -d ignored"); 165 #endif 166 break; 167 case 'k': 168 strnode_add(&keep_list, optarg, 0); 169 break; 170 case 's': 171 strnode_add(&skip_list, optarg, 0); 172 break; 173 default: 174 /* XXX should crunch it? */ 175 break; 176 } 177 argc -= optind; 178 argv += optind; 179 180 file_count = argc; 181 file_list = argv; 182 183 DPRINTF((stderr, "parse_args\n")); 184 initialize(); 185 DPRINTF((stderr, "initialize\n")); 186 crunch_all_files(); 187 DPRINTF((stderr, "crunch_all_files\n")); 188 generate_ordering(); 189 DPRINTF((stderr, "generate_ordering\n")); 190 191 exit(exit_code); 192 } 193 194 /* 195 * initialise various variables. 196 */ 197 void 198 initialize() 199 { 200 201 fn_head = &fn_head_s; 202 203 provide_hash = &provide_hash_s; 204 Hash_InitTable(provide_hash, file_count); 205 } 206 207 /* generic function to insert a new strnodelist element */ 208 void 209 strnode_add(listp, s, fnode) 210 strnodelist **listp; 211 char *s; 212 filenode *fnode; 213 { 214 strnodelist *ent; 215 216 ent = emalloc(sizeof *ent + strlen(s)); 217 ent->node = fnode; 218 strcpy(ent->s, s); 219 ent->next = *listp; 220 *listp = ent; 221 } 222 223 /* 224 * below are the functions that deal with creating the lists 225 * from the filename's given and the dependancies and provisions 226 * in each of these files. no ordering or checking is done here. 227 */ 228 229 /* 230 * we have a new filename, create a new filenode structure. 231 * fill in the bits, and put it in the filenode linked list 232 */ 233 filenode * 234 filenode_new(filename) 235 char *filename; 236 { 237 filenode *temp; 238 239 temp = emalloc(sizeof(*temp)); 240 memset(temp, 0, sizeof(*temp)); 241 temp->filename = estrdup(filename); 242 temp->req_list = NULL; 243 temp->prov_list = NULL; 244 temp->keyword_list = NULL; 245 temp->in_progress = RESET; 246 /* 247 * link the filenode into the list of filenodes. 248 * note that the double linking means we can delete a 249 * filenode without searching for where it belongs. 250 */ 251 temp->next = fn_head->next; 252 if (temp->next != NULL) 253 temp->next->last = temp; 254 temp->last = fn_head; 255 fn_head->next = temp; 256 return (temp); 257 } 258 259 /* 260 * add a requirement to a filenode. 261 */ 262 void 263 add_require(fnode, s) 264 filenode *fnode; 265 char *s; 266 { 267 Hash_Entry *entry; 268 f_reqnode *rnode; 269 int new; 270 271 entry = Hash_CreateEntry(provide_hash, s, &new); 272 if (new) 273 Hash_SetValue(entry, NULL); 274 rnode = emalloc(sizeof(*rnode)); 275 rnode->entry = entry; 276 rnode->next = fnode->req_list; 277 fnode->req_list = rnode; 278 } 279 280 /* 281 * add a provision to a filenode. if this provision doesn't 282 * have a head node, create one here. 283 */ 284 void 285 add_provide(fnode, s) 286 filenode *fnode; 287 char *s; 288 { 289 Hash_Entry *entry; 290 f_provnode *f_pnode; 291 provnode *pnode, *head; 292 int new; 293 294 entry = Hash_CreateEntry(provide_hash, s, &new); 295 head = Hash_GetValue(entry); 296 297 /* create a head node if necessary. */ 298 if (head == NULL) { 299 head = emalloc(sizeof(*head)); 300 head->head = SET; 301 head->in_progress = RESET; 302 head->fnode = NULL; 303 head->last = head->next = NULL; 304 Hash_SetValue(entry, head); 305 } 306 #if 0 307 /* 308 * Don't warn about this. We want to be able to support 309 * scripts that do two complex things: 310 * 311 * - Two independent scripts which both provide the 312 * same thing. Both scripts must be executed in 313 * any order to meet the barrier. An example: 314 * 315 * Script 1: 316 * 317 * PROVIDE: mail 318 * REQUIRE: LOGIN 319 * 320 * Script 2: 321 * 322 * PROVIDE: mail 323 * REQUIRE: LOGIN 324 * 325 * - Two interdependent scripts which both provide the 326 * same thing. Both scripts must be executed in 327 * graph order to meet the barrier. An example: 328 * 329 * Script 1: 330 * 331 * PROVIDE: nameservice dnscache 332 * REQUIRE: SERVERS 333 * 334 * Script 2: 335 * 336 * PROVIDE: nameservice nscd 337 * REQUIRE: dnscache 338 */ 339 else if (new == 0) { 340 warnx("file `%s' provides `%s'.", fnode->filename, s); 341 warnx("\tpreviously seen in `%s'.", 342 head->next->fnode->filename); 343 } 344 #endif 345 346 pnode = emalloc(sizeof(*pnode)); 347 pnode->head = RESET; 348 pnode->in_progress = RESET; 349 pnode->fnode = fnode; 350 pnode->next = head->next; 351 pnode->last = head; 352 head->next = pnode; 353 if (pnode->next != NULL) 354 pnode->next->last = pnode; 355 356 f_pnode = emalloc(sizeof(*f_pnode)); 357 f_pnode->pnode = pnode; 358 f_pnode->next = fnode->prov_list; 359 fnode->prov_list = f_pnode; 360 } 361 362 /* 363 * put the BEFORE: lines to a list and handle them later. 364 */ 365 void 366 add_before(fnode, s) 367 filenode *fnode; 368 char *s; 369 { 370 strnodelist *bf_ent; 371 372 bf_ent = emalloc(sizeof *bf_ent + strlen(s)); 373 bf_ent->node = fnode; 374 strcpy(bf_ent->s, s); 375 bf_ent->next = bl_list; 376 bl_list = bf_ent; 377 } 378 379 /* 380 * add a key to a filenode. 381 */ 382 void 383 add_keyword(fnode, s) 384 filenode *fnode; 385 char *s; 386 { 387 388 strnode_add(&fnode->keyword_list, s, fnode); 389 } 390 391 /* 392 * loop over the rest of a REQUIRE line, giving each word to 393 * add_require() to do the real work. 394 */ 395 void 396 parse_require(node, buffer) 397 filenode *node; 398 char *buffer; 399 { 400 char *s; 401 402 while ((s = strsep(&buffer, " \t\n")) != NULL) 403 if (*s != '\0') 404 add_require(node, s); 405 } 406 407 /* 408 * loop over the rest of a PROVIDE line, giving each word to 409 * add_provide() to do the real work. 410 */ 411 void 412 parse_provide(node, buffer) 413 filenode *node; 414 char *buffer; 415 { 416 char *s; 417 418 while ((s = strsep(&buffer, " \t\n")) != NULL) 419 if (*s != '\0') 420 add_provide(node, s); 421 } 422 423 /* 424 * loop over the rest of a BEFORE line, giving each word to 425 * add_before() to do the real work. 426 */ 427 void 428 parse_before(node, buffer) 429 filenode *node; 430 char *buffer; 431 { 432 char *s; 433 434 while ((s = strsep(&buffer, " \t\n")) != NULL) 435 if (*s != '\0') 436 add_before(node, s); 437 } 438 439 /* 440 * loop over the rest of a KEYWORD line, giving each word to 441 * add_keyword() to do the real work. 442 */ 443 void 444 parse_keywords(node, buffer) 445 filenode *node; 446 char *buffer; 447 { 448 char *s; 449 450 while ((s = strsep(&buffer, " \t\n")) != NULL) 451 if (*s != '\0') 452 add_keyword(node, s); 453 } 454 455 /* 456 * given a file name, create a filenode for it, read in lines looking 457 * for provision and requirement lines, building the graphs as needed. 458 */ 459 void 460 crunch_file(filename) 461 char *filename; 462 { 463 FILE *fp; 464 char *buf; 465 int require_flag, provide_flag, before_flag, keywords_flag; 466 enum { BEFORE_PARSING, PARSING, PARSING_DONE } state; 467 filenode *node; 468 char delims[3] = { '\\', '\\', '\0' }; 469 struct stat st; 470 471 if ((fp = fopen(filename, "r")) == NULL) { 472 warn("could not open %s", filename); 473 return; 474 } 475 476 if (fstat(fileno(fp), &st) == -1) { 477 warn("could not stat %s", filename); 478 fclose(fp); 479 return; 480 } 481 482 if (!S_ISREG(st.st_mode)) { 483 #if 0 484 warnx("%s is not a file", filename); 485 #endif 486 fclose(fp); 487 return; 488 } 489 490 node = filenode_new(filename); 491 492 /* 493 * we don't care about length, line number, don't want # for comments, 494 * and have no flags. 495 */ 496 for (state = BEFORE_PARSING; state != PARSING_DONE && 497 (buf = fparseln(fp, NULL, NULL, delims, 0)) != NULL; free(buf)) { 498 require_flag = provide_flag = before_flag = keywords_flag = 0; 499 if (strncmp(REQUIRE_STR, buf, REQUIRE_LEN) == 0) 500 require_flag = REQUIRE_LEN; 501 else if (strncmp(REQUIRES_STR, buf, REQUIRES_LEN) == 0) 502 require_flag = REQUIRES_LEN; 503 else if (strncmp(PROVIDE_STR, buf, PROVIDE_LEN) == 0) 504 provide_flag = PROVIDE_LEN; 505 else if (strncmp(PROVIDES_STR, buf, PROVIDES_LEN) == 0) 506 provide_flag = PROVIDES_LEN; 507 else if (strncmp(BEFORE_STR, buf, BEFORE_LEN) == 0) 508 before_flag = BEFORE_LEN; 509 else if (strncmp(KEYWORD_STR, buf, KEYWORD_LEN) == 0) 510 keywords_flag = KEYWORD_LEN; 511 else if (strncmp(KEYWORDS_STR, buf, KEYWORDS_LEN) == 0) 512 keywords_flag = KEYWORDS_LEN; 513 else { 514 if (state == PARSING) 515 state = PARSING_DONE; 516 continue; 517 } 518 519 state = PARSING; 520 if (require_flag) 521 parse_require(node, buf + require_flag); 522 else if (provide_flag) 523 parse_provide(node, buf + provide_flag); 524 else if (before_flag) 525 parse_before(node, buf + before_flag); 526 else if (keywords_flag) 527 parse_keywords(node, buf + keywords_flag); 528 } 529 fclose(fp); 530 } 531 532 Hash_Entry * 533 make_fake_provision(node) 534 filenode *node; 535 { 536 Hash_Entry *entry; 537 f_provnode *f_pnode; 538 provnode *head, *pnode; 539 static int i = 0; 540 int new; 541 char buffer[30]; 542 543 do { 544 snprintf(buffer, sizeof buffer, "fake_prov_%08d", i++); 545 entry = Hash_CreateEntry(provide_hash, buffer, &new); 546 } while (new == 0); 547 head = emalloc(sizeof(*head)); 548 head->head = SET; 549 head->in_progress = RESET; 550 head->fnode = NULL; 551 head->last = head->next = NULL; 552 Hash_SetValue(entry, head); 553 554 pnode = emalloc(sizeof(*pnode)); 555 pnode->head = RESET; 556 pnode->in_progress = RESET; 557 pnode->fnode = node; 558 pnode->next = head->next; 559 pnode->last = head; 560 head->next = pnode; 561 if (pnode->next != NULL) 562 pnode->next->last = pnode; 563 564 f_pnode = emalloc(sizeof(*f_pnode)); 565 f_pnode->pnode = pnode; 566 f_pnode->next = node->prov_list; 567 node->prov_list = f_pnode; 568 569 return (entry); 570 } 571 572 /* 573 * go through the BEFORE list, inserting requirements into the graph(s) 574 * as required. in the before list, for each entry B, we have a file F 575 * and a string S. we create a "fake" provision (P) that F provides. 576 * for each entry in the provision list for S, add a requirement to 577 * that provisions filenode for P. 578 */ 579 void 580 insert_before() 581 { 582 Hash_Entry *entry, *fake_prov_entry; 583 provnode *pnode; 584 f_reqnode *rnode; 585 strnodelist *bl; 586 int new; 587 588 while (bl_list != NULL) { 589 bl = bl_list->next; 590 591 fake_prov_entry = make_fake_provision(bl_list->node); 592 593 entry = Hash_CreateEntry(provide_hash, bl_list->s, &new); 594 if (new == 1) 595 warnx("file `%s' is before unknown provision `%s'", bl_list->node->filename, bl_list->s); 596 597 for (pnode = Hash_GetValue(entry); pnode; pnode = pnode->next) { 598 if (pnode->head) 599 continue; 600 601 rnode = emalloc(sizeof(*rnode)); 602 rnode->entry = fake_prov_entry; 603 rnode->next = pnode->fnode->req_list; 604 pnode->fnode->req_list = rnode; 605 } 606 607 free(bl_list); 608 bl_list = bl; 609 } 610 } 611 612 /* 613 * loop over all the files calling crunch_file() on them to do the 614 * real work. after we have built all the nodes, insert the BEFORE: 615 * lines into graph(s). 616 */ 617 void 618 crunch_all_files() 619 { 620 int i; 621 622 for (i = 0; i < file_count; i++) 623 crunch_file(file_list[i]); 624 insert_before(); 625 } 626 627 /* 628 * below are the functions that traverse the graphs we have built 629 * finding out the desired ordering, printing each file in turn. 630 * if missing requirements, or cyclic graphs are detected, a 631 * warning will be issued, and we will continue on.. 632 */ 633 634 /* 635 * given a requirement node (in a filename) we attempt to satisfy it. 636 * we do some sanity checking first, to ensure that we have providers, 637 * aren't already satisfied and aren't already being satisfied (ie, 638 * cyclic). if we pass all this, we loop over the provision list 639 * calling do_file() (enter recursion) for each filenode in this 640 * provision. 641 */ 642 void 643 satisfy_req(rnode, filename) 644 f_reqnode *rnode; 645 char *filename; 646 { 647 Hash_Entry *entry; 648 provnode *head; 649 650 entry = rnode->entry; 651 head = Hash_GetValue(entry); 652 653 if (head == NULL) { 654 warnx("requirement `%s' in file `%s' has no providers.", 655 Hash_GetKey(entry), filename); 656 exit_code = 1; 657 return; 658 } 659 660 /* return if the requirement is already satisfied. */ 661 if (head->next == NULL) 662 return; 663 664 /* 665 * if list is marked as in progress, 666 * print that there is a circular dependency on it and abort 667 */ 668 if (head->in_progress == SET) { 669 warnx("Circular dependency on provision `%s' in file `%s'.", 670 Hash_GetKey(entry), filename); 671 exit_code = 1; 672 return; 673 } 674 675 head->in_progress = SET; 676 677 /* 678 * while provision_list is not empty 679 * do_file(first_member_of(provision_list)); 680 */ 681 while (head->next != NULL) 682 do_file(head->next->fnode); 683 } 684 685 int 686 skip_ok(fnode) 687 filenode *fnode; 688 { 689 strnodelist *s; 690 strnodelist *k; 691 692 for (s = skip_list; s; s = s->next) 693 for (k = fnode->keyword_list; k; k = k->next) 694 if (strcmp(k->s, s->s) == 0) 695 return (0); 696 697 return (1); 698 } 699 700 int 701 keep_ok(fnode) 702 filenode *fnode; 703 { 704 strnodelist *s; 705 strnodelist *k; 706 707 for (s = keep_list; s; s = s->next) 708 for (k = fnode->keyword_list; k; k = k->next) 709 if (strcmp(k->s, s->s) == 0) 710 return (1); 711 712 /* an empty keep_list means every one */ 713 return (!keep_list); 714 } 715 716 /* 717 * given a filenode, we ensure we are not a cyclic graph. if this 718 * is ok, we loop over the filenodes requirements, calling satisfy_req() 719 * for each of them.. once we have done this, remove this filenode 720 * from each provision table, as we are now done. 721 */ 722 void 723 do_file(fnode) 724 filenode *fnode; 725 { 726 f_reqnode *r, *r_tmp; 727 f_provnode *p, *p_tmp; 728 provnode *pnode; 729 int was_set; 730 731 DPRINTF((stderr, "do_file on %s.\n", fnode->filename)); 732 733 /* 734 * if fnode is marked as in progress, 735 * print that fnode; is circularly depended upon and abort. 736 */ 737 if (fnode->in_progress == SET) { 738 warnx("Circular dependency on file `%s'.", 739 fnode->filename); 740 was_set = exit_code = 1; 741 } else 742 was_set = 0; 743 744 /* mark fnode */ 745 fnode->in_progress = SET; 746 747 /* 748 * for each requirement of fnode -> r 749 * satisfy_req(r, filename) 750 */ 751 r = fnode->req_list; 752 while (r != NULL) { 753 r_tmp = r; 754 satisfy_req(r, fnode->filename); 755 r = r->next; 756 free(r_tmp); 757 } 758 fnode->req_list = NULL; 759 760 /* 761 * for each provision of fnode -> p 762 * remove fnode from provision list for p in hash table 763 */ 764 p = fnode->prov_list; 765 while (p != NULL) { 766 p_tmp = p; 767 pnode = p->pnode; 768 if (pnode->next != NULL) { 769 pnode->next->last = pnode->last; 770 } 771 if (pnode->last != NULL) { 772 pnode->last->next = pnode->next; 773 } 774 free(pnode); 775 p = p->next; 776 free(p_tmp); 777 } 778 fnode->prov_list = NULL; 779 780 /* do_it(fnode) */ 781 DPRINTF((stderr, "next do: ")); 782 783 /* if we were already in progress, don't print again */ 784 if (was_set == 0 && skip_ok(fnode) && keep_ok(fnode)) 785 printf("%s\n", fnode->filename); 786 787 if (fnode->next != NULL) { 788 fnode->next->last = fnode->last; 789 } 790 if (fnode->last != NULL) { 791 fnode->last->next = fnode->next; 792 } 793 794 DPRINTF((stderr, "nuking %s\n", fnode->filename)); 795 free(fnode->filename); 796 free(fnode); 797 } 798 799 void 800 generate_ordering() 801 { 802 803 /* 804 * while there remain undone files{f}, 805 * pick an arbitrary f, and do_file(f) 806 * Note that the first file in the file list is perfectly 807 * arbitrary, and easy to find, so we use that. 808 */ 809 810 /* 811 * N.B.: the file nodes "self delete" after they execute, so 812 * after each iteration of the loop, the head will be pointing 813 * to something totally different. The loop ends up being 814 * executed only once for every strongly connected set of 815 * nodes. 816 */ 817 while (fn_head->next != NULL) { 818 DPRINTF((stderr, "generate on %s\n", fn_head->next->filename)); 819 do_file(fn_head->next); 820 } 821 } 822