//===-- ObjectContainerBSDArchive.cpp ---------------------------*- C++ -*-===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#include "ObjectContainerBSDArchive.h"

#include <ar.h>

#include "lldb/Core/Stream.h"
#include "lldb/Core/ArchSpec.h"
#include "lldb/Core/Module.h"
#include "lldb/Core/PluginManager.h"
#include "lldb/Core/RegularExpression.h"
#include "lldb/Host/Mutex.h"
#include "lldb/Symbol/ObjectFile.h"

using namespace lldb;
using namespace lldb_private;



ObjectContainerBSDArchive::Object::Object() :
    ar_name(),
    ar_date(0),
    ar_uid(0),
    ar_gid(0),
    ar_mode(0),
    ar_size(0),
    ar_file_offset(0),
    ar_file_size(0)
{
}

void
ObjectContainerBSDArchive::Object::Clear()
{
    ar_name.Clear();
    ar_date = 0;
    ar_uid  = 0;
    ar_gid  = 0;
    ar_mode = 0;
    ar_size = 0;
    ar_file_offset = 0;
    ar_file_size = 0;
}

uint32_t
ObjectContainerBSDArchive::Object::Extract (const DataExtractor& data, uint32_t offset)
{
    size_t ar_name_len = 0;
    std::string str;
    char *err;
    str.assign ((const char *)data.GetData(&offset, 16),    16);
    if (str.find("#1/") == 0)
    {
        // If the name is longer than 16 bytes, or contains an embedded space
        // then it will use this format where the length of the name is
        // here and the name characters are after this header.
        ar_name_len = strtoul(str.c_str() + 3, &err, 10);
    }
    else
    {
        // Strip off any spaces (if the object file name contains spaces it
        // will use the extended format above).
        str.erase (str.find(' '));
        ar_name.SetCString(str.c_str());
    }

    str.assign ((const char *)data.GetData(&offset, 12),    12);
    ar_date = strtoul(str.c_str(), &err, 10);

    str.assign ((const char *)data.GetData(&offset, 6), 6);
    ar_uid  = strtoul(str.c_str(), &err, 10);

    str.assign ((const char *)data.GetData(&offset, 6), 6);
    ar_gid  = strtoul(str.c_str(), &err, 10);

    str.assign ((const char *)data.GetData(&offset, 8), 8);
    ar_mode = strtoul(str.c_str(), &err, 8);

    str.assign ((const char *)data.GetData(&offset, 10),    10);
    ar_size = strtoul(str.c_str(), &err, 10);

    str.assign ((const char *)data.GetData(&offset, 2), 2);
    if (str == ARFMAG)
    {
        if (ar_name_len > 0)
        {
            str.assign ((const char *)data.GetData(&offset, ar_name_len), ar_name_len);
            ar_name.SetCString (str.c_str());
        }
        ar_file_offset = offset;
        ar_file_size = ar_size - ar_name_len;
        return offset;
    }
    return LLDB_INVALID_INDEX32;
}

ObjectContainerBSDArchive::Archive::Archive
(
    const lldb_private::ArchSpec &arch,
    const lldb_private::TimeValue &time
) :
    m_arch (arch),
    m_time (time),
    m_objects()
{
}

ObjectContainerBSDArchive::Archive::~Archive ()
{
}

size_t
ObjectContainerBSDArchive::Archive::ParseObjects (DataExtractor &data)
{
    std::string str;
    uint32_t offset = 0;
    str.assign((const char *)data.GetData(&offset, SARMAG), SARMAG);
    if (str == ARMAG)
    {
        Object obj;
        do
        {
            offset = obj.Extract (data, offset);
            if (offset == LLDB_INVALID_INDEX32)
                break;
            uint32_t obj_idx = m_objects.size();
            m_objects.push_back(obj);
            // Insert all of the C strings out of order for now...
            m_object_name_to_index_map.Append (obj.ar_name.GetCString(), obj_idx);
            offset += obj.ar_file_size;
            obj.Clear();
        } while (data.ValidOffset(offset));

        // Now sort all of the object name pointers
        m_object_name_to_index_map.Sort ();
    }
    return m_objects.size();
}

