/***************************************************************************/
/*                                                                         */
/*  readargs.c - Read command line arguments for injectso                  */
/*                                                                         */
/*  Copyright (C) 2000 Shaun Clowes                                        */
/*                                                                         */
/*  This program is free software; you can redistribute it and/or modify   */
/*  it under the terms of version 2 of the GNU General Public License as   */
/*  published by the Free Software Foundation;                             */
/*                                                                         */
/*  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., 675 Mass Ave, Cambridge, MA 02139, USA.              */
/*                                                                         */
/***************************************************************************/

#include <stdio.h>
#include <errno.h>
#include <stdarg.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <injectso.h>
#include <readargs.h>
#include <errorlog.h>

/* Argument Global                                                         */
SProgArgs tProgArgs;

/* Local function prototypes                                               */
int fix_files();
int is_proc_enabled();
int get_pid_filename(pid_t tPid, char *sFileName, unsigned long ulSize);

/* read_args() - Read the command line arguments and parse the options     */
int read_args(int argc, char *argv[]) {
   char cOpt;
   char *pcInvalid;
   int iOpt;
   int iError = 0;
   char sTargetFileName[BUFSIZ];

   /* Process arguments with getopt                                        */

   /* We don't want getopt to show error messages for us, so we switch it  */
   /* off                                                                  */
   opterr = 0;

   /* Setup defaults for relevant options                                  */
   memset(&tProgArgs, 0x0, sizeof(tProgArgs));
   tProgArgs.iOptDebug = 0;
   tProgArgs.tOptPid = 0;

   while (-1 != (iOpt = getopt(argc, argv, "hsvnlcp:"))) {
      cOpt = iOpt;
      switch (cOpt) {
         case 'p':
            tProgArgs.tOptPid = strtol(optarg, &pcInvalid, 10);
            if ((*optarg == '\0') || (*pcInvalid != '\0')) {
               show_error("Invalid pid %s\n", optarg);
               iError = 1;
            }
            break;
         case 'v':
            if (tProgArgs.iOptDebug == 3) {
               show_error("Only three v options may be specified\n");
               iError = 1;
            } else 
               tProgArgs.iOptDebug++;
            break;
         case 'l':
            if (tProgArgs.iOptNoHash != 0) {
               show_error("-l may only be specified once\n");
               iError = 1;
            } else 
               tProgArgs.iOptNoHash = 1;
            break;
         case 'c':
            if (tProgArgs.sOptCallFunc != NULL) {
               show_error("-c may only be specified once\n");
               iError = 1;
            } else {
               if (optarg && (*optarg == '\0'))
                  tProgArgs.sOptCallFunc = strdup(optarg);
               else
                  tProgArgs.sOptCallFunc = "intercept_begin";
            }
            break;
         case 'n':
            if (tProgArgs.iOptNoSysRestart != 0) {
               show_error("-n may only be specified once\n");
               iError = 1;
            } else 
               tProgArgs.iOptNoSysRestart = 1;
            break;
         case 'h':
            usage();
            break;
         case '?':
            switch(optopt) {
               case 'p':
                  show_error("A pid must be specified with the -p option\n");
                  break;
               case '?':
                  usage();
                  break;
               default:
                  show_error("Unrecognized option -%c\n", optopt);
            }
            iError = 1;
            break;
         default:
      }
   }

   if ((iError == 0) && (tProgArgs.tOptPid == 0)) {
      show_error("A target pid must be specified with the -p option\n");
      iError = 1;
   }

   if ((iError == 0) && (!(tProgArgs.sOptLibrary = argv[optind]) || 
       (*(tProgArgs.sOptLibrary) == '\0'))) {
      show_error("Please specify a library to inject\n");
      iError = 1;
   }
   
   optind++;

   if ((iError == 0) && (!(tProgArgs.sOptTarget = argv[optind]) || 
       (*(tProgArgs.sOptTarget) == '\0'))) {
      /* Ok, the user didn't specify the filename of the target executable */
      /* so we try and work it out through proc if this machine is proc    */
      /* filesystem enabled (and proc is mounted on /proc)                 */
      if (!is_proc_enabled()) {
         show_error("Please specify the object file of the target process\n");
         iError = 1;
      } else if (get_pid_filename(tProgArgs.tOptPid, sTargetFileName, 
                                   sizeof(sTargetFileName))) {
         iError = 1;
      } else {
         tProgArgs.sOptTarget = malloc(strlen(sTargetFileName) + 1);
         if (!tProgArgs.sOptTarget)
            show_abort("malloc error\n");
         strcpy(tProgArgs.sOptTarget, sTargetFileName);
      }
   }

   if (iError) {
      fprintf(stderr, "Try '%s -h' for more information\n", sProgName);
      return(-1);
   } 

   /* Make sure the target filenames specified are absolute                */
   fix_files();

   if (tProgArgs.iOptDebug > 2) {
      show_debug(3, "iOptDebug =           %d\n", tProgArgs.iOptDebug);
      show_debug(3, "iOptNoSysRestart =    %d\n", tProgArgs.iOptNoSysRestart);
      show_debug(3, "iOptNoHash =          %d\n", tProgArgs.iOptNoHash);
      show_debug(3, "tOptPid =             %d\n", tProgArgs.tOptPid);
      show_debug(3, "sOptLibrary =         %s\n", tProgArgs.sOptLibrary);
      show_debug(3, "sOptTarget =          %s\n", tProgArgs.sOptTarget);
      show_debug(3, "sOptCallFunc =        %s\n", (tProgArgs.sOptCallFunc ? 
                                                   tProgArgs.sOptCallFunc : ""));
   }

   return(0);
}
   
