build with 64bit interfaces
[proj/gcc-config.git] / wrapper.c
1 /*
2 * Copyright 1999-2012 Gentoo Foundation
3 * Distributed under the terms of the GNU General Public License v2
4 * Author: Martin Schlemmer <azarah@gentoo.org>
5 * az's lackey: Mike Frysinger <vapier@gentoo.org>
6 */
7
8 #ifdef DEBUG
9 # define USE_DEBUG 1
10 #else
11 # define USE_DEBUG 0
12 #endif
13
14 #include <errno.h>
15 #include <libgen.h>
16 #include <limits.h>
17 #include <stdio.h>
18 #include <stdlib.h>
19 #include <string.h>
20 #include <unistd.h>
21 #include <sys/stat.h>
22 #include <sys/types.h>
23
24 #define GCC_CONFIG "/usr/bin/gcc-config"
25 #define ENVD_BASE "/etc/env.d/05gcc"
26
27 #define ARRAY_SIZE(arr) (sizeof(arr)/sizeof(arr[0]))
28
29 /* basename(3) is allowed to modify memory */
30 #undef basename
31 #define basename(path) \
32 ({ \
33 char *__path = path; \
34 char *__ret = strrchr(__path, '/'); \
35 __ret ? __ret + 1 : __path; \
36 })
37
38 struct wrapper_data {
39 const char *name;
40 char *fullname, *bin, *path;
41 };
42
43 static const struct {
44 const char *alias;
45 const char *target;
46 } wrapper_aliases[] = {
47 { "cc", "gcc" },
48 { "f77", "gfortran" },
49 { "f95", "gfortran" },
50 };
51
52 #define wrapper_warn(fmt, ...) fprintf(stderr, "%s" fmt "\n", "gcc-config: ", ## __VA_ARGS__)
53 #define wrapper_err(fmt, ...) ({ wrapper_warn("%s" fmt, "error: ", ## __VA_ARGS__); exit(1); })
54 #define wrapper_errp(fmt, ...) wrapper_err(fmt ": %s", ## __VA_ARGS__, strerror(errno))
55 #define wrapper_dbg(fmt, ...) ({ if (USE_DEBUG) wrapper_warn(fmt, ## __VA_ARGS__); })
56
57 #define xmemwrap(func, proto, use) \
58 static void *x ## func proto \
59 { \
60 void *ret = func use; \
61 if (!ret) \
62 wrapper_err(#func "%s", ": out of memory"); \
63 return ret; \
64 }
65 xmemwrap(malloc, (size_t size), (size))
66 xmemwrap(strdup, (const char *s), (s))
67
68 /* check_for_target checks in path for the file we are seeking
69 * it returns 1 if found (with data->bin setup), 0 if not and
70 * negative on error
71 */
72 static int check_for_target(char *path, struct wrapper_data *data)
73 {
74 struct stat sbuf;
75 char str[PATH_MAX + 1];
76 size_t path_len = strlen(path);
77 size_t len = path_len + strlen(data->name) + 2;
78
79 if (sizeof(str) < len)
80 wrapper_warn("path too long: %s", path);
81
82 strcpy(str, path);
83 str[path_len] = '/';
84 str[path_len+1] = '\0';
85 strcat(str, data->name);
86
87 /* Stat possible file to check that
88 * 1) it exist and is a regular file, and
89 * 2) it is not the wrapper itself, and
90 * 3) it is in a /gcc-bin/ directory tree
91 */
92 if (strcmp(str, data->fullname) != 0 &&
93 strstr(str, "/gcc-bin/") != NULL &&
94 stat(str, &sbuf) == 0 &&
95 (S_ISREG(sbuf.st_mode) || S_ISLNK(sbuf.st_mode)))
96 {
97 wrapper_dbg("%s: found in %s", data->name, path);
98 data->bin = xstrdup(str);
99 return 1;
100 }
101
102 wrapper_dbg("%s: did not find in %s", data->name, path);
103 return 0;
104 }
105
106 static int find_target_in_path(struct wrapper_data *data)
107 {
108 char *token = NULL, *state = NULL;
109 char *str;
110
111 if (data->path == NULL)
112 return 0;
113
114 /* Make a copy since strtok_r will modify path */
115 str = xstrdup(data->path);
116
117 /* Find the first file with suitable name in PATH. The idea here is
118 * that we do not want to bind ourselfs to something static like the
119 * default profile, or some odd environment variable, but want to be
120 * able to build something with a non default gcc by just tweaking
121 * the PATH ... */
122 token = strtok_r(str, ":", &state);
123 while (token != NULL) {
124 if (check_for_target(token, data))
125 return 1;
126 token = strtok_r(NULL, ":", &state);
127 }
128
129 wrapper_dbg("%s: did not find in PATH", data->name);
130 return 0;
131 }
132
133 /* find_target_in_envd parses /etc/env.d/05gcc, and tries to
134 * extract PATH, which is set to the current profile's bin
135 * directory ...
136 */
137 static int find_target_in_envd(struct wrapper_data *data, int cross_compile)
138 {
139 FILE *envfile = NULL;
140 char *token = NULL, *state;
141 char str[PATH_MAX + 1];
142 char *strp = str;
143 char envd_file[PATH_MAX + 1];
144
145 if (!cross_compile) {
146 /* for the sake of speed, we'll keep a symlink around for
147 * the native compiler. #190260
148 */
149 snprintf(envd_file, sizeof(envd_file)-1, "/etc/env.d/gcc/.NATIVE");
150 } else {
151 char *ctarget, *end = strrchr(data->name, '-');
152 if (end == NULL)
153 return 0;
154 ctarget = xstrdup(data->name);
155 ctarget[end - data->name] = '\0';
156 snprintf(envd_file, PATH_MAX, "%s-%s", ENVD_BASE, ctarget);
157 free(ctarget);
158 }
159
160 envfile = fopen(envd_file, "r");
161 if (envfile == NULL)
162 return 0;
163
164 while (fgets(strp, PATH_MAX, envfile) != NULL) {
165 /* Keep reading ENVD_FILE until we get a line that
166 * starts with 'GCC_PATH=' ... keep 'PATH=' around
167 * for older gcc versions.
168 */
169 if (strncmp(strp, "GCC_PATH=", strlen("GCC_PATH=")) &&
170 strncmp(strp, "PATH=", strlen("PATH=")))
171 continue;
172
173 token = strtok_r(strp, "=", &state);
174 if ((token != NULL) && token[0])
175 /* The second token should be the value of PATH .. */
176 token = strtok_r(NULL, "=", &state);
177 else
178 goto bail;
179
180 if ((token != NULL) && token[0]) {
181 strp = token;
182 /* A bash variable may be unquoted, quoted with " or
183 * quoted with ', so extract the value without those ..
184 */
185 token = strtok(strp, "\n\"\'");
186
187 while (token != NULL) {
188 if (check_for_target(token, data)) {
189 fclose(envfile);
190 return 1;
191 }
192
193 token = strtok(NULL, "\n\"\'");
194 }
195 }
196
197 strp = str;
198 }
199
200 bail:
201 fclose(envfile);
202 return (cross_compile ? 0 : find_target_in_envd(data, 1));
203 }
204
205 static void find_wrapper_target(struct wrapper_data *data)
206 {
207 if (find_target_in_path(data))
208 return;
209
210 if (find_target_in_envd(data, 0))
211 return;
212
213 /* Only our wrapper is in PATH, so get the CC path using
214 * gcc-config and execute the real binary in there ...
215 */
216 FILE *inpipe = popen("ROOT= " GCC_CONFIG " --get-bin-path", "r");
217 if (inpipe == NULL)
218 wrapper_errp("could not open pipe");
219
220 char str[PATH_MAX + 1];
221 if (fgets(str, PATH_MAX, inpipe) == 0)
222 wrapper_errp("could not get compiler binary path");
223
224 /* chomp! */
225 size_t plen = strlen(str);
226 if (str[plen-1] == '\n')
227 str[plen-1] = '\0';
228
229 data->bin = xmalloc(plen + 1 + strlen(data->name) + 1);
230 sprintf(data->bin, "%s/%s", str, data->name);
231
232 pclose(inpipe);
233 }
234
235 /* This function modifies PATH to have gcc's bin path appended */
236 static void modify_path(struct wrapper_data *data)
237 {
238 char *newpath = NULL, *token = NULL, *state;
239 char dname_data[PATH_MAX + 1], str[PATH_MAX + 1];
240 char *str2 = dname_data, *dname = dname_data;
241 size_t len = 0;
242
243 if (data->bin == NULL)
244 return;
245
246 if (data->path == NULL)
247 return;
248
249 snprintf(str2, PATH_MAX + 1, "%s", data->bin);
250
251 if ((dname = dirname(str2)) == NULL)
252 return;
253
254 /* Make a copy since strtok_r will modify path */
255 snprintf(str, PATH_MAX + 1, "%s", data->path);
256
257 token = strtok_r(str, ":", &state);
258
259 /* Check if we already appended our bin location to PATH */
260 if ((token != NULL) && token[0])
261 if (!strcmp(token, dname))
262 return;
263
264 len = strlen(dname) + strlen(data->path) + 2 + strlen("PATH") + 1;
265
266 newpath = xmalloc(len);
267 memset(newpath, 0, len);
268
269 snprintf(newpath, len, "PATH=%s:%s", dname, data->path);
270 putenv(newpath);
271 }
272
273 int main(int argc, char *argv[])
274 {
275 struct wrapper_data data;
276
277 memset(&data, 0, sizeof(data));
278
279 if (getenv("PATH"))
280 data.path = xstrdup(getenv("PATH"));
281
282 /* What should we find ? */
283 data.name = basename(argv[0]);
284
285 /* Allow for common compiler names like cc->gcc */
286 size_t i;
287 for (i = 0; i < ARRAY_SIZE(wrapper_aliases); ++i)
288 if (!strcmp(data.name, wrapper_aliases[i].alias))
289 data.name = wrapper_aliases[i].target;
290
291 /* What is the full name of our wrapper? */
292 data.fullname = xmalloc(strlen(data.name) + sizeof("/usr/bin/") + 1);
293 sprintf(data.fullname, "/usr/bin/%s", data.name);
294
295 find_wrapper_target(&data);
296
297 modify_path(&data);
298
299 free(data.path);
300 data.path = NULL;
301
302 /* Set argv[0] to the correct binary, else gcc can't find internal headers
303 * http://bugs.gentoo.org/8132
304 */
305 argv[0] = data.bin;
306
307 /* Ok, lets do it one more time ... */
308 execv(data.bin, argv);
309
310 /* shouldn't have made it here if things worked ... */
311 wrapper_err("could not run/locate '%s'", data.name);
312
313 return 123;
314 }