1 /* $NetBSD: lesskey.c,v 1.6 2023/10/06 07:26:47 simonb Exp $ */
2
3 /*
4 * Copyright (C) 1984-2023 Mark Nudelman
5 *
6 * You may distribute under the terms of either the GNU General Public
7 * License or the Less License, as specified in the README file.
8 *
9 * For more information, see the README file.
10 */
11
12
13 /*
14 * lesskey [-o output] [input]
15 *
16 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
17 *
18 * Make a .less file.
19 * If no input file is specified, standard input is used.
20 * If no output file is specified, $HOME/.less is used.
21 *
22 * The .less file is used to specify (to "less") user-defined
23 * key bindings. Basically any sequence of 1 to MAX_CMDLEN
24 * keystrokes may be bound to an existing less function.
25 *
26 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
27 *
28 * The input file is an ascii file consisting of a
29 * sequence of lines of the form:
30 * string <whitespace> action [chars] <newline>
31 *
32 * "string" is a sequence of command characters which form
33 * the new user-defined command. The command
34 * characters may be:
35 * 1. The actual character itself.
36 * 2. A character preceded by ^ to specify a
37 * control character (e.g. ^X means control-X).
38 * 3. A backslash followed by one to three octal digits
39 * to specify a character by its octal value.
40 * 4. A backslash followed by b, e, n, r or t
41 * to specify \b, ESC, \n, \r or \t, respectively.
42 * 5. Any character (other than those mentioned above) preceded
43 * by a \ to specify the character itself (characters which
44 * must be preceded by \ include ^, \, and whitespace.
45 * "action" is the name of a "less" action, from the table below.
46 * "chars" is an optional sequence of characters which is treated
47 * as keyboard input after the command is executed.
48 *
49 * Blank lines and lines which start with # are ignored,
50 * except for the special control lines:
51 * #command Signals the beginning of the command
52 * keys section.
53 * #line-edit Signals the beginning of the line-editing
54 * keys section.
55 * #env Signals the beginning of the environment
56 * variable section.
57 * #stop Stops command parsing in less;
58 * causes all default keys to be disabled.
59 *
60 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
61 *
62 * The output file is a non-ascii file, consisting of a header,
63 * one or more sections, and a trailer.
64 * Each section begins with a section header, a section length word
65 * and the section data. Normally there are three sections:
66 * CMD_SECTION Definition of command keys.
67 * EDIT_SECTION Definition of editing keys.
68 * END_SECTION A special section header, with no
69 * length word or section data.
70 *
71 * Section data consists of zero or more byte sequences of the form:
72 * string <0> <action>
73 * or
74 * string <0> <action|A_EXTRA> chars <0>
75 *
76 * "string" is the command string.
77 * "<0>" is one null byte.
78 * "<action>" is one byte containing the action code (the A_xxx value).
79 * If action is ORed with A_EXTRA, the action byte is followed
80 * by the null-terminated "chars" string.
81 *
82 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
83 */
84
85 #include "defines.h"
86 #include <stdio.h>
87 #include <string.h>
88 #include <stdlib.h>
89 #include "lesskey.h"
90 #include "cmd.h"
91
92 char fileheader[] = {
93 C0_LESSKEY_MAGIC,
94 C1_LESSKEY_MAGIC,
95 C2_LESSKEY_MAGIC,
96 C3_LESSKEY_MAGIC
97 };
98 char filetrailer[] = {
99 C0_END_LESSKEY_MAGIC,
100 C1_END_LESSKEY_MAGIC,
101 C2_END_LESSKEY_MAGIC
102 };
103 char cmdsection[1] = { CMD_SECTION };
104 char editsection[1] = { EDIT_SECTION };
105 char varsection[1] = { VAR_SECTION };
106 char endsection[1] = { END_SECTION };
107
108 char *infile = NULL;
109 char *outfile = NULL ;
110
111 extern char version[];
112
usage(void)113 static void usage(void)
114 {
115 fprintf(stderr, "usage: lesskey [-o output] [input]\n");
116 exit(1);
117 }
118
lesskey_parse_error(char * s)119 void lesskey_parse_error(char *s)
120 {
121 fprintf(stderr, "%s\n", s);
122 }
123
lstrtoi(char * buf,char ** ebuf,int radix)124 int lstrtoi(char *buf, char **ebuf, int radix)
125 {
126 return (int) strtol(buf, ebuf, radix);
127 }
128
out_of_memory(void)129 void out_of_memory(void)
130 {
131 fprintf(stderr, "lesskey: cannot allocate memory\n");
132 exit(1);
133 }
134
ecalloc(int count,unsigned int size)135 void * ecalloc(int count, unsigned int size)
136 {
137 void *p;
138
139 p = calloc(count, size);
140 if (p == NULL)
141 out_of_memory();
142 return (p);
143 }
144
mkpathname(char * dirname,char * filename)145 static char * mkpathname(char *dirname, char *filename)
146 {
147 char *pathname;
148
149 pathname = ecalloc(strlen(dirname) + strlen(filename) + 2, sizeof(char));
150 strcpy(pathname, dirname);
151 strcat(pathname, PATHNAME_SEP);
152 strcat(pathname, filename);
153 return (pathname);
154 }
155
156 /*
157 * Figure out the name of a default file (in the user's HOME directory).
158 */
homefile(char * filename)159 char * homefile(char *filename)
160 {
161 char *p;
162 char *pathname;
163
164 if ((p = getenv("HOME")) != NULL && *p != '\0')
165 pathname = mkpathname(p, filename);
166 #if OS2
167 else if ((p = getenv("INIT")) != NULL && *p != '\0')
168 pathname = mkpathname(p, filename);
169 #endif
170 else
171 {
172 fprintf(stderr, "cannot find $HOME - using current directory\n");
173 pathname = mkpathname(".", filename);
174 }
175 return (pathname);
176 }
177
178 /*
179 * Parse command line arguments.
180 */
parse_args(int argc,char ** argv)181 static void parse_args(int argc, char **argv)
182 {
183 char *arg;
184
185 outfile = NULL;
186 while (--argc > 0)
187 {
188 arg = *++argv;
189 if (arg[0] != '-')
190 /* Arg does not start with "-"; it's not an option. */
191 break;
192 if (arg[1] == '\0')
193 /* "-" means standard input. */
194 break;
195 if (arg[1] == '-' && arg[2] == '\0')
196 {
197 /* "--" means end of options. */
198 argc--;
199 argv++;
200 break;
201 }
202 switch (arg[1])
203 {
204 case '-':
205 if (strncmp(arg, "--output", 8) == 0)
206 {
207 if (arg[8] == '\0')
208 outfile = &arg[8];
209 else if (arg[8] == '=')
210 outfile = &arg[9];
211 else
212 usage();
213 goto opt_o;
214 }
215 if (strcmp(arg, "--version") == 0)
216 {
217 goto opt_V;
218 }
219 usage();
220 break;
221 case 'o':
222 outfile = &argv[0][2];
223 opt_o:
224 if (*outfile == '\0')
225 {
226 if (--argc <= 0)
227 usage();
228 outfile = *(++argv);
229 }
230 break;
231 case 'V':
232 opt_V:
233 printf("lesskey version %s\n", version);
234 exit(0);
235 default:
236 usage();
237 }
238 }
239 if (argc > 1)
240 usage();
241 /*
242 * Open the input file, or use DEF_LESSKEYINFILE if none specified.
243 */
244 if (argc > 0)
245 infile = *argv;
246 }
247
248 /*
249 * Output some bytes.
250 */
fputbytes(FILE * fd,char * buf,int len)251 static void fputbytes(FILE *fd, char *buf, int len)
252 {
253 while (len-- > 0)
254 {
255 fwrite(buf, sizeof(char), 1, fd);
256 buf++;
257 }
258 }
259
260 /*
261 * Output an integer, in special KRADIX form.
262 */
fputint(FILE * fd,unsigned int val)263 static void fputint(FILE *fd, unsigned int val)
264 {
265 char c;
266
267 if (val >= KRADIX*KRADIX)
268 {
269 fprintf(stderr, "error: cannot write %d, max %d\n",
270 val, KRADIX*KRADIX);
271 exit(1);
272 }
273 c = val % KRADIX;
274 fwrite(&c, sizeof(char), 1, fd);
275 c = val / KRADIX;
276 fwrite(&c, sizeof(char), 1, fd);
277 }
278
main(int argc,char * argv[])279 int main(int argc, char *argv[])
280 {
281 struct lesskey_tables tables;
282 FILE *out;
283 int errors;
284
285 #ifdef WIN32
286 if (getenv("HOME") == NULL)
287 {
288 /*
289 * If there is no HOME environment variable,
290 * try the concatenation of HOMEDRIVE + HOMEPATH.
291 */
292 char *drive = getenv("HOMEDRIVE");
293 char *path = getenv("HOMEPATH");
294 if (drive != NULL && path != NULL)
295 {
296 char *env = (char *) ecalloc(strlen(drive) +
297 strlen(path) + 6, sizeof(char));
298 strcpy(env, "HOME=");
299 strcat(env, drive);
300 strcat(env, path);
301 putenv(env);
302 }
303 }
304 #endif /* WIN32 */
305
306 /*
307 * Process command line arguments.
308 */
309 parse_args(argc, argv);
310 errors = parse_lesskey(infile, &tables);
311 if (errors)
312 {
313 fprintf(stderr, "%d errors; no output produced\n", errors);
314 return (1);
315 }
316
317 fprintf(stderr, "NOTE: lesskey is deprecated.\n It is no longer necessary to run lesskey,\n when using less version 582 and later.\n");
318
319 /*
320 * Write the output file.
321 * If no output file was specified, use "$HOME/.less"
322 */
323 if (outfile == NULL)
324 outfile = getenv("LESSKEY");
325 if (outfile == NULL)
326 outfile = homefile(LESSKEYFILE);
327 if ((out = fopen(outfile, "wb")) == NULL)
328 {
329 #if HAVE_PERROR
330 perror(outfile);
331 #else
332 fprintf(stderr, "Cannot open %s\n", outfile);
333 #endif
334 return (1);
335 }
336
337 /* File header */
338 fputbytes(out, fileheader, sizeof(fileheader));
339
340 /* Command key section */
341 fputbytes(out, cmdsection, sizeof(cmdsection));
342 fputint(out, tables.cmdtable.buf.end);
343 fputbytes(out, (char *)tables.cmdtable.buf.data, tables.cmdtable.buf.end);
344 /* Edit key section */
345 fputbytes(out, editsection, sizeof(editsection));
346 fputint(out, tables.edittable.buf.end);
347 fputbytes(out, (char *)tables.edittable.buf.data, tables.edittable.buf.end);
348
349 /* Environment variable section */
350 fputbytes(out, varsection, sizeof(varsection));
351 fputint(out, tables.vartable.buf.end);
352 fputbytes(out, (char *)tables.vartable.buf.data, tables.vartable.buf.end);
353
354 /* File trailer */
355 fputbytes(out, endsection, sizeof(endsection));
356 fputbytes(out, filetrailer, sizeof(filetrailer));
357 fclose(out);
358 return (0);
359 }
360