/*
 * 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.
 */

#if HAVE_CONFIG_H
#include "config.h"
#endif

#include <algorithm>

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>

#include <rld.h>

#if __WIN32__
#define CREATE_MODE (S_IRUSR | S_IWUSR)
#define OPEN_FLAGS  (O_BINARY)
#else
#define CREATE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH)
#define OPEN_FLAGS  (0)
#endif

namespace rld
{
  namespace files
  {
    /**
     * Scan the decimal number returning the value found.
     */
    uint64_t
    scan_decimal (const uint8_t* string, size_t len)
    {
      uint64_t value = 0;

      while (len && (*string != ' '))
      {
        value *= 10;
        value += *string - '0';
        ++string;
        --len;
      }

      return value;
    }

    void
    set_number (uint32_t value, uint8_t* string, size_t len, bool octal = false)
    {
      std::ostringstream oss;
      if (octal)
        oss << std::oct;
      oss << value;
      size_t l = oss.str ().length ();
      if (l > len)
        l = len;
      memcpy (string, oss.str ().c_str (), l);
    }

    file::file (const std::string& aname,
                const std::string& oname,
                off_t              offset,
                size_t             size)
      : aname_ (aname),
        oname_ (oname),
        offset_ (offset),
        size_ (size)
    {
    }

    file::file (const std::string& path, bool is_object)
      : offset_ (0),
        size_ (0)
    {
      set (path, is_object);
    }

    file::file ()
      : offset_ (0),
        size_ (0)
    {
    }

    void
    file::set (const std::string& path, bool is_object)
    {
      /*
       * If there is a path look for a colon. If there is no colon we assume
       * it is an object file. If the colon is the last character in the path
       * it is just an archive.
       */
      if (!path.empty ())
      {
        bool get_size = false;
        if (is_object)
        {
          size_t colon = path.find_last_of (':');
          if ((colon != std::string::npos) && (colon > RLD_DRIVE_SEPARATOR))
          {
            aname_ = path.substr (0, colon - 1);
            oname_ = path.substr (colon + 1);
            // @todo Add offset scanning.
          }
          else
          {
            oname_ = path;
            get_size = true;
          }
        }
        else
        {
          aname_ = path;
            get_size = true;
        }

        if (get_size)
        {
          struct stat sb;
          if (::stat (path.c_str (), &sb) == 0)
            size_ = sb.st_size;
        }
      }
    }

    bool
    file::is_archive () const
    {
      return !aname_.empty () && oname_.empty ();
    }

    bool
    file::is_object () const
    {
      return !oname_.empty ();
    }

    bool
    file::is_valid () const
    {
      return !aname_.empty () || !oname_.empty ();
    }

    bool
    file::exists () const
    {
      /*
       * No name set returns false.
       */
      bool result = false;
      const std::string p = path ();
      if (!p.empty ())
        result = path::check_file (p);
      return result;
    }

    const std::string
    file::path () const
    {
      if (!aname_.empty ())
        return aname_;
      return oname_;
    }

    const std::string
    file::full () const
    {
      std::string f;
      if (!aname_.empty ())
      {
        f = aname_;
        if (!oname_.empty ())
          f += ':';
      }
      if (!oname_.empty ())
        f += oname_;
      if (!aname_.empty () && !oname_.empty ())
        f += '@' + rld::to_string (offset_);
      return f;
    }

    const std::string
    file::basename () const
    {
      return rld::path::basename (full ());
    }

    const std::string&
    file::aname () const
    {
      return aname_;
    }

    const std::string&
    file::oname () const
    {
      return oname_;
    }

    off_t
    file::offset () const
    {
       return offset_;
    }

    size_t
    file::size () const
    {
       return size_;
    }

    image::image (file& name)
      : name_ (name),
        references_ (0),
        fd_ (-1),
        symbol_refs (0),
        writable (false),
        remove (false)
    {
    }

    image::image (const std::string& path, bool is_object)
      : name_ (path, is_object),
        references_ (0),
        fd_ (-1),
        symbol_refs (0),
        writable (false),
        remove (false)
    {
    }

    image::image ()
      : references_ (0),
        fd_ (-1),
        symbol_refs (0),
        writable (false),
        remove (false)
    {
    }

