Imported Upstream version 487
[platform/upstream/less.git] / lesskey.c
1 /*
2  * Copyright (C) 1984-2016  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         register char *p;
365         register char ch;
366         register 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");
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         register 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         register 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         register 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");
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");
624         return (A_INVALID);
625 }
626
627         void
628 error(s)
629         char *s;
630 {
631         fprintf(stderr, "line %d: %s\n", linenum, s);
632         errors++;
633 }
634
635
636         void
637 parse_cmdline(p)
638         char *p;
639 {
640         int cmdlen;
641         char *actname;
642         int action;
643         char *s;
644         char c;
645
646         /*
647          * Parse the command string and store it in the current table.
648          */
649         cmdlen = 0;
650         do
651         {
652                 s = tstr(&p, 1);
653                 cmdlen += (int) strlen(s);
654                 if (cmdlen > MAX_CMDLEN)
655                         error("command too long");
656                 else
657                         add_cmd_str(s);
658         } while (*p != ' ' && *p != '\t' && *p != '\0');
659         /*
660          * Terminate the command string with a null byte.
661          */
662         add_cmd_char('\0');
663
664         /*
665          * Skip white space between the command string
666          * and the action name.
667          * Terminate the action name with a null byte.
668          */
669         p = skipsp(p);
670         if (*p == '\0')
671         {
672                 error("missing action");
673                 return;
674         }
675         actname = p;
676         p = skipnsp(p);
677         c = *p;
678         *p = '\0';
679
680         /*
681          * Parse the action name and store it in the current table.
682          */
683         action = findaction(actname);
684
685         /*
686          * See if an extra string follows the action name.
687          */
688         *p = c;
689         p = skipsp(p);
690         if (*p == '\0')
691         {
692                 add_cmd_char(action);
693         } else
694         {
695                 /*
696                  * OR the special value A_EXTRA into the action byte.
697                  * Put the extra string after the action byte.
698                  */
699                 add_cmd_char(action | A_EXTRA);
700                 while (*p != '\0')
701                         add_cmd_str(tstr(&p, 0));
702                 add_cmd_char('\0');
703         }
704 }
705
706         void
707 parse_varline(p)
708         char *p;
709 {
710         char *s;
711
712         do
713         {
714                 s = tstr(&p, 0);
715                 add_cmd_str(s);
716         } while (*p != ' ' && *p != '\t' && *p != '=' && *p != '\0');
717         /*
718          * Terminate the variable name with a null byte.
719          */
720         add_cmd_char('\0');
721
722         p = skipsp(p);
723         if (*p++ != '=')
724         {
725                 error("missing =");
726                 return;
727         }
728
729         add_cmd_char(EV_OK|A_EXTRA);
730
731         p = skipsp(p);
732         while (*p != '\0')
733         {
734                 s = tstr(&p, 0);
735                 add_cmd_str(s);
736         }
737         add_cmd_char('\0');
738 }
739
740 /*
741  * Parse a line from the lesskey file.
742  */
743         void
744 parse_line(line)
745         char *line;
746 {
747         char *p;
748
749         /*
750          * See if it is a control line.
751          */
752         if (control_line(line))
753                 return;
754         /*
755          * Skip leading white space.
756          * Replace the final newline with a null byte.
757          * Ignore blank lines and comments.
758          */
759         p = clean_line(line);
760         if (*p == '\0')
761                 return;
762
763         if (currtable == &vartable)
764                 parse_varline(p);
765         else
766                 parse_cmdline(p);
767 }
768
769         int
770 main(argc, argv)
771         int argc;
772         char *argv[];
773 {
774         FILE *desc;
775         FILE *out;
776         char line[1024];
777
778 #ifdef WIN32
779         if (getenv("HOME") == NULL)
780         {
781                 /*
782                  * If there is no HOME environment variable,
783                  * try the concatenation of HOMEDRIVE + HOMEPATH.
784                  */
785                 char *drive = getenv("HOMEDRIVE");
786                 char *path  = getenv("HOMEPATH");
787                 if (drive != NULL && path != NULL)
788                 {
789                         char *env = (char *) calloc(strlen(drive) + 
790                                         strlen(path) + 6, sizeof(char));
791                         strcpy(env, "HOME=");
792                         strcat(env, drive);
793                         strcat(env, path);
794                         putenv(env);
795                 }
796         }
797 #endif /* WIN32 */
798
799         /*
800          * Process command line arguments.
801          */
802         parse_args(argc, argv);
803         init_tables();
804
805         /*
806          * Open the input file.
807          */
808         if (strcmp(infile, "-") == 0)
809                 desc = stdin;
810         else if ((desc = fopen(infile, "r")) == NULL)
811         {
812 #if HAVE_PERROR
813                 perror(infile);
814 #else
815                 fprintf(stderr, "Cannot open %s\n", infile);
816 #endif
817                 usage();
818         }
819
820         /*
821          * Read and parse the input file, one line at a time.
822          */
823         errors = 0;
824         linenum = 0;
825         while (fgets(line, sizeof(line), desc) != NULL)
826         {
827                 ++linenum;
828                 parse_line(line);
829         }
830
831         /*
832          * Write the output file.
833          * If no output file was specified, use "$HOME/.less"
834          */
835         if (errors > 0)
836         {
837                 fprintf(stderr, "%d errors; no output produced\n", errors);
838                 exit(1);
839         }
840
841         if (outfile == NULL)
842                 outfile = getenv("LESSKEY");
843         if (outfile == NULL)
844                 outfile = homefile(LESSKEYFILE);
845         if ((out = fopen(outfile, "wb")) == NULL)
846         {
847 #if HAVE_PERROR
848                 perror(outfile);
849 #else
850                 fprintf(stderr, "Cannot open %s\n", outfile);
851 #endif
852                 exit(1);
853         }
854
855         /* File header */
856         fputbytes(out, fileheader, sizeof(fileheader));
857
858         /* Command key section */
859         fputbytes(out, cmdsection, sizeof(cmdsection));
860         fputint(out, cmdtable.pbuffer - cmdtable.buffer);
861         fputbytes(out, (char *)cmdtable.buffer, cmdtable.pbuffer-cmdtable.buffer);
862         /* Edit key section */
863         fputbytes(out, editsection, sizeof(editsection));
864         fputint(out, edittable.pbuffer - edittable.buffer);
865         fputbytes(out, (char *)edittable.buffer, edittable.pbuffer-edittable.buffer);
866
867         /* Environment variable section */
868         fputbytes(out, varsection, sizeof(varsection)); 
869         fputint(out, vartable.pbuffer - vartable.buffer);
870         fputbytes(out, (char *)vartable.buffer, vartable.pbuffer-vartable.buffer);
871
872         /* File trailer */
873         fputbytes(out, endsection, sizeof(endsection));
874         fputbytes(out, filetrailer, sizeof(filetrailer));
875         return (0);
876 }