ObjectContainerBSDArchive::Object *
ObjectContainerBSDArchive::Archive::FindObject (const ConstString &object_name)
{
    const UniqueCStringMap<uint32_t>::Entry *match = m_object_name_to_index_map.FindFirstValueForName (object_name.GetCString());
    if (match)
        return &m_objects[match->value];
    return NULL;
}


ObjectContainerBSDArchive::Archive::shared_ptr
ObjectContainerBSDArchive::Archive::FindCachedArchive (const FileSpec &file, const ArchSpec &arch, const TimeValue &time)
{
    Mutex::Locker locker(Archive::GetArchiveCacheMutex ());
    shared_ptr archive_sp;
    Archive::Map &archive_map = Archive::GetArchiveCache ();
    Archive::Map::iterator pos = archive_map.find (file);
    // Don't cache a value for "archive_map.end()" below since we might
    // delete an archive entry...
    while (pos != archive_map.end() && pos->first == file)
    {
        if (pos->second->GetArchitecture() == arch)
        {
            if (pos->second->GetModificationTime() == time)
            {
                return pos->second;
            }
            else
            {
                // We have a file at the same path with the same architecture
                // whose modification time doesn't match. It doesn't make sense
                // for us to continue to use this BSD archive since we cache only
                // the object info which consists of file time info and also the
                // file offset and file size of any contianed objects. Since
                // this information is now out of date, we won't get the correct
                // information if we go and extract the file data, so we should 
                // remove the old and outdated entry.
                archive_map.erase (pos);
                pos = archive_map.find (file);
                continue;
            }
        }
        ++pos;
    }
    return archive_sp;
}

ObjectContainerBSDArchive::Archive::shared_ptr
ObjectContainerBSDArchive::Archive::ParseAndCacheArchiveForFile
(
    const FileSpec &file,
    const ArchSpec &arch,
    const TimeValue &time,
    DataExtractor &data
)
{
    shared_ptr archive_sp(new Archive (arch, time));
    if (archive_sp)
    {
        if (archive_sp->ParseObjects (data) > 0)
        {
            Mutex::Locker locker(Archive::GetArchiveCacheMutex ());
            Archive::GetArchiveCache().insert(std::make_pair(file, archive_sp));
        }
        else
        {
            archive_sp.reset();
        }
    }
    return archive_sp;
}

ObjectContainerBSDArchive::Archive::Map &
ObjectContainerBSDArchive::Archive::GetArchiveCache ()
{
    static Archive::Map g_archive_map;
    return g_archive_map;
}

Mutex &
ObjectContainerBSDArchive::Archive::GetArchiveCacheMutex ()
{
    static Mutex g_archive_map_mutex (Mutex::eMutexTypeRecursive);
    return g_archive_map_mutex;
}


void
ObjectContainerBSDArchive::Initialize()
{
    PluginManager::RegisterPlugin (GetPluginNameStatic(),
                                   GetPluginDescriptionStatic(),
                                   CreateInstance);
}

void
ObjectContainerBSDArchive::Terminate()
{
    PluginManager::UnregisterPlugin (CreateInstance);
}


const char *
ObjectContainerBSDArchive::GetPluginNameStatic()
{
    return "object-container.bsd-archive";
}

const char *
ObjectContainerBSDArchive::GetPluginDescriptionStatic()
{
    return "BSD Archive object container reader.";
}


ObjectContainer *
ObjectContainerBSDArchive::CreateInstance
(
    Module* module,
    DataBufferSP& data_sp,
    const FileSpec *file,
    addr_t offset,
    addr_t length)
{
    if (file && data_sp && ObjectContainerBSDArchive::MagicBytesMatch(data_sp))
    {
        Archive::shared_ptr archive_sp (Archive::FindCachedArchive (*file, module->GetArchitecture(), module->GetModificationTime()));
        
        if (archive_sp)
        {
            // We already have this archive in our cache, use it
            std::auto_ptr<ObjectContainerBSDArchive> container_ap(new ObjectContainerBSDArchive (module, data_sp, file, offset, length));
            if (container_ap.get())
            {
                container_ap->SetArchive (archive_sp);
                return container_ap.release();
            }
        }
        
        // Read everything since we need that in order to index all the
        // objects in the archive
        data_sp = file->MemoryMapFileContents (offset, length);

        std::auto_ptr<ObjectContainerBSDArchive> container_ap(new ObjectContainerBSDArchive (module, data_sp, file, offset, length));
        if (container_ap->ParseHeader())
            return container_ap.release();
    }
    return NULL;
}



