1/* $NetBSD: rcorder.c,v 1.15 2008/05/29 14:51:25 mrg 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 72int 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 93int exit_code; 94int file_count; 95char **file_list; 96 97enum { 98 RESET = 0, 99 SET = 1, 100}; 101 102Hash_Table provide_hash_s, *provide_hash; 103 104typedef struct provnode provnode; 105typedef struct filenode filenode; 106typedef struct f_provnode f_provnode; 107typedef struct f_reqnode f_reqnode; 108typedef struct strnodelist strnodelist; 109 110struct provnode { 111 int head; 112 int in_progress; 113 filenode *fnode; 114 provnode *next, *last; 115}; 116 117struct f_provnode { 118 provnode *pnode; 119 f_provnode *next; 120}; 121 122struct f_reqnode { 123 Hash_Entry *entry; 124 f_reqnode *next; 125}; 126 127struct strnodelist { 128 filenode *node; 129 strnodelist *next; 130 char s[1]; 131}; 132 133struct 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 142filenode fn_head_s, *fn_head; 143 144strnodelist *bl_list; 145strnodelist *keep_list; 146strnodelist *skip_list; 147 148void do_file(filenode *fnode); 149void strnode_add(strnodelist **, char *, filenode *); 150int skip_ok(filenode *fnode); 151int keep_ok(filenode *fnode); 152void satisfy_req(f_reqnode *rnode, char *); 153void crunch_file(char *); 154void parse_line(filenode *, char *, void (*)(filenode *, char *)); 155filenode *filenode_new(char *); 156void add_require(filenode *, char *); 157void add_provide(filenode *, char *); 158void add_before(filenode *, char *); 159void add_keyword(filenode *, char *); 160void insert_before(void); 161Hash_Entry *make_fake_provision(filenode *); 162void crunch_all_files(void); 163void initialize(void); 164void generate_ordering(void); 165int main(int, char *[]); 166 167int 168main(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 */ 211void 212initialize(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 */ 222void 223strnode_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 */ 244filenode * 245filenode_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 */ 272void 273add_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 */ 292void 293add_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 */ 371void 372add_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 */ 381void 382add_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 */ 392void 393parse_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 */ 406void 407crunch_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 478Hash_Entry * 479make_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 */ 524void 525insert_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 */ 563void 564crunch_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 */ 588void 589satisfy_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 629int 630skip_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 643int 644keep_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 */ 668void 669do_file(filenode *fnode) 670{ 671 f_reqnode *r, *r_tmp; 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 r_tmp = r; 699 satisfy_req(r, fnode->filename); 700 r = r->next; 701#if 0 702 free(r_tmp); 703#endif 704 } 705 fnode->req_list = NULL; 706 707 /* 708 * for each provision of fnode -> p 709 * remove fnode from provision list for p in hash table 710 */ 711 p = fnode->prov_list; 712 while (p != NULL) { 713 p_tmp = p; 714 pnode = p->pnode; 715 if (pnode->next != NULL) { 716 pnode->next->last = pnode->last; 717 } 718 if (pnode->last != NULL) { 719 pnode->last->next = pnode->next; 720 } 721 free(pnode); 722 p = p->next; 723 free(p_tmp); 724 } 725 fnode->prov_list = NULL; 726 727 /* do_it(fnode) */ 728 DPRINTF((stderr, "next do: ")); 729 730 /* if we were already in progress, don't print again */ 731 if (was_set == 0 && skip_ok(fnode) && keep_ok(fnode)) 732 printf("%s\n", fnode->filename); 733 734 if (fnode->next != NULL) { 735 fnode->next->last = fnode->last; 736 } 737 if (fnode->last != NULL) { 738 fnode->last->next = fnode->next; 739 } 740 741 DPRINTF((stderr, "nuking %s\n", fnode->filename)); 742#if 0 743 free(fnode->filename); 744 free(fnode); 745#endif 746} 747 748void 749generate_ordering(void) 750{ 751 752 /* 753 * while there remain undone files{f}, 754 * pick an arbitrary f, and do_file(f) 755 * Note that the first file in the file list is perfectly 756 * arbitrary, and easy to find, so we use that. 757 */ 758 759 /* 760 * N.B.: the file nodes "self delete" after they execute, so 761 * after each iteration of the loop, the head will be pointing 762 * to something totally different. The loop ends up being 763 * executed only once for every strongly connected set of 764 * nodes. 765 */ 766 while (fn_head->next != NULL) { 767 DPRINTF((stderr, "generate on %s\n", fn_head->next->filename)); 768 do_file(fn_head->next); 769 } 770} 771