/* fix_files() - If the location of the object file or the library to be   */
/* injected isn't absolute we prefix the current directory here            */
int fix_files() {
   char sBuffer[1024];
   char sCwd[1024];

   sCwd[0] = '\0';
   getcwd(sCwd, sizeof(sCwd));
   if (*(tProgArgs.sOptTarget) != '/') {
      snprintf(sBuffer, sizeof(sBuffer), "%s/%s", sCwd, tProgArgs.sOptTarget);
      sBuffer[sizeof(sBuffer) - 1] = '\0';
      show_debug(3, "Resolved relative target object %s to absolute path %s\n",
                   tProgArgs.sOptTarget, sBuffer);   
      tProgArgs.sOptTarget = strdup(sBuffer);
   }
   if (*(tProgArgs.sOptLibrary) != '/') {
      snprintf(sBuffer, sizeof(sBuffer), "%s/%s", sCwd, tProgArgs.sOptLibrary);
      sBuffer[sizeof(sBuffer) - 1] = '\0';
      show_debug(3, "Resolved relative library name %s to absolute path %s\n",
                   tProgArgs.sOptLibrary, sBuffer);   
      tProgArgs.sOptLibrary = strdup(sBuffer);
   }

   return(0);
}

/* check_proc_enabled() - Determine if this machine has a proc filesystem  */
/* from which we can read the executable name of a running process         */
int is_proc_enabled() {
   if (!access("/proc/self/object/a.out", R_OK))
      return(1);
   else if (!access("/proc/self/exe", R_OK))
      return(1);
   else
      return(0);
}

/* get_pid_filename() - Given a pid, return the filename of the executable */
/* used to create that process (using the proc filesystem information).    */
/* Filename may be up to ulSize - 1 (NULL terminated). Returns 0 on        */
/* success, 1 on failure                                                   */
int get_pid_filename(pid_t tPid, char *sFileName, unsigned long ulSize) {
   char sProcFileName[BUFSIZ];
   int iRc;

   if (!access("/proc/self/object/a.out", R_OK)) {
      /* Just feed back /proc/pid/object/a.out which will always           */
      /* reference the target executable (if it exists)                    */
      snprintf(sProcFileName, sizeof(sProcFileName), "/proc/%u/object/a.out",
               tPid);
      sProcFileName[sizeof(sProcFileName) - 1] = '\0';
      if (access(sProcFileName, R_OK)) {
         show_error("Could not read /proc file for target, "
                    "please specify the target executable filename "
                    "on the command line\n");
         return(1);
      }
      strncpy(sFileName, sProcFileName, ulSize);
      sFileName[ulSize - 1] = '\0';
   } else if (!access("/proc/self/exe", R_OK)) {
      /* Try to work out the executable target through /proc/pid/exe       */
      snprintf(sProcFileName, sizeof(sProcFileName), "/proc/%u/exe",
               tProgArgs.tOptPid);
      sProcFileName[sizeof(sProcFileName) - 1] = '\0';
      if (-1 == (iRc = readlink(sProcFileName, sFileName, ulSize))) {
         show_error("Could not read /proc link to target file, "
                    "please specify the target executable filename "
                    "on the command line\n");
         return(1);
      }
      if (iRc > ulSize - 1)
         iRc = ulSize - 1;
      sFileName[iRc] = '\0';
   }

   return(0);
}

/* usage() - Show usage and exit                                           */
void usage() {
   if (!is_proc_enabled())
      fprintf(stderr, "Usage: %s [option ...] -p pid library targetprogram\n", sProgName);
   else
      fprintf(stderr, "Usage: %s [option ...] -p pid library [targetprogram]\n", sProgName);
   fprintf(stderr, "Inject shared library into a process\n");
   fprintf(stderr, "%-20s Attach to process <pid>\n", "  -p pid");
   fprintf(stderr, "%-20s Output verbose information\n", "  -v, -vv, -vvv");
   fprintf(stderr, "%-20s Call function <name> after injection (default 'intercept_begin')\n", "  -c [name]");
   fprintf(stderr, "%-20s Do NOT attempt to restart syscalls, return EINTR instead\n", "  -n");
   fprintf(stderr, "%-20s Do NOT use symbol hash tables to locate symbols\n", "  -l");
   fprintf(stderr, "%-20s Display this help and exit\n", "  -h, -?");

   exit(1);
}

