/* * Copyright 1999-2003 Gentoo Technologies, Inc. * Distributed under the terms of the GNU General Public License v2 * Author: Martin Schlemmer * $Header: /home/cvsroot/gentoo-x86/sys-devel/gcc-config/files/wrapper-1.4.1.c,v 1.1 2003/04/12 18:44:22 azarah Exp $ */ #define _REENTRANT #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #define BIN_PATH "/usr/bin" /* common definitions -- should be configurable */ #define GCC_CONFIG "/usr/bin/gcc-config" /* ENVD_PATH is the location of gcc-config's configuration files */ #define ENVD_PATH "/etc/env.d/gcc" /* HOME_PATH is the relative path in each users $HOME directory; by default, this creates a subdirectory for all the configuration files */ #define HOME_PATH ".gcc-config" /* CONFIG_FILE is the base name for config files; with non-native toolchains, this will be suffixed with "-${CHOST}" */ #define CONFIG_FILE "config" /* ENVD_FILE is the name of the fallback env file (relative to ENVD_PATH */ #define ENVD_FILE "../05gcc" struct wrapper_data { /* our inputs */ char *chost; /* determined via argv[0], CBUILD, or CHOST */ char *name; /* munged basename of argv[0] */ char *fullname; /* /usr/bin/${name} */ /* our output */ char *bin; /* holds fully qualified name of the "real" binary */ }; /* ============================================================== */ static void wrapper_exit(char *msg, ...) { va_list args; va_start(args, msg); vfprintf(stderr, msg, args); va_end(args); exit(1); } static void *xmalloc(size_t len) { void *x = malloc(len); if (NULL == x) wrapper_exit("gcc-config wrapper: unable to allocate memory\n"); return x; } static char *xstrdup(const char *str) { void *x = strdup(str); if (NULL == x) wrapper_exit("gcc-config wrapper: unable to allocate string\n"); return x; } static char *join_filename(char *dir, char *name) { char *str; size_t len; len = strlen(dir) + 1 + strlen(name) + 1; if (len > MAXPATHLEN) wrapper_exit("%s wrapper: path too long", name); str = xmalloc(len); sprintf(str, "%s/%s", dir, name); return str; } static const char *wrapper_strerror(int err) { char *str = xmalloc(MAXPATHLEN); strerror_r(err, str, sizeof(str)); return str; } /* ============================================================== */ /* strip_shell_stuff cleanses a parameter set in a bash assignment returns a portion of passed string, possibly shortened */ static char* strip_shell_stuff(char *str) { size_t len = strlen(str), last; /* remove possible trailing newline */ if ((0 != len) && ('\n' == str[len - 1])) len -= 1; /* strip matching pairs of quotes */ last = len - 1; while ( (len > 2) && ((('\'' == str[0]) && ('\'' == str[last])) || (('\"' == str[0]) && ('\"' == str[last]))) ) { str++; len -= 2; last = len - 1; } /* now (re)terminate string at (possibly shorter) len */ str[len] = 0; return str; } /* ============================================================== */ static void wrapper_setenv(char *str, char* value) { if (-1 == setenv(str, value, 1)) wrapper_exit("wrapper: unable to update environment"); } /* wrapper_tokenize works like strtok but takes a function pointer that allows each token to be parsed differently. It also only works on colon delimited strings. */ static int wrapper_tokenize(char *str, int (*func)(char *token, void *data), void *data) { char *s; char *state; char *token; int result; s = xstrdup(str); token = strtok_r(s, ":", &state); do { result = (*func)(token, data); if (0 != result) break; } while (NULL != (token = strtok_r(NULL, ":", &state))); free(s); return result; } /* find_str_in_list searches a colon delimted list for an occurance of str; returns 1 if found, 0 if not. find_str_parse_callback is called for each item in list */ static int find_str_token_callback(char *token, void *data) { return (0 == strcmp(token, (char*)data)); } static int find_str_in_list(char *str, char* list) { wrapper_tokenize(list, find_str_token_callback, str); return 0; } /* need_to_prepend returns 1 if value is appropriately located in the envval; if atstart is 1, value *must* appear at the very beginning of the list; otherwise, it can be anywhere */ static int need_to_prepend(char *envval, char *value, int atstart) { int result; if (atstart) { /* envval may already have value first */ size_t len = strlen(value); result = (0 != strncmp(envval, value, len)) || ((':' != envval[len]) && (0 != envval[len])); } else { /* it can be anywhere -- hmmmm, what would use this? */ result = !find_str_in_list(value, envval); } return result; } /* prepend_env_value adds the specified env value prepended to the appropriate env var */ static void prepend_env_value(char *key, char* value, int atstart) { /* yah, see, tha thing is... */ char *envval; char *newval; envval = getenv(key); if (NULL == envval) { wrapper_setenv(key, value); return; } /* only prepend the value if required */ if (need_to_prepend(envval, value, atstart)) { /* create new env string and store it in env */ newval = xmalloc(strlen(value) + 1 + strlen(envval) + 1); sprintf(newval, "%s:%s", value, envval); wrapper_setenv(key, newval); free(newval); } } /* check_config_line does basic sanity checks on lines read from configuration files. This avoids errors and extra processing. */ static int check_config_line(char *str, char *file, int line) { size_t len = strlen(str); if ((MAXPATHLEN == len) && ('\n' != str[len - 1])) wrapper_exit("%s: line %d is too long", file, line); if ('#' == str[0]) return 0; return 1; } /* get_config_value returns the value of a configuration setting, this is used to read 1) the chost configuration file, which specifies 2) the chost environment file, which has the bin path Note: if prefix is NULL, all env vars are set into active env */ static char *get_config_value(char *prefix, char *name) { FILE *file = NULL; char *value = NULL; char *path = NULL; char *str; size_t len; int line = 1; str = xmalloc(MAXPATHLEN + 1); file = fopen(name, "r"); if (NULL == file) return 0; if (NULL != prefix) len = strlen(prefix); while (NULL != fgets(str, MAXPATHLEN, file)) { /* check for errors and skip over useless stuff */ if (!check_config_line(str, name, line++)) continue; value = strchr(str, '='); if (NULL == value) { fprintf(stderr, "%s:%d: unexpected input: %s\n", name, line, str); continue; } *value++ = 0; if (NULL != prefix) { if (0 == strcmp(prefix, str)) break; } else { /* set up everthing, but only vars that matter */ if (0 == strcmp(str, "PATH")) { path = xstrdup(value); } else if (0 == strcmp(str, "LDPATH")) { prepend_env_value(str, strip_shell_stuff(value), 1); } else if (0 == strcmp(str, "STDCXX_INCDIR")) wrapper_setenv(str, strip_shell_stuff(value)); } } fclose(file); if (NULL != path) { strcpy(str, path); value = str; free(path); } /* if we have a value, strip it and dup it for return */ if (NULL != value) value = xstrdup(strip_shell_stuff(value)); free(str); return value; } /* ============================================================== */ static char* check_config_file(char *path, char *conffile) { char *confname; struct stat s; int result; confname = join_filename(path, conffile); result = access(confname, R_OK | F_OK); if (0 == result) { result = stat(confname, &s); if ((0 == result) && !(s.st_mode & S_IFREG)) result = -1; } if (0 != result) { free(confname); confname = NULL; } return confname; } /* make_chost_config_name returns "config" or "config-${CHOST}" (today) */ static char* make_chost_config_name(char *chost) { char *confname; if (NULL != chost) { confname = xmalloc(strlen(CONFIG_FILE) + 1 + strlen(chost) + 1); sprintf(confname, "%s-%s", CONFIG_FILE, chost); } else confname = xstrdup(CONFIG_FILE); return confname; } /* check_home_config_file tries to access the user's own config file, returning its full path name if successful*/ static char *check_home_config_file(char *conffile) { char *confname = NULL; char *home; home = getenv("HOME"); if (NULL != home) { char *confdir = join_filename(home, HOME_PATH); confname = check_config_file(confdir, conffile); free(confdir); } return confname; } /* get_chost_config_filename returns the name of the gcc-config configuration file for the specified chost. This file contains the "CURRENT" setting, which specifies the active envd file. The search order for this is the following: 1) look for a profile matching chost in GCCC_PROFILES */ static char *get_chost_config_filename(char *chost) { char *conffile; char *confname; /* 2) look for $HOME/.gcc-config/ */ conffile = make_chost_config_name(chost); confname = check_home_config_file(conffile); /* 3) if not found, look for /etc/env.d/gcc/ */ if (NULL == confname) confname = check_config_file(ENVD_PATH, conffile); free(conffile); return confname; } /* find_env_profile_* are used to locate the CURRENT spec for chost from the GCCC_PROFILES list. */ struct find_env_profile_data { char *profile; char *chost; size_t chostlen; }; static int find_env_profile_callback(char *token, void *data) { struct find_env_profile_data *pd = data; if (0 == strncmp(token, pd->chost, pd->chostlen)) { pd->profile = xstrdup(token); return 1; } return 0; } static char *find_env_profile_name(char *chost, char *profiles) { struct find_env_profile_data *pd; char *conffile; pd = xmalloc(sizeof(*pd)); pd->chost = chost; pd->chostlen = strlen(chost); pd->profile = NULL; (void)wrapper_tokenize(profiles, find_env_profile_callback, pd); conffile = pd->profile; free(pd); return conffile; } /* get_envd_config_filename returns the name of the active configuration configuration file for a given chost. It reads the config-$CHOST file to extract the CURRENT setting. */ static char* get_envd_config_filename(char *chost) { char *specfile = NULL; char *specname; /* if we don't know our CHOST, we can't use GCCC_PROFILES! */ if (NULL != chost) { /* look for CURRENT profile spec in GCCC_PROFILES */ char *gccc_profiles = getenv("GCCC_PROFILES"); if (NULL != gccc_profiles) specfile = find_env_profile_name(chost, gccc_profiles); } if (NULL == specfile) { /* find the right config file and read the profile there */ char *confname = get_chost_config_filename(chost); specfile = get_config_value("CURRENT", confname); free(confname); if (NULL == specfile) return NULL; } /* now create a copy of a name to return */ specname = join_filename(ENVD_PATH, specfile); free(specfile); return specname; } static char *get_path_from_envd(char *chost) { char *envname; char *envpath; /* search the appropriate environment file to search for a PATH */ envname = get_envd_config_filename(chost); if (NULL == envname) return NULL; #if 0 /* only set PATH for native compiiles (no!) */ envpath = get_config_value("PATH", envname); #else envpath = get_config_value(NULL, envname); #endif free(envname); return envpath; } /* ============================================================== */ /* check_for_target checks in dir for the file we are seeking * it returns 1 if found (with data->bin setup), 0 if not and * negative on error */ static char *check_for_target(char *dir, char *name, char *fullname) { char *str; struct stat sbuf; int result; /* Stat possible file to check that * 1) it exist and is a regular file, and * 2) it is not the wrapper itself, and * 3) it is in a /gcc-bin/ directory tree */ str = join_filename(dir, name); result = stat(str, &sbuf); if ((0 != result) || !(sbuf.st_mode & S_IFREG) || (0 == strcmp(str, fullname)) || (NULL == strstr(str, "/gcc-bin/"))) { free(str); str = NULL; } return str; } /* find_target_in_envd uses gcc-config's configuration files to find the correct binary path directly. This now also works to locate the appropriate cross-compilers as well. */ static int find_target_in_envd(struct wrapper_data *data) { char *str; char *envpath; /* if we find a PATH, ensure that it's actually there */ str = xmalloc(MAXPATHLEN); envpath = get_path_from_envd(data->chost); if (NULL != envpath) { data->bin = check_for_target(envpath, data->name, data->fullname); } free(str); return NULL != data->bin; } /* find_target_in_path tries to find our target in PATH */ static int find_target_token_callback(char *token, void *xdata) { struct wrapper_data *data = xdata; if (0 != strlen(token)) return 0; data->bin = check_for_target(token, data->name, data->fullname); return NULL != data->bin; } static int find_target_in_path(struct wrapper_data *data) { char *path; /* sometimes, PATH may not be set (silly user-errors anyway) */ path = getenv("PATH"); if (NULL == path) return 0; return wrapper_tokenize(path, find_target_token_callback, data); } /* find_target_oldschool -- with the above enhancements, I question whether or not the remaining code is useful anymore. (DEPRECATED) */ static int find_target_oldschool(struct wrapper_data *data) { FILE *inpipe; char *str; /* find_target_in_envd now provides the same functionality that gcc-config --get-bin-path used to provide */ fprintf(stderr, "%s: warning: calling gcc-config to find target path\n", data->name); /* Only our wrapper is in PATH, so get the CC path using gcc-config and execute the real binary in there... */ str = xmalloc(MAXPATHLEN); sprintf(str, "%s --get-bin-path", GCC_CONFIG); if (NULL != data->chost) { strcat(str, " "); if (strlen(str) + strlen(data->chost) > MAXPATHLEN) { wrapper_exit("%s wrapper: invalid chost: %s\n", data->name, data->chost); } strcat(str, data->chost); } inpipe = popen(str, "r"); if (NULL == inpipe) { wrapper_exit( "%s wrapper: Could not open pipe for gcc-config: %s\n", data->name, wrapper_strerror(errno)); } if (0 == fgets(str, MAXPATHLEN, inpipe)) { wrapper_exit( "%s wrapper: Could not get compiler binary path: %s\n", wrapper_strerror(errno)); } pclose(inpipe); data->bin = check_for_target(str, data->name, data->fullname); free(str); return NULL != data->bin; } static void find_wrapper_target(struct wrapper_data *data) { /* Find the first file with suitable name in PATH. The idea here is * that we do not want to bind ourselfs to something static like the * default profile, or some odd environment variable, but want to be * able to build something with a non default gcc by just tweaking * the PATH. We skip this for cross-compiling... hmmm */ if ((NULL == data->chost) && find_target_in_path(data)) return; /* If we are cross-compiling or the PATH has been dorked up, * then we are forced to use using gcc-config's files directly */ if (find_target_in_envd(data)) return; /* If we make it here, we're probably dead in the water. But * we might as well make one last feeble attempt to call * gcc-config to read the same files we just tried to get at. * If this is happening, this is a BIG FAT BUG!!! */ if (find_target_oldschool(data)) return; wrapper_exit("%s wrapper: Could not find compiler binary path\n", data->name); } /* ============================================================== */ /* This function modifies PATH to have gcc's bin path prepended */ static void modify_path(struct wrapper_data *data) { char *str; char *dname; /* save a copy of the target binary location, and get its directory component */ str = xstrdup(data->bin); dname = dirname(str); if (NULL == dname) return; prepend_env_value("PATH", dname, 1); free(str); return; } /* ============================================================== */ /* guess_chost_from_env is used when the wrapper is called in an uncanonicalized form (e.g. 'gcc') to determine the compiler host if we can figure this out, we can avoid opening and reading the config-HOST file if GCCC_PROFILES is set*/ char *guess_chost_from_env() { char *cbuild; char *chost; cbuild = getenv("CBUILD"); chost = getenv("CHOST"); if ((NULL == cbuild) || (NULL == chost) || (0 == strcmp(cbuild, chost))) return NULL; return NULL; /*xstrdup(cbuild);*/ } void capture_chost_name(char *argv0, struct wrapper_data *data) { size_t len = 0; char *cp; cp = xstrdup(argv0); data->name = xstrdup(basename(cp)); free(cp); cp = strrchr(data->name, '-'); if (NULL != cp) { len = cp - data->name; if (0 == len) wrapper_exit("gcc-config wrapper: unknown link: %s\n", argv0); data->chost = xstrdup(data->name); data->chost[len++] = 0; } else data->chost = guess_chost_from_env(); /* fixup cc calls -- i feel so dirty for doing this... */ if (0 == strcmp(data->name + len, "cc")) strcpy(data->name + len, "gcc"); /* What is the full name of our wrapper? */ data->fullname = join_filename(BIN_PATH, data->name); } int main(int argc, char **argv) { struct wrapper_data *data; char *bin; data = alloca(sizeof(*data)); if (NULL == data) wrapper_exit("%s wrapper: out of memory\n", argv[0]); memset(data, 0, sizeof(*data)); /* the program name tells us what to do, so save it (and chost prefix) */ capture_chost_name(argv[0], data); /* okay, now find the wrapper target */ find_wrapper_target(data); /* then prepend its path to PATH, if not already there */ modify_path(data); bin = alloca(strlen(data->bin) + 1); strcpy(bin, data->bin); free(data->chost); free(data->fullname); free(data->name); free(data->bin); /* Set argv[0] to the correct binary, else gcc do not find internal * headers, etc (bug #8132). Then Just Do It */ argv[0] = bin; if (execv(bin, argv) < 0) wrapper_exit("Could not run/locate \"%s\"\n", data->name); return 0; }