Home | History | Annotate | Line # | Download | only in util
      1 /*	$NetBSD: name_mask.c,v 1.5 2026/05/09 18:49:23 christos Exp $	*/
      2 
      3 /*++
      4 /* NAME
      5 /*	name_mask 3
      6 /* SUMMARY
      7 /*	map names to bit mask
      8 /* SYNOPSIS
      9 /*	#include <name_mask.h>
     10 /*
     11 /*	int	name_mask(context, table, names)
     12 /*	const char *context;
     13 /*	const NAME_MASK *table;
     14 /*	const char *names;
     15 /*
     16 /*	long	long_name_mask(context, table, names)
     17 /*	const char *context;
     18 /*	const LONG_NAME_MASK *table;
     19 /*	const char *names;
     20 /*
     21 /*	const char *str_name_mask(context, table, mask)
     22 /*	const char *context;
     23 /*	const NAME_MASK *table;
     24 /*	int	mask;
     25 /*
     26 /*	const char *str_long_name_mask(context, table, mask)
     27 /*	const char *context;
     28 /*	const LONG_NAME_MASK *table;
     29 /*	long	mask;
     30 /*
     31 /*	int	name_mask_opt(context, table, names, flags)
     32 /*	const char *context;
     33 /*	const NAME_MASK *table;
     34 /*	const char *names;
     35 /*	int	flags;
     36 /*
     37 /*	long	long_name_mask_opt(context, table, names, flags)
     38 /*	const char *context;
     39 /*	const LONG_NAME_MASK *table;
     40 /*	const char *names;
     41 /*	int	flags;
     42 /*
     43 /*	int	name_mask_delim_opt(context, table, names, delim, flags)
     44 /*	const char *context;
     45 /*	const NAME_MASK *table;
     46 /*	const char *names;
     47 /*	const char *delim;
     48 /*	int	flags;
     49 /*
     50 /*	long	long_name_mask_delim_opt(context, table, names, delim, flags)
     51 /*	const char *context;
     52 /*	const LONG_NAME_MASK *table;
     53 /*	const char *names;
     54 /*	const char *delim;
     55 /*	int	flags;
     56 /*
     57 /*	const char *str_name_mask_opt(buf, context, table, mask, flags)
     58 /*	VSTRING	*buf;
     59 /*	const char *context;
     60 /*	const NAME_MASK *table;
     61 /*	int	mask;
     62 /*	int	flags;
     63 /*
     64 /*	const char *str_name_mask_delim_opt(
     65 /*	VSTRING	*buf,
     66 /*	const char *context,
     67 /*	const NAME_MASK *table,
     68 /*	int	mask,
     69 /*	const char *delim,
     70 /*	int	flags)
     71 /*
     72 /*	const char *str_long_name_mask_opt(buf, context, table, mask, flags)
     73 /*	VSTRING	*buf;
     74 /*	const char *context;
     75 /*	const LONG_NAME_MASK *table;
     76 /*	long	mask;
     77 /*	int	flags;
     78 /* DESCRIPTION
     79 /*	name_mask() takes a null-terminated \fItable\fR with (name, mask)
     80 /*	values and computes the bit-wise OR of the masks that correspond
     81 /*	to the names listed in the \fInames\fR argument, separated by
     82 /*	comma and/or whitespace characters. The "long_" version returns
     83 /*	a "long int" bitmask, rather than an "int" bitmask.
     84 /*
     85 /*	str_name_mask() translates a mask into its equivalent names.
     86 /*	The result is written to a static buffer that is overwritten
     87 /*	upon each call. The "long_" version converts a "long int"
     88 /*	bitmask, rather than an "int" bitmask.
     89 /*
     90 /*	name_mask_opt() and str_name_mask_opt() are extended versions
     91 /*	with additional fine control. name_mask_delim_opt() supports
     92 /*	non-default delimiter characters.
     93 /*
     94 /*	Arguments:
     95 /* .IP buf
     96 /*	Null pointer or pointer to buffer storage.
     97 /* .IP context
     98 /*	What kind of names and
     99 /*	masks are being manipulated, in order to make error messages
    100 /*	more understandable. Typically, this would be the name of a
    101 /*	user-configurable parameter.
    102 /* .IP table
    103 /*	Table with (name, bit mask) pairs.
    104 /* .IP names
    105 /*	A list of names that is to be converted into a bit mask.
    106 /* .IP mask
    107 /*	A bit mask.
    108 /* .IP delim
    109 /*	When converting from string to mask, the set of delimiter
    110 /*	characters to use instead of whitespace and commas. When
    111 /*	converting from mask to string, the delimiter string to use
    112 /*	instead what may be specified with flags.
    113 /* .IP flags
    114 /*	Bit-wise OR of one or more of the following.  Where features
    115 /*	would have conflicting results (e.g., FATAL versus IGNORE),
    116 /*	the feature that takes precedence is described first.
    117 /*
    118 /*	When converting from string to mask, at least one of the
    119 /*	following must be specified: NAME_MASK_FATAL, NAME_MASK_RETURN,
    120 /*	NAME_MASK_WARN or NAME_MASK_IGNORE.
    121 /*
    122 /*	When converting from mask to string, at least one of the
    123 /*	following must be specified: NAME_MASK_NUMBER, NAME_MASK_FATAL,
    124 /*	NAME_MASK_RETURN, NAME_MASK_WARN or NAME_MASK_IGNORE.
    125 /* .RS
    126 /* .IP NAME_MASK_NUMBER
    127 /*	When converting from string to mask, accept hexadecimal
    128 /*	inputs starting with "0x" followed by hexadecimal digits.
    129 /*	Each hexadecimal input may specify multiple bits.  This
    130 /*	feature is ignored for hexadecimal inputs that cannot be
    131 /*	converted (malformed, out of range, etc.).
    132 /*
    133 /*	When converting from mask to string, represent bits not
    134 /*	defined in \fItable\fR as "0x" followed by hexadecimal
    135 /*	digits. This conversion always succeeds.
    136 /* .IP NAME_MASK_FATAL
    137 /*	Require that all names listed in \fIname\fR exist in
    138 /*	\fItable\fR or that they can be parsed as a hexadecimal
    139 /*	string, and require that all bits listed in \fImask\fR exist
    140 /*	in \fItable\fR or that they can be converted to hexadecimal
    141 /*	string.  Terminate with a fatal run-time error if this
    142 /*	condition is not met.  This feature is enabled by default
    143 /*	when calling name_mask() or str_name_mask().
    144 /* .IP NAME_MASK_RETURN
    145 /*	Require that all names listed in \fIname\fR exist in
    146 /*	\fItable\fR or that they can be parsed as a hexadecimal
    147 /*	string, and require that all bits listed in \fImask\fR exist
    148 /*	in \fItable\fR or that they can be converted to hexadecimal
    149 /*	string.  Log a warning, and return 0 (name_mask()) or a
    150 /*	null pointer (str_name_mask()) if this condition is not
    151 /*	met.  This feature is not enabled by default when calling
    152 /*	name_mask() or str_name_mask().
    153 /* .IP NAME_MASK_WARN
    154 /*	Require that all names listed in \fIname\fR exist in
    155 /*	\fItable\fR or that they can be parsed as a hexadecimal
    156 /*	string, and require that all bits listed in \fImask\fR exist
    157 /*	in \fItable\fR or that they can be converted to hexadecimal
    158 /*	string.  Log a warning if this condition is not met, continue
    159 /*	processing, and return all valid bits or names.  This feature
    160 /*	is not enabled by default when calling name_mask() or
    161 /*	str_name_mask().
    162 /* .IP NAME_MASK_IGNORE
    163 /*	Silently ignore names listed in \fIname\fR that don't exist
    164 /*	in \fItable\fR and that can't be parsed as a hexadecimal
    165 /*	string, and silently ignore bits listed in \fImask\fR that
    166 /*	don't exist in \fItable\fR and that can't be converted to
    167 /*	hexadecimal string.
    168 /* .IP NAME_MASK_ANY_CASE
    169 /*	Enable case-insensitive matching.
    170 /*	This feature is not enabled by default when calling name_mask();
    171 /*	it has no effect with str_name_mask().
    172 /* .IP NAME_MASK_COMMA
    173 /*	Use comma instead of space when converting a mask to string.
    174 /* .IP NAME_MASK_PIPE
    175 /*	Use "|" instead of space when converting a mask to string.
    176 /* .RE
    177 /*	The value NAME_MASK_NONE explicitly requests no features,
    178 /*	and NAME_MASK_DEFAULT enables the default options.
    179 /* DIAGNOSTICS
    180 /*	Fatal: the \fInames\fR argument specifies a name not found in
    181 /*	\fItable\fR, or the \fImask\fR specifies a bit not found in
    182 /*	\fItable\fR.
    183 /* LICENSE
    184 /* .ad
    185 /* .fi
    186 /*	The Secure Mailer license must be distributed with this software.
    187 /* AUTHOR(S)
    188 /*	Wietse Venema
    189 /*	IBM T.J. Watson Research
    190 /*	P.O. Box 704
    191 /*	Yorktown Heights, NY 10598, USA
    192 /*--*/
    193 
    194 /* System library. */
    195 
    196 #include <sys_defs.h>
    197 #include <string.h>
    198 #include <errno.h>
    199 #include <stdlib.h>
    200 
    201 #ifdef STRCASECMP_IN_STRINGS_H
    202 #include <strings.h>
    203 #endif
    204 
    205 /* Utility library. */
    206 
    207 #include <msg.h>
    208 #include <mymalloc.h>
    209 #include <stringops.h>
    210 #include <name_mask.h>
    211 #include <vstring.h>
    212 
    213 static int hex_to_ulong(char *, unsigned long, unsigned long *);
    214 
    215 #define STR(x) vstring_str(x)
    216 
    217 /* name_mask_delim_opt - compute mask corresponding to list of names */
    218 
    219 int     name_mask_delim_opt(const char *context, const NAME_MASK *table,
    220 		            const char *names, const char *delim, int flags)
    221 {
    222     const char *myname = "name_mask";
    223     char   *saved_names = mystrdup(names);
    224     char   *bp = saved_names;
    225     int     result = 0;
    226     const NAME_MASK *np;
    227     char   *name;
    228     int     (*lookup) (const char *, const char *);
    229     unsigned long ulval;
    230 
    231     if ((flags & NAME_MASK_REQUIRED) == 0)
    232 	msg_panic("%s: missing NAME_MASK_FATAL/RETURN/WARN/IGNORE flag",
    233 		  myname);
    234 
    235     if (flags & NAME_MASK_ANY_CASE)
    236 	lookup = strcasecmp;
    237     else
    238 	lookup = strcmp;
    239 
    240     /*
    241      * Break up the names string, and look up each component in the table. If
    242      * the name is found, merge its mask with the result.
    243      */
    244     while ((name = mystrtok(&bp, delim)) != 0) {
    245 	for (np = table; /* void */ ; np++) {
    246 	    if (np->name == 0) {
    247 		if ((flags & NAME_MASK_NUMBER)
    248 		    && hex_to_ulong(name, ~0U, &ulval)) {
    249 		    result |= (unsigned int) ulval;
    250 		} else if (flags & NAME_MASK_FATAL) {
    251 		    msg_fatal("unknown %s value \"%s\" in \"%s\"",
    252 			      context, name, names);
    253 		} else if (flags & NAME_MASK_RETURN) {
    254 		    msg_warn("unknown %s value \"%s\" in \"%s\"",
    255 			     context, name, names);
    256 		    myfree(saved_names);
    257 		    return (0);
    258 		} else if (flags & NAME_MASK_WARN) {
    259 		    msg_warn("unknown %s value \"%s\" in \"%s\"",
    260 			     context, name, names);
    261 		}
    262 		break;
    263 	    }
    264 	    if (lookup(name, np->name) == 0) {
    265 		if (msg_verbose)
    266 		    msg_info("%s: %s", myname, name);
    267 		result |= np->mask;
    268 		break;
    269 	    }
    270 	}
    271     }
    272     myfree(saved_names);
    273     return (result);
    274 }
    275 
    276 /* str_name_mask_delim_opt - mask to string */
    277 
    278 const char *str_name_mask_delim_opt(VSTRING *buf, const char *context,
    279 				            const NAME_MASK *table,
    280 				            int mask, const char *delim,
    281 				            int flags)
    282 {
    283     const char *myname = "str_name_mask";
    284     const NAME_MASK *np;
    285     ssize_t len;
    286     static VSTRING *my_buf = 0;
    287 
    288     if ((flags & STR_NAME_MASK_REQUIRED) == 0)
    289 	msg_panic("%s: missing NAME_MASK_NUMBER/FATAL/RETURN/WARN/IGNORE flag",
    290 		  myname);
    291 
    292     if (buf == 0) {
    293 	if (my_buf == 0)
    294 	    my_buf = vstring_alloc(1);
    295 	buf = my_buf;
    296     }
    297     VSTRING_RESET(buf);
    298 
    299     for (np = table; mask != 0; np++) {
    300 	if (np->name == 0) {
    301 	    if (flags & NAME_MASK_NUMBER) {
    302 		vstring_sprintf_append(buf, "0x%x%s", mask, delim);
    303 	    } else if (flags & NAME_MASK_FATAL) {
    304 		msg_fatal("%s: unknown %s bit in mask: 0x%x",
    305 			  myname, context, mask);
    306 	    } else if (flags & NAME_MASK_RETURN) {
    307 		msg_warn("%s: unknown %s bit in mask: 0x%x",
    308 			 myname, context, mask);
    309 		return (0);
    310 	    } else if (flags & NAME_MASK_WARN) {
    311 		msg_warn("%s: unknown %s bit in mask: 0x%x",
    312 			 myname, context, mask);
    313 	    }
    314 	    break;
    315 	}
    316 	if (mask & np->mask) {
    317 	    mask &= ~np->mask;
    318 	    vstring_sprintf_append(buf, "%s%s", np->name, delim);
    319 	}
    320     }
    321     if ((len = VSTRING_LEN(buf)) > 0)
    322 	vstring_truncate(buf, len - strlen(delim));
    323     VSTRING_TERMINATE(buf);
    324 
    325     return (STR(buf));
    326 }
    327 
    328 /* str_name_mask_opt - mask to string */
    329 
    330 const char *str_name_mask_opt(VSTRING *buf, const char *context,
    331 			              const NAME_MASK *table,
    332 			              int mask, int flags)
    333 {
    334     const char *delim = (flags & NAME_MASK_COMMA ? "," :
    335 			 (flags & NAME_MASK_PIPE ? "|" : " "));
    336 
    337     return (str_name_mask_delim_opt(buf, context, table, mask, delim, flags));
    338 }
    339 
    340 /* long_name_mask_delim_opt - compute mask corresponding to list of names */
    341 
    342 long    long_name_mask_delim_opt(const char *context,
    343 				         const LONG_NAME_MASK *table,
    344 			               const char *names, const char *delim,
    345 				         int flags)
    346 {
    347     const char *myname = "name_mask";
    348     char   *saved_names = mystrdup(names);
    349     char   *bp = saved_names;
    350     long    result = 0;
    351     const LONG_NAME_MASK *np;
    352     char   *name;
    353     int     (*lookup) (const char *, const char *);
    354     unsigned long ulval;
    355 
    356     if ((flags & NAME_MASK_REQUIRED) == 0)
    357 	msg_panic("%s: missing NAME_MASK_FATAL/RETURN/WARN/IGNORE flag",
    358 		  myname);
    359 
    360     if (flags & NAME_MASK_ANY_CASE)
    361 	lookup = strcasecmp;
    362     else
    363 	lookup = strcmp;
    364 
    365     /*
    366      * Break up the names string, and look up each component in the table. If
    367      * the name is found, merge its mask with the result.
    368      */
    369     while ((name = mystrtok(&bp, delim)) != 0) {
    370 	for (np = table; /* void */ ; np++) {
    371 	    if (np->name == 0) {
    372 		if ((flags & NAME_MASK_NUMBER)
    373 		    && hex_to_ulong(name, ~0UL, &ulval)) {
    374 		    result |= ulval;
    375 		} else if (flags & NAME_MASK_FATAL) {
    376 		    msg_fatal("unknown %s value \"%s\" in \"%s\"",
    377 			      context, name, names);
    378 		} else if (flags & NAME_MASK_RETURN) {
    379 		    msg_warn("unknown %s value \"%s\" in \"%s\"",
    380 			     context, name, names);
    381 		    myfree(saved_names);
    382 		    return (0);
    383 		} else if (flags & NAME_MASK_WARN) {
    384 		    msg_warn("unknown %s value \"%s\" in \"%s\"",
    385 			     context, name, names);
    386 		}
    387 		break;
    388 	    }
    389 	    if (lookup(name, np->name) == 0) {
    390 		if (msg_verbose)
    391 		    msg_info("%s: %s", myname, name);
    392 		result |= np->mask;
    393 		break;
    394 	    }
    395 	}
    396     }
    397 
    398     myfree(saved_names);
    399     return (result);
    400 }
    401 
    402 /* str_long_name_mask_opt - mask to string */
    403 
    404 const char *str_long_name_mask_opt(VSTRING *buf, const char *context,
    405 				           const LONG_NAME_MASK *table,
    406 				           long mask, int flags)
    407 {
    408     const char *myname = "name_mask";
    409     ssize_t len;
    410     static VSTRING *my_buf = 0;
    411     int     delim = (flags & NAME_MASK_COMMA ? ',' :
    412 		     (flags & NAME_MASK_PIPE ? '|' : ' '));
    413     const LONG_NAME_MASK *np;
    414 
    415     if ((flags & STR_NAME_MASK_REQUIRED) == 0)
    416 	msg_panic("%s: missing NAME_MASK_NUMBER/FATAL/RETURN/WARN/IGNORE flag",
    417 		  myname);
    418 
    419     if (buf == 0) {
    420 	if (my_buf == 0)
    421 	    my_buf = vstring_alloc(1);
    422 	buf = my_buf;
    423     }
    424     VSTRING_RESET(buf);
    425 
    426     for (np = table; mask != 0; np++) {
    427 	if (np->name == 0) {
    428 	    if (flags & NAME_MASK_NUMBER) {
    429 		vstring_sprintf_append(buf, "0x%lx%c", mask, delim);
    430 	    } else if (flags & NAME_MASK_FATAL) {
    431 		msg_fatal("%s: unknown %s bit in mask: 0x%lx",
    432 			  myname, context, mask);
    433 	    } else if (flags & NAME_MASK_RETURN) {
    434 		msg_warn("%s: unknown %s bit in mask: 0x%lx",
    435 			 myname, context, mask);
    436 		return (0);
    437 	    } else if (flags & NAME_MASK_WARN) {
    438 		msg_warn("%s: unknown %s bit in mask: 0x%lx",
    439 			 myname, context, mask);
    440 	    }
    441 	    break;
    442 	}
    443 	if (mask & np->mask) {
    444 	    mask &= ~np->mask;
    445 	    vstring_sprintf_append(buf, "%s%c", np->name, delim);
    446 	}
    447     }
    448     if ((len = VSTRING_LEN(buf)) > 0)
    449 	vstring_truncate(buf, len - 1);
    450     VSTRING_TERMINATE(buf);
    451 
    452     return (STR(buf));
    453 }
    454 
    455 /* hex_to_ulong - 0x... to unsigned long or smaller */
    456 
    457 static int hex_to_ulong(char *value, unsigned long mask, unsigned long *ulp)
    458 {
    459     unsigned long result;
    460     char   *cp;
    461 
    462     if (strncasecmp(value, "0x", 2) != 0)
    463 	return (0);
    464 
    465     /*
    466      * Check for valid hex number. Since the value starts with 0x, strtoul()
    467      * will not allow a negative sign before the first nibble. So we don't
    468      * need to worry about explicit +/- signs.
    469      */
    470     errno = 0;
    471     result = strtoul(value, &cp, 16);
    472     if (*cp != '\0' || errno == ERANGE)
    473 	return (0);
    474 
    475     *ulp = (result & mask);
    476     return (*ulp == result);
    477 }
    478 
    479 #ifdef TEST
    480 
    481  /*
    482   * Stand-alone test program.
    483   */
    484 #include <stdlib.h>
    485 #include <vstream.h>
    486 #include <vstring_vstream.h>
    487 
    488 int     main(int argc, char **argv)
    489 {
    490     static const NAME_MASK demo_table[] = {
    491 	"zero", 1 << 0,
    492 	"one", 1 << 1,
    493 	"two", 1 << 2,
    494 	"three", 1 << 3,
    495 	0, 0,
    496     };
    497     static const NAME_MASK feature_table[] = {
    498 	"DEFAULT", NAME_MASK_DEFAULT,
    499 	"FATAL", NAME_MASK_FATAL,
    500 	"ANY_CASE", NAME_MASK_ANY_CASE,
    501 	"RETURN", NAME_MASK_RETURN,
    502 	"COMMA", NAME_MASK_COMMA,
    503 	"PIPE", NAME_MASK_PIPE,
    504 	"NUMBER", NAME_MASK_NUMBER,
    505 	"WARN", NAME_MASK_WARN,
    506 	"IGNORE", NAME_MASK_IGNORE,
    507 	0,
    508     };
    509     int     in_feature_mask;
    510     int     out_feature_mask;
    511     int     demo_mask;
    512     const char *demo_str;
    513     VSTRING *out_buf = vstring_alloc(1);
    514     VSTRING *in_buf = vstring_alloc(1);
    515 
    516     if (argc != 3)
    517 	msg_fatal("usage: %s in-feature-mask out-feature-mask", argv[0]);
    518     in_feature_mask = name_mask(argv[1], feature_table, argv[1]);
    519     out_feature_mask = name_mask(argv[2], feature_table, argv[2]);
    520     while (vstring_get_nonl(in_buf, VSTREAM_IN) != VSTREAM_EOF) {
    521 	demo_mask = name_mask_opt("name", demo_table,
    522 				  STR(in_buf), in_feature_mask);
    523 	demo_str = str_name_mask_opt(out_buf, "mask", demo_table,
    524 				     demo_mask, out_feature_mask);
    525 	vstream_printf("%s -> 0x%x -> %s\n",
    526 		       STR(in_buf), demo_mask,
    527 		       demo_str ? demo_str : "(null)");
    528 	vstream_fflush(VSTREAM_OUT);
    529     }
    530     vstring_free(in_buf);
    531     vstring_free(out_buf);
    532     exit(0);
    533 }
    534 
    535 #endif
    536