/*
 * Copyright (c) 2011-2012, 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 ELF format images.
 *
 */

#include <string.h>

#include <rld.h>

namespace rld
{
  namespace elf
  {
    /**
     * Throw an ELF error.
     *
     * @param where Where the error is raised.
     */
    void libelf_error (const std::string& where)
    {
      throw rld::error (::elf_errmsg (-1), "libelf:" + where);
    }

    /**
     * We record the first class, machine and .. type of object file we get the
     * header of and all header must match. We cannot mix object module types.
     */
    static unsigned int elf_object_class = ELFCLASSNONE;
    static unsigned int elf_object_machinetype = EM_NONE;
    static unsigned int elf_object_datatype = ELFDATANONE;

    /**
     * A single place to initialise the libelf library. This must be called
     * before any libelf API calls are made.
     */
    static void
    libelf_initialise ()
    {
      static bool libelf_initialised = false;
      if (!libelf_initialised)
      {
        if (::elf_version (EV_CURRENT) == EV_NONE)
          libelf_error ("initialisation");
        libelf_initialised = true;
      }
    }

    relocation::relocation (const symbols::symbol& sym,
                            elf_addr               offset,
                            elf_xword              info,
                            elf_sxword             addend)
      : sym (&sym),
        offset_ (offset),
        info_ (info),
        addend_ (addend)
    {
    }

    relocation::relocation ()
      : sym (0),
        offset_ (0),
        info_ (0),
        addend_ (0)
    {
    }

    elf_addr
    relocation::offset () const
    {
      return offset_;
    }

    uint32_t
    relocation::type () const
    {
      return GELF_R_TYPE (info_);
    }

    elf_xword
    relocation::info () const
    {
      return info_;
    }

    elf_sxword
    relocation::addend () const
    {
      return addend_;
    }

    const symbols::symbol&
    relocation::symbol () const
    {
      if (sym)
        return *sym;
      throw rld::error ("no symbol", "elf:relocation");
    }

    section::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,
                      elf_word           info,
                      elf_xword          entry_size)
      : file_ (&file_),
        index_ (index_),
        name_ (name_),
        scn (0),
        data_ (0),
        rela (false)
    {
      if (!file_.is_writable ())
        throw rld::error ("not writable",
                          "elf:section" + file_.name () + " (" + name_ + ')');

      scn = ::elf_newscn (file_.get_elf ());
      if (!scn)
        libelf_error ("elf_newscn: " + name_ + " (" + file_.name () + ')');

      if (::gelf_getshdr(scn, &shdr) == 0)
        libelf_error ("gelf_getshdr: " + name_ + " (" + file_.name () + ')');

      shdr.sh_name = 0;
      shdr.sh_type = type;
      shdr.sh_flags = flags;
      shdr.sh_addr = addr;
      shdr.sh_offset = offset;
      shdr.sh_size = size;
      shdr.sh_link = link;
      shdr.sh_info = info;
      shdr.sh_addralign = alignment;
      shdr.sh_entsize = entry_size;

      if (type == SHT_NOBITS)
        add_data (ELF_T_BYTE, alignment, size);

      if (!gelf_update_shdr (scn, &shdr))
        libelf_error ("gelf_update_shdr: " + name_ + " (" + file_.name () + ')');
    }

    section::section (file& file_, int index_)
      : file_ (&file_),
        index_ (index_),
        scn (0),
        data_ (0),
        rela (false)
    {
      memset (&shdr, 0, sizeof (shdr));

      scn = ::elf_getscn (file_.get_elf (), index_);
      if (!scn)
        libelf_error ("elf_getscn: " + file_.name ());

      if (!::gelf_getshdr (scn, &shdr))
        libelf_error ("gelf_getshdr: " + file_.name ());

      if (shdr.sh_type != SHT_NULL)
      {
        name_ = file_.get_string (shdr.sh_name);
        data_ = ::elf_getdata (scn, 0);
        if (!data_)
        {
          data_ = ::elf_rawdata (scn, 0);
          if (!data_)
            libelf_error ("elf_getdata: " + name_ + '(' + file_.name () + ')');
        }
      }

      if (rld::verbose () >= RLD_VERBOSE_FULL_DEBUG)
        std::cout << "elf::section: index=" << index ()
                  << " name='" << name () << "'"
                  << " size=" << size ()
                  << " align=" << alignment ()
                  << " flags=0x" << std::hex << flags () << std::dec
                  << std::endl;
    }

