libmap.c revision 232974
1/*
2 * $FreeBSD: head/libexec/rtld-elf/libmap.c 232974 2012-03-14 15:39:59Z kib $
3 */
4
5#include <sys/param.h>
6#include <sys/fcntl.h>
7#include <sys/mman.h>
8#include <sys/queue.h>
9#include <sys/stat.h>
10#include <errno.h>
11#include <stdlib.h>
12#include <string.h>
13
14#include "debug.h"
15#include "rtld.h"
16#include "libmap.h"
17
18#ifndef _PATH_LIBMAP_CONF
19#define	_PATH_LIBMAP_CONF	"/etc/libmap.conf"
20#endif
21
22#ifdef COMPAT_32BIT
23#undef _PATH_LIBMAP_CONF
24#define	_PATH_LIBMAP_CONF	"/etc/libmap32.conf"
25#endif
26
27TAILQ_HEAD(lm_list, lm);
28struct lm {
29	char *f;
30	char *t;
31	TAILQ_ENTRY(lm)	lm_link;
32};
33
34TAILQ_HEAD(lmp_list, lmp) lmp_head = TAILQ_HEAD_INITIALIZER(lmp_head);
35struct lmp {
36	char *p;
37	enum { T_EXACT=0, T_BASENAME, T_DIRECTORY } type;
38	struct lm_list lml;
39	TAILQ_ENTRY(lmp) lmp_link;
40};
41
42static int lm_count;
43
44static void lmc_parse(char *, size_t);
45static void lm_add(const char *, const char *, const char *);
46static void lm_free(struct lm_list *);
47static char *lml_find(struct lm_list *, const char *);
48static struct lm_list *lmp_find(const char *);
49static struct lm_list *lmp_init(char *);
50static const char *quickbasename(const char *);
51
52#define	iseol(c)	(((c) == '#') || ((c) == '\0') || \
53			 ((c) == '\n') || ((c) == '\r'))
54
55/*
56 * Do not use ctype.h macros, which rely on working TLS.  It is
57 * too early to have thread-local variables functional.
58 */
59#define	rtld_isspace(c)	((c) == ' ' || (c) == '\t')
60
61int
62lm_init(char *libmap_override)
63{
64	struct stat st;
65	char *lm_map, *p;
66	int fd;
67
68	dbg("lm_init(\"%s\")", libmap_override);
69	TAILQ_INIT(&lmp_head);
70
71	fd = open(_PATH_LIBMAP_CONF, O_RDONLY);
72	if (fd == -1) {
73		dbg("lm_init: open(\"%s\") failed, %s", _PATH_LIBMAP_CONF,
74		    rtld_strerror(errno));
75		goto override;
76	}
77	if (fstat(fd, &st) == -1) {
78		close(fd);
79		dbg("lm_init: fstat(\"%s\") failed, %s", _PATH_LIBMAP_CONF,
80		    rtld_strerror(errno));
81		goto override;
82	}
83	lm_map = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
84	if (lm_map == (const char *)MAP_FAILED) {
85		close(fd);
86		dbg("lm_init: mmap(\"%s\") failed, %s", _PATH_LIBMAP_CONF,
87		    rtld_strerror(errno));
88		goto override;
89	}
90	close(fd);
91	lmc_parse(lm_map, st.st_size);
92	munmap(lm_map, st.st_size);
93
94override:
95	if (libmap_override) {
96		/*
97		 * Do some character replacement to make $LIBMAP look
98		 * like a text file, then parse it.
99		 */
100		libmap_override = xstrdup(libmap_override);
101		for (p = libmap_override; *p; p++) {
102			switch (*p) {
103			case '=':
104				*p = ' ';
105				break;
106			case ',':
107				*p = '\n';
108				break;
109			}
110		}
111		lmc_parse(p, strlen(p));
112		free(p);
113	}
114
115	return (lm_count == 0);
116}
117
118static void
119lmc_parse(char *lm_p, size_t lm_len)
120{
121	char *cp, *f, *t, *c, *p;
122	char prog[MAXPATHLEN];
123	char line[MAXPATHLEN + 2];
124	size_t cnt;
125	int i;
126
127	cnt = 0;
128	p = NULL;
129	while (cnt < lm_len) {
130		i = 0;
131		while (lm_p[cnt] != '\n' && cnt < lm_len &&
132		    i < sizeof(line) - 1) {
133			line[i] = lm_p[cnt];
134			cnt++;
135			i++;
136		}
137		line[i] = '\0';
138		while (lm_p[cnt] != '\n' && cnt < lm_len)
139			cnt++;
140		/* skip over nl */
141		cnt++;
142
143		cp = &line[0];
144		t = f = c = NULL;
145
146		/* Skip over leading space */
147		while (rtld_isspace(*cp)) cp++;
148
149		/* Found a comment or EOL */
150		if (iseol(*cp)) continue;
151
152		/* Found a constraint selector */
153		if (*cp == '[') {
154			cp++;
155
156			/* Skip leading space */
157			while (rtld_isspace(*cp)) cp++;
158
159			/* Found comment, EOL or end of selector */
160			if  (iseol(*cp) || *cp == ']')
161				continue;
162
163			c = cp++;
164			/* Skip to end of word */
165			while (!rtld_isspace(*cp) && !iseol(*cp) && *cp != ']')
166				cp++;
167
168			/* Skip and zero out trailing space */
169			while (rtld_isspace(*cp)) *cp++ = '\0';
170
171			/* Check if there is a closing brace */
172			if (*cp != ']') continue;
173
174			/* Terminate string if there was no trailing space */
175			*cp++ = '\0';
176
177			/*
178			 * There should be nothing except whitespace or comment
179			  from this point to the end of the line.
180			 */
181			while(rtld_isspace(*cp)) cp++;
182			if (!iseol(*cp)) continue;
183
184			strcpy(prog, c);
185			p = prog;
186			continue;
187		}
188
189		/* Parse the 'from' candidate. */
190		f = cp++;
191		while (!rtld_isspace(*cp) && !iseol(*cp)) cp++;
192
193		/* Skip and zero out the trailing whitespace */
194		while (rtld_isspace(*cp)) *cp++ = '\0';
195
196		/* Found a comment or EOL */
197		if (iseol(*cp)) continue;
198
199		/* Parse 'to' mapping */
200		t = cp++;
201		while (!rtld_isspace(*cp) && !iseol(*cp)) cp++;
202
203		/* Skip and zero out the trailing whitespace */
204		while (rtld_isspace(*cp)) *cp++ = '\0';
205
206		/* Should be no extra tokens at this point */
207		if (!iseol(*cp)) continue;
208
209		*cp = '\0';
210		lm_add(p, f, t);
211	}
212}
213
214static void
215lm_free (struct lm_list *lml)
216{
217	struct lm *lm;
218
219	dbg("%s(%p)", __func__, lml);
220
221	while (!TAILQ_EMPTY(lml)) {
222		lm = TAILQ_FIRST(lml);
223		TAILQ_REMOVE(lml, lm, lm_link);
224		free(lm->f);
225		free(lm->t);
226		free(lm);
227	}
228	return;
229}
230
231void
232lm_fini (void)
233{
234	struct lmp *lmp;
235
236	dbg("%s()", __func__);
237
238	while (!TAILQ_EMPTY(&lmp_head)) {
239		lmp = TAILQ_FIRST(&lmp_head);
240		TAILQ_REMOVE(&lmp_head, lmp, lmp_link);
241		free(lmp->p);
242		lm_free(&lmp->lml);
243		free(lmp);
244	}
245	return;
246}
247
248static void
249lm_add (const char *p, const char *f, const char *t)
250{
251	struct lm_list *lml;
252	struct lm *lm;
253
254	if (p == NULL)
255		p = "$DEFAULT$";
256
257	dbg("%s(\"%s\", \"%s\", \"%s\")", __func__, p, f, t);
258
259	if ((lml = lmp_find(p)) == NULL)
260		lml = lmp_init(xstrdup(p));
261
262	lm = xmalloc(sizeof(struct lm));
263	lm->f = xstrdup(f);
264	lm->t = xstrdup(t);
265	TAILQ_INSERT_HEAD(lml, lm, lm_link);
266	lm_count++;
267}
268
269char *
270lm_find (const char *p, const char *f)
271{
272	struct lm_list *lml;
273	char *t;
274
275	dbg("%s(\"%s\", \"%s\")", __func__, p, f);
276
277	if (p != NULL && (lml = lmp_find(p)) != NULL) {
278		t = lml_find(lml, f);
279		if (t != NULL) {
280			/*
281			 * Add a global mapping if we have
282			 * a successful constrained match.
283			 */
284			lm_add(NULL, f, t);
285			return (t);
286		}
287	}
288	lml = lmp_find("$DEFAULT$");
289	if (lml != NULL)
290		return (lml_find(lml, f));
291	else
292		return (NULL);
293}
294
295/* Given a libmap translation list and a library name, return the
296   replacement library, or NULL */
297#ifdef COMPAT_32BIT
298char *
299lm_findn (const char *p, const char *f, const int n)
300{
301	char pathbuf[64], *s, *t;
302
303	if (n < sizeof(pathbuf) - 1)
304		s = pathbuf;
305	else
306		s = xmalloc(n + 1);
307	memcpy(s, f, n);
308	s[n] = '\0';
309	t = lm_find(p, s);
310	if (s != pathbuf)
311		free(s);
312	return (t);
313}
314#endif
315
316static char *
317lml_find (struct lm_list *lmh, const char *f)
318{
319	struct lm *lm;
320
321	dbg("%s(%p, \"%s\")", __func__, lmh, f);
322
323	TAILQ_FOREACH(lm, lmh, lm_link)
324		if (strcmp(f, lm->f) == 0)
325			return (lm->t);
326	return (NULL);
327}
328
329/* Given an executable name, return a pointer to the translation list or
330   NULL if no matches */
331static struct lm_list *
332lmp_find (const char *n)
333{
334	struct lmp *lmp;
335
336	dbg("%s(\"%s\")", __func__, n);
337
338	TAILQ_FOREACH(lmp, &lmp_head, lmp_link)
339		if ((lmp->type == T_EXACT && strcmp(n, lmp->p) == 0) ||
340		    (lmp->type == T_DIRECTORY && strncmp(n, lmp->p, strlen(lmp->p)) == 0) ||
341		    (lmp->type == T_BASENAME && strcmp(quickbasename(n), lmp->p) == 0))
342			return (&lmp->lml);
343	return (NULL);
344}
345
346static struct lm_list *
347lmp_init (char *n)
348{
349	struct lmp *lmp;
350
351	dbg("%s(\"%s\")", __func__, n);
352
353	lmp = xmalloc(sizeof(struct lmp));
354	lmp->p = n;
355	if (n[strlen(n)-1] == '/')
356		lmp->type = T_DIRECTORY;
357	else if (strchr(n,'/') == NULL)
358		lmp->type = T_BASENAME;
359	else
360		lmp->type = T_EXACT;
361	TAILQ_INIT(&lmp->lml);
362	TAILQ_INSERT_HEAD(&lmp_head, lmp, lmp_link);
363
364	return (&lmp->lml);
365}
366
367/* libc basename is overkill.  Return a pointer to the character after the
368   last /, or the original string if there are no slashes. */
369static const char *
370quickbasename (const char *path)
371{
372	const char *p = path;
373	for (; *path; path++) {
374		if (*path == '/')
375			p = path+1;
376	}
377	return (p);
378}
379