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