aboutsummaryrefslogtreecommitdiff
path: root/libexec/rtld-elf/libmap.c
diff options
context:
space:
mode:
Diffstat (limited to 'libexec/rtld-elf/libmap.c')
-rw-r--r--libexec/rtld-elf/libmap.c497
1 files changed, 497 insertions, 0 deletions
diff --git a/libexec/rtld-elf/libmap.c b/libexec/rtld-elf/libmap.c
new file mode 100644
index 000000000000..26386efcf487
--- /dev/null
+++ b/libexec/rtld-elf/libmap.c
@@ -0,0 +1,497 @@
+/*
+ */
+
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/fcntl.h>
+#include <sys/mman.h>
+#include <sys/queue.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "debug.h"
+#include "rtld.h"
+#include "libmap.h"
+#include "rtld_paths.h"
+#include "rtld_libc.h"
+
+TAILQ_HEAD(lm_list, lm);
+struct lm {
+ char *f;
+ char *t;
+ TAILQ_ENTRY(lm) lm_link;
+};
+
+static TAILQ_HEAD(lmp_list, lmp) lmp_head = TAILQ_HEAD_INITIALIZER(lmp_head);
+struct lmp {
+ char *p;
+ enum { T_EXACT=0, T_BASENAME, T_DIRECTORY } type;
+ struct lm_list lml;
+ TAILQ_ENTRY(lmp) lmp_link;
+};
+
+static TAILQ_HEAD(lmc_list, lmc) lmc_head = TAILQ_HEAD_INITIALIZER(lmc_head);
+struct lmc {
+ char *path;
+ dev_t dev;
+ ino_t ino;
+ TAILQ_ENTRY(lmc) next;
+};
+
+static int lm_count;
+
+static void lmc_parse(char *, size_t);
+static void lmc_parse_file(const char *);
+static void lmc_parse_dir(const char *);
+static void lm_add(const char *, const char *, const char *);
+static void lm_free(struct lm_list *);
+static char *lml_find(struct lm_list *, const char *);
+static struct lm_list *lmp_find(const char *);
+static struct lm_list *lmp_init(char *);
+static const char *quickbasename(const char *);
+
+#define iseol(c) (((c) == '#') || ((c) == '\0') || \
+ ((c) == '\n') || ((c) == '\r'))
+
+/*
+ * Do not use ctype.h macros, which rely on working TLS. Rtld does
+ * not support TLS for itself.
+ */
+#define rtld_isspace(c) ((c) == ' ' || (c) == '\t')
+
+int
+lm_init(const char *libmap_override)
+{
+ char *l, *p;
+
+ dbg("lm_init(\"%s\")", libmap_override);
+ TAILQ_INIT(&lmp_head);
+
+ lmc_parse_file(ld_path_libmap_conf);
+
+ if (libmap_override != NULL) {
+ /*
+ * Do some character replacement to make $LD_LIBMAP look
+ * like a text file, then parse it.
+ */
+ l = xstrdup(libmap_override);
+ for (p = l; *p != 0; p++) {
+ switch (*p) {
+ case '=':
+ *p = ' ';
+ break;
+ case ',':
+ *p = '\n';
+ break;
+ }
+ }
+ lmc_parse(l, p - l);
+ free(l);
+ }
+
+ return (lm_count == 0);
+}
+
+static void
+lmc_parse_file(const char *path)
+{
+ struct lmc *p;
+ char *lm_map;
+ struct stat st;
+ ssize_t retval;
+ int fd, saved_errno;
+
+ TAILQ_FOREACH(p, &lmc_head, next) {
+ if (strcmp(p->path, path) == 0)
+ return;
+ }
+
+ fd = open(path, O_RDONLY | O_CLOEXEC);
+ if (fd == -1) {
+ dbg("lm_parse_file: open(\"%s\") failed, %s", path,
+ rtld_strerror(errno));
+ return;
+ }
+ if (fstat(fd, &st) == -1) {
+ dbg("lm_parse_file: fstat(\"%s\") failed, %s", path,
+ rtld_strerror(errno));
+ close(fd);
+ return;
+ }
+
+ TAILQ_FOREACH(p, &lmc_head, next) {
+ if (p->dev == st.st_dev && p->ino == st.st_ino) {
+ close(fd);
+ return;
+ }
+ }
+
+ lm_map = xmalloc(st.st_size);
+ retval = read(fd, lm_map, st.st_size);
+ saved_errno = errno;
+ close(fd);
+ if (retval != st.st_size) {
+ if (retval == -1) {
+ dbg("lm_parse_file: read(\"%s\") failed, %s", path,
+ rtld_strerror(saved_errno));
+ } else {
+ dbg("lm_parse_file: short read(\"%s\"), %zd vs %jd",
+ path, retval, (uintmax_t)st.st_size);
+ }
+ free(lm_map);
+ return;
+ }
+ p = xmalloc(sizeof(struct lmc));
+ p->path = xstrdup(path);
+ p->dev = st.st_dev;
+ p->ino = st.st_ino;
+ TAILQ_INSERT_HEAD(&lmc_head, p, next);
+ lmc_parse(lm_map, st.st_size);
+ free(lm_map);
+}
+
+static void
+lmc_parse_dir(const char *idir)
+{
+ DIR *d;
+ struct dirent *dp;
+ struct lmc *p;
+ char conffile[MAXPATHLEN];
+ char *ext;
+
+ TAILQ_FOREACH(p, &lmc_head, next) {
+ if (strcmp(p->path, idir) == 0)
+ return;
+ }
+ d = opendir(idir);
+ if (d == NULL)
+ return;
+
+ p = xmalloc(sizeof(struct lmc));
+ p->path = xstrdup(idir);
+ p->dev = NODEV;
+ p->ino = 0;
+ TAILQ_INSERT_HEAD(&lmc_head, p, next);
+
+ while ((dp = readdir(d)) != NULL) {
+ if (dp->d_ino == 0)
+ continue;
+ if (dp->d_type != DT_REG)
+ continue;
+ ext = strrchr(dp->d_name, '.');
+ if (ext == NULL)
+ continue;
+ if (strcmp(ext, ".conf") != 0)
+ continue;
+ if (strlcpy(conffile, idir, MAXPATHLEN) >= MAXPATHLEN)
+ continue; /* too long */
+ if (strlcat(conffile, "/", MAXPATHLEN) >= MAXPATHLEN)
+ continue; /* too long */
+ if (strlcat(conffile, dp->d_name, MAXPATHLEN) >= MAXPATHLEN)
+ continue; /* too long */
+ lmc_parse_file(conffile);
+ }
+ closedir(d);
+}
+
+static void
+lmc_parse(char *lm_p, size_t lm_len)
+{
+ char *cp, *f, *t, *c, *p;
+ char prog[MAXPATHLEN];
+ /* allow includedir + full length path */
+ char line[MAXPATHLEN + 13];
+ size_t cnt, i;
+
+ cnt = 0;
+ p = NULL;
+ while (cnt < lm_len) {
+ i = 0;
+ while (cnt < lm_len && lm_p[cnt] != '\n' &&
+ i < sizeof(line) - 1) {
+ line[i] = lm_p[cnt];
+ cnt++;
+ i++;
+ }
+ line[i] = '\0';
+ while (cnt < lm_len && lm_p[cnt] != '\n')
+ cnt++;
+ /* skip over nl */
+ cnt++;
+
+ cp = &line[0];
+ t = f = c = NULL;
+
+ /* Skip over leading space */
+ while (rtld_isspace(*cp))
+ cp++;
+
+ /* Found a comment or EOL */
+ if (iseol(*cp))
+ continue;
+
+ /* Found a constraint selector */
+ if (*cp == '[') {
+ cp++;
+
+ /* Skip leading space */
+ while (rtld_isspace(*cp))
+ cp++;
+
+ /* Found comment, EOL or end of selector */
+ if (iseol(*cp) || *cp == ']')
+ continue;
+
+ c = cp++;
+ /* Skip to end of word */
+ while (!rtld_isspace(*cp) && !iseol(*cp) && *cp != ']')
+ cp++;
+
+ /* Skip and zero out trailing space */
+ while (rtld_isspace(*cp))
+ *cp++ = '\0';
+
+ /* Check if there is a closing brace */
+ if (*cp != ']')
+ continue;
+
+ /* Terminate string if there was no trailing space */
+ *cp++ = '\0';
+
+ /*
+ * There should be nothing except whitespace or comment
+ from this point to the end of the line.
+ */
+ while (rtld_isspace(*cp))
+ cp++;
+ if (!iseol(*cp))
+ continue;
+
+ if (strlcpy(prog, c, sizeof prog) >= sizeof prog)
+ continue;
+ p = prog;
+ continue;
+ }
+
+ /* Parse the 'from' candidate. */
+ f = cp++;
+ while (!rtld_isspace(*cp) && !iseol(*cp))
+ cp++;
+
+ /* Skip and zero out the trailing whitespace */
+ while (rtld_isspace(*cp))
+ *cp++ = '\0';
+
+ /* Found a comment or EOL */
+ if (iseol(*cp))
+ continue;
+
+ /* Parse 'to' mapping */
+ t = cp++;
+ while (!rtld_isspace(*cp) && !iseol(*cp))
+ cp++;
+
+ /* Skip and zero out the trailing whitespace */
+ while (rtld_isspace(*cp))
+ *cp++ = '\0';
+
+ /* Should be no extra tokens at this point */
+ if (!iseol(*cp))
+ continue;
+
+ *cp = '\0';
+ if (strcmp(f, "includedir") == 0)
+ lmc_parse_dir(t);
+ else if (strcmp(f, "include") == 0)
+ lmc_parse_file(t);
+ else
+ lm_add(p, f, t);
+ }
+}
+
+static void
+lm_free(struct lm_list *lml)
+{
+ struct lm *lm;
+
+ dbg("%s(%p)", __func__, lml);
+
+ while (!TAILQ_EMPTY(lml)) {
+ lm = TAILQ_FIRST(lml);
+ TAILQ_REMOVE(lml, lm, lm_link);
+ free(lm->f);
+ free(lm->t);
+ free(lm);
+ }
+}
+
+void
+lm_fini(void)
+{
+ struct lmp *lmp;
+ struct lmc *p;
+
+ dbg("%s()", __func__);
+
+ while (!TAILQ_EMPTY(&lmc_head)) {
+ p = TAILQ_FIRST(&lmc_head);
+ TAILQ_REMOVE(&lmc_head, p, next);
+ free(p->path);
+ free(p);
+ }
+
+ while (!TAILQ_EMPTY(&lmp_head)) {
+ lmp = TAILQ_FIRST(&lmp_head);
+ TAILQ_REMOVE(&lmp_head, lmp, lmp_link);
+ free(lmp->p);
+ lm_free(&lmp->lml);
+ free(lmp);
+ }
+}
+
+static void
+lm_add(const char *p, const char *f, const char *t)
+{
+ struct lm_list *lml;
+ struct lm *lm;
+ const char *t1;
+
+ if (p == NULL)
+ p = "$DEFAULT$";
+
+ dbg("%s(\"%s\", \"%s\", \"%s\")", __func__, p, f, t);
+
+ if ((lml = lmp_find(p)) == NULL)
+ lml = lmp_init(xstrdup(p));
+
+ t1 = lml_find(lml, f);
+ if (t1 == NULL || strcmp(t1, t) != 0) {
+ lm = xmalloc(sizeof(struct lm));
+ lm->f = xstrdup(f);
+ lm->t = xstrdup(t);
+ TAILQ_INSERT_HEAD(lml, lm, lm_link);
+ lm_count++;
+ }
+}
+
+char *
+lm_find(const char *p, const char *f)
+{
+ struct lm_list *lml;
+ char *t;
+
+ dbg("%s(\"%s\", \"%s\")", __func__, p, f);
+
+ if (p != NULL && (lml = lmp_find(p)) != NULL) {
+ t = lml_find(lml, f);
+ if (t != NULL) {
+ /*
+ * Add a global mapping if we have
+ * a successful constrained match.
+ */
+ lm_add(NULL, f, t);
+ return (t);
+ }
+ }
+ lml = lmp_find("$DEFAULT$");
+ if (lml != NULL)
+ return (lml_find(lml, f));
+ return (NULL);
+}
+
+/*
+ * Given a libmap translation list and a library name, return the
+ * replacement library, or NULL.
+ */
+char *
+lm_findn(const char *p, const char *f, const size_t n)
+{
+ char pathbuf[64], *s, *t;
+
+ if (n < sizeof(pathbuf) - 1)
+ s = pathbuf;
+ else
+ s = xmalloc(n + 1);
+ memcpy(s, f, n);
+ s[n] = '\0';
+ t = lm_find(p, s);
+ if (s != pathbuf)
+ free(s);
+ return (t);
+}
+
+static char *
+lml_find(struct lm_list *lmh, const char *f)
+{
+ struct lm *lm;
+
+ dbg("%s(%p, \"%s\")", __func__, lmh, f);
+
+ TAILQ_FOREACH(lm, lmh, lm_link) {
+ if (strcmp(f, lm->f) == 0)
+ return (lm->t);
+ }
+ return (NULL);
+}
+
+/*
+ * Given an executable name, return a pointer to the translation list or
+ * NULL if no matches.
+ */
+static struct lm_list *
+lmp_find(const char *n)
+{
+ struct lmp *lmp;
+
+ dbg("%s(\"%s\")", __func__, n);
+
+ TAILQ_FOREACH(lmp, &lmp_head, lmp_link) {
+ if ((lmp->type == T_EXACT && strcmp(n, lmp->p) == 0) ||
+ (lmp->type == T_DIRECTORY && strncmp(n, lmp->p,
+ strlen(lmp->p)) == 0) ||
+ (lmp->type == T_BASENAME && strcmp(quickbasename(n),
+ lmp->p) == 0))
+ return (&lmp->lml);
+ }
+ return (NULL);
+}
+
+static struct lm_list *
+lmp_init(char *n)
+{
+ struct lmp *lmp;
+
+ dbg("%s(\"%s\")", __func__, n);
+
+ lmp = xmalloc(sizeof(struct lmp));
+ lmp->p = n;
+ if (n[strlen(n) - 1] == '/')
+ lmp->type = T_DIRECTORY;
+ else if (strchr(n,'/') == NULL)
+ lmp->type = T_BASENAME;
+ else
+ lmp->type = T_EXACT;
+ TAILQ_INIT(&lmp->lml);
+ TAILQ_INSERT_HEAD(&lmp_head, lmp, lmp_link);
+
+ return (&lmp->lml);
+}
+
+/*
+ * libc basename is overkill. Return a pointer to the character after
+ * the last /, or the original string if there are no slashes.
+ */
+static const char *
+quickbasename(const char *path)
+{
+ const char *p;
+
+ for (p = path; *path != '\0'; path++) {
+ if (*path == '/')
+ p = path + 1;
+ }
+ return (p);
+}