/* Memory access checker.
   Copyright 1993 Tristan Gingold
		  Written October 1993 by Tristan Gingold

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 library 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; see the file COPYING.  If not, write to
the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

   The author may be reached (Email) at the address marc@david.saclay.cea.fr,
   or (US/French mail) as Tristan Gingold 
   			  8 rue Parmentier
   			  F91120 PALAISEAU
   			  FRANCE
*/

#include <fcntl.h>
#include <sys/param.h>
#include <signal.h>
#include "checker.h"
#include "errlist.h"
#include "message.h"
#include "dmstrings.h"

#ifndef DEFAULT_HOME
#define DEFAULT_HOME "/tmp"
#endif
#ifndef OPTIONS_FILE
#define OPTIONS_FILE "/.checker"	/* never forget the '/' */
#endif

static int silent;
static int stop;
static char *progname;
static dev_t st_dev;	/* just for checking */
static ino_t st_ino;
static char chkr_out_file[MAXPATHLEN] = "/dev/tty";
unsigned int null_pointer_zone = 0x04;
#ifdef CHKR_SAVESTACK
int nosymtab;
#endif
#ifdef CHKR_PROFILE
int profile = 0;
#endif

extern int chkr_is_init;

/* This is for parse_args() */
#define isspace(c) ((c)==' ' || (c)=='\t')

extern void init_morecore(void);
static int parse_args(char *input, char *ouput);
static void parse_opt(char *option);
static void print_message(char **message);
static void read_config_file(char *file);
static void add_disable(uint fisrt, uint last);
static int atod(const char *c);
static int ctod(const char c);
static void store_output_spec(void);
extern void save_signals(void);

struct mem_range (*disable)[];
int disable_cur;
int disable_max;

static char* init_message[]=
{
  M_COPY_M1,
  M_COPY_M2,
  M_COPY_M3,
  M_COPY_M4,
  M_COPY_M5,
  M_COPY_M6,
  M_COPY_M7,
  "\n",
  NULL
};

static char* help_message[]=
{ M_HELP_M1,
  M_HELP_M2,
  M_HELP_M3,
  M_HELP_M4,
  M_HELP_M5
  M_HELP_M6,
#ifdef CHKR_SAVESTACK  
  M_HELP_M7,
#endif
  M_HELP_M8,
  M_HELP_M9,
#ifdef CHKR_PROFILE
  M_HELP_M10,
#endif    
  M_HELP_M11,
  M_HELP_M12,
  "\n",
  NULL
};
  
static char* abort_message[]=
{
  M_ABORT_M1,
  NULL
};
   
/* __chkr_init_chkr is called before main() */
void
__chkr_init_chkr(int argc, char *argv[], char *argp[])
{
 char *args;
 char *out;
 int offset;
 char cffile[MAXPATHLEN];
 static int already_init = 0;
 
 if (argc == 0)		/* when called by ldd */
   {
     print_message(init_message);
     _exit(0);
   }
 
 if (already_init)
   {
     chkr_printf(M_ALREADY_INIT);
     chkr_abort();
   }
 else
   already_init = 1;
   
 chkr_prog_path = chkr_find_executable(argv[0]);
 progname = argv[0];
 
 args = getenv("CHECKEROPTS");
 if (args != (char*)0)
   {
     offset = 0;
     out = alloca(strlen(args)+1);
     while ((offset = parse_args(args, out)))
       {
         args += offset;
         parse_opt(out);
       }
   }

 /* read ~/.checker file and parse it options */   
 args = getenv("HOME");
 if (args != (char*)0)
   strncpy(cffile, args, MAXPATHLEN-10);
 else
   strcpy(cffile, DEFAULT_HOME);
 strcat(cffile, OPTIONS_FILE);
 read_config_file(cffile);
     
 if (!silent)
   print_message(init_message);
    
 chkr_errno = E_UNKNOWN;
 /* some trivial checks. ??_red_zone must be a multiple of 4 */
 if ( be_red_zone % sizeof(void*) || af_red_zone % sizeof(void*))
   {
     chkr_errno = E_BADRDSIZE;
     chkr_perror();
   }
#ifdef CHKR_USE_BITMAP
 known_stack_limit = &argc;
 init_morecore();
#endif 
 if (chkr_prog_path == (char*)0)
   {
     chkr_header(M_PRGNAME_NOT_FOUND);
     chkr_printf(M_ARGV0_IS, argv[0]);
   }
 chkr_is_init = 1;
 
 if(stop)
   {
     chkr_printf(M_STOPPING_MYSELF, SIGCONT, getpid());
     kill(getpid(), SIGSTOP);
   }
 store_output_spec();
 save_signals();
}

