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