/**
 * Constructs a full data block representation from the specified minimal block
 * entry.  The resultant block's pointers are populated via hash table lookups.
 *
 * @param out_block             On success, this gets populated with the data
 *                                  block information.
 * @param block_entry           The source block entry to convert.
 *
 * @return                      0 on success; nonzero on failure.
 */
int
nffs_block_from_hash_entry(struct nffs_block *out_block,
                           struct nffs_hash_entry *block_entry)
{
    struct nffs_disk_block disk_block;
    uint32_t area_offset;
    uint8_t area_idx;
    int rc;

    assert(nffs_hash_id_is_block(block_entry->nhe_id));

    nffs_flash_loc_expand(block_entry->nhe_flash_loc, &area_idx, &area_offset);
    rc = nffs_block_read_disk(area_idx, area_offset, &disk_block);
    if (rc != 0) {
        return rc;
    }

    out_block->nb_hash_entry = block_entry;
    rc = nffs_block_from_disk(out_block, &disk_block, area_idx, area_offset);
    if (rc != 0) {
        return rc;
    }

    return 0;
}
/**
 * Checks that each block a chain of data blocks was properly restored.
 *
 * @param last_block_entry      The entry corresponding to the last block in
 *                                  the chain.
 *
 * @return                      0 if the block chain is OK;
 *                              FS_ECORRUPT if corruption is detected;
 *                              nonzero on other error.
 */
static int
nffs_restore_validate_block_chain(struct nffs_hash_entry *last_block_entry)
{
    struct nffs_disk_block disk_block;
    struct nffs_hash_entry *cur;
    struct nffs_block block;
    uint32_t area_offset;
    uint8_t area_idx;
    int rc;

    cur = last_block_entry;

    while (cur != NULL) {
        nffs_flash_loc_expand(cur->nhe_flash_loc, &area_idx, &area_offset);

        rc = nffs_block_read_disk(area_idx, area_offset, &disk_block);
        if (rc != 0) {
            return rc;
        }

        rc = nffs_block_from_hash_entry(&block, cur);
        if (rc != 0) {
            return rc;
        }

        cur = block.nb_prev;
    }

    return 0;
}
/**
 * Constructs a full data block representation from the specified minimal
 * block entry.  However, the resultant block's pointers are set to null,
 * rather than populated via hash table lookups.  This behavior is useful when
 * the RAM representation has not been fully constructed yet.
 *
 * @param out_block             On success, this gets populated with the data
 *                                  block information.
 * @param block_entry           The source block entry to convert.
 *
 * @return                      0 on success; nonzero on failure.
 */
int
nffs_block_from_hash_entry_no_ptrs(struct nffs_block *out_block,
                                   struct nffs_hash_entry *block_entry)
{
    struct nffs_disk_block disk_block;
    uint32_t area_offset;
    uint8_t area_idx;
    int rc;

    assert(nffs_hash_id_is_block(block_entry->nhe_id));

    if (nffs_hash_entry_is_dummy(block_entry)) {
        /*
         * We can't read this from disk so we'll be missing filling in anything
         * not already in inode_entry (e.g., prev_id).
         */
        out_block->nb_hash_entry = block_entry;
        return FS_ENOENT; /* let caller know it's a partial inode_entry */
    }

    nffs_flash_loc_expand(block_entry->nhe_flash_loc, &area_idx, &area_offset);
    rc = nffs_block_read_disk(area_idx, area_offset, &disk_block);
    if (rc != 0) {
        return rc;
    }

    out_block->nb_hash_entry = block_entry;
    nffs_block_from_disk_no_ptrs(out_block, &disk_block);

    return 0;
}
/**
 * Determines if a particular block can be found in RAM by following a chain of
 * previous block pointers, starting with the specified hash entry.
 *
 * @param start                 The block entry at which to start the search.
 * @param sought_id             The ID of the block to search for.
 *
 * @return                      0 if the sought after ID was found;
 *                              FS_ENOENT if the ID was not found;
 *                              Other FS code on error.
 */
