Home | History | Annotate | Line # | Download | only in dist
      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 
    113 static void usage(void)
    114 {
    115 	fprintf(stderr, "usage: lesskey [-o output] [input]\n");
    116 	exit(1);
    117 }
    118 
    119 void lesskey_parse_error(char *s)
    120 {
    121 	fprintf(stderr, "%s\n", s);
    122 }
    123 
    124 int lstrtoi(char *buf, char **ebuf, int radix)
    125 {
    126 	return (int) strtol(buf, ebuf, radix);
    127 }
    128 
    129 void out_of_memory(void)
    130 {
    131 	fprintf(stderr, "lesskey: cannot allocate memory\n");
    132 	exit(1);
    133 }
    134 
    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 
    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  */
    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  */
    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  */
    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  */
    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 
    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