From 9b907ef80bc421df23515afc4c306e4d96c67649 Mon Sep 17 00:00:00 2001 From: Sergei Trofimovich Date: Fri, 24 Aug 2018 18:36:57 +0100 Subject: Convert binary wrapper to a symlink wrapper. Before the change: /usr/bin/gcc and friends were a copy of /usr/$(libexecdir)/misc/gcc-config After the change: /usr/bin/gcc is a symlink to a real compiler binary. Examples: /usr/${CTARGET}/gcc-bin/${GCC_VER}/gcc (native) /usr/${CHOST}/${CTARGET}/gcc-bin/${GCC_VER}/gcc (cross) gcc-config is a binary wrapper that does (or did) a few things: - [removed in 2011] injects additional CFLAGS_${ABI} Removed in commit 7ac40f3eb8434961f70485247d883f5b3009dcf2 "Stop auto appending CFLAGS_ from the env." - traverses PATH for real compiler binary and reexecutes it. - reads /etc/env.d/05gcc-${CTARGET} as a fallback if PATH is empty. Today binary wrapper does only PATH resolution and re-execution. This change has a few minor benefits: - PATH will not contain explicit /usr/${CHOST}/${CTARGET}/gcc-bin/${GCC_VER} entry. This will make PATH shorter for those who have mavy cross-compilers installed. - compiler switch will not require sourcing '. /etc/profile' as changes are applied as soon an symlink is switched. - ccache will see gcc binary changes directly and react accordingly. Previously in default configuration ccache cache depended on state of /usr/$(libexecdir)/misc/gcc-config See bug #640958 where ccache did not notice USE=-pie -> USE=pie switch. - Reasoning about PATH ordering is straightforward: all available binaries (as symlinks) are in /usr/bin. See bug #255695 where PATH ordering changed and bug #626606 where people get confused on what is in /usr/bin/gcc binary. Bug: https://bugs.gentoo.org/626606 Bug: https://bugs.gentoo.org/255695 Bug: https://bugs.gentoo.org/640958 Signed-off-by: Sergei Trofimovich --- .gitignore | 3 - Makefile | 8 +- README | 69 +---- gcc-config | 33 +-- tests/rw-multi-native-configs/test.select | 4 +- .../usr/lib/gcc-config/wrapper | 0 tests/source/test.whitespace | 2 +- wrapper.c | 323 --------------------- 8 files changed, 28 insertions(+), 414 deletions(-) delete mode 100755 tests/rw-multi-native-configs/usr/lib/gcc-config/wrapper delete mode 100644 wrapper.c diff --git a/.gitignore b/.gitignore index 23938ed..b79124b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,4 @@ -a.out *~ -*.o *.diff *.patch @@ -10,4 +8,3 @@ a.out /gcc-config-*.tar.* /.gcc-config -/wrapper diff --git a/Makefile b/Makefile index 3cbb915..3e9e5a3 100644 --- a/Makefile +++ b/Makefile @@ -13,16 +13,15 @@ BINDIR = $(PREFIX)/bin ESELECTDIR = $(PREFIX)/share/eselect/modules SUBLIBDIR = lib LIBDIR = $(PREFIX)/$(SUBLIBDIR) -LIBEXECDIR = $(LIBDIR)/$(PN) MKDIR_P = mkdir -p -m 755 INSTALL_EXE = install -m 755 INSTALL_DATA = install -m 644 -all: .gcc-config wrapper +all: .gcc-config clean: - rm -f .gcc-config wrapper *.o core + rm -f .gcc-config .gcc-config: gcc-config sed \ @@ -34,8 +33,7 @@ clean: chmod a+rx $@ install: all - $(MKDIR_P) $(DESTDIR)$(BINDIR) $(DESTDIR)$(LIBEXECDIR) $(DESTDIR)$(ESELECTDIR) - $(INSTALL_EXE) wrapper $(DESTDIR)$(LIBEXECDIR)/wrapper + $(MKDIR_P) $(DESTDIR)$(BINDIR) $(DESTDIR)$(ESELECTDIR) $(INSTALL_EXE) .gcc-config $(DESTDIR)$(BINDIR)/gcc-config $(INSTALL_DATA) gcc.eselect $(DESTDIR)$(ESELECTDIR) diff --git a/README b/README index e7933d8..bea5e30 100644 --- a/README +++ b/README @@ -6,20 +6,18 @@ Gentoo allows switching gcc as system runs via gcc-config: $ gcc-config x86_64-pc-linux-gnu-7.2.0 Ideally changes should be visible instantly and atomically -without shell restart. Practically see TODOs and BUGs on PATH/ROOTPATH. +without shell restart. Files, variables, things. ------------------------- -- Wrappers (copies of /usr/$(libexec)/gcc-config/wrapper) - /usr/bin/gcc - /usr/bin/g++ - /usr/bin/${CTARGET}-gcc +- Wrappers (symlinks to compiler binary like /usr/${CTARGET}/gcc-bin/${GCC_VERSION}/gcc) + /usr/bin/gcc (native) + /usr/bin/g++ (native) + /usr/bin/${CTARGET}-gcc (native and cross) ... (all files from /usr/${CTARGET}/gcc-bin/$GCC_VERSION/*) - Wrapper reads env config and re-execs binary from `GCC_PATH`. - See `gcc-config` script for wrapping details. - private gcc configs (provided by `toolchain.eclass`, gcc ebuilds) @@ -39,7 +37,6 @@ Files, variables, things. GCC_PATH="/usr/x86_64-pc-linux-gnu/gcc-bin/8.1.0" Used by gcc-config to generate wrappers and 05gcc- env.d files. - Used by wrapper to extract GCC_PATH and re-exec(). - gcc env.d compiler entries (provided by gcc-config) @@ -47,8 +44,6 @@ Files, variables, things. Populates paths for native-compilers - PATH="/usr/x86_64-pc-linux-gnu/gcc-bin/8.2.0" - ROOTPATH="/usr/x86_64-pc-linux-gnu/gcc-bin/8.2.0" GCC_SPECS="" MANPATH="/usr/share/gcc-data/x86_64-pc-linux-gnu/8.2.0/man" INFOPATH="/usr/share/gcc-data/x86_64-pc-linux-gnu/8.2.0/info" @@ -57,60 +52,16 @@ Files, variables, things. /etc/env.d/05gcc-${CTARGET} (cross) - Populates paths for cross-compilers - - PATH="/usr/x86_64-pc-linux-gnu/powerpc64le-unknown-linux-gnu/gcc-bin/7.3.0" - ROOTPATH="/usr/x86_64-pc-linux-gnu/powerpc64le-unknown-linux-gnu/gcc-bin/7.3.0" - - Used by wrapper to extract PATH and re-exec(). - Used by env-update to populate PATH (TODO: remove PATH population). - -How does gcc-config work? -------------------------- - -/usr/bin/gcc (or /usr/bin/) is a wrapper (wrapper.c). -gcc-config allows runtime gcc switch via level of indirection. - -Tool name is ${CTARGET}- or . TODO: doesn't it break -on ${CTARGET}--? Doesn't ${CTARGET} get misdetected? - -Today's resolution order is the following: + Empty. -1. Searches PATH for and reexecute if found. Some - target paths are ignored: - - itself (/usr/bin/) is ignored to avoid recursion - - */gcc-bin/* (/usr/${CHOST}/${CTARGET}/gcc-bin/7.3.0) is ignored - as those should precede in PATH to avoid wrapping entirely or be - selected via gcc-config. -2. If [1.] failed search for /etc/env.d/: /etc/env.d/gcc/.NATIVE (native compiler), - /etc/env.d/05gcc-${CTARGET} (cross-compiler). - - There GCC_PATH= (native) or PATH= (cross) directory entry is searched. - - Usually it's GCC_PATH="${EPREFIX}/usr/${CHOST}/gcc-bin/" (native) - or GCC_PATH="${EPREFIX}/usr/${CHOST}/${CTARGET}/gcc-bin/" (cross) - -3. If [2.] failed run 'ROOT= gcc-config --get-bin-path' and use it's output. - -4. Prepend path fetched from above to PATH environment variable and re-exec(). - -5. Done - -`gcc-config` script maintains binaries in `/usr/bin/` to make the above model work. - -To make compiler switchable in active shell `/usr/bin/` path must precede -'/usr/${CHOST}/gcc-bin/'. Otherwise wrapper is skipped. + Until Aug 2018 used to contain paths for cross-compilers. + To be removed eventually. TODOs ----- - Write proper `gcc-config` manpage off this readme to be more discoverable. -- Make /usr/bin/ a symlink of /usr/$(libexec)/gcc-config/wrapper, - not a file copy. This will make the fact that Gentoo uses wrappers more obvious. - It's essential to know for people using ccache cache or similar. - -- Move /etc/env.d/05gcc-${CTARGET} and /etc/env.d/04gcc-${CTARGET} after - /usr/bin PATH injection to restore gcc-config wrapping. +- Figure out symlink ownership story. Today symlinks don't belong to any package. - See https://bugs.gentoo.org/255695 for some details. + See https://bugs.gentoo.org/626606 diff --git a/gcc-config b/gcc-config index 521e3ba..e7f3fb9 100755 --- a/gcc-config +++ b/gcc-config @@ -167,16 +167,14 @@ convert_profile_paths() { return 0 } -# Usage: atomic_cp -atomic_cp() { - local src=$1 dst=$2 dstfile=$3 ref=$4 tmp +# Usage: atomic_ln +atomic_ln() { + local src=$1 dst=$2 dstfile=$3 tmp tmp="${dst}/.gcc.config.${dstfile}" - # We need to do this mv to make the update atomic in case - # someone is compiling at the same time they're running - # gcc-config (which is OK if you're just updating gcc-config - # itself and picking the same profile). - cp -f "${src}" "${tmp}" - touch -r "${ref}" "${tmp}" + # `ln` will expand into unlink();symlink(); which + # is not atomic for a small amount of time, but + # `mv` is a single rename() call + ln -sf "${src}" "${tmp}" mv "${tmp}" "${dst}/${dstfile}" } @@ -197,13 +195,6 @@ update_wrappers() { # for new functionality (like a version bump). local x CTARGET=$1 - # Find the bin wrapper - local libdir wrapper - for libdir in ${GENTOO_LIBDIR} lib lib64 lib32 lib ; do - wrapper="${EROOT}usr/${libdir}/gcc-config/wrapper" - [[ -e ${wrapper} ]] && break - done - # Use the old dir to see what we wrapped up previously. local old_wrappers=( $( [[ -n ${OLD_GCC_PATH} ]] || exit 1 @@ -261,9 +252,9 @@ update_wrappers() { fi fi - # Now do the actual wrapper copy with paths to the reference binary + # Now do the actual linking to the target binary if [[ -x ${ref} ]] ; then - atomic_cp "${wrapper}" "${EROOT}usr/bin" "${x}" "${ref}" + atomic_ln "${ref#${ROOT}}" "${EROOT}usr/bin" "${x}" else ewarn "double insanity with ${x} and ${ref}" # Make sure we have no stale wrappers @@ -275,7 +266,7 @@ update_wrappers() { # install the canonical cpp wrapper if ! is_cross_compiler ; then - atomic_cp "${wrapper}" "${EROOT}lib" "cpp" "${EROOT}usr/bin/cpp" + atomic_ln "${EPREFIX%/}/usr/bin/cpp" "${EROOT}lib" "cpp" fi } @@ -619,8 +610,7 @@ switch_profile() { is_cross_compiler && envd_num="05" || envd_num="04" envd="${ENV_D}/${envd_num}gcc-${CTARGET}" cat <<-EOF > "${envd}.tmp" - PATH="${GCC_PATH}" - ROOTPATH="${GCC_PATH}" + # Autogenerated by 'gcc-config'. EOF if ! is_cross_compiler ; then # Only write GCC_SPECS for the native compiler. #420097 @@ -724,6 +714,7 @@ switch_profile() { eend 0 + # Not needed since Aug 2018. Can be removed later (say, in a year). if [[ ${envd_changed} -ne 0 ]] ; then echo ewarn "If you intend to use the gcc from the new profile in an already" diff --git a/tests/rw-multi-native-configs/test.select b/tests/rw-multi-native-configs/test.select index c6d9daf..a9cc1d1 100644 --- a/tests/rw-multi-native-configs/test.select +++ b/tests/rw-multi-native-configs/test.select @@ -18,10 +18,10 @@ for (( i = 1; i < 4; ++i )) ; do esac for b in "${do_want[@]}" ; do - [[ -e usr/bin/${b} ]] || exit 1 + [[ -L usr/bin/${b} ]] || exit 1 done for b in "${dont_want[@]}" ; do - [[ -e usr/bin/${b} ]] && exit 1 + [[ -L usr/bin/${b} ]] && exit 1 done [[ -e etc/env.d/04gcc-${CHOST} ]] || exit 1 done diff --git a/tests/rw-multi-native-configs/usr/lib/gcc-config/wrapper b/tests/rw-multi-native-configs/usr/lib/gcc-config/wrapper deleted file mode 100755 index e69de29..0000000 diff --git a/tests/source/test.whitespace b/tests/source/test.whitespace index 28108ae..26d1bdb 100644 --- a/tests/source/test.whitespace +++ b/tests/source/test.whitespace @@ -1,2 +1,2 @@ #!/bin/bash -! egrep -nH '[[:space:]]+$' "${TROOT}"/../{gcc-config,wrapper.c} +! egrep -nH '[[:space:]]+$' "${TROOT}"/../gcc-config diff --git a/wrapper.c b/wrapper.c deleted file mode 100644 index c245dbb..0000000 --- a/wrapper.c +++ /dev/null @@ -1,323 +0,0 @@ -/* - * Copyright 1999-2012 Gentoo Foundation - * Distributed under the terms of the GNU General Public License v2 - * Author: Martin Schlemmer - * az's lackey: Mike Frysinger - */ - -#ifdef DEBUG -# define USE_DEBUG 1 -#else -# define USE_DEBUG 0 -#endif - -#ifndef _FILE_OFFSET_BITS -# define _FILE_OFFSET_BITS 64 /* #471024 */ -#endif - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifndef EPREFIX -# define EPREFIX "" -#endif - -#define GCC_CONFIG EPREFIX "/usr/bin/gcc-config" -#define ENVD_BASE EPREFIX "/etc/env.d/05gcc" - -#define ARRAY_SIZE(arr) (sizeof(arr)/sizeof(arr[0])) - -/* basename(3) is allowed to modify memory */ -#undef basename -#define basename(path) \ -({ \ - char *__path = path; \ - char *__ret = strrchr(__path, '/'); \ - __ret ? __ret + 1 : __path; \ -}) - -struct wrapper_data { - const char *name; - char *fullname, *bin, *path; -}; - -static const struct { - const char *alias; - const char *target; -} wrapper_aliases[] = { - { "cc", "gcc" }, - { "f77", "gfortran" }, - { "f95", "gfortran" }, -}; - -#define wrapper_warn(fmt, ...) fprintf(stderr, "%s" fmt "\n", "gcc-config: ", ## __VA_ARGS__) -#define wrapper_err(fmt, ...) ({ wrapper_warn("%s" fmt, "error: ", ## __VA_ARGS__); exit(1); }) -#define wrapper_errp(fmt, ...) wrapper_err(fmt ": %s", ## __VA_ARGS__, strerror(errno)) -#define wrapper_dbg(fmt, ...) ({ if (USE_DEBUG) wrapper_warn(fmt, ## __VA_ARGS__); }) - -#define xmemwrap(func, proto, use) \ -static void *x ## func proto \ -{ \ - void *ret = func use; \ - if (!ret) \ - wrapper_err(#func "%s", ": out of memory"); \ - return ret; \ -} -xmemwrap(malloc, (size_t size), (size)) -xmemwrap(strdup, (const char *s), (s)) - -/* check_for_target checks in path for the file we are seeking - * it returns 1 if found (with data->bin setup), 0 if not and - * negative on error - */ -static int check_for_target(char *path, struct wrapper_data *data) -{ - struct stat sbuf; - char str[PATH_MAX + 1]; - size_t path_len = strlen(path); - size_t len = path_len + strlen(data->name) + 2; - - if (sizeof(str) < len) - wrapper_warn("path too long: %s", path); - - strcpy(str, path); - str[path_len] = '/'; - str[path_len+1] = '\0'; - strcat(str, data->name); - - /* 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 - */ - if (strcmp(str, data->fullname) != 0 && - strstr(str, "/gcc-bin/") != NULL && - stat(str, &sbuf) == 0 && - (S_ISREG(sbuf.st_mode) || S_ISLNK(sbuf.st_mode))) - { - wrapper_dbg("%s: found in %s", data->name, path); - data->bin = xstrdup(str); - return 1; - } - - wrapper_dbg("%s: did not find in %s", data->name, path); - return 0; -} - -static int find_target_in_path(struct wrapper_data *data) -{ - char *token = NULL, *state = NULL; - char *str; - - if (data->path == NULL) - return 0; - - /* Make a copy since strtok_r will modify path */ - str = xstrdup(data->path); - - /* 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 ... */ - token = strtok_r(str, ":", &state); - while (token != NULL) { - if (check_for_target(token, data)) - return 1; - token = strtok_r(NULL, ":", &state); - } - - wrapper_dbg("%s: did not find in PATH", data->name); - return 0; -} - -/* find_target_in_envd parses /etc/env.d/05gcc, and tries to - * extract PATH, which is set to the current profile's bin - * directory ... - */ -static int find_target_in_envd(struct wrapper_data *data, int cross_compile) -{ - FILE *envfile = NULL; - char *token = NULL, *state; - char str[PATH_MAX + 1]; - char *strp = str; - char envd_file[PATH_MAX + 1]; - - if (!cross_compile) { - /* for the sake of speed, we'll keep a symlink around for - * the native compiler. #190260 - */ - snprintf(envd_file, sizeof(envd_file)-1, EPREFIX "/etc/env.d/gcc/.NATIVE"); - } else { - char *ctarget, *end = strrchr(data->name, '-'); - if (end == NULL) - return 0; - ctarget = xstrdup(data->name); - ctarget[end - data->name] = '\0'; - snprintf(envd_file, PATH_MAX, "%s-%s", ENVD_BASE, ctarget); - free(ctarget); - } - - envfile = fopen(envd_file, "r"); - if (envfile == NULL) - return 0; - - while (fgets(strp, PATH_MAX, envfile) != NULL) { - /* Keep reading ENVD_FILE until we get a line that - * starts with 'GCC_PATH=' ... keep 'PATH=' around - * for older gcc versions. - */ - if (strncmp(strp, "GCC_PATH=", strlen("GCC_PATH=")) && - strncmp(strp, "PATH=", strlen("PATH="))) - continue; - - token = strtok_r(strp, "=", &state); - if ((token != NULL) && token[0]) - /* The second token should be the value of PATH .. */ - token = strtok_r(NULL, "=", &state); - else - goto bail; - - if ((token != NULL) && token[0]) { - strp = token; - /* A bash variable may be unquoted, quoted with " or - * quoted with ', so extract the value without those .. - */ - token = strtok(strp, "\n\"\'"); - - while (token != NULL) { - if (check_for_target(token, data)) { - fclose(envfile); - return 1; - } - - token = strtok(NULL, "\n\"\'"); - } - } - - strp = str; - } - - bail: - fclose(envfile); - return (cross_compile ? 0 : find_target_in_envd(data, 1)); -} - -static void find_wrapper_target(struct wrapper_data *data) -{ - if (find_target_in_path(data)) - return; - - if (find_target_in_envd(data, 0)) - return; - - /* Only our wrapper is in PATH, so get the CC path using - * gcc-config and execute the real binary in there ... - */ - FILE *inpipe = popen("ROOT= " GCC_CONFIG " --get-bin-path", "r"); - if (inpipe == NULL) - wrapper_errp("could not open pipe"); - - char str[PATH_MAX + 1]; - if (fgets(str, PATH_MAX, inpipe) == 0) - wrapper_errp("could not get compiler binary path"); - - /* chomp! */ - size_t plen = strlen(str); - if (str[plen-1] == '\n') - str[plen-1] = '\0'; - - data->bin = xmalloc(plen + 1 + strlen(data->name) + 1); - sprintf(data->bin, "%s/%s", str, data->name); - - pclose(inpipe); -} - -/* This function modifies PATH to have gcc's bin path appended */ -static void modify_path(struct wrapper_data *data) -{ - char *newpath = NULL, *token = NULL, *state; - char dname_data[PATH_MAX + 1], str[PATH_MAX + 1]; - char *str2 = dname_data, *dname = dname_data; - size_t len = 0; - - if (data->bin == NULL) - return; - - if (data->path == NULL) - return; - - snprintf(str2, PATH_MAX + 1, "%s", data->bin); - - if ((dname = dirname(str2)) == NULL) - return; - - /* Make a copy since strtok_r will modify path */ - snprintf(str, PATH_MAX + 1, "%s", data->path); - - token = strtok_r(str, ":", &state); - - /* Check if we already appended our bin location to PATH */ - if ((token != NULL) && token[0]) - if (!strcmp(token, dname)) - return; - - len = strlen(dname) + strlen(data->path) + 2 + strlen("PATH") + 1; - - newpath = xmalloc(len); - memset(newpath, 0, len); - - snprintf(newpath, len, "PATH=%s:%s", dname, data->path); - putenv(newpath); -} - -int main(int argc, char *argv[]) -{ - struct wrapper_data data; - (void)argc; /* unused variable */ - - memset(&data, 0, sizeof(data)); - - if (getenv("PATH")) - data.path = xstrdup(getenv("PATH")); - - /* What should we find ? */ - data.name = basename(argv[0]); - - /* Allow for common compiler names like cc->gcc */ - size_t i; - for (i = 0; i < ARRAY_SIZE(wrapper_aliases); ++i) - if (!strcmp(data.name, wrapper_aliases[i].alias)) - data.name = wrapper_aliases[i].target; - - /* What is the full name of our wrapper? */ - data.fullname = xmalloc(strlen(data.name) + sizeof(EPREFIX "/usr/bin/") + 1); - sprintf(data.fullname, EPREFIX "/usr/bin/%s", data.name); - - find_wrapper_target(&data); - - modify_path(&data); - - free(data.path); - data.path = NULL; - - /* Set argv[0] to the correct binary, else gcc can't find internal headers - * http://bugs.gentoo.org/8132 - */ - argv[0] = data.bin; - - /* Ok, lets do it one more time ... */ - execv(data.bin, argv); - - /* shouldn't have made it here if things worked ... */ - wrapper_err("could not run/locate '%s'", data.name); - - return 123; -} -- cgit v1.2.3-18-g5258