#define ALONE \
    set -ex; ${CC:-gcc} -s -Os -fno-stack-protector \
    -W -Wall -o xstatic "$0"; exit 0
/*
 * xstatic.c: trampoline for uClibc static linking with gcc, g++ or clang(++)
 * by pts@fazekas.hu at Sun Dec 15 20:08:54 CET 2013
 *
 * This program examines its argv, modifies some args, and exec()s the specified
 * gcc, g++, clang or clang++ for static linking with uClibc. The directory
 * layout where the uClibc .h, .a and .o files are locaed is hardwired.
 *
 * Since our process is short-lived, we don't bother free()ing memory.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#undef HAVE_REALLOC

#ifdef __XTINY__
#include <xtiny.h>
#else  /* not __XTINY__ */
#ifndef __MINIDIET__
#define HAVE_REALLOC 1
#endif
#ifdef USE_MINIINC
#include <miniinc.h>
#else
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/utsname.h>
#include <sys/wait.h>
#include <unistd.h>
extern char **environ;
#endif
#endif

static char *strdupcat(const char *a, const char *b, const char *c) {
  const size_t sa = strlen(a), sb = strlen(b), sc = strlen(c);
  char *p = malloc(sa + sb + sc + 1);
  memcpy(p, a, sa);
  memcpy(p + sa, b, sb);
  strcpy(p + sa + sb, c);
  return p;
}

static char *readlink_alloc(const char *path) {
  ssize_t len = 256;
  ssize_t got;
  char *buf = malloc(len);
  for (;;) {
    if (0 > (got = readlink(path, buf, len))) {
      free(buf);
      return NULL;
    }
    if (got < len) {
      buf[got] = '\0';
      return buf;
    }
#ifdef HAVE_REALLOC
    buf = realloc(buf, len <<= 1);
#else
    buf = realloc_grow(buf, len, len << 1);
    len <<= 1;
#endif
  }
}

static char *readlink_slash_alloc(char *path) {
  char *p, *q, c;
  for (p = path + strlen(path); p != path && p[-1] == '/'; --p) {}
  if (p == path) return NULL;
  for (q = p; q != path && q[-1] == '/'; --q) {}
  for (; q != path && q[-1] != '/'; --q) {}
  /* Don't follow symlink if ends with /.. or /. */
  if (q[0] == '.' && (q[1] == '/' || q[1] == '\0' ||
      (q[1] == '.' && (q[2] == '/' || q[2] == '\0')))) return NULL;
  c = *p;
  *p = '\0';
  q = readlink_alloc(path);
  *p = c;
  return q;
}

/* Combines two pathnames to a newly allocated string. The . and .. components
 * in the beginning of path2 are processed specially (and correctly).
 *
 * base must end with a '/'.
 */
static char *combine_paths_dotdot_alloc(const char *base, const char *path2) {
  char *path1;
  if (path2[0] == '/') return strdupcat(path2, "", "");
  path1 = malloc(strlen(base) + strlen(path2) + 4);
  strcpy(path1, base);
  while (path2[0] == '.') {
    if (path2[1] == '/') {
      path2 += 2;
      for (; path2[0] == '/'; ++path2) {}
      continue;
    } else if (path2[1] == '.' && path2[2] == '/' && path1[0] != '\0') {
      /* TODO(pts): Also special-case path2 == "..". */
      char *p, *path3;
      struct stat sta, stb;
      char c;
      size_t spath1;
      path2 += 3;
      for (; path2[0] == '/'; ++path2) {}
     do_path1_again:
      spath1 = strlen(path1);
      /* TODO(pts): Add loop limit for infinite symlinks. */
      while ((path3 = readlink_slash_alloc(path1))) {
        char *path4;
        size_t spath3;
        /* TODO(pts): Handle ../ in the beginning of path3. */
        spath3 = strlen(path3);
        if (path3[0] == '/') {
          spath1 = 0;  /* Truncate on absolute symlink. */
        } else {
          for (p = path1 + spath1; p != path1 && p[-1] == '/'; --p) {}
          for (; p != path1 && p[-1] != '/'; --p) {}
          spath1 = p - path1;
          *p = '\0';
        }
        path4 = malloc(spath1 + spath3 + strlen(path2) + 5);
        memcpy(path4, path1, spath1);
        free(path1);
        path1 = path4;
        memcpy(p = path4 + spath1, path3, spath3);
        free(path3);
        p += spath3;
        if (spath3 != 0 && p[-1] != '/') *p++ = '/';
        *p = '\0';
        spath1 = p - path1;
      }
      strcpy(path1 + spath1, "../");
      if (0 != lstat(path1, &sta)) { dotdot_dumb:
        strcpy(path1 + spath1 + 3, path2);
        return path1;
      }
      p = path1 + spath1;
      for (; p != path1 && p[-1] == '/'; --p) {}
      if (p == path1) goto dotdot_dumb;  /* Absolute /. */
      for (; p != path1 && p[-1] != '/'; --p) {}
      if (p[0] == '.' && p[1] == '/') {
        if (p == path1) goto dotdot_dumb;
        *p = '\0';
        goto do_path1_again;
      } else if (p[0] == '.' && p[1] == '.' && p[2] == '/') {
        goto dotdot_dumb;
      }
      if (p == path1) {
        if (0 != lstat(".", &stb) || sta.st_dev != stb.st_dev ||
            sta.st_ino != stb.st_ino) goto dotdot_dumb;
      } else {
        c = p[0];
        p[0] = '\0';  // path1 now ends with '/', it's OK.
        if (0 != lstat(path1, &stb) ||
            sta.st_dev != stb.st_dev || sta.st_ino != stb.st_ino) {
          p[0] = c;
          goto dotdot_dumb;
        }
      }
    } else {
      break;
    }
  }
  strcat(path1, path2);
  return path1;
}