/* extract the first field of INPUT and put it to OUTPUT
 * ' , " and \ are recognize
 * return the offset to the next field.
 */
static int
parse_args(char *input, char *output)
{
  char *buf;
  char *arg;
  int offset = 0;
  int squote = 0;	/* means ' */
  int dquote = 0;	/* means " */
  int bquote = 0;	/* means \ */
  
  arg = buf = alloca(strlen(input) + 1);
  
  /* forget the first blanks */
  while(*input != '\0' && isspace(*input))
    {
      input++;
      offset++;
    }
      
  while(*input != '\0')
    {
      if (bquote)
        {
          *arg++ = *input;
          bquote = 0;
        }
      else if (*input == '\\')
          bquote = 1;
      else if (squote)
        {
          if (*input == '\'')
            squote = 0;
          else
            *arg++ = *input;
        }
      else if (dquote)
        {
          if (*input == '\"')
            dquote = 0;
          else
            *arg++ = *input;
        }
      else if (*input == '\'')
        squote = 1;
      else if (*input == '\"')
        dquote = 1;
      else if (isspace(*input))
        break;
      else *arg++ = *input;
      input++;
      offset++;
    }
  *arg = '\0';
  
  if (arg != buf)
    {
      strcpy(output, buf);
      return offset;
    }
  else
    return 0;
}
      
struct Opt
{
 short int number;	/* number returned */
 char short_opt;	/* short flag */
 char *long_opt;	/* long flag */
 char arg;		/* Is there an arg ?*/
};

struct Opt options[]={
{1, 's', "silent", 0},		/* no message at the begin */
{1, 'q', "quiet", 0},		/* idem */
{1, 'h', "help", 0},		/* display help */
{1, 'o', "output", 1},		/* set the output file */
{1, 'i', "image", 1},		/* set the image file */
{1, 'n', "nosymtab", 0},	/* do not use symbol table */
{1, 'a', "abort", 0},		/* abort */
{1, 'p', "profile", 0},		/* display profile infos */
{1, 'N', "NULLzone", 1},	/* set the size of the NULL zone */
{1, 'd', "disable", 1},		/* disable an address */
{1, 'S', "stop", 0},		/* stop before main, use SIGCONT to restart */
{0, 0, "",0}};

char *optarg;
static int
getopt(char *option)
{
 struct Opt *tmp;
 
 if(option[0] != '-')
   return 0;
 if(option[1] == '-')
   {
     for(tmp = options; tmp->number != 0; tmp++)
       {
         if(tmp->arg && strncmp(option+2, tmp->long_opt, strlen(tmp->long_opt))==0)
           {
              optarg = option + 3 + strlen(tmp->long_opt);
              return tmp->short_opt;
           }
         else
           if(strcmp(option + 2, tmp->long_opt)==0)
             return tmp->short_opt;
       }
     return 0;
   }
 else
   {
     for(tmp = options; tmp->number != 0; tmp++)
       {
         if(tmp->arg && option[1] == tmp->short_opt && option[2] == '=')
           {
             optarg = option + 3;
             return tmp->short_opt;
           }
         else
           if (option[1] == tmp->short_opt && option[2] == '\0')
             return tmp->short_opt;
       }
   }
 return 0;
}

static void
parse_opt(char *option)
{
 switch(getopt(option))
   {
     case 's':
     case 'q':	/* quiet & silent */
       silent = 1;
       break;
     case 'h':  /* help */
       print_message(init_message);
       print_message(help_message);
       silent |= 2;
       break;
     case 'a':  /* abort */
       print_message(abort_message);
       chkr_abort();
       break;
     case 'n':  /* no symtab */
#ifdef CHKR_SAVESTACK
       nosymtab = 1;
#endif
       break;
     case 'o': /* output */
       chkr_out = open(optarg, O_WRONLY|O_CREAT|O_TRUNC, S_IRUSR|S_IWUSR);
       if (chkr_out == 0)
         chkr_out = 2;
       else
           strcpy(chkr_out_file, optarg);
       break;
     case 'p':  /* profile */
#ifdef CHKR_PROFILE
       profile = 1;
#endif       
       break;
     case 'N':  /* NULL zone size */
       null_pointer_zone = atod(optarg);
       break;
     case 'd':  /* disable */
       {
         char *c;
         char arg[strlen(optarg)];
         int a;
         
         strcpy(arg, optarg);
         c = strchr(arg, '-');
         if (c == (char*)0)
           {
             a = atod(arg);
             add_disable(a, a);
           }
         else
           {
             *c = '\0';
             add_disable(atod(arg), atod(c+1));
           }
         break;
       }
#if 1 /* BUG : we must check */
     case 'i':	/* image */
       chkr_prog_path = copy_of_exec_file(optarg);
       break;
#endif
     case 'S':  /* stop */
       stop = 1;
       break;
     default:
       chkr_printf(M_UNKNOWN_OP, option);
   }
}

