xref: /illumos-gate/usr/src/tools/find_elf/find_elf.c (revision 20a7641f9918de8574b8b3b47dbe35c4bfc78df1)
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 #ifndef O_DIRECTORY
233 	struct stat tsb;
234 	if (stat(dir, &tsb) == -1) {
235 		err(EXIT_FAILURE, "failed to stat %s", dir);
236 	}
237 	if (!S_ISDIR(tsb.st_mode)) {
238 		errx(EXIT_FAILURE, "not a directory: %s", dir);
239 	}
240 	rootfd = open(dir, O_RDONLY);
241 #else
242 	rootfd = open(dir, O_RDONLY|O_DIRECTORY);
243 #endif
244 	if (rootfd < 0) {
245 		err(EXIT_FAILURE, "%s", dir);
246 	}
247 
248 	path_init(&path, dir, relpath);
249 	if (relpath) {
250 		(void) printf("PREFIX %s\n", dir);
251 	}
252 	free(argcopy);
253 
254 	process_file(&path, fd, &sb, DF_NONE);
255 }
256 
257 /*
258  * Processing a directory has some subtleties. When we encounter a
259  * subdirectory that's a symlink, we want any files we encounter when
260  * we traverse it to be treated as aliases. We may also have a symlink that's
261  * a link back to ourself (e.g. 32 -> .). In this special case, we still want
262  * to reprocess the directory to generate alias names, but we use the
263  * DFLAG_SELF flag to avoid recursing forever.
264  *
265  * A limitation of this approach is that we cannot handle a loop over multiple
266  * levels (e.g. foo -> ../..). Nothing currently in illumos-gate creates any
267  * such symlinks in the proto area, so we've opted to avoid complicating
268  * processing further to handle scenarios that aren't realistically expected
269  * to occur.
270  *
271  * Note that dirfd is always closed upon return from process_dir().
272  */
273 static void
274 process_dir(path_t *p, int dirfd, const struct stat *dirsb, dir_flags_t dflags)
275 {
276 	DIR *d;
277 	struct dirent *de;
278 
279 	d = fdopendir(dirfd);
280 	if (d == NULL) {
281 		warn("opendir(%s)", path_fullpath(p));
282 		VERIFY0(close(dirfd));
283 		return;
284 	}
285 
286 	while ((de = readdir(d)) != NULL) {
287 		struct stat sb;
288 		int fd;
289 		bool is_link = false;
290 		size_t plen;
291 
292 		if (strcmp(de->d_name, ".") == 0 ||
293 		    strcmp(de->d_name, "..") == 0) {
294 			continue;
295 		}
296 
297 		plen = path_append(p, de->d_name);
298 
299 		if (fstatat(rootfd, path_name(p), &sb,
300 		    AT_SYMLINK_NOFOLLOW) < 0) {
301 			warn("%s", path_fullpath(p));
302 			path_pop(p, plen);
303 			continue;
304 		}
305 
306 		fd = openat(dirfd, de->d_name, O_RDONLY);
307 		if (fd < 0) {
308 			/*
309 			 * Symlinks in the proto area may point to a path
310 			 * that's not present (e.g. /dev/stdin -> fd/0).
311 			 * Silently skip such entries.
312 			 */
313 			if (errno != ENOENT || !S_ISLNK(sb.st_mode)) {
314 				warn("%s", path_fullpath(p));
315 			}
316 			path_pop(p, plen);
317 			continue;
318 		}
319 
320 		if (S_ISLNK(sb.st_mode)) {
321 			is_link = true;
322 
323 			if (fstat(fd, &sb) < 0) {
324 				warn("stat %s", path_fullpath(p));
325 				path_pop(p, plen);
326 				continue;
327 			}
328 		}
329 
330 		if (S_ISDIR(sb.st_mode)) {
331 			if ((dflags & DF_IS_SELF) != 0) {
332 				VERIFY0(close(fd));
333 				path_pop(p, plen);
334 				continue;
335 			}
336 
337 			dir_flags_t newflags = dflags;
338 
339 			/* The recursive process_dir() call closes fd */
340 			if (is_link)
341 				newflags |= DF_IS_SYMLINK;
342 			if (is_same(&sb, dirsb))
343 				newflags |= DF_IS_SELF;
344 			process_dir(p, fd, &sb, newflags);
345 		} else if (S_ISREG(sb.st_mode)) {
346 			if (!maybe_obj(de->d_name, sb.st_mode)) {
347 				VERIFY0(close(fd));
348 				path_pop(p, plen);
349 				continue;
350 			}
351 
352 			if ((dflags & (DF_IS_SELF | DF_IS_SYMLINK)) != 0)
353 				is_link = true;
354 
355 			/* process_file() closes fd */
356 			process_file(p, fd, &sb, is_link);
357 		}
358 
359 		path_pop(p, plen);
360 	}
361 
362 	/* Closes dirfd */
363 	VERIFY0(closedir(d));
364 }
365 
366 /* Note that fd is always closed upon return */
367 static void
368 process_file(path_t *p, int fd, const struct stat *sb, bool is_link)
369 {
370 	avl_index_t where = { 0 };
371 	obj_t templ = {
372 		.obj_dev = sb->st_dev,
373 		.obj_inode = sb->st_ino,
374 	};
375 	obj_t *obj = avl_find(&avl_byid, &templ, &where);
376 	elfinfo_t elfinfo = { 0 };
377 
378 	if (obj != NULL)
379 		goto done;
380 
381 	if (!get_elfinfo(p, fd, &elfinfo)) {
382 		VERIFY0(close(fd));
383 		return;
384 	}
385 
386 	obj = xcalloc(1, sizeof (*obj));
387 	obj->obj_dev = sb->st_dev;
388 	obj->obj_inode = sb->st_ino;
389 	obj->obj_elfinfo = elfinfo;
390 	avl_add(&avl_byid, obj);
391 
392 done:
393 	add_name(obj, p, is_link);
394 	VERIFY0(close(fd));
395 }
396 
397 static void
398 print_entry(void *a, void *arg __unused)
399 {
400 	obj_t *obj = a;
401 	const char *objname = obj->obj_names.ns_names[0].n_name;
402 	const char *bits = "";
403 	const char *type = "";
404 	const char *verdef = obj->obj_elfinfo.ei_hasverdef ?
405 	    "VERDEF" : "NOVERDEF";
406 
407 	switch (obj->obj_elfinfo.ei_class) {
408 	case ELFCLASS32:
409 		bits = "32";
410 		break;
411 	case ELFCLASS64:
412 		bits = "64";
413 		break;
414 	default:
415 		errx(EXIT_FAILURE, "unknown elfclass value %x for %s",
416 		    obj->obj_elfinfo.ei_class, objname);
417 	}
418 
419 	switch (obj->obj_elfinfo.ei_type) {
420 	case ET_REL:
421 		type = "REL";
422 		break;
423 	case ET_DYN:
424 		type = "DYN";
425 		break;
426 	case ET_EXEC:
427 		type = "EXEC";
428 		break;
429 	default:
430 		errx(EXIT_FAILURE, "unexpected elf type %x for %s",
431 		    obj->obj_elfinfo.ei_type, objname);
432 	}
433 
434 	names_t *names = &obj->obj_names;
435 
436 	VERIFY3U(names->ns_num, >, 0);
437 	VERIFY(!names->ns_names[0].n_is_symlink);
438 
439 	(void) printf("OBJECT %2s %-4s %-8s %s\n", bits, type, verdef,
440 	    objname);
441 
442 	for (uint_t i = 1; i < names->ns_num; i++) {
443 		if (do_alias) {
444 			(void) printf("%-23s %s\t%s\n",
445 			    "ALIAS", objname, names->ns_names[i].n_name);
446 		} else {
447 			(void) printf("OBJECT %2s %-4s %-8s %s\n", bits, type,
448 			    verdef, names->ns_names[i].n_name);
449 		}
450 	}
451 }
452 
453 /*
454  * Returns true and eip populated if name was an elf object, otherwise
455  * returns false.
456  */
457 static bool
458 get_elfinfo(const path_t *p, int fd, elfinfo_t *eip)
459 {
460 	Elf *elf = NULL;
461 	Elf_Scn *scn = NULL;
462 	GElf_Ehdr ehdr = { 0 };
463 	int eval;
464 
465 	if ((elf = elf_begin(fd, ELF_C_READ, NULL)) == NULL)
466 		goto fail_noend;
467 
468 	if ((eip->ei_class = gelf_getclass(elf)) == ELFCLASSNONE) {
469 		VERIFY0(elf_end(elf));
470 		return (false);
471 	}
472 
473 	if (gelf_getehdr(elf, &ehdr) == NULL)
474 		goto fail;
475 
476 	eip->ei_type = ehdr.e_type;
477 	eip->ei_hasverdef = false;
478 
479 	while ((scn = elf_nextscn(elf, scn)) != NULL) {
480 		Elf_Data *data = NULL;
481 		GElf_Shdr shdr = { 0 };
482 
483 		if (gelf_getshdr(scn, &shdr) == NULL)
484 			goto fail;
485 
486 		if (shdr.sh_type != SHT_DYNAMIC)
487 			continue;
488 
489 		if ((data = elf_getdata(scn, NULL)) == NULL)
490 			continue;
491 
492 		size_t nent = shdr.sh_size / shdr.sh_entsize;
493 
494 		for (size_t i = 0; i < nent; i++) {
495 			GElf_Dyn dyn = { 0 };
496 
497 			if (gelf_getdyn(data, i, &dyn) == NULL) {
498 				goto fail;
499 			}
500 
501 			if (dyn.d_tag == DT_VERDEF) {
502 				eip->ei_hasverdef = true;
503 				break;
504 			}
505 		}
506 	}
507 
508 	VERIFY0(elf_end(elf));
509 	return (true);
510 
511 fail:
512 	VERIFY0(elf_end(elf));
513 
514 fail_noend:
515 	eval = elf_errno();
516 
517 	warnx("%s: %s", path_fullpath(p), elf_errmsg(eval));
518 	return (false);
519 }
520 
521 static bool
522 is_special(const char *name)
523 {
524 	for (uint_t i = 0; i < ARRAY_SIZE(special_paths); i++) {
525 		if (strcmp(special_paths[i], name) == 0)
526 			return (true);
527 	}
528 	return (false);
529 }
530 
531 static void
532 sort_names(void *a, void *b)
533 {
534 	obj_t *obj = a;
535 	avl_tree_t *name_avl = b;
536 	names_t *names = &obj->obj_names;
537 
538 	/* We should always have at least one name */
539 	VERIFY3U(names->ns_num, >, 0);
540 
541 	/* If there is only one, they get the prize and we're done */
542 	if (names->ns_num == 1)
543 		return;
544 
545 	name_t *first = NULL;
546 
547 	/*
548 	 * Find the first (in sorted order) pathname for this object
549 	 * that isn't a symlink.
550 	 */
551 	for (uint_t i = 0; i < names->ns_num; i++) {
552 		name_t *n = &names->ns_names[i];
553 
554 		if (n->n_is_symlink)
555 			continue;
556 
557 		if (first == NULL) {
558 			first = n;
559 			continue;
560 		}
561 
562 		/*
563 		 * If we're treating isaexec as special, we always
564 		 * want it to be the first entry. Otherwise, pick
565 		 * the 'lowest' sorted pathname.
566 		 */
567 		if (do_special) {
568 			if (is_special(n->n_name)) {
569 				first = n;
570 				break;
571 			}
572 
573 			if (strcmp(n->n_name, first->n_name) < 0)
574 				first = n;
575 		}
576 	}
577 
578 	/*
579 	 * If the first pathname (in sorted order) isn't the first
580 	 * name entry, we swap it into first place (while also updating
581 	 * the names AVL tree).
582 	 */
583 	if (first != NULL && first != &names->ns_names[0]) {
584 		name_t tmp = names->ns_names[0];
585 
586 		avl_remove(name_avl, obj);
587 		(void) memcpy(&names->ns_names[0], first, sizeof (name_t));
588 		(void) memcpy(first, &tmp, sizeof (name_t));
589 		avl_add(name_avl, obj);
590 	}
591 
592 	/*
593 	 * The remaining names (symlinks or not) are sorted by
594 	 * their pathname.
595 	 */
596 	qsort(&names->ns_names[1], names->ns_num - 1, sizeof (name_t),
597 	    cmp_names);
598 }
599 
600 /*
601  * We grow the names array by NAME_CHUNK entries every time we need to
602  * extend it.
603  */
604 #define	NAME_CHUNK	4
605 
606 static name_t *
607 name_new(names_t *names)
608 {
609 	if (names->ns_num < names->ns_alloc)
610 		return (&names->ns_names[names->ns_num++]);
611 
612 	name_t *newn = NULL;
613 	uint_t newamt = names->ns_alloc + NAME_CHUNK;
614 
615 	/*
616 	 * Since we may be building on a machine that doesn't
617 	 * have reallocarray or the like, we avoid their use here.
618 	 * If we ever officially designate an illumos-gate build
619 	 * as the minimum required to build master that contains
620 	 * reallocarray and such, we can replace most of this logic
621 	 * with it.
622 	 *
623 	 * Also use xcalloc so we get the unused entries zeroed already.
624 	 * Not strictly necessary, but useful for debugging.
625 	 */
626 	newn = xcalloc(newamt, sizeof (name_t));
627 
628 	/* Move over existing entries */
629 	(void) memcpy(newn, names->ns_names, names->ns_num * sizeof (name_t));
630 
631 	free(names->ns_names);
632 
633 	names->ns_names = newn;
634 	names->ns_alloc = newamt;
635 	return (&names->ns_names[names->ns_num++]);
636 }
637 
638 static void
639 add_name(obj_t *obj, const path_t *p, bool is_symlink)
640 {
641 	names_t *ns = &obj->obj_names;
642 	const char *srcname = path_name(p);
643 
644 	/* We should never have duplicates */
645 	for (size_t i = 0; i < ns->ns_num; i++)
646 		VERIFY3S(strcmp(ns->ns_names[i].n_name, srcname), !=, 0);
647 
648 	name_t *n = name_new(ns);
649 
650 	n->n_name = xstrdup(srcname);
651 	n->n_is_symlink = is_symlink;
652 
653 	if (is_symlink)
654 		return;
655 
656 	/*
657 	 * If the name was not a symlink, first determine if there are
658 	 * existing (hard) links to this name already, and if so, which entry
659 	 * is the first one. Typically this will be the name we just added
660 	 * unless the file does have multiple hard links (e.g. isaexec).
661 	 */
662 	uint_t nhlink = 0;
663 	uint_t firsthlink = UINT_MAX;
664 
665 	for (uint_t i = 0; i < ns->ns_num; i++) {
666 		if (ns->ns_names[i].n_is_symlink)
667 			continue;
668 		if (nhlink == 0)
669 			firsthlink = i;
670 		nhlink++;
671 	}
672 
673 	if (nhlink > 1)
674 		return;
675 
676 	/*
677 	 * Put the first non-symlink name as the very first
678 	 * entry.
679 	 */
680 	VERIFY3U(firsthlink, !=, UINT_MAX);
681 
682 	if (firsthlink != 0) {
683 		name_t tmp = {
684 			.n_name = ns->ns_names[0].n_name,
685 			.n_is_symlink = ns->ns_names[0].n_is_symlink,
686 		};
687 
688 		(void) memcpy(&ns->ns_names[0], &ns->ns_names[firsthlink],
689 		    sizeof (name_t));
690 		(void) memcpy(&ns->ns_names[firsthlink], &tmp, sizeof (name_t));
691 	}
692 
693 	avl_add(&avl_byname, obj);
694 }
695 
696 /*
697  * This is an arbitrary initial value -- basically we can nest 16 directories
698  * deep before having to grow p->p_idx (which seems like a nice power of 2).
699  */
700 #define	PATH_INIT	16
701 
702 static void
703 path_init(path_t *p, const char *name, bool relpath)
704 {
705 	(void) memset(p, '\0', sizeof (*p));
706 
707 	if (custr_alloc(&p->p_name) < 0) {
708 		nomem();
709 	}
710 
711 	if (name != NULL && custr_append(p->p_name, name) < 0)
712 		nomem();
713 
714 	size_t len = custr_len(p->p_name);
715 
716 	/* Trim off any trailing /'s, but don't trim '/' to an empty path */
717 	while (len > 1 && custr_cstr(p->p_name)[len - 1] == '/') {
718 		VERIFY0(custr_rtrunc(p->p_name, 0));
719 		len--;
720 	}
721 
722 	p->p_pfxidx = relpath ? len + 1 : 0;
723 }
724 
725 static size_t
726 path_append(path_t *p, const char *name)
727 {
728 	size_t clen = custr_len(p->p_name);
729 
730 	if (clen > 0)
731 		VERIFY0(custr_appendc(p->p_name, '/'));
732 	VERIFY0(custr_append(p->p_name, name));
733 
734 	return (clen);
735 }
736 
737 static const char *
738 path_name(const path_t *p)
739 {
740 	return (custr_cstr(p->p_name) + p->p_pfxidx);
741 }
742 
743 static const char *
744 path_fullpath(const path_t *p)
745 {
746 	return (custr_cstr(p->p_name));
747 }
748 
749 static void
750 path_pop(path_t *p, size_t idx)
751 {
752 	VERIFY0(custr_trunc(p->p_name, idx));
753 }
754 
755 static int
756 cmp_id(const void *a, const void *b)
757 {
758 	const obj_t *l = a;
759 	const obj_t *r = b;
760 
761 	if (l->obj_dev < r->obj_dev)
762 		return (-1);
763 	if (l->obj_dev > r->obj_dev)
764 		return (1);
765 	if (l->obj_inode < r->obj_inode)
766 		return (-1);
767 	if (l->obj_inode > r->obj_inode)
768 		return (1);
769 	return (0);
770 }
771 
772 static int
773 cmp_objname(const void *a, const void *b)
774 {
775 	const obj_t *l = a;
776 	const obj_t *r = b;
777 	const name_t *ln = &l->obj_names.ns_names[0];
778 	const name_t *rn = &r->obj_names.ns_names[0];
779 
780 	return (cmp_names(ln, rn));
781 }
782 
783 static int
784 cmp_names(const void *a, const void *b)
785 {
786 	const name_t *l = a;
787 	const name_t *r = b;
788 	int cmp = strcmp(l->n_name, r->n_name);
789 
790 	if (cmp < 0)
791 		return (-1);
792 	if (cmp > 0)
793 		return (1);
794 	return (0);
795 }
796 
797 /*
798  * Use the filename and permission bits to determine if this file could
799  * possibly be an executable.
800  */
801 static bool
802 maybe_obj(const char *name, mode_t mode)
803 {
804 	/* If not in fast mode, check everything, so always return true */
805 	if (!fast_mode)
806 		return (true);
807 
808 	size_t len = strlen(name);
809 
810 	/* If the file name ends in .so, we check */
811 	if (len >= 3 && strcmp(&name[len - 3], ".so") == 0) {
812 		return (true);
813 	}
814 
815 	/* If the file name contains '.so', we check */
816 	if (len >= 4 && strstr(name, ".so.") != NULL) {
817 		return (true);
818 	}
819 
820 	/* If the above checks fail, we assume it's not a shared library */
821 	if (so_only)
822 		return (false);
823 
824 	/*
825 	 * The original perl script used a -x test which only looked at
826 	 * file permissions. This may return slightly different results
827 	 * than using access(2) or faccessat(2).
828 	 */
829 	if ((mode & (S_IXUSR|S_IXGRP|S_IXOTH)) == 0)
830 		return (false);
831 
832 	return (true);
833 }
834 
835 static void
836 foreach_avl(avl_tree_t *avl, void (*cb)(void *, void *), void *arg)
837 {
838 	void *obj;
839 
840 	for (obj = avl_first(avl); obj != NULL; obj = AVL_NEXT(avl, obj))
841 		cb(obj, arg);
842 }
843 
844 static char *
845 xstrdup(const char *s)
846 {
847 	char *news = strdup(s);
848 
849 	if (news == NULL) {
850 		nomem();
851 	}
852 	return (news);
853 }
854 
855 static void *
856 xcalloc(size_t nelem, size_t elsize)
857 {
858 	void *p = calloc(nelem, elsize);
859 
860 	if (p == NULL) {
861 		nomem();
862 	}
863 
864 	return (p);
865 }
866 
867 #define	NOMEM_MSG "out of memory\n"
868 static void __NORETURN
869 nomem(void)
870 {
871 	/* Try to get the error out before aborting */
872 	(void) write(STDERR_FILENO, NOMEM_MSG, sizeof (NOMEM_MSG));
873 	abort();
874 }
875