/*
 * Ess_resolver.c
 *
 * Solve problems when someone try to insmod esscom.o for linux2.4.18mdk on a different kernel.
 * Ess-technology please publish the driver sources for:
 * - ESS Technology ES2838/2839 SuperLink Modem
 * 
 * Copyright 2003 Luca Altamura <lordbuffy@gmx.it>
 *
 * 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.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <elf.h>
#include <linux/module.h>

#define get_data(fp, offset, whence, buffer, buffersize)	        \
({									\
	fseek (fp, offset, whence);					\
									\
	if (fread (buffer, buffersize, 1, fp) <= 0){			\
		fprintf (stderr, "Error: fread!\n");			\
		return (-1);						\
	}								\
})									\

#define put_data(fp, offset, whence, buffer, buffersize)               \
({                                                                      \
         fseek (fp, offset, whence);                                    \
	                                                                \
	 if (fwrite (buffer, buffersize, 1, fp) <= 0){                  \
	 	fprintf (stderr, "Error: fwrite!\n");                   \
		return (-1);                                            \
	 }                                                              \
})                                                                      \

struct module_symbol *ksyms = NULL;

int query_module (const char *name, int which, void *buf, size_t bufsize,
		  size_t * ret);

static char *
format_string (char *string)
{
  char *p = NULL;
  char *p1 = NULL;

  p = string;
  p1 = strchr (p, 'R');

  if (p == NULL || p1 == NULL)
    return NULL;

  p1 += 1;
  *p1 = '\0';

  return (char *) p;
}

static char *
scanner_strstr (struct module_symbol *ksyms, size_t nksyms, char *sym)
{
  struct module_symbol *ksym;
  unsigned int count = 0;

  if (sym == NULL)
    return NULL;

  for (ksym = ksyms; count < nksyms; ++count, ksyms++) {
    if (strstr ((char *) ksyms->name, sym) != NULL) {
      return (char *) ksyms->name;
    }
  }

  return NULL;
}

int
scanner_strcmp (struct module_symbol *ksyms, size_t nksyms, char *sym)
{
  struct module_symbol *ksym;
  unsigned int count = 0;

  if (sym == NULL)
    return 0;

  for (ksym = ksyms; count < nksyms; ++count, ksyms++) {
    if (strcmp ((char *) ksyms->name, sym) == 0) {
      return (1);
    }
  }

  return 0;
}

/* modutils */

int
resolver (void)
{
  struct module_symbol *syms;
  struct module_symbol *s;
  size_t nksyms;
  size_t nsyms;
  size_t ret;
  size_t bufsize;
  int j = 0;

  syms = (struct module_symbol *) malloc (bufsize = 16 * 1024);


  while (query_module (NULL, QM_SYMBOLS, syms, bufsize, &ret)) {
    syms = (struct module_symbol *) realloc (syms, bufsize = ret);
  }

  if (ret == 0) {
    free (syms);
    return (-1);
  }
  else {
    nksyms = nsyms = ret;

    ksyms = syms;

    for (j = 0, s = syms; j < nsyms; ++j, ++s)
      s->name += (unsigned long) syms;
  }


  return nsyms;
}

