1 /* 2 * Copyright (c) 2001-2003 3 * Fraunhofer Institute for Open Communication Systems (FhG Fokus). 4 * All rights reserved. 5 * 6 * Author: Harti Brandt <harti@freebsd.org> 7 * 8 * Redistribution of this software and documentation and use in source and 9 * binary forms, with or without modification, are permitted provided that 10 * the following conditions are met: 11 * 12 * 1. Redistributions of source code or documentation must retain the above 13 * copyright 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. Neither the name of the Institute nor the names of its contributors 18 * may be used to endorse or promote products derived from this software 19 * without specific prior written permission. 20 * 21 * THIS SOFTWARE AND DOCUMENTATION IS PROVIDED BY FRAUNHOFER FOKUS 22 * AND ITS CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, 23 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 24 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 25 * FRAUNHOFER FOKUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 26 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 27 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 28 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 29 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 30 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 31 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 * 33 * $Begemot: bsnmp/gensnmptree/gensnmptree.c,v 1.37 2004/04/13 15:18:15 novo Exp $ 34 * 35 * Generate OID table from table description. 36 * 37 * Syntax is: 38 * --------- 39 * tree := head elements ')' 40 * 41 * entry := head ':' index STRING elements ')' 42 * 43 * leaf := head TYPE STRING ACCESS ')' 44 * 45 * column := head TYPE ACCESS ')' 46 * 47 * head := '(' INT STRING 48 * 49 * elements := EMPTY | elements element 50 * 51 * element := tree | leaf 52 * 53 * index := TYPE | index TYPE 54 * 55 */ 56 #include <sys/types.h> 57 #include <sys/param.h> 58 #include <stdio.h> 59 #include <stdlib.h> 60 #include <stdarg.h> 61 #include <unistd.h> 62 #include <string.h> 63 #include <ctype.h> 64 #include <err.h> 65 #include <sys/queue.h> 66 #include "asn1.h" 67 #include "snmp.h" 68 #include "snmpagent.h" 69 70 /* 71 * Constant prefix for all OIDs 72 */ 73 static const asn_subid_t prefix[] = { 1, 3, 6 }; 74 #define PREFIX_LEN (sizeof(prefix) / sizeof(prefix[0])) 75 76 u_int tree_size; 77 static const char *file_prefix = ""; 78 static FILE *fp; 79 80 /* if true generate local include paths */ 81 static int localincs = 0; 82 83 static const char usgtxt[] = "\ 84 Generate SNMP tables. Copyright (c) 2001-2002 Fraunhofer Institute for\n\ 85 Open Communication Systems (FhG Fokus). All rights reserved.\n\ 86 usage: gensnmptree [-hel] [-p prefix] [name]...\n\ 87 options:\n\ 88 -h print this info\n\ 89 -e extrace the named oids\n\ 90 -l generate local include directives\n\ 91 -p prefix prepend prefix to file and variable names\n\ 92 "; 93 94 /* 95 * A node in the OID tree 96 */ 97 enum ntype { 98 NODE_LEAF = 1, 99 NODE_TREE, 100 NODE_ENTRY, 101 NODE_COLUMN 102 }; 103 104 enum { 105 FL_GET = 0x01, 106 FL_SET = 0x02, 107 }; 108 109 struct node; 110 TAILQ_HEAD(node_list, node); 111 112 struct node { 113 enum ntype type; 114 asn_subid_t id; /* last element of OID */ 115 char *name; /* name of node */ 116 TAILQ_ENTRY(node) link; 117 u_int lno; /* starting line number */ 118 u_int flags; /* allowed operations */ 119 120 union { 121 struct tree { 122 struct node_list subs; 123 } tree; 124 125 struct entry { 126 u_int32_t index; /* index for table entry */ 127 char *func; /* function for tables */ 128 struct node_list subs; 129 } entry; 130 131 struct leaf { 132 enum snmp_syntax syntax; /* syntax for this leaf */ 133 char *func; /* function name */ 134 } leaf; 135 136 struct column { 137 enum snmp_syntax syntax; /* syntax for this column */ 138 } column; 139 } u; 140 }; 141 142 struct func { 143 const char *name; 144 LIST_ENTRY(func) link; 145 }; 146 147 static LIST_HEAD(, func) funcs = LIST_HEAD_INITIALIZER(funcs); 148 149 /************************************************************ 150 * 151 * Allocate memory and panic just in the case... 152 */ 153 static void * 154 xalloc(size_t size) 155 { 156 void *ptr; 157 158 if ((ptr = malloc(size)) == NULL) 159 err(1, "allocing %u bytes", size); 160 161 return (ptr); 162 } 163 164 /************************************************************ 165 * 166 * Parsing input 167 */ 168 enum tok { 169 TOK_EOF = 0200, /* end-of-file seen */ 170 TOK_NUM, /* number */ 171 TOK_STR, /* string */ 172 TOK_ACCESS, /* access operator */ 173 TOK_TYPE, /* type operator */ 174 }; 175 176 static const struct { 177 const char *str; 178 enum tok tok; 179 u_int val; 180 } keywords[] = { 181 { "GET", TOK_ACCESS, FL_GET }, 182 { "SET", TOK_ACCESS, FL_SET }, 183 { "NULL", TOK_TYPE, SNMP_SYNTAX_NULL }, 184 { "INTEGER", TOK_TYPE, SNMP_SYNTAX_INTEGER }, 185 { "INTEGER32", TOK_TYPE, SNMP_SYNTAX_INTEGER }, 186 { "UNSIGNED32", TOK_TYPE, SNMP_SYNTAX_GAUGE }, 187 { "OCTETSTRING", TOK_TYPE, SNMP_SYNTAX_OCTETSTRING }, 188 { "IPADDRESS", TOK_TYPE, SNMP_SYNTAX_IPADDRESS }, 189 { "OID", TOK_TYPE, SNMP_SYNTAX_OID }, 190 { "TIMETICKS", TOK_TYPE, SNMP_SYNTAX_TIMETICKS }, 191 { "COUNTER", TOK_TYPE, SNMP_SYNTAX_COUNTER }, 192 { "GAUGE", TOK_TYPE, SNMP_SYNTAX_GAUGE }, 193 { "COUNTER64", TOK_TYPE, SNMP_SYNTAX_COUNTER64 }, 194 { NULL, 0, 0 } 195 }; 196 197 /* arbitrary upper limit on node names and function names */ 198 #define MAXSTR 1000 199 char str[MAXSTR]; 200 u_long val; /* integer values */ 201 u_int lno = 1; /* current line number */ 202 203 static void report(const char *, ...) __dead2 __printflike(1, 2); 204 static void report_node(const struct node *, const char *, ...) 205 __dead2 __printflike(2, 3); 206 207 /* 208 * Report an error and exit. 209 */ 210 static void 211 report(const char *fmt, ...) 212 { 213 va_list ap; 214 int c; 215 216 va_start(ap, fmt); 217 fprintf(stderr, "line %u: ", lno); 218 vfprintf(stderr, fmt, ap); 219 fprintf(stderr, "\n"); 220 fprintf(stderr, "context: \""); 221 while ((c = getchar()) != EOF && c != '\n') 222 fprintf(stderr, "%c", c); 223 fprintf(stderr, "\n"); 224 va_end(ap); 225 exit(1); 226 } 227 static void 228 report_node(const struct node *np, const char *fmt, ...) 229 { 230 va_list ap; 231 232 va_start(ap, fmt); 233 fprintf(stderr, "line %u, node %s: ", np->lno, np->name); 234 vfprintf(stderr, fmt, ap); 235 fprintf(stderr, "\n"); 236 va_end(ap); 237 exit(1); 238 } 239 240 /* 241 * Return a fresh copy of the string constituting the current token. 242 */ 243 static char * 244 savetok(void) 245 { 246 return (strcpy(xalloc(strlen(str)+1), str)); 247 } 248 249 /* 250 * Get the next token from input. 251 */ 252 static int 253 gettoken(void) 254 { 255 int c; 256 257 again: 258 /* 259 * Skip any whitespace before the next token 260 */ 261 while ((c = getchar()) != EOF) { 262 if (c == '\n') 263 lno++; 264 if (!isspace(c)) 265 break; 266 } 267 if (c == EOF) 268 return (TOK_EOF); 269 if (!isascii(c)) 270 report("unexpected character %#2x", (u_int)c); 271 272 /* 273 * Skip comments 274 */ 275 if (c == '#') { 276 while ((c = getchar()) != EOF) { 277 if (c == '\n') { 278 lno++; 279 goto again; 280 } 281 } 282 report("unexpected EOF in comment"); 283 } 284 285 /* 286 * Single character tokens 287 */ 288 if (c == ')' || c == '(' || c == ':') 289 return (c); 290 291 /* 292 * Sort out numbers 293 */ 294 if (isdigit(c)) { 295 ungetc(c, stdin); 296 scanf("%lu", &val); 297 return (TOK_NUM); 298 } 299 300 /* 301 * So that has to be a string. 302 */ 303 if (isalpha(c) || c == '_') { 304 size_t n = 0; 305 str[n++] = c; 306 while ((c = getchar()) != EOF) { 307 if (!isalnum(c) && c != '_') { 308 ungetc(c, stdin); 309 break; 310 } 311 if (n == sizeof(str) - 1) { 312 str[n++] = '\0'; 313 report("string too long '%s...'", str); 314 } 315 str[n++] = c; 316 } 317 str[n++] = '\0'; 318 319 /* 320 * Keywords 321 */ 322 for (c = 0; keywords[c].str != NULL; c++) 323 if (strcmp(keywords[c].str, str) == 0) { 324 val = keywords[c].val; 325 return (keywords[c].tok); 326 } 327 328 return (TOK_STR); 329 } 330 if (isprint(c)) 331 errx(1, "%u: unexpected character '%c'", lno, c); 332 else 333 errx(1, "%u: unexpected character 0x%02x", lno, (u_int)c); 334 } 335 336 /* 337 * Parse the next node (complete with all subnodes) 338 */ 339 static struct node * 340 parse(enum tok tok) 341 { 342 struct node *node; 343 struct node *sub; 344 u_int index_count; 345 346 node = xalloc(sizeof(struct node)); 347 node->lno = lno; 348 349 if (tok != '(') 350 report("'(' expected at begin of node"); 351 if (gettoken() != TOK_NUM) 352 report("node id expected after opening '('"); 353 if (val > ASN_MAXID) 354 report("subid too large '%lu'", val); 355 node->id = (asn_subid_t)val; 356 if (gettoken() != TOK_STR) 357 report("node name expected after '(' ID"); 358 node->name = savetok(); 359 360 if ((tok = gettoken()) == TOK_TYPE) { 361 /* LEAF or COLUM */ 362 u_int syntax = val; 363 364 if ((tok = gettoken()) == TOK_STR) { 365 /* LEAF */ 366 node->type = NODE_LEAF; 367 node->u.leaf.func = savetok(); 368 node->u.leaf.syntax = syntax; 369 tok = gettoken(); 370 } else { 371 /* COLUMN */ 372 node->type = NODE_COLUMN; 373 node->u.column.syntax = syntax; 374 } 375 376 while (tok != ')') { 377 if (tok != TOK_ACCESS) 378 report("access keyword or ')' expected"); 379 node->flags |= (u_int)val; 380 tok = gettoken(); 381 } 382 383 } else if (tok == ':') { 384 /* ENTRY */ 385 node->type = NODE_ENTRY; 386 TAILQ_INIT(&node->u.entry.subs); 387 388 index_count = 0; 389 node->u.entry.index = 0; 390 while ((tok = gettoken()) == TOK_TYPE) { 391 if (index_count++ == SNMP_INDEXES_MAX) 392 report("too many table indexes"); 393 node->u.entry.index |= 394 val << (SNMP_INDEX_SHIFT * index_count); 395 } 396 node->u.entry.index |= index_count; 397 if (index_count == 0) 398 report("need at least one index"); 399 400 if (tok != TOK_STR) 401 report("function name expected"); 402 403 node->u.entry.func = savetok(); 404 405 tok = gettoken(); 406 407 while (tok != ')') { 408 sub = parse(tok); 409 TAILQ_INSERT_TAIL(&node->u.entry.subs, sub, link); 410 tok = gettoken(); 411 } 412 413 } else { 414 /* subtree */ 415 node->type = NODE_TREE; 416 TAILQ_INIT(&node->u.tree.subs); 417 418 while (tok != ')') { 419 sub = parse(tok); 420 TAILQ_INSERT_TAIL(&node->u.tree.subs, sub, link); 421 tok = gettoken(); 422 } 423 } 424 return (node); 425 } 426 427 /* 428 * Generate the C-code table part for one node. 429 */ 430 static void 431 gen_node(struct node *np, struct asn_oid *oid, u_int idx, const char *func) 432 { 433 u_int n; 434 struct node *sub; 435 u_int syntax; 436 437 if (oid->len == ASN_MAXOIDLEN) 438 report_node(np, "OID too long"); 439 oid->subs[oid->len++] = np->id; 440 441 if (np->type == NODE_TREE) { 442 TAILQ_FOREACH(sub, &np->u.tree.subs, link) 443 gen_node(sub, oid, 0, NULL); 444 oid->len--; 445 return; 446 } 447 if (np->type == NODE_ENTRY) { 448 TAILQ_FOREACH(sub, &np->u.entry.subs, link) 449 gen_node(sub, oid, np->u.entry.index, np->u.entry.func); 450 oid->len--; 451 return; 452 } 453 454 /* leaf or column */ 455 if ((np->flags & (FL_GET|FL_SET)) == 0) { 456 oid->len--; 457 return; 458 } 459 460 fprintf(fp, " {{ %u, {", oid->len); 461 for (n = 0; n < oid->len; n++) 462 fprintf(fp, " %u,", oid->subs[n]); 463 fprintf(fp, " }}, \"%s\", ", np->name); 464 465 if (np->type == NODE_COLUMN) { 466 syntax = np->u.column.syntax; 467 fprintf(fp, "SNMP_NODE_COLUMN, "); 468 } else { 469 syntax = np->u.leaf.syntax; 470 fprintf(fp, "SNMP_NODE_LEAF, "); 471 } 472 473 switch (syntax) { 474 475 case SNMP_SYNTAX_NULL: 476 fprintf(fp, "SNMP_SYNTAX_NULL, "); 477 break; 478 479 case SNMP_SYNTAX_INTEGER: 480 fprintf(fp, "SNMP_SYNTAX_INTEGER, "); 481 break; 482 483 case SNMP_SYNTAX_OCTETSTRING: 484 fprintf(fp, "SNMP_SYNTAX_OCTETSTRING, "); 485 break; 486 487 case SNMP_SYNTAX_IPADDRESS: 488 fprintf(fp, "SNMP_SYNTAX_IPADDRESS, "); 489 break; 490 491 case SNMP_SYNTAX_OID: 492 fprintf(fp, "SNMP_SYNTAX_OID, "); 493 break; 494 495 case SNMP_SYNTAX_TIMETICKS: 496 fprintf(fp, "SNMP_SYNTAX_TIMETICKS, "); 497 break; 498 499 case SNMP_SYNTAX_COUNTER: 500 fprintf(fp, "SNMP_SYNTAX_COUNTER, "); 501 break; 502 503 case SNMP_SYNTAX_GAUGE: 504 fprintf(fp, "SNMP_SYNTAX_GAUGE, "); 505 break; 506 507 case SNMP_SYNTAX_COUNTER64: 508 fprintf(fp, "SNMP_SYNTAX_COUNTER64, "); 509 break; 510 511 case SNMP_SYNTAX_NOSUCHOBJECT: 512 case SNMP_SYNTAX_NOSUCHINSTANCE: 513 case SNMP_SYNTAX_ENDOFMIBVIEW: 514 abort(); 515 } 516 517 if (np->type == NODE_COLUMN) 518 fprintf(fp, "%s, ", func); 519 else 520 fprintf(fp, "%s, ", np->u.leaf.func); 521 522 fprintf(fp, "0"); 523 if (np->flags & FL_SET) 524 fprintf(fp, "|SNMP_NODE_CANSET"); 525 fprintf(fp, ", %#x, NULL, NULL },\n", idx); 526 oid->len--; 527 return; 528 } 529 530 /* 531 * Generate the header file with the function declarations. 532 */ 533 static void 534 gen_header(struct node *np, u_int oidlen, const char *func) 535 { 536 char f[MAXSTR + 4]; 537 struct node *sub; 538 struct func *ptr; 539 540 oidlen++; 541 if (np->type == NODE_TREE) { 542 TAILQ_FOREACH(sub, &np->u.tree.subs, link) 543 gen_header(sub, oidlen, NULL); 544 return; 545 } 546 if (np->type == NODE_ENTRY) { 547 TAILQ_FOREACH(sub, &np->u.entry.subs, link) 548 gen_header(sub, oidlen, np->u.entry.func); 549 return; 550 } 551 552 if((np->flags & (FL_GET|FL_SET)) == 0) 553 return; 554 555 if (np->type == NODE_COLUMN) 556 sprintf(f, "%s", func); 557 else 558 sprintf(f, "%s", np->u.leaf.func); 559 560 LIST_FOREACH(ptr, &funcs, link) 561 if (strcmp(ptr->name, f) == 0) 562 break; 563 564 if (ptr == NULL) { 565 ptr = xalloc(sizeof(*ptr)); 566 ptr->name = strcpy(xalloc(strlen(f)+1), f); 567 LIST_INSERT_HEAD(&funcs, ptr, link); 568 569 fprintf(fp, "int %s(struct snmp_context *, " 570 "struct snmp_value *, u_int, u_int, " 571 "enum snmp_op);\n", f); 572 } 573 574 fprintf(fp, "# define LEAF_%s %u\n", np->name, np->id); 575 } 576 577 /* 578 * Generate the OID table. 579 */ 580 static void 581 gen_table(struct node *node) 582 { 583 struct asn_oid oid; 584 585 fprintf(fp, "#include <sys/types.h>\n"); 586 fprintf(fp, "#include <stdio.h>\n"); 587 if (localincs) { 588 fprintf(fp, "#include \"asn1.h\"\n"); 589 fprintf(fp, "#include \"snmp.h\"\n"); 590 fprintf(fp, "#include \"snmpagent.h\"\n"); 591 } else { 592 fprintf(fp, "#include <bsnmp/asn1.h>\n"); 593 fprintf(fp, "#include <bsnmp/snmp.h>\n"); 594 fprintf(fp, "#include <bsnmp/snmpagent.h>\n"); 595 } 596 fprintf(fp, "#include \"%stree.h\"\n", file_prefix); 597 fprintf(fp, "\n"); 598 599 fprintf(fp, "const struct snmp_node %sctree[] = {\n", file_prefix); 600 601 oid.len = PREFIX_LEN; 602 memcpy(oid.subs, prefix, sizeof(prefix)); 603 gen_node(node, &oid, 0, NULL); 604 605 fprintf(fp, "};\n\n"); 606 } 607 608 static int 609 extract(const struct node *np, struct asn_oid *oid, const char *obj) 610 { 611 struct node *sub; 612 u_long n; 613 614 if (oid->len == ASN_MAXOIDLEN) 615 report_node(np, "OID too long"); 616 oid->subs[oid->len++] = np->id; 617 618 if (strcmp(obj, np->name) == 0) { 619 fprintf(fp, "#define OID_%s\t%u\n", np->name, np->id); 620 fprintf(fp, "#define OIDLEN_%s\t%u\n", np->name, oid->len); 621 fprintf(fp, "#define OIDX_%s\t{ %u, {", np->name, oid->len); 622 for (n = 0; n < oid->len; n++) 623 fprintf(fp, " %u,", oid->subs[n]); 624 fprintf(fp, " } }\n"); 625 return (0); 626 } 627 628 if (np->type == NODE_TREE) { 629 TAILQ_FOREACH(sub, &np->u.tree.subs, link) 630 if (!extract(sub, oid, obj)) 631 return (0); 632 } else if (np->type == NODE_ENTRY) { 633 TAILQ_FOREACH(sub, &np->u.entry.subs, link) 634 if (!extract(sub, oid, obj)) 635 return (0); 636 } 637 oid->len--; 638 return (1); 639 } 640 641 static int 642 gen_extract(const struct node *root, const char *object) 643 { 644 struct asn_oid oid; 645 646 oid.len = PREFIX_LEN; 647 memcpy(oid.subs, prefix, sizeof(prefix)); 648 return (extract(root, &oid, object)); 649 } 650 651 652 static void 653 check_sub_order(const struct node *np, const struct node_list *subs) 654 { 655 int first; 656 const struct node *sub; 657 asn_subid_t maxid = 0; 658 659 /* ensure, that subids are ordered */ 660 first = 1; 661 TAILQ_FOREACH(sub, subs, link) { 662 if (!first && sub->id <= maxid) 663 report_node(np, "subids not ordered at %s", sub->name); 664 maxid = sub->id; 665 first = 0; 666 } 667 } 668 669 /* 670 * Do some sanity checks on the tree definition and do some computations. 671 */ 672 static void 673 check_tree(struct node *np) 674 { 675 struct node *sub; 676 677 if (np->type == NODE_LEAF || np->type == NODE_COLUMN) { 678 if ((np->flags & (FL_GET|FL_SET)) != 0) 679 tree_size++; 680 return; 681 } 682 683 if (np->type == NODE_ENTRY) { 684 check_sub_order(np, &np->u.entry.subs); 685 686 /* ensure all subnodes are columns */ 687 TAILQ_FOREACH(sub, &np->u.entry.subs, link) { 688 if (sub->type != NODE_COLUMN) 689 report_node(np, "entry subnode '%s' is not " 690 "a column", sub->name); 691 check_tree(sub); 692 } 693 } else { 694 check_sub_order(np, &np->u.tree.subs); 695 696 TAILQ_FOREACH(sub, &np->u.tree.subs, link) 697 check_tree(sub); 698 } 699 } 700 701 int 702 main(int argc, char *argv[]) 703 { 704 int do_extract = 0; 705 int opt; 706 struct node *root; 707 char fname[MAXPATHLEN + 1]; 708 709 while ((opt = getopt(argc, argv, "help:")) != EOF) 710 switch (opt) { 711 712 case 'h': 713 fprintf(stderr, "%s", usgtxt); 714 exit(0); 715 716 case 'e': 717 do_extract = 1; 718 break; 719 720 case 'l': 721 localincs = 1; 722 break; 723 724 case 'p': 725 file_prefix = optarg; 726 if (strlen(file_prefix) + strlen("tree.c") > 727 MAXPATHLEN) 728 errx(1, "prefix too long"); 729 break; 730 } 731 732 if (!do_extract && argc != optind) 733 errx(1, "no arguments allowed"); 734 if (do_extract && argc == optind) 735 errx(1, "no objects specified"); 736 737 root = parse(gettoken()); 738 if (gettoken() != TOK_EOF) 739 report("junk after closing ')'"); 740 741 check_tree(root); 742 743 if (do_extract) { 744 fp = stdout; 745 while (optind < argc) { 746 if (gen_extract(root, argv[optind])) 747 errx(1, "object not found: %s", argv[optind]); 748 optind++; 749 } 750 751 return (0); 752 } 753 sprintf(fname, "%stree.h", file_prefix); 754 if ((fp = fopen(fname, "w")) == NULL) 755 err(1, "%s: ", fname); 756 gen_header(root, PREFIX_LEN, NULL); 757 758 fprintf(fp, "#define %sCTREE_SIZE %u\n", file_prefix, tree_size); 759 fprintf(fp, "extern const struct snmp_node %sctree[];\n", file_prefix); 760 761 fclose(fp); 762 763 sprintf(fname, "%stree.c", file_prefix); 764 if ((fp = fopen(fname, "w")) == NULL) 765 err(1, "%s: ", fname); 766 gen_table(root); 767 fclose(fp); 768 769 return (0); 770 } 771