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