/* Resolve symlinks until a non-symlink is found. */
static char *readlink_alloc_all(const char *path) {
  char *path2, *path1 = strdup(path);
  while ((path2 = readlink_alloc(path1))) {
    if (path2[0] != '/') {
      char *p;
      for (p = path1 + strlen(path1); p != path1 && p[-1] != '/'; --p) {}
      if (p != path1) {
        *p = '\0';  /* Remove basename from path1. */
        p = combine_paths_dotdot_alloc(path1, path2);
        free(path2);
        path2 = p;
      }
    }
    free(path1);
    path1 = path2;
  }
  return path1;
}

/* Returns a newly malloced string containing the directory 1 level above
 * the specified directory.
 */
typedef enum archbit_t {
  ARCHBIT_UNKNOWN = -1,
  ARCHBIT_UNSPECIFIED = -2,
  ARCHBIT_DETECTED = -3,
  ARCHBIT_32 = 32,
  ARCHBIT_64 = 64,
} archbit_t;

#if 0
static archbit_t get_archbit_detected(char **argv) {
  char **argi, *arg;
  for (argi = argv + 1; (arg = *argi); ++argi) {
    /* TODO(pts): Support Clang >3.3 flag `-arch i386'. */
    /* Please note that -march=k8 doesn't force 64-bit compilation mode. */
    /* Please note that we ignore -mcpu=, -march= and -mtune=, because neither
     * Clang nor GCC autoswitch from -m32 to -m64 just by -march=k8.
     */
    /* gcc: `-mcpu=' is deprecated. Use `-mtune=' or '-march=' instead. */
    if (0 == strcmp(arg, "-target") && argv[1]) {  /* Clang. */
      /* -target=... doesn't work in Clang, so we don't match it. */
      const char *p;
      for (p = argv[1]; *p != '-' && *p != '\0'; ++p) {
        /* Matches amd64 and x86_64. */
        if (p[0] == '6' && p[1] == '4') return ARCHBIT_64;
      }
      return ARCHBIT_32;
    } else if (0 == strcmp(arg, "-m32")) {
      return ARCHBIT_32;
    } else if (0 == strcmp(arg, "-m64")) {
      return ARCHBIT_64;
    }
  }
  return ARCHBIT_UNSPECIFIED;
}
#endif

#if 0
static archbit_t get_archbit_override(archbit_t archbit_detected) {
  struct utsname ut;
  if (archbit_detected >= 0) return ARCHBIT_DETECTED;
  if (uname(&ut) < 0) return ARCHBIT_DETECTED;
  if (strstr(ut.machine, "64")) {  /* 64-bit kernel. */
    /* Since nowadays there isn't anything useful in /lib directly, let's
     * see if /bin/sh is a 64-bit ELF executable.
     */
    int fd = open("/bin/sh", O_RDONLY);
    int got;
    char buf[5];
    if (fd < 0) return ARCHBIT_DETECTED;
    if ((got = read(fd, buf, 5)) < 5) {
      close(fd);
      return ARCHBIT_DETECTED;
    }
    close(fd);
    /* 64-bit ELF binary. */
    if (0 == memcmp(buf, "\177ELF\002", 5)) return ARCHBIT_64;
  }
  return ARCHBIT_32;
}
#endif

/* Return a newly malloc()ed string containing prefix + escaped(argv) +
 * suffix, the caller takes ownership.
 */