    image::~image ()
    {
      if (references_)
	std::cerr << "rtl:file:image: references when destructing" << std::endl;

      if (fd_ >= 0)
      {
        ::close (fd_);
        fd_= -1;
        if (writable && remove)
        {
          if (rld::verbose () >= RLD_VERBOSE_INFO)
            std::cout << "image::close: removing " << name ().full ()
                        << std::endl;
          ::unlink (name_.path ().c_str ());
        }
      }

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

    void
    image::open (file& name)
    {
      name_ = name;
      open ();
    }

    void
    image::open (bool writable_)
    {
      const std::string path = name_.path ();

      if (path.empty ())
        throw rld::error ("No file name", "open:" + path);

      if (rld::verbose () >= RLD_VERBOSE_TRACE_FILE)
        std::cout << "image::open:  " << name (). full ()
                  << " refs:" << references_ + 1
                  << " writable:" << (char*) (writable_ ? "yes" : "no")
                  << std::endl;

      if (fd_ < 0)
      {
        writable = writable_;

        if (writable)
          fd_ = ::open (path.c_str (), OPEN_FLAGS | O_RDWR | O_CREAT | O_TRUNC, CREATE_MODE);
        else
          fd_ = ::open (path.c_str (), OPEN_FLAGS | O_RDONLY);
        if (fd_ < 0)
          throw rld::error (::strerror (errno), "open:" + path);
      }
      else
      {
        if (writable_ != writable)
          throw rld::error ("Cannot change write status", "open:" + path);
      }

      ++references_;
    }

    void
    image::close ()
    {
      if (references_ > 0)
      {
        if (rld::verbose () >= RLD_VERBOSE_TRACE_FILE)
          std::cout << "image::close: " << name ().full ()
                    << " refs:" << references_ << std::endl;

        --references_;
        if (references_ == 0)
        {
          ::close (fd_);
          fd_ = -1;
          if (writable && remove)
          {
            if (rld::verbose () >= RLD_VERBOSE_INFO)
              std::cout << "image::close: removing " << name ().full ()
                        << std::endl;
            ::unlink (name_.path ().c_str ());
            remove = false;
          }
        }
      }
    }

    ssize_t
    image::read (void* buffer_, size_t size)
    {
      uint8_t* buffer = static_cast <uint8_t*> (buffer_);
      size_t   have_read = 0;
      size_t   to_read = size;
      while (have_read < size)
      {
        const ssize_t rsize = ::read (fd (), buffer, to_read);
        if (rsize < 0)
          throw rld::error (strerror (errno), "read:" + name ().path ());
        if (rsize == 0)
          break;
        have_read += rsize;
        to_read -= rsize;
        buffer += rsize;
      }
      return have_read;
    }

    ssize_t
    image::write (const void* buffer_, size_t size)
    {
      const uint8_t* buffer = static_cast <const uint8_t*> (buffer_);
      size_t         have_written = 0;
      size_t         to_write = size;
      while (have_written < size)
      {
        const ssize_t wsize = ::write (fd (), buffer, to_write);
        if (wsize < 0)
          throw rld::error (strerror (errno), "write:" + name ().path ());
        have_written += wsize;
        to_write -= wsize;
        buffer += wsize;
      }
      return have_written;
    }

    void
    image::seek (off_t offset)
    {
      if (::lseek (fd (), name_.offset () + offset, SEEK_SET) < 0)
        throw rld::error (strerror (errno), "lseek:" + name ().path ());
    }

    bool
    image::seek_read (off_t offset, uint8_t* buffer, size_t size)
    {
      seek (offset);
      return size == (size_t) read (buffer, size);
    }

    bool
    image::seek_write (off_t offset, const void* buffer, size_t size)
    {
      seek (offset);
      return size == (size_t) write (buffer, size);
    }

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

    int
    image::references () const
    {
      return references_;
    }

    size_t
    image::size () const
    {
      return name ().size ();
    }

    int
    image::fd () const
    {
      return fd_;
    }

    rld::elf::file&
    image::elf ()
    {
      return elf_;
    }

    byteorder
    image::get_byteorder () const
    {
      switch (elf_.data_type ())
      {
        case ELFDATA2LSB:
          return little_endian;
        case ELFDATA2MSB:
          return big_endian;
      }
      throw rld::error ("invalid elf data type", "byteorder: " + name ().path ());
    }

    void
    image::symbol_referenced ()
    {
      ++symbol_refs;
    }

    int
    image::symbol_references () const
    {
      return symbol_refs;
    }

    void
    copy_file (image& in, image& out, size_t size)
    {
      #define COPY_FILE_BUFFER_SIZE (8 * 1024)
      uint8_t* buffer = 0;

      if (size == 0)
        size = in.name ().size ();

      try
      {
        buffer = new uint8_t[COPY_FILE_BUFFER_SIZE];
        while (size)
        {
          /*
           * @fixme the reading and writing are not POSIX; sigints could split them.
           */

          size_t l = size < COPY_FILE_BUFFER_SIZE ? size : COPY_FILE_BUFFER_SIZE;
          ssize_t r = ::read (in.fd (), buffer, l);

          if (r < 0)
            throw rld::error (::strerror (errno), "reading: " + in.name ().full ());

          if (r == 0)
          {
            std::ostringstream oss;
            oss << "reading: " + in.name ().full () << " (" << size << ')';
            throw rld::error ("input too short", oss.str ());
          }

          ssize_t w = ::write (out.fd (), buffer, r);

          if (w < 0)
            throw rld::error (::strerror (errno), "writing: " + out.name ().full ());

          if (w != r)
            throw rld::error ("output trucated", "writing: " + out.name ().full ());

          size -= r;
        }
      }
      catch (...)
      {
        delete [] buffer;
        throw;
      }

      if (buffer)
        delete [] buffer;
    }

    /**
     * Defines for the header of an archive.
     */
    #define rld_archive_ident         "!<arch>\n"
    #define rld_archive_ident_size    (sizeof (rld_archive_ident) - 1)
    #define rld_archive_fhdr_base     rld_archive_ident_size
    #define rld_archive_fname         (0)
    #define rld_archive_fname_size    (16)
    #define rld_archive_mtime         (16)
    #define rld_archive_mtime_size    (12)
    #define rld_archive_uid           (28)
    #define rld_archive_uid_size      (6)
    #define rld_archive_gid           (34)
    #define rld_archive_gid_size      (6)
    #define rld_archive_mode          (40)
    #define rld_archive_mode_size     (8)
    #define rld_archive_size          (48)
    #define rld_archive_size_size     (10)
    #define rld_archive_magic         (58)
    #define rld_archive_magic_size    (2)
    #define rld_archive_fhdr_size     (60)
    #define rld_archive_max_file_size (1024)

    archive::archive (const std::string& path)
      : image (path, false)
    {
      if (!name ().is_valid ())
        throw rld_error_at ("name is empty");
      if (!name ().is_archive ())
        throw rld_error_at ("name is not an archive: " + name ().oname ());
    }

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

    void
    archive::begin ()
    {
      if (references () == 1)
      {
        elf ().begin (name ().full (), fd ());

        /*
         * Make sure it is an archive.
         */
        if (!elf ().is_archive ())
          throw rld::error ("Not an archive.",
                            "archive-begin:" + name ().full ());
      }
    }

    void
    archive::end ()
    {
      if (references () == 1)
        elf ().end ();
    }

    bool
    archive::is (const std::string& path) const
    {
      return name ().path () == path;
    }

    bool
    archive::is_valid ()
    {
      open ();
      uint8_t header[rld_archive_ident_size];
      seek_read (0, &header[0], rld_archive_ident_size);
      bool result = ::memcmp (header, rld_archive_ident,
                              rld_archive_ident_size) == 0 ? true : false;
      close ();
      return result;
    }

    void
    archive::load_objects (objects& objs)
    {
      off_t extended_file_names = 0;
      off_t offset = rld_archive_fhdr_base;
      size_t size = 0;

      while (true)
      {
        uint8_t header[rld_archive_fhdr_size];

        if (!read_header (offset, &header[0]))
          break;

        /*
         * The archive file headers are always aligned to an even address.
         */
        size =
          (scan_decimal (&header[rld_archive_size],
                         rld_archive_size_size) + 1) & ~1;

        /*
         * Check for the GNU extensions.
         */
        if (header[0] == '/')
        {
          off_t extended_off;

          switch (header[1])
          {
            case ' ':
              /*
               * Symbols table. Ignore the table.
               */
              break;
            case '/':
              /*
               * Extended file names table. Remember.
               */
              extended_file_names = offset + rld_archive_fhdr_size;
              break;
            case '0':
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
            case '8':
            case '9':
              /*
               * Offset into the extended file name table. If we do not have the
               * offset to the extended file name table find it.
               */
              extended_off = scan_decimal (&header[1], rld_archive_fname_size);

              if (extended_file_names == 0)
              {
                off_t off = offset;
                while (extended_file_names == 0)
                {
                  size_t esize =
                    (scan_decimal (&header[rld_archive_size],
                                   rld_archive_size_size) + 1) & ~1;
                  off += esize + rld_archive_fhdr_size;

                  if (!read_header (off, &header[0]))
                    throw rld::error ("No GNU extended file name section found",
                                      "get-names:" + name ().path ());

                  if ((header[0] == '/') && (header[1] == '/'))
                  {
                    extended_file_names = off + rld_archive_fhdr_size;
                    break;
                  }
                }
              }

              if (extended_file_names)
              {
                /*
                 * We know the offset in the archive to the extended file. Read
                 * the name from the table and compare with the name we are
                 * after.
                 */
                char cname[rld_archive_max_file_size];
                seek_read (extended_file_names + extended_off,
                           (uint8_t*) &cname[0], rld_archive_max_file_size);
                add_object (objs, cname,
                            offset + rld_archive_fhdr_size, size);
              }
              break;
            default:
              /*
               * Ignore the file because we do not know what it it.
               */
              break;
          }
        }
        else
        {
          /*
           * Normal archive name.
           */
          add_object (objs,
                      (char*) &header[rld_archive_fname],
                      offset + rld_archive_fhdr_size, size);
        }

        offset += size + rld_archive_fhdr_size;
      }
    }

    bool
    archive::operator< (const archive& rhs) const
    {
      return name ().path () < rhs.name ().path ();
    }

    bool
    archive::read_header (off_t offset, uint8_t* header)
    {
      if (!seek_read (offset, header, rld_archive_fhdr_size))
        return false;

      if ((header[rld_archive_magic] != 0x60) ||
          (header[rld_archive_magic + 1] != 0x0a))
        throw rld::error ("Invalid header magic numbers at " +
                          rld::to_string (offset), "read-header:" + name ().path ());

      return true;
    }

    void
    archive::add_object (objects& objs, const char* path, off_t offset, size_t size)
    {
      const char* end = path;
      while ((*end != '\0') && (*end != '/') && (*end != '\n'))
        ++end;

      std::string str;
      str.append (path, end - path);

      if (rld::verbose () >= RLD_VERBOSE_FULL_DEBUG)
        std::cout << "archive::add-object: " << str << std::endl;

      file n (name ().path (), str, offset, size);
      objs[n.full()] = new object (*this, n);
    }

    void
    archive::write_header (const std::string& name,
                           uint32_t           mtime,
                           int                uid,
                           int                gid,
                           int                mode,
                           size_t             size)
    {
        uint8_t header[rld_archive_fhdr_size];

        memset (header, ' ', sizeof (header));

        size_t len = name.length ();
        if (len > rld_archive_fname_size)
          len = rld_archive_fname_size;
        memcpy (&header[rld_archive_fname], &name[0], len);

        set_number (mtime, header + rld_archive_mtime, rld_archive_mtime_size);
        set_number (uid, header + rld_archive_uid, rld_archive_uid_size);
        set_number (gid, header + rld_archive_gid, rld_archive_gid_size);
        set_number (mode, header + rld_archive_mode, rld_archive_mode_size, true);
        set_number (size, header + rld_archive_size, rld_archive_size_size);

        header[rld_archive_magic] = 0x60;
        header[rld_archive_magic + 1] = 0x0a;

        write (header, sizeof (header));
    }

    void
    archive::create (object_list& objects)
    {
      if (rld::verbose () >= RLD_VERBOSE_DETAILS)
        std::cout << "archive::create: " << name ().full ()
                  << ", objects: " << objects.size () << std::endl;

      open (true);

      try
      {
        seek_write (0, rld_archive_ident, rld_archive_ident_size);

        /*
         * GNU extended filenames.
         */
        std::string extended_file_names;

        for (object_list::iterator oi = objects.begin ();
             oi != objects.end ();
             ++oi)
        {
          object& obj = *(*oi);
          const std::string&  oname = path::basename (obj.name ().oname ());
          if (oname.length () >= rld_archive_fname_size)
            extended_file_names += oname + '\n';
        }

        if (!extended_file_names.empty ())
        {
          if (extended_file_names.length () & 1)
          {
            extended_file_names += ' ';
          }
          write_header ("//", 0, 0, 0, 0, extended_file_names.length ());
          write (extended_file_names.c_str (), extended_file_names.length ());
        }

        for (object_list::iterator oi = objects.begin ();
             oi != objects.end ();
             ++oi)
        {
          object& obj = *(*oi);

          obj.open ();

          try
          {
            std::string oname = path::basename (obj.name ().oname ());

            /*
             * Convert the file name to an offset into the extended file name
             * table if the file name is too long for the header.
             */

            if (oname.length () >= rld_archive_fname_size)
            {
              size_t pos = extended_file_names.find (oname + '\n');
              if (pos == std::string::npos)
                throw rld_error_at ("extended file name not found");
              std::ostringstream oss;
              oss << '/' << pos;
              oname = oss.str ();
            }
            else oname += '/';

            write_header (oname, 0, 0, 0, 0666, (obj.name ().size () + 1) & ~1);
            obj.seek (0);
            copy_file (obj, *this);
            if (obj.name ().size () & 1)
              write ("\n", 1);
          }
          catch (...)
          {
            obj.close ();
            throw;
          }

          obj.close ();
        }
      }
      catch (...)
      {
        close ();
        throw;
      }

      close ();
    }

    relocation::relocation (const elf::relocation& er)
      : offset (er.offset ()),
        type (er.type ()),
        info (er.info ()),
        addend (er.addend ()),
        symname (er.symbol ().name ()),
        symtype (er.symbol ().type ()),
        symsect (er.symbol ().section_index ()),
        symvalue (er.symbol ().value ()),
        symbinding (er.symbol ().binding ())
    {
    }

    section::section (const elf::section& es)
      : name (es.name ()),
        index (es.index ()),
        type (es.type ()),
        size (es.size ()),
        alignment (es.alignment ()),
        link (es.link ()),
        info (es.info ()),
        flags (es.flags ()),
        offset (es.offset ()),
        address (es.address ()),
        rela (es.get_reloc_type ())
    {
    }

    void
    section::load_relocations (const elf::section& es)
    {
      const elf::relocations& es_relocs = es.get_relocations ();
      for (elf::relocations::const_iterator ri = es_relocs.begin ();
           ri != es_relocs.end ();
           ++ri)
      {
        relocs.push_back (relocation (*ri));
      }
      rela = es.get_reloc_type ();
    }

    size_t
    sum_sizes (const sections& secs)
    {
      size_t size = 0;

      for (sections::const_iterator si = secs.begin ();
           si != secs.end ();
           ++si)
      {
        const section& sec = *si;

        if ((size % sec.alignment) != 0)
          size -= (size % sec.alignment) + sec.alignment;
        size += sec.size;
      }

      return size;
    }

    const section*
    find (const sections& secs, const int index)
    {
      for (sections::const_iterator si = secs.begin ();
           si != secs.end ();
           ++si)
      {
        const section& sec = *si;

        if (index == sec.index)
          return &sec;
      }

      return 0;
    }

    object::object (archive& archive_, file& name_)
      : image (name_),
        archive_ (&archive_),
        valid_ (false),
        resolving_ (false),
        resolved_ (false)
    {
      if (!name ().is_valid ())
        throw rld_error_at ("name is empty");
    }

    object::object (const std::string& path)
      : image (path),
        archive_ (0),
        valid_ (false),
        resolving_ (false),
        resolved_ (false)
    {
      if (!name ().is_valid ())
        throw rld_error_at ("name is empty");
    }

    object::object ()
      : archive_ (0),
        valid_ (false),
        resolving_ (false),
        resolved_ (false)
    {
    }

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

    void
    object::open (bool writable)
    {
      if (archive_)
      {
        if (writable)
          throw rld_error_at ("object files in archives are not writable");
        archive_->open ();
      }
      else
        image::open (writable);
    }

    void
    object::close ()
    {
      if (archive_)
      {
        archive_->end ();
        archive_->close ();
      }
      else
      {
        end ();
        image::close ();
      }
    }

    void
    object::begin ()
    {
      /*
       * Begin a session.
       */

      if (rld::verbose () >= RLD_VERBOSE_TRACE_FILE)
        std::cout << "object:begin: " << name ().full () << " in-archive:"
                  << ((char*) (archive_ ? "yes" : "no")) << std::endl;

      if (archive_)
        elf ().begin (name ().full (), archive_->elf(), name ().offset ());
      else
        elf ().begin (name ().full (), fd (), is_writable ());

      /*
       * Cannot be an archive.
       */
      if (elf ().is_archive ())
        throw rld::error ("Is an archive not an object file.",
                          "object-begin:" + name ().full ());

      /*
       * We only support executable or relocatable ELF files.
       */
      if (!is_writable ())
      {
        if (!elf ().is_executable () && !elf ().is_relocatable ())
          throw rld::error ("Invalid ELF type (only ET_EXEC/ET_REL supported).",
                            "object-begin:" + name ().full ());

        elf::check_file (elf ());

        /**
         * We assume the ELF file is invariant over the linking process.
         */

        if (secs.empty ())
        {
          elf::sections elf_secs;

          elf ().get_sections (elf_secs, 0);

          for (elf::sections::const_iterator esi = elf_secs.begin ();
               esi != elf_secs.end ();
               ++esi)
          {
            secs.push_back (section (*(*esi)));
          }
        }
      }

      /*
       * This is a valid object file. The file format checks out.
       */
      valid_ = true;
    }

    void
    object::end ()
    {
      if (rld::verbose () >= RLD_VERBOSE_TRACE_FILE)
        std::cout << "object:end: " << name ().full () << std::endl;

      elf ().end ();
    }

    bool
    object::valid () const
    {
      return valid_;
    }

    void
    object::load_symbols (rld::symbols::table& symbols, bool local)
    {
      if (rld::verbose () >= RLD_VERBOSE_TRACE_SYMS)
        std::cout << "object:load-sym: " << name ().full () << std::endl;

      rld::symbols::pointers syms;

      if (local)
      {
        elf ().get_symbols (syms, false, true, false, false);

        if (rld::verbose () >= RLD_VERBOSE_TRACE_SYMS)
          std::cout << "object:load-sym: local: total "
                    << syms.size () << std::endl;

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

          if (rld::verbose () >= RLD_VERBOSE_TRACE_SYMS)
            std::cout << "object:load-sym: local: " << sym << std::endl;

          sym.set_object (*this);
          symbols.add_local (sym);
        }
      }

      elf ().get_symbols (syms, false, false, true, false);

      if (rld::verbose () >= RLD_VERBOSE_TRACE_SYMS)
        std::cout << "object:load-sym: weak: total "
                  << syms.size () << std::endl;

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

        if (rld::verbose () >= RLD_VERBOSE_TRACE_SYMS)
          std::cout << "object:load-sym: weak: " << sym << std::endl;

        sym.set_object (*this);
        symbols.add_weak (sym);
        externals.push_back (&sym);
      }

