1 /* 2 * Copyright (C) 1984-2017 Mark Nudelman 3 * 4 * You may distribute under the terms of either the GNU General Public 5 * License or the Less License, as specified in the README file. 6 * 7 * For more information, see the README file. 8 */ 9 10 11 /* 12 * lesskey [-o output] [input] 13 * 14 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 15 * 16 * Make a .less file. 17 * If no input file is specified, standard input is used. 18 * If no output file is specified, $HOME/.less is used. 19 * 20 * The .less file is used to specify (to "less") user-defined 21 * key bindings. Basically any sequence of 1 to MAX_CMDLEN 22 * keystrokes may be bound to an existing less function. 23 * 24 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 25 * 26 * The input file is an ascii file consisting of a 27 * sequence of lines of the form: 28 * string <whitespace> action [chars] <newline> 29 * 30 * "string" is a sequence of command characters which form 31 * the new user-defined command. The command 32 * characters may be: 33 * 1. The actual character itself. 34 * 2. A character preceded by ^ to specify a 35 * control character (e.g. ^X means control-X). 36 * 3. A backslash followed by one to three octal digits 37 * to specify a character by its octal value. 38 * 4. A backslash followed by b, e, n, r or t 39 * to specify \b, ESC, \n, \r or \t, respectively. 40 * 5. Any character (other than those mentioned above) preceded 41 * by a \ to specify the character itself (characters which 42 * must be preceded by \ include ^, \, and whitespace. 43 * "action" is the name of a "less" action, from the table below. 44 * "chars" is an optional sequence of characters which is treated 45 * as keyboard input after the command is executed. 46 * 47 * Blank lines and lines which start with # are ignored, 48 * except for the special control lines: 49 * #command Signals the beginning of the command 50 * keys section. 51 * #line-edit Signals the beginning of the line-editing 52 * keys section. 53 * #env Signals the beginning of the environment 54 * variable section. 55 * #stop Stops command parsing in less; 56 * causes all default keys to be disabled. 57 * 58 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 59 * 60 * The output file is a non-ascii file, consisting of a header, 61 * one or more sections, and a trailer. 62 * Each section begins with a section header, a section length word 63 * and the section data. Normally there are three sections: 64 * CMD_SECTION Definition of command keys. 65 * EDIT_SECTION Definition of editing keys. 66 * END_SECTION A special section header, with no 67 * length word or section data. 68 * 69 * Section data consists of zero or more byte sequences of the form: 70 * string <0> <action> 71 * or 72 * string <0> <action|A_EXTRA> chars <0> 73 * 74 * "string" is the command string. 75 * "<0>" is one null byte. 76 * "<action>" is one byte containing the action code (the A_xxx value). 77 * If action is ORed with A_EXTRA, the action byte is followed 78 * by the null-terminated "chars" string. 79 * 80 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 81 */ 82 83 #include "less.h" 84 #include "lesskey.h" 85 #include "cmd.h" 86 87 struct cmdname 88 { 89 char *cn_name; 90 int cn_action; 91 }; 92 93 struct cmdname cmdnames[] = 94 { 95 { "back-bracket", A_B_BRACKET }, 96 { "back-line", A_B_LINE }, 97 { "back-line-force", A_BF_LINE }, 98 { "back-screen", A_B_SCREEN }, 99 { "back-scroll", A_B_SCROLL }, 100 { "back-search", A_B_SEARCH }, 101 { "back-window", A_B_WINDOW }, 102 { "debug", A_DEBUG }, 103 { "digit", A_DIGIT }, 104 { "display-flag", A_DISP_OPTION }, 105 { "display-option", A_DISP_OPTION }, 106 { "end", A_GOEND }, 107 { "end-scroll", A_RRSHIFT }, 108 { "examine", A_EXAMINE }, 109 { "filter", A_FILTER }, 110 { "first-cmd", A_FIRSTCMD }, 111 { "firstcmd", A_FIRSTCMD }, 112 { "flush-repaint", A_FREPAINT }, 113 { "forw-bracket", A_F_BRACKET }, 114 { "forw-forever", A_F_FOREVER }, 115 { "forw-until-hilite", A_F_UNTIL_HILITE }, 116 { "forw-line", A_F_LINE }, 117 { "forw-line-force", A_FF_LINE }, 118 { "forw-screen", A_F_SCREEN }, 119 { "forw-screen-force", A_FF_SCREEN }, 120 { "forw-scroll", A_F_SCROLL }, 121 { "forw-search", A_F_SEARCH }, 122 { "forw-window", A_F_WINDOW }, 123 { "goto-end", A_GOEND }, 124 { "goto-end-buffered", A_GOEND_BUF }, 125 { "goto-line", A_GOLINE }, 126 { "goto-mark", A_GOMARK }, 127 { "help", A_HELP }, 128 { "index-file", A_INDEX_FILE }, 129 { "invalid", A_UINVALID }, 130 { "left-scroll", A_LSHIFT }, 131 { "next-file", A_NEXT_FILE }, 132 { "next-tag", A_NEXT_TAG }, 133 { "noaction", A_NOACTION }, 134 { "no-scroll", A_LLSHIFT }, 135 { "percent", A_PERCENT }, 136 { "pipe", A_PIPE }, 137 { "prev-file", A_PREV_FILE }, 138 { "prev-tag", A_PREV_TAG }, 139 { "quit", A_QUIT }, 140 { "remove-file", A_REMOVE_FILE }, 141 { "repaint", A_REPAINT }, 142 { "repaint-flush", A_FREPAINT }, 143 { "repeat-search", A_AGAIN_SEARCH }, 144 { "repeat-search-all", A_T_AGAIN_SEARCH }, 145 { "reverse-search", A_REVERSE_SEARCH }, 146 { "reverse-search-all", A_T_REVERSE_SEARCH }, 147 { "right-scroll", A_RSHIFT }, 148 { "set-mark", A_SETMARK }, 149 { "shell", A_SHELL }, 150 { "status", A_STAT }, 151 { "toggle-flag", A_OPT_TOGGLE }, 152 { "toggle-option", A_OPT_TOGGLE }, 153 { "undo-hilite", A_UNDO_SEARCH }, 154 { "version", A_VERSION }, 155 { "visual", A_VISUAL }, 156 { NULL, 0 } 157 }; 158 159 struct cmdname editnames[] = 160 { 161 { "back-complete", EC_B_COMPLETE }, 162 { "backspace", EC_BACKSPACE }, 163 { "delete", EC_DELETE }, 164 { "down", EC_DOWN }, 165 { "end", EC_END }, 166 { "expand", EC_EXPAND }, 167 { "forw-complete", EC_F_COMPLETE }, 168 { "home", EC_HOME }, 169 { "insert", EC_INSERT }, 170 { "invalid", EC_UINVALID }, 171 { "kill-line", EC_LINEKILL }, 172 { "abort", EC_ABORT }, 173 { "left", EC_LEFT }, 174 { "literal", EC_LITERAL }, 175 { "right", EC_RIGHT }, 176 { "up", EC_UP }, 177 { "word-backspace", EC_W_BACKSPACE }, 178 { "word-delete", EC_W_DELETE }, 179 { "word-left", EC_W_LEFT }, 180 { "word-right", EC_W_RIGHT }, 181 { NULL, 0 } 182 }; 183 184 struct table 185 { 186 struct cmdname *names; 187 char *pbuffer; 188 char buffer[MAX_USERCMD]; 189 }; 190 191 struct table cmdtable; 192 struct table edittable; 193 struct table vartable; 194 struct table *currtable = &cmdtable; 195 196 char fileheader[] = { 197 C0_LESSKEY_MAGIC, 198 C1_LESSKEY_MAGIC, 199 C2_LESSKEY_MAGIC, 200 C3_LESSKEY_MAGIC 201 }; 202 char filetrailer[] = { 203 C0_END_LESSKEY_MAGIC, 204 C1_END_LESSKEY_MAGIC, 205 C2_END_LESSKEY_MAGIC 206 }; 207 char cmdsection[1] = { CMD_SECTION }; 208 char editsection[1] = { EDIT_SECTION }; 209 char varsection[1] = { VAR_SECTION }; 210 char endsection[1] = { END_SECTION }; 211 212 char *infile = NULL; 213 char *outfile = NULL ; 214 215 int linenum; 216 int errors; 217 218 extern char version[]; 219 220 void 221 usage() 222 { 223 fprintf(stderr, "usage: lesskey [-o output] [input]\n"); 224 exit(1); 225 } 226 227 char * 228 mkpathname(dirname, filename) 229 char *dirname; 230 char *filename; 231 { 232 char *pathname; 233 234 pathname = calloc(strlen(dirname) + strlen(filename) + 2, sizeof(char)); 235 strcpy(pathname, dirname); 236 strcat(pathname, PATHNAME_SEP); 237 strcat(pathname, filename); 238 return (pathname); 239 } 240 241 /* 242 * Figure out the name of a default file (in the user's HOME directory). 243 */ 244 char * 245 homefile(filename) 246 char *filename; 247 { 248 char *p; 249 char *pathname; 250 251 if ((p = getenv("HOME")) != NULL && *p != '\0') 252 pathname = mkpathname(p, filename); 253 #if OS2 254 else if ((p = getenv("INIT")) != NULL && *p != '\0') 255 pathname = mkpathname(p, filename); 256 #endif 257 else 258 { 259 fprintf(stderr, "cannot find $HOME - using current directory\n"); 260 pathname = mkpathname(".", filename); 261 } 262 return (pathname); 263 } 264 265 /* 266 * Parse command line arguments. 267 */ 268 void 269 parse_args(argc, argv) 270 int argc; 271 char **argv; 272 { 273 char *arg; 274 275 outfile = NULL; 276 while (--argc > 0) 277 { 278 arg = *++argv; 279 if (arg[0] != '-') 280 /* Arg does not start with "-"; it's not an option. */ 281 break; 282 if (arg[1] == '\0') 283 /* "-" means standard input. */ 284 break; 285 if (arg[1] == '-' && arg[2] == '\0') 286 { 287 /* "--" means end of options. */ 288 argc--; 289 argv++; 290 break; 291 } 292 switch (arg[1]) 293 { 294 case '-': 295 if (strncmp(arg, "--output", 8) == 0) 296 { 297 if (arg[8] == '\0') 298 outfile = &arg[8]; 299 else if (arg[8] == '=') 300 outfile = &arg[9]; 301 else 302 usage(); 303 goto opt_o; 304 } 305 if (strcmp(arg, "--version") == 0) 306 { 307 goto opt_V; 308 } 309 usage(); 310 break; 311 case 'o': 312 outfile = &argv[0][2]; 313 opt_o: 314 if (*outfile == '\0') 315 { 316 if (--argc <= 0) 317 usage(); 318 outfile = *(++argv); 319 } 320 break; 321 case 'V': 322 opt_V: 323 printf("lesskey version %s\n", version); 324 exit(0); 325 default: 326 usage(); 327 } 328 } 329 if (argc > 1) 330 usage(); 331 /* 332 * Open the input file, or use DEF_LESSKEYINFILE if none specified. 333 */ 334 if (argc > 0) 335 infile = *argv; 336 else 337 infile = homefile(DEF_LESSKEYINFILE); 338 } 339 340 /* 341 * Initialize data structures. 342 */ 343 void 344 init_tables() 345 { 346 cmdtable.names = cmdnames; 347 cmdtable.pbuffer = cmdtable.buffer; 348 349 edittable.names = editnames; 350 edittable.pbuffer = edittable.buffer; 351 352 vartable.names = NULL; 353 vartable.pbuffer = vartable.buffer; 354 } 355 356 /* 357 * Parse one character of a string. 358 */ 359 char * 360 tstr(pp, xlate) 361 char **pp; 362 int xlate; 363 { 364 char *p; 365 char ch; 366 int i; 367 static char buf[10]; 368 static char tstr_control_k[] = 369 { SK_SPECIAL_KEY, SK_CONTROL_K, 6, 1, 1, 1, '\0' }; 370 371 p = *pp; 372 switch (*p) 373 { 374 case '\\': 375 ++p; 376 switch (*p) 377 { 378 case '0': case '1': case '2': case '3': 379 case '4': case '5': case '6': case '7': 380 /* 381 * Parse an octal number. 382 */ 383 ch = 0; 384 i = 0; 385 do 386 ch = 8*ch + (*p - '0'); 387 while (*++p >= '0' && *p <= '7' && ++i < 3); 388 *pp = p; 389 if (xlate && ch == CONTROL('K')) 390 return tstr_control_k; 391 buf[0] = ch; 392 buf[1] = '\0'; 393 return (buf); 394 case 'b': 395 *pp = p+1; 396 return ("\b"); 397 case 'e': 398 *pp = p+1; 399 buf[0] = ESC; 400 buf[1] = '\0'; 401 return (buf); 402 case 'n': 403 *pp = p+1; 404 return ("\n"); 405 case 'r': 406 *pp = p+1; 407 return ("\r"); 408 case 't': 409 *pp = p+1; 410 return ("\t"); 411 case 'k': 412 if (xlate) 413 { 414 switch (*++p) 415 { 416 case 'u': ch = SK_UP_ARROW; break; 417 case 'd': ch = SK_DOWN_ARROW; break; 418 case 'r': ch = SK_RIGHT_ARROW; break; 419 case 'l': ch = SK_LEFT_ARROW; break; 420 case 'U': ch = SK_PAGE_UP; break; 421 case 'D': ch = SK_PAGE_DOWN; break; 422 case 'h': ch = SK_HOME; break; 423 case 'e': ch = SK_END; break; 424 case 'x': ch = SK_DELETE; break; 425 default: 426 error("illegal char after \\k", NULL_PARG); 427 *pp = p+1; 428 return (""); 429 } 430 *pp = p+1; 431 buf[0] = SK_SPECIAL_KEY; 432 buf[1] = ch; 433 buf[2] = 6; 434 buf[3] = 1; 435 buf[4] = 1; 436 buf[5] = 1; 437 buf[6] = '\0'; 438 return (buf); 439 } 440 /* FALLTHRU */ 441 default: 442 /* 443 * Backslash followed by any other char 444 * just means that char. 445 */ 446 *pp = p+1; 447 buf[0] = *p; 448 buf[1] = '\0'; 449 if (xlate && buf[0] == CONTROL('K')) 450 return tstr_control_k; 451 return (buf); 452 } 453 case '^': 454 /* 455 * Caret means CONTROL. 456 */ 457 *pp = p+2; 458 buf[0] = CONTROL(p[1]); 459 buf[1] = '\0'; 460 if (buf[0] == CONTROL('K')) 461 return tstr_control_k; 462 return (buf); 463 } 464 *pp = p+1; 465 buf[0] = *p; 466 buf[1] = '\0'; 467 if (xlate && buf[0] == CONTROL('K')) 468 return tstr_control_k; 469 return (buf); 470 } 471 472 /* 473 * Skip leading spaces in a string. 474 */ 475 public char * 476 skipsp(s) 477 char *s; 478 { 479 while (*s == ' ' || *s == '\t') 480 s++; 481 return (s); 482 } 483 484 /* 485 * Skip non-space characters in a string. 486 */ 487 public char * 488 skipnsp(s) 489 char *s; 490 { 491 while (*s != '\0' && *s != ' ' && *s != '\t') 492 s++; 493 return (s); 494 } 495 496 /* 497 * Clean up an input line: 498 * strip off the trailing newline & any trailing # comment. 499 */ 500 char * 501 clean_line(s) 502 char *s; 503 { 504 int i; 505 506 s = skipsp(s); 507 for (i = 0; s[i] != '\n' && s[i] != '\r' && s[i] != '\0'; i++) 508 if (s[i] == '#' && (i == 0 || s[i-1] != '\\')) 509 break; 510 s[i] = '\0'; 511 return (s); 512 } 513 514 /* 515 * Add a byte to the output command table. 516 */ 517 void 518 add_cmd_char(c) 519 int c; 520 { 521 if (currtable->pbuffer >= currtable->buffer + MAX_USERCMD) 522 { 523 error("too many commands", NULL_PARG); 524 exit(1); 525 } 526 *(currtable->pbuffer)++ = c; 527 } 528 529 /* 530 * Add a string to the output command table. 531 */ 532 void 533 add_cmd_str(s) 534 char *s; 535 { 536 for ( ; *s != '\0'; s++) 537 add_cmd_char(*s); 538 } 539 540 /* 541 * See if we have a special "control" line. 542 */ 543 int 544 control_line(s) 545 char *s; 546 { 547 #define PREFIX(str,pat) (strncmp(str,pat,strlen(pat)) == 0) 548 549 if (PREFIX(s, "#line-edit")) 550 { 551 currtable = &edittable; 552 return (1); 553 } 554 if (PREFIX(s, "#command")) 555 { 556 currtable = &cmdtable; 557 return (1); 558 } 559 if (PREFIX(s, "#env")) 560 { 561 currtable = &vartable; 562 return (1); 563 } 564 if (PREFIX(s, "#stop")) 565 { 566 add_cmd_char('\0'); 567 add_cmd_char(A_END_LIST); 568 return (1); 569 } 570 return (0); 571 } 572 573 /* 574 * Output some bytes. 575 */ 576 void 577 fputbytes(fd, buf, len) 578 FILE *fd; 579 char *buf; 580 int len; 581 { 582 while (len-- > 0) 583 { 584 fwrite(buf, sizeof(char), 1, fd); 585 buf++; 586 } 587 } 588 589 /* 590 * Output an integer, in special KRADIX form. 591 */ 592 void 593 fputint(fd, val) 594 FILE *fd; 595 unsigned int val; 596 { 597 char c; 598 599 if (val >= KRADIX*KRADIX) 600 { 601 fprintf(stderr, "error: integer too big (%d > %d)\n", 602 val, KRADIX*KRADIX); 603 exit(1); 604 } 605 c = val % KRADIX; 606 fwrite(&c, sizeof(char), 1, fd); 607 c = val / KRADIX; 608 fwrite(&c, sizeof(char), 1, fd); 609 } 610 611 /* 612 * Find an action, given the name of the action. 613 */ 614 int 615 findaction(actname) 616 char *actname; 617 { 618 int i; 619 620 for (i = 0; currtable->names[i].cn_name != NULL; i++) 621 if (strcmp(currtable->names[i].cn_name, actname) == 0) 622 return (currtable->names[i].cn_action); 623 error("unknown action", NULL_PARG); 624 return (A_INVALID); 625 } 626 627 void 628 error(s, parg) 629 char *s; 630 PARG *parg; 631 { 632 fprintf(stderr, "line %d: %s\n", linenum, s); 633 errors++; 634 (void) parg; 635 } 636 637 638 void 639 parse_cmdline(p) 640 char *p; 641 { 642 int cmdlen; 643 char *actname; 644 int action; 645 char *s; 646 char c; 647 648 /* 649 * Parse the command string and store it in the current table. 650 */ 651 cmdlen = 0; 652 do 653 { 654 s = tstr(&p, 1); 655 cmdlen += (int) strlen(s); 656 if (cmdlen > MAX_CMDLEN) 657 error("command too long", NULL_PARG); 658 else 659 add_cmd_str(s); 660 } while (*p != ' ' && *p != '\t' && *p != '\0'); 661 /* 662 * Terminate the command string with a null byte. 663 */ 664 add_cmd_char('\0'); 665 666 /* 667 * Skip white space between the command string 668 * and the action name. 669 * Terminate the action name with a null byte. 670 */ 671 p = skipsp(p); 672 if (*p == '\0') 673 { 674 error("missing action", NULL_PARG); 675 return; 676 } 677 actname = p; 678 p = skipnsp(p); 679 c = *p; 680 *p = '\0'; 681 682 /* 683 * Parse the action name and store it in the current table. 684 */ 685 action = findaction(actname); 686 687 /* 688 * See if an extra string follows the action name. 689 */ 690 *p = c; 691 p = skipsp(p); 692 if (*p == '\0') 693 { 694 add_cmd_char(action); 695 } else 696 { 697 /* 698 * OR the special value A_EXTRA into the action byte. 699 * Put the extra string after the action byte. 700 */ 701 add_cmd_char(action | A_EXTRA); 702 while (*p != '\0') 703 add_cmd_str(tstr(&p, 0)); 704 add_cmd_char('\0'); 705 } 706 } 707 708 void 709 parse_varline(p) 710 char *p; 711 { 712 char *s; 713 714 do 715 { 716 s = tstr(&p, 0); 717 add_cmd_str(s); 718 } while (*p != ' ' && *p != '\t' && *p != '=' && *p != '\0'); 719 /* 720 * Terminate the variable name with a null byte. 721 */ 722 add_cmd_char('\0'); 723 724 p = skipsp(p); 725 if (*p++ != '=') 726 { 727 error("missing =", NULL_PARG); 728 return; 729 } 730 731 add_cmd_char(EV_OK|A_EXTRA); 732 733 p = skipsp(p); 734 while (*p != '\0') 735 { 736 s = tstr(&p, 0); 737 add_cmd_str(s); 738 } 739 add_cmd_char('\0'); 740 } 741 742 /* 743 * Parse a line from the lesskey file. 744 */ 745 void 746 parse_line(line) 747 char *line; 748 { 749 char *p; 750 751 /* 752 * See if it is a control line. 753 */ 754 if (control_line(line)) 755 return; 756 /* 757 * Skip leading white space. 758 * Replace the final newline with a null byte. 759 * Ignore blank lines and comments. 760 */ 761 p = clean_line(line); 762 if (*p == '\0') 763 return; 764 765 if (currtable == &vartable) 766 parse_varline(p); 767 else 768 parse_cmdline(p); 769 } 770 771 int 772 main(argc, argv) 773 int argc; 774 char *argv[]; 775 { 776 FILE *desc; 777 FILE *out; 778 char line[1024]; 779 780 #ifdef WIN32 781 if (getenv("HOME") == NULL) 782 { 783 /* 784 * If there is no HOME environment variable, 785 * try the concatenation of HOMEDRIVE + HOMEPATH. 786 */ 787 char *drive = getenv("HOMEDRIVE"); 788 char *path = getenv("HOMEPATH"); 789 if (drive != NULL && path != NULL) 790 { 791 char *env = (char *) calloc(strlen(drive) + 792 strlen(path) + 6, sizeof(char)); 793 strcpy(env, "HOME="); 794 strcat(env, drive); 795 strcat(env, path); 796 putenv(env); 797 } 798 } 799 #endif /* WIN32 */ 800 801 /* 802 * Process command line arguments. 803 */ 804 parse_args(argc, argv); 805 init_tables(); 806 807 /* 808 * Open the input file. 809 */ 810 if (strcmp(infile, "-") == 0) 811 desc = stdin; 812 else if ((desc = fopen(infile, "r")) == NULL) 813 { 814 #if HAVE_PERROR 815 perror(infile); 816 #else 817 fprintf(stderr, "Cannot open %s\n", infile); 818 #endif 819 usage(); 820 } 821 822 /* 823 * Read and parse the input file, one line at a time. 824 */ 825 errors = 0; 826 linenum = 0; 827 while (fgets(line, sizeof(line), desc) != NULL) 828 { 829 ++linenum; 830 parse_line(line); 831 } 832 833 /* 834 * Write the output file. 835 * If no output file was specified, use "$HOME/.less" 836 */ 837 if (errors > 0) 838 { 839 fprintf(stderr, "%d errors; no output produced\n", errors); 840 exit(1); 841 } 842 843 if (outfile == NULL) 844 outfile = getenv("LESSKEY"); 845 if (outfile == NULL) 846 outfile = homefile(LESSKEYFILE); 847 if ((out = fopen(outfile, "wb")) == NULL) 848 { 849 #if HAVE_PERROR 850 perror(outfile); 851 #else 852 fprintf(stderr, "Cannot open %s\n", outfile); 853 #endif 854 exit(1); 855 } 856 857 /* File header */ 858 fputbytes(out, fileheader, sizeof(fileheader)); 859 860 /* Command key section */ 861 fputbytes(out, cmdsection, sizeof(cmdsection)); 862 fputint(out, cmdtable.pbuffer - cmdtable.buffer); 863 fputbytes(out, (char *)cmdtable.buffer, cmdtable.pbuffer-cmdtable.buffer); 864 /* Edit key section */ 865 fputbytes(out, editsection, sizeof(editsection)); 866 fputint(out, edittable.pbuffer - edittable.buffer); 867 fputbytes(out, (char *)edittable.buffer, edittable.pbuffer-edittable.buffer); 868 869 /* Environment variable section */ 870 fputbytes(out, varsection, sizeof(varsection)); 871 fputint(out, vartable.pbuffer - vartable.buffer); 872 fputbytes(out, (char *)vartable.buffer, vartable.pbuffer-vartable.buffer); 873 874 /* File trailer */ 875 fputbytes(out, endsection, sizeof(endsection)); 876 fputbytes(out, filetrailer, sizeof(filetrailer)); 877 return (0); 878 } 879