root/src/kits/storage/ResourceFile.cpp
/*
 * Copyright 2002-2009, Ingo Weinhold, ingo_weinhold@gmx.de.
 * Distributed under the terms of the MIT License.
 */


/*!
        \file ResourceFile.cpp
        ResourceFile implementation.
*/


#include <ResourceFile.h>

#include <algorithm>
#include <new>
#include <stdio.h>

#include <AutoDeleter.h>
#include <BufferIO.h>
#include <Elf.h>
#include <Exception.h>
#include <Pef.h>
#include <ResourceItem.h>
#include <ResourcesContainer.h>
#include <ResourcesDefs.h>
//#include <Warnings.h>


namespace BPrivate {
namespace Storage {


// ELF defs
static const uint32     kMaxELFHeaderSize
        = std::max(sizeof(Elf32_Ehdr), sizeof(Elf64_Ehdr)) + 32;
static const char       kELFFileMagic[4]                        = { 0x7f, 'E', 'L', 'F' };

// sanity bounds
static const uint32     kMaxResourceCount                       = 10000;
static const uint32     kELFMaxResourceAlignment        = 1024 * 1024 * 10;     // 10 MB


// recognized file types (indices into kFileTypeNames)
enum {
        FILE_TYPE_UNKNOWN               = 0,
        FILE_TYPE_X86_RESOURCE  = 1,
        FILE_TYPE_PPC_RESOURCE  = 2,
        FILE_TYPE_ELF                   = 3,
        FILE_TYPE_PEF                   = 4,
        FILE_TYPE_EMPTY                 = 5,
};


const char* kFileTypeNames[] = {
        "unknown",
        "x86 resource file",
        "PPC resource file",
        "ELF object file",
        "PEF object file",
        "empty file",
};


// debugging
//#define DBG(x) x
#define DBG(x)
#define OUT     printf

#define B_VERSION_INFO_TYPE 'APPV'

static const uint32 kVersionInfoIntCount = 5;

// #pragma mark - helper functions/classes


static void
read_exactly(BPositionIO& file, off_t position, void* buffer, size_t size,
        const char* errorMessage = NULL)
{
        ssize_t read = file.ReadAt(position, buffer, size);
        if (read < 0)
                throw Exception(read, errorMessage);
        else if ((size_t)read != size) {
                if (errorMessage) {
                        throw Exception("%s Read too few bytes (%ld/%lu).", errorMessage,
                                                        read, size);
                } else
                        throw Exception("Read too few bytes (%ld/%lu).", read, size);
        }
}


static void
write_exactly(BPositionIO& file, off_t position, const void* buffer,
        size_t size, const char* errorMessage = NULL)
{
        ssize_t written = file.WriteAt(position, buffer, size);
        if (written < 0)
                throw Exception(written, errorMessage);
        else if ((size_t)written != size) {
                if (errorMessage) {
                        throw Exception("%s Wrote too few bytes (%ld/%lu).", errorMessage,
                                                        written, size);
                } else
                        throw Exception("Wrote too few bytes (%ld/%lu).", written, size);
        }
}


template<typename TV, typename TA>
static inline TV
align_value(const TV& value, const TA& alignment)
{
        return ((value + alignment - 1) / alignment) * alignment;
}


static uint32
calculate_checksum(const void* data, uint32 size)
{
        uint32 checkSum = 0;
        const uint8* csData = (const uint8*)data;
        const uint8* dataEnd = csData + size;
        const uint8* current = csData;
        for (; current < dataEnd; current += 4) {
                uint32 word = 0;
                int32 bytes = std::min((int32)4, (int32)(dataEnd - current));
                for (int32 i = 0; i < bytes; i++)
                        word = (word << 8) + current[i];
                checkSum += word;
        }
        return checkSum;
}


static inline const void*
skip_bytes(const void* buffer, int32 offset)
{
        return (const char*)buffer + offset;
}


static inline void*
skip_bytes(void* buffer, int32 offset)
{
        return (char*)buffer + offset;
}


static void
fill_pattern(uint32 byteOffset, void* _buffer, uint32 count)
{
        uint32* buffer = (uint32*)_buffer;
        for (uint32 i = 0; i < count; i++)
                buffer[i] = kUnusedResourceDataPattern[(byteOffset / 4 + i) % 3];
}


static void
fill_pattern(const void* dataBegin, void* buffer, uint32 count)
{
        fill_pattern((char*)buffer - (const char*)dataBegin, buffer, count);
}


static void
fill_pattern(const void* dataBegin, void* buffer, const void* bufferEnd)
{
        fill_pattern(dataBegin, buffer,
                                 ((const char*)bufferEnd - (char*)buffer) / 4);
}


static bool
check_pattern(uint32 byteOffset, void* _buffer, uint32 count,
        bool hostEndianess)
{
        bool result = true;
        uint32* buffer = (uint32*)_buffer;
        for (uint32 i = 0; result && i < count; i++) {
                uint32 value = buffer[i];
                if (!hostEndianess)
                        value = B_SWAP_INT32(value);
                result
                        = (value == kUnusedResourceDataPattern[(byteOffset / 4 + i) % 3]);
        }
        return result;
}


// #pragma mark -


struct MemArea {
        MemArea(const void* data, uint32 size) : data(data), size(size) {}

        inline bool check(const void* _current, uint32 skip = 0) const
        {
                const char* start = (const char*)data;
                const char* current = (const char*)_current;
                return (start <= current && start + size >= current + skip);
        }