static char *escape_argv(const char *prefix, char **argv, char *suffix) {
  size_t sprefix = strlen(prefix);
  /* An upper limit of the output size. */
  size_t size = sprefix + strlen(suffix);
  char **argi, *arg;
  char *out, *p, c;
  for (argi = argv; (arg = *argi); ++argi) {
    size += 3 + strlen(arg);  /* Space (or '\0') plus the single quotes. */
    for (; *arg; ++arg) {
      if (*arg == '\'') size += 3;
    }
  }
  p = out = malloc(size);
  memcpy(p, prefix, sprefix);
  p += sprefix;
  for (argi = argv; (arg = *argi); ++argi) {
    char is_simple = (*arg != '=') && (*arg != '\0');
    if (is_simple) {
      for (; (c = *arg) != '\0' || (c == '+' || c == '-' || c == '_' ||
             c == '=' || c == '.' || c == '/' ||
             ((c | 32) - 'a' + 0U) <= 'z' - 'a' + 0U ||
             (c - '0' + 0U) <= 9U); ++arg) {}
      is_simple = (*arg == '\0');
      arg = *argi;
    }
    if (is_simple) {  /* Append unescaped. */
      sprefix = strlen(arg);
      memcpy(p, arg, sprefix);
      p += sprefix;
    } else {  /* Escape with single quotes. */
      *p++ = '\'';
      for (; (c = *arg); ++arg) {
        if (c == '\'') {
          *p++ = '\'';
          *p++ = '\\';
          *p++ = '\'';
        }
        *p++ = c;
      }
      *p++ = '\'';
    }
    *p++ = ' ';
  }
  strcpy(--p, suffix);  /* `--' to replace the space. */
  return out;
}

/* Return true iff we've received a linker command-line. */
static char detect_linker(char **argv) {
  char **argi, *arg;
  for (argi = argv + 1; (arg = *argi) &&
       /* We could also use `-z relro' or `-m elf_i386' or ther values. */
       0 != strcmp(arg, "--eh-frame-hdr") &&  /* By clang. */
       0 != strcmp(arg, "--build-id") &&  /* By clang and gcc. */
       0 != strncmp(arg, "--hash-style=", 13) &&
       0 != strcmp(arg, "--do-xstaticcld") &&
       0 != strcmp(arg, "-dynamic-linker") &&  /* By clang. */
       0 != strcmp(arg, "--start-group") &&  /* By gcc/clang -static. */
       0 != strcmp(arg, "--as-needed") &&  /* By gcc/clang witout -static. */
       0 != strcmp(arg, "--no-as-needed"); ++argi) {}
  return ! !*argi;
}

static void fdprint(int fd, const char *msg) {
  const size_t smsg = strlen(msg);
  if (smsg != 0U + write(fd, msg, smsg)) exit(121);
}

static char is_dirprefix(const char *s, const char *prefix) {
  const size_t sprefix = strlen(prefix);
  return 0 == strncmp(s, prefix, sprefix) && (
      s[sprefix] == '\0' || s[sprefix] == '/');
}

static char is_argprefix(const char *s, const char *prefix) {
  const size_t sprefix = strlen(prefix);
  return 0 == strncmp(s, prefix, sprefix) && (
      s[sprefix] == '\0' || s[sprefix] == '=');
}

static void check_bflags(char **argv) {
  char **argi, *arg;
  for (argi = argv + 1; (arg = *argi); ++argi) {
    if ((arg[0] == '-' && arg[1] == 'B') ||
        is_argprefix(arg, "--sysroot") ||
        is_argprefix(arg, "--gcc-toolchain")) {
      fdprint(2, strdupcat(
          "error: flag not supported in this ldmode: ", arg, "\n"));
      exit(122);
    }
  }
}

static void check_xflags(char **argv) {
  char **argi, *arg;
  for (argi = argv + 1; (arg = *argi); ++argi) {
    /* TODO(pts): Copy some of these flags to clang_trampoline.cc */
    if ((arg[0] == '-' && arg[1] == 'B') ||
        is_argprefix(arg, "--sysroot") ||
        is_argprefix(arg, "--gcc-toolchain") ||
        /* TODO(pts): Accept a few targets (and don't pass the flag). */
        0 == strcmp(arg, "-target") ||  /* Clang-specific. */
        0 == strcmp(arg, "-m64") ||
        0 == strcmp(arg, "-sysld") ||
        0 == strcmp(arg, "--sysld") ||
        0 == strcmp(arg, "-p") ||  /* Needs Mcrt1.o (| gcrt1.o), clang crt1.o */
        0 == strcmp(arg, "-pg") ||  /* Needs gcrt1.o, clang uses crt1.o */
        0 == strcmp(arg, "-pie") ||
        0 == strcmp(arg, "-fpic") ||
        0 == strcmp(arg, "-fPIC") ||
        0 == strcmp(arg, "-fpie") ||
        0 == strcmp(arg, "-fPIE") ||  /* This would link Scrt1.o etc. */
        0 == strcmp(arg, "-shared") ||
        0 == strcmp(arg, "-shared-libgcc")) {
      fdprint(2, strdupcat(
          "xstatic: error: flag not supported: ", arg, "\n"));
      exit(122);
    }
  }
}

