commit:     554171c6a3d43f87ac771e45be2b5136b18fd2af
Author:     Michael Weiser <michael <AT> weiser <DOT> dinsnail <DOT> net>
AuthorDate: Sun Dec 24 14:40:33 2017 +0000
Commit:     Fabian Groffen <grobian <AT> gentoo <DOT> org>
CommitDate: Sun Dec 24 14:40:33 2017 +0000
URL:        https://gitweb.gentoo.org/repo/proj/prefix.git/commit/?id=554171c6

sys-devel/binutils-config: add cross-support to ldwrapper

I've extended ldwrapper:
- to detect a cross-ld at runtime from the argv[0] it was called with,
- always call ld with the full path in argv[0] so it can find its
ldscript directory relative to that
- incidentally fix a bug where finding ld via binutils-config could
never have worked since previous tries to find it via PATH had reduced
PATH to its first component where binutils-config would not usually be

 sys-devel/binutils-config/files/ldwrapper.c | 392 +++++++++++++++++++---------
 1 file changed, 274 insertions(+), 118 deletions(-)

diff --git a/sys-devel/binutils-config/files/ldwrapper.c 
b/sys-devel/binutils-config/files/ldwrapper.c
index 80831a7142..81b78adf20 100644
--- a/sys-devel/binutils-config/files/ldwrapper.c
+++ b/sys-devel/binutils-config/files/ldwrapper.c
@@ -13,6 +13,8 @@
 #include <ctype.h>
 #include <sys/stat.h>
 #include <errno.h>
+#include <glob.h>
+#include <stdarg.h>
 
 /**
  * ldwrapper: Prefix helper to inject -L and -R flags to the invocation
@@ -38,9 +40,20 @@
 # error CHOST must be defined!
 #endif
 
+static inline int is_cross(const char *ctarget) {
+       return strcmp(ctarget, CHOST);
+}
+
+static inline int is_darwin(const char *ctarget) {
+       return (strstr(ctarget, "-darwin") != NULL);
+}
 
-static inline void
-find_real_ld(char **ld, char verbose, char *wrapper)
+static inline int is_aix(const char *ctarget) {
+       return (strstr(ctarget, "-aix") != NULL);
+}
+
+static inline char *
+find_real_ld(const char verbose, const char *wrapper, const char *ctarget)
 {
        FILE *f = NULL;
        char *ldoveride;
@@ -48,9 +61,9 @@ find_real_ld(char **ld, char verbose, char *wrapper)
 #define ESIZ 1024  /* POSIX_MAX_PATH */
        char *ret;
        struct stat lde;
-
-       /* we may not succeed finding the linker */
-       *ld = NULL;
+       char *config;
+       const char *config_prefix;
+       size_t configlen;
 
        /* respect the override in environment */
        ldoveride = getenv("BINUTILS_CONFIG_LD");
@@ -58,92 +71,164 @@ find_real_ld(char **ld, char verbose, char *wrapper)
                if (verbose)
                        fprintf(stdout, "%s: using BINUTILS_CONFIG_LD=%s "
                                        "from environment\n", wrapper, 
ldoveride);
-               *ld = ldoveride;
-               return;
+               return ldoveride;
        }
        if (verbose)
                fprintf(stdout, "%s: BINUTILS_CONFIG_LD not found in 
environment\n",
                                wrapper);
-       
+
        ret = malloc(sizeof(char) * ESIZ);
