xref: /freebsd/contrib/less/lesskey.c (revision e6bfd18d21b225af6a0ed67ceeaf1293b7b9eba5)
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 
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 usage(void)
112 {
113 	fprintf(stderr, "usage: lesskey [-o output] [input]\n");
114 	exit(1);
115 }
116 
117 void lesskey_parse_error(char *s)
118 {
119 	fprintf(stderr, "%s\n", s);
120 }
121 
122 int lstrtoi(char *buf, char **ebuf, int radix)
123 {
124 	return (int) strtol(buf, 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(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 
143 static 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  */
157 char * 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  */
179 static 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  */
249 static 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  */
261 static 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 
277 int 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