static void
parse_line(char *line)
{
 int offset;
 char *out;
 char *basename;
 
 /* skip comment lines */
 if (line[0] == '\0' || line[0] == '#')
   return;
   
 /* extract the basename */
 basename = out = progname;
 while (*out)
   if (*out++ == '/')
     basename = out;
 
 out = alloca(strlen(line)+1);
 offset = parse_args(line, out);
 line += offset;
 if ((chkr_prog_path && strcmp(out, chkr_prog_path) == 0)
     || strcmp(out, progname) == 0 
     || strcmp(out, basename) == 0
     || strcmp(out, "default") == 0)
   while((offset = parse_args(line, out)))
     {
       line += offset;
       parse_opt(out);
     }
}

static void
read_config_file(char *file)
{
 int fd;
 int n;
 int offset;
 char *rc;
 char *line;
 int len;
 char buffer[_POSIX2_LINE_MAX + 1];
 
 fd = open(file, O_RDONLY);
 if (fd == -1)
   return;
 offset = 0;
 do
   {
     n = read(fd, buffer + offset, _POSIX2_LINE_MAX - offset);
     line = buffer;
     n += offset;
     len = n;
     if (n == 0)
       break;     
     while ((rc = memchr(line, '\n', len)) != (char*)0)
       {
         *rc = '\0';
         parse_line(line);
         len -= rc - line + 1;
         line = rc + 1;
       }
     strncpy(buffer, line, len);
     offset = len;
     if (offset == n)
       offset = 0;	/* line too long */
   }
 while (n == _POSIX2_LINE_MAX);
 
 /* there is perhaps no return at the last line */
 if (len != 0)
   {
     line[len] = '\0';
     parse_line(line);
   }
   
 close (fd);
}

static void
print_message(char **message)
{
 while(*message != (char*)0)
   {
      write(chkr_out, *message, strlen(*message));
      message++;
   }
}

/* convert a string to an int.
   Format are:
   0nnnnn...	base 8,
   0xnnnn...	base 16,
   0bnnnn...	base 2,
   nnnnnn...	base 10
 */
static int
atod(const char *c)
{
  int val=0;
  int base=10;
  int digit;
  
  if(*c == '0')
    {
      if(c[1] == 'x' || c[1] == 'X')
        {
          base = 16;
          c++;
        }
      else if(c[1] == 'b' || c[1] == 'B')
        {
          base = 2;
          c++;
        }
      else
        base = 8;
      c++;
    }
  while(*c != '\0')
    {
       digit = ctod(*c);
       c++;
       if (digit == -1 || digit >= base)
         break;
       val *= base;
       val += digit;
    }
  return val;
}
  
/* convert a char to an int */
static int
ctod(const char c)
{
  if (c >= '0' && c <= '9')
    return c - '0';
  if (c >= 'a' && c <= 'f')
    return c - 'a' + 10;
  if (c >= 'A' && c <= 'F')
    return c - 'A' + 10;
  return -1;
}

static void
add_disable(uint first, uint last)
{
 if (disable_cur + 1 > disable_max)
   {
     disable = sys_realloc(disable, 
                          (disable_max + 16) * sizeof(struct mem_range));
     disable_max += 16;
   }
 (*disable)[disable_cur].first = first;
 (*disable)[disable_cur].last = last;
 disable_cur++;
 return;
}

void
chkr_do_end()
{
 chkr_remove_symtabfile();
#ifdef CHKR_PROFILE
 if (profile)
   display_profile();
#endif  
}

static void
store_output_spec()
{
  int errno_ori = errno;
  struct stat outstat;
  
  if (fstat(chkr_out, &outstat) != 0)
      st_dev = st_ino = 0;
  else
    {
      st_dev = outstat.st_dev;
      st_ino = outstat.st_ino;
    }
  errno = errno_ori;
} 

void
check_output_spec()
{
 int errno_ori = errno;
 struct stat outstat;
 
 if (st_dev == 0 && st_ino == 0)
   return;
 
  if (fstat(chkr_out, &outstat) != 0 
      || st_dev != outstat.st_dev || st_ino != outstat.st_ino)
    {
      chkr_out = open(chkr_out_file,O_APPEND|O_WRONLY);
      if (chkr_out == -1)
        chkr_out = 2;
      store_output_spec();
    }
   errno = errno_ori;
   return;
}
