/*
** section.c for libelfsh
** 
** Started on  Mon Feb 26 04:12:42 2001 mayhem
** Last update Sun May 12 14:50:36 2002 mayhem
*/
#include "libelfsh.h"






/* Return a pointer on a section giving its name */
Elfsh32_Sect	*elfsh_get_section_by_name(Elfsh32_Obj	*file,
					   char		*name,
					   int		*idx,
					   int		*strindex,
					   int		*num)
{
  char		*sname;
  Elfsh32_Sect	*section;
  int		index;

  if (!file->sht && !elfsh_get_sht(file, NULL))
    return (NULL);
  
  for (index = 0, section = file->sections; section; section = section->next, index++)
    {
      sname = elfsh_get_section_name(file, section->shdr);
      if (!strcmp(name, sname))
	{
	  if (idx)
	    *idx = index;
	  if (num)
	    *num = section->shdr->sh_size;
	  if (strindex)
	    *strindex = section->shdr->sh_link;
	  return (section);
	}
    }

  ELFSH_SETERROR("libelfsh: Section not found .", NULL);
}






/* Return the section header for the 'range' occurence of a 'type typed section */
/* Return NULL if failed */
Elfsh32_Sect		*elfsh_get_section_by_type(Elfsh32_Obj	*file,
						   u_int	type,
						   int		*index,
						   int		*strindex,
						   int		*num,
						   int		range)
{
  int			number;
  int			local_index;
  int			nbr;
  Elfsh32_Sect		*section;

  if (!elfsh_get_sht(file, &nbr))
    return (NULL);

  section = file->sections;
  for (local_index = number = 0; local_index < nbr; local_index++)
    {
      if (local_index == file->hdr->e_shnum)
	ELFSH_SETERROR("libelfsh: Section not found .", NULL);
      if (section->shdr->sh_type == type && ++number > range)
	{
	  
	  if (strindex)
	    *strindex = section->shdr->sh_link;
	  
	  if (num)
	    *num = section->shdr->sh_size;  
	  
	  if (index)
	    *index = local_index;

	  return (section);
	  
	}
      section = section->next;
    }
  
  return (NULL);

}






/* Return a Elf32_Shdr pointer giving the section index in the sht */
Elfsh32_Sect		*elfsh_get_section_by_index(Elfsh32_Obj	*file,
						    int		index,
						    int		*strindex,
						    int		*num)
{
  Elfsh32_Sect		*section;
  int			local;

  if (!file->sht && !elfsh_get_sht(file, num))
    return (NULL);
  
  if (strindex)
    *strindex = file->sht[index].sh_link;
  
  if (num)
    *num = file->sht[index].sh_size;
  
  for (section = file->sections, local = 0; local < index; local++, section = section->next)
    if (!section)
      ELFSH_SETERROR("libelfsh: No section with this index . \n", NULL);

  return (section);
}







/*
** Load the code of a section giving its header
** Should be called after a elfsh_get_section_by_{name,type} to load the code
*/
void		*elfsh_load_section(Elfsh32_Obj	*file, Elf32_Shdr *shdr)
{
  void		*sct;

  if (shdr->sh_size == 0)
    return (NULL);
  XSEEK(file->fd, shdr->sh_offset, SEEK_SET, NULL);
  XALLOC(sct, shdr->sh_size, NULL);
  XREAD(file->fd, sct, shdr->sh_size, NULL);
  return (sct);
}






/* Fill an anonymous (unknown content) section */
void		*elfsh_get_anonymous_section(Elfsh32_Obj *file, Elfsh32_Sect *sect)
{

  /* If the section is already loaded */
  if (sect->data)
    return (sect->data);
  if (!file || !sect)
    ELFSH_SETERROR("libelfsh: invalid NULL parameter", NULL);

  /* So that we deal with padding */
  sect->nbr = (sect->next ? sect->next->shdr->sh_offset - sect->shdr->sh_offset : sect->shdr->sh_size);
  if ((sect->data = elfsh_load_section(file, sect->shdr)) == NULL)
    return (NULL);

  return (sect->data);
}





