xref: /freebsd/sbin/ldconfig/elfhints.c (revision 23f282aa31e9b6fceacd449020e936e98d6f2298)
1 /*-
2  * Copyright (c) 1998 John D. Polstra
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24  * SUCH DAMAGE.
25  *
26  * $FreeBSD$
27  */
28 
29 #include <sys/param.h>
30 #include <sys/mman.h>
31 #include <sys/stat.h>
32 
33 #include <ctype.h>
34 #include <dirent.h>
35 #include <elf.h>
36 #include <err.h>
37 #include <errno.h>
38 #include <fcntl.h>
39 #include <stdio.h>
40 #include <stdlib.h>
41 #include <string.h>
42 #include <unistd.h>
43 
44 #include "ldconfig.h"
45 
46 #define MAXDIRS		1024		/* Maximum directories in path */
47 #define MAXFILESIZE	(16*1024)	/* Maximum hints file size */
48 
49 static void	add_dir(const char *, const char *);
50 static void	read_dirs_from_file(const char *, const char *);
51 static void	read_elf_hints(const char *, int);
52 static void	write_elf_hints(const char *);
53 
54 static const char	*dirs[MAXDIRS];
55 static int		 ndirs;
56 
57 static void
58 add_dir(const char *hintsfile, const char *name)
59 {
60 	int	i;
61 
62 	for (i = 0;  i < ndirs;  i++)
63 		if (strcmp(dirs[i], name) == 0)
64 			return;
65 	if (ndirs >= MAXDIRS)
66 		errx(1, "\"%s\": Too many directories in path", hintsfile);
67 	dirs[ndirs++] = name;
68 }
69 
70 void
71 list_elf_hints(const char *hintsfile)
72 {
73 	int	i;
74 	int	nlibs;
75 
76 	read_elf_hints(hintsfile, 1);
77 	printf("%s:\n", hintsfile);
78 	printf("\tsearch directories:");
79 	for (i = 0;  i < ndirs;  i++)
80 		printf("%c%s", i == 0 ? ' ' : ':', dirs[i]);
81 	printf("\n");
82 
83 	nlibs = 0;
84 	for (i = 0;  i < ndirs;  i++) {
85 		DIR		*dirp;
86 		struct dirent	*dp;
87 
88 		if ((dirp = opendir(dirs[i])) == NULL)
89 			continue;
90 		while ((dp = readdir(dirp)) != NULL) {
91 			int		 len;
92 			int		 namelen;
93 			const char	*name;
94 			const char	*vers;
95 
96 			/* Name can't be shorter than "libx.so.0" */
97 			if ((len = strlen(dp->d_name)) < 9 ||
98 			    strncmp(dp->d_name, "lib", 3) != 0)
99 				continue;
100 			name = dp->d_name + 3;
101 			vers = dp->d_name + len;
102 			while (vers > dp->d_name && isdigit(*(vers-1)))
103 				vers--;
104 			if (vers == dp->d_name + len)
105 				continue;
106 			if (vers < dp->d_name + 4 ||
107 			    strncmp(vers - 4, ".so.", 4) != 0)
108 				continue;
109 
110 			/* We have a valid shared library name. */
111 			namelen = (vers - 4) - name;
112 			printf("\t%d:-l%.*s.%s => %s/%s\n", nlibs,
113 			    namelen, name, vers, dirs[i], dp->d_name);
114 			nlibs++;
115 		}
116 		closedir(dirp);
117 	}
118 }
119 
120 static void
121 read_dirs_from_file(const char *hintsfile, const char *listfile)
122 {
123 	FILE	*fp;
124 	char	 buf[MAXPATHLEN];
125 	int	 linenum;
126 
127 	if ((fp = fopen(listfile, "r")) == NULL)
128 		err(1, "%s", listfile);
129 
130 	linenum = 0;
131 	while (fgets(buf, sizeof buf, fp) != NULL) {
132 		char	*cp, *sp;
133 
134 		linenum++;
135 		cp = buf;
136 		/* Skip leading white space. */
137 		while (isspace(*cp))
138 			cp++;
139 		if (*cp == '#' || *cp == '\0')
140 			continue;
141 		sp = cp;
142 		/* Advance over the directory name. */
143 		while (!isspace(*cp) && *cp != '\0')
144 			cp++;
145 		/* Terminate the string and skip trailing white space. */
146 		if (*cp != '\0') {
147 			*cp++ = '\0';
148 			while (isspace(*cp))
149 				cp++;
150 		}
151 		/* Now we had better be at the end of the line. */
152 		if (*cp != '\0')
153 			warnx("%s:%d: trailing characters ignored",
154 			    listfile, linenum);
155 
156 		if ((sp = strdup(sp)) == NULL)
157 			errx(1, "Out of memory");
158 		add_dir(hintsfile, sp);
159 	}
160 
161 	fclose(fp);
162 }
163 
164 static void
165 read_elf_hints(const char *hintsfile, int must_exist)
166 {
167 	int	 		 fd;
168 	struct stat		 s;
169 	void			*mapbase;
170 	struct elfhints_hdr	*hdr;
171 	char			*strtab;
172 	char			*dirlist;
173 	char			*p;
174 
175 	if ((fd = open(hintsfile, O_RDONLY)) == -1) {
176 		if (errno == ENOENT && !must_exist)
177 			return;
178 		err(1, "Cannot open \"%s\"", hintsfile);
179 	}
180 	if (fstat(fd, &s) == -1)
181 		err(1, "Cannot stat \"%s\"", hintsfile);
182 	if (s.st_size > MAXFILESIZE)
183 		errx(1, "\"%s\" is unreasonably large", hintsfile);
184 	/*
185 	 * We use a read-write, private mapping so that we can null-terminate
186 	 * some strings in it without affecting the underlying file.
187 	 */
188 	mapbase = mmap(NULL, s.st_size, PROT_READ|PROT_WRITE,
189 	    MAP_PRIVATE, fd, 0);
190 	if (mapbase == MAP_FAILED)
191 		err(1, "Cannot mmap \"%s\"", hintsfile);
192 	close(fd);
193 
194 	hdr = (struct elfhints_hdr *)mapbase;
195 	if (hdr->magic != ELFHINTS_MAGIC)
196 		errx(1, "\"%s\": invalid file format", hintsfile);
197 	if (hdr->version != 1)
198 		errx(1, "\"%s\": unrecognized file version (%d)", hintsfile,
199 		    hdr->version);
200 
201 	strtab = (char *)mapbase + hdr->strtab;
202 	dirlist = strtab + hdr->dirlist;
203 
204 	if (*dirlist != '\0')
205 		while ((p = strsep(&dirlist, ":")) != NULL)
206 			add_dir(hintsfile, p);
207 }
208 
209 void
210 update_elf_hints(const char *hintsfile, int argc, char **argv, int merge)
211 {
212 	int	i;
213 
214 	if (merge)
215 		read_elf_hints(hintsfile, 0);
216 	for (i = 0;  i < argc;  i++) {
217 		struct stat	s;
218 
219 		if (stat(argv[i], &s) == -1)
220 			warn("warning: %s", argv[i]);
221 		else if (S_ISREG(s.st_mode))
222 			read_dirs_from_file(hintsfile, argv[i]);
223 		else
224 			add_dir(hintsfile, argv[i]);
225 	}
226 	write_elf_hints(hintsfile);
227 }
228 
229 static void
230 write_elf_hints(const char *hintsfile)
231 {
232 	struct elfhints_hdr	 hdr;
233 	char			*tempname;
234 	int			 fd;
235 	FILE			*fp;
236 	int			 i;
237 
238 	if (asprintf(&tempname, "%s.XXXXXX", hintsfile) == -1)
239 		errx(1, "Out of memory");
240 	if ((fd = mkstemp(tempname)) ==  -1)
241 		err(1, "mkstemp(%s)", tempname);
242 	if (fchmod(fd, 0444) == -1)
243 		err(1, "fchmod(%s)", tempname);
244 	if ((fp = fdopen(fd, "wb")) == NULL)
245 		err(1, "fdopen(%s)", tempname);
246 
247 	hdr.magic = ELFHINTS_MAGIC;
248 	hdr.version = 1;
249 	hdr.strtab = sizeof hdr;
250 	hdr.strsize = 0;
251 	hdr.dirlist = 0;
252 	memset(hdr.spare, 0, sizeof hdr.spare);
253 
254 	/* Count up the size of the string table. */
255 	if (ndirs > 0) {
256 		hdr.strsize += strlen(dirs[0]);
257 		for (i = 1;  i < ndirs;  i++)
258 			hdr.strsize += 1 + strlen(dirs[i]);
259 	}
260 	hdr.dirlistlen = hdr.strsize;
261 	hdr.strsize++;	/* For the null terminator */
262 
263 	/* Write the header. */
264 	if (fwrite(&hdr, 1, sizeof hdr, fp) != sizeof hdr)
265 		err(1, "%s: write error", tempname);
266 	/* Write the strings. */
267 	if (ndirs > 0) {
268 		if (fputs(dirs[0], fp) == EOF)
269 			err(1, "%s: write error", tempname);
270 		for (i = 1;  i < ndirs;  i++)
271 			if (fprintf(fp, ":%s", dirs[i]) < 0)
272 				err(1, "%s: write error", tempname);
273 	}
274 	if (putc('\0', fp) == EOF || fclose(fp) == EOF)
275 		err(1, "%s: write error", tempname);
276 
277 	if (rename(tempname, hintsfile) == -1)
278 		err(1, "rename %s to %s", tempname, hintsfile);
279 	free(tempname);
280 }
281