]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/less/lesskey.c
less: upgrade to v581.
[FreeBSD/FreeBSD.git] / contrib / less / lesskey.c
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 }