/* Add a given section to the file's section list */
/* INTERNAL FUNCTION ! Use elfsh_insert_(un)mapped_section() instead */
int		elfsh_add_section(Elfsh32_Obj *file, Elfsh32_Sect *sct, u_int range, void *data, int shiftmode)
{
  Elfsh32_Sect	*tmp;
  char		inserted;

  /* Load the ELF header if not done */
  if (!elfsh_get_hdr(file))
    return (-1);
  
  /* Refuse to insert a section for some conditions */
  if (range >= file->hdr->e_shnum)
    ELFSH_SETERROR("libelfsh: Cannot add section : unknown sht slot \n", -1);
  else if (sct->flags & ELFSH_SECTION_INSERTED)
      ELFSH_SETERROR("libelfsh: Cannot add section : Section already inserted \n", -1);
  else if (sct->data)
    ELFSH_SETERROR("libelfsh: Cannot add section: Section must be empty \n", -1);

  sct->shdr   = file->sht + range;
  sct->parent = file;
  sct->index  = range;
  sct->data   = data;
  inserted    = 0;

#ifdef __DEBUG__
  printf("[LIBELFSH] Adding section [index = %u] in file %s \n", 
	 sct->index, file->name);
#endif

  /* Parse the section list, update indexes, insert section, and insert the data in it */
  for (tmp = file->sections; tmp && tmp->next; tmp = tmp->next)
    if (tmp->index == range)
      {
	sct->prev = tmp->prev;
	sct->next = tmp;
	if (sct->prev)
	  sct->prev->next = sct;
	tmp->prev = sct;
	ELFSH_SHIFT_SECTION(sct, tmp, shiftmode);
      }
    else if (tmp->index >= range)
      ELFSH_SHIFT_SECTION(sct, tmp, shiftmode);

  /* ==> Now the special cases <== */
  /* The section must be inserted at the first place */	 
  if (!inserted && !tmp)
    file->sections = sct;
  /* The section must be inserted at the last place */
  else if (!inserted && !tmp->next)
    {
      tmp->next = sct;
      sct->prev = tmp;
    }
  /* shift the last section if the insertion has already been done */
  else if (inserted && !tmp->next)
    ELFSH_SHIFT_SECTION(sct, tmp, shiftmode);
  
  /* Final fixes */
  sct->phdr   = elfsh_get_parent_segment(file, sct);
  sct->flags |= ELFSH_SECTION_INSERTED;

  return (0);

}







/* Creation a new orphelin section */
Elfsh32_Sect	*elfsh_create_section(char *name)
{
  Elfsh32_Sect	*new;

  XALLOC(new, sizeof(Elfsh32_Sect), NULL);
  new->name = strdup(name);
  return (new);
}






/* Return the parent tht entry for this symbol or NULL if not found */
Elfsh32_Sect	*elfsh_get_parent_section(Elfsh32_Obj *file, u_int value, int *offset)
{
  Elfsh32_Sect	*s;

  if (!file || !value || !elfsh_get_sht(file, NULL))
    return (NULL);
  
  for (s = file->sections; s; s = s->next)
    if (INTERVAL(s->shdr->sh_addr, value, (s->shdr->sh_addr + s->shdr->sh_size)))
      {
	if (offset)
	  *offset = value - s->shdr->sh_addr;
	return (s);
      }
  return (NULL);
}





/* Return the parent tht entry for this symbol or NULL if not found */
Elfsh32_Sect	*elfsh_get_parent_section_by_foffset(Elfsh32_Obj *file, u_int value, int *offset)
{
  Elfsh32_Sect	*s;

  if (!file || !value || (!file->sht && !elfsh_get_sht(file, NULL)))
    return (NULL);
  
  for (s = file->sections; s; s = s->next)
    if (INTERVAL(s->shdr->sh_offset, value, (s->shdr->sh_offset + s->shdr->sh_size)))
      {
	if (offset)
	  *offset = value - s->shdr->sh_offset;
	return (s);
      }
  return (NULL);
}










