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