/*
 * Copyright (c) 2011, Chris Johns <chrisj@rtems.org>
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */
/**
 * @file
 *
 * @ingroup rtems-ld
 *
 * @brief RTEMS Linker ELF module manages the libelf interface.
 *
 */

#if !defined (_RLD_ELF_H_)
#define _RLD_ELF_H_

#include <list>
#include <map>
#include <vector>

#include <rld.h>

namespace rld
{
  namespace elf
  {
    /**
     * Forward decl.
     */
    class file;

    /**
     * A relocation record.
     */
    class relocation
    {
    public:
      /**
       * Construct a relocation record.
       *
       * @param sym The symbol the relocation references.
       * @param offset The offset in the section the relocation applies to.
       * @param info The relocation info.
       * @param addend The constant addend value.
       */
      relocation (const symbols::symbol& sym,
                  elf_addr               offset,
                  elf_xword              info,
                  elf_sxword             addend = 0);

      /**
       * Default constructor.
       */
      relocation ();

      /**
       * The offset.
       */
      elf_addr offset () const;

      /**
       * The type of the relocation record.
       */
      uint32_t type () const;

      /**
       * The info.
       */
      elf_xword info () const;

      /**
       * The constant addend.
       */
      elf_sxword addend () const;

      /**
       * Return the symbol.
       */
      const symbols::symbol& symbol () const;

    private:
      const symbols::symbol* sym;     //< The symbol reference.
      elf_addr               offset_;  //< The offset in the section.
      elf_xword              info_;    //< The record's information.
      elf_sxword             addend_;  //< The constant addend value.
    };

    /**
     * A container of relocation records.
     */
    typedef std::vector < relocation > relocations;

    /**
     * An ELF Section. The current implementation only supports a single data
     * descriptor with a section.
     */
    class section
    {
    public:
      /**
       * Construct the section getting the details from the ELF file given the
       * section index.
       *
       * The section types are (from elf(3)):
       *
       *  Section Type         Library Type     Description
       *  ------------         ------------     -----------
       *   SHT_DYNAMIC          ELF_T_DYN        `.dynamic' section entries.
       *   SHT_DYNSYM           ELF_T_SYM        Symbols for dynamic linking.
       *   SHT_FINI_ARRAY       ELF_T_ADDR       Termination function pointers.
       *   SHT_GROUP            ELF_T_WORD       Section group marker.
       *   SHT_HASH             ELF_T_HASH       Symbol hashes.
       *   SHT_INIT_ARRAY       ELF_T_ADDR       Initialization function pointers.
       *   SHT_NOBITS           ELF_T_BYTE       Empty sections.  See elf(5).
       *   SHT_NOTE             ELF_T_NOTE       ELF note records.
       *   SHT_PREINIT_ARRAY    ELF_T_ADDR       Pre-initialization function
       *                                         pointers.
       *   SHT_PROGBITS         ELF_T_BYTE       Machine code.
       *   SHT_REL              ELF_T_REL        ELF relocation records.
       *   SHT_RELA             ELF_T_RELA       Relocation records with addends.
       *   SHT_STRTAB           ELF_T_BYTE       String tables.
       *   SHT_SYMTAB           ELF_T_SYM        Symbol tables.
       *   SHT_SYMTAB_SHNDX     ELF_T_WORD       Used with extended section
       *                                         numbering.
       *   SHT_GNU_verdef       ELF_T_VDEF       Symbol version definitions.
       *   SHT_GNU_verneed      ELF_T_VNEED      Symbol versioning requirements.
       *   SHT_GNU_versym       ELF_T_HALF       Version symbols.
       *   SHT_SUNW_move        ELF_T_MOVE       ELF move records.
       *   SHT_SUNW_syminfo     ELF_T_SYMINFO    Additional symbol flags.
       *
       * @param file_ The ELF file this section is part of.
       * @param index_ The section's index.
       * @param name The section's name.
       * @param type The section's type.
       * @param alignment The section's alignment.
       * @param flags The section's flags.
       * @param addr The section's in-memory address.
       * @param offset The section's offset in the file.
       * @param size The section's file in bytes.
       * @param link The section's header table link.
       * @param info The section's extra information.
       * @param entry_size The section's entry size.
       */
      section (file&              file_,
               int                index_,
               const std::string& name,
               elf_word           type,
               elf_xword          alignment,
               elf_xword          flags,
               elf_addr           addr,
               elf_off            offset,
               elf_xword          size,
               elf_word           link = 0,
               elf_word           info = 0,
               elf_xword          entry_size = 0);

