rtld: Shrink by eliminating stdio
[dragonfly.git] / libexec / rtld-elf / libmap.c
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 }