static char detect_need_linker(char **argv) {
  char **argi, *arg, c;
  for (argi = argv + 1; (arg = *argi); ++argi) {
    if (arg[0] != '-') continue;
    c = arg[1];
    /* E.g. with "-E" the linker won't be invoked. */
    if ((c == 'M' && arg[2] == 'M' && arg[3] == '\0') ||
        ((c == 'E' || c == 'S' || c == 'c' || c == 'M') && arg[2] == '\0')) {
      return 0;
    }
  }
  return 1;
}

static void detect_nostdinc(char **argv,
                            /* Output bool arguments. */
                            char *has_nostdinc, char *has_nostdincxx) {
  char **argi, *arg;
  *has_nostdinc = *has_nostdincxx = 0;
  for (argi = argv + 1; (arg = *argi); ++argi) {
    if (0 == strcmp(arg, "-nostdinc")) {
      *has_nostdinc = 1;
    } else if (0 == strcmp(arg, "-nostdinc++")) {
      *has_nostdincxx = 1;
    }
  }
}

/* Return NULL if not found. Return cmd if it contains a slash. */
static char *find_on_path(const char *cmd) {
  const char *path;
  char *pathname;
  const char *p, *q;
  size_t scmd, s;
  struct stat st;
  if (strstr(cmd, "/")) {
    if (0 == stat(cmd, &st) && !S_ISDIR(st.st_mode)) {
      return strdupcat(cmd, "", "");
    }
  } else {
    path = getenv("PATH");
    if (!path || !path[0]) path = "/bin:/usr/bin";
    scmd = strlen(cmd);
    for (p = path; *p != '\0'; p = q + 1) {
      for (q = p; *q != '\0' && *q != ':'; ++q) {}
      s = q - p;
      pathname = malloc(s + 2 + scmd);
      memcpy(pathname, p, s);
      pathname[s] = '/';
      strcpy(pathname + s + 1, cmd);
      if (0 == stat(pathname, &st) && !S_ISDIR(st.st_mode)) return pathname;
      free(pathname);  /* TODO(pts): Realloc. */
      if (*q == '\0') break;
    }
  }
  return NULL;
}

typedef struct lang_t {
  char is_cxx;
  char is_cxx_prog;
  char is_clang;
  char is_compiling;
} lang_t;

static void detect_lang(const char *prog, char **argv, lang_t *lang) {
  char **argi;
  const char *arg, *basename, *ext;
  lang->is_compiling = 0;
  arg = prog;
  for (basename = arg + strlen(arg); basename != arg && basename[-1] != '/';
       --basename) {}
  lang->is_cxx = lang->is_cxx_prog = strstr(basename, "++") != NULL;
  lang->is_clang = strstr(basename, "clang") != NULL;
  for (argi = argv + 1; (arg = *argi); ++argi) {
    if (0 == strcmp(arg, "-xc++")) {
      lang->is_cxx = 1;
    } else if (0 == strcmp(arg, "--driver-mode=g++") ||
               0 == strcmp(arg, "-ccc-cxx")) {  /* Enable C++ for Clang. */
      lang->is_cxx = 1;
      /* -ccc-cxx (between clang 3.0 and 3.3) and --driver-mode=g++ (Clang 3.4)
       * are Clang-specific flags, gcc doesn't have them.
       */
      lang->is_clang = 1;
    } else if (0 == strcmp(arg, "-x") && argi[1]) {
      lang->is_cxx = 0 == strcmp(*++argi, "c++");
    } else if (arg[0] != '-') {
      for (ext = arg + strlen(arg);
           ext != arg && ext[-1] != '.' && ext[-1] != '/';
           --ext) {}
      if (ext == basename) ext = "";
      if (0 == strcmp(ext, "cc") ||
          0 == strcmp(ext, "cp") ||
          0 == strcmp(ext, "cxx") ||
          0 == strcmp(ext, "cpp") ||
          0 == strcmp(ext, "CPP") ||
          0 == strcmp(ext, "c++") ||
          0 == strcmp(ext, "C")) {
        lang->is_cxx = 1;
        lang->is_compiling = 1;
      } else if (0 == strcmp(ext, "c") ||
                 0 == strcmp(ext, "i") ||
                 0 == strcmp(ext, "ii") ||
                 0 == strcmp(ext, "m") ||
                 0 == strcmp(ext, "mi") ||
                 0 == strcmp(ext, "mm") ||
                 0 == strcmp(ext, "M") ||
                 0 == strcmp(ext, "mii") ||
                 0 == strcmp(ext, "h") ||
                 0 == strcmp(ext, "H") ||
                 0 == strcmp(ext, "hp") ||
                 0 == strcmp(ext, "hxx") ||
                 0 == strcmp(ext, "hpp") ||
                 0 == strcmp(ext, "HPP") ||
                 0 == strcmp(ext, "h++") ||
                 0 == strcmp(ext, "tcc") ||
                 0 == strcmp(ext, "s") ||
                 0 == strcmp(ext, "S") ||
                 0 == strcmp(ext, "sx")) {
        /* Extensions taken from `man gcc', Clang supports only a subset:
         * C, C++, Objective C and Objective C++. Fortran, ADA, Go, Java etc.
         * skipped.
         */
        lang->is_compiling = 1;
      }
    }
  }
}

