xref: /illumos-gate/usr/src/tools/find_elf/find_elf.c (revision ec6d8ca621f19d1cd1a46117b9b0fde4831c5794)
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