/* Copyright (C) 2002 Red Hat, Inc.
   Written by Ulrich Drepper <drepper@redhat.com>, 2002.

   This program is Open Source software; you can redistribute it and/or
   modify it under the terms of the Open Software License version 1.0 as
   published by the Open Source Initiative.

   You should have received a copy of the Open Software License along
   with this program; if not, you may obtain a copy of the Open Software
   License version 1.0 from http://www.opensource.org/licenses/osl.php or
   by writing the Open Source Initiative c/o Lawrence Rosen, Esq.,
   3001 King Ranch Road, Ukiah, CA 95482.   */

#include <fcntl.h>
#include <libasm.h>
#include <libelf.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>


static const char fname[] = "asm-tst3-out.o";


static const char *scnnames[5] =
  {
    [0] = "",
    [1] = ".data",
    [2] = ".strtab",
    [3] = ".symtab",
    [4] = ".shstrtab"
  };


static unsigned int scntypes[5] =
  {
    [0] = SHT_NULL,
    [1] = SHT_PROGBITS,
    [2] = SHT_STRTAB,
    [3] = SHT_SYMTAB,
    [4] = SHT_STRTAB
  };


int
main (void)
{
  AsmCtx_t *ctx;
  AsmScn_t *scn1;
  AsmScn_t *scn2;
  int result = 0;
  int fd;
  Elf *elf;
  GElf_Ehdr ehdr_mem;
  GElf_Ehdr *ehdr;
  size_t cnt;

  elf_version (EV_CURRENT);

  ctx = asm_begin (fname, false, EM_386, ELFCLASS32, ELFDATA2LSB);
  if (ctx == NULL)
    {
      printf ("cannot create assembler context: %s\n", asm_errmsg (-1));
      return 1;
    }

  /* Create two sections.  */
  scn1 = asm_newscn (ctx, ".data", SHT_PROGBITS, SHF_ALLOC | SHF_WRITE);
  scn2 = asm_newsubscn (scn1, 1);
  if (scn1 == NULL || scn2 == NULL)
    {
      printf ("cannot create section in output file: %s\n", asm_errmsg (-1));
      asm_abort (ctx);
      return 1;
    }

  /* Special alignment for the .text section.  */
  if (asm_align (scn1, 16) != 0)
    {
      printf ("cannot align .text section: %s\n", asm_errmsg (-1));
      result = 1;
    }

  /* Add a few strings with names.  */
  if (asm_newsym (scn1, "one", 4, STT_OBJECT, STB_GLOBAL) == NULL)
    {
      printf ("cannot create first name: %s\n", asm_errmsg (-1));
      result = 1;
    }
  if (asm_addstrz (scn1, "one", 4) != 0)
    {
      printf ("cannot insert first string: %s\n", asm_errmsg (-1));
      result = 1;
    }
  if (asm_newsym (scn2, "three", 6, STT_OBJECT, STB_WEAK) == NULL)
    {
      printf ("cannot create second name: %s\n", asm_errmsg (-1));
      result = 1;
    }
  if (asm_addstrz (scn2, "three", 0) != 0)
    {
      printf ("cannot insert second string: %s\n", asm_errmsg (-1));
      result = 1;
    }
  if (asm_newsym (scn1, "two", 4, STT_OBJECT, STB_LOCAL) == NULL)
    {
      printf ("cannot create third name: %s\n", asm_errmsg (-1));
      result = 1;
    }
  if (asm_addstrz (scn1, "two", 4) != 0)
    {
      printf ("cannot insert third string: %s\n", asm_errmsg (-1));
      result = 1;
    }

  /* Create the output file.  */
  if (asm_end (ctx) != 0)
    {
      printf ("cannot create output file: %s\n", asm_errmsg (-1));
      asm_abort (ctx);
      return 1;
    }

  /* Check the file.  */
  fd = open (fname, O_RDONLY);
  if (fd == -1)
    {
      printf ("cannot open generated file: %m\n");
      result = 1;
      goto out;
    }

  elf = elf_begin (fd, ELF_C_READ, NULL);
  if (elf == NULL)
    {
      printf ("cannot create ELF descriptor: %s\n", elf_errmsg (-1));
      result = 1;
      goto out_close;
    }
  if (elf_kind (elf) != ELF_K_ELF)
    {
      puts ("not a valid ELF file");
      result = 1;
      goto out_close2;
    }

  ehdr = gelf_getehdr (elf, &ehdr_mem);
  if (ehdr == NULL)
    {
      printf ("cannot get ELF header: %s\n", elf_errmsg (-1));
      result = 1;
      goto out_close2;
    }

  for (cnt = 1; cnt < 5; ++cnt)
    {
      Elf_Scn *scn;
      GElf_Shdr shdr_mem;
      GElf_Shdr *shdr;

      scn = elf_getscn (elf, cnt);
      if (scn == NULL)
	{
	  printf ("cannot get section %Zd: %s\n", cnt, elf_errmsg (-1));
	  result = 1;
	  continue;
	}

      shdr = gelf_getshdr (scn, &shdr_mem);
      if (shdr == NULL)
	{
	  printf ("cannot get section header for section %Zd: %s\n",
		  cnt, elf_errmsg (-1));
	  result = 1;
	  continue;
	}

      if (strcmp (elf_strptr (elf, ehdr->e_shstrndx, shdr->sh_name),
		  scnnames[cnt]) != 0)
	{
	  printf ("section %Zd's name differs: %s vs %s\n", cnt,
		  elf_strptr (elf, ehdr->e_shstrndx, shdr->sh_name),
		  scnnames[cnt]);
	  result = 1;
	}

      if (shdr->sh_type != scntypes[cnt])
	{
	  printf ("section %Zd's type differs\n", cnt);
	  result = 1;
	}

      if ((cnt == 1 && shdr->sh_flags != (SHF_ALLOC | SHF_WRITE))
	  || (cnt != 1 && shdr->sh_flags != 0))
	{
	  printf ("section %Zd's flags differs\n", cnt);
	  result = 1;
	}

      if (shdr->sh_addr != 0)
	{
	  printf ("section %Zd's address differs\n", cnt);
	  result = 1;
	}

      if (cnt == 3)
	{
	  Elf_Data *data;

	  if (shdr->sh_link != 2)
	    {
	      puts ("symbol table has incorrect link");
	      result = 1;
	    }

	  data = elf_getdata (scn, NULL);
	  if (data == NULL)
	    {
	      puts ("cannot get data of symbol table");
	      result = 1;
	    }
	  else
	    {
	      size_t inner;

	      for (inner = 1;
		   inner < (shdr->sh_size
			    / gelf_fsize (elf, ELF_T_SYM, 1, EV_CURRENT));
		   ++inner)
		{
		  GElf_Sym sym_mem;
		  GElf_Sym *sym;

		  sym = gelf_getsym (data, inner, &sym_mem);
		  if (sym == NULL)
		    {
		      printf ("cannot get symbol %zu: %s\n",
			      inner, elf_errmsg (-1));
		      result = 1;
		    }
		  else
		    {
		      /* The order of the third and fourth entry depends
			 on how the hash table is organized.  */
		      static const char *names[4] =
			{
			  [0] = "",
			  [1] = "two",
			  [2] = "one",
			  [3] = "three"
			};
		      static const int info[4] =
			{
			  [0] = GELF_ST_INFO (STB_LOCAL, STT_NOTYPE),
			  [1] = GELF_ST_INFO (STB_LOCAL, STT_OBJECT),
			  [2] = GELF_ST_INFO (STB_GLOBAL, STT_OBJECT),
			  [3] = GELF_ST_INFO (STB_WEAK, STT_OBJECT)
			};
		      static const unsigned value[4] =
			{
			  [0] = 0,
			  [1] = 4,
			  [2] = 0,
			  [3] = 8
			};

		      if (strcmp (names[inner],
				  elf_strptr (elf, shdr->sh_link,
					      sym->st_name)) != 0)
			{
			  printf ("symbol %zu has different name\n", inner);
			  result = 1;
			}

		      if (sym->st_value != value[inner])
			{
			  printf ("symbol %zu has wrong value\n", inner);
			  result = 1;
			}

		      if (sym->st_other != 0)
			{
			  printf ("symbol %zu has wrong other info\n", inner);
			  result = 1;
			}

		      if (sym->st_shndx != 1)
			{
			  printf ("symbol %zu has wrong section reference\n",
				  inner);
			  result = 1;
			}

		      if (sym->st_info != info[inner])
			{
			  printf ("symbol %zu has wrong type or binding\n",
				  inner);
			  result = 1;
			}

		      if ((inner != 3 && sym->st_size != 4)
			  || (inner == 3 && sym->st_size != 6))
			{
			  printf ("symbol %zu has wrong size\n", inner);
			  result = 1;
			}
		    }
		}
	    }
	}
    }

 out_close2:
  elf_end (elf);
 out_close:
  close (fd);
 out:
  /* We don't need the file anymore.  */
  unlink (fname);

  return result;
}