      /**
       * Construct the section given the details. The ELF file must be
       * writable.
       *
       * @param file_ The ELF file this section is part of.
       * @param index The section's index in the ELF file.
       */
      section (file& file_, int index);

      /**
       * Default constructor.
       */
      section ();

      /**
       * Add a data segment descriptor to the section if the file is writable.
       *
       * These are following data types (from elf(3)):
       *
       *   ELF_T_ADDR     Machine addresses.
       *   ELF_T_BYTE     Byte data.  The library will not attempt to translate
       *                  byte data.
       *   ELF_T_CAP      Software and hardware capability records.
       *   ELF_T_DYN      Records used in a section of type SHT_DYNAMIC.
       *   ELF_T_EHDR     ELF executable header.
       *   ELF_T_HALF     16-bit unsigned words.
       *   ELF_T_LWORD    64 bit unsigned words.
       *   ELF_T_MOVE     ELF Move records.
       *   ELF_T_NOTE     ELF Note structures.
       *   ELF_T_OFF      File offsets.
       *   ELF_T_PHDR     ELF program header table entries.
       *   ELF_T_REL      ELF relocation entries.
       *   ELF_T_RELA     ELF relocation entries with addends.
       *   ELF_T_SHDR     ELF section header entries.
       *   ELF_T_SWORD    Signed 32-bit words.
       *   ELF_T_SXWORD   Signed 64-bit words.
       *   ELF_T_SYMINFO  ELF symbol information.
       *   ELF_T_SYM      ELF symbol table entries.
       *   ELF_T_VDEF     Symbol version definition records.
       *   ELF_T_VNEED    Symbol version requirement records.
       *   ELF_T_WORD     Unsigned 32-bit words.
       *   ELF_T_XWORD    Unsigned 64-bit words.
       *
       * @param type The type of data in the segment.
       * @param alignment The in-file alignment of the data. Must be a power of 2.
       * @param size The number of bytes in this data descriptor.
       * @param buffer The data in memory.
       * @param offset The offset within the containing section. Can be computed.
       */
      void add_data (elf_type  type,
                     elf_xword alignment,
                     elf_xword size,
                     void*     buffer = 0,
                     elf_off   offset = 0);

       /**
       * The section's index in the ELF file.
       *
       * @return int The section number.
       */
      int index () const;

      /**
       * The name of the section.
       *
       * @return const std::string& The section's name.
       */
      const std::string& name () const;

      /**
       * The section's data.
       */
      elf_data* data ();

      /**
       * Get the type of the section.
       */
      elf_word type () const;

      /**
       * The section flags.
       */
      elf_xword flags () const;

      /**
       * In-memory address of the section.
       */
      elf_addr address () const;

      /**
       * Alignment constraint.
       */
      elf_xword alignment () const;

      /**
       * The file offset of the section.
       */
      elf_off offset () const;

      /**
       * The header table link.
       */
      elf_word link () const;

      /**
       * Extra information.
       */
      elf_word info () const;

      /**
       * Size of the section.
       */
      elf_xword size () const;

      /**
       * Size of the entries in the section.
       */
      elf_xword entry_size () const;

      /**
       * Number of entries.
       */
      int entries () const;

      /**
       * Return true if the relocation record have an addend field.
       *
       * @retval true The relocation record have the addend field.
       */
      bool get_reloc_type () const;

      /**
       * Set the name index if writable. This is normally done
       * automatically when adding the section to the file.
       */
      void set_name (unsigned int index);

      /**
       * Set the type of relocation records.
       *
       * @param rela If true the records are rela type.
       */
      void set_reloc_type (bool rela);

      /**
       * Add a relocation.
       *
       * @param reloc The relocation record to add.
       */
      void add (const relocation& reloc);

      /**
       * Get the relocations.
       */
      const relocations& get_relocations () const;

    private:

      /**
       * Check the section is valid.
       *
       * @param where Where the check is being made.
       */
      void check (const char* where) const;

      /**
       * Check the section is valid and writable.
       *
       * @param where Where the check is being made.
       */
      void check_writable (const char* where) const;

      file*       file_;  //< The ELF file.
      int         index_; //< The section header index.
      std::string name_;  //< The section's name.
      elf_scn*    scn;    //< ELF private section data.
      elf_shdr    shdr;   //< The section header.
      elf_data*   data_;  //< The section's data.
      bool        rela;   //< The type of relocation records.
      relocations relocs; //< The relocation records.
    };

    /**
     * Container of ELF section pointers.
     */
    typedef std::list < section* > sections;

    /**
     * Container of ELF section as a map, ie associative array.
     */
    typedef std::map < std::string, section > section_table;

    /**
     * An ELF program header.
     */
    class program_header
    {
    public:
      /**
       * Construct a program header.
       */
      program_header ();