/* Append data to the section and change the file offset for following sections */
int	  elfsh_append_data_to_section(Elfsh32_Sect *sect, void *input, int len)
{
  Elfsh32_Sect	*actual;
  
  
  /* Put the data at the end of the section */
  if (sect->data)
    XREALLOC(sect->data, sect->data, sect->shdr->sh_size + len, -1);
  else
    XALLOC(sect->data, len, -1);
  memcpy(sect->data + sect->shdr->sh_size, input, len);
  sect->shdr->sh_size += len;

  /* Modify the section header table file offset if we necessary */
  if (sect->parent->hdr->e_shoff > sect->shdr->sh_offset)
    sect->parent->hdr->e_shoff += len;

  /* Update vaddr and foffset for all next sections */
  for (actual = sect->next; actual; actual = actual->next)
    {
      if (actual->shdr->sh_addr)
	actual->shdr->sh_addr += len;
      actual->shdr->sh_offset += len;
    }

  return (sect->shdr->sh_size - len);
}






/* Insert a mapped section in the object */
int		elfsh_insert_mapped_section(Elfsh32_Obj *file, Elfsh32_Sect *sect, Elf32_Shdr hdr, void *data)
{
  Elfsh32_Sect	*bss;
  
  /* Preliminary checks */
  if (!file || !sect)
    ELFSH_SETERROR("libelfsh: Invalid parameters for elfsh_insert_mapped_section . \n", -1);

  /* Insert the bss physically in the file if not already done */
  if (elfsh_fixup_bss(file) < 0)
    ELFSH_SETERROR("libelfsh: Cant fixup .bss section . \n", -1);

  /* Get a ptr on the bss */
  bss = elfsh_get_section_by_name(file, ELFSH_SECTION_NAME_BSS, NULL, NULL, NULL);
  if (!bss)
    ELFSH_SETERROR("libelfsh: Cant find .bss section . \n", -1);

  /* Extend the segment to insert the new section */
  hdr.sh_addr = bss->shdr->sh_addr + bss->shdr->sh_size;
  hdr.sh_offset = bss->shdr->sh_offset + bss->shdr->sh_size;
  bss->phdr->p_filesz += hdr.sh_size;
  bss->phdr->p_memsz  += hdr.sh_size;

  /* Inject our section with the associated header */
  if (elfsh_insert_section_header(file, hdr, bss->index + 1, sect->name) < 0)
    return (-1);
  if (elfsh_add_section(file, sect, bss->index + 1, data, ELFSH_SHIFTING_COMPLETE) < 0)
    return (-1);

  return (0);
}






/* Insert a non-mapped section in the object */
int		elfsh_insert_unmapped_section(Elfsh32_Obj *file, Elfsh32_Sect *sect, Elf32_Shdr hdr, void *data)
{
  Elfsh32_Sect	*s;

  /* Preliminary checks */
  if (!file || !sect)
    ELFSH_SETERROR("libelfsh: Invalid parameters for elfsh_insert_mapped_section . \n", -1);

  if (!elfsh_get_hdr(file))
    return (-1);

  /* Get the last current section */
  s = elfsh_get_section_by_index(file, file->hdr->e_shnum - 1, NULL, NULL);
  if (!s)
    return (-1);

  /* Fix the file offset for the new section */
  hdr.sh_offset = s->shdr->sh_offset + s->shdr->sh_size;

  /* Inject the new section and the associated header */
  if (elfsh_insert_section_header(file, hdr, file->hdr->e_shnum, sect->name) < 0)
    return (-1);
  if (elfsh_add_section(file, sect, file->hdr->e_shnum - 1, data, ELFSH_SHIFTING_COMPLETE) < 0)
    return (-1);
  return (0);

}











