summaryrefslogtreecommitdiff
path: root/usr/src
diff options
context:
space:
mode:
Diffstat (limited to 'usr/src')
-rw-r--r--usr/src/pkg/manifests/developer-build-onbld.p5m3
-rw-r--r--usr/src/tools/Makefile2
-rw-r--r--usr/src/tools/find_elf/Makefile68
-rw-r--r--usr/src/tools/find_elf/find_elf.1onbld (renamed from usr/src/tools/scripts/find_elf.1onbld)21
-rw-r--r--usr/src/tools/find_elf/find_elf.c863
-rw-r--r--usr/src/tools/scripts/Makefile3
-rw-r--r--usr/src/tools/scripts/find_elf.pl457
7 files changed, 955 insertions, 462 deletions
diff --git a/usr/src/pkg/manifests/developer-build-onbld.p5m b/usr/src/pkg/manifests/developer-build-onbld.p5m
index efa509a89b..ca55e019f7 100644
--- a/usr/src/pkg/manifests/developer-build-onbld.p5m
+++ b/usr/src/pkg/manifests/developer-build-onbld.p5m
@@ -27,6 +27,7 @@
# Copyright 2019 Joyent, Inc.
# Copyright 2016 Toomas Soome <tsoome@me.com>
# Copyright 2021 OmniOS Community Edition (OmniOSce) Association.
+# Copyright 2022 Jason King
#
set name=pkg.fmri value=pkg:/developer/build/onbld@$(PKGVERS)
@@ -59,6 +60,7 @@ file path=opt/onbld/bin/$(ARCH)/ctfstrip mode=0555
file path=opt/onbld/bin/$(ARCH)/cw mode=0555
link path=opt/onbld/bin/$(ARCH)/dmake target=make
$(i386_ONLY)file path=opt/onbld/bin/$(ARCH)/elfextract mode=0555
+file path=opt/onbld/bin/$(ARCH)/find_elf mode=0555
file path=opt/onbld/bin/$(ARCH)/findunref mode=0555
$(sparc_ONLY)file path=opt/onbld/bin/$(ARCH)/forth mode=0555
$(sparc_ONLY)file path=opt/onbld/bin/$(ARCH)/forth_preload.so.1 mode=0555
@@ -89,7 +91,6 @@ file path=opt/onbld/bin/copyrightchk mode=0555 \
pkg.depend.bypass-generate=.*(?:Checks|onbld|Copyright).*
file path=opt/onbld/bin/cstyle mode=0555
file path=opt/onbld/bin/elfcmp mode=0555
-file path=opt/onbld/bin/find_elf mode=0555
file path=opt/onbld/bin/findcrypto mode=0555
file path=opt/onbld/bin/flg.flp mode=0555
file path=opt/onbld/bin/genoffsets mode=0555
diff --git a/usr/src/tools/Makefile b/usr/src/tools/Makefile
index 0f2d1800e8..6a1f363ce9 100644
--- a/usr/src/tools/Makefile
+++ b/usr/src/tools/Makefile
@@ -26,6 +26,7 @@
# Copyright (c) 2016, Chris Fraire <cfraire@me.com>.
# Copyright 2019 Joyent, Inc.
# Copyright 2019 OmniOS Community Edition (OmniOSce) Association.
+# Copyright 2021 Jason King
#
include ../Makefile.master
@@ -53,6 +54,7 @@ COMMON_SUBDIRS= \
codesign \
cscope-fast \
env \
+ find_elf \
findunref \
lintdump \
make \
diff --git a/usr/src/tools/find_elf/Makefile b/usr/src/tools/find_elf/Makefile
new file mode 100644
index 0000000000..db723147bf
--- /dev/null
+++ b/usr/src/tools/find_elf/Makefile
@@ -0,0 +1,68 @@
+#
+# This file and its contents are supplied under the terms of the
+# Common Development and Distribution License ("CDDL"), version 1.0.
+# You may only use this file in accordance with the terms of version
+# 1.0 of the CDDL.
+#
+# A full copy of the text of the CDDL should have accompanied this
+# source. A copy of the CDDL is also available via the Internet at
+# http://www.illumos.org/license/CDDL.
+#
+
+#
+# Copyright 2022 Jason King
+#
+
+PROG = find_elf
+MAN1ONBLDFILES = find_elf.1onbld
+
+#
+# Since libcustr is private, we just build and link in the code directly
+# into the binary. If more build utilities require it in the future, we
+# can transition to building a tools version of the library and link
+# against it.
+CUSTRDIR = $(SRC)/lib/libcustr/common
+
+OBJS = find_elf.o custr.o
+
+include $(SRC)/tools/Makefile.tools
+include $(SRC)/cmd/Makefile.ctf
+
+$(ROOTONBLDMAN1ONBLDFILES) := FILEMODE= 644
+
+LDLIBS = -lelf -lavl
+NATIVE_LIBS += libelf.so libc.so libavl.so
+
+CPPFLAGS += -I$(CUSTRDIR)
+LDFLAGS = \
+ -L$(ROOTONBLDLIBMACH) \
+ '-R$$ORIGIN/../../lib/$(MACH)' \
+ $(BDIRECT) $(ZLAZYLOAD)
+
+CSTD = $(CSTD_GNU99)
+
+.KEEP_STATE:
+
+.PARALLEL: $(OBJS)
+
+all: $(PROG)
+
+install: all .WAIT $(ROOTONBLDMACHPROG) $(ROOTONBLDDATAFILES) \
+ $(ROOTONBLDMAN1ONBLDFILES)
+
+clean:
+ $(RM) -f $(OBJS)
+
+$(PROG): $(OBJS)
+ $(LINK.c) $(OBJS) -o $@ $(LDLIBS)
+ $(POST_PROCESS)
+
+%.o: %.c
+ $(COMPILE.c) -o $@ $<
+ $(POST_PROCESS_O)
+
+%.o: $(CUSTRDIR)/%.c
+ $(COMPILE.c) -o $@ $<
+ $(POST_PROCESS_O)
+
+include $(SRC)/tools/Makefile.targ
diff --git a/usr/src/tools/scripts/find_elf.1onbld b/usr/src/tools/find_elf/find_elf.1onbld
index ac578ea9df..8fe9b7713b 100644
--- a/usr/src/tools/scripts/find_elf.1onbld
+++ b/usr/src/tools/find_elf/find_elf.1onbld
@@ -19,11 +19,13 @@
.\"
.\" CDDL HEADER END
.\"
-.TH FIND_ELF 1ONBLD "December 3, 2021"
+.\" Copyright 2022 Jason King
+.\"
+.TH FIND_ELF 1ONBLD "May 29, 2022"
.SH NAME
find_elf \- Locate ELF objects
.SH SYNOPSIS
-\fBfind_elf [-afrs] path\fP
+\fBfind_elf [-afhnrs] path\fP
.SH DESCRIPTION
The
.I find_elf
@@ -46,6 +48,21 @@ shared objects must end with a .so extension. Files that do not
meet these requirements are silently eliminated from consideration without
further analysis.
.TP 4
+.B \-h
+Show usage message.
+.TP 4
+.B \-n
+Do not treat well known hard-linked binaries as special. Certain well known
+binaries (currently \fBalias\fP and \fBisaexec\fP) are hard linked to many
+other names in a proto directory tree.
+.P
+By default,
+.I find_elf
+will use these well known names as the initial name and all other hard links
+to those binaries are treated as aliases. Disabling this behavior with the
+\fB-n\fR option will choose the first name encountered during directory
+traversal as the name, and all other hard links to the binary as aliases.
+.TP 4
.B \-r
Report file names as relative paths, relative to the given file or directory,
instead of fully qualified.
diff --git a/usr/src/tools/find_elf/find_elf.c b/usr/src/tools/find_elf/find_elf.c
new file mode 100644
index 0000000000..7c31ccc685
--- /dev/null
+++ b/usr/src/tools/find_elf/find_elf.c
@@ -0,0 +1,863 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source. A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2020 Joyent, Inc.
+ * Copyright 2022 Jason King
+ */
+
+#include <sys/debug.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/avl.h>
+#include <sys/fcntl.h>
+#include <sys/sysmacros.h>
+#include <dirent.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <gelf.h>
+#include <libcustr.h>
+#include <libelf.h>
+#include <libgen.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+static inline bool
+is_same(const struct stat *s1, const struct stat *s2)
+{
+ if ((s1->st_ino == s2->st_ino) && (s1->st_dev == s2->st_dev))
+ return (true);
+ return (false);
+}
+
+typedef enum dir_flags {
+ DF_NONE = 0,
+ DF_IS_SELF = 1 << 0,
+ DF_IS_SYMLINK = 1 << 2,
+} dir_flags_t;
+
+typedef struct path {
+ custr_t *p_name;
+ size_t p_pfxidx;
+} path_t;
+
+typedef struct name {
+ char *n_name;
+ bool n_is_symlink;
+} name_t;
+
+typedef struct names {
+ name_t *ns_names;
+ uint_t ns_num;
+ uint_t ns_alloc;
+} names_t;
+
+typedef struct elfinfo {
+ int ei_class;
+ uint16_t ei_type;
+ bool ei_hasverdef;
+} elfinfo_t;
+
+typedef struct obj {
+ avl_node_t obj_avlid;
+ avl_node_t obj_avlname;
+ dev_t obj_dev;
+ ino_t obj_inode;
+ names_t obj_names;
+ elfinfo_t obj_elfinfo;
+} obj_t;
+
+static void path_init(path_t *, const char *, bool);
+static size_t path_append(path_t *, const char *);
+static const char *path_name(const path_t *);
+static const char *path_fullpath(const path_t *);
+static void path_pop(path_t *, size_t);
+
+static bool maybe_obj(const char *, mode_t);
+static bool get_elfinfo(const path_t *, int, elfinfo_t *);
+static void add_name(obj_t *, const path_t *, bool);
+static int cmp_id(const void *, const void *);
+static int cmp_objname(const void *, const void *);
+static int cmp_names(const void *, const void *);
+
+static void process_dir(path_t *, int, const struct stat *, dir_flags_t);
+static void process_file(path_t *, int, const struct stat *, bool);
+static void process_arg(char *);
+
+static void sort_names(void *, void *);
+static void print_entry(void *, void *);
+
+static void foreach_avl(avl_tree_t *, void (*)(void *, void *), void *);
+
+static void nomem(void);
+static char *xstrdup(const char *);
+static void *xcalloc(size_t, size_t);
+
+static avl_tree_t avl_byid;
+static avl_tree_t avl_byname;
+
+static const char *special_paths[] = {
+ "usr/bin/alias",
+ "usr/lib/isaexec",
+};
+
+static int rootfd = -1;
+
+/* By default, we process aliases */
+static bool do_alias = true;
+
+/* By default, we treat certain well known names (e.g. isaexec) as special */
+static bool do_special = true;
+
+/* fast_mode, relpath, and so_only are all false by default */
+static bool fast_mode;
+static bool relpath;
+static bool so_only;
+
+static void __NORETURN
+usage(const char *name)
+{
+ (void) fprintf(stderr,
+ "Usage: %s [-afnrs] file | dir\n"
+ "\t[-a]\texpand symlink aliases\n"
+ "\t[-f]\tuse file name at mode to speed search\n"
+ "\t[-h]\tshow this help\n"
+ "\t[-n]\tdon\'t treat well known paths as special in sorting\n"
+ "\t[-r]\treport relative paths\n"
+ "\t[-s]\tonly remote shareable (ET_DYN) objects\n", name);
+ exit(EXIT_FAILURE);
+}
+
+int
+main(int argc, char **argv)
+{
+ int c;
+
+ if (elf_version(EV_CURRENT) == EV_NONE)
+ errx(EXIT_FAILURE, "elf library is out of date");
+
+ while ((c = getopt(argc, argv, "ahfnrs")) != -1) {
+ switch (c) {
+ case 'a':
+ do_alias = false;
+ break;
+ case 'f':
+ fast_mode = true;
+ break;
+ case 'n':
+ do_special = false;
+ break;
+ case 'r':
+ relpath = true;
+ break;
+ case 's':
+ so_only = true;
+ break;
+ case 'h':
+ usage(argv[0]);
+ case '?':
+ (void) fprintf(stderr, "Unknown option -%c\n", optopt);
+ usage(argv[0]);
+ }
+ }
+
+ if (optind == argc) {
+ (void) fprintf(stderr, "Missing file or dir parameter\n");
+ usage(argv[0]);
+ }
+
+ if (argv[optind][0] == '\0')
+ errx(EXIT_FAILURE, "Invalid file or dir value");
+
+ avl_create(&avl_byid, cmp_id, sizeof (obj_t),
+ offsetof(obj_t, obj_avlid));
+ avl_create(&avl_byname, cmp_objname, sizeof (obj_t),
+ offsetof(obj_t, obj_avlname));
+
+ process_arg(argv[optind]);
+ foreach_avl(&avl_byid, sort_names, &avl_byname);
+ foreach_avl(&avl_byname, print_entry, NULL);
+
+ return (EXIT_SUCCESS);
+}
+
+static void
+process_arg(char *arg)
+{
+ path_t path;
+ struct stat sb;
+ int fd;
+
+ if ((fd = open(arg, O_RDONLY)) == -1) {
+ err(EXIT_FAILURE, "could not open %s", arg);
+ }
+
+ if (fstat(fd, &sb) < 0) {
+ err(EXIT_FAILURE, "failed to stat %s", arg);
+ }
+
+ if (S_ISDIR(sb.st_mode)) {
+ path_init(&path, arg, relpath);
+ if (relpath) {
+ (void) printf("PREFIX %s\n", arg);
+ }
+
+ rootfd = fd;
+ /* process_dir() will close fd */
+ process_dir(&path, fd, &sb, DF_NONE);
+ return;
+ }
+
+ char *argcopy = xstrdup(arg);
+ char *dir = dirname(argcopy);
+
+ if (!S_ISREG(sb.st_mode)) {
+ err(EXIT_FAILURE, "not a file or directory: %s", arg);
+ }
+
+ rootfd = open(dir, O_RDONLY|O_DIRECTORY);
+ if (rootfd < 0) {
+ err(EXIT_FAILURE, "%s", dir);
+ }
+
+ path_init(&path, dir, relpath);
+ if (relpath) {
+ (void) printf("PREFIX %s\n", dir);
+ }
+ free(argcopy);
+
+ process_file(&path, fd, &sb, DF_NONE);
+}
+
+/*
+ * Processing a directory has some subtleties. When we encounter a
+ * subdirectory that's a symlink, we want any files we encounter when
+ * we traverse it to be treated as aliases. We may also have a symlink that's
+ * a link back to ourself (e.g. 32 -> .). In this special case, we still want
+ * to reprocess the directory to generate alias names, but we use the
+ * DFLAG_SELF flag to avoid recursing forever.
+ *
+ * A limitation of this approach is that we cannot handle a loop over multiple
+ * levels (e.g. foo -> ../..). Nothing currently in illumos-gate creates any
+ * such symlinks in the proto area, so we've opted to avoid complicating
+ * processing further to handle scenarios that aren't realistically expected
+ * to occur.
+ *
+ * Note that dirfd is always closed upon return from process_dir().
+ */
+static void
+process_dir(path_t *p, int dirfd, const struct stat *dirsb, dir_flags_t dflags)
+{
+ DIR *d;
+ struct dirent *de;
+
+ d = fdopendir(dirfd);
+ if (d == NULL) {
+ warn("opendir(%s)", path_fullpath(p));
+ VERIFY0(close(dirfd));
+ return;
+ }
+
+ while ((de = readdir(d)) != NULL) {
+ struct stat sb;
+ int fd;
+ bool is_link = false;
+ size_t plen;
+
+ if (strcmp(de->d_name, ".") == 0 ||
+ strcmp(de->d_name, "..") == 0) {
+ continue;
+ }
+
+ plen = path_append(p, de->d_name);
+
+ if (fstatat(rootfd, path_name(p), &sb,
+ AT_SYMLINK_NOFOLLOW) < 0) {
+ warn("%s", path_fullpath(p));
+ path_pop(p, plen);
+ continue;
+ }
+
+ fd = openat(dirfd, de->d_name, O_RDONLY);
+ if (fd < 0) {
+ /*
+ * Symlinks in the proto area may point to a path
+ * that's not present (e.g. /dev/stdin -> fd/0).
+ * Silently skip such entries.
+ */
+ if (errno != ENOENT || !S_ISLNK(sb.st_mode)) {
+ warn("%s", path_fullpath(p));
+ }
+ path_pop(p, plen);
+ continue;
+ }
+
+ if (S_ISLNK(sb.st_mode)) {
+ is_link = true;
+
+ if (fstat(fd, &sb) < 0) {
+ warn("stat %s", path_fullpath(p));
+ path_pop(p, plen);
+ continue;
+ }
+ }
+
+ if (S_ISDIR(sb.st_mode)) {
+ if ((dflags & DF_IS_SELF) != 0) {
+ VERIFY0(close(fd));
+ path_pop(p, plen);
+ continue;
+ }
+
+ dir_flags_t newflags = dflags;
+
+ /* The recursive process_dir() call closes fd */
+ if (is_link)
+ newflags |= DF_IS_SYMLINK;
+ if (is_same(&sb, dirsb))
+ newflags |= DF_IS_SELF;
+ process_dir(p, fd, &sb, newflags);
+ } else if (S_ISREG(sb.st_mode)) {
+ if (!maybe_obj(de->d_name, sb.st_mode)) {
+ VERIFY0(close(fd));
+ path_pop(p, plen);
+ continue;
+ }
+
+ if ((dflags & (DF_IS_SELF | DF_IS_SYMLINK)) != 0)
+ is_link = true;
+
+ /* process_file() closes fd */
+ process_file(p, fd, &sb, is_link);
+ }
+
+ path_pop(p, plen);
+ }
+
+ /* Closes dirfd */
+ VERIFY0(closedir(d));
+}
+
+/* Note that fd is always closed upon return */
+static void
+process_file(path_t *p, int fd, const struct stat *sb, bool is_link)
+{
+ avl_index_t where = { 0 };
+ obj_t templ = {
+ .obj_dev = sb->st_dev,
+ .obj_inode = sb->st_ino,
+ };
+ obj_t *obj = avl_find(&avl_byid, &templ, &where);
+ elfinfo_t elfinfo = { 0 };
+
+ if (obj != NULL)
+ goto done;
+
+ if (!get_elfinfo(p, fd, &elfinfo)) {
+ VERIFY0(close(fd));
+ return;
+ }
+
+ obj = xcalloc(1, sizeof (*obj));
+ obj->obj_dev = sb->st_dev;
+ obj->obj_inode = sb->st_ino;
+ obj->obj_elfinfo = elfinfo;
+ avl_add(&avl_byid, obj);
+
+done:
+ add_name(obj, p, is_link);
+ VERIFY0(close(fd));
+}
+
+static void
+print_entry(void *a, void *arg __unused)
+{
+ obj_t *obj = a;
+ const char *objname = obj->obj_names.ns_names[0].n_name;
+ const char *bits = "";
+ const char *type = "";
+ const char *verdef = obj->obj_elfinfo.ei_hasverdef ?
+ "VERDEF" : "NOVERDEF";
+
+ switch (obj->obj_elfinfo.ei_class) {
+ case ELFCLASS32:
+ bits = "32";
+ break;
+ case ELFCLASS64:
+ bits = "64";
+ break;
+ default:
+ errx(EXIT_FAILURE, "unknown elfclass value %x for %s",
+ obj->obj_elfinfo.ei_class, objname);
+ }
+
+ switch (obj->obj_elfinfo.ei_type) {
+ case ET_REL:
+ type = "REL";
+ break;
+ case ET_DYN:
+ type = "DYN";
+ break;
+ case ET_EXEC:
+ type = "EXEC";
+ break;
+ default:
+ errx(EXIT_FAILURE, "unexpected elf type %x for %s",
+ obj->obj_elfinfo.ei_type, objname);
+ }
+
+ names_t *names = &obj->obj_names;
+
+ VERIFY3U(names->ns_num, >, 0);
+ VERIFY(!names->ns_names[0].n_is_symlink);
+
+ (void) printf("OBJECT %2s %-4s %-8s %s\n", bits, type, verdef,
+ objname);
+
+ for (uint_t i = 1; i < names->ns_num; i++) {
+ if (do_alias) {
+ (void) printf("%-23s %s\t%s\n",
+ "ALIAS", objname, names->ns_names[i].n_name);
+ } else {
+ (void) printf("OBJECT %2s %-4s %-8s %s\n", bits, type,
+ verdef, names->ns_names[i].n_name);
+ }
+ }
+}
+
+/*
+ * Returns true and eip populated if name was an elf object, otherwise
+ * returns false.
+ */
+static bool
+get_elfinfo(const path_t *p, int fd, elfinfo_t *eip)
+{
+ Elf *elf = NULL;
+ Elf_Scn *scn = NULL;
+ GElf_Ehdr ehdr = { 0 };
+ int eval;
+
+ if ((elf = elf_begin(fd, ELF_C_READ, NULL)) == NULL)
+ goto fail_noend;
+
+ if ((eip->ei_class = gelf_getclass(elf)) == ELFCLASSNONE) {
+ VERIFY0(elf_end(elf));
+ return (false);
+ }
+
+ if (gelf_getehdr(elf, &ehdr) == NULL)
+ goto fail;
+
+ eip->ei_type = ehdr.e_type;
+ eip->ei_hasverdef = false;
+
+ while ((scn = elf_nextscn(elf, scn)) != NULL) {
+ Elf_Data *data = NULL;
+ GElf_Shdr shdr = { 0 };
+
+ if (gelf_getshdr(scn, &shdr) == NULL)
+ goto fail;
+
+ if (shdr.sh_type != SHT_DYNAMIC)
+ continue;
+
+ if ((data = elf_getdata(scn, NULL)) == NULL)
+ continue;
+
+ size_t nent = shdr.sh_size / shdr.sh_entsize;
+
+ for (size_t i = 0; i < nent; i++) {
+ GElf_Dyn dyn = { 0 };
+
+ if (gelf_getdyn(data, i, &dyn) == NULL) {
+ goto fail;
+ }
+
+ if (dyn.d_tag == DT_VERDEF) {
+ eip->ei_hasverdef = true;
+ break;
+ }
+ }
+ }
+
+ VERIFY0(elf_end(elf));
+ return (true);
+
+fail:
+ VERIFY0(elf_end(elf));
+
+fail_noend:
+ eval = elf_errno();
+
+ warnx("%s: %s", path_fullpath(p), elf_errmsg(eval));
+ return (false);
+}
+
+static bool
+is_special(const char *name)
+{
+ for (uint_t i = 0; i < ARRAY_SIZE(special_paths); i++) {
+ if (strcmp(special_paths[i], name) == 0)
+ return (true);
+ }
+ return (false);
+}
+
+static void
+sort_names(void *a, void *b)
+{
+ obj_t *obj = a;
+ avl_tree_t *name_avl = b;
+ names_t *names = &obj->obj_names;
+
+ /* We should always have at least one name */
+ VERIFY3U(names->ns_num, >, 0);
+
+ /* If there is only one, they get the prize and we're done */
+ if (names->ns_num == 1)
+ return;
+
+ name_t *first = NULL;
+
+ /*
+ * Find the first (in sorted order) pathname for this object
+ * that isn't a symlink.
+ */
+ for (uint_t i = 0; i < names->ns_num; i++) {
+ name_t *n = &names->ns_names[i];
+
+ if (n->n_is_symlink)
+ continue;
+
+ if (first == NULL) {
+ first = n;
+ continue;
+ }
+
+ /*
+ * If we're treating isaexec as special, we always
+ * want it to be the first entry. Otherwise, pick
+ * the 'lowest' sorted pathname.
+ */
+ if (do_special) {
+ if (is_special(n->n_name)) {
+ first = n;
+ break;
+ }
+
+ if (strcmp(n->n_name, first->n_name) < 0)
+ first = n;
+ }
+ }
+
+ /*
+ * If the first pathname (in sorted order) isn't the first
+ * name entry, we swap it into first place (while also updating
+ * the names AVL tree).
+ */
+ if (first != NULL && first != &names->ns_names[0]) {
+ name_t tmp = names->ns_names[0];
+
+ avl_remove(name_avl, obj);
+ (void) memcpy(&names->ns_names[0], first, sizeof (name_t));
+ (void) memcpy(first, &tmp, sizeof (name_t));
+ avl_add(name_avl, obj);
+ }
+
+ /*
+ * The remaining names (symlinks or not) are sorted by
+ * their pathname.
+ */
+ qsort(&names->ns_names[1], names->ns_num - 1, sizeof (name_t),
+ cmp_names);
+}
+
+/*
+ * We grow the names array by NAME_CHUNK entries every time we need to
+ * extend it.
+ */
+#define NAME_CHUNK 4
+
+static name_t *
+name_new(names_t *names)
+{
+ if (names->ns_num < names->ns_alloc)
+ return (&names->ns_names[names->ns_num++]);
+
+ name_t *newn = NULL;
+ uint_t newamt = names->ns_alloc + NAME_CHUNK;
+
+ /*
+ * Since we may be building on a machine that doesn't
+ * have reallocarray or the like, we avoid their use here.
+ * If we ever officially designate an illumos-gate build
+ * as the minimum required to build master that contains
+ * reallocarray and such, we can replace most of this logic
+ * with it.
+ *
+ * Also use xcalloc so we get the unused entries zeroed already.
+ * Not strictly necessary, but useful for debugging.
+ */
+ newn = xcalloc(newamt, sizeof (name_t));
+
+ /* Move over existing entries */
+ (void) memcpy(newn, names->ns_names, names->ns_num * sizeof (name_t));
+
+ free(names->ns_names);
+
+ names->ns_names = newn;
+ names->ns_alloc = newamt;
+ return (&names->ns_names[names->ns_num++]);
+}
+
+static void
+add_name(obj_t *obj, const path_t *p, bool is_symlink)
+{
+ names_t *ns = &obj->obj_names;
+ const char *srcname = path_name(p);
+
+ /* We should never have duplicates */
+ for (size_t i = 0; i < ns->ns_num; i++)
+ VERIFY3S(strcmp(ns->ns_names[i].n_name, srcname), !=, 0);
+
+ name_t *n = name_new(ns);
+
+ n->n_name = xstrdup(srcname);
+ n->n_is_symlink = is_symlink;
+
+ if (is_symlink)
+ return;
+
+ /*
+ * If the name was not a symlink, first determine if there are
+ * existing (hard) links to this name already, and if so, which entry
+ * is the first one. Typically this will be the name we just added
+ * unless the file does have multiple hard links (e.g. isaexec).
+ */
+ uint_t nhlink = 0;
+ uint_t firsthlink = UINT_MAX;
+
+ for (uint_t i = 0; i < ns->ns_num; i++) {
+ if (ns->ns_names[i].n_is_symlink)
+ continue;
+ if (nhlink == 0)
+ firsthlink = i;
+ nhlink++;
+ }
+
+ if (nhlink > 1)
+ return;
+
+ /*
+ * Put the first non-symlink name as the very first
+ * entry.
+ */
+ VERIFY3U(firsthlink, !=, UINT_MAX);
+
+ if (firsthlink != 0) {
+ name_t tmp = {
+ .n_name = ns->ns_names[0].n_name,
+ .n_is_symlink = ns->ns_names[0].n_is_symlink,
+ };
+
+ (void) memcpy(&ns->ns_names[0], &ns->ns_names[firsthlink],
+ sizeof (name_t));
+ (void) memcpy(&ns->ns_names[firsthlink], &tmp, sizeof (name_t));
+ }
+
+ avl_add(&avl_byname, obj);
+}
+
+/*
+ * This is an arbitrary initial value -- basically we can nest 16 directories
+ * deep before having to grow p->p_idx (which seems like a nice power of 2).
+ */
+#define PATH_INIT 16
+
+static void
+path_init(path_t *p, const char *name, bool relpath)
+{
+ (void) memset(p, '\0', sizeof (*p));
+
+ if (custr_alloc(&p->p_name) < 0) {
+ nomem();
+ }
+
+ if (name != NULL && custr_append(p->p_name, name) < 0)
+ nomem();
+
+ size_t len = custr_len(p->p_name);
+
+ /* Trim off any trailing /'s, but don't trim '/' to an empty path */
+ while (len > 1 && custr_cstr(p->p_name)[len - 1] == '/') {
+ VERIFY0(custr_rtrunc(p->p_name, 0));
+ len--;
+ }
+
+ p->p_pfxidx = relpath ? len + 1 : 0;
+}
+
+static size_t
+path_append(path_t *p, const char *name)
+{
+ size_t clen = custr_len(p->p_name);
+
+ if (clen > 0)
+ VERIFY0(custr_appendc(p->p_name, '/'));
+ VERIFY0(custr_append(p->p_name, name));
+
+ return (clen);
+}
+
+static const char *
+path_name(const path_t *p)
+{
+ return (custr_cstr(p->p_name) + p->p_pfxidx);
+}
+
+static const char *
+path_fullpath(const path_t *p)
+{
+ return (custr_cstr(p->p_name));
+}
+
+static void
+path_pop(path_t *p, size_t idx)
+{
+ VERIFY0(custr_trunc(p->p_name, idx));
+}
+
+static int
+cmp_id(const void *a, const void *b)
+{
+ const obj_t *l = a;
+ const obj_t *r = b;
+
+ if (l->obj_dev < r->obj_dev)
+ return (-1);
+ if (l->obj_dev > r->obj_dev)
+ return (1);
+ if (l->obj_inode < r->obj_inode)
+ return (-1);
+ if (l->obj_inode > r->obj_inode)
+ return (1);
+ return (0);
+}
+
+static int
+cmp_objname(const void *a, const void *b)
+{
+ const obj_t *l = a;
+ const obj_t *r = b;
+ const name_t *ln = &l->obj_names.ns_names[0];
+ const name_t *rn = &r->obj_names.ns_names[0];
+
+ return (cmp_names(ln, rn));
+}
+
+static int
+cmp_names(const void *a, const void *b)
+{
+ const name_t *l = a;
+ const name_t *r = b;
+ int cmp = strcmp(l->n_name, r->n_name);
+
+ if (cmp < 0)
+ return (-1);
+ if (cmp > 0)
+ return (1);
+ return (0);
+}
+
+/*
+ * Use the filename and permission bits to determine if this file could
+ * possibly be an executable.
+ */
+static bool
+maybe_obj(const char *name, mode_t mode)
+{
+ /* If not in fast mode, check everything, so always return true */
+ if (!fast_mode)
+ return (true);
+
+ size_t len = strlen(name);
+
+ /* If the file name ends in .so, we check */
+ if (len >= 3 && strcmp(&name[len - 4], ".so") == 0) {
+ return (true);
+ }
+
+ /* If the file name contains '.so', we check */
+ if (len >= 4 && strstr(name, ".so.") != NULL) {
+ return (true);
+ }
+
+ /* If the above checks fail, we assume it's not a shared library */
+ if (so_only)
+ return (false);
+
+ /*
+ * The original perl script used a -x test which only looked at
+ * file permissions. This may return slightly different results
+ * than using access(2) or faccessat(2).
+ */
+ if ((mode & (S_IXUSR|S_IXGRP|S_IXOTH)) == 0)
+ return (false);
+
+ return (true);
+}
+
+static void
+foreach_avl(avl_tree_t *avl, void (*cb)(void *, void *), void *arg)
+{
+ void *obj;
+
+ for (obj = avl_first(avl); obj != NULL; obj = AVL_NEXT(avl, obj))
+ cb(obj, arg);
+}
+
+static char *
+xstrdup(const char *s)
+{
+ char *news = strdup(s);
+
+ if (news == NULL) {
+ nomem();
+ }
+ return (news);
+}
+
+static void *
+xcalloc(size_t nelem, size_t elsize)
+{
+ void *p = calloc(nelem, elsize);
+
+ if (p == NULL) {
+ nomem();
+ }
+
+ return (p);
+}
+
+#define NOMEM_MSG "out of memory\n"
+static void __NORETURN
+nomem(void)
+{
+ /* Try to get the error out before aborting */
+ (void) write(STDERR_FILENO, NOMEM_MSG, sizeof (NOMEM_MSG));
+ abort();
+}
diff --git a/usr/src/tools/scripts/Makefile b/usr/src/tools/scripts/Makefile
index d6cd4cbfda..f28b285b14 100644
--- a/usr/src/tools/scripts/Makefile
+++ b/usr/src/tools/scripts/Makefile
@@ -25,6 +25,7 @@
#
# Copyright 2020 Joyent, Inc.
# Copyright 2020 OmniOS Community Edition (OmniOSce) Association.
+# Copyright 2022 Jason King
SHELL=/usr/bin/ksh93
@@ -51,7 +52,6 @@ SHFILES= \
PERLFILES= \
check_rtime \
- find_elf \
interface_check \
interface_cmp \
jstyle \
@@ -86,7 +86,6 @@ MAN1ONBLDFILES= \
check_rtime.1onbld \
ctfconvert.1onbld \
cstyle.1onbld \
- find_elf.1onbld \
flg.flp.1onbld \
git-pbchk.1onbld \
hdrchk.1onbld \
diff --git a/usr/src/tools/scripts/find_elf.pl b/usr/src/tools/scripts/find_elf.pl
deleted file mode 100644
index a1e0afa1f0..0000000000
--- a/usr/src/tools/scripts/find_elf.pl
+++ /dev/null
@@ -1,457 +0,0 @@
-#!/usr/bin/perl -w
-#
-# CDDL HEADER START
-#
-# The contents of this file are subject to the terms of the
-# Common Development and Distribution License (the "License").
-# You may not use this file except in compliance with the License.
-#
-# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
-# or http://www.opensolaris.org/os/licensing.
-# See the License for the specific language governing permissions
-# and limitations under the License.
-#
-# When distributing Covered Code, include this CDDL HEADER in each
-# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
-# If applicable, add the following below this CDDL HEADER, with the
-# fields enclosed by brackets "[]" replaced with your own identifying
-# information: Portions Copyright [yyyy] [name of copyright owner]
-#
-# CDDL HEADER END
-#
-
-#
-# Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved.
-# Copyright 2020 OmniOS Community Edition (OmniOSce) Association.
-#
-
-#
-# Find ELF executables and sharable objects
-#
-# This script descends a directory hierarchy and reports the ELF
-# objects found, one object per line of output.
-#
-# find_elf [-frs] path
-#
-# Where path is a file or directory.
-#
-# Each line of output is of the form:
-#
-# ELFCLASS ELFTYPE VERDEF|NOVERDEF relpath
-#
-# where relpath is the path relative to the directory from which the
-# search started.
-
-use strict;
-
-use vars qw($Prog %Output @SaveArgv);
-use vars qw(%opt $HaveElfedit);
-
-# Hashes used to detect aliases --- symlinks that reference a common file
-#
-# id_hash - Maps the unique st_dev/st_ino pair to the real file
-# alias_hash - Maps symlinks to the real file they reference
-#
-use vars qw(%id_hash %alias_hash);
-
-use POSIX qw(getenv);
-use Getopt::Std;
-use File::Basename;
-use IO::Dir;
-use Config;
-
-BEGIN {
- if ($Config{useithreads}) {
- require threads;
- require threads::shared;
- threads::shared->import(qw(share));
- require Thread::Queue;
- }
-}
-
-#
-# We need to clamp the maximum number of threads that we use. Because
-# this program is mostly an exercise in fork1, more threads doesn't
-# actually help us after a certain point as we just spend more and more
-# time trying to stop all of our LWPs than making forward progress.
-# We've seen experimentally on both 2P systems with 128 cores and 1P
-# systems with 16 cores that around 8 threads is a relative sweet spot.
-#
-chomp (my $NPROCESSORS_ONLN = `getconf NPROCESSORS_ONLN 2>/dev/null` || 1);
-my $max_threads = $ENV{DMAKE_MAX_JOBS} || $NPROCESSORS_ONLN;
-if ($max_threads > 8) {
- $max_threads = 8;
-}
-
-my $tq;
-
-if ($Config{useithreads}) {
- share(%Output);
- share(%id_hash);
- share(%alias_hash);
-
- $tq = Thread::Queue->new;
-}
-
-## GetObjectInfo(path)
-#
-# Return a 3 element output array describing the object
-# given by path. The elements of the array contain:
-#
-# Index Meaning
-# -----------------------------------------------
-# 0 ELFCLASS of object (0 if not an ELF object)
-# 1 Type of object (NONE if not an ELF object)
-# 2 VERDEF if object defines versions, NOVERDEF otherwise
-#
-sub GetObjectInfo {
- my $path = $_[0];
-
- # If elfedit is available, we use it to obtain the desired information
- # by executing three commands in order, to produce a 0, 2, or 3
- # element output array.
- #
- # Command Meaning
- # -----------------------------------------------
- # ehdr:ei_class ELFCLASS of object
- # ehdr:ei_e_type Type of object
- # dyn:tag verdef Address of verdef items
- #
- # We discard stderr, and simply examine the resulting array to
- # determine the situation:
- #
- # # Array Elements Meaning
- # -----------------------------------------------
- # 0 File is not ELF object
- # 2 Object with no versions (no VERDEF)
- # 3 Object that has versions
- if ($HaveElfedit) {
- my $ecmd = "elfedit -r -o simple -e ehdr:ei_class " .
- "-e ehdr:e_type -e 'dyn:tag verdef'";
- my @Elf = split(/\n/, `$ecmd $path 2>/dev/null`);
-
- my $ElfCnt = scalar @Elf;
-
- # Return ET_NONE array if not an ELF object
- return (0, 'NONE', 'NOVERDEF') if ($ElfCnt == 0);
-
- # Otherwise, convert the result to standard form
- $Elf[0] =~ s/^ELFCLASS//;
- $Elf[1] =~ s/^ET_//;
- $Elf[2] = ($ElfCnt == 3) ? 'VERDEF' : 'NOVERDEF';
- return @Elf;
- }
-
- # For older platforms, we use elfdump to get the desired information.
- my @Elf = split(/\n/, `elfdump -ed $path 2>&1`);
- my $Header = 'None';
- my $Verdef = 'NOVERDEF';
- my ($Class, $Type);
-
- foreach my $Line (@Elf) {
- # If we have an invalid file type (which we can tell from the
- # first line), or we're processing an archive, bail.
- if ($Header eq 'None') {
- if (($Line =~ /invalid file/) ||
- ($Line =~ /$path(.*):/)) {
- return (0, 'NONE', 'NOVERDEF');
- }
- }
-
- if ($Line =~ /^ELF Header/) {
- $Header = 'Ehdr';
- next;
- }
-
- if ($Line =~ /^Dynamic Section/) {
- $Header = 'Dyn';
- next;
- }
-
- if ($Header eq 'Ehdr') {
- if ($Line =~ /e_type:\s*ET_([^\s]+)/) {
- $Type = $1;
- next;
- }
- if ($Line =~ /ei_class:\s+ELFCLASS(\d+)/) {
- $Class = $1;
- next;
- }
- next;
- }
-
- if (($Header eq 'Dyn') &&
- ($Line =~ /^\s*\[\d+\]\s+VERDEF\s+/)) {
- $Verdef = 'VERDEF';
- next;
- }
- }
- return ($Class, $Type, $Verdef);
-}
-
-
-## ProcFile(FullPath, RelPath, AliasedPath, IsSymLink, dev, ino)
-#
-# Determine whether this a ELF dynamic object and if so, add a line
-# of output for it to @Output describing it.
-#
-# entry:
-# FullPath - Fully qualified path
-# RelPath - Path relative to starting root directory
-# AliasedPath - True if RelPath contains a symlink directory component.
-# Such a path represents an alias to the same file found
-# completely via actual directories.
-# IsSymLink - True if basename (final component) of path is a symlink.
-#
-sub ProcFile {
- my($FullPath, $RelPath, $AliasedPath, $IsSymLink, $dev, $ino) = @_;
- my(@Elf, @Pvs, @Pvs_don, @Vers, %TopVer);
- my($Aud, $Max, $Priv, $Pub, $ElfCnt, $Val, $Ttl, $NotPlugin);
-
- my $uniqid = sprintf("%llx-%llx", $dev, $ino);
-
- # Remove ./ from front of relative path
- $RelPath =~ s/^\.\///;
-
- my $name = $opt{r} ? $RelPath : $FullPath;
-
- # If this is a symlink, or the path contains a symlink, put it in
- # the alias hash for later analysis. We do this before testing to
- # see if it is an ELF file, because that's a relatively expensive
- # test. The tradeoff is that the alias hash will contain some files
- # we don't care about. That is a small cost.
- if (($IsSymLink || $AliasedPath) && !$opt{a}) {
- $alias_hash{$name} = $uniqid;
- return;
- }
-
- # Obtain the ELF information for this object.
- @Elf = GetObjectInfo($FullPath);
-
- if ($Elf[1] eq 'NONE') {
- return;
- }
-
- # Return quietly if object is executable or relocatable but the -s
- # option was used.
- if ((($Elf[1] eq 'EXEC') || ($Elf[1] eq 'REL')) && $opt{s}) {
- return;
- }
-
- $Output{$name} = sprintf("OBJECT %2s %-4s %-8s %s\n",
- $Elf[0], $Elf[1], $Elf[2], $name);
-
- # Remember it for later alias analysis
- $id_hash{$uniqid} = $name;
-}
-
-
-## ProcDir(FullPath, RelPath, AliasedPath, SelfSymlink)
-#
-# Recursively search directory for dynamic ELF objects, calling
-# ProcFile() on each one.
-#
-# entry:
-# FullPath - Fully qualified path
-# RelPath - Path relative to starting root directory
-# AliasedPath - True if RelPath contains a symlink directory component.
-# Such a path represents an alias to the same file found
-# completely via actual directories.
-# SelfSymlink - True (1) if the last segment in the path is a symlink
-# that points at the same directory (i.e. 32->.). If SelfSymlink
-# is True, ProcDir() examines the given directory for objects,
-# but does not recurse past it. This captures the aliases for
-# those objects, while avoiding entering a recursive loop,
-# or generating nonsensical paths (i.e., 32/amd64/...).
-#
-sub ProcDir {
- if ($Config{useithreads}) {
- threads->create(sub {
- while (my $q = $tq->dequeue) {
- ProcFile(@$q)
- }
- }) for (1 .. $max_threads);
- }
-
- _ProcDir(@_);
-
- if ($Config{useithreads}) {
- $tq->end;
- $_->join for threads->list;
- }
-}
-
-sub _ProcDir {
- my($FullDir, $RelDir, $AliasedPath, $SelfSymlink) = @_;
- my($NewFull, $NewRel, $Entry);
-
- # Open the directory and read each entry, omit files starting with "."
- my $Dir = IO::Dir->new($FullDir);
- if (defined($Dir)) {
- foreach $Entry ($Dir->read()) {
-
- # In fast mode, we skip any file name that starts
- # with a dot, which by side effect also skips the
- # '.' and '..' entries. In regular mode, we must
- # explicitly filter out those entries.
- if ($opt{f}) {
- next if ($Entry =~ /^\./);
- } else {
- next if ($Entry =~ /^\.\.?$/);
- }
-
- $NewFull = join('/', $FullDir, $Entry);
-
- # We need to follow symlinks in order to capture
- # all possible aliases for each object. However,
- # symlinks that point back at the same directory
- # (e.g. 32->.) must be flagged via the SelfSymlink
- # argument to our recursive self in order to avoid
- # taking it more than one level down.
- my $RecurseAliasedPath = $AliasedPath;
- my $RecurseSelfSymlink = 0;
- my $IsSymLink = -l $NewFull;
- if ($IsSymLink) {
- my $trans = readlink($NewFull);
-
- $trans =~ s/\/*$//;
- $RecurseSelfSymlink = 1 if $trans eq '.';
- $RecurseAliasedPath = 1;
- }
-
- if (!stat($NewFull)) {
- next;
- }
- $NewRel = join('/', $RelDir, $Entry);
-
- # Descend into and process any directories.
- if (-d _) {
- # If we have recursed here via a $SelfSymlink,
- # then do not persue directories. We only
- # want to find objects in the same directory
- # via that link.
- next if $SelfSymlink;
-
- _ProcDir($NewFull, $NewRel, $RecurseAliasedPath,
- $RecurseSelfSymlink);
- next;
- }
-
- # In fast mode, we skip objects unless they end with
- # a .so extension, or are executable. We touch
- # considerably fewer files this way.
- if ($opt{f} && !($Entry =~ /\.so$/) &&
- !($Entry =~ /\.so\./) &&
- ($opt{s} || (! -x _))) {
- next;
- }
-
- # Process any standard files.
- if (-f _) {
- my ($dev, $ino) = stat(_);
- if ($Config{useithreads}) {
- $tq->enqueue([ $NewFull, $NewRel,
- $AliasedPath, $IsSymLink, $dev,
- $ino ]);
- }
- else {
- ProcFile($NewFull, $NewRel,
- $AliasedPath, $IsSymLink, $dev,
- $ino);
- }
- next;
- }
-
- }
- $Dir->close();
- }
-}
-
-
-# -----------------------------------------------------------------------------
-
-# Establish a program name for any error diagnostics.
-chomp($Prog = `basename $0`);
-
-# The onbld_elfmod package is maintained in the same directory as this
-# script, and is installed in ../lib/perl. Use the local one if present,
-# and the installed one otherwise.
-my $moddir = dirname($0);
-$moddir = "$moddir/../lib/perl" if ! -f "$moddir/onbld_elfmod.pm";
-require "$moddir/onbld_elfmod.pm";
-
-# Check that we have arguments.
-@SaveArgv = @ARGV;
-if ((getopts('afrs', \%opt) == 0) || (scalar(@ARGV) != 1)) {
- print "usage: $Prog [-frs] file | dir\n";
- print "\t[-a]\texpand symlink aliases\n";
- print "\t[-f]\tuse file name at mode to speed search\n";
- print "\t[-r]\treport relative paths\n";
- print "\t[-s]\tonly remote sharable (ET_DYN) objects\n";
- exit 1;
-}
-
-%Output = ();
-%id_hash = ();
-%alias_hash = ();
-$HaveElfedit = -x '/usr/bin/elfedit';
-
-my $Arg = $ARGV[0];
-my $Error = 0;
-
-ARG: {
- # Process simple files.
- if (-f $Arg) {
- my($RelPath) = $Arg;
-
- if ($opt{r}) {
- my $Prefix = $Arg;
-
- $Prefix =~ s/(^.*)\/.*$/$1/;
- $Prefix = '.' if ($Prefix eq $Arg);
- print "PREFIX $Prefix\n";
- }
- $RelPath =~ s/^.*\//.\//;
- my ($dev, $ino) = stat(_);
- my $IsSymLink = -l $Arg;
- ProcFile($Arg, $RelPath, 0, $IsSymLink, $dev, $ino);
- next;
- }
-
- # Process directories.
- if (-d $Arg) {
- $Arg =~ s/\/$//;
- print "PREFIX $Arg\n" if $opt{r};
- ProcDir($Arg, ".", 0, 0);
- next;
- }
-
- print STDERR "$Prog: not a file or directory: $Arg\n";
- $Error = 1;
-}
-
-# Build a hash, using the primary file name as the key, that has the
-# strings for any aliases to that file.
-my %alias_text = ();
-foreach my $Alias (sort keys %alias_hash) {
- my $id = $alias_hash{$Alias};
- if (defined($id_hash{$id})) {
- my $obj = $id_hash{$id};
- my $str = "ALIAS $id_hash{$id}\t$Alias\n";
-
- if (defined($alias_text{$obj})) {
- $alias_text{$obj} .= $str;
- } else {
- $alias_text{$obj} = $str;
- }
- }
-}
-
-# Output the main files sorted by name. Place the alias lines immediately
-# following each main file.
-foreach my $Path (sort keys %Output) {
- print $Output{$Path};
- print $alias_text{$Path} if defined($alias_text{$Path});
-}
-
-exit $Error;