      /**
       * Desctruct a program header.
       */
      ~program_header ();

      /**
       * Set the program header.
       *
       * @param type The type of segment.
       * @param flags The segment's flags.
       * @param offset The offet to segment.
       * @param filesz The segment size in the file.
       * @param memsz The segment size in memory.
       * @param align The segment alignment.
       * @param vaddr The virtual address in memory.
       * @param paddr The physical address if any.
       */
      void set (elf_word type,
                elf_word flags,
                elf_off offset,
                elf_xword filesz,
                elf_xword memsz,
                elf_xword align,
                elf_addr vaddr,
                elf_addr paddr = 0);

    private:

      elf_phdr phdr;  //< The ELF program header.
    };

    /**
     * A container of program headers.
     */
    typedef std::list < program_header > program_headers;

    /**
     * An ELF file.
     */
    class file
    {
    public:
     /**
       * Construct an ELF file.
       */
      file ();

      /**
       * Destruct the ELF file object.
       */
      ~file ();

      /**
       * Begin using the ELF file.
       *
       * @param name The full name of the file.
       * @param fd The file descriptor to read or write the file.
       * @param writable The file is writeable. The default is false.
       */
      void begin (const std::string& name, int fd, const bool writable = false);

      /**
       * Begin using the ELF file in an archive.
       *
       * @param name The full name of the file.
       * @param archive The file that is the archive.
       * @param offset The offset of the ELF file in the archive.
       */
      void begin (const std::string& name, file& archive, off_t offset);

      /**
       * End using the ELF file.
       */
      void end ();

      /**
       * Write the ELF file creating it if it is writable. You should have
       * added the sections and the data segment descriptors to the sections
       * before calling write.
       */
      void write ();

      /**
       * Load the header. Done automatically.
       */
      void load_header ();

      /**
       * Get the machine type.
       */
      unsigned int machinetype () const;

      /**
       * Get the type of ELF file.
       */
      unsigned int type () const;

      /**
       * Get the class of the object file.
       */
      unsigned int object_class () const;

      /**
       * Get the data type, ie LSB or MSB.
       */
      unsigned int data_type () const;

      /**
       * Is the file an archive format file ?
       */
      bool is_archive () const;

      /**
       * Is the file an executable ?
       */
      bool is_executable () const;

      /**
       * Is the file relocatable ?
       */
      bool is_relocatable() const;

      /**
       * The number of sections in the file.
       */
      int section_count () const;

      /**
       * Load the sections.
       */
      void load_sections ();

      /**
       * Get a filtered container of the sections. The key is the section
       * type. If the sections are not loaded they are loaded. If the type is 0
       * all sections are returned.
       *
       * @param filtered_secs The container the copy of the filtered sections
       *                      are placed in.
       * @param type The type of sections to filter on. If 0 all sections are
       *             matched.
       */
      void get_sections (sections& filtered_secs, unsigned int type);

      /**
       * Return the section with given index.
       *
       * @param index The section's index to look for.
       * @retval section The section matching the index.
       */
      section& get_section (int index);

      /**
       * Return the index of the string section.
       */
      int strings_section () const;

      /**
       * Get the string from the specified section at the requested offset.
       *
       * @param section The section to search for the string.
       * @param offset The offset in the string section.
       * @return std::string The string.
       */
      std::string get_string (int section, size_t offset);

      /**
       * Get the string from the ELF header declared string section at the
       * requested offset.
       *
       * @param offset The offset in the string section.
       * @return std::string The string.
       */
      std::string get_string (size_t offset);

      /**
       * Load the symbols.
       */
      void load_symbols ();

      /**
       * Get a filtered container of symbols given the various types. If the
       * symbols are not loaded they are loaded.
       *
       * @param filtered_syms The filtered symbols found in the file. This is a
       *                      container of pointers.
       * @param unresolved Return unresolved symbols.
       * @param local Return local symbols.
       * @param weak Return weak symbols.
       * @param global Return global symbols.
       */
      void get_symbols (rld::symbols::pointers& filtered_syms,
                        bool                    unresolved = false,
                        bool                    local = false,
                        bool                    weak = true,
                        bool                    global = true);

      /**
       * Get the symbol by index in the symtabl section.
       */
      const symbols::symbol& get_symbol (const int index) const;

      /**
       * Load the relocation records.
       */
      void load_relocations ();

      /**
       * Clear the relocation records.
       */
      void clear_relocations ();