    section::section (const section& orig)
      : file_ (orig.file_),
        index_ (orig.index_),
        name_ (orig.name_),
        scn (orig.scn),
        shdr (orig.shdr),
        data_ (orig.data_),
        rela (orig.rela),
        relocs (orig.relocs)
    {
    }

    section::section ()
      : file_ (0),
        index_ (-1),
        scn (0),
        data_ (0),
        rela (false)
    {
      memset (&shdr, 0, sizeof (shdr));
    }

    void
    section::add_data (elf_type  type,
                       elf_xword alignment,
                       elf_xword size,
                       void*     buffer,
                       elf_off   offset)
    {
      check_writable ("add_data");

      data_ = ::elf_newdata(scn);
      if (!data_)
        libelf_error ("elf_newdata: " + name_ + " (" + file_->name () + ')');

      data_->d_type = type;
      data_->d_off = offset;
      data_->d_size = size;
      data_->d_align = alignment;
      data_->d_version = EV_CURRENT;
      data_->d_buf = buffer;

      if (!gelf_update_shdr (scn, &shdr))
        libelf_error ("gelf_update_shdr: " + name_ + " (" + file_->name () + ')');
    }

    int
    section::index () const
    {
      check ("index");
      return index_;
    }

    const std::string&
    section::name () const
    {
      check ("name");
      return name_;
    }

    elf_data*
    section::data ()
    {
      check ("data");
      return data_;
    }

    elf_word
    section::type () const
    {
      check ("type");
      return shdr.sh_type;
    }

    elf_xword
    section::flags () const
    {
      check ("flags");
      return shdr.sh_flags;
    }

    elf_addr
    section::address () const
    {
      check ("address");
      return shdr.sh_addr;
    }

    elf_xword
    section::alignment () const
    {
      check ("alignment");
      return shdr.sh_addralign;
    }

    elf_off
    section::offset () const
    {
      check ("offset");
      return shdr.sh_offset;
    }

    elf_word
    section::link () const
    {
      check ("link");
      return shdr.sh_link;
    }

    elf_word
    section::info () const
    {
      check ("info");
      return shdr.sh_info;
    }

    elf_xword
    section::size () const
    {
      check ("size");
      return shdr.sh_size;
    }

    elf_xword
    section::entry_size () const
    {
      check ("entry_size");
      return shdr.sh_entsize;
    }

    int
    section::entries () const
    {
      return size () / entry_size ();
    }

    bool
    section::get_reloc_type () const
    {
      return rela;
    }

    void
    section::set_name (unsigned int index)
    {
      check_writable ("set_name");
      shdr.sh_name = index;
      if (!gelf_update_shdr (scn, &shdr))
        libelf_error ("gelf_update_shdr: " + name_ + " (" + file_->name () + ')');
    }

    void
    section::set_reloc_type (bool rela_)
    {
      rela = rela_;
    }

    void
    section::add (const relocation& reloc)
    {
      relocs.push_back (reloc);
    }

    const relocations&
    section::get_relocations () const
    {
      return relocs;
    }

    void
    section::check (const char* where) const
    {
      if (!file_ || (index_ < 0) || !scn)
      {
        std::string w = where;
        throw rld::error ("Section not initialised.", "section:check:" + w);
      }
    }

    void
    section::check_writable (const char* where) const
    {
      check (where);
      if (!file_->is_writable ())
      {
        std::string w = where;
        throw rld::error ("File is read-only.", "section:check:");
      }
    }

    program_header::program_header ()
    {
      memset (&phdr, 0, sizeof (phdr));
    }

    program_header::~program_header ()
    {
    }