/* Check that the linker is not instructed to link *crt*.o files from the
 * wrong directory. Most of the time this is a configuration issue, e.g. the
 * wrong value for the -B compiler flag was specified, and now the compiler
 * has instructed the linker to look for the *crt*.o files a system-default
 * (e.g. /usr/lib/...) directory.
 */
static void check_ld_crtoarg(char **argv) {
  /* If a relevant *crt*.o file is missing from xstaticcld, then
   * gcc generates e.g.
   * /usr/lib/gcc/x86_64-linux-gnu/4.6/../../../i386-linux-gnu/crtn.o , and
   * pts-static-clang generates crtn.o . We detect and fail on both.
   */
  char had_error = 0;
  char **argi, *supported_dir;
  const char *arg, *basename, *argend, *supbasename;
  size_t supdirlen;
  for (supbasename = argv[0] + strlen(argv[0]);
       supbasename != argv[0] && supbasename[-1] != '/'; --supbasename) {}
  supdirlen = supbasename - argv[0];
  for (argi = argv + 1; (arg = *argi); ++argi) {
    if (0 == strcmp(arg, "-o") && argi[1]) {
      ++argi;
      continue;
    }
    if (arg[0] == '-') continue;
    argend = arg + strlen(arg);
    for (basename = argend; basename != arg && basename[-1] != '/';
         --basename) {}
    if (basename[0] == '\0' || argend - arg < 2 ||
        argend[-1] != 'o' || argend[-2] != '.' ||
        ((0 != strncmp(basename, "crt", 3) ||
          (0 != strcmp(basename, "crt0.o") &&
           0 != strcmp(basename, "crt1.o") &&
           0 != strcmp(basename, "crt2.o") &&
           0 != strcmp(basename, "crti.o") &&
           0 != strcmp(basename, "crtn.o") &&
           0 != strcmp(basename, "crtbegin.o") &&
           0 != strcmp(basename, "crtbeginS.o") &&
           0 != strcmp(basename, "crtbeginT.o") &&
           0 != strcmp(basename, "crtend.o") &&
           0 != strcmp(basename, "crtendS.o") &&
           0 != strcmp(basename, "crtfastmath.o"))) &&
         (0 != strcmp(basename + 1, "crt1.o") ||
          (0 != strcmp(basename, "Scrt1.o") &&
           0 != strcmp(basename, "Mcrt1.o") &&
           0 != strcmp(basename, "gcrt1.o"))))) continue;
    if (0 == strncmp(arg, argv[0], supdirlen)) continue;
    if (supdirlen == 0) {
      supported_dir = ".";
    } else {
      supported_dir = malloc(supdirlen);
      memcpy(supported_dir, argv[0], supdirlen - 1);
      supported_dir[supdirlen - 1] = '\0';
    }
    fdprint(2, strdupcat(
        "xstatic-ld: error: *crt*.o file linked from unsupported location "
        "(supported is ", strdupcat(supported_dir, "): ", arg), "\n"));
    had_error = 1;
  }
  if (had_error) exit(122);
}

static char *get_up_dir_alloc(const char *dir) {
  char *dirup = NULL, *p;
  if (0 == strcmp(dir, "..") || (dirup = readlink_alloc(dir))) {
    free(dirup);
    return strdupcat(dir, "/..", "");
  }
  dirup = strdupcat(dir, ".", "");
  for (p = dirup + strlen(dirup) - 1; p != dirup && p[-1] != '/'; --p) {}
  if (p == dirup) {
    *p++ = '.';
    if (0 == strcmp(dir, ".")) *p++ = '.';
    *p = '\0';
  } else if (dirup[0] == '/' && dirup[1] == '\0') {
    fdprint(2, strdupcat("xstatic: error: no parent dir for: ", dir, "\n"));
    exit(122);
  } else {
    p[-1] = '\0';  /* Remove basename of dirup. */
  }
  return dirup;
}

/* Read everything from file descriptor fd, find "!fno-use-linker-plugin:",
 * return bool indicating whether found
 */
static char does_contain_linker_plugin(int fd) {
  char const kHaystack[] = "fno-use-linker-plugin:";
  char buf[4096], *p = buf, *pend;
  int got;
  struct __StaticAssertBufSize {
    int StaticAssertBufSize : sizeof(buf) >= sizeof(kHaystack);
  };
  while ((got = read(fd, p, sizeof(buf) - (p - buf))) > 0) {
    pend = p + got;
    p = buf;
    while (p != pend) {
      if (*p++ != '!') continue;
      if (pend - p + 0U >= (sizeof(kHaystack) - 1) && 0 == memcmp(p, kHaystack, (sizeof(kHaystack) - 1))) {
        /* Read and discard the rest of the input on fd. */
        while ((got = read(fd, buf, sizeof(buf))) > 0) {}
        return 1;  /* Found. */
      }
    }
    if (p - buf + 0U > (sizeof(kHaystack) - 1)) {
      /* Move the end of the buffer to the beginning for more matches. */
      for (pend = p - (sizeof(kHaystack) - 1), p = buf; p != buf + (sizeof(kHaystack) - 1); *p++ = *pend++) {}
    }
  }
  return 0;  /* Not found. */
}

