diff options
Diffstat (limited to 'usr/src')
| -rw-r--r-- | usr/src/pkg/manifests/developer-build-onbld.p5m | 3 | ||||
| -rw-r--r-- | usr/src/tools/Makefile | 2 | ||||
| -rw-r--r-- | usr/src/tools/find_elf/Makefile | 68 | ||||
| -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.c | 863 | ||||
| -rw-r--r-- | usr/src/tools/scripts/Makefile | 3 | ||||
| -rw-r--r-- | usr/src/tools/scripts/find_elf.pl | 457 |
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; |