      elf ().get_symbols (syms, false, false, false, true);

      if (rld::verbose () >= RLD_VERBOSE_TRACE_SYMS)
        std::cout << "object:load-sym: global: total "
                  << syms.size () << std::endl;

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

        if (rld::verbose () >= RLD_VERBOSE_TRACE_SYMS)
          std::cout << "object:load-sym: global: " << sym << std::endl;

        sym.set_object (*this);
        symbols.add_global (sym);
        externals.push_back (&sym);
      }

      elf ().get_symbols (syms, true, false, true, true);

      if (rld::verbose () >= RLD_VERBOSE_TRACE_SYMS)
        std::cout << "object:load-sym: unresolved: total "
                  << syms.size () << std::endl;

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

        if (rld::verbose () >= RLD_VERBOSE_TRACE_SYMS)
        {
          std::cout << "object:load-sym: unresolved: ";
          sym.output (std::cout);
          std::cout << std::endl;
        }

        unresolved[sym.name ()] = &sym;
      }
    }

    void
    object::load_relocations ()
    {
      if (rld::verbose () >= RLD_VERBOSE_TRACE)
        std::cout << "object:load-relocs: " << name ().full () << std::endl;

      elf ().load_relocations ();

      for (sections::iterator si = secs.begin ();
           si != secs.end ();
           ++si)
      {
        section&            sec = *si;
        const elf::section& elf_sec = elf ().get_section (sec.index);
        sec.load_relocations (elf_sec);
      }
    }

    int
    object::references () const
    {
      if (archive_)
        return archive_->references ();
      return image::references ();
    }

    size_t
    object::size () const
    {
      if (archive_)
        return archive_->size ();
      return image::size ();
    }

    int
    object::fd () const
    {
      if (archive_)
        return archive_->fd ();
      return image::fd ();
    }

    void
    object::symbol_referenced ()
    {
      image::symbol_referenced ();
      if (archive_)
        archive_->symbol_referenced ();
    }

    archive*
    object::get_archive ()
    {
      return archive_;
    }

    rld::symbols::symtab&
    object::unresolved_symbols ()
    {
      return unresolved;
    }

    rld::symbols::pointers&
    object::external_symbols ()
    {
      return externals;
    }

    void
    object::get_sections (sections& filtered_secs,
                          uint32_t  type,
                          uint64_t  flags_in,
                          uint64_t  flags_out)
    {
      for (sections::const_iterator si = secs.begin ();
           si != secs.end ();
           ++si)
      {
        const section& sec = *si;
        if ((type == 0) || (type == sec.type))
        {
          if ((flags_in == 0) ||
              (((sec.flags & flags_in) == flags_in) &&
               ((sec.flags & flags_out) == 0)))
          {
            filtered_secs.push_back (sec);
          }
        }
      }
    }

    void
    object::get_sections (sections& filtered_secs, const std::string& matching_name)
    {
      for (sections::const_iterator si = secs.begin ();
           si != secs.end ();
           ++si)
      {
        const section& sec = *si;
        if (sec.name == matching_name)
        {
          filtered_secs.push_back (sec);
        }
      }
    }

    const section&
    object::get_section (int index) const
    {
      for (sections::const_iterator si = secs.begin ();
           si != secs.end ();
           ++si)
      {
        const section& sec = *si;
        if (sec.index == index)
          return sec;
      }

      throw rld::error ("Section index '" + rld::to_string (index) +
                        "' not found: " + name ().full (), "object::get-section");
    }

    void
    object::resolve_set ()
    {
      resolving_ = true;
    }

    void
    object::resolve_clear ()
    {
      resolving_ = false;
    }

    bool
    object::resolving () const
    {
      return resolving_;
    }

    void
    object::resolved_set ()
    {
      resolved_ = true;
    }

    bool
    object::resolved () const
    {
      return resolved_;
    }

    cache::cache ()
      : opened (false)
    {
    }

    cache::~cache ()
    {
      try
      {
        close ();
      }
      catch (rld::error re)
      {
        std::cerr << "error: rld::files:cache::~cache: "
                  << re.where << ": " << re.what
                  << std::endl;
      }
      catch (...)
      {
        std::cerr << "error: rld::files::cache::~cache: unhandled exception"
                  << std::endl;
      }
    }

    void
    cache::open ()
    {
      if (!opened)
      {
        collect_object_files ();
        archives_begin ();
        opened = true;
      }
    }

    void
    cache::close ()
    {
      if (opened)
      {
        /*
         * Must delete the object first as they could depend on archives.
         */
        for (objects::iterator oi = objects_.begin (); oi != objects_.end (); ++oi)
          delete (*oi).second;
        for (archives::iterator ai = archives_.begin (); ai != archives_.end (); ++ai)
          delete (*ai).second;
        opened = false;
      }
    }

    void
    cache::add (const std::string& path)
    {
        paths_.push_back (path);
        input (path);
    }

    void
    cache::add (path::paths& paths__)
    {
      for (path::paths::iterator pi = paths__.begin();
           pi != paths__.end();
           ++pi)
        add (*pi);
    }

    void
    cache::add_libraries (path::paths& paths__)
    {
      for (path::paths::iterator pi = paths__.begin();
           pi != paths__.end();
           ++pi)
        input (*pi);
    }

    void
    cache::archive_begin (const std::string& path)
    {
      archives::iterator ai = archives_.find (path);
      if (ai != archives_.end ())
      {
        archive* ar = (*ai).second;
        if (!ar->is_open ())
        {
          if (rld::verbose () >= RLD_VERBOSE_TRACE)
            std::cout << "cache:archive-begin: " << path << std::endl;
          ar->open ();
          ar->begin ();
        }
      }
    }

    void
    cache::archive_end (const std::string& path)
    {
      archives::iterator ai = archives_.find (path);
      if (ai != archives_.end ())
      {
        archive* ar = (*ai).second;
        if (ar->is_open ())
        {
          if (rld::verbose () >= RLD_VERBOSE_TRACE)
            std::cout << "cache:archive-end: " << path << std::endl;
          ar->end ();
          ar->close ();
        }
      }
    }

    void
    cache::archives_begin ()
    {
      for (archives::iterator ai = archives_.begin (); ai != archives_.end (); ++ai)
        archive_begin (((*ai).second)->path ());
    }

    void
    cache::archives_end ()
    {
      for (archives::iterator ai = archives_.begin (); ai != archives_.end (); ++ai)
        archive_end (((*ai).second)->path ());
    }

    void
    cache::collect_object_files ()
    {
      for (path::paths::iterator ni = paths_.begin (); ni != paths_.end (); ++ni)
        collect_object_files (*ni);
    }

    void
    cache::collect_object_files (const std::string& path)
    {
      archive* ar = new archive (path);

      if (ar->is_valid ())
      {
        try
        {
          ar->open ();
          ar->load_objects (objects_);
          ar->close ();
          archives_[path] = ar;
        }
        catch (...)
        {
          delete ar;
          throw;
        }
      }
      else
      {
        delete ar;
        object* obj = new object (path);
        if (!obj->name ().exists ())
        {
          delete obj;
          throw rld::error ("'" + path + "', Not found or a regular file.",
                            "file-check");
        }
        try
        {
          obj->open ();
          obj->begin ();
          obj->end ();
          obj->close ();
          objects_[path] = obj;
        }
        catch (...)
        {
          delete obj;
          throw;
        }
      }
    }

    void
    cache::load_symbols (rld::symbols::table& symbols, bool local)
    {
      if (rld::verbose () >= RLD_VERBOSE_INFO)
        std::cout << "cache:load-sym: object files: " << objects_.size ()
                  << std::endl;

      for (objects::iterator oi = objects_.begin ();
           oi != objects_.end ();
           ++oi)
      {
        object* obj = (*oi).second;
        obj->open ();
        obj->begin ();
        obj->load_symbols (symbols, local);
        obj->end ();
        obj->close ();
      }

      if (rld::verbose () >= RLD_VERBOSE_INFO)
        std::cout << "cache:load-sym: symbols: " << symbols.size ()
                  << std::endl;
    }

    void
    cache::output_unresolved_symbols (std::ostream& out)
    {
      for (objects::iterator oi = objects_.begin ();
           oi != objects_.end ();
           ++oi)
      {
        object* obj = (*oi).second;
        if (obj)
        {
          out << obj->name ().full () << ':' << std::endl;
          rld::symbols::output (out, obj->unresolved_symbols ());
        }
      }
    }

    archives&
    cache::get_archives ()
    {
      return archives_;
    }

    objects&
    cache::get_objects ()
    {
      return objects_;
    }

    void
    cache::get_objects (object_list& list) const
    {
      list.clear ();
      for (path::paths::const_iterator pi = paths_.begin ();
           pi != paths_.end ();
           ++pi)
      {
        objects::const_iterator oi = objects_.find (*pi);
        if (oi == objects_.end ())
          throw rld::error ("Not located or a valid format", *pi);
        list.push_back ((*oi).second);
      }
    }

    const path::paths&
    cache::get_paths () const
    {
      return paths_;
    }

    int
    cache::archive_count () const
    {
      return archives_.size ();
    }

    int
    cache::object_count () const
    {
      return objects_.size ();
    }

    int
    cache::path_count () const
    {
      return paths_.size ();
    }

    void
    cache::get_archive_files (files& afiles)
    {
      for (archives::iterator ai = archives_.begin (); ai != archives_.end (); ++ai)
        afiles.push_back ((*ai).second->name ().full ());
    }

    void
    cache::get_object_files (files& ofiles)
    {
      for (objects::iterator oi = objects_.begin (); oi != objects_.end (); ++oi)
        ofiles.push_back ((*oi).second->name ());
    }

    void
    cache::output_archive_files (std::ostream& out)
    {
      for (archives::iterator ai = archives_.begin (); ai != archives_.end (); ++ai)
        out << ' ' << (*ai).second->name ().full () << std::endl;
    }

    void
    cache::output_object_files (std::ostream& out)
    {
      for (objects::iterator oi = objects_.begin (); oi != objects_.end (); ++oi)
        out << ' ' << (*oi).second->name ().full () << std::endl;
    }

    void
    cache::input (const std::string& path)
    {
      if (opened)
      {
        collect_object_files (path);
        archive_begin (path);
      }
    }

    void
    find_libraries (path::paths& libraries,
                    path::paths& libpaths,
                    path::paths& libs)
    {
      if (rld::verbose () >= RLD_VERBOSE_INFO)
        std::cout << "Finding libraries:" << std::endl;
      libraries.clear ();
      for (path::paths::size_type l = 0; l < libs.size (); ++l)
      {
        std::string lib = "lib" + libs[l] + ".a";
        if (rld::verbose () >= RLD_VERBOSE_DETAILS)
          std::cout << " searching: " << lib << std::endl;
        bool found = false;
        for (path::paths::size_type p = 0; p < libpaths.size (); ++p)
        {
          std::string plib;
          path::path_join (libpaths[p], lib, plib);
          if (rld::verbose () >= RLD_VERBOSE_DETAILS)
              std::cout << " checking: " << plib << std::endl;
          if (path::check_file (plib))
          {
            if (rld::verbose () >= RLD_VERBOSE_INFO)
              std::cout << " found: " << plib << std::endl;
            libraries.push_back (plib);
            found = true;
            break;
          }
        }

        if (!found)
          throw rld::error ("Not found", lib);
      }
    }

  }
}