static char does_gcc_support_linker_plugin(const char *gcc_prog) {
  char result;
  pid_t pid;
  int p[2], status;
  if (0 != pipe(p)) {
    fdprint(2, "xstatic: error: pipe failed\n");
    exit(122);
  }
  if ((pid = fork()) == 0) {  /* Child process. */
    char *args[3];
    close(p[0]);
    if (p[1] != 1) {
      dup2(p[1], 1);  /* Redirect gcc's stdout so our parent can read it. */
      close(p[1]);
    }
    args[0] = (char*)gcc_prog;
    args[1] = "-dumpspecs";
    args[2] = 0;
    execve(gcc_prog, args, environ);
    exit(124);
  }
  if (pid < 0) {
    fdprint(2, "xstatic: error: fork failed\n");
    exit(122);
  }
  close(p[1]);
  result = does_contain_linker_plugin(p[0]);
  close(p[0]);
  if (pid != waitpid(pid, &status, 0) || status != 0) {
    fdprint(2, strdupcat("xstatic: error: detection failed: ", gcc_prog, " -dumpspecs\n"));
    exit(122);
  }
  return result;
}

typedef enum ldmode_t {
  LM_XCLANGLD = 0,  /* Use the ld and -lgcc shipped with clang. */
  LM_XSYSLD = 1,
  LM_XSTATIC = 2,
} ldmode_t;

