1/*
2 * Copyright (C) 1984-2023  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 "defines.h"
84#include <stdio.h>
85#include <string.h>
86#include <stdlib.h>
87#include "lesskey.h"
88#include "cmd.h"
89
90char fileheader[] = {
91	C0_LESSKEY_MAGIC,
92	C1_LESSKEY_MAGIC,
93	C2_LESSKEY_MAGIC,
94	C3_LESSKEY_MAGIC
95};
96char filetrailer[] = {
97	C0_END_LESSKEY_MAGIC,
98	C1_END_LESSKEY_MAGIC,
99	C2_END_LESSKEY_MAGIC
100};
101char cmdsection[1] =    { CMD_SECTION };
102char editsection[1] =   { EDIT_SECTION };
103char varsection[1] =    { VAR_SECTION };
104char endsection[1] =    { END_SECTION };
105
106char *infile = NULL;
107char *outfile = NULL ;
108
109extern char version[];
110
111static void usage(void)
112{
113	fprintf(stderr, "usage: lesskey [-o output] [input]\n");
114	exit(1);
115}
116
117void lesskey_parse_error(char *s)
118{
119	fprintf(stderr, "%s\n", s);
120}
121
122int lstrtoi(char *buf, char **ebuf, int radix)
123{
124	return (int) strtol(buf, ebuf, radix);
125}
126
127void out_of_memory(void)
128{
129	fprintf(stderr, "lesskey: cannot allocate memory\n");
130	exit(1);
131}
132
133void * ecalloc(int count, unsigned int size)
134{
135	void *p;
136
137	p = calloc(count, size);
138	if (p == NULL)
139		out_of_memory();
140	return (p);
141}
142
143static char * mkpathname(char *dirname, char *filename)
144{
145	char *pathname;
146
147	pathname = ecalloc(strlen(dirname) + strlen(filename) + 2, sizeof(char));
148	strcpy(pathname, dirname);
149	strcat(pathname, PATHNAME_SEP);
150	strcat(pathname, filename);
151	return (pathname);
152}
153
154/*
155 * Figure out the name of a default file (in the user's HOME directory).
156 */
157char * homefile(char *filename)
158{
159	char *p;
160	char *pathname;
161
162	if ((p = getenv("HOME")) != NULL && *p != '\0')
163		pathname = mkpathname(p, filename);
164#if OS2
165	else if ((p = getenv("INIT")) != NULL && *p != '\0')
166		pathname = mkpathname(p, filename);
167#endif
168	else
169	{
170		fprintf(stderr, "cannot find $HOME - using current directory\n");
171		pathname = mkpathname(".", filename);
172	}
173	return (pathname);
174}
175
176/*
177 * Parse command line arguments.
178 */
179static void parse_args(int argc, char **argv)
180{
181	char *arg;
182
183	outfile = NULL;
184	while (--argc > 0)
185	{
186		arg = *++argv;
187		if (arg[0] != '-')
188			/* Arg does not start with "-"; it's not an option. */
189			break;
190		if (arg[1] == '\0')
191			/* "-" means standard input. */
192			break;
193		if (arg[1] == '-' && arg[2] == '\0')
194		{
195			/* "--" means end of options. */
196			argc--;
197			argv++;
198			break;
199		}
200		switch (arg[1])
201		{
202		case '-':
203			if (strncmp(arg, "--output", 8) == 0)
204			{
205				if (arg[8] == '\0')
206					outfile = &arg[8];
207				else if (arg[8] == '=')
208					outfile = &arg[9];
209				else
210					usage();
211				goto opt_o;
212			}
213			if (strcmp(arg, "--version") == 0)
214			{
215				goto opt_V;
216			}
217			usage();
218			break;
219		case 'o':
220			outfile = &argv[0][2];
221		opt_o:
222			if (*outfile == '\0')
223			{
224				if (--argc <= 0)
225					usage();
226				outfile = *(++argv);
227			}
228			break;
229		case 'V':
230		opt_V:
231			printf("lesskey  version %s\n", version);
232			exit(0);
233		default:
234			usage();
235		}
236	}
237	if (argc > 1)
238		usage();
239	/*
240	 * Open the input file, or use DEF_LESSKEYINFILE if none specified.
241	 */
242	if (argc > 0)
243		infile = *argv;
244}
245
246/*
247 * Output some bytes.
248 */
249static void fputbytes(FILE *fd, char *buf, int len)
250{
251	while (len-- > 0)
252	{
253		fwrite(buf, sizeof(char), 1, fd);
254		buf++;
255	}
256}
257
258/*
259 * Output an integer, in special KRADIX form.
260 */
261static void fputint(FILE *fd, unsigned int val)
262{
263	char c;
264
265	if (val >= KRADIX*KRADIX)
266	{
267		fprintf(stderr, "error: cannot write %d, max %d\n",
268			val, KRADIX*KRADIX);
269		exit(1);
270	}
271	c = val % KRADIX;
272	fwrite(&c, sizeof(char), 1, fd);
273	c = val / KRADIX;
274	fwrite(&c, sizeof(char), 1, fd);
275}
276
277int main(int argc, char *argv[])
278{
279	struct lesskey_tables tables;
280	FILE *out;
281	int errors;
282
283#ifdef WIN32
284	if (getenv("HOME") == NULL)
285	{
286		/*
287		 * If there is no HOME environment variable,
288		 * try the concatenation of HOMEDRIVE + HOMEPATH.
289		 */
290		char *drive = getenv("HOMEDRIVE");
291		char *path  = getenv("HOMEPATH");
292		if (drive != NULL && path != NULL)
293		{
294			char *env = (char *) ecalloc(strlen(drive) +
295					strlen(path) + 6, sizeof(char));
296			strcpy(env, "HOME=");
297			strcat(env, drive);
298			strcat(env, path);
299			putenv(env);
300		}
301	}
302#endif /* WIN32 */
303
304	/*
305	 * Process command line arguments.
306	 */
307	parse_args(argc, argv);
308	errors = parse_lesskey(infile, &tables);
309	if (errors)
310	{
311		fprintf(stderr, "%d errors; no output produced\n", errors);
312		return (1);
313	}
314
315	fprintf(stderr, "NOTE: lesskey is deprecated.\n      It is no longer necessary to run lesskey,\n      when using less version 582 and later.\n");
316
317	/*
318	 * Write the output file.
319	 * If no output file was specified, use "$HOME/.less"
320	 */
321	if (outfile == NULL)
322		outfile = getenv("LESSKEY");
323	if (outfile == NULL)
324		outfile = homefile(LESSKEYFILE);
325	if ((out = fopen(outfile, "wb")) == NULL)
326	{
327#if HAVE_PERROR
328		perror(outfile);
329#else
330		fprintf(stderr, "Cannot open %s\n", outfile);
331#endif
332		return (1);
333	}
334
335	/* File header */
336	fputbytes(out, fileheader, sizeof(fileheader));
337
338	/* Command key section */
339	fputbytes(out, cmdsection, sizeof(cmdsection));
340	fputint(out, tables.cmdtable.buf.end);
341	fputbytes(out, (char *)tables.cmdtable.buf.data, tables.cmdtable.buf.end);
342	/* Edit key section */
343	fputbytes(out, editsection, sizeof(editsection));
344	fputint(out, tables.edittable.buf.end);
345	fputbytes(out, (char *)tables.edittable.buf.data, tables.edittable.buf.end);
346
347	/* Environment variable section */
348	fputbytes(out, varsection, sizeof(varsection));
349	fputint(out, tables.vartable.buf.end);
350	fputbytes(out, (char *)tables.vartable.buf.data, tables.vartable.buf.end);
351
352	/* File trailer */
353	fputbytes(out, endsection, sizeof(endsection));
354	fputbytes(out, filetrailer, sizeof(filetrailer));
355	fclose(out);
356	return (0);
357}
358