        const void*     data;
        uint32          size;
};


struct resource_parse_info {
        off_t                           file_size;
        int32                           resource_count;
        ResourcesContainer*     container;
        char*                           info_table;
        uint32                          info_table_offset;
        uint32                          info_table_size;
};


// #pragma mark -


ResourceFile::ResourceFile()
        :
        fFile(),
        fFileType(FILE_TYPE_UNKNOWN),
        fHostEndianess(true),
        fEmptyResources(true)
{
}


ResourceFile::~ResourceFile()
{
        Unset();
}


status_t
ResourceFile::SetTo(BFile* file, bool clobber)
{
        status_t error = (file ? B_OK : B_BAD_VALUE);
        Unset();
        if (error == B_OK) {
                try {
                        _InitFile(*file, clobber);
                } catch (Exception& exception) {
                        Unset();
                        if (exception.Error() != B_OK)
                                error = exception.Error();
                        else
                                error = B_ERROR;
                }
        }
        return error;
}


void
ResourceFile::Unset()
{
        fFile.Unset();
        fFileType = FILE_TYPE_UNKNOWN;
        fHostEndianess = true;
        fEmptyResources = true;
}


status_t
ResourceFile::InitCheck() const
{
        return fFile.InitCheck();
}


status_t
ResourceFile::InitContainer(ResourcesContainer& container)
{
        container.MakeEmpty();
        status_t error = InitCheck();
        if (error == B_OK && !fEmptyResources) {
                resource_parse_info parseInfo;
                parseInfo.file_size = 0;
                parseInfo.resource_count = 0;
                parseInfo.container = &container;
                parseInfo.info_table = NULL;
                parseInfo.info_table_offset = 0;
                parseInfo.info_table_size = 0;
                try {
                        // get the file size
                        error = fFile.GetSize(&parseInfo.file_size);
                        if (error != B_OK)
                                throw Exception(error, "Failed to get the file size.");
                        _ReadHeader(parseInfo);
                        _ReadIndex(parseInfo);
                        _ReadInfoTable(parseInfo);
                        container.SetModified(false);
                } catch (Exception& exception) {
                        if (exception.Error() != B_OK)
                                error = exception.Error();
                        else
                                error = B_ERROR;
                }
                delete[] parseInfo.info_table;
        }
        return error;
}


status_t
ResourceFile::ReadResource(ResourceItem& resource, bool force)
{
        status_t error = InitCheck();
        size_t size = resource.DataSize();
        if (error == B_OK && (force || !resource.IsLoaded())) {
                void* data = NULL;
                error = resource.SetSize(size);

                if (error == B_OK) {
                        data = resource.Data();
                        ssize_t bytesRead = fFile.ReadAt(resource.Offset(), data, size);
                        if (bytesRead < 0)
                                error = bytesRead;
                        else if ((size_t)bytesRead != size)
                                error = B_IO_ERROR;
                }
                if (error == B_OK) {
                        // convert the data, if necessary
                        if (!fHostEndianess) {
                                if (resource.Type() == B_VERSION_INFO_TYPE) {
                                        // Version info contains integers that need to be swapped
                                        swap_data(B_UINT32_TYPE, data,
                                                kVersionInfoIntCount * sizeof(uint32),
                                                B_SWAP_ALWAYS);
                                } else
                                        swap_data(resource.Type(), data, size, B_SWAP_ALWAYS);
                        }
                        resource.SetLoaded(true);
                        resource.SetModified(false);
                }
        }
        return error;
}


status_t
ResourceFile::ReadResources(ResourcesContainer& container, bool force)
{
        status_t error = InitCheck();
        int32 count = container.CountResources();
        for (int32 i = 0; error == B_OK && i < count; i++) {
                if (ResourceItem* resource = container.ResourceAt(i))
                        error = ReadResource(*resource, force);
                else
                        error = B_ERROR;
        }
        return error;
}


status_t
ResourceFile::WriteResources(ResourcesContainer& container)
{
        status_t error = InitCheck();
        if (error == B_OK && !fFile.File()->IsWritable())
                error = B_NOT_ALLOWED;
        if (error == B_OK && fFileType == FILE_TYPE_EMPTY)
                error = _MakeEmptyResourceFile();
        if (error == B_OK)
                error = _WriteResources(container);
        if (error == B_OK)
                fEmptyResources = false;
        return error;
}


void
ResourceFile::_InitFile(BFile& file, bool clobber)
{
        status_t error = B_OK;
        fFile.Unset();
        // get the file size first
        off_t fileSize = 0;
        error = file.GetSize(&fileSize);
        if (error != B_OK)
                throw Exception(error, "Failed to get the file size.");
        // read the first four bytes, and check, if they identify a resource file
        char magic[4];
        if (fileSize >= 4)
                read_exactly(file, 0, magic, 4, "Failed to read magic number.");
        else if (fileSize > 0 && !clobber)
                throw Exception(B_IO_ERROR, "File is not a resource file.");
        if (fileSize == 0) {
                // empty file
                fHostEndianess = true;
                fFileType = FILE_TYPE_EMPTY;
                fFile.SetTo(&file, 0);
                fEmptyResources = true;
        } else if (!memcmp(magic, kX86ResourceFileMagic, 4)) {
                // x86 resource file
                fHostEndianess = B_HOST_IS_LENDIAN;
                fFileType = FILE_TYPE_X86_RESOURCE;
                fFile.SetTo(&file, kX86ResourcesOffset);
                fEmptyResources = false;
        } else if (!memcmp(magic, kPEFFileMagic1, 4)) {
                PEFContainerHeader pefHeader;
                read_exactly(file, 0, &pefHeader, kPEFContainerHeaderSize,
                        "Failed to read PEF container header.");
                if (!memcmp(pefHeader.tag2, kPPCResourceFileMagic, 4)) {
                        // PPC resource file
                        fHostEndianess = B_HOST_IS_BENDIAN;
                        fFileType = FILE_TYPE_PPC_RESOURCE;
                        fFile.SetTo(&file, kPPCResourcesOffset);
                        fEmptyResources = false;
                } else if (!memcmp(pefHeader.tag2, kPEFFileMagic2, 4)) {
                        // PEF file
                        fFileType = FILE_TYPE_PEF;
                        _InitPEFFile(file, pefHeader);
                } else
                        throw Exception(B_IO_ERROR, "File is not a resource file.");
        } else if (!memcmp(magic, kELFFileMagic, 4)) {
                // ELF file
                fFileType = FILE_TYPE_ELF;
                _InitELFFile(file);
        } else if (!memcmp(magic, kX86ResourceFileMagic, 2)) {
                // x86 resource file with screwed magic?
//              Warnings::AddCurrentWarning("File magic is 0x%08lx. Should be 0x%08lx "
//                                                                      "for x86 resource file. Try anyway.",
//                                                                      ntohl(*(uint32*)magic),
//                                                                      ntohl(*(uint32*)kX86ResourceFileMagic));
                fHostEndianess = B_HOST_IS_LENDIAN;
                fFileType = FILE_TYPE_X86_RESOURCE;
                fFile.SetTo(&file, kX86ResourcesOffset);
                fEmptyResources = true;
        } else {
                if (clobber) {
                        // make it an x86 resource file
                        fHostEndianess = true;
                        fFileType = FILE_TYPE_EMPTY;
                        fFile.SetTo(&file, 0);
                } else
                        throw Exception(B_IO_ERROR, "File is not a resource file.");
        }
        error = fFile.InitCheck();
        if (error != B_OK)
                throw Exception(error, "Failed to initialize resource file.");
        // clobber, if desired
        if (clobber) {
                // just write an empty resources container
                ResourcesContainer container;
                WriteResources(container);
        }
}


void
ResourceFile::_InitELFFile(BFile& file)
{
        status_t error = B_OK;

        // get the file size
        off_t fileSize = 0;
        error = file.GetSize(&fileSize);
        if (error != B_OK)
                throw Exception(error, "Failed to get the file size.");

        // read the ELF headers e_ident field
        unsigned char identification[EI_NIDENT];
        read_exactly(file, 0, identification, EI_NIDENT,
                "Failed to read ELF identification.");

        // check version
        if (identification[EI_VERSION] != EV_CURRENT)
                throw Exception(B_UNSUPPORTED, "Unsupported ELF version.");

        // check data encoding (endianess)
        switch (identification[EI_DATA]) {
                case ELFDATA2LSB:
                        fHostEndianess = B_HOST_IS_LENDIAN;
                        break;
                case ELFDATA2MSB:
                        fHostEndianess = B_HOST_IS_BENDIAN;
                        break;
                default:
                case ELFDATANONE:
                        throw Exception(B_UNSUPPORTED, "Unsupported ELF data encoding.");
        }

        // check class (32/64 bit) and call the respective method handling it
        switch (identification[EI_CLASS]) {
                case ELFCLASS32:
                        _InitELFXFile<Elf32_Ehdr, Elf32_Phdr, Elf32_Shdr>(file, fileSize);
                        break;
                case ELFCLASS64:
                        _InitELFXFile<Elf64_Ehdr, Elf64_Phdr, Elf64_Shdr>(file, fileSize);
                        break;
                default:
                        throw Exception(B_UNSUPPORTED, "Unsupported ELF class.");
        }
}


template<typename ElfHeader, typename ElfProgramHeader,
        typename ElfSectionHeader>
void
ResourceFile::_InitELFXFile(BFile& file, uint64 fileSize)
{
        // read ELF header
        ElfHeader fileHeader;
        read_exactly(file, 0, &fileHeader, sizeof(ElfHeader),
                "Failed to read ELF header.");

        // get the header values
        uint32 headerSize                               = _GetInt(fileHeader.e_ehsize);
        uint64 programHeaderTableOffset = _GetInt(fileHeader.e_phoff);
        uint32 programHeaderSize                = _GetInt(fileHeader.e_phentsize);
        uint32 programHeaderCount               = _GetInt(fileHeader.e_phnum);
        uint64 sectionHeaderTableOffset = _GetInt(fileHeader.e_shoff);
        uint32 sectionHeaderSize                = _GetInt(fileHeader.e_shentsize);
        uint32 sectionHeaderCount               = _GetInt(fileHeader.e_shnum);
        bool hasProgramHeaderTable = (programHeaderTableOffset != 0);
        bool hasSectionHeaderTable = (sectionHeaderTableOffset != 0);

        // check the sanity of the header values
        // ELF header size
        if (headerSize < sizeof(ElfHeader) || headerSize > kMaxELFHeaderSize) {
                throw Exception(B_IO_ERROR,
                        "Invalid ELF header: invalid ELF header size: %" B_PRIu32 ".",
                        headerSize);
        }
        uint64 resourceOffset = headerSize;
        uint64 resourceAlignment = 0;

        // program header table offset and entry count/size
        uint64 programHeaderTableSize = 0;
        if (hasProgramHeaderTable) {
                if (programHeaderTableOffset < headerSize
                        || programHeaderTableOffset > fileSize) {
                        throw Exception(B_IO_ERROR, "Invalid ELF header: invalid program "
                                "header table offset: %lu.", programHeaderTableOffset);
                }
                programHeaderTableSize = (uint64)programHeaderSize * programHeaderCount;
                if (programHeaderSize < sizeof(ElfProgramHeader)
                        || programHeaderTableOffset + programHeaderTableSize > fileSize) {
                        throw Exception(B_IO_ERROR, "Invalid ELF header: program header "
                                "table exceeds file: %lu.",
                                programHeaderTableOffset + programHeaderTableSize);
                }
                resourceOffset = std::max(resourceOffset,
                        programHeaderTableOffset + programHeaderTableSize);

                // load the program headers into memory
                uint8* programHeaders = (uint8*)malloc(
                        programHeaderCount * programHeaderSize);
                if (programHeaders == NULL)
                        throw Exception(B_NO_MEMORY);
                MemoryDeleter programHeadersDeleter(programHeaders);

                read_exactly(file, programHeaderTableOffset, programHeaders,
                        programHeaderCount * programHeaderSize,
                        "Failed to read ELF program headers.");

                // iterate through the program headers
                for (uint32 i = 0; i < programHeaderCount; i++) {
                        ElfProgramHeader& programHeader
                                = *(ElfProgramHeader*)(programHeaders + i * programHeaderSize);

                        // get the header values
                        uint32 type                     = _GetInt(programHeader.p_type);
                        uint64 offset           = _GetInt(programHeader.p_offset);
                        uint64 size                     = _GetInt(programHeader.p_filesz);
                        uint64 alignment        = _GetInt(programHeader.p_align);

                        // check the values
                        // PT_NULL marks the header unused,
                        if (type != PT_NULL) {
                                if (/*offset < headerSize ||*/ offset > fileSize) {
                                        throw Exception(B_IO_ERROR, "Invalid ELF program header: "
                                                "invalid program offset: %lu.", offset);
                                }
                                uint64 segmentEnd = offset + size;
                                if (segmentEnd > fileSize) {
                                        throw Exception(B_IO_ERROR, "Invalid ELF section header: "
                                                "segment exceeds file: %lu.", segmentEnd);
                                }
                                resourceOffset = std::max(resourceOffset, segmentEnd);
                                resourceAlignment = std::max(resourceAlignment, alignment);
                        }
                }
        }

        // section header table offset and entry count/size
        uint64 sectionHeaderTableSize = 0;
        if (hasSectionHeaderTable) {
                if (sectionHeaderTableOffset < headerSize
                        || sectionHeaderTableOffset > fileSize) {
                        throw Exception(B_IO_ERROR, "Invalid ELF header: invalid section "
                                "header table offset: %lu.", sectionHeaderTableOffset);
                }
                sectionHeaderTableSize = (uint64)sectionHeaderSize * sectionHeaderCount;
                if (sectionHeaderSize < sizeof(ElfSectionHeader)
                        || sectionHeaderTableOffset + sectionHeaderTableSize > fileSize) {
                        throw Exception(B_IO_ERROR, "Invalid ELF header: section header "
                                "table exceeds file: %lu.",
                                sectionHeaderTableOffset + sectionHeaderTableSize);
                }
                resourceOffset = std::max(resourceOffset,
                        sectionHeaderTableOffset + sectionHeaderTableSize);

                // load the section headers into memory
                uint8* sectionHeaders = (uint8*)malloc(
                        sectionHeaderCount * sectionHeaderSize);
                if (sectionHeaders == NULL)
                        throw Exception(B_NO_MEMORY);
                MemoryDeleter sectionHeadersDeleter(sectionHeaders);

                read_exactly(file, sectionHeaderTableOffset, sectionHeaders,
                        sectionHeaderCount * sectionHeaderSize,
                        "Failed to read ELF section headers.");

                // iterate through the section headers
                for (uint32 i = 0; i < sectionHeaderCount; i++) {
                        ElfSectionHeader& sectionHeader
                                = *(ElfSectionHeader*)(sectionHeaders + i * sectionHeaderSize);

                        // get the header values
                        uint32 type             = _GetInt(sectionHeader.sh_type);
                        uint64 offset   = _GetInt(sectionHeader.sh_offset);
                        uint64 size             = _GetInt(sectionHeader.sh_size);

                        // check the values
                        // SHT_NULL marks the header unused,
                        // SHT_NOBITS sections take no space in the file
                        if (type != SHT_NULL && type != SHT_NOBITS) {
                                if (offset < headerSize || offset > fileSize) {
                                        throw Exception(B_IO_ERROR, "Invalid ELF section header: "
                                                "invalid section offset: %lu.", offset);
                                }
                                uint64 sectionEnd = offset + size;
                                if (sectionEnd > fileSize) {
                                        throw Exception(B_IO_ERROR, "Invalid ELF section header: "
                                                "section exceeds file: %lu.", sectionEnd);
                                }
                                resourceOffset = std::max(resourceOffset, sectionEnd);
                        }
                }
        }

        // align the offset
        if (fileHeader.e_ident[EI_CLASS] == ELFCLASS64) {
                // For ELF64 binaries we use a different alignment behaviour. It is
                // not necessary to align the position of the resources in the file to
                // the maximum value of p_align, and in fact on x86_64 this behaviour
                // causes an undesirable effect: since the default segment alignment is
                // 2MB, aligning to p_align causes all binaries to be at least 2MB when
                // resources have been added. So, just align to an 8-byte boundary.
                resourceAlignment = 8;
        } else {
                // Retain previous alignment behaviour for compatibility.
                if (resourceAlignment < kELFMinResourceAlignment)
                        resourceAlignment = kELFMinResourceAlignment;
                if (resourceAlignment > kELFMaxResourceAlignment) {
                        throw Exception(B_IO_ERROR, "The ELF object file requires an "
                                "invalid alignment: %lu.", resourceAlignment);
                }
        }

        resourceOffset = align_value(resourceOffset, resourceAlignment);
        if (resourceOffset >= fileSize) {
//              throw Exception("The ELF object file does not contain resources.");
                fEmptyResources = true;
        } else
                fEmptyResources = false;

        // fine, init the offset file
        fFile.SetTo(&file, resourceOffset);
}


void
ResourceFile::_InitPEFFile(BFile& file, const PEFContainerHeader& pefHeader)
{
        status_t error = B_OK;
        // get the file size
        off_t fileSize = 0;
        error = file.GetSize(&fileSize);
        if (error != B_OK)
                throw Exception(error, "Failed to get the file size.");
        // check architecture -- we support PPC only
        if (memcmp(pefHeader.architecture, kPEFArchitecturePPC, 4) != 0)
                throw Exception(B_IO_ERROR, "PEF file architecture is not PPC.");
        fHostEndianess = B_HOST_IS_BENDIAN;
        // get the section count
        uint16 sectionCount = _GetInt(pefHeader.sectionCount);
        // iterate through the PEF sections headers
        uint32 sectionHeaderTableOffset = kPEFContainerHeaderSize;
        uint32 sectionHeaderTableEnd
                = sectionHeaderTableOffset + sectionCount * kPEFSectionHeaderSize;
        uint32 resourceOffset = sectionHeaderTableEnd;
        for (int32 i = 0; i < (int32)sectionCount; i++) {
                uint32 shOffset = sectionHeaderTableOffset + i * kPEFSectionHeaderSize;
                PEFSectionHeader sectionHeader;
                read_exactly(file, shOffset, &sectionHeader, kPEFSectionHeaderSize,
                        "Failed to read PEF section header.");
                // get the header values
                uint32 offset   = _GetInt(sectionHeader.containerOffset);
                uint32 size             = _GetInt(sectionHeader.packedSize);
                // check the values
                if (offset < sectionHeaderTableEnd || offset > fileSize) {
                        throw Exception(B_IO_ERROR, "Invalid PEF section header: invalid "
                                "section offset: %" B_PRIu32 ".", offset);
                }
                uint32 sectionEnd = offset + size;
                if (sectionEnd > fileSize) {
                        throw Exception(B_IO_ERROR, "Invalid PEF section header: section "
                                "exceeds file: %" B_PRIu32 ".", sectionEnd);
                }
                resourceOffset = std::max(resourceOffset, sectionEnd);
        }
        if (resourceOffset >= fileSize) {
//              throw Exception("The PEF object file does not contain resources.");
                fEmptyResources = true;
        } else
                fEmptyResources = false;
        // init the offset file
        fFile.SetTo(&file, resourceOffset);
}


void
ResourceFile::_ReadHeader(resource_parse_info& parseInfo)
{
        // read the header
        resources_header header;
        read_exactly(fFile, 0, &header, kResourcesHeaderSize,
                "Failed to read the header.");
        // check the header
        // magic
        uint32 magic = _GetInt(header.rh_resources_magic);
        if (magic == kResourcesHeaderMagic) {
                // everything is fine
        } else if (B_SWAP_INT32(magic) == kResourcesHeaderMagic) {
//              const char* endianessStr[2] = { "little", "big" };
//              int32 endianess
//                      = (fHostEndianess == ((bool)B_HOST_IS_LENDIAN ? 0 : 1));
//              Warnings::AddCurrentWarning("Endianess seems to be %s, although %s "
//                                                                      "was expected.",
//                                                                      endianessStr[1 - endianess],
//                                                                      endianessStr[endianess]);
                fHostEndianess = !fHostEndianess;
        } else
                throw Exception(B_IO_ERROR, "Invalid resources header magic.");
        // resource count
        uint32 resourceCount = _GetInt(header.rh_resource_count);
        if (resourceCount > kMaxResourceCount)
                throw Exception(B_IO_ERROR, "Bad number of resources.");
        // index section offset
        uint32 indexSectionOffset = _GetInt(header.rh_index_section_offset);
        if (indexSectionOffset != kResourceIndexSectionOffset) {
                throw Exception(B_IO_ERROR, "Unexpected resource index section "
                        "offset. Is: %" B_PRIu32 ", should be: %" B_PRIu32 ".",
                        indexSectionOffset, kResourceIndexSectionOffset);
        }
        // admin section size
        uint32 indexSectionSize = kResourceIndexSectionHeaderSize
                                                          + kResourceIndexEntrySize * resourceCount;
        indexSectionSize = align_value(indexSectionSize,
                                                                   kResourceIndexSectionAlignment);
        uint32 adminSectionSize = _GetInt(header.rh_admin_section_size);
        if (adminSectionSize != indexSectionOffset + indexSectionSize) {
                throw Exception(B_IO_ERROR, "Unexpected resource admin section size. "
                        "Is: %" B_PRIu32 ", should be: %" B_PRIu32 ".", adminSectionSize,
                        indexSectionOffset + indexSectionSize);
        }
        // set the resource count
        parseInfo.resource_count = resourceCount;
}


void
ResourceFile::_ReadIndex(resource_parse_info& parseInfo)
{
        int32& resourceCount = parseInfo.resource_count;
        off_t& fileSize = parseInfo.file_size;
        BBufferIO buffer(&fFile, 2048, false);

        // read the header
        resource_index_section_header header;
        read_exactly(buffer, kResourceIndexSectionOffset, &header,
                kResourceIndexSectionHeaderSize,
                "Failed to read the resource index section header.");
        // check the header
        // index section offset
        uint32 indexSectionOffset = _GetInt(header.rish_index_section_offset);
        if (indexSectionOffset != kResourceIndexSectionOffset) {
                throw Exception(B_IO_ERROR, "Unexpected resource index section "
                        "offset. Is: %" B_PRIu32 ", should be: %" B_PRIu32 ".",
                        indexSectionOffset, kResourceIndexSectionOffset);
        }
        // index section size
        uint32 expectedIndexSectionSize = kResourceIndexSectionHeaderSize
                + kResourceIndexEntrySize * resourceCount;
        expectedIndexSectionSize = align_value(expectedIndexSectionSize,
                                                                                   kResourceIndexSectionAlignment);
        uint32 indexSectionSize = _GetInt(header.rish_index_section_size);
        if (indexSectionSize != expectedIndexSectionSize) {
                throw Exception(B_IO_ERROR, "Unexpected resource index section size. "
                        "Is: %" B_PRIu32 ", should be: %" B_PRIu32 ".", indexSectionSize,
                        expectedIndexSectionSize);
        }
        // unknown section offset
        uint32 unknownSectionOffset
                = _GetInt(header.rish_unknown_section_offset);
        if (unknownSectionOffset != indexSectionOffset + indexSectionSize) {
                throw Exception(B_IO_ERROR, "Unexpected resource index section size. "
                        "Is: %" B_PRIu32 ", should be: %" B_PRIu32 ".",
                        unknownSectionOffset, indexSectionOffset + indexSectionSize);
        }
        // unknown section size
        uint32 unknownSectionSize = _GetInt(header.rish_unknown_section_size);
        if (unknownSectionSize != kUnknownResourceSectionSize) {
                throw Exception(B_IO_ERROR, "Unexpected resource index section "
                        "offset. Is: %" B_PRIu32 ", should be: %" B_PRIu32 ".",
                        unknownSectionOffset, kUnknownResourceSectionSize);
        }

        // info table offset and size
        uint32 infoTableOffset = _GetInt(header.rish_info_table_offset);
        uint32 infoTableSize = _GetInt(header.rish_info_table_size);
        if (infoTableOffset + infoTableSize > fileSize)
                throw Exception(B_IO_ERROR, "Invalid info table location.");
        parseInfo.info_table_offset = infoTableOffset;
        parseInfo.info_table_size = infoTableSize;

        // read the index entries
        uint32 indexTableOffset = indexSectionOffset
                + kResourceIndexSectionHeaderSize;
        int32 maxResourceCount = (unknownSectionOffset - indexTableOffset)
                / kResourceIndexEntrySize;
        int32 actualResourceCount = 0;
        bool tableEndReached = false;
        for (int32 i = 0; !tableEndReached && i < maxResourceCount; i++) {
                // read one entry
                tableEndReached = !_ReadIndexEntry(buffer, parseInfo, i,
                        indexTableOffset, (i >= resourceCount));
                if (!tableEndReached)
                        actualResourceCount++;
        }
        // check resource count
        if (actualResourceCount != resourceCount) {
                if (actualResourceCount > resourceCount) {
//                      Warnings::AddCurrentWarning("Resource index table contains "
//                                                                              "%ld entries, although it should be "
//                                                                              "%ld only.", actualResourceCount,
//                                                                              resourceCount);
                }
                resourceCount = actualResourceCount;
        }
}


bool
ResourceFile::_ReadIndexEntry(BPositionIO& buffer,
        resource_parse_info& parseInfo, int32 index, uint32 tableOffset,
        bool peekAhead)
{
        off_t& fileSize = parseInfo.file_size;
        bool result = true;
        resource_index_entry entry;

        // read one entry
        off_t entryOffset = tableOffset + index * kResourceIndexEntrySize;
        read_exactly(buffer, entryOffset, &entry, kResourceIndexEntrySize,
                "Failed to read a resource index entry.");

        // check, if the end is reached early
        if (result && check_pattern(entryOffset, &entry,
                        kResourceIndexEntrySize / 4, fHostEndianess)) {
                result = false;
        }
        uint32 offset = _GetInt(entry.rie_offset);
        uint32 size = _GetInt(entry.rie_size);

        // check the location
        if (result && offset + size > fileSize) {
                if (!peekAhead) {
                        throw Exception(B_IO_ERROR, "Invalid resource index entry: index: "
                                "%" B_PRId32 ", offset: %" B_PRIu32 " (%" B_PRIx32 "), "
                                "size: %" B_PRIu32 " (%" B_PRIx32 ").",
                                index + 1, offset, offset, size, size);
                }
                result = false;
        }

        // add the entry
        if (result) {
                ResourceItem* item = new(std::nothrow) ResourceItem;
                if (!item)
                        throw Exception(B_NO_MEMORY);
                item->SetLocation(offset, size);
                if (!parseInfo.container->AddResource(item, index, false)) {
                        delete item;
                        throw Exception(B_NO_MEMORY);
                }
        }

        return result;
}


void
ResourceFile::_ReadInfoTable(resource_parse_info& parseInfo)
{
        int32& resourceCount = parseInfo.resource_count;
        // read the info table
        // alloc memory for the table
        char* tableData = new(std::nothrow) char[parseInfo.info_table_size];
        if (!tableData)
                throw Exception(B_NO_MEMORY);
        int32 dataSize = parseInfo.info_table_size;
        parseInfo.info_table = tableData;       // freed by the info owner
        read_exactly(fFile, parseInfo.info_table_offset, tableData, dataSize,
                "Failed to read resource info table.");
        //
        bool* readIndices = new(std::nothrow) bool[resourceCount + 1];
                // + 1 => always > 0
        if (!readIndices)
                throw Exception(B_NO_MEMORY);
        ArrayDeleter<bool> readIndicesDeleter(readIndices);
        for (int32 i = 0; i < resourceCount; i++)
                readIndices[i] = false;
        MemArea area(tableData, dataSize);
        const void* data = tableData;
        // check the table end/check sum
        if (_ReadInfoTableEnd(data, dataSize))
                dataSize -= kResourceInfoTableEndSize;
        // read the infos
        int32 resourceIndex = 1;
        uint32 minRemainderSize
                = kMinResourceInfoBlockSize + kResourceInfoSeparatorSize;
        while (area.check(data, minRemainderSize)) {
                // read a resource block
                if (!area.check(data, kMinResourceInfoBlockSize)) {
                        throw Exception(B_IO_ERROR, "Unexpected end of resource info "
                                "table at index %" B_PRId32 ".", resourceIndex);
                }
                const resource_info_block* infoBlock
                        = (const resource_info_block*)data;
                type_code type = _GetInt(infoBlock->rib_type);
                // read the infos of this block
                const resource_info* info = infoBlock->rib_info;
                while (info) {
                        data = _ReadResourceInfo(parseInfo, area, info, type, readIndices);
                        // prepare for next iteration, if there is another info
                        if (!area.check(data, kResourceInfoSeparatorSize)) {
                                throw Exception(B_IO_ERROR, "Unexpected end of resource info "
                                        "table after index %" B_PRId32 ".", resourceIndex);
                        }
                        const resource_info_separator* separator
                                = (const resource_info_separator*)data;
                        if (_GetInt(separator->ris_value1) == 0xffffffff
                                && _GetInt(separator->ris_value2) == 0xffffffff) {
                                // info block ends
                                info = NULL;
                                data = skip_bytes(data, kResourceInfoSeparatorSize);
                        } else {
                                // another info follows
                                info = (const resource_info*)data;
                        }
                        resourceIndex++;
                }
                // end of the info block
        }
        // handle special case: empty resource info table
        if (resourceIndex == 1) {
                if (!area.check(data, kResourceInfoSeparatorSize)) {
                        throw Exception(B_IO_ERROR, "Unexpected end of resource info "
                                "table.");
                }
                const resource_info_separator* tableTerminator
                        = (const resource_info_separator*)data;
                if (_GetInt(tableTerminator->ris_value1) != 0xffffffff
                        || _GetInt(tableTerminator->ris_value2) != 0xffffffff) {
                        throw Exception(B_IO_ERROR, "The resource info table ought to be "
                                "empty, but is not properly terminated.");
                }
                data = skip_bytes(data, kResourceInfoSeparatorSize);
        }
        // Check, if the correct number of bytes are remaining.
        uint32 bytesLeft = (const char*)tableData + dataSize - (const char*)data;
        if (bytesLeft != 0) {
                throw Exception(B_IO_ERROR, "Error at the end of the resource info "
                        "table: %" B_PRIu32 " bytes are remaining.", bytesLeft);
        }
        // check, if all items have been initialized
        for (int32 i = resourceCount - 1; i >= 0; i--) {
                if (!readIndices[i]) {
//                      Warnings::AddCurrentWarning("Resource item at index %ld "
//                                                                              "has no info. Item removed.", i + 1);
                        if (ResourceItem* item = parseInfo.container->RemoveResource(i))
                                delete item;
                        resourceCount--;
                }
        }
}


bool
ResourceFile::_ReadInfoTableEnd(const void* data, int32 dataSize)
{
        bool hasTableEnd = true;
        if ((uint32)dataSize < kResourceInfoSeparatorSize)
                throw Exception(B_IO_ERROR, "Info table is too short.");
        if ((uint32)dataSize < kResourceInfoTableEndSize)
                hasTableEnd = false;
        if (hasTableEnd) {
                const resource_info_table_end* tableEnd
                        = (const resource_info_table_end*)
                          skip_bytes(data, dataSize - kResourceInfoTableEndSize);
                if (_GetInt(tableEnd->rite_terminator) != 0)
                        hasTableEnd = false;
                if (hasTableEnd) {
                        dataSize -= kResourceInfoTableEndSize;
                        // checksum
                        uint32 checkSum = calculate_checksum(data, dataSize);
                        uint32 fileCheckSum = _GetInt(tableEnd->rite_check_sum);
                        if (checkSum != fileCheckSum) {
                                throw Exception(B_IO_ERROR, "Invalid resource info table check"
                                        " sum: In file: %" B_PRIx32 ", calculated: %" B_PRIx32 ".",
                                        fileCheckSum, checkSum);
                        }
                }
        }
//      if (!hasTableEnd)
//              Warnings::AddCurrentWarning("resource info table has no check sum.");
        return hasTableEnd;
}


const void*
ResourceFile::_ReadResourceInfo(resource_parse_info& parseInfo,
        const MemArea& area, const resource_info* info, type_code type,
        bool* readIndices)
{
        int32& resourceCount = parseInfo.resource_count;
        int32 id = _GetInt(info->ri_id);
        int32 index = _GetInt(info->ri_index);
        uint16 nameSize = _GetInt(info->ri_name_size);
        const char* name = info->ri_name;
        // check the values
        bool ignore = false;
        // index
        if (index < 1 || index > resourceCount) {
//              Warnings::AddCurrentWarning("Invalid index field in resource "
//                                                                      "info table: %lu.", index);
                ignore = true;
        }
        if (!ignore) {
                if (readIndices[index - 1]) {
                        throw Exception(B_IO_ERROR, "Multiple resource infos with the "
                                "same index field: %" B_PRId32 ".", index);
                }
                readIndices[index - 1] = true;
        }
        // name size
        if (!area.check(name, nameSize)) {
                throw Exception(B_IO_ERROR, "Invalid name size (%" B_PRIu16 ") "
                        "for index %" B_PRId32 " in resource info table.",
                        nameSize, index);
        }
        // check, if name is null terminated
        if (name[nameSize - 1] != 0) {
//              Warnings::AddCurrentWarning("Name for index %ld in "
//                                                                      "resource info table is not null "
//                                                                      "terminated.", index);
        }
        // set the values
        if (!ignore) {
                BString resourceName(name, nameSize);
                if (ResourceItem* item = parseInfo.container->ResourceAt(index - 1))
                        item->SetIdentity(type, id, resourceName.String());
                else {
                        throw Exception(B_IO_ERROR, "Unexpected error: No resource item "
                                "at index %" B_PRId32 ".", index);
                }
        }
        return skip_bytes(name, nameSize);
}


status_t
ResourceFile::_WriteResources(ResourcesContainer& container)
{
        status_t error = B_OK;
        int32 resourceCount = container.CountResources();
        char* buffer = NULL;
        try {
                // calculate sizes and offsets
                // header
                uint32 size = kResourcesHeaderSize;
                size_t bufferSize = size;
                // index section
                uint32 indexSectionOffset = size;
                uint32 indexSectionSize = kResourceIndexSectionHeaderSize
                        + resourceCount * kResourceIndexEntrySize;
                indexSectionSize = align_value(indexSectionSize,
                        kResourceIndexSectionAlignment);
                size += indexSectionSize;
                bufferSize = std::max((uint32)bufferSize, indexSectionSize);
                // unknown section
                uint32 unknownSectionOffset = size;
                uint32 unknownSectionSize = kUnknownResourceSectionSize;
                size += unknownSectionSize;
                bufferSize = std::max((uint32)bufferSize, unknownSectionSize);
                // data
                uint32 dataOffset = size;
                uint32 dataSize = 0;
                for (int32 i = 0; i < resourceCount; i++) {
                        ResourceItem* item = container.ResourceAt(i);
                        if (!item->IsLoaded())
                                throw Exception(B_IO_ERROR, "Resource is not loaded.");
                        dataSize += item->DataSize();
                        bufferSize = std::max(bufferSize, item->DataSize());
                }
                size += dataSize;
                // info table
                uint32 infoTableOffset = size;
                uint32 infoTableSize = 0;
                type_code type = 0;
                for (int32 i = 0; i < resourceCount; i++) {
                        ResourceItem* item = container.ResourceAt(i);
                        if (i == 0 || type != item->Type()) {
                                if (i != 0)
                                        infoTableSize += kResourceInfoSeparatorSize;
                                type = item->Type();
                                infoTableSize += kMinResourceInfoBlockSize;
                        } else
                                infoTableSize += kMinResourceInfoSize;

                        const char* name = item->Name();
                        if (name && name[0] != '\0')
                                infoTableSize += strlen(name) + 1;
                }
                infoTableSize += kResourceInfoSeparatorSize
                        + kResourceInfoTableEndSize;
                size += infoTableSize;
                bufferSize = std::max((uint32)bufferSize, infoTableSize);

                // write...
                // set the file size
                fFile.SetSize(size);
                buffer = new(std::nothrow) char[bufferSize];
                if (!buffer)
                        throw Exception(B_NO_MEMORY);
                void* data = buffer;
                // header
                resources_header* resourcesHeader = (resources_header*)data;
                resourcesHeader->rh_resources_magic = kResourcesHeaderMagic;
                resourcesHeader->rh_resource_count = resourceCount;
                resourcesHeader->rh_index_section_offset = indexSectionOffset;
                resourcesHeader->rh_admin_section_size = indexSectionOffset
                                                                                                 + indexSectionSize;
                for (int32 i = 0; i < 13; i++)
                        resourcesHeader->rh_pad[i] = 0;
                write_exactly(fFile, 0, buffer, kResourcesHeaderSize,
                        "Failed to write resources header.");
                // index section
                data = buffer;
                // header
                resource_index_section_header* indexHeader
                        = (resource_index_section_header*)data;
                indexHeader->rish_index_section_offset = indexSectionOffset;
                indexHeader->rish_index_section_size = indexSectionSize;
                indexHeader->rish_unknown_section_offset = unknownSectionOffset;
                indexHeader->rish_unknown_section_size = unknownSectionSize;
                indexHeader->rish_info_table_offset = infoTableOffset;
                indexHeader->rish_info_table_size = infoTableSize;
                fill_pattern(buffer - indexSectionOffset,
                        &indexHeader->rish_unused_data1, 1);
                fill_pattern(buffer - indexSectionOffset,
                        indexHeader->rish_unused_data2, 25);
                fill_pattern(buffer - indexSectionOffset,
                        &indexHeader->rish_unused_data3, 1);
                // index table
                data = skip_bytes(data, kResourceIndexSectionHeaderSize);
                resource_index_entry* entry = (resource_index_entry*)data;
                uint32 entryOffset = dataOffset;
                for (int32 i = 0; i < resourceCount; i++, entry++) {
                        ResourceItem* item = container.ResourceAt(i);
                        uint32 entrySize = item->DataSize();
                        entry->rie_offset = entryOffset;
                        entry->rie_size = entrySize;
                        entry->rie_pad = 0;
                        entryOffset += entrySize;
                }
                fill_pattern(buffer - indexSectionOffset, entry,
                        buffer + indexSectionSize);
                write_exactly(fFile, indexSectionOffset, buffer, indexSectionSize,
                        "Failed to write index section.");
                // unknown section
                fill_pattern(unknownSectionOffset, buffer, unknownSectionSize / 4);
                write_exactly(fFile, unknownSectionOffset, buffer, unknownSectionSize,
                        "Failed to write unknown section.");
                // data
                uint32 itemOffset = dataOffset;
                for (int32 i = 0; i < resourceCount; i++) {
                        data = buffer;
                        ResourceItem* item = container.ResourceAt(i);
                        const void* itemData = item->Data();
                        uint32 itemSize = item->DataSize();
                        if (!itemData && itemSize > 0)
                                throw Exception(error, "Invalid resource item data.");
                        if (itemData) {
                                // swap data, if necessary
                                if (!fHostEndianess) {
                                        memcpy(data, itemData, itemSize);
                                        if (item->Type() == B_VERSION_INFO_TYPE) {
                                                // Version info contains integers
                                                // that need to be swapped
                                                swap_data(B_UINT32_TYPE, data,
                                                        kVersionInfoIntCount * sizeof(uint32),
                                                        B_SWAP_ALWAYS);
                                        } else
                                                swap_data(item->Type(), data, itemSize, B_SWAP_ALWAYS);
                                        itemData = data;
                                }
                                write_exactly(fFile, itemOffset, itemData, itemSize,
                                        "Failed to write resource item data.");
                        }
                        item->SetOffset(itemOffset);
                        itemOffset += itemSize;
                }
                // info table
                data = buffer;
                type = 0;
                for (int32 i = 0; i < resourceCount; i++) {
                        ResourceItem* item = container.ResourceAt(i);
                        resource_info* info = NULL;
                        if (i == 0 || type != item->Type()) {
                                if (i != 0) {
                                        resource_info_separator* separator
                                                = (resource_info_separator*)data;
                                        separator->ris_value1 = 0xffffffff;
                                        separator->ris_value2 = 0xffffffff;
                                        data = skip_bytes(data, kResourceInfoSeparatorSize);
                                }
                                type = item->Type();
                                resource_info_block* infoBlock = (resource_info_block*)data;
                                infoBlock->rib_type = type;
                                info = infoBlock->rib_info;
                        } else
                                info = (resource_info*)data;
                        // info
                        info->ri_id = item->ID();
                        info->ri_index = i + 1;
                        info->ri_name_size = 0;
                        data = info->ri_name;

                        const char* name = item->Name();
                        if (name && name[0] != '\0') {
                                uint32 nameLen = strlen(name);
                                memcpy(info->ri_name, name, nameLen + 1);
                                data = skip_bytes(data, nameLen + 1);
                                info->ri_name_size = nameLen + 1;
                        }
                }
                // separator
                resource_info_separator* separator = (resource_info_separator*)data;
                separator->ris_value1 = 0xffffffff;
                separator->ris_value2 = 0xffffffff;
                // table end
                data = skip_bytes(data, kResourceInfoSeparatorSize);
                resource_info_table_end* tableEnd = (resource_info_table_end*)data;
                tableEnd->rite_check_sum = calculate_checksum(buffer,
                        infoTableSize - kResourceInfoTableEndSize);
                tableEnd->rite_terminator = 0;
                write_exactly(fFile, infoTableOffset, buffer, infoTableSize,
                        "Failed to write info table.");
        } catch (Exception& exception) {
                if (exception.Error() != B_OK)
                        error = exception.Error();
                else
                        error = B_ERROR;
        }
        delete[] buffer;
        return error;
}


status_t
ResourceFile::_MakeEmptyResourceFile()
{
        status_t error = fFile.InitCheck();
        if (error == B_OK && !fFile.File()->IsWritable())
                error = B_NOT_ALLOWED;
        if (error == B_OK) {
                try {
                        BFile* file = fFile.File();
                        // make it an x86 resource file
                        error = file->SetSize(4);
                        if (error != B_OK)
                                throw Exception(error, "Failed to set file size.");
                        write_exactly(*file, 0, kX86ResourceFileMagic, 4,
                                "Failed to write magic number.");
                        fHostEndianess = B_HOST_IS_LENDIAN;
                        fFileType = FILE_TYPE_X86_RESOURCE;
                        fFile.SetTo(file, kX86ResourcesOffset);
                        fEmptyResources = true;
                } catch (Exception& exception) {
                        if (exception.Error() != B_OK)
                                error = exception.Error();
                        else
                                error = B_ERROR;
                }
        }
        return error;
}


};      // namespace Storage
};      // namespace BPrivate