/*--------------------------------------------------------------------*/
/*--- Launching valgrind                         launcher-darwin.c ---*/
/*--------------------------------------------------------------------*/

/*
   This file is part of Valgrind, a dynamic binary instrumentation
   framework.

   Copyright (C) 2000-2011 Julian Seward 
      jseward@acm.org

   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.

   The GNU General Public License is contained in the file COPYING.
*/

/* Note: this is a "normal" program and not part of Valgrind proper,
   and so it doesn't have to conform to Valgrind's arcane rules on
   no-glibc-usage etc. */

#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <libgen.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/user.h>
#include <unistd.h>
#include <mach-o/fat.h>
#include <mach-o/loader.h>

#include "pub_core_debuglog.h"
#include "pub_core_vki.h"       // Avoids warnings from pub_core_libcfile.h
#include "pub_core_libcproc.h"  // For VALGRIND_LIB, VALGRIND_LAUNCHER
#include "pub_core_ume.h"

static struct {
   cpu_type_t cputype;
   const char *apple_name;     // e.g. x86_64
   const char *valgrind_name;  // e.g. amd64
} valid_archs[] = {
   { CPU_TYPE_X86,       "i386",   "x86" }, 
   { CPU_TYPE_X86_64,    "x86_64", "amd64" }, 
   { CPU_TYPE_ARM,       "arm",    "arm" }, 
   { CPU_TYPE_POWERPC,   "ppc",    "ppc32" }, 
   { CPU_TYPE_POWERPC64, "ppc64",  "ppc64" }, 
};
static int valid_archs_count = sizeof(valid_archs)/sizeof(valid_archs[0]);

static const char *name_for_cputype(cpu_type_t cputype)
{
   int i;
   for (i = 0; i < valid_archs_count; i++) {
      if (valid_archs[i].cputype == cputype) {
         return valid_archs[i].valgrind_name;
      }
   }
   return NULL;
}

/* Report fatal errors */
__attribute__((noreturn))
static void barf ( const char *format, ... )
{
   va_list vargs;

   va_start(vargs, format);
   fprintf(stderr, "valgrind: ");
   vfprintf(stderr, format, vargs);
   fprintf(stderr, "\n");
   va_end(vargs);

   exit(1);
   /*NOTREACHED*/
   assert(0);
}

/* Search the path for the client program */
static const char *find_client(const char *clientname)
{
   static char fullname[PATH_MAX];
   const char *path = getenv("PATH");
   const char *colon;

   while (path)
   {
      if ((colon = strchr(path, ':')) == NULL)
      {
         strcpy(fullname, path);
         path = NULL;
      }
      else
      {
         memcpy(fullname, path, colon - path);
         fullname[colon - path] = '\0';
         path = colon + 1;
      }

      strcat(fullname, "/");
      strcat(fullname, clientname);

      if (access(fullname, R_OK|X_OK) == 0)
         return fullname;
   }

   return clientname;
}

static int fat_has_cputype(struct fat_header *fh, cpu_type_t cputype)
{
   struct fat_arch *fa = (struct fat_arch *)(fh+1);
   uint32_t nfat_arch = ntohl(fh->nfat_arch);
   uint32_t i;
   for (i = 0; i < nfat_arch; i++) {
      if (ntohl(fa[i].cputype) == cputype) return 1;
   }
   return 0;
}

/* Examine the client and work out which arch it is for */
static const char *select_arch(
      const char *clientname, cpu_type_t default_cputype,
      const char *default_arch)
{
   uint8_t buf[4096];
   ssize_t bytes;
   int fd = open(find_client(clientname), O_RDONLY);
   if (fd < 0) {
      barf("%s: %s", clientname, strerror(errno));
   }

   bytes = read(fd, buf, sizeof(buf));
   close(fd);
   if (bytes != sizeof(buf)) {
      return NULL;
   }
   
   // If it's thin, return that arch.
   {
      struct mach_header *mh = (struct mach_header *)buf;
      if (mh->magic == MH_MAGIC  ||  mh->magic == MH_MAGIC_64) {
         return name_for_cputype(mh->cputype);
      } else if (mh->magic == MH_CIGAM  ||  mh->magic == MH_CIGAM_64) {
         return name_for_cputype(OSSwapInt32(mh->cputype));
      }
   }

   // If it's fat, look for a good arch.
   {
      struct fat_header *fh = (struct fat_header *)buf;
      if (ntohl(fh->magic) == FAT_MAGIC) {
         uint32_t nfat_arch = ntohl(fh->nfat_arch);
         int i;
         // If only one fat arch, use it.
         if (nfat_arch == 1) {
            struct fat_arch *fa = (struct fat_arch *)(fh+1);
            return name_for_cputype(ntohl(fa->cputype));
         }
         // Scan fat headers for default arch.
         if (fat_has_cputype(fh, default_cputype)) {
            return default_arch;
         }
         
         // Scan fat headers for any supported arch.
         for (i = 0; i < valid_archs_count; i++) {
            if (fat_has_cputype(fh, valid_archs[i].cputype)) {
               return valid_archs[i].valgrind_name;
            }
         }
      }
   }
   
   return NULL;
}