int
nffs_block_find_predecessor(struct nffs_hash_entry *start, uint32_t sought_id)
{
    struct nffs_hash_entry *entry;
    struct nffs_disk_block disk_block;
    uint32_t area_offset;
    uint8_t area_idx;
    int rc;

    entry = start;
    while (entry != NULL && entry->nhe_id != sought_id) {
        nffs_flash_loc_expand(entry->nhe_flash_loc, &area_idx, &area_offset);
        rc = nffs_block_read_disk(area_idx, area_offset, &disk_block);
        if (rc != 0) {
            return rc;
        }

        if (disk_block.ndb_prev_id == NFFS_ID_NONE) {
            entry = NULL;
        } else {
            entry = nffs_hash_find(disk_block.ndb_prev_id);
        }
    }

    if (entry == NULL) {
        rc = FS_ENOENT;
    } else {
        rc = 0;
    }

    return rc;
}
/**
 * Reads a single disk object from flash.
 *
 * @param area_idx              The area to read the object from.
 * @param area_offset           The offset within the area to read from.
 * @param out_disk_object       On success, the restored object gets written
 *                                  here.
 *
 * @return                      0 on success; nonzero on failure.
 */
static int
nffs_restore_disk_object(int area_idx, uint32_t area_offset,
                         struct nffs_disk_object *out_disk_object)
{
    uint32_t magic;
    int rc;

    rc = nffs_flash_read(area_idx, area_offset, &magic, sizeof magic);
    if (rc != 0) {
        return rc;
    }

    switch (magic) {
    case NFFS_INODE_MAGIC:
        out_disk_object->ndo_type = NFFS_OBJECT_TYPE_INODE;
        rc = nffs_inode_read_disk(area_idx, area_offset,
                                 &out_disk_object->ndo_disk_inode);
        break;

    case NFFS_BLOCK_MAGIC:
        out_disk_object->ndo_type = NFFS_OBJECT_TYPE_BLOCK;
        rc = nffs_block_read_disk(area_idx, area_offset,
                                 &out_disk_object->ndo_disk_block);
        break;

    case 0xffffffff:
        rc = FS_EEMPTY;
        break;

    default:
        rc = FS_ECORRUPT;
        break;
    }

    if (rc != 0) {
        return rc;
    }

    out_disk_object->ndo_area_idx = area_idx;
    out_disk_object->ndo_offset = area_offset;

    return 0;
}
/**
 * Constructs a block representation from a minimal block hash entry.  If the
 * hash entry references other objects (inode or previous data block), the
 * resulting block object is populated with pointers to the referenced objects.
 * If the any referenced objects are not present in the NFFS RAM
 * representation, this indicates file system corruption.  In this case, the
 * resulting block is populated with all valid references, and an FS_ECORRUPT
 * code is returned.
 *
 * @param out_block             On success, this gets populated with the data
 *                                  block information.
 * @param block_entry           The source block entry to convert.
 *
 * @return                      0 on success;
 *                              FS_ECORRUPT if one or more pointers could not
 *                                  be filled in due to file system corruption;
 *                              FS_EOFFSET on an attempt to read an invalid
 *                                  address range;
 *                              FS_EHW on flash error;
 *                              FS_EUNEXP if the specified disk location does
 *                                  not contain a block.
 */
int
nffs_block_from_hash_entry(struct nffs_block *out_block,
                           struct nffs_hash_entry *block_entry)
{
    struct nffs_disk_block disk_block;
    uint32_t area_offset;
    uint8_t area_idx;
    int rc;

    assert(nffs_hash_id_is_block(block_entry->nhe_id));

    if (nffs_block_is_dummy(block_entry)) {
        out_block->nb_hash_entry = block_entry;
        out_block->nb_inode_entry = NULL;
        out_block->nb_prev = NULL;
        /*
         * Dummy block added when inode was read in before real block
         * (see nffs_restore_inode()). Return success (because there's 
         * too many places that ned to check for this,
         * but it's the responsibility fo the upstream code to check
         * whether this is still a dummy entry.  XXX
         */
        return 0;
        /*return FS_ENOENT;*/
    }
    nffs_flash_loc_expand(block_entry->nhe_flash_loc, &area_idx, &area_offset);
    rc = nffs_block_read_disk(area_idx, area_offset, &disk_block);
    if (rc != 0) {
        return rc;
    }

    out_block->nb_hash_entry = block_entry;
    rc = nffs_block_from_disk(out_block, &disk_block);
    if (rc != 0) {
        return rc;
    }

    return 0;
}