      /**
       * Set the ELF header. Must be writable.
       *
       * The classes are:
       *   ELFCLASSNONE  This class is invalid.
       *   ELFCLASS32    This defines the 32-bit architecture.  It sup- ports
       *                 machines with files and virtual address spa- ces up to
       *                 4 Gigabytes.
       *   ELFCLASS64    This defines the 64-bit architecture.
       *
       * The types are:
       *   ET_NONE  An unknown type.
       *   ET_REL   A relocatable file.
       *   ET_EXEC  An executable file.
       *   ET_DYN   A shared object.
       *   ET_CORE  A core file.
       *
       * The machine types are:
       *   TDB
       *
       * The datatypes are:
       *   ELFDATA2LSB  Two's complement, little-endian.
       *   ELFDATA2MSB  Two's complement, big-endian.
       *
       * @param type The type of ELF file, ie executable, relocatable etc.
       * @param class_ The files ELF class.
       * @param machinetype The type of machine code present in the ELF file.
       * @param datatype The data type, ie LSB or MSB.
       */
      void set_header (elf_half      type,
                       int           class_,
                       elf_half      machinetype,
                       unsigned char datatype);

      /**
       * Add a section to the ELF file if writable.
       */
      void add (section& sec);

      /**
       * Add a program header to the ELF file if writable.
       */
      void add (program_header& phdr);

      /**
       * Get the ELF reference.
       */
      elf* get_elf ();

      /**
       * Get the name of the file.
       */
      const std::string& name () const;

      /**
       * Is the file writable ?
       */
      bool is_writable () const;

      /**
       * Obtain a reference to this object. End fails while references are
       * held.
       */
      void reference_obtain ();

      /**
       * Release the reference to this object.
       */
      void reference_release ();

      /**
       * Get the machine size in bytes.
       */
      size_t machine_size () const;

      /**
       * Returns true if little endian.
       */
      bool is_little_endian () const;

    private:

      /**
       * Begin using the ELF file.
       *
       * @param name The full name of the file.
       * @param fd The file descriptor to read or write the file.
       * @param writable The file is writeable. It cannot be part of an archive.
       * @param archive The archive's ELF handle or 0 if not an archive.
       * @param offset The offset of the ELF file in the archive if elf is non-zero.
       */
      void begin (const std::string& name,
                  int                fd,
                  const bool         writable,
                  file*              archive,
                  off_t              offset);

      /**
       * Check if the file is usable. Throw an exception if not.
       *
       * @param where Where the check is performed.
       */
      void check (const char* where) const;

      /**
       * Check if the file is usable and writable. Throw an exception if not.
       *
       * @param where Where the check is performed.
       */
      void check_writable (const char* where) const;

      /**
       * Check if the ELF header is valid. Throw an exception if not.
       *
       * @param where Where the check is performed.
       */
      void check_ehdr (const char* where) const;

      /**
       * Check if the ELF program header is valid. Throw an exception if not.
       *
       * @param where Where the check is performed.
       */
      void check_phdr (const char* where) const;

      /**
       * Generate libelf error.
       *
       * @param where Where the error is generated.
       */
      void error (const char* where) const;

      int                  fd_;        //< The file handle.
      int                  refs;       //< The reference count.
      std::string          name_;      //< The name of the file.
      bool                 archive;    //< The ELF file is part of an archive.
      bool                 writable;   //< The file is writeable.
      elf*                 elf_;       //< The ELF handle.
      unsigned int         mtype;      //< The machine type.
      unsigned int         oclass;     //< The object class.
      const char*          ident_str;  //< The ELF file's ident string.
      size_t               ident_size; //< The size of the ident.
      elf_ehdr*            ehdr;       //< The ELF header.
      elf_phdr*            phdr;       //< The ELF program header.
      section_table        secs;       //< The sections as a table.
      program_headers      phdrs;      //< The program headers when creating
                                       //  ELF files.
      rld::symbols::bucket symbols;    //< The symbols. All tables point here.
    };

    /**
     * Return the machine type label given the machine type.
     *
     * @param machinetype The ELF machine type.
     */
    const std::string machine_type (unsigned int machinetype);

    /**
     * Return the global machine type set by the check_file call as a string.
     */
    const std::string machine_type ();

    /**
     * Return the global class set by the check_file call.
     */
    unsigned int object_class ();

    /**
     * Return the global machine type set by the check_file call.
     */
    unsigned int object_machine_type ();

    /**
     * Return the global data type set by the check_file call.
     */
    unsigned int object_datatype ();

    /**
     * Check the file against the global machine type, object class and data
     * type. If this is the first file checked it becomes the default all
     * others are checked against. This is a simple way to make sure all files
     * are the same type.
     *
     * @param file The check to check.
     */
    void check_file(const file& file);

  }
}

#endif
