1 /* 2 * This file and its contents are supplied under the terms of the 3 * Common Development and Distribution License ("CDDL"), version 1.0. 4 * You may only use this file in accordance with the terms of version 5 * 1.0 of the CDDL. 6 * 7 * A full copy of the text of the CDDL should have accompanied this 8 * source. A copy of the CDDL is also available via the Internet at 9 * http://www.illumos.org/license/CDDL. 10 */ 11 12 /* 13 * Copyright 2020 Joyent, Inc. 14 * Copyright 2022 Jason King 15 */ 16 17 #include <sys/debug.h> 18 #include <sys/types.h> 19 #include <sys/stat.h> 20 #include <sys/avl.h> 21 #include <sys/fcntl.h> 22 #include <sys/sysmacros.h> 23 #include <dirent.h> 24 #include <err.h> 25 #include <errno.h> 26 #include <fcntl.h> 27 #include <gelf.h> 28 #include <libcustr.h> 29 #include <libelf.h> 30 #include <libgen.h> 31 #include <limits.h> 32 #include <stdbool.h> 33 #include <stddef.h> 34 #include <stdio.h> 35 #include <stdlib.h> 36 #include <string.h> 37 #include <unistd.h> 38 39 static inline bool 40 is_same(const struct stat *s1, const struct stat *s2) 41 { 42 if ((s1->st_ino == s2->st_ino) && (s1->st_dev == s2->st_dev)) 43 return (true); 44 return (false); 45 } 46 47 typedef enum dir_flags { 48 DF_NONE = 0, 49 DF_IS_SELF = 1 << 0, 50 DF_IS_SYMLINK = 1 << 2, 51 } dir_flags_t; 52 53 typedef struct path { 54 custr_t *p_name; 55 size_t p_pfxidx; 56 } path_t; 57 58 typedef struct name { 59 char *n_name; 60 bool n_is_symlink; 61 } name_t; 62 63 typedef struct names { 64 name_t *ns_names; 65 uint_t ns_num; 66 uint_t ns_alloc; 67 } names_t; 68 69 typedef struct elfinfo { 70 int ei_class; 71 uint16_t ei_type; 72 bool ei_hasverdef; 73 } elfinfo_t; 74 75 typedef struct obj { 76 avl_node_t obj_avlid; 77 avl_node_t obj_avlname; 78 dev_t obj_dev; 79 ino_t obj_inode; 80 names_t obj_names; 81 elfinfo_t obj_elfinfo; 82 } obj_t; 83 84 static void path_init(path_t *, const char *, bool); 85 static size_t path_append(path_t *, const char *); 86 static const char *path_name(const path_t *); 87 static const char *path_fullpath(const path_t *); 88 static void path_pop(path_t *, size_t); 89 90 static bool maybe_obj(const char *, mode_t); 91 static bool get_elfinfo(const path_t *, int, elfinfo_t *); 92 static void add_name(obj_t *, const path_t *, bool); 93 static int cmp_id(const void *, const void *); 94 static int cmp_objname(const void *, const void *); 95 static int cmp_names(const void *, const void *); 96 97 static void process_dir(path_t *, int, const struct stat *, dir_flags_t); 98 static void process_file(path_t *, int, const struct stat *, bool); 99 static void process_arg(char *); 100 101 static void sort_names(void *, void *); 102 static void print_entry(void *, void *); 103 104 static void foreach_avl(avl_tree_t *, void (*)(void *, void *), void *); 105 106 static void nomem(void); 107 static char *xstrdup(const char *); 108 static void *xcalloc(size_t, size_t); 109 110 static avl_tree_t avl_byid; 111 static avl_tree_t avl_byname; 112 113 static const char *special_paths[] = { 114 "usr/bin/alias", 115 "usr/lib/isaexec", 116 }; 117 118 static int rootfd = -1; 119 120 /* By default, we process aliases */ 121 static bool do_alias = true; 122 123 /* By default, we treat certain well known names (e.g. isaexec) as special */ 124 static bool do_special = true; 125 126 /* fast_mode, relpath, and so_only are all false by default */ 127 static bool fast_mode; 128 static bool relpath; 129 static bool so_only; 130 131 static void __NORETURN 132 usage(const char *name) 133 { 134 (void) fprintf(stderr, 135 "Usage: %s [-afnrs] file | dir\n" 136 "\t[-a]\texpand symlink aliases\n" 137 "\t[-f]\tuse file name at mode to speed search\n" 138 "\t[-h]\tshow this help\n" 139 "\t[-n]\tdon\'t treat well known paths as special in sorting\n" 140 "\t[-r]\treport relative paths\n" 141 "\t[-s]\tonly remote shareable (ET_DYN) objects\n", name); 142 exit(EXIT_FAILURE); 143 } 144 145 int 146 main(int argc, char **argv) 147 { 148 int c; 149 150 if (elf_version(EV_CURRENT) == EV_NONE) 151 errx(EXIT_FAILURE, "elf library is out of date"); 152 153 while ((c = getopt(argc, argv, "ahfnrs")) != -1) { 154 switch (c) { 155 case 'a': 156 do_alias = false; 157 break; 158 case 'f': 159 fast_mode = true; 160 break; 161 case 'n': 162 do_special = false; 163 break; 164 case 'r': 165 relpath = true; 166 break; 167 case 's': 168 so_only = true; 169 break; 170 case 'h': 171 usage(argv[0]); 172 case '?': 173 (void) fprintf(stderr, "Unknown option -%c\n", optopt); 174 usage(argv[0]); 175 } 176 } 177 178 if (optind == argc) { 179 (void) fprintf(stderr, "Missing file or dir parameter\n"); 180 usage(argv[0]); 181 } 182 183 if (argv[optind][0] == '\0') 184 errx(EXIT_FAILURE, "Invalid file or dir value"); 185 186 avl_create(&avl_byid, cmp_id, sizeof (obj_t), 187 offsetof(obj_t, obj_avlid)); 188 avl_create(&avl_byname, cmp_objname, sizeof (obj_t), 189 offsetof(obj_t, obj_avlname)); 190 191 process_arg(argv[optind]); 192 foreach_avl(&avl_byid, sort_names, &avl_byname); 193 foreach_avl(&avl_byname, print_entry, NULL); 194 195 return (EXIT_SUCCESS); 196 } 197 198 static void 199 process_arg(char *arg) 200 { 201 path_t path; 202 struct stat sb; 203 int fd; 204 205 if ((fd = open(arg, O_RDONLY)) == -1) { 206 err(EXIT_FAILURE, "could not open %s", arg); 207 } 208 209 if (fstat(fd, &sb) < 0) { 210 err(EXIT_FAILURE, "failed to stat %s", arg); 211 } 212 213 if (S_ISDIR(sb.st_mode)) { 214 path_init(&path, arg, relpath); 215 if (relpath) { 216 (void) printf("PREFIX %s\n", arg); 217 } 218 219 rootfd = fd; 220 /* process_dir() will close fd */ 221 process_dir(&path, fd, &sb, DF_NONE); 222 return; 223 } 224 225 char *argcopy = xstrdup(arg); 226 char *dir = dirname(argcopy); 227 228 if (!S_ISREG(sb.st_mode)) { 229 err(EXIT_FAILURE, "not a file or directory: %s", arg); 230 } 231 232 rootfd = open(dir, O_RDONLY|O_DIRECTORY); 233 if (rootfd < 0) { 234 err(EXIT_FAILURE, "%s", dir); 235 } 236 237 path_init(&path, dir, relpath); 238 if (relpath) { 239 (void) printf("PREFIX %s\n", dir); 240 } 241 free(argcopy); 242 243 process_file(&path, fd, &sb, DF_NONE); 244 } 245 246 /* 247 * Processing a directory has some subtleties. When we encounter a 248 * subdirectory that's a symlink, we want any files we encounter when 249 * we traverse it to be treated as aliases. We may also have a symlink that's 250 * a link back to ourself (e.g. 32 -> .). In this special case, we still want 251 * to reprocess the directory to generate alias names, but we use the 252 * DFLAG_SELF flag to avoid recursing forever. 253 * 254 * A limitation of this approach is that we cannot handle a loop over multiple 255 * levels (e.g. foo -> ../..). Nothing currently in illumos-gate creates any 256 * such symlinks in the proto area, so we've opted to avoid complicating 257 * processing further to handle scenarios that aren't realistically expected 258 * to occur. 259 * 260 * Note that dirfd is always closed upon return from process_dir(). 261 */ 262 static void 263 process_dir(path_t *p, int dirfd, const struct stat *dirsb, dir_flags_t dflags) 264 { 265 DIR *d; 266 struct dirent *de; 267 268 d = fdopendir(dirfd); 269 if (d == NULL) { 270 warn("opendir(%s)", path_fullpath(p)); 271 VERIFY0(close(dirfd)); 272 return; 273 } 274 275 while ((de = readdir(d)) != NULL) { 276 struct stat sb; 277 int fd; 278 bool is_link = false; 279 size_t plen; 280 281 if (strcmp(de->d_name, ".") == 0 || 282 strcmp(de->d_name, "..") == 0) { 283 continue; 284 } 285 286 plen = path_append(p, de->d_name); 287 288 if (fstatat(rootfd, path_name(p), &sb, 289 AT_SYMLINK_NOFOLLOW) < 0) { 290 warn("%s", path_fullpath(p)); 291 path_pop(p, plen); 292 continue; 293 } 294 295 fd = openat(dirfd, de->d_name, O_RDONLY); 296 if (fd < 0) { 297 /* 298 * Symlinks in the proto area may point to a path 299 * that's not present (e.g. /dev/stdin -> fd/0). 300 * Silently skip such entries. 301 */ 302 if (errno != ENOENT || !S_ISLNK(sb.st_mode)) { 303 warn("%s", path_fullpath(p)); 304 } 305 path_pop(p, plen); 306 continue; 307 } 308 309 if (S_ISLNK(sb.st_mode)) { 310 is_link = true; 311 312 if (fstat(fd, &sb) < 0) { 313 warn("stat %s", path_fullpath(p)); 314 path_pop(p, plen); 315 continue; 316 } 317 } 318 319 if (S_ISDIR(sb.st_mode)) { 320 if ((dflags & DF_IS_SELF) != 0) { 321 VERIFY0(close(fd)); 322 path_pop(p, plen); 323 continue; 324 } 325 326 dir_flags_t newflags = dflags; 327 328 /* The recursive process_dir() call closes fd */ 329 if (is_link) 330 newflags |= DF_IS_SYMLINK; 331 if (is_same(&sb, dirsb)) 332 newflags |= DF_IS_SELF; 333 process_dir(p, fd, &sb, newflags); 334 } else if (S_ISREG(sb.st_mode)) { 335 if (!maybe_obj(de->d_name, sb.st_mode)) { 336 VERIFY0(close(fd)); 337 path_pop(p, plen); 338 continue; 339 } 340 341 if ((dflags & (DF_IS_SELF | DF_IS_SYMLINK)) != 0) 342 is_link = true; 343 344 /* process_file() closes fd */ 345 process_file(p, fd, &sb, is_link); 346 } 347 348 path_pop(p, plen); 349 } 350 351 /* Closes dirfd */ 352 VERIFY0(closedir(d)); 353 } 354 355 /* Note that fd is always closed upon return */ 356 static void 357 process_file(path_t *p, int fd, const struct stat *sb, bool is_link) 358 { 359 avl_index_t where = { 0 }; 360 obj_t templ = { 361 .obj_dev = sb->st_dev, 362 .obj_inode = sb->st_ino, 363 }; 364 obj_t *obj = avl_find(&avl_byid, &templ, &where); 365 elfinfo_t elfinfo = { 0 }; 366 367 if (obj != NULL) 368 goto done; 369 370 if (!get_elfinfo(p, fd, &elfinfo)) { 371 VERIFY0(close(fd)); 372 return; 373 } 374 375 obj = xcalloc(1, sizeof (*obj)); 376 obj->obj_dev = sb->st_dev; 377 obj->obj_inode = sb->st_ino; 378 obj->obj_elfinfo = elfinfo; 379 avl_add(&avl_byid, obj); 380 381 done: 382 add_name(obj, p, is_link); 383 VERIFY0(close(fd)); 384 } 385 386 static void 387 print_entry(void *a, void *arg __unused) 388 { 389 obj_t *obj = a; 390 const char *objname = obj->obj_names.ns_names[0].n_name; 391 const char *bits = ""; 392 const char *type = ""; 393 const char *verdef = obj->obj_elfinfo.ei_hasverdef ? 394 "VERDEF" : "NOVERDEF"; 395 396 switch (obj->obj_elfinfo.ei_class) { 397 case ELFCLASS32: 398 bits = "32"; 399 break; 400 case ELFCLASS64: 401 bits = "64"; 402 break; 403 default: 404 errx(EXIT_FAILURE, "unknown elfclass value %x for %s", 405 obj->obj_elfinfo.ei_class, objname); 406 } 407 408 switch (obj->obj_elfinfo.ei_type) { 409 case ET_REL: 410 type = "REL"; 411 break; 412 case ET_DYN: 413 type = "DYN"; 414 break; 415 case ET_EXEC: 416 type = "EXEC"; 417 break; 418 default: 419 errx(EXIT_FAILURE, "unexpected elf type %x for %s", 420 obj->obj_elfinfo.ei_type, objname); 421 } 422 423 names_t *names = &obj->obj_names; 424 425 VERIFY3U(names->ns_num, >, 0); 426 VERIFY(!names->ns_names[0].n_is_symlink); 427 428 (void) printf("OBJECT %2s %-4s %-8s %s\n", bits, type, verdef, 429 objname); 430 431 for (uint_t i = 1; i < names->ns_num; i++) { 432 if (do_alias) { 433 (void) printf("%-23s %s\t%s\n", 434 "ALIAS", objname, names->ns_names[i].n_name); 435 } else { 436 (void) printf("OBJECT %2s %-4s %-8s %s\n", bits, type, 437 verdef, names->ns_names[i].n_name); 438 } 439 } 440 } 441 442 /* 443 * Returns true and eip populated if name was an elf object, otherwise 444 * returns false. 445 */ 446 static bool 447 get_elfinfo(const path_t *p, int fd, elfinfo_t *eip) 448 { 449 Elf *elf = NULL; 450 Elf_Scn *scn = NULL; 451 GElf_Ehdr ehdr = { 0 }; 452 int eval; 453 454 if ((elf = elf_begin(fd, ELF_C_READ, NULL)) == NULL) 455 goto fail_noend; 456 457 if ((eip->ei_class = gelf_getclass(elf)) == ELFCLASSNONE) { 458 VERIFY0(elf_end(elf)); 459 return (false); 460 } 461 462 if (gelf_getehdr(elf, &ehdr) == NULL) 463 goto fail; 464 465 eip->ei_type = ehdr.e_type; 466 eip->ei_hasverdef = false; 467 468 while ((scn = elf_nextscn(elf, scn)) != NULL) { 469 Elf_Data *data = NULL; 470 GElf_Shdr shdr = { 0 }; 471 472 if (gelf_getshdr(scn, &shdr) == NULL) 473 goto fail; 474 475 if (shdr.sh_type != SHT_DYNAMIC) 476 continue; 477 478 if ((data = elf_getdata(scn, NULL)) == NULL) 479 continue; 480 481 size_t nent = shdr.sh_size / shdr.sh_entsize; 482 483 for (size_t i = 0; i < nent; i++) { 484 GElf_Dyn dyn = { 0 }; 485 486 if (gelf_getdyn(data, i, &dyn) == NULL) { 487 goto fail; 488 } 489 490 if (dyn.d_tag == DT_VERDEF) { 491 eip->ei_hasverdef = true; 492 break; 493 } 494 } 495 } 496 497 VERIFY0(elf_end(elf)); 498 return (true); 499 500 fail: 501 VERIFY0(elf_end(elf)); 502 503 fail_noend: 504 eval = elf_errno(); 505 506 warnx("%s: %s", path_fullpath(p), elf_errmsg(eval)); 507 return (false); 508 } 509 510 static bool 511 is_special(const char *name) 512 { 513 for (uint_t i = 0; i < ARRAY_SIZE(special_paths); i++) { 514 if (strcmp(special_paths[i], name) == 0) 515 return (true); 516 } 517 return (false); 518 } 519 520 static void 521 sort_names(void *a, void *b) 522 { 523 obj_t *obj = a; 524 avl_tree_t *name_avl = b; 525 names_t *names = &obj->obj_names; 526 527 /* We should always have at least one name */ 528 VERIFY3U(names->ns_num, >, 0); 529 530 /* If there is only one, they get the prize and we're done */ 531 if (names->ns_num == 1) 532 return; 533 534 name_t *first = NULL; 535 536 /* 537 * Find the first (in sorted order) pathname for this object 538 * that isn't a symlink. 539 */ 540 for (uint_t i = 0; i < names->ns_num; i++) { 541 name_t *n = &names->ns_names[i]; 542 543 if (n->n_is_symlink) 544 continue; 545 546 if (first == NULL) { 547 first = n; 548 continue; 549 } 550 551 /* 552 * If we're treating isaexec as special, we always 553 * want it to be the first entry. Otherwise, pick 554 * the 'lowest' sorted pathname. 555 */ 556 if (do_special) { 557 if (is_special(n->n_name)) { 558 first = n; 559 break; 560 } 561 562 if (strcmp(n->n_name, first->n_name) < 0) 563 first = n; 564 } 565 } 566 567 /* 568 * If the first pathname (in sorted order) isn't the first 569 * name entry, we swap it into first place (while also updating 570 * the names AVL tree). 571 */ 572 if (first != NULL && first != &names->ns_names[0]) { 573 name_t tmp = names->ns_names[0]; 574 575 avl_remove(name_avl, obj); 576 (void) memcpy(&names->ns_names[0], first, sizeof (name_t)); 577 (void) memcpy(first, &tmp, sizeof (name_t)); 578 avl_add(name_avl, obj); 579 } 580 581 /* 582 * The remaining names (symlinks or not) are sorted by 583 * their pathname. 584 */ 585 qsort(&names->ns_names[1], names->ns_num - 1, sizeof (name_t), 586 cmp_names); 587 } 588 589 /* 590 * We grow the names array by NAME_CHUNK entries every time we need to 591 * extend it. 592 */ 593 #define NAME_CHUNK 4 594 595 static name_t * 596 name_new(names_t *names) 597 { 598 if (names->ns_num < names->ns_alloc) 599 return (&names->ns_names[names->ns_num++]); 600 601 name_t *newn = NULL; 602 uint_t newamt = names->ns_alloc + NAME_CHUNK; 603 604 /* 605 * Since we may be building on a machine that doesn't 606 * have reallocarray or the like, we avoid their use here. 607 * If we ever officially designate an illumos-gate build 608 * as the minimum required to build master that contains 609 * reallocarray and such, we can replace most of this logic 610 * with it. 611 * 612 * Also use xcalloc so we get the unused entries zeroed already. 613 * Not strictly necessary, but useful for debugging. 614 */ 615 newn = xcalloc(newamt, sizeof (name_t)); 616 617 /* Move over existing entries */ 618 (void) memcpy(newn, names->ns_names, names->ns_num * sizeof (name_t)); 619 620 free(names->ns_names); 621 622 names->ns_names = newn; 623 names->ns_alloc = newamt; 624 return (&names->ns_names[names->ns_num++]); 625 } 626 627 static void 628 add_name(obj_t *obj, const path_t *p, bool is_symlink) 629 { 630 names_t *ns = &obj->obj_names; 631 const char *srcname = path_name(p); 632 633 /* We should never have duplicates */ 634 for (size_t i = 0; i < ns->ns_num; i++) 635 VERIFY3S(strcmp(ns->ns_names[i].n_name, srcname), !=, 0); 636 637 name_t *n = name_new(ns); 638 639 n->n_name = xstrdup(srcname); 640 n->n_is_symlink = is_symlink; 641 642 if (is_symlink) 643 return; 644 645 /* 646 * If the name was not a symlink, first determine if there are 647 * existing (hard) links to this name already, and if so, which entry 648 * is the first one. Typically this will be the name we just added 649 * unless the file does have multiple hard links (e.g. isaexec). 650 */ 651 uint_t nhlink = 0; 652 uint_t firsthlink = UINT_MAX; 653 654 for (uint_t i = 0; i < ns->ns_num; i++) { 655 if (ns->ns_names[i].n_is_symlink) 656 continue; 657 if (nhlink == 0) 658 firsthlink = i; 659 nhlink++; 660 } 661 662 if (nhlink > 1) 663 return; 664 665 /* 666 * Put the first non-symlink name as the very first 667 * entry. 668 */ 669 VERIFY3U(firsthlink, !=, UINT_MAX); 670 671 if (firsthlink != 0) { 672 name_t tmp = { 673 .n_name = ns->ns_names[0].n_name, 674 .n_is_symlink = ns->ns_names[0].n_is_symlink, 675 }; 676 677 (void) memcpy(&ns->ns_names[0], &ns->ns_names[firsthlink], 678 sizeof (name_t)); 679 (void) memcpy(&ns->ns_names[firsthlink], &tmp, sizeof (name_t)); 680 } 681 682 avl_add(&avl_byname, obj); 683 } 684 685 /* 686 * This is an arbitrary initial value -- basically we can nest 16 directories 687 * deep before having to grow p->p_idx (which seems like a nice power of 2). 688 */ 689 #define PATH_INIT 16 690 691 static void 692 path_init(path_t *p, const char *name, bool relpath) 693 { 694 (void) memset(p, '\0', sizeof (*p)); 695 696 if (custr_alloc(&p->p_name) < 0) { 697 nomem(); 698 } 699 700 if (name != NULL && custr_append(p->p_name, name) < 0) 701 nomem(); 702 703 size_t len = custr_len(p->p_name); 704 705 /* Trim off any trailing /'s, but don't trim '/' to an empty path */ 706 while (len > 1 && custr_cstr(p->p_name)[len - 1] == '/') { 707 VERIFY0(custr_rtrunc(p->p_name, 0)); 708 len--; 709 } 710 711 p->p_pfxidx = relpath ? len + 1 : 0; 712 } 713 714 static size_t 715 path_append(path_t *p, const char *name) 716 { 717 size_t clen = custr_len(p->p_name); 718 719 if (clen > 0) 720 VERIFY0(custr_appendc(p->p_name, '/')); 721 VERIFY0(custr_append(p->p_name, name)); 722 723 return (clen); 724 } 725 726 static const char * 727 path_name(const path_t *p) 728 { 729 return (custr_cstr(p->p_name) + p->p_pfxidx); 730 } 731 732 static const char * 733 path_fullpath(const path_t *p) 734 { 735 return (custr_cstr(p->p_name)); 736 } 737 738 static void 739 path_pop(path_t *p, size_t idx) 740 { 741 VERIFY0(custr_trunc(p->p_name, idx)); 742 } 743 744 static int 745 cmp_id(const void *a, const void *b) 746 { 747 const obj_t *l = a; 748 const obj_t *r = b; 749 750 if (l->obj_dev < r->obj_dev) 751 return (-1); 752 if (l->obj_dev > r->obj_dev) 753 return (1); 754 if (l->obj_inode < r->obj_inode) 755 return (-1); 756 if (l->obj_inode > r->obj_inode) 757 return (1); 758 return (0); 759 } 760 761 static int 762 cmp_objname(const void *a, const void *b) 763 { 764 const obj_t *l = a; 765 const obj_t *r = b; 766 const name_t *ln = &l->obj_names.ns_names[0]; 767 const name_t *rn = &r->obj_names.ns_names[0]; 768 769 return (cmp_names(ln, rn)); 770 } 771 772 static int 773 cmp_names(const void *a, const void *b) 774 { 775 const name_t *l = a; 776 const name_t *r = b; 777 int cmp = strcmp(l->n_name, r->n_name); 778 779 if (cmp < 0) 780 return (-1); 781 if (cmp > 0) 782 return (1); 783 return (0); 784 } 785 786 /* 787 * Use the filename and permission bits to determine if this file could 788 * possibly be an executable. 789 */ 790 static bool 791 maybe_obj(const char *name, mode_t mode) 792 { 793 /* If not in fast mode, check everything, so always return true */ 794 if (!fast_mode) 795 return (true); 796 797 size_t len = strlen(name); 798 799 /* If the file name ends in .so, we check */ 800 if (len >= 3 && strcmp(&name[len - 4], ".so") == 0) { 801 return (true); 802 } 803 804 /* If the file name contains '.so', we check */ 805 if (len >= 4 && strstr(name, ".so.") != NULL) { 806 return (true); 807 } 808 809 /* If the above checks fail, we assume it's not a shared library */ 810 if (so_only) 811 return (false); 812 813 /* 814 * The original perl script used a -x test which only looked at 815 * file permissions. This may return slightly different results 816 * than using access(2) or faccessat(2). 817 */ 818 if ((mode & (S_IXUSR|S_IXGRP|S_IXOTH)) == 0) 819 return (false); 820 821 return (true); 822 } 823 824 static void 825 foreach_avl(avl_tree_t *avl, void (*cb)(void *, void *), void *arg) 826 { 827 void *obj; 828 829 for (obj = avl_first(avl); obj != NULL; obj = AVL_NEXT(avl, obj)) 830 cb(obj, arg); 831 } 832 833 static char * 834 xstrdup(const char *s) 835 { 836 char *news = strdup(s); 837 838 if (news == NULL) { 839 nomem(); 840 } 841 return (news); 842 } 843 844 static void * 845 xcalloc(size_t nelem, size_t elsize) 846 { 847 void *p = calloc(nelem, elsize); 848 849 if (p == NULL) { 850 nomem(); 851 } 852 853 return (p); 854 } 855 856 #define NOMEM_MSG "out of memory\n" 857 static void __NORETURN 858 nomem(void) 859 { 860 /* Try to get the error out before aborting */ 861 (void) write(STDERR_FILENO, NOMEM_MSG, sizeof (NOMEM_MSG)); 862 abort(); 863 } 864