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
87struct cmdname
88{
89	char *cn_name;
90	int cn_action;
91};
92
93struct 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
162struct 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
187struct table
188{
189	struct cmdname *names;
190	char *pbuffer;
191	char buffer[MAX_USERCMD];
192};
193
194struct table cmdtable;
195struct table edittable;
196struct table vartable;
197struct table *currtable = &cmdtable;
198
199char fileheader[] = {
200	C0_LESSKEY_MAGIC,
201	C1_LESSKEY_MAGIC,
202	C2_LESSKEY_MAGIC,
203	C3_LESSKEY_MAGIC
204};
205char filetrailer[] = {
206	C0_END_LESSKEY_MAGIC,
207	C1_END_LESSKEY_MAGIC,
208	C2_END_LESSKEY_MAGIC
209};
210char cmdsection[1] =    { CMD_SECTION };
211char editsection[1] =   { EDIT_SECTION };
212char varsection[1] =    { VAR_SECTION };
213char endsection[1] =    { END_SECTION };
214
215char *infile = NULL;
216char *outfile = NULL ;
217
218int linenum;
219int errors;
220
221extern char version[];
222
223	void
224usage(VOID_PARAM)
225{
226	fprintf(stderr, "usage: lesskey [-o output] [input]\n");
227	exit(1);
228}
229
230	char *
231mkpathname(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 *
248homefile(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
272parse_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
347init_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 *
363tstr(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 *
479skipsp(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 *
491skipnsp(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 *
504clean_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
521add_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
536add_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
547control_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
580fputbytes(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
596fputint(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
618findaction(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
631error(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
642parse_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
712parse_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
749parse_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
775main(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}
882