int
main (int argc, char **argv)
{
  FILE *file;
  struct stat statbuf;

  Elf32_Ehdr elf;

  Elf32_Shdr *sh = NULL;
  Elf32_Shdr shstrtab;
  Elf32_Shdr strtab;

  Elf32_Sym *symbol = NULL;

  unsigned int i = 0;
  unsigned int j = 0;
  unsigned int nksyms = 0;

  char *section_strtab = NULL;
  char *symbol_strtab = NULL;
  char *format = NULL;
  char *format_sym = NULL;

  if (argc < 2) {
    fprintf (stderr, "Ess-resolver. Lordbuffy@gmx.it\n");
    fprintf (stderr, "usage: %s file.o\n", argv[0]);
    return (-1);
  }

  if (stat (argv[1], &statbuf) < 0) {
    fprintf (stderr, "Error: stat!\n");
    return (-1);

  }

  if ((file = fopen (argv[1], "rb+")) == NULL) {
    fprintf (stderr, "Error: fopen!\n");
    return (-1);
  }

  if (fread (&elf, sizeof (elf), 1, file) <= 0) {
    fprintf (stderr, "Error:  fread!\n");
    return (-1);
  }

  /* some controls ehhehe */

  if (elf.e_ident[EI_MAG0] != 0x7f || elf.e_ident[EI_MAG1] != 'E' ||
      elf.e_ident[EI_MAG2] != 'L' || elf.e_ident[EI_MAG3] != 'F') {
    fprintf (stderr, "Error: %s is not an elf file!\n", argv[0]);
    return (-1);
  }

  if (elf.e_type != ET_REL) {
    fprintf (stderr, "Error: %s must be an object-rel file!\n", argv[0]);
    return (-1);
  }

  /* get kernel syms */

  if ((nksyms = resolver ()) == 0) {
    fprintf (stderr, "Error: No Symbols!\n");
    return (-1);
  }

  /* get shstrtab struct from file */

  get_data (file, elf.e_shoff + (elf.e_shstrndx * elf.e_shentsize), SEEK_SET,
	    &shstrtab, elf.e_shentsize);

  fseek (file, shstrtab.sh_offset, SEEK_SET);

  section_strtab = (char *) malloc (shstrtab.sh_size + 1);

  memset (section_strtab, '\0', shstrtab.sh_size + 1);

  /* get shstrtab content from file */

  if (fread (section_strtab, shstrtab.sh_size, 1, file) <= 0) {
    fprintf (stderr, "Error: fread!\n");
    return (-1);
  }

  sh = (Elf32_Shdr *) malloc (elf.e_shnum * elf.e_shentsize);

  get_data (file, elf.e_shoff, SEEK_SET, sh, elf.e_shnum * elf.e_shentsize);

  /* parsing sections & symbols */

  for (i = 0; i < elf.e_shnum; i++) {
    if (strstr (section_strtab + sh[i].sh_name, ".dynsym") ||
	strstr (section_strtab + sh[i].sh_name, ".symtab")) {
      symbol = (Elf32_Sym *) malloc (sh[i].sh_size);
      get_data (file, sh[i].sh_offset, SEEK_SET, symbol, sh[i].sh_size);
      for (j = 0; j < (sh[i].sh_size / sh[i].sh_entsize); j++) {
	if (sh[i].sh_link == elf.e_shstrndx) {
	  symbol_strtab = section_strtab;

	  if (symbol[j].st_shndx == SHN_UNDEF
	      && ELF32_ST_BIND (symbol[j].st_info) != STB_WEAK
	      && ELF32_ST_TYPE (symbol[j].st_info) == 0) {
	    if (strchr (symbol_strtab + symbol[j].st_name, 'R') != NULL) {
	      if (scanner_strcmp
		  (ksyms, nksyms, symbol_strtab + symbol[j].st_name) == 0) {
		format = format_string (symbol_strtab + symbol[j].st_name);
		if ((format_sym =
		     scanner_strstr (ksyms, nksyms, format)) != NULL) {
		  put_data (file,
			    shstrtab.sh_offset +
			    symbol[j].st_name, SEEK_SET,
			    format_sym, strlen (format_sym));

		}
	      }
	    }
	  }
	}
	else {
	  get_data (file,
		    elf.e_shoff + (sh[i].sh_link * elf.e_shentsize),
		    SEEK_SET, &strtab, elf.e_shentsize);

	  symbol_strtab = (char *) malloc (strtab.sh_size + 1);

	  memset (symbol_strtab, '\0', (strtab.sh_size + 1));

	  get_data (file, strtab.sh_offset, SEEK_SET, symbol_strtab,
		    strtab.sh_size);

	  if (symbol[j].st_shndx == SHN_UNDEF
	      && ELF32_ST_BIND (symbol[j].st_info) != STB_WEAK
	      && ELF32_ST_TYPE (symbol[j].st_info) == 0) {
	    if (strchr (symbol_strtab + symbol[j].st_name, 'R') != NULL) {
	      if (scanner_strcmp
		  (ksyms, nksyms, symbol_strtab + symbol[j].st_name) == 0) {
		format = format_string (symbol_strtab + symbol[j].st_name);
		if ((format_sym =
		     scanner_strstr (ksyms, nksyms, format)) != NULL) {
		  put_data (file,
			    strtab.sh_offset +
			    symbol[j].st_name, SEEK_SET,
			    format_sym, strlen (format_sym));
		}
	      }
	    }
	  }
	  free (symbol_strtab);
	}
      }
    }
  }

  fclose (file);
  free (section_strtab);

  return (0);

}

