1 /* 2 * File contexts backend for labeling system 3 * 4 * Author : Eamon Walsh <ewalsh (at) tycho.nsa.gov> 5 */ 6 7 #include <fcntl.h> 8 #include <stdarg.h> 9 #include <string.h> 10 #include <stdio.h> 11 #include <ctype.h> 12 #include <errno.h> 13 #include <limits.h> 14 #include <regex.h> 15 #include <sys/types.h> 16 #include <sys/stat.h> 17 #include <unistd.h> 18 #include "callbacks.h" 19 #include "label_internal.h" 20 21 /* 22 * Internals, mostly moved over from matchpathcon.c 23 */ 24 25 /* A file security context specification. */ 26 typedef struct spec { 27 struct selabel_lookup_rec lr; /* holds contexts for lookup result */ 28 char *regex_str; /* regular expession string for diagnostics */ 29 char *type_str; /* type string for diagnostic messages */ 30 regex_t regex; /* compiled regular expression */ 31 char regcomp; /* regex_str has been compiled to regex */ 32 mode_t mode; /* mode format value */ 33 int matches; /* number of matching pathnames */ 34 int hasMetaChars; /* regular expression has meta-chars */ 35 int stem_id; /* indicates which stem-compression item */ 36 size_t prefix_len; /* length of fixed path prefix */ 37 } spec_t; 38 39 /* A regular expression stem */ 40 typedef struct stem { 41 char *buf; 42 int len; 43 } stem_t; 44 45 /* Our stored configuration */ 46 struct saved_data { 47 /* 48 * The array of specifications, initially in the same order as in 49 * the specification file. Sorting occurs based on hasMetaChars. 50 */ 51 spec_t *spec_arr; 52 unsigned int nspec; 53 unsigned int ncomp; 54 55 /* 56 * The array of regular expression stems. 57 */ 58 stem_t *stem_arr; 59 int num_stems; 60 int alloc_stems; 61 }; 62 63 /* Return the length of the text that can be considered the stem, returns 0 64 * if there is no identifiable stem */ 65 static int get_stem_from_spec(const char *const buf) 66 { 67 const char *tmp = strchr(buf + 1, '/'); 68 const char *ind; 69 70 if (!tmp) 71 return 0; 72 73 for (ind = buf; ind < tmp; ind++) { 74 if (strchr(".^$?*+|[({", (int)*ind)) 75 return 0; 76 } 77 return tmp - buf; 78 } 79 80 /* return the length of the text that is the stem of a file name */ 81 static int get_stem_from_file_name(const char *const buf) 82 { 83 const char *tmp = strchr(buf + 1, '/'); 84 85 if (!tmp) 86 return 0; 87 return tmp - buf; 88 } 89 90 /* find the stem of a file spec, returns the index into stem_arr for a new 91 * or existing stem, (or -1 if there is no possible stem - IE for a file in 92 * the root directory or a regex that is too complex for us). */ 93 static int find_stem_from_spec(struct saved_data *data, const char *buf) 94 { 95 int i, num = data->num_stems; 96 int stem_len = get_stem_from_spec(buf); 97 98 if (!stem_len) 99 return -1; 100 for (i = 0; i < num; i++) { 101 if (stem_len == data->stem_arr[i].len 102 && !strncmp(buf, data->stem_arr[i].buf, stem_len)) 103 return i; 104 } 105 if (data->alloc_stems == num) { 106 stem_t *tmp_arr; 107 data->alloc_stems = data->alloc_stems * 2 + 16; 108 tmp_arr = (stem_t *) realloc(data->stem_arr, 109 sizeof(stem_t) * data->alloc_stems); 110 if (!tmp_arr) 111 return -1; 112 data->stem_arr = tmp_arr; 113 } 114 data->stem_arr[num].len = stem_len; 115 data->stem_arr[num].buf = (char *) malloc(stem_len + 1); 116 if (!data->stem_arr[num].buf) 117 return -1; 118 memcpy(data->stem_arr[num].buf, buf, stem_len); 119 data->stem_arr[num].buf[stem_len] = '\0'; 120 data->num_stems++; 121 buf += stem_len; 122 return num; 123 } 124 125 /* find the stem of a file name, returns the index into stem_arr (or -1 if 126 * there is no match - IE for a file in the root directory or a regex that is 127 * too complex for us). Makes buf point to the text AFTER the stem. */ 128 static int find_stem_from_file(struct saved_data *data, const char **buf) 129 { 130 int i; 131 int stem_len = get_stem_from_file_name(*buf); 132 133 if (!stem_len) 134 return -1; 135 for (i = 0; i < data->num_stems; i++) { 136 if (stem_len == data->stem_arr[i].len 137 && !strncmp(*buf, data->stem_arr[i].buf, stem_len)) { 138 *buf += stem_len; 139 return i; 140 } 141 } 142 return -1; 143 } 144 145 /* 146 * Warn about duplicate specifications. 147 */ 148 static int nodups_specs(struct saved_data *data, const char *path) 149 { 150 int rc = 0; 151 unsigned int ii, jj; 152 struct spec *curr_spec, *spec_arr = data->spec_arr; 153 154 for (ii = 0; ii < data->nspec; ii++) { 155 curr_spec = &spec_arr[ii]; 156 for (jj = ii + 1; jj < data->nspec; jj++) { 157 if ((!strcmp 158 (spec_arr[jj].regex_str, curr_spec->regex_str)) 159 && (!spec_arr[jj].mode || !curr_spec->mode 160 || spec_arr[jj].mode == curr_spec->mode)) { 161 rc = -1; 162 errno = EINVAL; 163 if (strcmp 164 (spec_arr[jj].lr.ctx_raw, 165 curr_spec->lr.ctx_raw)) { 166 selinux_log 167 (SELINUX_ERROR, 168 "%s: Multiple different specifications for %s (%s and %s).\n", 169 path, curr_spec->regex_str, 170 spec_arr[jj].lr.ctx_raw, 171 curr_spec->lr.ctx_raw); 172 } else { 173 selinux_log 174 (SELINUX_ERROR, 175 "%s: Multiple same specifications for %s.\n", 176 path, curr_spec->regex_str); 177 } 178 } 179 } 180 } 181 return rc; 182 } 183 184 /* Determine if the regular expression specification has any meta characters. */ 185 static void spec_hasMetaChars(struct spec *spec) 186 { 187 char *c; 188 size_t len; 189 char *end; 190 191 c = spec->regex_str; 192 len = strlen(spec->regex_str); 193 end = c + len; 194 195 spec->hasMetaChars = 0; 196 spec->prefix_len = len; 197 198 /* Look at each character in the RE specification string for a 199 * meta character. Return when any meta character reached. */ 200 while (c != end) { 201 switch (*c) { 202 case '.': 203 case '^': 204 case '$': 205 case '?': 206 case '*': 207 case '+': 208 case '|': 209 case '[': 210 case '(': 211 case '{': 212 spec->hasMetaChars = 1; 213 spec->prefix_len = c - spec->regex_str; 214 return; 215 case '\\': /* skip the next character */ 216 c++; 217 break; 218 default: 219 break; 220 221 } 222 c++; 223 } 224 return; 225 } 226 227 static int compile_regex(struct saved_data *data, spec_t *spec, char **errbuf) 228 { 229 char *reg_buf, *anchored_regex, *cp; 230 stem_t *stem_arr = data->stem_arr; 231 size_t len; 232 int regerr; 233 234 if (spec->regcomp) 235 return 0; /* already done */ 236 237 data->ncomp++; /* how many compiled regexes required */ 238 239 /* Skip the fixed stem. */ 240 reg_buf = spec->regex_str; 241 if (spec->stem_id >= 0) 242 reg_buf += stem_arr[spec->stem_id].len; 243 244 /* Anchor the regular expression. */ 245 len = strlen(reg_buf); 246 cp = anchored_regex = (char *) malloc(len + 3); 247 if (!anchored_regex) 248 return -1; 249 /* Create ^...$ regexp. */ 250 *cp++ = '^'; 251 memcpy(cp, reg_buf, len); 252 cp += len; 253 *cp++ = '$'; 254 *cp = '\0'; 255 256 /* Compile the regular expression. */ 257 regerr = regcomp(&spec->regex, anchored_regex, 258 REG_EXTENDED | REG_NOSUB); 259 if (regerr != 0) { 260 size_t errsz = 0; 261 errsz = regerror(regerr, &spec->regex, NULL, 0); 262 if (errsz && errbuf) 263 *errbuf = (char *) malloc(errsz); 264 if (errbuf && *errbuf) 265 (void)regerror(regerr, &spec->regex, 266 *errbuf, errsz); 267 268 free(anchored_regex); 269 return -1; 270 } 271 free(anchored_regex); 272 273 /* Done. */ 274 spec->regcomp = 1; 275 276 return 0; 277 } 278 279 280 static int process_line(struct selabel_handle *rec, 281 const char *path, const char *prefix, 282 char *line_buf, int pass, unsigned lineno) 283 { 284 int items, len; 285 char buf1[BUFSIZ], buf2[BUFSIZ], buf3[BUFSIZ]; 286 char *buf_p, *regex = buf1, *type = buf2, *context = buf3; 287 struct saved_data *data = (struct saved_data *)rec->data; 288 spec_t *spec_arr = data->spec_arr; 289 unsigned int nspec = data->nspec; 290 291 len = strlen(line_buf); 292 if (line_buf[len - 1] == '\n') 293 line_buf[len - 1] = 0; 294 buf_p = line_buf; 295 while (isspace(*buf_p)) 296 buf_p++; 297 /* Skip comment lines and empty lines. */ 298 if (*buf_p == '#' || *buf_p == 0) 299 return 0; 300 items = sscanf(line_buf, "%255s %255s %255s", regex, type, context); 301 if (items < 2) { 302 selinux_log(SELINUX_WARNING, 303 "%s: line %d is missing fields, skipping\n", path, 304 lineno); 305 return 0; 306 } else if (items == 2) { 307 /* The type field is optional. */ 308 context = type; 309 type = NULL; 310 } 311 312 len = get_stem_from_spec(regex); 313 if (len && prefix && strncmp(prefix, regex, len)) { 314 /* Stem of regex does not match requested prefix, discard. */ 315 return 0; 316 } 317 318 if (pass == 1) { 319 /* On the second pass, process and store the specification in spec. */ 320 char *errbuf = NULL; 321 spec_arr[nspec].stem_id = find_stem_from_spec(data, regex); 322 spec_arr[nspec].regex_str = strdup(regex); 323 if (!spec_arr[nspec].regex_str) { 324 selinux_log(SELINUX_WARNING, 325 "%s: out of memory at line %d on regex %s\n", 326 path, lineno, regex); 327 return -1; 328 329 } 330 if (rec->validating && compile_regex(data, &spec_arr[nspec], &errbuf)) { 331 selinux_log(SELINUX_WARNING, 332 "%s: line %d has invalid regex %s: %s\n", 333 path, lineno, regex, 334 (errbuf ? errbuf : "out of memory")); 335 } 336 337 /* Convert the type string to a mode format */ 338 spec_arr[nspec].mode = 0; 339 if (!type) 340 goto skip_type; 341 spec_arr[nspec].type_str = strdup(type); 342 len = strlen(type); 343 if (type[0] != '-' || len != 2) { 344 selinux_log(SELINUX_WARNING, 345 "%s: line %d has invalid file type %s\n", 346 path, lineno, type); 347 return 0; 348 } 349 switch (type[1]) { 350 case 'b': 351 spec_arr[nspec].mode = S_IFBLK; 352 break; 353 case 'c': 354 spec_arr[nspec].mode = S_IFCHR; 355 break; 356 case 'd': 357 spec_arr[nspec].mode = S_IFDIR; 358 break; 359 case 'p': 360 spec_arr[nspec].mode = S_IFIFO; 361 break; 362 case 'l': 363 spec_arr[nspec].mode = S_IFLNK; 364 break; 365 case 's': 366 spec_arr[nspec].mode = S_IFSOCK; 367 break; 368 case '-': 369 spec_arr[nspec].mode = S_IFREG; 370 break; 371 default: 372 selinux_log(SELINUX_WARNING, 373 "%s: line %d has invalid file type %s\n", 374 path, lineno, type); 375 return 0; 376 } 377 378 skip_type: 379 spec_arr[nspec].lr.ctx_raw = strdup(context); 380 381 if (strcmp(context, "<<none>>") && rec->validating) { 382 if (selabel_validate(rec, &spec_arr[nspec].lr) < 0) { 383 selinux_log(SELINUX_WARNING, 384 "%s: line %d has invalid context %s\n", 385 path, lineno, spec_arr[nspec].lr.ctx_raw); 386 } 387 } 388 389 /* Determine if specification has 390 * any meta characters in the RE */ 391 spec_hasMetaChars(&spec_arr[nspec]); 392 } 393 394 data->nspec = ++nspec; 395 return 0; 396 } 397 398 static int init(struct selabel_handle *rec, const struct selinux_opt *opts, 399 unsigned n) 400 { 401 struct saved_data *data = (struct saved_data *)rec->data; 402 const char *path = NULL; 403 const char *prefix = NULL; 404 FILE *fp; 405 FILE *localfp = NULL; 406 FILE *homedirfp = NULL; 407 char local_path[PATH_MAX + 1]; 408 char homedir_path[PATH_MAX + 1]; 409 char line_buf[BUFSIZ]; 410 unsigned int lineno, pass, i, j, maxnspec; 411 spec_t *spec_copy = NULL; 412 int status = -1, baseonly = 0; 413 struct stat sb; 414 415 /* Process arguments */ 416 while (n--) 417 switch(opts[n].type) { 418 case SELABEL_OPT_PATH: 419 path = opts[n].value; 420 break; 421 case SELABEL_OPT_SUBSET: 422 prefix = opts[n].value; 423 break; 424 case SELABEL_OPT_BASEONLY: 425 baseonly = !!opts[n].value; 426 break; 427 } 428 429 /* Open the specification file. */ 430 if ((fp = fopen(path, "r")) == NULL) 431 return -1; 432 433 if (fstat(fileno(fp), &sb) < 0) 434 return -1; 435 if (!S_ISREG(sb.st_mode)) { 436 errno = EINVAL; 437 return -1; 438 } 439 440 if (!baseonly) { 441 snprintf(homedir_path, sizeof(homedir_path), "%s.homedirs", 442 path); 443 homedirfp = fopen(homedir_path, "r"); 444 445 snprintf(local_path, sizeof(local_path), "%s.local", path); 446 localfp = fopen(local_path, "r"); 447 } 448 449 /* 450 * Perform two passes over the specification file. 451 * The first pass counts the number of specifications and 452 * performs simple validation of the input. At the end 453 * of the first pass, the spec array is allocated. 454 * The second pass performs detailed validation of the input 455 * and fills in the spec array. 456 */ 457 maxnspec = UINT_MAX / sizeof(spec_t); 458 for (pass = 0; pass < 2; pass++) { 459 lineno = 0; 460 data->nspec = 0; 461 data->ncomp = 0; 462 while (fgets(line_buf, sizeof line_buf - 1, fp) 463 && data->nspec < maxnspec) { 464 if (process_line(rec, path, prefix, line_buf, 465 pass, ++lineno) != 0) 466 goto finish; 467 } 468 if (pass == 1) { 469 status = nodups_specs(data, path); 470 if (status) 471 goto finish; 472 } 473 lineno = 0; 474 if (homedirfp) 475 while (fgets(line_buf, sizeof line_buf - 1, homedirfp) 476 && data->nspec < maxnspec) { 477 if (process_line 478 (rec, homedir_path, prefix, 479 line_buf, pass, ++lineno) != 0) 480 goto finish; 481 } 482 483 lineno = 0; 484 if (localfp) 485 while (fgets(line_buf, sizeof line_buf - 1, localfp) 486 && data->nspec < maxnspec) { 487 if (process_line 488 (rec, local_path, prefix, line_buf, 489 pass, ++lineno) != 0) 490 goto finish; 491 } 492 493 if (pass == 0) { 494 if (data->nspec == 0) { 495 status = 0; 496 goto finish; 497 } 498 if (NULL == (data->spec_arr = 499 (spec_t *) malloc(sizeof(spec_t) * data->nspec))) 500 goto finish; 501 memset(data->spec_arr, 0, sizeof(spec_t)*data->nspec); 502 maxnspec = data->nspec; 503 rewind(fp); 504 if (homedirfp) 505 rewind(homedirfp); 506 if (localfp) 507 rewind(localfp); 508 } 509 } 510 511 /* Move exact pathname specifications to the end. */ 512 spec_copy = (spec_t *) malloc(sizeof(spec_t) * data->nspec); 513 if (!spec_copy) 514 goto finish; 515 j = 0; 516 for (i = 0; i < data->nspec; i++) 517 if (data->spec_arr[i].hasMetaChars) 518 memcpy(&spec_copy[j++], 519 &data->spec_arr[i], sizeof(spec_t)); 520 for (i = 0; i < data->nspec; i++) 521 if (!data->spec_arr[i].hasMetaChars) 522 memcpy(&spec_copy[j++], 523 &data->spec_arr[i], sizeof(spec_t)); 524 free(data->spec_arr); 525 data->spec_arr = spec_copy; 526 527 status = 0; 528 finish: 529 fclose(fp); 530 if (data->spec_arr != spec_copy) 531 free(data->spec_arr); 532 if (homedirfp) 533 fclose(homedirfp); 534 if (localfp) 535 fclose(localfp); 536 return status; 537 } 538 539 /* 540 * Backend interface routines 541 */ 542 static void closef(struct selabel_handle *rec) 543 { 544 struct saved_data *data = (struct saved_data *)rec->data; 545 struct spec *spec; 546 struct stem *stem; 547 unsigned int i; 548 549 for (i = 0; i < data->nspec; i++) { 550 spec = &data->spec_arr[i]; 551 free(spec->regex_str); 552 free(spec->type_str); 553 free(spec->lr.ctx_raw); 554 free(spec->lr.ctx_trans); 555 if (spec->regcomp) 556 regfree(&spec->regex); 557 } 558 559 for (i = 0; i < (unsigned int)data->num_stems; i++) { 560 stem = &data->stem_arr[i]; 561 free(stem->buf); 562 } 563 564 if (data->spec_arr) 565 free(data->spec_arr); 566 if (data->stem_arr) 567 free(data->stem_arr); 568 569 free(data); 570 } 571 572 static spec_t *lookup_common(struct selabel_handle *rec, 573 const char *key, 574 int type, 575 bool partial) 576 { 577 struct saved_data *data = (struct saved_data *)rec->data; 578 spec_t *spec_arr = data->spec_arr; 579 int i, rc, file_stem; 580 mode_t mode = (mode_t)type; 581 const char *buf; 582 spec_t *ret = NULL; 583 char *clean_key = NULL; 584 const char *prev_slash, *next_slash; 585 unsigned int sofar = 0; 586 size_t keylen = strlen(key); 587 588 if (!data->nspec) { 589 errno = ENOENT; 590 goto finish; 591 } 592 593 /* Remove duplicate slashes */ 594 if ((next_slash = strstr(key, "//"))) { 595 clean_key = (char *) malloc(strlen(key) + 1); 596 if (!clean_key) 597 goto finish; 598 prev_slash = key; 599 while (next_slash) { 600 memcpy(clean_key + sofar, prev_slash, next_slash - prev_slash); 601 sofar += next_slash - prev_slash; 602 prev_slash = next_slash + 1; 603 next_slash = strstr(prev_slash, "//"); 604 } 605 strcpy(clean_key + sofar, prev_slash); 606 key = clean_key; 607 } 608 609 buf = key; 610 file_stem = find_stem_from_file(data, &buf); 611 mode &= S_IFMT; 612 613 /* 614 * Check for matching specifications in reverse order, so that 615 * the last matching specification is used. 616 */ 617 for (i = data->nspec - 1; i >= 0; i--) { 618 /* if the spec in question matches no stem or has the same 619 * stem as the file AND if the spec in question has no mode 620 * specified or if the mode matches the file mode then we do 621 * a regex check */ 622 if ((spec_arr[i].stem_id == -1 623 || spec_arr[i].stem_id == file_stem) 624 && (!mode || !spec_arr[i].mode 625 || mode == spec_arr[i].mode)) { 626 if (compile_regex(data, &spec_arr[i], NULL) < 0) 627 goto finish; 628 if (spec_arr[i].stem_id == -1) 629 rc = regexec(&spec_arr[i].regex, key, 0, 0, 0); 630 else 631 rc = regexec(&spec_arr[i].regex, buf, 0, 0, 0); 632 633 if (rc == 0) { 634 spec_arr[i].matches++; 635 break; 636 } 637 638 if (partial) { 639 /* 640 * We already checked above to see if the 641 * key has any direct match. Now we just need 642 * to check for partial matches. 643 * Since POSIX regex functions do not support 644 * partial match, we crudely approximate it 645 * via a prefix match. 646 * This is imprecise and could yield 647 * false positives or negatives but 648 * appears to work with our current set of 649 * regex strings. 650 * Convert to using pcre partial match 651 * if/when pcre becomes available in Android. 652 */ 653 if (spec_arr[i].prefix_len > 1 && 654 !strncmp(key, spec_arr[i].regex_str, 655 keylen < spec_arr[i].prefix_len ? 656 keylen : spec_arr[i].prefix_len)) 657 break; 658 } 659 660 if (rc == REG_NOMATCH) 661 continue; 662 /* else it's an error */ 663 goto finish; 664 } 665 } 666 667 if (i < 0 || strcmp(spec_arr[i].lr.ctx_raw, "<<none>>") == 0) { 668 /* No matching specification. */ 669 errno = ENOENT; 670 goto finish; 671 } 672 673 ret = &spec_arr[i]; 674 675 finish: 676 free(clean_key); 677 return ret; 678 } 679 680 static struct selabel_lookup_rec *lookup(struct selabel_handle *rec, 681 const char *key, int type) 682 { 683 spec_t *spec; 684 spec = lookup_common(rec, key, type, false); 685 if (spec) 686 return &spec->lr; 687 return NULL; 688 } 689 690 static bool partial_match(struct selabel_handle *rec, const char *key) 691 { 692 return lookup_common(rec, key, 0, true) ? true : false; 693 } 694 695 static struct selabel_lookup_rec *lookup_best_match(struct selabel_handle *rec, 696 const char *key, 697 const char **aliases, 698 int type) 699 { 700 size_t n, i; 701 int best = -1; 702 spec_t **specs; 703 size_t prefix_len = 0; 704 struct selabel_lookup_rec *lr = NULL; 705 706 if (!aliases || !aliases[0]) 707 return lookup(rec, key, type); 708 709 for (n = 0; aliases[n]; n++) 710 ; 711 712 specs = calloc(n+1, sizeof(spec_t *)); 713 if (!specs) 714 return NULL; 715 specs[0] = lookup_common(rec, key, type, false); 716 if (specs[0]) { 717 if (!specs[0]->hasMetaChars) { 718 /* exact match on key */ 719 lr = &specs[0]->lr; 720 goto out; 721 } 722 best = 0; 723 prefix_len = specs[0]->prefix_len; 724 } 725 for (i = 1; i <= n; i++) { 726 specs[i] = lookup_common(rec, aliases[i-1], type, false); 727 if (specs[i]) { 728 if (!specs[i]->hasMetaChars) { 729 /* exact match on alias */ 730 lr = &specs[i]->lr; 731 goto out; 732 } 733 if (specs[i]->prefix_len > prefix_len) { 734 best = i; 735 prefix_len = specs[i]->prefix_len; 736 } 737 } 738 } 739 740 if (best >= 0) { 741 /* longest fixed prefix match on key or alias */ 742 lr = &specs[best]->lr; 743 } 744 745 out: 746 free(specs); 747 return lr; 748 } 749 750 static void stats(struct selabel_handle *rec) 751 { 752 struct saved_data *data = (struct saved_data *)rec->data; 753 unsigned int i, nspec = data->nspec; 754 spec_t *spec_arr = data->spec_arr; 755 756 for (i = 0; i < nspec; i++) { 757 if (spec_arr[i].matches == 0) { 758 if (spec_arr[i].type_str) { 759 selinux_log(SELINUX_WARNING, 760 "Warning! No matches for (%s, %s, %s)\n", 761 spec_arr[i].regex_str, 762 spec_arr[i].type_str, 763 spec_arr[i].lr.ctx_raw); 764 } else { 765 selinux_log(SELINUX_WARNING, 766 "Warning! No matches for (%s, %s)\n", 767 spec_arr[i].regex_str, 768 spec_arr[i].lr.ctx_raw); 769 } 770 } 771 } 772 } 773 774 int selabel_file_init(struct selabel_handle *rec, const struct selinux_opt *opts, 775 unsigned nopts) 776 { 777 struct saved_data *data; 778 779 data = (struct saved_data *)malloc(sizeof(*data)); 780 if (!data) 781 return -1; 782 memset(data, 0, sizeof(*data)); 783 784 rec->data = data; 785 rec->func_close = &closef; 786 rec->func_stats = &stats; 787 rec->func_lookup = &lookup; 788 rec->func_partial_match = &partial_match; 789 rec->func_lookup_best_match = &lookup_best_match; 790 791 return init(rec, opts, nopts); 792 } 793