Files
seta75D d6fe8fe829 Init
2021-10-11 22:19:34 -03:00

2016 lines
61 KiB
C

#ifndef lint
static char sccsid[] = "@(#)47 1.13 src/bos/usr/bin/setmaps/setmaps.c, cmdtty, bos411, 9428A410j 3/22/94 02:55:40";
#endif
/*
* COMPONENT_NAME: CMDTTY terminal control commands
*
* FUNCTIONS: main (setmaps.c)
*
* ORIGINS: 27, 83
*
*/
/*
* IBM CONFIDENTIAL -- (IBM Confidential Restricted when
* combined with the aggregated modules for this product)
* SOURCE MATERIALS
* (C) COPYRIGHT International Business Machines Corp. 1988, 1994
* All Rights Reserved
*
* US Government Users Restricted Rights - Use, duplication or
* disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
*/
/*
* LEVEL 1, 5 Years Bull Confidential Information
*/
/*
* This program manages mappings on ttys. It serves three purposes:
* 1) Tells whichs maps, if any are in use on the invoking tty
* 2) connects the invoking tty to a previously loaded map
* 3) loads a new map into the kernel, which can then be connected to
*
* This program reads input/output translation maps into the structures
* defined in /usr/include/sys/ttmap.h. Maps are then loaded into the
* kernel using the TCSMAP(set map) ioctl in tty.c. Only su can load
* a map. Once loaded, a terminal may use the map by setting the imap
* and omap parameters with stty or adding them in the /etc/ports file.
* Both stty.c and getty.c call setmaps.
*
* A map file contains a set of rules which define the translation of
* one or more characters to a replacement character or string. The
* syntax of each rule is pattern:newstring. Examples:
* \033b:ps -ef remaps 2nd func key on an ibm3161 terminal
* \@:\? remap '@' to '?'. Slash req'd for special char
* \x80:\x20 remap vt220 8-bit char to space
* a?a:b$2b ? matches any single byte; $n outputs nth char
* from pattern.
*
* The ttmapch() routine in tt1.c uses the loaded maps to do
* translations on incoming and outgoing characters. The purpose of
* these translations is to provide a consistent character set across
* all ascii terminals.
*
* This program also manages code set maps (the -s option).
* A code set map determines the length and width of each code point.
* Length means the number of bytes of storage used and
* width means the number of display columns to use.
*
* The -i or -I option causes the program to install a code set map
* by calling the c library routine setcsmap. The setcsmap routine
* reads the file specified, parses it, builds a csmap structure and
* calls ioctl with the TCSCSMAP option to install it in the kernel.
* The -o or -O option causes the program to retrieve the current code
* set map by calling ioctl with the TCGCSMAP option, converting the
* resulting csmap structure to file format and writing it to the file
* specified.
*
* The code set map file format is 512 lines each ended with a '\n'.
* The first 256 lines specify the length of the code points starting
* with the value of the line number - 1. The second 256 lines specify
* the width of the code points starting with the value of the line
* number - 257. For example, if line 20 is "1'\n'", then the length
* of code points starting with a value of 19 in the first byte is 1.
*
* main - program driver; read arguments presented on command line and
* call the appropriate routines to take action.
* clear_maps - disconnects this tty from the input, output, or both
* maps. If the map the tty was using has no other ttys connected to
* it, the map is thrown away by the kernel, unless it was
* specified as a sticky map when it was loaded.
* use_map - connect a terminal to an already-loaded map.
* load_map - read file provided by user and load it into the kernel
* via the TCSMAP ioctl(must have root access to load a new map)
* show_map - show which maps are in use(if any).
* magic_key - Check a map key to see if it is a magic name which
* really means to delete the mapping from the tty.
* keyname - find the filename only given the entire pathname. The
* .in and .out suffixes are part of the keyname.
* fullpath - takes a map name and builds the full pathname of the
* file we need to open to get the map.
* usage - tell the user about the flags they can use.
* makemap - read a line at a time from the specified map file and
* store the pattern/replacement pair in a linked list of ttrules.
* add_rule - Add a rule to the map.
* chain_to_hash - Add a new explicit rule to the end of a hash chain.
* chain_to_wild - Chain a new wildcard rule onto the end of the
* wildcard rule chain.
* hash_char - Determine the character which represents the first char
* of a pattern
* translate - Translate a mapping string from the external ascii form
* to the internal form used by the kernel mapping code.
* xlate_esc_char - Handle the various types of backslash escapes which
* can be present in the input string.
* xlate_char_set - Interpret a set of chars of the form [abcd] or [a-d]
* no_op - Check to see if pattern/replacement pair really do anything
* find_replace - Find the start of repl pattern in the input string.
* is_meta: Is this a meta character?
* has_meta: Are there meta characters in this string?
* is_wild - Is this pattern a wildcard?
* asciify - Debug code. Given a char, return an ascii string
* representing it.
* asciistr - Debug code. Given a string and a pointer to a buffer,
* make new string using asciify to convert non-printables to
* printable representions.
* dump_map - Debug code. Dump the map to stdout.
* csmap_path - takes a map name and builds the full pathname of the
* file we need to open to get the code set map.
*/
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/termio.h>
#include <termios.h>
#include <sys/ttmap.h>
#include <string.h>
#include <stropts.h>
#include <sys/eucioctl.h>
#include <sys/str_tty.h>
#include <locale.h>
#include <nl_types.h>
#include "setmaps_msg.h"
#include <sys/device.h>
#include <sys/sysconfig.h>
/* needed for cfg'ing the nls module after it's been loaded into
* the kernel. This is defined in snls.h
*/
typedef struct nlsdd_init {
long which_dds; /* must be NLS_DDS - defined in <sys/str_tty.h>*/
};
nl_catd catd;
#define MSGSTR(num,str) catgets(catd,MS_SETMAPS,num,str)
#define NLS_DIR "/usr/lib/nls/termmap/"
#define CSMAP_DIR "/usr/lib/nls/csmap/"
#define IN_SUFFIX ".in"
#define OUT_SUFFIX ".out"
#define MAGIC_KEY1 "." /* magic word to clear a map */
#define MAGIC_KEY2 "NOMAP" /* magic word to clear a map */
#define NOTHING 0 /* Initial state */
#define SET_INPUT 1 /* set the input map */
#define SET_INPUT_FROM_PATH 2 /* set input map, given full path */
#define SET_OUTPUT 3 /* set the output map */
#define SET_OUTPUT_FROM_PATH 4 /* set output map, given full path */
#define SET_BOTH 5 /* set both input and output maps */
#define LOAD_ONLY 6 /* load the map, don't use it */
#define LOAD_ONLY_FROM_PATH 7 /* load the map, given full path */
#define CLEAR_MAPS 8 /* remove mapping */
#define NO_FLAGS 0 /* means just what it says */
#define OK 0 /* good result from an internal func */
#define BAD_RES -1 /* an internal func had a problem */
#define NLS_MODULE "nls" /* name of the nls module, currently str_nls */
int verb = NOTHING; /* what we're going to do */
int force_reload = FALSE; /* force a load even if it's there */
int Dump_map = FALSE; /* dump the map to stdout */
int verbose = FALSE; /* print verbose info */
char *map_name = NULL; /* map name provided by user */
char *explicit_key = NULL; /* explicit key given by user */
char *nls_dir = NLS_DIR; /* dir where maps live */
char *csmap_dir = CSMAP_DIR; /* dir where csmaps live */
int csmap_option = 0; /* whether code set map is specified */
char *arg0; /* name we were invoked as */
/* Function prototypes */
void main(int argc, char **argv);
int clear_maps(long flags);
int use_map(int verb, char *map_name, char *explicit_key, int level);
int load_map(int verb, int flags, char *map_name, int key);
void show_map(int code);
int magic_key(char *p);
char *keyname(char *map_name, char *explicit_key, int verb);
char *fullpath(int verb, char *map_name);
void usage();
int makemap(struct ttmap *ttmap, char *path, char *mapname);
int add_rule(struct ttmap *ttmap, char *pat, char *rep);
void chain_to_hash(struct ttmap *ttmap, int hash, int new, int end);
void chain_to_wild(struct ttmap *ttmap, int first, int new);
int hash_char(char *p);
int translate(char *in, char *out, int bufsize, int which);
int xlate_esc_char(char *p, char **new);
int xlate_char_set(char *in, char **pp, char **qp, char *fence);
int no_op(char *p, char *q);
char *find_replace(char *p);
int is_meta(int c, int which);
int has_meta(char *s, int which);
int is_wild(char *s);
char *asciify(unsigned char c);
char *asciistr(unsigned char *p, char *buf);
void dump_map(struct ttmap *ttmap);
int x_ioctl(int fd, int cmd, struct tty_map *tty_map, int size);
char *csmap_path(int verb, char *map_name);
int check_nls();
void main(int argc, char **argv)
{
long flags; /* state flags while parsing args */
int rc, c; /* scratch */
char *p; /* scratch */
extern int optind; /* option index, set by getopt */
extern char *optarg; /* argument of switch, set by getopt */
int dflag = 0; /* flag for -d option */
setlocale(LC_ALL,"") ;
catd = catopen(MF_SETMAPS, NL_CAT_LOCALE);
arg0 = *argv; /* save name we were invoked with */
p = strrchr(arg0, '/');
if (p != NULL) {
arg0 = ++p; /* skip dir part if any */
}
/*
* No arguments to setmaps. Display names of maps currently
* loaded for this tty.
*/
if (argc == 1) {
show_map(TM_INPUT);
show_map(TM_OUTPUT);
exit(0);
}
/* Process flags passed in by user. */
while ((c = getopt(argc,argv,"t:i:o:I:O:k:l:L:d:rcDvhs")) != EOF) {
switch (c) {
/*
* Code set map instead of terminal map.
*/
case 's':
if (Dump_map) /* Check flag to see if */
usage(); /* prev args conflict */
csmap_option = 1;
break;
/*
* Two-way mapping. To make sense, this must be a path
* name or filename with no .in/.out suffix.
*/
case 't':
if (verb != NOTHING) /* Check flag to see if */
usage(); /* prev args conflict */
verb = SET_BOTH;
map_name = optarg;
break;
/*
* Input map. Expecting filename only, with or without
* suffix.
*/
case 'i':
if (verb != NOTHING)
usage();
verb = SET_INPUT;
map_name = optarg;
break;
/*
* Output map. Expecting filename only, with or without
* suffix.
*/
case 'o':
if (verb != NOTHING)
usage();
verb = SET_OUTPUT;
map_name = optarg;
break;
/*
* Input map does not live in /usr/lib/nls/termmap. Use full
* path name provided by user. User must include suffix.
*/
case 'I':
if ((verb != NOTHING) ||
dflag)
usage();
verb = SET_INPUT_FROM_PATH;
map_name = optarg;
break;
/* Output map does not live in /usr/lib/nls/termmap. Use
* full path name provided by user. User must include
* suffix.
*/
case 'O':
if ((verb != NOTHING) ||
dflag)
usage();
verb = SET_OUTPUT_FROM_PATH;
map_name = optarg;
break;
/*
* Specify map name to be used in ttmap struct. Permits
* su to specify name of map in linked list of maps in
* the kernel. May or may not include a suffix.
*/
case 'k':
explicit_key = optarg;
break;
/*
* Load a map. Only superuser may do this. Must include
* suffix
*/
case 'l':
if (geteuid() != 0) {
fprintf(stderr,
MSGSTR(ROOT, "%s: must be root to load a map\n"),
arg0);
usage();
}
if (verb != NOTHING)
usage();
verb = LOAD_ONLY;
map_name = optarg;
break;
case 'L':
if (geteuid() != 0) {
fprintf(stderr,
MSGSTR(ROOT, "%s: must be root to load a map\n"),
arg0);
usage();
}
if (verb != NOTHING)
usage();
verb = LOAD_ONLY_FROM_PATH;
map_name = optarg;
break;
case 'r': /* Force reload of map */
if (dflag)
usage();
if (geteuid() != 0) {
fprintf(stderr,
MSGSTR(ROOT, "%s: must be root to reload a map\n"),
arg0);
usage();
}
force_reload = TRUE;
break;
case 'd': /* Override default dir */
if (dflag || force_reload ||
(verb == SET_INPUT_FROM_PATH) ||
(verb == SET_OUTPUT_FROM_PATH))
usage();
else dflag = 1;
nls_dir = optarg;
break;
case 'c': /* clear all mappings for this tty */
if ((verb != NOTHING) || Dump_map || csmap_option)
usage();
verb = CLEAR_MAPS;
break;
case 'D': /* Dump map to stdout */
if ((verb == CLEAR_MAPS) || csmap_option)
usage();
Dump_map = TRUE;
break;
case 'v': /* be verbose */
verbose = TRUE;
break;
case 'h': /* print usage message and exit */
usage();
break;
default: /* Bad flag. */
usage();
/*NOTREACHED*/
} /* end of switch */
} /* end of while (getopt) */
/* Any floating args, not preceded by switches? */
if (optind < argc)
usage();
/*
* If the -s option is used, then the code set map is being installed
* or retrieved. The only options that are compatible with -s are
* -i, -I, -o, and -O, plus -v.
*/
if (csmap_option) {
char *path;
/*
* The mapname is required.
*/
if (map_name == (char *)NULL)
usage();
rc = OK;
switch (verb) {
case SET_INPUT:
case SET_INPUT_FROM_PATH:
/*
* Determine the pathname for the code set map.
*/
path = csmap_path(verb, map_name);
if (verbose)
fprintf(stderr,
MSGSTR(CSMAPPATH, "%s: csmap pathname is: %s\n"),
arg0, path);
/*
* Call the library routine which will read the code set map,
* parse it, build a csmap structure and install it in the kernel.
*/
rc = setcsmap(path);
break;
/* We no longer support the option of retrieving the
* current code set and moving it to a file.
*/
case SET_OUTPUT:
case SET_OUTPUT_FROM_PATH:
default:
usage();
}
exit(rc);
}
flags = NO_FLAGS;
/* Check to see if map name given really means to delete a map */
if (magic_key(map_name)) {
switch (verb) {
case CLEAR_MAPS:
case SET_BOTH:
flags = TM_INPUT | TM_OUTPUT;
/* fall thru */
case SET_INPUT:
if (flags == NO_FLAGS)
flags = TM_INPUT;
/* fall thru */
case SET_OUTPUT:
if (flags == NO_FLAGS)
flags = TM_OUTPUT;
verb = CLEAR_MAPS;
break;
default:
usage();
/*NOTREACHED*/
}
}
rc = OK;
switch (verb) {
char *key;
case CLEAR_MAPS:
rc = clear_maps(flags);
break;
case SET_INPUT:
case SET_INPUT_FROM_PATH:
rc = use_map(verb, map_name, explicit_key, 0);
break;
case SET_OUTPUT:
case SET_OUTPUT_FROM_PATH:
rc = use_map(verb, map_name, explicit_key, 0);
break;
case SET_BOTH:
if (explicit_key != NULL) {
fprintf(stderr, MSGSTR(CANTUSE, "%s: can't use -k with -t\n"),
arg0);
rc = BAD_RES;
break;
}
if ((rc = use_map(SET_INPUT, map_name, explicit_key, 0)) != OK)
break;
rc = use_map(SET_OUTPUT, map_name, explicit_key, 0);
break;
case LOAD_ONLY:
case LOAD_ONLY_FROM_PATH:
key = keyname(map_name, explicit_key, verb);
rc = load_map(verb,
TM_STICKY |((force_reload) ? TM_RELOAD : TM_LOAD),
map_name, (int)key);
break;
default:
fprintf(stderr,
MSGSTR(INTERN, "%s: internal error, unexpected verb=%d\n"),
arg0, verb);
rc = BAD_RES;
break;
}
exit(rc);
/*NOTREACHED*/
}
/* clear_maps - remove mappings for this tty */
int clear_maps(long flags)
{
struct tty_map tty_map;
struct strioctl i_str;
memset((void *)&tty_map, 0, (size_t)sizeof(struct tty_map));
tty_map.tm_version = TTMAP_VERSION;
tty_map.tm_flags = flags | TM_CLEAR;
i_str.ic_cmd = TCSMAP;
i_str.ic_timout = 0;
i_str.ic_len = sizeof(struct tty_map);
i_str.ic_dp = (char *)&tty_map;
if (ioctl(fileno(stdin), I_STR, &i_str) != 0 && errno != EINVAL) {
perror("setmaps: clear_maps TCSMAP");
return(BAD_RES);
}
if (verbose) {
int both = TM_INPUT | TM_OUTPUT;
if ((flags & both) == both)
fprintf(stderr,
MSGSTR(RMBOTH, "%s: both mappings have been removed\n"),
arg0);
else if ((flags & both) == TM_INPUT)
fprintf(stderr,
MSGSTR(RMINPUT, "%s: input mappings have been removed\n"),
arg0);
else
fprintf(stderr,
MSGSTR(RMOUTPUT, "%s: output mappings have been removed\n"),
arg0);
}
return(OK);
}
/* use_map - give a terminal access to an already-loaded map. */
int use_map(int verb, char *map_name, char *explicit_key, int level)
{
long flags;
char *key;
struct tty_map tty_map;
struct str_list strlist;
int nmods;
int size;
switch (verb) {
case SET_INPUT:
case SET_INPUT_FROM_PATH:
flags = TM_INPUT;
key = keyname(map_name, explicit_key, verb);
break;
case SET_OUTPUT:
case SET_OUTPUT_FROM_PATH:
flags = TM_OUTPUT;
key = keyname(map_name, explicit_key, verb);
break;
default:
fprintf(stderr,
MSGSTR(INT2, "%s: internal error, bad verb in use_map: %d\n"),
verb);
break;
}
size = sizeof(struct tty_map);
memset((void *)&tty_map, 0, (size_t)size);
tty_map.tm_version = TTMAP_VERSION;
strncpy(tty_map.tm_mapname, key, (size_t)TTMAP_NAMELEN);
tty_map.tm_flags = flags | TM_USE;
/* at this point will need to check for the nls module */
/* if level != 0, then this is a second pass through use_map,
and check_nls was done on the previous pass..
*/
if ((level == 0) &&
(check_nls() < 0)) {
/* unable to verify the presence of the nls module or
unable to push the nls modules on the stream */
fprintf(stderr,
MSGSTR(NLSDD, "%s: can't determine if NLS module on the stream\n"), arg0);
return(BAD_RES);
}
if (verbose)
fprintf(stderr, MSGSTR(ATTEMPT, "%s: attempting to use map %s\n"),
arg0, key);
if (x_ioctl(fileno(stdin), TCSMAP, &tty_map, size) == 0) {
if (verbose)
fprintf(stderr,
MSGSTR(LOADED, "%s: %s already loaded, now using it\n"),
arg0, key);
return(OK); /* it worked, we're outta here */
}
/*
* ioctl failed. If errno is ENOENT, that means the requested map
* is not loaded in the kernel. Anything else is unexpected. If
* we're recursing on the second attempt to use a map, we'll
* bounce out thru here because we supposedly just loaded the map
* we're trying to use.
*/
if ((level != 0) ||(errno != ENOENT)) {
perror("setmaps: use_map TCSMAP");
return(BAD_RES);
}
if (verbose)
fprintf(stderr,
MSGSTR(NOTLOADED, "%s: %s is not presently loaded\n"),
arg0, key);
/* Map isn't loaded, if we are root, we can continue, else error */
if ((geteuid() != 0) && (Dump_map != TRUE)) {
fprintf(stderr,
MSGSTR(RLOAD, "%s: must be root to load a new map\n"), arg0);
return(BAD_RES);
}
if (verbose)
fprintf(stderr, MSGSTR(ATTEMPT2, "%s: will attempt to load %s\n"),
arg0, key);
/*
* The map isn't already in the kernel, and we have root access,
* so let's try loading it. If the load works, we recurse and try
* to select the map again. The level parm assures we only cycle
* once. If the second attempt to use the map fails after a
* successful load, there's probably something wrong in the kernel
*/
if (load_map(verb, TM_LOAD, map_name, (int)key) != 0) {
return(BAD_RES);
}
if (verbose)
fprintf(stderr,
MSGSTR(LOADOK, "%s: %s loaded ok, will try again to use it\n"),
arg0, key);
/* The load worked, let's try using the map again. */
return(use_map(verb, map_name, explicit_key, level + 1));
}
/*
* load_map - read file provided by user and store the data into ttmap
* structure. ttmap struct(and related info) is defined in
* <sys/ttmap.h>
*/
int load_map(int verb, int flags, char *map_name, int key)
{
struct tty_map *tty_map;
struct ttmap *ttmap; /* pointer to actual map */
struct ttmap *ttmap_tmp; /* pointer to map in tty_map */
char *path; /* path of map file */
int rc, n;
int size;
path = fullpath(verb, map_name);
if (verbose)
fprintf(stderr, MSGSTR(PATH, "%s: map pathname is: %s\n"),
arg0, path);
/* alloc space for map */
ttmap =(struct ttmap *)malloc((size_t)TTMAP_MAXSIZE);
if (ttmap == NULL_MAP) {
fprintf(stderr,
MSGSTR(NOMEM, "%s: can't allocate memory to build map\n"),
arg0);
return(BAD_RES);
}
/* make the map - this will give us the actual size needed in
tm_len */
n = makemap(ttmap, path, (char *)key);
if (n <= 1) { /* n = translations in map */
fprintf(stderr,
MSGSTR(CREATE, "%s: unable to create map from %s\n"),
arg0, path);
return(BAD_RES);
}
/* create a tty_map struct with enough space to hold the map */
size = sizeof(struct tty_map) + ttmap->tm_len;
tty_map = (struct tty_map *)malloc((size_t)size);
if (tty_map == NULL) {
fprintf(stderr,
MSGSTR(NOMEM, "%s: can't allocate memory to build map\n"),
arg0);
return(BAD_RES);
}
memset((void *)tty_map, 0, (size_t)size);
tty_map->tm_version = TTMAP_VERSION;
/* this indicates to nls that the ttmap is appended to the tty_map
* struct.
*/
tty_map->tm_addr = (char *)0;
strncpy(tty_map->tm_mapname, (char *)key, (size_t)TTMAP_NAMELEN);
/* copy map to tty_map struct */
ttmap_tmp = (struct ttmap *)(tty_map + 1);
bcopy(ttmap, ttmap_tmp, ttmap->tm_len);
if (Dump_map) /* do a debug map dump? */
dump_map(ttmap);
/* We managed to grok the map, set map size in tty_map struct */
tty_map->tm_len = ttmap->tm_len;
tty_map->tm_flags = flags;
/* Umm, excuse me Mr. Kernel. I have this map here, and... */
rc = x_ioctl(fileno(stdin), TCSMAP, tty_map, size);
if (rc < 0) {
rc = BAD_RES;
if (errno == EEXIST) {
fprintf(stderr,
MSGSTR(ALOADED, "%s: NLS map %s is already loaded\n"),
arg0, key);
} else {
fprintf(stderr, "%s: ", arg0);
perror("TCSMAP");
}
} else {
rc = OK;
}
free(ttmap);
free(tty_map);
return(rc);
}
/*
* show_map - determine if a map is already loaded. If so, print its
* name. Otherwise, print "none installed". NB: The printf
* statements in this function must remain in the same
* format they are; they are parsed by stty. Stty should
* really have a function similar to this one in it to
* query the map state directly rather than calling this
* program to do it.
*/
void show_map(int code)
{
struct tty_map tty_map; /* user ioctl structure */
int rc;
char *type =(code == TM_INPUT) ? "input" : "output";
char buf [TTMAP_NAMELEN + 1];
struct strioctl i_str;
memset((void *)&tty_map, 0, (size_t)sizeof(struct tty_map));
tty_map.tm_version = TTMAP_VERSION;
tty_map.tm_flags = code;
/* don't want the actual map, just the info about it */
tty_map.tm_addr = NULL;
tty_map.tm_len = TTMAP_MAXSIZE;
/* for the stream framework, use the streams ioctl */
i_str.ic_cmd = TCGMAP;
i_str.ic_timout = 0;
i_str.ic_len = sizeof(struct tty_map);
i_str.ic_dp = (char *)&tty_map;
rc = ioctl(0, I_STR, &i_str); /* get map info */
if (rc < 0) {
if (errno == ENOENT || errno == EINVAL) {
printf(MSGSTR(NONEI, "%s map: none installed\n"), type);
return;
} else {
fprintf(stderr, "%s: ", arg0);
perror("ioctl TCGMAP");
}
} else {
strncpy(buf, tty_map.tm_mapname, (size_t)TTMAP_NAMELEN);
buf [TTMAP_NAMELEN] = '\0'; /* make sure it's terminated */
printf(MSGSTR(MAP, "%s map: %s\n"), type,(buf [0]) ? buf :
MSGSTR(UNKNOWN, "<UNKNOWN?>"));
/*
* if the name is a null string, print <UNKNOWN?> instead.
* A map is in use on the tty, but doesn't have a name,
* this "can't happen".
*/
}
}
/*
* magic_key - Check a map key to see if it is a magic name which
* really means to delete the mapping from the tty. The
* prefered magic word (because I prefer it) is "NOMAP",
* but this function also recognizes "." as a magic key,
* for backward compatability. Also, if the pointer to the
* key string is NULL we claim it's a magic word. We can't
* use a null name as a map, so we assume that means
* "don't use any map". It's unlikely we'll be called that
* way, since getopt should protect us from missing parms,
* but best to be paranoid.
*/
int magic_key(char *p)
{
if (p == NULL)
return(TRUE);
if (strlen(p) == 0)
return TRUE;
if (strcmp(p, MAGIC_KEY1) == 0)
return TRUE;
if (strcmp(p, MAGIC_KEY2) == 0)
return TRUE;
return(FALSE);
}
/*
* keyname - extract the key name from the mapname given on the
* command line. A suffix is added if approriate, if it
* isn't already present.
*/
char *keyname(char *map_name, char *explicit_key, int verb)
{
char *p, *q, *suffix;
if (explicit_key != NULL) {
return(explicit_key);
}
switch (verb) {
case SET_INPUT:
suffix = IN_SUFFIX;
break;
case SET_OUTPUT:
suffix = OUT_SUFFIX;
break;
default:
suffix = "";
break;
}
q = strrchr(map_name, '/'); /* scan backwards for a slash */
if (q == NULL) {
q = map_name; /* no slash, use full name */
} else {
q++; /* point to char following slash */
}
p = (char *)malloc((size_t)(strlen(q) + strlen(suffix) + 1));
strcpy(p, q); /* copy it */
if (strlen(suffix) == 0) {
return(p);
}
q = strrchr(p, '.'); /* scan backwards for a dot */
if (q != NULL) {
if (strcmp(q, suffix) == 0) { /* suffix already there? */
return(p); /* yes */
}
}
strcat(p, suffix); /* append the suffix */
return(p); /* return what we think is the key */
}
/*
* fullpath - takes a map name and builds the full pathname of the
* file we need to open to get the map. The pathname string
* is built from <NLS directory> + <map name> + <suffix>.
* The NLS directory may have been modified with a -d
* option, if it doesn't end in / then one is appended. If
* the map name already has the appropriate suffix on it
* then it is not appended, otherwise .in or .out is added
* as appropriate. Note that the wrong suffix will not be
* caught, ie, foo.out as the name of an input map will
* become foo.out.in. No suffix is appended for simple
* loads, since we can't intuit what it should be, the user
* will have to provide it, such as: setmaps -l foo.in.
* Also, if we are given a full pathname, by the -I, -O or
* -L options, we do nothing to the path name, it must be
* the entire complete path including the suffix.
*/
char *fullpath(int verb, char *map_name)
{
char *p;
switch (verb) {
case SET_INPUT_FROM_PATH:
case SET_OUTPUT_FROM_PATH:
case LOAD_ONLY_FROM_PATH:
return(map_name);
}
/* space for <NLS dir> + <map name> + <slop for suffix, /, and '\0'> */
p = (char *)malloc((size_t)(strlen(nls_dir) + strlen(map_name) + 10));
strcpy(p, nls_dir); /* copy in dir name */
if (p [strlen(p) - 1] != '/') /* and / if needed */
strcat(p, "/");
switch (verb) {
case LOAD_ONLY: /* no suffix append */
strcat(p, map_name); /* add map name */
break;
case SET_INPUT:
case SET_OUTPUT:
strcat(p, keyname(map_name, (char *)NULL, verb));
break;
default:
fprintf(stderr, MSGSTR(FULL, "%s: internal error, fullpath botch\n"),
arg0);
return(NULL);
}
return(p); /* return combined string */
}
/*
* usage - tell the user about the flags they can use. rint the usage
* message to stderr. This is called when the -h option s
* given, or when the user gives unrecognizeable arguments, or
* when ome impossible combination of arguments is given. The
* routine never eturns, it exits here.
*/
void usage()
{
char *str;
str = MSGSTR(USAGE,
"to use a map: setmaps [-v] -{i | o | t} mapname\n"
" use one of -i, -o, or -t\n"
" -i = use mapname.in as your input map\n"
" -o = use mapname.out as your output map\n"
" -t = two-way mapping, use both mapname.in and mapname.out\n"
" the special name NOMAP will clear the corresponding mapping:\n"
" setmaps -i NOMAP\n"
"\n"
"to clear all mappings on this terminal: setmaps -c\n"
"\n"
"to use a code set map: setmaps [-v] -s -{i | o} mapname\n"
"\n"
"-h = help, print this message(all other options ignored)\n"
"-v = verbose\n"
"\n"
"Type: setmaps -v -h for advanced usage information\n");
write(fileno(stdout), str, strlen(str));
if ((getuid() == 0) || verbose) {
str = MSGSTR(VUSAGE,
"\n"
"advanced usage(administrators only):\n"
" -l mapname Load a map for later use(must include suffix if any)\n"
" -k keyname Overrides mapname, normally filename is used\n"
" -d dirpath Overrides directory where map files are assumed to be\n"
" -I, -O, -L Same as -i, -o, -l, except full path is given by arg\n"
" -r Force reload of a map even if already loaded\n"
" -D Produce debug dump of map on stdout before loading\n"
"(useful for making new maps, don't run as root until\n"
" map is fully debugged to prevent actual load)\n"
"\n"
"All maps loaded must have unique names, use -k to eliminate conflicts\n"
"Only -i, -o and -t implicitly add a suffix, other options specifying\n"
"mapnames should include a suffix if appropriate.\n"
"If a requested mapname is already loaded in the kernel, it will be\n"
"used, even if the path info implies a different map. Administrator\n"
"may force a reload with the -r option\n");
write(fileno(stdout), str, strlen(str));
}
exit(BAD_RES);
}
/*
* From here on down is the code for reading in and parsing maps. This
* tuff really ought to be in a seperate .c file, so that other
* programs ould share the map making code. This was done for
* debugging with "kernel simulator" to drive the tty code in a user
* program. This ode has been inserted back inline here at the end of
* setmaps.c because t was too painful at the time to go modify the
* library makefiles o build setmaps in two pieces.
*/
#define PAT 0 /* lets translation func know which */
#define REP 1 /* half of the pattern/replace pair */
/* it's working on, sets([abc]) are */
/* not recognized on replacement */
#define ESC 0x1b /* ascii escape char code */
/*
* A map file contains a set of rules which define the translation of
* one or more characters to a replacement character or string. The
* syntax of each rule is pattern:newstring. Examples: \033b:ps -ef
* remaps 2nd func key on an ibm3161 terminal \@:\? remap '@' to '?'.
* Slash req'd for special char \x80:\x20 remap vt220 8-bit char to
* space a?a:a$2a ? matches any single byte; $n outputs nth char from
* pattern. Returns number of translations(rules) in map.
*/
/*
* The skinny on the internal map structure.
*
* The ttmap struct contains a hash table and an array of translation rules.
* The hash table is used to quickly locate the first applicable rule when
* starting a new pattern. For instance, in the kernel, if the character 'a'
* is received it is used as an index into the hash table to determine the
* first translation rule to use(rule = ttmap->tm_hash ['a']). The
* translation rules in ttmap->tm_rule [rule] are then applied to the char 'a',
* if it satisfies the pattern then the replacement pattern is inserted
* in the data stream and the cycle starts over. If the pattern isn't
* satisfied, then the next rule(ttmap->tm_rule [rule].next) is tried,
* and so on, until a match is found. The last rule in every chain is the
* default rule(?:$1) which will match anything. A sample map setup
* will probably be useful.(Assume a char set which ranges from a to d
* with a = 0)
*
* ttmap:
* tm_num_rules = 7
* tm_default = 0
* tm_first_wild = 4
*
* Rules list:
* Hash table(p = tm_pattern, r = tm_replace)
* ---------- --------------------------------
* tm_hash ['a'] = 1 tm_rule [0]: p = ? r = $1 tm_next = -1
* tm_hash ['b'] = 4 [1]: p = abc r = bca tm_next = 3
* tm_hash ['c'] = 5 [2]: p = dab r = bad tm_next = 4
* tm_hash ['d'] = 2 [3]: p = a??d r = $3a tm_next = 4
* [4]: p = ?b r = c$1b tm_next = 6
* [5]: p = c[ad] r = cc tm_next = 4
* [6]: p = [b-c]a r = a$1 tm_next = 0
*
* Notice that all the chains converge on the first wildcard rule(4), which
* ultimately winds up at the default rule(0)
*
* Whaddaya call a data structure like this, a hydra? :-)
*/
int makemap(struct ttmap *ttmap, char *path, char *mapname)
{
FILE *file;
char *p, *q;
int no_ops = 0; /* num no-op rules ignored */
int bad_rules = 0; /* num mangled rules ignored */
char buf [256]; /* buffer to hold an input line */
char pattern [TTMAP_PAT_SIZE * 2]; /* processed pattern(temp) */
char replace [TTMAP_REP_SIZE * 2]; /* processed replacement(temp) */
add_rule(NULL_MAP, (char *)NULL, (char *)NULL); /* reset rule handler */
memset((void *)ttmap, 0, (size_t)sizeof(struct ttmap)); /* clear map buffer */
strcpy(ttmap->tm_mapname, mapname); /* copy in map name */
ttmap->tm_len = sizeof(struct ttmap); /* account for struct */
file = fopen(path,"r"); /* open the map file */
if (file ==(FILE *)0) { /* doesn't exist */
perror("setmaps: makemap");
return(BAD_RES);
}
/*
* Read a line at a time from the map file into buf. A return value
* of NULL from fgets indicates EOF.
*/
while (fgets(buf, (int)sizeof(buf), file) != NULL) {
int len;
len = strlen(buf);
if ((len == (sizeof(buf)-1)) && (buf[len - 1] != '\n')) {
/* line is too long, if not a comment then complain */
if ((buf[0] != '#') && (buf[0] != '\0')) {
fprintf(stderr,MSGSTR(LONGLN,
"%s: input line too long, ignored.\n"),arg0);
bad_rules++;
}
/* throw away the rest of the line */
while (fgets(buf, (int)sizeof(buf), file) != NULL) {
len = strlen(buf);
if ((len != (sizeof(buf)-1)) || (buf[len - 1] == '\n'))
break;
}
continue;
}
buf [len - 1] = '\0'; /* snuff the \n */
p = q = buf; /* point at line just read */
if ((*p == '#') ||(*p == '\0'))
continue; /* comment or null line */
/* find second half, following unescaped ':' */
if ((q = find_replace(p)) == NULL) {
bad_rules++;
continue;
}
/*
* Translate each half of the pattern/replacement pair from
* external ascii to internal strings, converting octal and
* hex escapes and such. If translation fails, it kicks out
* a message and returns BAD_RES. In that case we ignore this
* rule and go get the next one.
*/
if (translate(p, pattern, sizeof(pattern), PAT) == BAD_RES ||
translate(q, replace, sizeof(replace), REP) == BAD_RES) {
bad_rules++;
continue;
}
/* does this mapping do anything? */
if (no_op(pattern, replace)) {
no_ops++; /* nope, count it and toss it */
if (verbose) {
fprintf(stderr,
MSGSTR(IGNORED, "%s: null mapping ignored, pat=%s, rep=%s\n"),
arg0, p, q);
}
continue;
}
/*
* So far so good, add this rule to the map. If it can't
* be added for some reason, we complain and press on
*/
if (add_rule(ttmap, pattern, replace) == BAD_RES) {
fprintf(stderr,
MSGSTR(CANTADD, "%s: can't add rule, pat=%s, rep=%s\n"),
arg0, p, q);
bad_rules++;
continue;
}
} /* end while (fgets()) */
fclose(file); /* finished with the file */
if (ttmap->tm_num_rules < 2) { /* anything besides default? */
fprintf(stderr, MSGSTR(MAKEMAP, "%s: makemap, no rules loaded\n"),
arg0);
return(BAD_RES);
}
/* If verbose, and any no_op rules were ignored, say so */
if (verbose &&(no_ops != 0)) {
fprintf(stderr, MSGSTR(NULLMAP, "%s: %d null mappings ignored\n"),
arg0, no_ops);
}
/* If verbose, and any rules were rejected, syay so */
if (verbose &&(bad_rules != 0)) {
fprintf(stderr, MSGSTR(BADRULE, "%s: %d bad rules rejected\n"),
arg0, bad_rules);
}
return(ttmap->tm_num_rules); /* finished */
}
/*
* Add a rule to the map. The rule is stored as two seperate strings,
* one is the pattern to be matched in the input stream, the other is
* the eplacement string to substitute in its place. ote: References
* to "pointers" to rules in the comments below refer to ndexes of
* ttrule structs in the tm_rule array in the ttmap struct. verything
* in the ttmap struct is in a contiguous block of storage so hat it
* can be copied into and out of the kernel as a block.
*/
int add_rule(struct ttmap *ttmap, char *pat, char *rep)
{
static int first_time = TRUE; /* first call? */
static int first_wildcard = 0; /* index of first wildcard */
static int new_rule = 1; /* index where next rule goes */
int i; /* scratch */
if (ttmap == NULL_MAP) { /* re-init for next map */
first_time = TRUE; /* reset, for first rule */
first_wildcard = 0; /* reset indexes */
new_rule = 1;
return(OK);
}
/*
* If this is the first call, setup the default rule and initialize
* the hash table so that all entries point to the default.
*/
if (first_time == TRUE) {
ttmap->tm_rule [0].tm_next = -1; /* default rule */
strcpy(ttmap->tm_rule [0].tm_pattern, "?");
strcpy(ttmap->tm_rule [0].tm_replace, "$1");
ttmap->tm_default = 0; /* default is in slot 0 */
ttmap->tm_num_rules = 1; /* one rule in the map */
first_time = FALSE; /* initial setup is done */
for (i = 0; i < 256; i++) { /* setup hash table */
ttmap->tm_hash [i] = 0; /* point all to default */
}
}
/* make sure the rule strings will fit in the rule struct */
if (strlen(pat) > TTMAP_PAT_SIZE || strlen(rep) > TTMAP_REP_SIZE) {
fprintf(stderr,
MSGSTR(TOOLONG, "%s: mapping too long, pat=%s, replace=%s\n"),
arg0, pat, rep);
return(BAD_RES);
}
/* put the translation info into the next available rule slot */
ttmap->tm_rule [new_rule].tm_next = 0;
strcpy(ttmap->tm_rule [new_rule].tm_pattern, pat);
strcpy(ttmap->tm_rule [new_rule].tm_replace, rep);
if (is_wild(pat)) { /* is this a wildcard rule? */
if (first_wildcard == 0) { /* yes, first one? */
/*
* Yes, wildcards should be searched before reaching
* the default rule, so when we see the first one we
* we scan the hash list and change any entries
* which are pointing at the default to point to
* this wildcard rule. This means that this wildcard
* rule will be the first one tried for any character
* which does not have an explicit rule. After
* updating the hash list, we then scan all the loaded
* rules and patch the next pointers of any rules
* which point at the default rule to point at this
* wildcard. This has the effect of chaining the list
* of all wildcards to the end of every explicit rule
* chain. The default rule is at the end of the
* wildcard chain, and therefore at the end of every
* chain.
*/
ttmap->tm_first_wild = first_wildcard = new_rule;
for (i = 0; i < 256; i++) { /* patch hash */
if (ttmap->tm_hash [i] == 0) {
ttmap->tm_hash [i] = new_rule;
}
}
for (i = 1; i < new_rule; i++) { /* patch rules */
if (ttmap->tm_rule [i].tm_next == 0) {
ttmap->tm_rule [i].tm_next = new_rule;
}
}
} else {
/*
* This is a wildcard, other wildcards already present,
* chain this wildcard to the end of the wildcard
* list. This effectively inserts it at the end of
* the wildcard list just before the default rule
*/
chain_to_wild(ttmap, first_wildcard, new_rule);
}
} else {
/*
* Not a wildcard rule. This rule begins with a specific,
* quantifiable character. The first character of this
* explicit rule is used as a hash key. This character
* is used as an index into the hash table and the entry at that
* location will point to the head of the chain of all rules
* beginning with this char. All the explicit rule chains
* link to the first wildcard rule, which is the head of the
* wildcard rule chain, which has the default rule as its
* last entry(the default rule is a wildcard, and may be the
* only entry in the wildcard chain). This new explicit rule
* is chained to the end of this explict rule chain, just
* before the first wildcard.
*/
chain_to_hash(ttmap, hash_char(pat), new_rule,
first_wildcard);
}
new_rule++; /* move to next rule slot */
ttmap->tm_num_rules++; /* another rule was added */
ttmap->tm_len += sizeof(struct ttrule); /* adjust total map length */
return(OK); /* all according to plan */
}
/*
* Add a new explicit rule to the end of a hash chain. Each explicit chain is
* pointed at by an entry in the hash table, indexed by the first character
* in the rule pattern string. The parm hash is the hash table index that
* this new rule should be chained onto, new is the index of the new rule
* being chained in, and end is the index of the rule following the last
* rule in this chain(The first wildcard rule, in other words. In this
* routine end means "when you see this rule, that's the end of your search").
* The new rule is linked into the chain pointed at by the hash entry, just
* before the rule pointed at by end.
*/
void chain_to_hash(struct ttmap *ttmap, int hash, int new, int end)
{
int i;
/*
* Is the hash chain empty? If the hash entry for this hash key
* points at the end rule(first wildcard), then there is no explicit
* chain present for this slot. Insert the index of this rule in the
* hash table, then have this new rule point at what the hash entry
* was pointing at.
*/
if (ttmap->tm_hash [hash] == end) {
ttmap->tm_hash [hash] = new;
ttmap->tm_rule [new].tm_next = end;
return;
}
/*
* Already have one or more rules in the explicit chain which hangs
* off this hash entry. Scan the chain until we find the end of the
* explicit rules, then link the new guy in after the last explicit
* rule in the chain and before the first wildcard
*/
i = ttmap->tm_hash [hash]; /* get chain head from hash table */
while (ttmap->tm_rule [i].tm_next != end) { /* watch for end */
i = ttmap->tm_rule [i].tm_next;
}
ttmap->tm_rule [new].tm_next = end; /* link new guy to first wild */
ttmap->tm_rule [i].tm_next = new; /* link last explicit to new */
}
/*
* Chain a new wildcard rule onto the end of the wildcard rule chain. The
* idea here is similar to the previous routine, but is somewhat simpler
* because the hash table is not involved. We simply start searhing at the
* first wildcard and scan until we find a rule pointing at the default rule
*(default is always in slot zero). We then link the new rule into the
* chain just before the default.
*/
void chain_to_wild(struct ttmap *ttmap, int first, int new)
{
int i = first;
while (ttmap->tm_rule [i].tm_next != 0)
i = ttmap->tm_rule [i].tm_next;
ttmap->tm_rule [new].tm_next = 0; /* point new guy at default */
ttmap->tm_rule [i].tm_next = new; /* last guy points at me now */
}
/*
* Determine the character which represents the first character of a pattern
* and which therefore is used as the hash key for this rule. There are
* really only two cases to worry about here, when the rule starts with '@'
* and when the first char is escaped with '\'. The '@' char is a state
* requirement and is not part of the match pattern, so we skip it and resume
* looking with the third char. For escapes, there is a special case for \0
* which represents null(rules are stored as strings and can't contain a
* real null). Other escapes have the real char following them. There won't
* be any \x12 or \123 type escapes here, those have all been processed
* already.
*/
int hash_char(char *p)
{
if (*p == '@')
p += 2; /* skip @n */
if (*p == '\\') {
if (*++p == '0') /* special case */
return(0); /* return the value zero */
}
return((int) *p); /* return the char itself */
}
/*
* Translate a mapping string from the external ascii form to the internal
* form used by the kernel mapping code. This basically boils down to
* interpreting the escapes which represent other things, such as \xf2 and
* \n. The which parm let's this routine know if it is processing the match
* pattern or the replacement string, because the [ meta char should not be
* interpreted in the replacement string since it has no special meaning
* there.
*/
int translate(char *in, char *out, int bufsize, int which)
{
char *p = in, *q = out; /* working pointers */
char *fence = out + bufsize; /* don't store beyond this address */
int c; /* scratch */
while ((q < fence) && *p) { /* while more input and space left */
switch (*p) { /* let's see here... */
case '\\': /* next char is escaped */
p++;
c = xlate_esc_char(p, &p);
if (is_meta(c, which) ||(c == '\0') ||(c == '\\')) {
*q++ = '\\'; /* needs escape in output */
if (c == '\0')
c = '0'; /* special case for NULL */
}
*q++ = c;
break;
case '$': /* copy char from source spec */
if (which == REP) { /* only applies to rep string */
c = *(p + 1); /* verify legality */
if (c < '1' || c > '9') {
fprintf(stderr,
MSGSTR(XLATE, "%s: xlate, illegal $ spec(%c%c) in replacement: %s\n"),
arg0, *p, *(p+1), in);
return(BAD_RES);
}
}
*q++ = *p++; /* copy out $, second char will be */
break; /* handled by default case next time */
case '[': /* begin set, [abc] or [a-f] */
/* only do this case on pattern string */
if (which == PAT) {
if (xlate_char_set(in, &p, &q, fence) == BAD_RES)
return(BAD_RES);
/* fall out */
}
/* else fall through */
default:
*q++ = *p++; /* maps to itself, nothing special */
break;
}
}
if (q == fence) {
fprintf(stderr,
MSGSTR(OVERRAN, "%s: xlate, overran translation buffer: %s\n"),
arg0, in);
return(BAD_RES);
}
*q = '\0'; /* terminate output string */
return(OK);
}
/*
* Handle the various types of backslash escapes which can be present in the
* input string. The new parm is where scanning should resume. This routine
* uses a library function called strtol() which actually scans the hex and
* octal escapes. This allows this routine to handle short numbers properly
* so that a string like this:
* a\41c
* will be properly parsed as:
* a!c(octal 041 is ascii !)
* This routine does protect itself from long strings of digits though, so:
* a\041345
* is parsed as:
* a!345
*/
int xlate_esc_char(char *p, char **new)
{
int c, tmp; /* scratch */
char *q; /* scratch */
char *newp = p + 1; /* default resume position */
switch (*p) { /* whadda we got here... */
case '\0': /* a \ was at the end of the string */
c = '\\';
break;
case 'n': /* new line */
c = '\n';
break;
case 't': /* tab */
c = '\t';
break;
case 'f': /* form feed */
c = '\f';
break;
case 'r': /* carriage return */
c = '\r';
break;
case 'E': /* ascii escape ala termcap/terminfo */
c = ESC;
break;
case 'x': /* two digit hex number */
case 'X':
p++; /* point at first digit */
q = p + 2; /* pos of char after second digit */
tmp = *q; /* save that char */
*q = '\0'; /* temporarily shorten the string */
c = strtol(p, &newp, 16); /* interpret the string */
*q = tmp; /* restore saved char */
break;
case '0': /* three digit octal number */
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
q = p + 3; /* pos of char following third digit */
tmp = *q; /* save it */
*q = '\0'; /* end the string there */
c = strtol(p, &newp, 8); /* interpret the string */
*q = tmp; /* restore the saved char */
break;
default: /* hmm, \x, don't know any special */
c = *p; /* meaning for x, just return it */
break;
}
if (new != NULL) /* if they gave us a pointer to a */
*new = newp; /* pointer send back new p value */
return(c);
}
/*
* Interpret a set of chars of the form [abcd] or [a-d]. These are just like
* in regular expresions, but we don't support "not" sets. We do, however
* support \ escapes within the []s, such as [\05-a], or [\E\n\r].
* The parm in is a pointer to the beginning of the pattern(for error
* messages), pp is a pointer to a pointer to the first char of the set
*(the '['), qp is a pointer to a pointer to the current output position,
* fence is the upper boundary of the output buffer. We need pointers to
* pointers here because we're handling an unknown number of input chars and
* are adding to the output so we need to adjust the caller's state.
*/
int xlate_char_set(char *in, char **pp, char **qp, char *fence)
{
int c;
char *p = *pp, *q = *qp;
*q++ = *p++; /* copy out the '[' */
while (*p != ']') {
if (*p == '\0') { /* premature end of string */
fprintf(stderr,
MSGSTR(NOCLOSE, "%s: xlate, no closing ']': %s\n"),
arg0, in);
return(BAD_RES);
}
if ((*p == '-') &&(*(p + 1) == ']')) { /* bad range spec */
fprintf(stderr,
MSGSTR(MISSEND, "%s: xlate, missing end-of-range: %s\n"),
arg0, in);
return(BAD_RES);
}
if (*p == '\\') { /* an escaped char */
p++;
c = xlate_esc_char(p, &p);
if (is_meta(c, PAT) ||(c == '\0') ||(c == '\\')) {
*q++ = '\\';
if (c == '\0')
c = '0'; /* special case for NULL */
}
*q++ = c;
continue;
}
*q++ = *p++; /* copy the char to output */
if (q >= fence) { /* watch for end of buf */
fprintf(stderr,
MSGSTR(REPBUF, "%s: xlate, overran replacement buf: %s\n"),
arg0, in);
return(BAD_RES);
}
}
*pp = p;
*qp = q;
return(OK);
}
/*
* Check to see if a pattern/replacement pair really do anything. If not
* then indicate so. With no explict rules, chars map to themselves anyway,
* there's no need to fill up the search space with unnecessary rules,
* especially since the mapping is done in the kernel at interrupt level.
*/
int no_op(char *p, char *q)
{
if (strcmp(p, q) != 0) /* doesn't match exactly */
return(FALSE);
if (has_meta(p, PAT))
return(FALSE); /* may be a meta-char in pattern */
if (has_meta(q, REP))
return(FALSE); /* may be a meta-char in replacement */
return(TRUE); /* maps to self */
}
/*
* Find the replacement string in the input string. We scan the input
* string looking for an unescaped colon, when we find it we replace it
* with a null and return the address of the char following the colon
* which is the beginning of the replacement string. These are each
* translated to remove most escapes and then stored as two sepereate
* strings in the actual map.
*/
char *find_replace(char *p)
{
char *q = p;
while (TRUE) {
q = strchr(q, ':'); /* look for a ':' */
if ((q == NULL) ||(q == p)) {
/* something's fishy here */
fprintf(stderr,
MSGSTR(MAL, "%s: malformed NLS pattern: %s\n"),
arg0, p);
return(NULL);
}
/* is it escaped? */
if ((*(q - 1) == '\\') &&(*(q - 2) != '\\')) {
q++; /* yep, keep looking */
continue;
}
/*
* Found the pattern/replacement seperator, replace
* the colon with a null. Now we have two strings
* in the buffer, p points at the pattern, q points
* at the replacement string.
*/
*q++ = '\0';
return(q); /* all done */
}
}
/* Is this a meta character? */
int is_meta(int c, int which)
{
if (c == '@')
return(TRUE);
if (which == PAT) {
if (c == '?')
return(TRUE);
if (c == '[')
return(TRUE);
if (c == ']')
return(TRUE);
} else {
if (c == '$')
return(TRUE);
}
return(FALSE);
}
/* Are there meta characters in this string? */
int has_meta(char *s, int which)
{
char *p = s;
while (*p) {
if (is_meta((int)(*p), which)) {
if (p == s)
return(TRUE); /* first char, no \ */
if (*(p - 1) == '\\') { /* \ preceding? */
if (*(p - 2) == '\\') { /* was it \\ ? */
return(TRUE); /* yes, not escaped */
}
/* meta char was escaped, keep going */
}
}
p++; /* next char */
}
return(FALSE); /* I don't see no meta chars */
}
/*
* Is this pattern a wildcard? Only the first char counts. Meta chars
* imbedded in the middle of a pattern don't make the pattern a wildcard,
* only if it doesn't start with a quantifiable char
*/
int is_wild(char *s)
{
if (*s == '@') /* skip state spec */
s += 2;
if ((*s == '?') ||(*s == '[')) /* first char multi-matcher? */
return(TRUE);
return(FALSE);
}
/*
* Debug code
* Given a char, return an ascii string representing it. Printables return
* a one byte string of themselves. Non-printables return the hex
* representation enclosed in <>. This is only used by the dump_map routine
* for printing the hash table and the rule strings.
*/
char *asciify(unsigned char c)
{
static char buf [10];
if ((c >= ' ') &&(c <= '~')) { /* in printable range */
buf [0] = c;
buf [1] = '\0';
return(buf);
}
sprintf(buf, "<%02x>",(unsigned int)c);
return(buf);
}
/*
* Debug code.
* Given a string and a pointer to a buffer, make new string using asciify
* above to convert non-printables to printable representions. This is only
* used by dump_map for printing the rule strings.
*/
char *asciistr(unsigned char *p, char *buf)
{
buf [0] = '\0';
while (*p) {
strcat(buf, asciify(*p++));
}
return(buf);
}
/*
* Debug code.
* Dump the map to stdout. This is invoked by the -D option. This is
* for debug.
*/
void dump_map(struct ttmap *ttmap)
{
int i;
char buf1 [32], buf2 [32];
printf(MSGSTR(NAME, "\nMap dump, name: %s\n"), ttmap->tm_mapname);
printf(MSGSTR(TMAP, " total map length:\t%d\n"), ttmap->tm_len);
printf(MSGSTR(RULES, " rules in map:\t%d\n"), ttmap->tm_num_rules);
printf(MSGSTR(DEF, " default rule:\t%d\n"), ttmap->tm_default);
printf(MSGSTR(WILD, " first wildcard:\t%d\n\n"), ttmap->tm_first_wild);
printf(MSGSTR(HASH, "Hash table:\n"));
for (i = 0; i< 256; i++) {
printf(" %d('%s')\t%d\n", i, asciify((unsigned char)i),
ttmap->tm_hash [i]);
}
printf(MSGSTR(RULE2, "\nRules:\n"));
for (i = 0; i < ttmap->tm_num_rules; i++) {
printf(MSGSTR(STATS, " %d\tpat=%s\trep=%s\tnext=%d\n"), i,
asciistr(ttmap->tm_rule [i].tm_pattern, buf1),
asciistr(ttmap->tm_rule [i].tm_replace, buf2),
ttmap->tm_rule [i].tm_next);
}
fflush(stdout);
}
int x_ioctl(int fd, int cmd, struct tty_map *tty_map, int size)
{
int err;
struct strioctl i_str;
i_str.ic_cmd = cmd;
i_str.ic_timout = 0;
i_str.ic_len = size;
i_str.ic_dp = (char *)tty_map;
if ((err = ioctl(fd, I_STR, &i_str)) && errno == EINVAL) {
/* ioctl may fail due to lack of nls module on the stream;
* check for presence of nls module and retry
*/
if (check_nls() < 0) {
fprintf(stderr,
MSGSTR(NLSDD,
"%s: can't determine if NLS module on the stream\n"),
arg0);
exit(1);
}
err = ioctl(fd, I_STR, &i_str);
}
return err;
}
/*
* csmap_path - takes a code set map name and builds the full pathname of the
* file we need to open to get the map. The pathname string
* is built from <CSMAP directory> + <map name>.
* If we are given a full pathname, by the -I or -O
* options, we do nothing to the path name because it is
* the entire path.
*/
char *csmap_path(int verb, char *map_name)
{
char *p;
switch (verb) {
case SET_INPUT_FROM_PATH:
case SET_OUTPUT_FROM_PATH:
return(map_name);
}
/*
* Allocate space for <CSMAP dir> + <map name>, /, and '\0'.
*/
p = (char *)malloc((size_t)(strlen(csmap_dir) + strlen(map_name) + 2));
/*
* Copy in dir name and / if needed.
*/
strcpy(p, csmap_dir);
if (p [strlen(p) - 1] != '/')
strcat(p, "/");
/*
* Copy in mapname.
*/
switch (verb) {
case SET_INPUT:
case SET_OUTPUT:
strcat(p, map_name);
break;
default:
fprintf(stderr,
MSGSTR(CSMAPINTL, "%s: internal error, csmap_path botch\n"),
arg0);
return(NULL);
}
/*
* Return combined string.
*/
return(p);
}
/*
* Function that checks the stream to see if the nls module
* had been pushed. If it has been pushed, just return 0.
* If it has not been pushed, attempt to push. If successful
* return 0, otherwise return -1.
*
*/
int
check_nls()
{
struct str_list strlist;
int nmods;
char buf1[FMNAMESZ+1];
char buf2[FMNAMESZ+1];
unsigned size;
struct str_mlist *p;
int ii;
struct termios ldt;
int res = OK;
int fd = fileno(stdin);
struct cfg_load cfg_l;
struct cfg_kmod cfg;
struct nlsdd_init nlsdd_init;
mid_t res2;
/* where to find the nls module. there are two paths because sysconfig
* will view as different the link in /etc/drivers and the actual file
* in /usr/lib/drivers. this is a problem since we can't be sure
* from where the file was actually loaded, we can only check the
* the two most likely places.
*/
char *nls_path1 = "/etc/drivers/nls";
char *nls_path2 = "/usr/lib/drivers/nls";
/* get list of modules in the stream */
if ((nmods = ioctl(fd, I_LIST, (char *)0)) < 0) {
fprintf(stderr,
MSGSTR(NLSDD, "%s: can't determine if NLS module on the stream\n"), arg0);
return (BAD_RES);
}
size = nmods * sizeof(struct str_mlist);
strlist.sl_nmods = nmods;
strlist.sl_modlist = (struct str_mlist *)malloc(size);
memset(strlist.sl_modlist, '\0', (int)size);
if ((nmods = ioctl(fileno(stdin), I_LIST, (char *)&strlist)) < 0) {
fprintf(stderr,
MSGSTR(NLSDD, "%s: can't determine if NLS module on the stream\n"), arg0);
return (BAD_RES);
}
else if (nmods != strlist.sl_nmods) {
fprintf(stderr,
MSGSTR(NLSDD, "%s: can't determine if NLS module on the stream\n"), arg0);
return (BAD_RES);
}
/* search for the nls module - assume the name is 'nls' */
/* already have the list.... if the nls module is present, */
/* it will be module just before the driver, i.e., the next */
/* to last item in the list... */
p = strlist.sl_modlist;
nmods = strlist.sl_nmods;
p += nmods - 2;
if (strcmp(p->l_name, NLS_MODULE) == 0) {
return (OK);
}
/* nls module not on the stream, put it there - just above
the driver... must be root to do this
*/
/* we must cfg in all cases because we cannot tell if the
module has been configed even if it has been loaded...
*/
cfg_l.libpath = (char *) NULL;
cfg_l.path = nls_path1;
if (sysconfig(SYS_QUERYLOAD, (void *)&cfg_l,
(int)sizeof(struct cfg_load)) < 0) {
perror("setmaps ");
return(BAD_RES);
}
if (cfg_l.kmid == (mid_t) NULL) {
cfg_l.path = nls_path2;
if (sysconfig(SYS_QUERYLOAD, (void *)&cfg_l,
(int)sizeof(struct cfg_load)) < 0) {
perror("setmaps ");
return(BAD_RES);
}
}
if (cfg_l.kmid == (mid_t) NULL) {
/* not loaded; use /etc/drivers/nls for the nls path as this is the
* default used by loadext.
*/
cfg_l.path = nls_path1;
if (sysconfig(SYS_SINGLELOAD, (void *)&cfg_l,
(int)sizeof(struct cfg_load)) < 0) {
perror("setmaps ");
fprintf(stderr,
MSGSTR(NLSLOAD, "%s: can't load NLS module into the kernel\n"), arg0);
return(BAD_RES);
}
}
cfg.kmid = cfg_l.kmid;
cfg.cmd = CFG_INIT;
nlsdd_init.which_dds = NLS_DDS;
cfg.mdiptr = (caddr_t)&nlsdd_init;
cfg.mdilen = sizeof(struct nlsdd_init);
if ((sysconfig(SYS_CFGKMOD, (void *)&cfg,
(int)sizeof(struct cfg_kmod))) < 0) {
fprintf(stderr,
MSGSTR(NLSCFG, "%s: can't configure NLS module\n"),
arg0);
return (BAD_RES);
}
/* now pop them all, push str_nls, and push them all */
/* before popping off the modules, save the state of ldterm
so it can be restored after we push it back on the stream
*/
if (tcgetattr(fd, &ldt) == -1) {
fprintf(stderr, "setmaps: ");
perror("tcgetattr");
return(BAD_RES);
}
nmods = strlist.sl_nmods - 1; /* decr to prevent trying to pop the
driver */
p = strlist.sl_modlist;
for (ii=0;ii < nmods; ii++) {
if (ioctl(fd, I_POP, 0) < 0) {
/* a failure to pop will be a disaster, if the failure occurs
after some pops have already occurred - do we attmpt to
clean up ???
*/
fprintf(stderr,
MSGSTR(NLSPOP, "%s: unable to pop module from stream\n"),
arg0);
return (BAD_RES);
}
}
/* push the nls module */
if (ioctl(fd, I_PUSH, NLS_MODULE) < 0) {
fprintf(stderr,
MSGSTR(NLSPUSH, "%s: unable to push module %s on stream\n"),
arg0, NLS_MODULE);
res = BAD_RES;
}
/* now push modules back on the stream - remember these must
be pushed on in reverse order */
nmods = strlist.sl_nmods - 1;
p = strlist.sl_modlist;
for (ii=nmods-1;ii >= 0; ii--) {
if (ioctl(fd, I_PUSH, (p+ii)->l_name) < 0) {
fprintf(stderr,
MSGSTR(NLSPUSH, "%s: unable to push module %s on stream\n"),
arg0, (p+ii)->l_name);
return (BAD_RES);
}
}
if (tcsetattr(0, TCSANOW, &ldt) == -1) {
fprintf(stderr, "setmaps: ");
perror("setattr");
return (BAD_RES);
}
return (res);
}