bool
ObjectContainerBSDArchive::MagicBytesMatch (DataBufferSP& dataSP)
{
    DataExtractor data(dataSP, lldb::endian::InlHostByteOrder(), 4);
    uint32_t offset = 0;
    const char* armag = (const char* )data.PeekData (offset, sizeof(ar_hdr));
    if (armag && ::strncmp(armag, ARMAG, SARMAG) == 0)
    {
        armag += offsetof(struct ar_hdr, ar_fmag) + SARMAG;
        if (strncmp(armag, ARFMAG, 2) == 0)
            return true;
    }
    return false;
}

ObjectContainerBSDArchive::ObjectContainerBSDArchive
(
    Module* module,
    DataBufferSP& dataSP,
    const lldb_private::FileSpec *file,
    lldb::addr_t offset,
    lldb::addr_t size
) :
    ObjectContainer (module, file, offset, size, dataSP),
    m_archive_sp ()
{
}
void
ObjectContainerBSDArchive::SetArchive (Archive::shared_ptr &archive_sp)
{
    m_archive_sp  = archive_sp;
}



ObjectContainerBSDArchive::~ObjectContainerBSDArchive()
{
}

bool
ObjectContainerBSDArchive::ParseHeader ()
{
    if (m_archive_sp.get() == NULL)
    {
        if (m_data.GetByteSize() > 0)
        {
            m_archive_sp = Archive::ParseAndCacheArchiveForFile (m_file,
                                                                 m_module->GetArchitecture(),
                                                                 m_module->GetModificationTime(),
                                                                 m_data);
            // The archive might be huge, so clear "m_data" to free up the
            // memory since it will contain the entire file (possibly more than
            // one architecture slice). We already have an index of all objects
            // in the file, so we will be ready to serve up those objects.
            m_data.Clear();
        }
    }
    return m_archive_sp.get() != NULL;
}

void
ObjectContainerBSDArchive::Dump (Stream *s) const
{
    s->Printf("%p: ", this);
    s->Indent();
    const size_t num_archs = GetNumArchitectures();
    const size_t num_objects = GetNumObjects();
    s->Printf("ObjectContainerBSDArchive, num_archs = %lu, num_objects = %lu", num_archs, num_objects);
    uint32_t i;
    ArchSpec arch;
    s->IndentMore();
    for (i=0; i<num_archs; i++)
    {
        s->Indent();
        GetArchitectureAtIndex(i, arch);
        s->Printf("arch[%u] = %s\n", i, arch.GetArchitectureName());
    }
    for (i=0; i<num_objects; i++)
    {
        s->Indent();
        s->Printf("object[%u] = %s\n", i, GetObjectNameAtIndex (i));
    }
    s->IndentLess();
    s->EOL();
}

ObjectFileSP
ObjectContainerBSDArchive::GetObjectFile (const FileSpec *file)
{
    if (m_module->GetObjectName() && m_archive_sp)
    {
        Object *object = m_archive_sp->FindObject (m_module->GetObjectName());
        if (object)
            return ObjectFile::FindPlugin (m_module, file, m_offset + object->ar_file_offset, object->ar_file_size);
    }
    return ObjectFileSP();
}


//------------------------------------------------------------------
// PluginInterface protocol
//------------------------------------------------------------------
const char *
ObjectContainerBSDArchive::GetPluginName()
{
    return "object-container.bsd-archive";
}

const char *
ObjectContainerBSDArchive::GetShortPluginName()
{
    return GetPluginNameStatic();
}

uint32_t
ObjectContainerBSDArchive::GetPluginVersion()
{
    return 1;
}

