1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 /* 22 * Copyright 2008 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 */ 25 /* 26 * Copyright 2014 Nexenta Systems, Inc. All rights reserved. 27 * Copyright (c) 2017, Joyent, Inc. 28 */ 29 30 /* 31 * Common code and structures used by name-service-switch "files" backends. 32 */ 33 34 /* 35 * An implementation that used mmap() sensibly would be a wonderful thing, 36 * but this here is just yer standard fgets() thang. 37 */ 38 39 #include "files_common.h" 40 #include <stdio.h> 41 #include <stdlib.h> 42 #include <string.h> 43 #include <ctype.h> 44 #include <fcntl.h> 45 #include <poll.h> 46 #include <unistd.h> 47 #include <sys/stat.h> 48 #include <sys/mman.h> 49 50 /*ARGSUSED*/ 51 nss_status_t 52 _nss_files_setent(be, dummy) 53 files_backend_ptr_t be; 54 void *dummy; 55 { 56 if (be->f == 0) { 57 if (be->filename == 0) { 58 /* Backend isn't initialized properly? */ 59 return (NSS_UNAVAIL); 60 } 61 if ((be->f = fopen(be->filename, "rF")) == 0) { 62 return (NSS_UNAVAIL); 63 } 64 } else { 65 rewind(be->f); 66 } 67 return (NSS_SUCCESS); 68 } 69 70 /*ARGSUSED*/ 71 nss_status_t 72 _nss_files_endent(be, dummy) 73 files_backend_ptr_t be; 74 void *dummy; 75 { 76 if (be->f != 0) { 77 (void) fclose(be->f); 78 be->f = 0; 79 } 80 if (be->buf != 0) { 81 free(be->buf); 82 be->buf = 0; 83 } 84 return (NSS_SUCCESS); 85 } 86 87 /* 88 * This routine reads a line, including the processing of continuation 89 * characters. It always leaves (or inserts) \n\0 at the end of the line. 90 * It returns the length of the line read, excluding the \n\0. Who's idea 91 * was this? 92 * Returns -1 on EOF. 93 * 94 * Note that since each concurrent call to _nss_files_read_line has 95 * it's own FILE pointer, we can use getc_unlocked w/o difficulties, 96 * a substantial performance win. 97 */ 98 int 99 _nss_files_read_line(f, buffer, buflen) 100 FILE *f; 101 char *buffer; 102 int buflen; 103 { 104 int linelen; /* 1st unused slot in buffer */ 105 int c; 106 107 /*CONSTCOND*/ 108 while (1) { 109 linelen = 0; 110 while (linelen < buflen - 1) { /* "- 1" saves room for \n\0 */ 111 switch (c = getc_unlocked(f)) { 112 case EOF: 113 if (linelen == 0 || 114 buffer[linelen - 1] == '\\') { 115 return (-1); 116 } else { 117 buffer[linelen ] = '\n'; 118 buffer[linelen + 1] = '\0'; 119 return (linelen); 120 } 121 case '\n': 122 if (linelen > 0 && 123 buffer[linelen - 1] == '\\') { 124 --linelen; /* remove the '\\' */ 125 } else { 126 buffer[linelen ] = '\n'; 127 buffer[linelen + 1] = '\0'; 128 return (linelen); 129 } 130 break; 131 default: 132 buffer[linelen++] = c; 133 } 134 } 135 /* Buffer overflow -- eat rest of line and loop again */ 136 /* ===> Should syslog() */ 137 do { 138 c = getc_unlocked(f); 139 if (c == EOF) { 140 return (-1); 141 } 142 } while (c != '\n'); 143 } 144 /*NOTREACHED*/ 145 } 146 147 /* 148 * used only for getgroupbymem() now. 149 */ 150 nss_status_t 151 _nss_files_do_all(be, args, filter, func) 152 files_backend_ptr_t be; 153 void *args; 154 const char *filter; 155 files_do_all_func_t func; 156 { 157 long grlen; 158 char *buffer; 159 int buflen; 160 nss_status_t res; 161 162 if (be->buf == 0) { 163 if ((grlen = sysconf(_SC_GETGR_R_SIZE_MAX)) > 0) 164 be->minbuf = grlen; 165 if ((be->buf = malloc(be->minbuf)) == 0) 166 return (NSS_UNAVAIL); 167 } 168 buffer = be->buf; 169 buflen = be->minbuf; 170 171 if ((res = _nss_files_setent(be, 0)) != NSS_SUCCESS) { 172 return (res); 173 } 174 175 res = NSS_NOTFOUND; 176 177 do { 178 int linelen; 179 180 if ((linelen = _nss_files_read_line(be->f, buffer, 181 buflen)) < 0) { 182 /* End of file */ 183 break; 184 } 185 if (filter != 0 && strstr(buffer, filter) == 0) { 186 /* 187 * Optimization: if the entry doesn't contain the 188 * filter string then it can't be the entry we want, 189 * so don't bother looking more closely at it. 190 */ 191 continue; 192 } 193 res = (*func)(buffer, linelen, args); 194 195 } while (res == NSS_NOTFOUND); 196 197 (void) _nss_files_endent(be, 0); 198 return (res); 199 } 200 201 /* 202 * Could implement this as an iterator function on top of _nss_files_do_all(), 203 * but the shared code is small enough that it'd be pretty silly. 204 */ 205 nss_status_t 206 _nss_files_XY_all(be, args, netdb, filter, check) 207 files_backend_ptr_t be; 208 nss_XbyY_args_t *args; 209 int netdb; /* whether it uses netdb */ 210 /* format or not */ 211 const char *filter; /* advisory, to speed up */ 212 /* string search */ 213 files_XY_check_func check; /* NULL means one-shot, for getXXent */ 214 { 215 char *r; 216 nss_status_t res; 217 int parsestat; 218 int (*func)(); 219 220 if (filter != NULL && *filter == '\0') 221 return (NSS_NOTFOUND); 222 if (be->buf == 0 || (be->minbuf < args->buf.buflen)) { 223 if (be->minbuf < args->buf.buflen) { 224 if (be->buf == 0) { 225 be->minbuf = args->buf.buflen; 226 } else if ( 227 (r = realloc(be->buf, args->buf.buflen)) != NULL) { 228 be->buf = r; 229 be->minbuf = args->buf.buflen; 230 } 231 } 232 if (be->buf == 0 && 233 (be->buf = malloc(be->minbuf)) == 0) 234 return (NSS_UNAVAIL); 235 } 236 237 if (check != 0 || be->f == 0) { 238 if ((res = _nss_files_setent(be, 0)) != NSS_SUCCESS) { 239 return (res); 240 } 241 } 242 243 res = NSS_NOTFOUND; 244 245 /*CONSTCOND*/ 246 while (1) { 247 char *instr = be->buf; 248 int linelen; 249 250 if ((linelen = _nss_files_read_line(be->f, instr, 251 be->minbuf)) < 0) { 252 /* End of file */ 253 args->returnval = 0; 254 args->returnlen = 0; 255 break; 256 } 257 if (filter != 0 && strstr(instr, filter) == 0) { 258 /* 259 * Optimization: if the entry doesn't contain the 260 * filter string then it can't be the entry we want, 261 * so don't bother looking more closely at it. 262 */ 263 continue; 264 } 265 if (netdb) { 266 char *first; 267 char *last; 268 269 if ((last = strchr(instr, '#')) == 0) { 270 last = instr + linelen; 271 } 272 *last-- = '\0'; /* Nuke '\n' or #comment */ 273 274 /* 275 * Skip leading whitespace. Normally there isn't 276 * any, so it's not worth calling strspn(). 277 */ 278 for (first = instr; isspace(*first); first++) { 279 ; 280 } 281 if (*first == '\0') { 282 continue; 283 } 284 /* 285 * Found something non-blank on the line. Skip back 286 * over any trailing whitespace; since we know 287 * there's non-whitespace earlier in the line, 288 * checking for termination is easy. 289 */ 290 while (isspace(*last)) { 291 --last; 292 } 293 294 linelen = last - first + 1; 295 if (first != instr) { 296 instr = first; 297 } 298 } 299 300 args->returnval = 0; 301 args->returnlen = 0; 302 303 if (check != NULL && (*check)(args, instr, linelen) == 0) 304 continue; 305 306 parsestat = NSS_STR_PARSE_SUCCESS; 307 if (be->filename != NULL) { 308 /* 309 * Special case for passwd and group wherein we 310 * replace uids/gids > MAXUID by ID_NOBODY 311 * because files backend does not support 312 * ephemeral ids. 313 */ 314 if (strcmp(be->filename, PF_PATH) == 0) 315 parsestat = validate_passwd_ids(instr, 316 &linelen, be->minbuf, 2); 317 else if (strcmp(be->filename, GF_PATH) == 0) 318 parsestat = validate_group_ids(instr, 319 &linelen, be->minbuf, 2, check); 320 } 321 322 if (parsestat == NSS_STR_PARSE_SUCCESS) { 323 func = args->str2ent; 324 parsestat = (*func)(instr, linelen, args->buf.result, 325 args->buf.buffer, args->buf.buflen); 326 } 327 328 if (parsestat == NSS_STR_PARSE_SUCCESS) { 329 args->returnval = (args->buf.result != NULL)? 330 args->buf.result : args->buf.buffer; 331 args->returnlen = linelen; 332 res = NSS_SUCCESS; 333 break; 334 } else if (parsestat == NSS_STR_PARSE_ERANGE) { 335 args->erange = 1; 336 break; 337 } /* else if (parsestat == NSS_STR_PARSE_PARSE) don't care ! */ 338 } 339 340 /* 341 * stayopen is set to 0 by default in order to close the opened 342 * file. Some applications may break if it is set to 1. 343 */ 344 if (check != 0 && !args->stayopen) { 345 (void) _nss_files_endent(be, 0); 346 } 347 348 return (res); 349 } 350 351 /* 352 * File hashing support. Critical for sites with large (e.g. 1000+ lines) 353 * /etc/passwd or /etc/group files. Currently only used by getpw*() and 354 * getgr*() routines, but any files backend can use this stuff. 355 */ 356 static void 357 _nss_files_hash_destroy(files_hash_t *fhp) 358 { 359 free(fhp->fh_table); 360 fhp->fh_table = NULL; 361 free(fhp->fh_line); 362 fhp->fh_line = NULL; 363 free(fhp->fh_file_start); 364 fhp->fh_file_start = NULL; 365 } 366 #ifdef PIC 367 /* 368 * It turns out the hashing stuff really needs to be disabled for processes 369 * other than the nscd; the consumption of swap space and memory is otherwise 370 * unacceptable when the nscd is killed w/ a large passwd file (4M) active. 371 * See 4031930 for details. 372 * So we just use this psuedo function to enable the hashing feature. Since 373 * this function name is private, we just create a function w/ the name 374 * __nss_use_files_hash in the nscd itself and everyone else uses the old 375 * interface. 376 * We also disable hashing for .a executables to avoid problems with large 377 * files.... 378 */ 379 380 #pragma weak __nss_use_files_hash 381 382 extern void __nss_use_files_hash(void); 383 #endif /* pic */ 384 385 /*ARGSUSED*/ 386 nss_status_t 387 _nss_files_XY_hash(files_backend_ptr_t be, nss_XbyY_args_t *args, 388 int netdb, files_hash_t *fhp, int hashop, files_XY_check_func check) 389 { 390 /* LINTED E_FUNC_VAR_UNUSED */ 391 int fd, retries, ht, stat; 392 /* LINTED E_FUNC_VAR_UNUSED */ 393 uint_t hash, line, f; 394 /* LINTED E_FUNC_VAR_UNUSED */ 395 files_hashent_t *hp, *htab; 396 /* LINTED E_FUNC_VAR_UNUSED */ 397 char *cp, *first, *last; 398 /* LINTED E_FUNC_VAR_UNUSED */ 399 nss_XbyY_args_t xargs; 400 /* LINTED E_FUNC_VAR_UNUSED */ 401 struct stat64 st; 402 403 #ifndef PIC 404 return (_nss_files_XY_all(be, args, netdb, 0, check)); 405 } 406 #else 407 if (__nss_use_files_hash == 0) 408 return (_nss_files_XY_all(be, args, netdb, 0, check)); 409 410 mutex_lock(&fhp->fh_lock); 411 retry: 412 retries = 100; 413 while (stat64(be->filename, &st) < 0) { 414 /* 415 * This can happen only in two cases: Either the file is 416 * completely missing and we were not able to read it yet 417 * (fh_table is NULL), or there is some brief period when the 418 * file is being modified/renamed. Keep trying until things 419 * settle down, but eventually give up. 420 */ 421 if (fhp->fh_table == NULL || --retries == 0) 422 goto unavail; 423 poll(0, 0, 100); 424 } 425 426 if (st.st_mtim.tv_sec == fhp->fh_mtime.tv_sec && 427 st.st_mtim.tv_nsec == fhp->fh_mtime.tv_nsec && 428 fhp->fh_table != NULL) { 429 htab = &fhp->fh_table[hashop * fhp->fh_size]; 430 hash = fhp->fh_hash_func[hashop](args, 1, NULL, 0); 431 for (hp = htab[hash % fhp->fh_size].h_first; hp != NULL; 432 hp = hp->h_next) { 433 if (hp->h_hash != hash) 434 continue; 435 line = hp - htab; 436 if ((*check)(args, fhp->fh_line[line].l_start, 437 fhp->fh_line[line].l_len) == 0) 438 continue; 439 440 if (be->filename != NULL) { 441 stat = NSS_STR_PARSE_SUCCESS; 442 if (strcmp(be->filename, PF_PATH) == 0) 443 stat = validate_passwd_ids( 444 fhp->fh_line[line].l_start, 445 &fhp->fh_line[line].l_len, 446 fhp->fh_line[line].l_len + 1, 447 1); 448 else if (strcmp(be->filename, GF_PATH) == 0) 449 stat = validate_group_ids( 450 fhp->fh_line[line].l_start, 451 &fhp->fh_line[line].l_len, 452 fhp->fh_line[line].l_len + 1, 453 1, check); 454 if (stat != NSS_STR_PARSE_SUCCESS) { 455 if (stat == NSS_STR_PARSE_ERANGE) 456 args->erange = 1; 457 continue; 458 } 459 } 460 461 if ((*args->str2ent)(fhp->fh_line[line].l_start, 462 fhp->fh_line[line].l_len, args->buf.result, 463 args->buf.buffer, args->buf.buflen) == 464 NSS_STR_PARSE_SUCCESS) { 465 args->returnval = (args->buf.result)? 466 args->buf.result:args->buf.buffer; 467 args->returnlen = fhp->fh_line[line].l_len; 468 mutex_unlock(&fhp->fh_lock); 469 return (NSS_SUCCESS); 470 } else { 471 args->erange = 1; 472 } 473 } 474 args->returnval = 0; 475 args->returnlen = 0; 476 mutex_unlock(&fhp->fh_lock); 477 return (NSS_NOTFOUND); 478 } 479 480 _nss_files_hash_destroy(fhp); 481 482 if (st.st_size > SSIZE_MAX) 483 goto unavail; 484 485 if ((fhp->fh_file_start = malloc((ssize_t)st.st_size + 1)) == NULL) 486 goto unavail; 487 488 if ((fd = open(be->filename, O_RDONLY)) < 0) 489 goto unavail; 490 491 if (read(fd, fhp->fh_file_start, (ssize_t)st.st_size) != 492 (ssize_t)st.st_size) { 493 close(fd); 494 goto retry; 495 } 496 497 close(fd); 498 499 fhp->fh_file_end = fhp->fh_file_start + (off_t)st.st_size; 500 *fhp->fh_file_end = '\n'; 501 fhp->fh_mtime = st.st_mtim; 502 503 /* 504 * If the file changed since we read it, or if it's less than 505 * 1-2 seconds old, don't trust it; its modification may still 506 * be in progress. The latter is a heuristic hack to minimize 507 * the likelihood of damage if someone modifies /etc/mumble 508 * directly (as opposed to editing and renaming a temp file). 509 * 510 * Note: the cast to u_int is there in case (1) someone rdated 511 * the system backwards since the last modification of /etc/mumble 512 * or (2) this is a diskless client whose time is badly out of sync 513 * with its server. The 1-2 second age hack doesn't cover these 514 * cases -- oh well. 515 */ 516 if (stat64(be->filename, &st) < 0 || 517 st.st_mtim.tv_sec != fhp->fh_mtime.tv_sec || 518 st.st_mtim.tv_nsec != fhp->fh_mtime.tv_nsec || 519 (uint_t)(time(0) - st.st_mtim.tv_sec + 2) < 4) { 520 poll(0, 0, 1000); 521 goto retry; 522 } 523 524 line = 1; 525 for (cp = fhp->fh_file_start; cp < fhp->fh_file_end; cp++) 526 if (*cp == '\n') 527 line++; 528 529 for (f = 2; f * f <= line; f++) { /* find next largest prime */ 530 if (line % f == 0) { 531 f = 1; 532 line++; 533 } 534 } 535 536 fhp->fh_size = line; 537 fhp->fh_line = malloc(line * sizeof (files_linetab_t)); 538 fhp->fh_table = calloc(line * fhp->fh_nhtab, sizeof (files_hashent_t)); 539 if (fhp->fh_line == NULL || fhp->fh_table == NULL) 540 goto unavail; 541 542 line = 0; 543 cp = fhp->fh_file_start; 544 while (cp < fhp->fh_file_end) { 545 first = cp; 546 while (*cp != '\n') 547 cp++; 548 if (cp > first && *(cp - 1) == '\\') { 549 memmove(first + 2, first, cp - first - 1); 550 cp = first + 2; 551 continue; 552 } 553 last = cp; 554 *cp++ = '\0'; 555 if (netdb) { 556 if ((last = strchr(first, '#')) == 0) 557 last = cp - 1; 558 *last-- = '\0'; /* nuke '\n' or #comment */ 559 while (isspace(*first)) /* nuke leading whitespace */ 560 first++; 561 if (*first == '\0') /* skip content-free lines */ 562 continue; 563 while (isspace(*last)) /* nuke trailing whitespace */ 564 --last; 565 *++last = '\0'; 566 } 567 for (ht = 0; ht < fhp->fh_nhtab; ht++) { 568 hp = &fhp->fh_table[ht * fhp->fh_size + line]; 569 hp->h_hash = fhp->fh_hash_func[ht](&xargs, 0, first, 570 last - first); 571 } 572 fhp->fh_line[line].l_start = first; 573 fhp->fh_line[line++].l_len = last - first; 574 } 575 576 /* 577 * Populate the hash tables in reverse order so that the hash chains 578 * end up in forward order. This ensures that hashed lookups find 579 * things in the same order that a linear search of the file would. 580 * This is essential in cases where there could be multiple matches. 581 * For example: until 2.7, root and smtp both had uid 0; but we 582 * certainly wouldn't want getpwuid(0) to return smtp. 583 */ 584 for (ht = 0; ht < fhp->fh_nhtab; ht++) { 585 htab = &fhp->fh_table[ht * fhp->fh_size]; 586 for (hp = &htab[line - 1]; hp >= htab; hp--) { 587 uint_t bucket = hp->h_hash % fhp->fh_size; 588 hp->h_next = htab[bucket].h_first; 589 htab[bucket].h_first = hp; 590 } 591 } 592 593 goto retry; 594 595 unavail: 596 _nss_files_hash_destroy(fhp); 597 mutex_unlock(&fhp->fh_lock); 598 return (NSS_UNAVAIL); 599 } 600 #endif /* PIC */ 601 602 nss_status_t 603 _nss_files_getent_rigid(be, a) 604 files_backend_ptr_t be; 605 void *a; 606 { 607 nss_XbyY_args_t *args = (nss_XbyY_args_t *)a; 608 609 return (_nss_files_XY_all(be, args, 0, 0, 0)); 610 } 611 612 nss_status_t 613 _nss_files_getent_netdb(be, a) 614 files_backend_ptr_t be; 615 void *a; 616 { 617 nss_XbyY_args_t *args = (nss_XbyY_args_t *)a; 618 619 return (_nss_files_XY_all(be, args, 1, 0, 0)); 620 } 621 622 /*ARGSUSED*/ 623 nss_status_t 624 _nss_files_destr(be, dummy) 625 files_backend_ptr_t be; 626 void *dummy; 627 { 628 if (be != 0) { 629 if (be->f != 0) { 630 (void) _nss_files_endent(be, 0); 631 } 632 if (be->hashinfo != NULL) { 633 (void) mutex_lock(&be->hashinfo->fh_lock); 634 if (--be->hashinfo->fh_refcnt == 0) 635 _nss_files_hash_destroy(be->hashinfo); 636 (void) mutex_unlock(&be->hashinfo->fh_lock); 637 } 638 free(be); 639 } 640 return (NSS_SUCCESS); /* In case anyone is dumb enough to check */ 641 } 642 643 nss_backend_t * 644 _nss_files_constr(ops, n_ops, filename, min_bufsize, fhp) 645 files_backend_op_t ops[]; 646 int n_ops; 647 const char *filename; 648 int min_bufsize; 649 files_hash_t *fhp; 650 { 651 files_backend_ptr_t be; 652 653 if ((be = (files_backend_ptr_t)malloc(sizeof (*be))) == 0) { 654 return (0); 655 } 656 be->ops = ops; 657 be->n_ops = n_ops; 658 be->filename = filename; 659 be->minbuf = min_bufsize; 660 be->f = 0; 661 be->buf = 0; 662 be->hashinfo = fhp; 663 664 if (fhp != NULL) { 665 (void) mutex_lock(&fhp->fh_lock); 666 fhp->fh_refcnt++; 667 (void) mutex_unlock(&fhp->fh_lock); 668 } 669 670 return ((nss_backend_t *)be); 671 } 672 673 int 674 _nss_files_check_name_colon(nss_XbyY_args_t *argp, const char *line, 675 int linelen) 676 { 677 const char *linep, *limit; 678 const char *keyp = argp->key.name; 679 680 linep = line; 681 limit = line + linelen; 682 while (*keyp && linep < limit && *keyp == *linep) { 683 keyp++; 684 linep++; 685 } 686 return (linep < limit && *keyp == '\0' && *linep == ':'); 687 } 688 689 /* 690 * This routine is used to parse lines of the form: 691 * name number aliases 692 * It returns 1 if the key in argp matches any one of the 693 * names in the line, otherwise 0 694 * Used by rpc, networks, protocols 695 */ 696 int 697 _nss_files_check_name_aliases(nss_XbyY_args_t *argp, const char *line, 698 int linelen) 699 { 700 const char *limit, *linep, *keyp; 701 702 linep = line; 703 limit = line + linelen; 704 keyp = argp->key.name; 705 706 /* compare name */ 707 while (*keyp && linep < limit && !isspace(*linep) && *keyp == *linep) { 708 keyp++; 709 linep++; 710 } 711 if (*keyp == '\0' && linep < limit && isspace(*linep)) 712 return (1); 713 /* skip remainder of the name, if any */ 714 while (linep < limit && !isspace(*linep)) 715 linep++; 716 /* skip the delimiting spaces */ 717 while (linep < limit && isspace(*linep)) 718 linep++; 719 /* compare with the aliases */ 720 while (linep < limit) { 721 /* 722 * 1st pass: skip number 723 * Other passes: skip remainder of the alias name, if any 724 */ 725 while (linep < limit && !isspace(*linep)) 726 linep++; 727 /* skip the delimiting spaces */ 728 while (linep < limit && isspace(*linep)) 729 linep++; 730 /* compare with the alias name */ 731 keyp = argp->key.name; 732 while (*keyp && linep < limit && !isspace(*linep) && 733 *keyp == *linep) { 734 keyp++; 735 linep++; 736 } 737 if (*keyp == '\0' && (linep == limit || isspace(*linep))) 738 return (1); 739 } 740 return (0); 741 } 742 743 /* 744 * A few NSS modules hold onto data for the duration of their module. In this 745 * case, when that module goes away, we must free that data. This is a place 746 * that allows for modules to register items to take care of. 747 */ 748 #pragma fini(_nss_files_fini) 749 static void 750 _nss_files_fini(void) 751 { 752 getexecattr_fini(); 753 } 754