xref: /freebsd/contrib/less/lesskey.c (revision c77c488926555ca344ae3a417544cf7a720e1de1)
1 /*
2  * Copyright (C) 1984-2024  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 
90 constant char fileheader[] = {
91 	C0_LESSKEY_MAGIC,
92 	C1_LESSKEY_MAGIC,
93 	C2_LESSKEY_MAGIC,
94 	C3_LESSKEY_MAGIC
95 };
96 constant char filetrailer[] = {
97 	C0_END_LESSKEY_MAGIC,
98 	C1_END_LESSKEY_MAGIC,
99 	C2_END_LESSKEY_MAGIC
100 };
101 constant char cmdsection[1] =    { CMD_SECTION };
102 constant char editsection[1] =   { EDIT_SECTION };
103 constant char varsection[1] =    { VAR_SECTION };
104 constant char endsection[1] =    { END_SECTION };
105 
106 constant char *infile = NULL;
107 constant char *outfile = NULL;
108 
109 extern char version[];
110 
111 static void usage(void)
112 {
113 	fprintf(stderr, "usage: lesskey [-o output] [input]\n");
114 	exit(1);
115 }
116 
117 void lesskey_parse_error(constant char *s)
118 {
119 	fprintf(stderr, "%s\n", s);
120 }
121 
122 int lstrtoi(constant char *buf, constant char **ebuf, int radix)
123 {
124 	return (int) strtol(buf, (char**)ebuf, radix);
125 }
126 
127 void out_of_memory(void)
128 {
129 	fprintf(stderr, "lesskey: cannot allocate memory\n");
130 	exit(1);
131 }
132 
133 void * ecalloc(size_t count, size_t size)
134 {
135 	void *p;
136 
137 	p = calloc(count, size);
138 	if (p == NULL)
139 		out_of_memory();
140 	return (p);
141 }
142 
143 static char * mkpathname(constant char *dirname, constant 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  */
157 char * homefile(constant char *filename)
158 {
159 	constant 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  */
179 static void parse_args(int argc, constant char **argv)
180 {
181 	constant 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  */
249 static void fputbytes(FILE *fd, constant char *buf, size_t 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  */
261 static void fputint(FILE *fd, size_t val)
262 {
263 	char c1, c2;
264 
265 	if (val >= KRADIX*KRADIX)
266 	{
267 		fprintf(stderr, "error: cannot write %ld, max %ld\n",
268 			(long) val, (long) (KRADIX*KRADIX));
269 		exit(1);
270 	}
271 	c1 = (char) (val % KRADIX);
272 	val /= KRADIX;
273 	c2 = (char) (val % KRADIX);
274 	val /= KRADIX;
275 	if (val != 0) {
276 		fprintf(stderr, "error: %ld exceeds max integer size (%ld)\n",
277 			(long) val, (long) (KRADIX*KRADIX));
278 		exit(1);
279 	}
280 	fwrite(&c1, sizeof(char), 1, fd);
281 	fwrite(&c2, sizeof(char), 1, fd);
282 }
283 
284 int main(int argc, constant char *argv[])
285 {
286 	struct lesskey_tables tables;
287 	FILE *out;
288 	int errors;
289 
290 #ifdef WIN32
291 	if (getenv("HOME") == NULL)
292 	{
293 		/*
294 		 * If there is no HOME environment variable,
295 		 * try the concatenation of HOMEDRIVE + HOMEPATH.
296 		 */
297 		constant char *drive = getenv("HOMEDRIVE");
298 		constant char *path  = getenv("HOMEPATH");
299 		if (drive != NULL && path != NULL)
300 		{
301 			char *env = (char *) ecalloc(strlen(drive) +
302 					strlen(path) + 6, sizeof(char));
303 			strcpy(env, "HOME=");
304 			strcat(env, drive);
305 			strcat(env, path);
306 			putenv(env);
307 		}
308 	}
309 #endif /* WIN32 */
310 
311 	/*
312 	 * Process command line arguments.
313 	 */
314 	parse_args(argc, argv);
315 	errors = parse_lesskey(infile, &tables);
316 	if (errors)
317 	{
318 		fprintf(stderr, "%d errors; no output produced\n", errors);
319 		return (1);
320 	}
321 
322 	fprintf(stderr, "NOTE: lesskey is deprecated.\n      It is no longer necessary to run lesskey,\n      when using less version 582 and later.\n");
323 
324 	/*
325 	 * Write the output file.
326 	 * If no output file was specified, use "$HOME/.less"
327 	 */
328 	if (outfile == NULL)
329 		outfile = getenv("LESSKEY");
330 	if (outfile == NULL)
331 		outfile = homefile(LESSKEYFILE);
332 	if ((out = fopen(outfile, "wb")) == NULL)
333 	{
334 #if HAVE_PERROR
335 		perror(outfile);
336 #else
337 		fprintf(stderr, "Cannot open %s\n", outfile);
338 #endif
339 		return (1);
340 	}
341 
342 	/* File header */
343 	fputbytes(out, fileheader, sizeof(fileheader));
344 
345 	/* Command key section */
346 	fputbytes(out, cmdsection, sizeof(cmdsection));
347 	fputint(out, tables.cmdtable.buf.end);
348 	fputbytes(out, xbuf_char_data(&tables.cmdtable.buf), tables.cmdtable.buf.end);
349 	/* Edit key section */
350 	fputbytes(out, editsection, sizeof(editsection));
351 	fputint(out, tables.edittable.buf.end);
352 	fputbytes(out, xbuf_char_data(&tables.edittable.buf), tables.edittable.buf.end);
353 
354 	/* Environment variable section */
355 	fputbytes(out, varsection, sizeof(varsection));
356 	fputint(out, tables.vartable.buf.end);
357 	fputbytes(out, xbuf_char_data(&tables.vartable.buf), tables.vartable.buf.end);
358 
359 	/* File trailer */
360 	fputbytes(out, endsection, sizeof(endsection));
361 	fputbytes(out, filetrailer, sizeof(filetrailer));
362 	fclose(out);
363 	return (0);
364 }
365