#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;
static bool do_alias = true;
static bool do_special = true;
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(&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);
}
#ifndef O_DIRECTORY
struct stat tsb;
if (stat(dir, &tsb) == -1) {
err(EXIT_FAILURE, "failed to stat %s", dir);
}
if (!S_ISDIR(tsb.st_mode)) {
errx(EXIT_FAILURE, "not a directory: %s", dir);
}
rootfd = open(dir, O_RDONLY);
#else
rootfd = open(dir, O_RDONLY|O_DIRECTORY);
#endif
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);
}
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) {
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;
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(p, fd, &sb, is_link);
}
path_pop(p, plen);
}
VERIFY0(closedir(d));
}
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);
}
}
}
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;
VERIFY3U(names->ns_num, >, 0);
if (names->ns_num == 1)
return;
name_t *first = NULL;
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 (do_special) {
if (is_special(n->n_name)) {
first = n;
break;
}
if (strcmp(n->n_name, first->n_name) < 0)
first = n;
}
}
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);
}
qsort(&names->ns_names[1], names->ns_num - 1, sizeof (name_t),
cmp_names);
}
#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;
newn = xcalloc(newamt, sizeof (name_t));
(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);
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;
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;
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);
}
#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);
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);
}
static bool
maybe_obj(const char *name, mode_t mode)
{
if (!fast_mode)
return (true);
size_t len = strlen(name);
if (len >= 3 && strcmp(&name[len - 3], ".so") == 0) {
return (true);
}
if (len >= 4 && strstr(name, ".so.") != NULL) {
return (true);
}
if (so_only)
return (false);
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)
{
(void) write(STDERR_FILENO, NOMEM_MSG, sizeof (NOMEM_MSG));
abort();
}