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