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