    void
    program_header::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)
    {
      phdr.p_type = type;
      phdr.p_flags = flags;
      phdr.p_offset = offset;
      phdr.p_vaddr = vaddr;
      phdr.p_paddr = paddr;
      phdr.p_filesz = filesz;
      phdr.p_memsz = memsz;
      phdr.p_align = align;
    }

    file::file ()
      : fd_ (-1),
        refs (0),
        archive (false),
        writable (false),
        elf_ (0),
        mtype (0),
        oclass (0),
        ident_str (0),
        ident_size (0),
        ehdr (0),
        phdr (0)
    {
    }

    file::~file ()
    {
      try
      {
        end ();
      }
      catch (rld::error re)
      {
        std::cerr << "error: rld::elf::file::~file: "
                  << re.where << ": " << re.what
                  << std::endl;
      }
      catch (...)
      {
        std::cerr << "error: rld::elf::file::~file: unhandled exception"
                  << std::endl;
      }
    }

    void
    file::reference_obtain ()
    {
      ++refs;
    }

    void
    file::reference_release ()
    {
      if (refs > 0)
        --refs;
    }

    void
    file::begin (const std::string& name__, int fd__, const bool writable_)
    {
      begin (name__, fd__, writable_, 0, 0);
    }

    void
    file::begin (const std::string& name__, file& archive_, off_t offset)
    {
      archive_.check ("begin:archive");

      if (archive_.writable)
        throw rld::error ("archive is writable", "elf:file:begin");

      begin (name__, archive_.fd_, false, &archive_, offset);
    }

    #define rld_archive_fhdr_size (60)

    void
    file::begin (const std::string& name__,
                 int                fd__,
                 const bool         writable_,
                 file*              archive_,
                 off_t              offset_)
    {
      if (fd__ < 0)
        throw rld::error ("No file descriptor", "elf:file:begin");

      /*
       * Begin's are not nesting.
       */
      if (elf_ || (fd_ >= 0))
        throw rld::error ("Already called", "elf:file:begin");

      /*
       * Cannot write directly into archive. Create a file then archive it.
       */
      if (archive_ && writable_)
        throw rld::error ("Cannot write into archives directly",
                          "elf:file:begin");

      libelf_initialise ();

      /*
       * Is this image part of an archive ?
       */
      if (archive_)
      {
        ssize_t offset = offset_ - rld_archive_fhdr_size;
        if (::elf_rand (archive_->elf_, offset) != offset)
          libelf_error ("rand: " + archive_->name_);
      }

      /*
       * Note, the elf passed is either the archive or NULL.
       */
      elf* elf__ = ::elf_begin (fd__,
                                writable_ ? ELF_C_WRITE : ELF_C_READ,
                                archive_ ? archive_->elf_ : 0);
      if (!elf__)
        libelf_error ("begin: " + name__);

      if (rld::verbose () >= RLD_VERBOSE_FULL_DEBUG)
        std::cout << "elf::begin: " << elf__ << ' ' << name__ << std::endl;

      elf_kind ek = ::elf_kind (elf__);

      /*
       * If this is inside an archive it must be an ELF file.
       */

      if (archive_ && (ek != ELF_K_ELF))
        throw rld::error ("File format in archive not ELF",
                          "elf:file:begin: " + name__);

      if (ek == ELF_K_AR)
        archive = true;
      else if (ek == ELF_K_ELF)
        archive = false;
      else
        throw rld::error ("File format not ELF or archive",
                          "elf:file:begin: " + name__);

      if (!writable_)
      {
        /*
         * If an ELF file make sure they all match. On the first file that
         * begins an ELF session record its settings.
         */
        if (ek == ELF_K_ELF)
        {
          oclass = ::gelf_getclass (elf__);
          ident_str = ::elf_getident (elf__, &ident_size);
        }
      }

      fd_ = fd__;
      name_ = name__;
      writable = writable_;
      elf_ = elf__;

      if (!archive && !writable)
      {
        load_header ();
        load_sections ();
      }
    }

    void
    file::end ()
    {
      if (refs > 0)
        throw rld::error ("References still held", "elf:file:end: " + name_);

      if (fd_ >= 0)
      {
        if (!writable)
        {
          if (ehdr)
          {
            delete ehdr;
            ehdr = 0;
          }
          if (phdr)
          {
            delete phdr;
            phdr = 0;
          }
        }

        fd_ = -1;
        name_.clear ();
        archive = false;
        oclass = 0;
        ident_str = 0;
        ident_size = 0;
        writable = false;

        secs.clear ();

        if (elf_)
        {
          if (rld::verbose () >= RLD_VERBOSE_FULL_DEBUG)
            std::cout << "libelf::end: " << elf_
                      << ' ' << name_ << std::endl;
          ::elf_end (elf_);
          elf_ = 0;
        }
      }
    }

    void
    file::write ()
    {
      check_writable ("write");

      std::string shstrtab;

      for (section_table::iterator sti = secs.begin ();
           sti != secs.end ();
           ++sti)
      {
        section& sec = (*sti).second;
        int added_at = shstrtab.size ();
        shstrtab += '\0' + sec.name ();
        sec.set_name (added_at + 1);
      }

      unsigned int shstrtab_name = shstrtab.size () + 1;

      /*
       * Done this way to clang happy on darwin.
       */
      shstrtab += '\0';
      shstrtab += ".shstrtab";

      /*
       * Create the string table section.
       */
      section shstrsec (*this,
                        secs.size () + 1,          /* index */
                        ".shstrtab",               /* name */
                        SHT_STRTAB,                /* type */
                        1,                         /* alignment */
                        SHF_STRINGS | SHF_ALLOC,   /* flags */
                        0,                         /* address */
                        0,                         /* offset */
                        shstrtab.size ());         /* size */

      shstrsec.add_data (ELF_T_BYTE,
                         1,
                         shstrtab.size (),
                         (void*) shstrtab.c_str ());

      shstrsec.set_name (shstrtab_name);

      ::elf_setshstrndx (elf_, shstrsec.index ());
      ::elf_flagehdr (elf_, ELF_C_SET, ELF_F_DIRTY);

      if (elf_update (elf_, ELF_C_NULL) < 0)
        libelf_error ("elf_update:layout: " + name_);

      ::elf_flagphdr (elf_, ELF_C_SET, ELF_F_DIRTY);

      if (::elf_update (elf_, ELF_C_WRITE) < 0)
        libelf_error ("elf_update:write: " + name_);
    }

    void
    file::load_header ()
    {
      check ("load_header");

      if (!ehdr)
      {
        if (!writable)
          ehdr = new elf_ehdr;
        else
        {
          throw rld::error ("No ELF header; set the header first",
                            "elf:file:load_header: " + name_);
        }
      }

      if (::gelf_getehdr (elf_, ehdr) == 0)
        error ("gelf_getehdr");
    }

    unsigned int
    file::machinetype () const
    {
      check_ehdr ("machinetype");
      return ehdr->e_machine;
    }

    unsigned int
    file::type () const
    {
      check_ehdr ("type");
      return ehdr->e_type;
    }

    unsigned int
    file::object_class () const
    {
      check ("object_class");
      return oclass;
    }

    unsigned int
    file::data_type () const
    {
      check ("data_type");
      if (!ident_str)
        throw rld::error ("No ELF ident str", "elf:file:data_type: " + name_);
      return ident_str[EI_DATA];
    }

    bool
    file::is_archive () const
    {
      check ("is_archive");
      return archive;
    }

    bool
    file::is_executable () const
    {
      check_ehdr ("is_executable");
      return ehdr->e_type != ET_REL;
    }

    bool
    file::is_relocatable() const
    {
      check_ehdr ("is_relocatable");
      return ehdr->e_type == ET_REL;
    }

    int
    file::section_count () const
    {
      check_ehdr ("section_count");
      return ehdr->e_shnum;
    }

    void
    file::load_sections ()
    {
      if (secs.empty ())
      {
        check ("load_sections_headers");
        for (int sn = 0; sn < section_count (); ++sn)
        {
          section sec (*this, sn);
          secs[sec.name ()] = sec;
        }
      }
    }

    void
    file::get_sections (sections& filtered_secs, unsigned int type)
    {
      load_sections ();
      for (section_table::iterator si = secs.begin ();
           si != secs.end ();
           ++si)
      {
        section& sec = (*si).second;
        if ((type == 0) || (sec.type () == type))
          filtered_secs.push_back (&sec);
      }
    }

    section&
    file::get_section (int index)
    {
      load_sections ();
      for (section_table::iterator si = secs.begin ();
           si != secs.end ();
           ++si)
      {
        section& sec = (*si).second;
        if (index == sec.index ())
          return sec;
      }

      throw rld::error ("section index '" + rld::to_string (index) + "'not found",
                        "elf:file:get_section: " + name_);
    }

    int
    file::strings_section () const
    {
      check_ehdr ("strings_sections");
      return ehdr->e_shstrndx;
    }

    void
    file::load_symbols ()
    {
      if (symbols.empty ())
      {
        if (rld::verbose () >= RLD_VERBOSE_FULL_DEBUG)
          std::cout << "elf:symbol: " << name () << std::endl;

        sections symbol_secs;

        get_sections (symbol_secs, SHT_SYMTAB);

        for (sections::iterator si = symbol_secs.begin ();
             si != symbol_secs.end ();
             ++si)
        {
          section& sec = *(*si);
          int      syms = sec.entries ();

          for (int s = 0; s < syms; ++s)
          {
            elf_sym esym;

            if (!::gelf_getsym (sec.data (), s, &esym))
             error ("gelf_getsym");

            std::string     name = get_string (sec.link (), esym.st_name);
            symbols::symbol sym (s, name, esym);

            if (rld::verbose () >= RLD_VERBOSE_FULL_DEBUG)
              std::cout << "elf:symbol: " << sym << std::endl;

            symbols.push_back (sym);
          }
        }
      }
    }

    void
    file::get_symbols (symbols::pointers& filtered_syms,
                       bool               unresolved,
                       bool               local,
                       bool               weak,
                       bool               global)
    {
      if (rld::verbose () >= RLD_VERBOSE_TRACE_SYMS)
        std::cout << "elf:get-syms: unresolved:" << unresolved
                  << " local:" << local
                  << " weak:" << weak
                  << " global:" << global
                  << " " << name_
                  << std::endl;

      load_symbols ();

      filtered_syms.clear ();

      for (symbols::bucket::iterator si = symbols.begin ();
           si != symbols.end ();
           ++si)
      {
        symbols::symbol& sym = *si;

        int stype = sym.type ();
        int sbind = sym.binding ();

        /*
         * If wanting unresolved symbols and the type is no-type and the
         * section is undefined, or, the type is no-type or object or function
         * and the bind is local and we want local symbols, or the bind is weak
         * and we want weak symbols, or the bind is global and we want global
         * symbols then add the filtered symbols container.
         */
        bool add = false;

        if ((stype == STT_NOTYPE) &&
            (sbind == STB_GLOBAL) &&
            (sym.section_index () == SHN_UNDEF))
        {
          if (unresolved)
            add = true;
        }
        else
        {
          if (((stype == STT_NOTYPE) ||
               (stype == STT_OBJECT) ||
               (stype == STT_FUNC)) &&
              ((weak && (sbind == STB_WEAK)) ||
               (!unresolved && ((local && (sbind == STB_LOCAL)) ||
                                (global && (sbind == STB_GLOBAL))))))
            add = true;
        }

        if (add)
          filtered_syms.push_back (&sym);
      }
    }

    const symbols::symbol&
    file::get_symbol (const int index) const
    {
      for (symbols::bucket::const_iterator si = symbols.begin ();
           si != symbols.end ();
           ++si)
      {
        const symbols::symbol& sym = *si;
        if (index == sym.index ())
          return sym;
      }

      throw rld::error ("symbol index '" + rld::to_string (index) + "' not found",
                        "elf:file:get_symbol: " + name_);
    }

    void
    file::load_relocations ()
    {
      if (rld::verbose () >= RLD_VERBOSE_FULL_DEBUG)
        std::cout << "elf:reloc: " << name () << std::endl;

      sections rel_secs;

      get_sections (rel_secs, SHT_REL);
      get_sections (rel_secs, SHT_RELA);

      for (sections::iterator si = rel_secs.begin ();
           si != rel_secs.end ();
           ++si)
      {
        section& sec = *(*si);
        section& targetsec = get_section (sec.info ());
        int      rels = sec.entries ();
        bool     rela = sec.type () == SHT_RELA;

        targetsec.set_reloc_type (rela);

        if (rld::verbose () >= RLD_VERBOSE_FULL_DEBUG)
          std::cout << "elf:reloc: " << sec.name ()
                    << " -> " << targetsec.name ()
                    << std::endl;

        for (int r = 0; r < rels; ++r)
        {
          if (rela)
          {
            elf_rela erela;

            if (!::gelf_getrela (sec.data (), r, &erela))
              error ("gelf_getrela");

            if (rld::verbose () >= RLD_VERBOSE_FULL_DEBUG)
              std::cout << "elf:reloc: rela: offset: " << erela.r_offset
                        << " sym:" << GELF_R_SYM (erela.r_info)
                        << " type:" << GELF_R_TYPE (erela.r_info)
                        << " addend:" << erela.r_addend
                        << std::endl;

            /*
             * The target section is updated with the fix up, and symbol
             * section indicates the section offset being referenced by the
             * fixup.
             */

            const symbols::symbol& sym = get_symbol (GELF_R_SYM (erela.r_info));

            relocation reloc (sym,
                              erela.r_offset,
                              erela.r_info,
                              erela.r_addend);

            targetsec.add (reloc);
          }
          else
          {
            elf_rel erel;

            if (!::gelf_getrel (sec.data (), r, &erel))
              error ("gelf_getrel");

            if (rld::verbose () >= RLD_VERBOSE_FULL_DEBUG)
              std::cout << "elf:reloc: rel: offset: " << erel.r_offset
                        << " sym:" << GELF_R_SYM (erel.r_info)
                        << " type:" << GELF_R_TYPE (erel.r_info)
                        << std::endl;

            const symbols::symbol& sym = get_symbol (GELF_R_SYM (erel.r_info));

            relocation reloc (sym, erel.r_offset, erel.r_info);

            targetsec.add (reloc);
          }
        }
      }
    }

    std::string
    file::get_string (int section, size_t offset)
    {
      check ("get_string");
      char* s = ::elf_strptr (elf_, section, offset);
      if (!s)
        error ("elf_strptr");
      return s;
    }

    std::string
    file::get_string (size_t offset)
    {
      check ("get_string");
      char* s = ::elf_strptr (elf_, strings_section (), offset);
      if (!s)
        error ("elf_strptr");
      return s;
    }

    void
    file::set_header (elf_half      type,
                      int           class_,
                      elf_half      machinetype,
                      unsigned char datatype)
    {
      check_writable ("set_header");

      if (ehdr)
          throw rld::error ("ELF header already set",
                            "elf:file:set_header: " + name_);

      ehdr = (elf_ehdr*) ::gelf_newehdr (elf_, class_);
      if (ehdr == 0)
        error ("gelf_newehdr");

      if (class_ == ELFCLASS32)
      {
        if((ehdr = (elf_ehdr*) ::elf32_getehdr (elf_)) == 0)
          error ("elf32_getehdr");
      }
      else if (::gelf_getehdr (elf_, ehdr) == 0)
        error ("gelf_getehdr");

      if (class_ == ELFCLASS32)
      {
        ((elf32_ehdr*)ehdr)->e_type = type;
        ((elf32_ehdr*)ehdr)->e_machine = machinetype;
        ((elf32_ehdr*)ehdr)->e_flags = 0;
        ((elf32_ehdr*)ehdr)->e_ident[EI_DATA] = datatype;
        ((elf32_ehdr*)ehdr)->e_version = EV_CURRENT;
      }
      else
      {
        ehdr->e_type = type;
        ehdr->e_machine = machinetype;
        ehdr->e_flags = 0;
        ehdr->e_ident[EI_DATA] = datatype;
        ehdr->e_version = EV_CURRENT;
      }

      ::elf_flagphdr (elf_, ELF_C_SET , ELF_F_DIRTY);
    }

    void
    file::add (section& sec)
    {
      check_writable ("add");
      secs[sec.name ()] = sec;
    }

    void
    file::add (program_header& phdr)
    {
      check_writable ("add");
      phdrs.push_back (phdr);
    }

    elf*
    file::get_elf ()
    {
      return elf_;
    }

    const std::string&
    file::name () const
    {
      return name_;
    }

    bool
    file::is_writable () const
    {
      return writable;
    }

    size_t
    file::machine_size () const
    {
      size_t bytes;
      switch (object_class ())
      {
      case ELFCLASS64:
        bytes = sizeof (uint64_t);
        break;
      default:
        bytes = sizeof (uint32_t);
        break;
      }
      return bytes;
    }

    bool
    file::is_little_endian () const
    {
      return data_type () == ELFDATA2LSB;
    }

    void
    file::check (const char* where) const
    {
      if (!elf_ || (fd_ < 0))
      {
        std::string w = where;
        throw rld::error ("No ELF file or file descriptor", "elf:file:" + w);
      }
    }

    void
    file::check_ehdr (const char* where) const
    {
      check (where);
      if (!ehdr)
      {
        std::string w = where;
        throw rld::error ("no elf header", "elf:file:" + w);
      }
    }

    void
    file::check_phdr (const char* where) const
    {
      check (where);
      if (!phdr)
      {
        std::string w = where;
        throw rld::error ("no elf program header", "elf:file:" + w);
      }
    }

    void
    file::check_writable (const char* where) const
    {
      check (where);
      if (!writable)
      {
        std::string w = where;
        throw rld::error ("not writable", "elf:file:" + w);
      }
    }

    void
    file::error (const char* where) const
    {
      std::string w = where;
      libelf_error (w + ": " + name_);
    }

    const std::string
    machine_type (unsigned int machinetype)
    {
      struct types_and_labels
      {
        const char*  name;        //< The RTEMS label.
        unsigned int machinetype; //< The machine type.
      };
      types_and_labels types_to_labels[] =
      {
        { "arm",     EM_ARM },
        { "avr",     EM_AVR },
        { "bfin",    EM_BLACKFIN },
        { "h8300",   EM_H8_300 },
        { "i386",    EM_386 },
     /* { "m32c",    EM_M32C }, Not in libelf I imported */
        { "m32r",    EM_M32R },
        { "m68k",    EM_68K },
        { "m68k",    EM_COLDFIRE },
        { "mips",    EM_MIPS },
        { "powerpc", EM_PPC },
#ifndef EM_RISCV
        { "riscv",   243 }, /* If not in libelf yet */
#else
        { "riscv",   EM_RISCV },
#endif
        { "sh",      EM_SH },
        { "sparc",   EM_SPARC },
        { "sparc64", EM_SPARC },
        { 0,         EM_NONE }
      };

      int m = 0;
      while (types_to_labels[m].machinetype != EM_NONE)
      {
        if (machinetype == types_to_labels[m].machinetype)
          return types_to_labels[m].name;
        ++m;
      }

      std::ostringstream what;
      what << "unknown machine type: " << elf_object_machinetype;
      throw rld::error (what, "machine-type");
    }

    const std::string
    machine_type ()
    {
      return machine_type (elf_object_machinetype);
    }

    unsigned int
    object_class ()
    {
      return elf_object_class;
    }

    unsigned int
    object_machine_type ()
    {
      return elf_object_machinetype;
    }

    unsigned int
    object_datatype ()
    {
      return elf_object_datatype;
    }

    void
    check_file(const file& file)
    {
      if (elf_object_machinetype == EM_NONE)
        elf_object_machinetype = file.machinetype ();
      else if (file.machinetype () != elf_object_machinetype)
      {
        std::ostringstream oss;
        oss << "elf:check_file:" << file.name ()
            << ": " << elf_object_machinetype << '/' << file.machinetype ();
        throw rld::error ("Mixed machine types not supported.", oss.str ());
      }

      if (elf_object_class == ELFCLASSNONE)
        elf_object_class = file.object_class ();
      else if (file.object_class () != elf_object_class)
        throw rld::error ("Mixed classes not allowed (32bit/64bit).",
                          "elf:check_file: " + file.name ());

      if (elf_object_datatype == ELFDATANONE)
        elf_object_datatype = file.data_type ();
      else if (elf_object_datatype != file.data_type ())
        throw rld::error ("Mixed data types not allowed (LSB/MSB).",
                          "elf:check_file: " + file.name ());
    }

  }
}
