1 /*
2 * Copyright (C) 1984-2025 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
usage(void)111 static void usage(void)
112 {
113 fprintf(stderr, "usage: lesskey [-o output] [input]\n");
114 exit(1);
115 }
116
lesskey_parse_error(constant char * s)117 void lesskey_parse_error(constant char *s)
118 {
119 fprintf(stderr, "%s\n", s);
120 }
121
lstrtoi(constant char * buf,constant char ** ebuf,int radix)122 int lstrtoi(constant char *buf, constant char **ebuf, int radix)
123 {
124 return (int) strtol(buf, (char**)ebuf, radix);
125 }
126
out_of_memory(void)127 void out_of_memory(void)
128 {
129 fprintf(stderr, "lesskey: cannot allocate memory\n");
130 exit(1);
131 }
132
ecalloc(size_t count,size_t size)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
mkpathname(constant char * dirname,constant char * filename)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 */
homefile(constant char * filename)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 */
parse_args(int argc,constant char ** argv)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 */
fputbytes(FILE * fd,constant char * buf,size_t len)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 */
fputint(FILE * fd,size_t val)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
main(int argc,constant char * argv[])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