/* Where we expect to find all our aux files */
static const char *valgrind_lib;

int main(int argc, char** argv, char** envp)
{
   int i, j, loglevel;
   const char *toolname = NULL;
   const char *clientname = NULL;
   int clientname_arg = 0;
   const char *archname = NULL;
   const char *arch;
   const char *default_arch;
   cpu_type_t default_cputype;
   char *toolfile;
   char launcher_name[PATH_MAX+1];
   char* new_line;
   char* set_cwd;
   char* cwd;
   char** new_env;
   char **new_argv;
   int new_argc;

   /* Start the debugging-log system ASAP.  First find out how many 
      "-d"s were specified.  This is a pre-scan of the command line.
      At the same time, look for the tool name. */
   loglevel = 0;
   for (i = 1; i < argc; i++) {
      if (argv[i][0] != '-') {
         clientname = argv[i];
         clientname_arg = i;
         break;
      }
      if (0 == strcmp(argv[i], "--")) {
         if (i+1 < argc) {
            clientname = argv[i+1];
            clientname_arg = i;
         }
         break;
      }
      if (0 == strcmp(argv[i], "-d")) 
         loglevel++;
      if (0 == strncmp(argv[i], "--tool=", 7)) 
         toolname = argv[i] + 7;
      if (0 == strncmp(argv[i], "--arch=", 7))
         archname = argv[i] + 7;
   }

   /* ... and start the debug logger.  Now we can safely emit logging
      messages all through startup. */
   VG_(debugLog_startup)(loglevel, "Stage 1");

   /* Make sure we know which tool we're using */
   if (toolname) {
      VG_(debugLog)(1, "launcher", "tool '%s' requested\n", toolname);
   } else {
      VG_(debugLog)(1, "launcher", 
                       "no tool requested, defaulting to 'memcheck'\n");
      toolname = "memcheck";
   }

   /* Find the real executable if clientname is an app bundle. */
   if (clientname) {
      struct stat st;
      if (0 == stat(clientname, &st)  &&  (st.st_mode & S_IFDIR)) {
         char *copy = strdup(clientname);
         char *appname = basename(copy);
         char *dot = strrchr(appname, '.');
         if (dot) {
            char *newclient;
            *dot = '\0';
            asprintf(&newclient, "%s/Contents/MacOS/%s", clientname, appname);
            VG_(debugLog)(1, "launcher", "Using executable in app bundle: %s\n", newclient);
            clientname = newclient;
            argv[clientname_arg] = newclient;
         }
         free(copy);
      }
   }

   /* Establish the correct VALGRIND_LIB. */
   {  const char *cp;
      cp = getenv(VALGRIND_LIB);
      valgrind_lib = ( cp == NULL ? VG_LIBDIR : cp );
      VG_(debugLog)(1, "launcher", "valgrind_lib = %s\n", valgrind_lib);
   }

   /* Find installed architectures. Use vgpreload_core-<platform>.so as the
    * indicator of whether the platform is installed. */
   for (i = 0; i < valid_archs_count; i++) {
      char *vgpreload_core;
      asprintf(&vgpreload_core, "%s/vgpreload_core-%s-darwin.so", valgrind_lib, valid_archs[i].valgrind_name);
      if (access(vgpreload_core, R_OK|X_OK) != 0) {
         VG_(debugLog)(1, "launcher", "arch '%s' IS NOT installed\n", valid_archs[i].valgrind_name);
         bzero(&valid_archs[i], sizeof(valid_archs[i]));
      } else {
         VG_(debugLog)(1, "launcher", "arch '%s' IS installed\n", valid_archs[i].valgrind_name);
      }
      free(vgpreload_core);
   }

   /* Find the "default" arch (VGCONF_ARCH_PRI from configure). 
      This is the preferred arch from fat files and the fallback. */
   default_arch = NULL;
   default_cputype = 0;
   for (i = 0; i < valid_archs_count; i++) {
      if (!valid_archs[i].cputype) continue;
      if (0 == strncmp(VG_PLATFORM, valid_archs[i].valgrind_name, 
                       strlen(valid_archs[i].valgrind_name))) 
      {
         default_arch = valid_archs[i].valgrind_name;
         default_cputype = valid_archs[i].cputype;
         break;
      }
   }
   if (i == valid_archs_count) barf("Unknown/uninstalled VG_PLATFORM '%s'", VG_PLATFORM);
   assert(NULL != default_arch);
   assert(0 != default_cputype);

   /* Work out what arch to use, or use the default arch if not possible. */
   if (archname != NULL) {
      // --arch from command line
      arch = NULL;
      for (i = 0; i < valid_archs_count; i++) {
         if (0 == strcmp(archname, valid_archs[i].apple_name)  ||  
             0 == strcmp(archname, valid_archs[i].valgrind_name))
         {
            arch = valid_archs[i].valgrind_name;
            break;
         }
      }
      if (i == valid_archs_count) barf("Unknown --arch '%s'", archname);
      assert(NULL != arch);
      VG_(debugLog)(1, "launcher", "using arch '%s' from --arch=%s\n", 
                    arch, archname);
   } 
   else if (clientname == NULL) {
      // no client executable; use default as fallback
      VG_(debugLog)(1, "launcher", 
                       "no client specified, defaulting arch to '%s'\n",
                        default_arch);
      arch = default_arch;
   } 
   else if ((arch = select_arch(clientname, default_cputype,default_arch))) {
      // arch from client executable
      VG_(debugLog)(1, "launcher", "selected arch '%s'\n", arch);
   } 
   else {
      // nothing found in client executable; use default as fallback
      VG_(debugLog)(1, "launcher", 
                       "no arch detected, defaulting arch to '%s'\n",
                       default_arch);
      arch = default_arch;
   }
   
   cwd = getcwd(NULL, 0);
   if (!cwd) barf("Current directory no longer exists.");

   /* Figure out the name of this executable (viz, the launcher), so
      we can tell stage2.  stage2 will use the name for recursive
      invokations of valgrind on child processes. */
   memset(launcher_name, 0, PATH_MAX+1);
   for (i = 0; envp[i]; i++) 
       ; /* executable path is after last envp item */
   /* envp[i] == NULL ; envp[i+1] == executable_path */
   if (envp[i+1][0] != '/') {
      strcpy(launcher_name, cwd);
      strcat(launcher_name, "/");
   }
   if (strlen(launcher_name) + strlen(envp[i+1]) > PATH_MAX)
      barf("launcher path is too long");
   strcat(launcher_name, envp[i+1]);
   VG_(debugLog)(1, "launcher", "launcher_name = %s\n", launcher_name);

   /* tediously augment the env: VALGRIND_LAUNCHER=launcher_name */
   asprintf(&new_line, VALGRIND_LAUNCHER "=%s", launcher_name);

   /* tediously augment the env: VALGRIND_STARTUP_PWD_%PID_XYZZY=current_working_dir */
   asprintf(&set_cwd, "VALGRIND_STARTUP_PWD_%u_XYZZY=%s", getppid(), cwd);

   // Note that Apple binaries get a secret fourth arg, "char* apple", which
   // contains the executable path.  Don't forget about it.
   for (j = 0; envp[j]; j++)
      ;
   new_env = malloc((j+4) * sizeof(char*));
   if (new_env == NULL)
      barf("malloc of new_env failed.");
   for (i = 0; i < j; i++)
      new_env[i] = envp[i];
   new_env[i++] = new_line;
   new_env[i++] = set_cwd;
   new_env[i++] = NULL;
   new_env[i  ] = envp[i-2]; // the 'apple' arg == the executable_path
   assert(i == j+3);

   /* tediously edit env: hide dyld options from valgrind's captive dyld */
   for (i = 0; envp[i]; i++) {
      if (0 == strncmp(envp[i], "DYLD_", 5)) {
         envp[i][0] = 'V';  /* VYLD_; changed back by initimg-darwin */
      }
   }

   /* tediously edit argv: remove --arch= */
   new_argv = malloc((1+argc) * sizeof(char *));
   for (i = 0, new_argc = 0; i < argc; i++) {
      if (0 == strncmp(argv[i], "--arch=", 7)) {
         // skip
      } else {
         new_argv[new_argc++] = argv[i];
      }
   }
   new_argv[new_argc++] = NULL;

   /* Build the stage2 invokation, and execve it.  Bye! */
   asprintf(&toolfile, "%s/%s-%s-darwin", valgrind_lib, toolname, arch);
   if (access(toolfile, R_OK|X_OK) != 0) {
      barf("tool '%s' not installed (%s) (%s)", toolname, toolfile, strerror(errno));
   }

   VG_(debugLog)(1, "launcher", "launching %s\n", toolfile);

   execve(toolfile, new_argv, new_env);

   fprintf(stderr, "valgrind: failed to start tool '%s' for platform '%s-darwin': %s\n",
                   toolname, arch, strerror(errno));

   exit(1);
}