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