-       if (ret == NULL)
-               return;
-
-       /* find ld in PATH, allowing easy PATH overrides */
-       path = getenv("PATH");
-       while (path > (char*)1 && *path != '\0') {
-               char *q = strchr(path, ':');
-               if (q)
-                       *q = '\0';
-               if (strstr(path, "/" CHOST "/binutils-bin/") != NULL) {
-                       snprintf(ret, ESIZ, "%s/%s", path, wrapper);
-                       if (stat(ret, &lde) == 0)
-                               *ld = ret;
+       if (ret == NULL) {
+               fprintf(stderr, "%s: out of memory allocating string for path 
to ld\n",
+                               wrapper);
+               exit(1);
+       }
+
+       /* find ld in PATH, allowing easy PATH overrides. strdup it because
+        * modifying it would otherwise corrupt the actual PATH environment
+        * variable which we might need to be intact later on to call
+        * binutils-config via popen. */
+       path = strdup(getenv("PATH"));
+       if (path != NULL && *path != '\0') {
+               char *p;
+               char *q;
+               char *match;
+               const char *match_anchor = "/binutils-bin/";
+               size_t matchlen = 1 + strlen(ctarget) +
+                       strlen(match_anchor) + 1;
+
+               match = malloc(sizeof(char) * matchlen);
+               if (match == NULL) {
+                       fprintf(stderr, "%s: out of memory allocating "
+                                       "buffer for path matching\n",
+                                       wrapper);
+                       exit(1);
+               }
+
+               /* construct /CTARGET/binutils-bin/ for matchin against PATH */
+               snprintf(match, matchlen, "/%s%s", ctarget, match_anchor);
+
+               for (p = path; (q = strchr(p, ':')) != NULL; p = q + 1) {
+                       if (q)
+                               *q = '\0';
+                       if (strstr(p, match) != NULL) {
+                               snprintf(ret, ESIZ, "%s/%s", p, wrapper);
+                               if (stat(ret, &lde) == 0) {
+                                       free(match);
+                                       return ret;
+                               }
+                       }
+                       if (!q)
+                               break;
                }
-               if (q)
-                       *q = ':'; /* restore PATH value */
-               if (*ld)
-                       return;
-               path = q + 1;
+
+               free(match);
        }
        if (verbose)
                fprintf(stdout, "%s: linker not found in PATH\n", wrapper);
 
-       /* parse EPREFIX/etc/env.d/binutils/config-CHOST to get CURRENT, then
-        * consider $EPREFIX/usr/CHOST/binutils-bin/CURRENT where we should
+       /* parse EPREFIX/etc/env.d/binutils/config-CTARGET to get CURRENT, then
+        * consider $EPREFIX/usr/CTARGET/binutils-bin/CURRENT where we should
         * be able to find ld */
-       ret[0] = '\0';
-       if ((f = fopen(EPREFIX "/etc/env.d/binutils/config-" CHOST, "r")) != 
NULL) {
+       config_prefix = EPREFIX "/etc/env.d/binutils/config-";
+       configlen = strlen(config_prefix) + strlen(ctarget) + 1;
+       config = malloc(sizeof(char) * configlen);
+       if (config == NULL) {
+               fprintf(stderr, "%s: out of memory allocating "
+                       "buffer for configuration file name\n",
+                       wrapper);
+               exit(1);
+       }
+
+       snprintf(config, configlen, "%s%s", config_prefix, ctarget);
+       if ((f = fopen(config, "r")) != NULL) {
                char p[ESIZ];
+               char *q;
                while (fgets(p, ESIZ, f) != NULL) {
-                       if (strncmp(p, "CURRENT=", strlen("CURRENT=")) == 0) {
-                               char *q = p + strlen(p);
-                               /* strip trailing whitespace (fgets at least 
includes
-                                * the \n) */
-                               for (q--; isspace(*q); q--)
-                                       *q = '\0';
-                                       ;
+                       if (strncmp(p, "CURRENT=", strlen("CURRENT=")) != 0)
+                               continue;
+
+                       q = p + strlen(p);
+                       /* strip trailing whitespace (fgets at least includes
+                        * the \n) */
+                       for (q--; isspace(*q); q--)
+                               *q = '\0';
+
+                       q = p + strlen("CURRENT=");
+                       if (is_cross(ctarget)) {
+                               snprintf(ret, ESIZ, EPREFIX "/usr/" CHOST 
"/%s/binutils-bin/%s/%s",
+                                               ctarget, q, wrapper);
+                       } else {
                                snprintf(ret, ESIZ, EPREFIX "/usr/" CHOST 
"/binutils-bin/%s/%s",
-                                               p + strlen("CURRENT="), 
wrapper);
-                               break;
+                                               q, wrapper);
                        }
+                       break;
                }
                fclose(f);
                if (stat(ret, &lde) == 0) {
-                       *ld = ret;
-                       return;
+                       free(config);
+                       return ret;
                }
        }
        if (verbose)
-               fprintf(stdout, "%s: linker not found via " EPREFIX
-                               "/etc/env.d/binutils/config-" CHOST " 
(ld=%s)\n",
-                               wrapper, ret);
-       
+               fprintf(stdout, "%s: linker not found via %s\n", wrapper, 
config);
+       free(config);
+
        /* last try, call binutils-config to tell us what the linker is
         * supposed to be */
-       ret[0] = '\0';
-       if ((f = popen("binutils-config -c", "r")) != NULL) {
+       config_prefix = "binutils-config -c ";
+       configlen = strlen(config_prefix) + strlen(ctarget) + 1;
+       config = malloc(sizeof(char) * configlen);
+       if (config == NULL) {
+               fprintf(stderr, "%s: out of memory allocating "
+                       "buffer for binutils-config command\n",
+                       wrapper);
+               exit(1);
+       }
+
+       snprintf(config, configlen, "%s%s", config_prefix, ctarget);
+       if ((f = popen(config, "r")) != NULL) {
                char p[ESIZ];
-               char *q;
-               if (fgets(p, ESIZ, f) != NULL) {
-                       q = p;
-                       if (strncmp(q, CHOST "-", strlen(CHOST "-")) == 0)
-                               q += strlen(CHOST "-");
-                       snprintf(ret, ESIZ, EPREFIX "/usr/" CHOST 
"/binutils-bin/%s/%s",
-                                       q, wrapper);
-               } else {
-                       *p = '\0';
-               }
+               char *q = fgets(p, ESIZ, f);
                fclose(f);
-               if (*p && stat(ret, &lde) == 0) {
-                       *ld = ret;
-                       return;
+               if (q != NULL) {
+                       size_t ctargetlen = strlen(ctarget);
+
+                       /* binutils-config should report CTARGET-<version> */
+                       if (strncmp(p, ctarget, ctargetlen) == 0 &&
+                                       strlen(p) > ctargetlen &&
+                                       p[ctargetlen] == '-') {
+                               /* strip trailing whitespace (fgets at least 
includes
+                                * the \n) */
+                               q = p + strlen(p);
+                               for (q--; isspace(*q); q--)
+                                       *q = '\0';
+
+                               q = p + ctargetlen + 1;
+                               if (is_cross(ctarget)) {
+                                       snprintf(ret, ESIZ, EPREFIX "/usr/" 
CHOST
+                                                       
"/%s/binutils-bin/%s/%s",
+                                                       ctarget, q, wrapper);
+                               } else {
+                                       snprintf(ret, ESIZ, EPREFIX "/usr/" 
CHOST
+                                                       "/binutils-bin/%s/%s",
+                                                       q, wrapper);
+                               }
+
+                               if (stat(ret, &lde) == 0) {
+                                       free(config);
+                                       return ret;
+                               }
+                       }
                }
        }
        if (verbose)
-               fprintf(stdout, "%s: linker not found via binutils-config -c 
(ld=%s)\n",
-                               wrapper, ret);
-       free(ret);
+               fprintf(stdout, "%s: linker not found via %s\n",
+                               wrapper, config);
+       free(config);
+
+       /* we didn't succeed finding the linker */
+       return NULL;
 }
 
 int
@@ -153,6 +238,7 @@ main(int argc, char *argv[])
        int newargc = 0;
        char **newargv = NULL;
        char *wrapper = argc > 0 ? argv[0] : "ld-wrapper";
+       char *wrapperdir = NULL;
        char verbose = getenv("BINUTILS_CONFIG_VERBOSE") != NULL;
        char *builddir = getenv("PORTAGE_BUILDDIR");
        size_t builddirlen;
@@ -160,13 +246,68 @@ main(int argc, char *argv[])
        int i;
        int j;
        int k;
+       glob_t m;
+       char *ctarget = CHOST;
+       size_t ctargetlen;
 
-       /* cannonicanise wrapper, stripping path and CHOST */
-       if ((p = strrchr(wrapper, '/')) != NULL)
+       /* two ways to determine CTARGET from argv[0]:
+        * 1. called as <CTARGET>-ld (manually)
+        * 2. called as EPREFIX/usr/libexec/gcc/<CTARGET>/ld (by gcc's collect2)
+        *
+        * TODO: Make argv[0] absolute without resolving symlinks so no. 2 can
+        * work when added to PATH (which shouldn't happen in the wild, but
+        * eh!?). */
+       if ((p = strrchr(wrapper, '/')) != NULL) {
+               /* cannonicanise wrapper step 1: strip path */
                wrapper = p + 1;
-       p = CHOST "-";
-       if (strncmp(wrapper, p, strlen(p)) == 0)
-               wrapper += strlen(p);
+
+               /* remember directory to see if it's CTARGET but only
+                * if parent is /gcc/ */
+               *p = '\0';
+               if ((p = strrchr(argv[0], '/')) != NULL) {
+                       char *q;
+
+                       *p = '\0';
+                       if ((q = strrchr(argv[0], '/')) != NULL &&
+                               strncmp(q + 1, "gcc", strlen("gcc")) == 0) {
+                               wrapperdir = p + 1;
+                       }
+               }
+       }
+
+       /* see if we have a known CTARGET prefix */
+       i = glob(EPREFIX "/etc/env.d/binutils/config-*", GLOB_NOSORT, NULL, &m);
+       if (i == GLOB_NOSPACE) {
+               fprintf(stderr, "%s: out of memory when inspecting "
+                               "binutils configuration\n", wrapper);
+               exit(1);
+       }
+       if (i == 0) {
+               for (i = 0; i < m.gl_pathc; i++) {
+                       p = strrchr(m.gl_pathv[i], '/');
+                       if (p == NULL || strncmp(p, "/config-", 
strlen("/config-")) != 0)
+                               continue;
+
+                       /* 
EPREFIX/etc/env.d/binutils/config-arm-something-or-other
+                        *                         move here ^ */
+                       p += strlen("/config-");
+                       if (strncmp(wrapper, p, strlen(p)) == 0 ||
+                               (wrapperdir != NULL && strcmp(wrapperdir, p) == 
0)) {
+                               /* this is us! (MEMLEAK) */
+                               ctarget = strdup(p);
+                               break;
+                       }
+               }
+       }
+       /* ignore GLOB_NOMATCH and (possibly) GLOB_ABORTED */
+       globfree(&m);
+
+       /* cannonicanise wrapper step2: strip CTARGET */
+       ctargetlen = strlen(ctarget);
+       if (strncmp(wrapper, ctarget, ctargetlen) == 0 &&
+                       wrapper[ctargetlen] == '-') {
+               wrapper += ctargetlen + 1;
+       }
 
        /* ensure builddir is something useful */
        if (builddir != NULL && *builddir != '/')
@@ -186,21 +327,28 @@ main(int argc, char *argv[])
        }
        /* account the original arguments */
        newargc += argc > 0 ? argc : 1;
-#ifdef TARGET_DARWIN
-       /* add the 2 prefix paths (-L), -search_paths_first and a
-        * null-terminator */
-       newargc += 2 + 1 + 1;
-#else
-       /* add the 4 paths we want (-L + -R) and a null-terminator */
-       newargc += 8 + 1;
-#endif
-#ifdef TARGET_AIX
-       /* AIX ld accepts -R only with -bsvr4 */
-       newargc++; /* -bsvr4 */
-#endif
+       /* we always add a null-terminator */
+       newargc ++;
+       /* If a package being cross-compiled injects standard directories, it's
+        * non-cross-compilable on any platform, prefix or no prefix. So no
+        * need to add PREFIX- or CTARGET-aware libdirs. */
+       if (!is_cross(ctarget)) {
+               if (is_darwin(ctarget)) {
+                       /* add the 2 prefix paths (-L) and -search_paths_first 
*/
+                       newargc += 2 + 1;
+               } else {
+                       /* add the 4 paths we want (-L + -R) */
+                       newargc += 8;
+               }
+
+               if (is_aix(ctarget)) {
+                       /* AIX ld accepts -R only with -bsvr4 */
+                       newargc++; /* -bsvr4 */
+               }
+       }
 
        /* let's first try to find the real ld */
-       find_real_ld(&ld, verbose, wrapper);
+       ld = find_real_ld(verbose, wrapper, ctarget);
        if (ld == NULL) {
                fprintf(stderr, "%s: failed to locate the real ld!\n", wrapper);
                exit(1);
@@ -215,32 +363,38 @@ main(int argc, char *argv[])
 
        /* construct the new argv */
        j = 0;
-       if ((p = strrchr(ld, '/')) != NULL) {
-               newargv[j++] = p + 1;
-       } else {
-               newargv[j++] = ld;
+
+       /* put the full path to ld into the new argv[0] we're calling it with
+        * because binutils ld finds its ldscripts directory relative to its
+        * own call path derived from its argv[0] */
+       newargv[j++] = ld;
+
+       if (!is_cross(ctarget) && is_darwin(ctarget)) {
+               /* inject this first to make the intention clear */
+               newargv[j++] = "-search_paths_first";
        }
-#ifdef TARGET_DARWIN
-       /* inject this first to make the intention clear */
-       newargv[j++] = "-search_paths_first";
-#endif
+
        /* position k right after the original arguments */
        k = j - 1 + argc;
        for (i = 1; i < argc; i++, j++) {
-#ifdef TARGET_AIX
-               /* AIX ld has this problem:
-                *   $ /usr/ccs/bin/ld -bsvr4 -bE:xx.exp -bnoentry xx.o
-                *   ld: 0706-005 Cannot find or open file: l
-                *       ld:open(): No such file or directory
-                * Simplest workaround is to put -bsvr4 last.
-                */
-               if (strcmp(argv[i], "-bsvr4") == 0) {
-                       --j; --k;
-                       continue;
+               if (is_aix(ctarget)) {
+                       /* AIX ld has this problem:
+                        *   $ /usr/ccs/bin/ld -bsvr4 -bE:xx.exp -bnoentry xx.o
+                        *   ld: 0706-005 Cannot find or open file: l
+                        *       ld:open(): No such file or directory
+                        * Simplest workaround is to put -bsvr4 last.
+                        */
+                       if (strcmp(argv[i], "-bsvr4") == 0) {
+                               --j; --k;
+                               continue;
+                       }
                }
-#endif
+
                newargv[j] = argv[i];
-#ifndef TARGET_DARWIN
+
+               if (is_cross(ctarget) || is_darwin(ctarget))
+                       continue;
+
                /* on ELF targets we add runpaths for all found search paths */
                if (argv[i][0] == '-' && argv[i][1] == 'L') {
                        char *path;
@@ -280,25 +434,27 @@ main(int argc, char *argv[])
                        snprintf(newargv[k], len, "-R%s", path);
                        k++;
                }
-#endif
        }
        /* add the custom paths */
-#ifdef TARGET_DARWIN
-       newargv[k++] = "-L" EPREFIX "/usr/lib";
-       newargv[k++] = "-L" EPREFIX "/lib";
-#else
-       newargv[k++] = "-L" EPREFIX "/usr/" CHOST "/lib/gcc";
-       newargv[k++] = "-R" EPREFIX "/usr/" CHOST "/lib/gcc";
-       newargv[k++] = "-L" EPREFIX "/usr/" CHOST "/lib";
-       newargv[k++] = "-R" EPREFIX "/usr/" CHOST "/lib";
-       newargv[k++] = "-L" EPREFIX "/usr/lib";
-       newargv[k++] = "-R" EPREFIX "/usr/lib";
-       newargv[k++] = "-L" EPREFIX "/lib";
-       newargv[k++] = "-R" EPREFIX "/lib";
-#endif
-#ifdef TARGET_AIX
-       newargv[k++] = "-bsvr4"; /* last one, see above */
-#endif
+       if (!is_cross(ctarget)) {
+               if (is_darwin(ctarget)) {
+                       /* FIXME: no support for cross-compiling *to* Darwin */
+                       newargv[k++] = "-L" EPREFIX "/usr/lib";
+                       newargv[k++] = "-L" EPREFIX "/lib";
+               } else {
+                       newargv[k++] = "-L" EPREFIX "/usr/" CHOST "/lib/gcc";
+                       newargv[k++] = "-R" EPREFIX "/usr/" CHOST "/lib/gcc";
+                       newargv[k++] = "-L" EPREFIX "/usr/" CHOST "/lib";
+                       newargv[k++] = "-R" EPREFIX "/usr/" CHOST "/lib";
+                       newargv[k++] = "-L" EPREFIX "/usr/lib";
+                       newargv[k++] = "-R" EPREFIX "/usr/lib";
+                       newargv[k++] = "-L" EPREFIX "/lib";
+                       newargv[k++] = "-R" EPREFIX "/lib";
+               }
+
+               if (is_aix(ctarget))
+                       newargv[k++] = "-bsvr4"; /* last one, see above */
+       }
        newargv[k] = NULL;
 
        if (verbose) {

Reply via email to