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