int main(int argc, char **argv) {
  char *prog;
  char *dir, *dirup;
  char *p;
  char **args, **argp;
  char is_verbose = 0;
  char **argi, *arg;
  struct stat st;

  dir = find_on_path(argv[0]);
  if (!dir) {
    fdprint(2, "xstatic: error: could not find myself on $PATH\n");
    return 122;
  }
  dir = readlink_alloc_all(dir);
  for (p = dir + strlen(dir); p != dir && p[-1] != '/'; --p) {}
  if (p == dir) {
    strcpy(dir, ".");
  } else {
    for (; p != dir && p[-1] == '/'; --p) {}
    p[p == dir] = '\0';  /* Remove basename of dir. */
  }
  dirup = get_up_dir_alloc(dir);
  if (detect_linker(argv)) {  /* clang trampoline runs as as ld. */
    char is_static = 0;
    char **argli;  /* Where to add the -L flags. */

    /* All we do is exec()ing ld.bin with the following dropped from argv:
     *
     * -L/usr/lib...
     * -L/lib...
     * --hash-style=both (because not supported by old ld)
     * --build-id (because not supported by old ld)
     * -z relro (because it increases the binary size and it's useless for static)
     */
    check_ld_crtoarg(argv);
    argp = args = malloc(sizeof(*args) * (argc + 5));
    *argp++ = *argv;
    for (argi = argv + 1; (arg = *argi); ++argi) {
      if (0 == strcmp(arg, "--do-xstaticldv")) {
        is_verbose = 1;
      } else if (0 == strcmp(arg, "-static")) {
        is_static = 1;
      }
    }
    if (!is_static) {
      fdprint(2, "xstatic: error: missing -static\n");
      exit(1);
    }

    *argp++ = "-nostdlib";  /* No system directories to find .a files. */
    /* Find argli: in front of the last -L (which will be removed), but no
     * later than just before the first -l.
     */
    argli = NULL;
    for (argi = argv + 1; (arg = *argi); ++argi) {
      if (arg[0] == '-') {
        if (arg[1] == 'L') {  /* "-L". */
          argli = argi;
        } else if (arg[1] == 'l') {  /* "-l". */
          if (!argli) argli = argi;
          break;
        }
      }
    }
    if (!argli) argli = argv + 1;

    for (argi = argv + 1; (arg = *argi); ++argi) {
      if (argi == argli) {
        char **argj;
        /* Add the user-specified link dirs first (just like non-xstatic). */
        for (argj = argv + 1; *argj; ++argj) {
          if (0 == strncmp(*argj, "-=L", 3)) {
            *argp++ = strdupcat("-L", "", *argj + 3);
          }
        }
        /* We put xstaticcld with libgcc.a first, because clang puts
         * /usr/lib/gcc/i486-linux-gnu/4.4 with libgcc.a before /usr/lib with
         * libc.a .
         *
         * We add these -L flags even if compiler -nostdlib was specified,
         * because non-xstatic compilers do the same.
         */
        *argp++ = strdupcat("-L", dirup, "/xstaticcld");
        *argp++ = strdupcat("-L", dirup, "/xstaticusr/lib");
        argli = NULL;  /* Don't insert the -L flags again. */
      }
      if (0 == strcmp(arg, "-z") &&
          argi[1] &&
          (0 == strcmp(argi[1], "relro") ||
           0 == strcmp(argi[1], "norelro"))) {
        /* Would increase size. */
        ++argi;  /* Omit both arguments: -z relro */
      } else if (arg[0] == '-' && arg[1] == 'L') {  /* "-L". */
        if (arg[2] == '\0' && argi[1]) ++argi;
        /* Omit -L... containing the system link dirs, the user-specified link
         * dir flags were passed as -=L...
         */
      } else if (0 == strncmp(arg, "-=L", 3)) {
        /* Omit -=L here, gets added at position argli. */
      } else if (
          0 == strcmp(arg, "-nostdlib") ||
          0 == strcmp(arg, "--do-xstaticcld") ||
          0 == strcmp(arg, "--do-xstaticldv") ||
          0 == strncmp(arg, "--hash-style=", 13) ||  /* Would increase size. */
          0 == strcmp(arg, "--build-id") ||
          0 == strncmp(arg, "--sysroot=", 10)) {
        /* Omit this argument. */
      } else {
        *argp++ = *argi;
      }
    }
    *argp = NULL;
    prog = strdupcat(argv[0], ".bin", "");  /* "ld.bin". */
    args[0] = prog;
    if (is_verbose) {
      fdprint(2, escape_argv("xstatic-ld: info: running ld:\n", args, "\n"));
    }
    execve(prog, args, environ);
    fdprint(2, strdupcat("xstatic-ld: error: exec failed: ", prog, "\n"));
    return 120;
  }

  if (!argv[1] || 0 == strcmp(argv[1], "--help")) {
    fdprint(1, strdupcat(
        "Usage: ", argv[0], " <gcc|g++|clang|clang++> [<compiler-arg>...]\n"
        "Invokes the C/C++ compiler with -static against the xstatic uClibc.\n"
        ));
    return 1;
  }
  if (argv[1][0] == '-') {
    fdprint(2, "xstatic: error: please specify gcc|clang prog in 1st arg\n");
    return 1;
  }
  check_bflags(argv);
  check_xflags(argv);
  p = strdupcat(dirup, "/xstaticcld/ld.bin", "");
  if (0 != stat(p, &st) || !S_ISREG(st.st_mode)) {
   file_missing:
    fdprint(2, strdupcat(
        "xstatic: error: file missing, please install: ", p, "\n"));
    return 123;
  }
  p = strdupcat(dirup, "/xstaticcld/as", "");
  if (0 != stat(p, &st) || !S_ISREG(st.st_mode)) goto file_missing;
  p = strdupcat(dirup, "/xstaticusr/lib/libc.a", "");
  if (0 != stat(p, &st) || !S_ISREG(st.st_mode)) goto file_missing;
  p = strdupcat(dirup, "/xstaticusr/include/stdio.h", "");
  if (0 != stat(p, &st) || !S_ISREG(st.st_mode)) goto file_missing;
  p = strdupcat(dirup, "/xstaticusr/lib/crt1.o", "");
  if (0 != stat(p, &st) || !S_ISREG(st.st_mode)) goto file_missing;
  p = strdupcat(dirup, "/xstaticcld/crtbeginT.o", "");
  if (0 != stat(p, &st) || !S_ISREG(st.st_mode)) goto file_missing;
  prog = find_on_path(argv[1]);
  if (!prog) {
    fdprint(2, strdupcat(
        "xstatic: error: compiler not found: ", argv[1], "\n"));
    return 119;
  }
  argv[1] = argv[0];
  ++argv;
  --argc;
  argp = args = malloc(sizeof(*args) * (argc + 21));
  *argp++ = prog;  /* Set destination argv[0]. */
  
  /* No need to check for -m64, check_bflags has already done that. */
  if (!argv[1] ||
      (!argv[2] &&
       (0 == strcmp(argv[1], "-v") || 0 == strcmp(argv[1], "-m32"))) ||
      (argv[2] && !argv[3] &&
       0 == strcmp(argv[1], "-m32") &&
       0 == strcmp(argv[2], "-v"))) {
    /* This changes the `Target: ...' of Clang to i386, but of GCC. */
    if (!argv[1] || (!argv[2] && 0 != strcmp(argv[1], "-m32"))) {
      *argp++ = "-m32";
    }
    /* Don't add any flags, because the user wants some version info, and with
     * `-Wl,... -v' gcc and clang won't display version info.
     */
    memcpy(argp, argv + 1, argc * sizeof(*argp));
  } else {
    char need_linker;
    char has_nostdinc, has_nostdincxx;
    lang_t lang;
    need_linker = detect_need_linker(argv);
    detect_nostdinc(argv, &has_nostdinc, &has_nostdincxx);
    detect_lang(prog, argv, &lang);

    /* When adding more arguments here, increase the args malloc count. */
    /* We don't need get_autodetect_archflag(argv), we always send "-m32". */
    *argp++ = "-m32";
    *argp++ = "-static";
    /* The linker would be ../xstaticcld/ld, which is also a trampoline binary
     * of ours.
     */
    *argp++ = strdupcat("-B", dirup, "/xstaticcld");
    /* This puts xstaticusr/include to the top of the include path, and keeps
     * the gcc or clang headers below that. Specifying --nostdinc (when
     * lang.is_compiling) would remove these compiler-specific headers (e.g.
     * stdarg.h), which we don't want removed, because libc headers depend
     * on them.
     *
     * TODO(pts): Make /usr/local/bin/clang also work.
     */
    *argp++ = strdupcat("--sysroot=", dirup,
        lang.is_clang && is_dirprefix(prog, "/usr/bin") ?
        "/xstaticclangempty" : "/xstaticempty");
    if (lang.is_compiling) {
      /*
       * Without this we get the following error compiling binutils 2.20.1:
       * chew.c:(.text+0x233f): undefined reference to `__stack_chk_fail'
       * We can't implement this in a compatible way, glibc gcc generates %gs:20,
       * uClibc-0.9.33 has symbol __stack_chk_guard.
       */
      *argp++ = "-fno-stack-protector";
      if (lang.is_cxx) {
        /* Glitch: It's not possible to disable the gcc warning ``cc1: warning:
         * command line option "-nostdinc++" is valid for C++/ObjC++ but not
         * for C'', there is no such clang warning. Example invocation (with
         * both .c and .cc files): ``xstatic gcc -c -W -Wall -O2 co.c
         * hello.cc''.
         */
        *argp++ = "-nostdinc++";
        if (!has_nostdincxx) {
          /* Clang has -cxx-isystem, which is a no-op when compiling C code,
           * but it adds the directory after the regular -isystem. But we need
           * the C++ headers in front of the C headers (because of conflicting
           * files, e.g. complex.h, tgmath.h, fenv.h).
           *
           * So we don't use -cxx-isystem, but we use -isystem uniformly for
           * Clang and GCC.
           *
           * A small glitch: if there are both C and C++ source files
           * specified (because C source files mustn't have C++ headers on
           * their include path), and a non-C++ compiler is used (e.g.
           * ``xstatic gcc -c -W -Wall -O2 helloco.c hello.cc''), then
           * `#include <vector>' in the .c file would find the C++ vector
           * header, and fail with a confusing error message when parsing it.
           */
          *argp++ = "-isystem";
          *argp++ = strdupcat(dirup, "/xstaticusr/c++include", "");
          /* Regular g++ libstdc++ has non-backward and then backward. */
          *argp++ = "-isystem";
          *argp++ = strdupcat(dirup, "/xstaticusr/c++include/backward", "");
        }
      }
      if (!has_nostdinc) {
        *argp++ = "-isystem";
        *argp++ = strdupcat(dirup, "/xstaticusr/include", "");
      }
    }

    for (argi = argv + 1; (arg = *argi); ++argi) {
      if (0 == strcmp(arg, "-v")) {
        is_verbose = 1;
        *argp++ = arg;
      } else if (0 == strcmp(arg, "-static") ||
                 0 == strcmp(arg, "-xstatic") ||
                 0 == strcmp(arg, "--xstatic") ||
                 0 == strcmp(arg, "-nostdinc") ||
                 0 == strcmp(arg, "-nostdinc++") ||
                 0 == strcmp(arg, "-m32")) {
      } else if (arg[0] == '-' && arg[1] == 'L') {  /* "-L". */
        /* Convert -L to -=L in the linker command-line on which our ld wrapper
         * code can trigger.
         */
        if (arg[2] == '\0' && argi[1]) {
          *argp++ = strdupcat("-Wl,-=L", "", *++argi);
        } else {
          *argp++ = strdupcat("-Wl,-=L", "", arg + 2);
        }
      } else {
        *argp++ = arg;
      }
    }

    if (need_linker) {
      *argp++ = "-Wl,--do-xstaticcld";
      if (is_verbose) *argp++ = "-Wl,--do-xstaticldv";
      /* The default -fuse-linker-plugin breaks our ld with gcc-6.4 and
       * gcc-7.3, so we have to specify -fno-use-linker-plugin. But older
       * gcc-4.4 doesn't support it and fails.
       */
      if (does_gcc_support_linker_plugin(prog)) {
        *argp++ = "-fno-use-linker-plugin";
      }
    }
    *argp++ = NULL;
  }

  if (is_verbose) {
    fdprint(2, escape_argv("xstatic: info: running compiler:\n", args, "\n"));
  }
  execve(prog, args, environ);
  fdprint(2, strdupcat("xstatic: error: exec failed: ", prog, "\n"));
  return 120;
}