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