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