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