/**
 * Indicates whether the specified data block is superseded by the just-read
 * disk data block.  A data block supersedes another if its ID is equal and its
 * sequence number is greater than that of the other block.
 *
 * @param out_should_replace    On success, 0 or 1 gets written here, to
 *                                  indicate whether replacement should occur.
 * @param old_block             The data block which has already been read and
 *                                  converted to its RAM representation.  This
 *                                  is the block that may be superseded.
 * @param disk_block            The disk data block that was just read from
 *                                  flash.  This is the block which may
 *                                  supersede the other.
 *
 * @return                      0 on success; nonzero on failure.
 */
static int
nffs_restore_block_gets_replaced(const struct nffs_block *old_block,
                                 const struct nffs_disk_block *disk_block,
                                 int *out_should_replace)
{
    assert(old_block->nb_hash_entry->nhe_id == disk_block->ndb_id);

    if (nffs_block_is_dummy(old_block->nb_hash_entry)) {
        assert(0);
        *out_should_replace = 1;
        return 0;
    }

    if (old_block->nb_seq < disk_block->ndb_seq) {
        *out_should_replace = 2;
        return 0;
    }

    if (old_block->nb_seq == disk_block->ndb_seq) {
        /* This is a duplicate of an previously-read block.  This should never
         * happen.
         */
        return FS_EEXIST;
    }

    *out_should_replace = 0;
    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;
}
/**
 * Populates the nffs RAM state with the memory representation of the specified
 * disk data block.
 *
 * @param disk_block            The source disk block to insert.
 * @param area_idx              The ID of the area containing the block.
 * @param area_offset           The area_offset within the area of the block.
 *
 * @return                      0 on success; nonzero on failure.
 */
static int
nffs_restore_block(const struct nffs_disk_block *disk_block, uint8_t area_idx,
                   uint32_t area_offset)
{
    struct nffs_inode_entry *inode_entry;
    struct nffs_hash_entry *entry;
    struct nffs_block block;
    int do_replace;
    int new_block;
    int rc;

    new_block = 0;

    /* Check the block's CRC.  If the block is corrupt, discard it.  If this
     * block would have superseded another, the old block becomes current.
     */
    rc = nffs_crc_disk_block_validate(disk_block, area_idx, area_offset);
    if (rc != 0) {
        goto err;
    }

    entry = nffs_hash_find_block(disk_block->ndb_id);
    if (entry != NULL) {

        rc = nffs_block_from_hash_entry_no_ptrs(&block, entry);
        if (rc != 0 && rc != FS_ENOENT) {
            goto err;
        }

        /*
         * If the old block reference is for a 'dummy' block, it was added
         * because the owning inode's lastblock was not yet restored.
         * Update the block hash entry and inode to reference the entry.
         */
        if (nffs_block_is_dummy(entry)) {

            assert(entry->nhe_id == disk_block->ndb_id);

            /*
             * Entry is no longer dummy as it references the correct location
             */
            entry->nhe_flash_loc = nffs_flash_loc(area_idx, area_offset);

            inode_entry = nffs_hash_find_inode(disk_block->ndb_inode_id);

            /*
             * Turn off flags in previously restored inode recording the
             * allocation of a dummy block
             */
            if (inode_entry) {
                nffs_inode_unsetflags(inode_entry, NFFS_INODE_FLAG_DUMMYLSTBLK);
            }
        }

        rc = nffs_restore_block_gets_replaced(&block, disk_block,
                                              &do_replace);
        if (rc != 0) {
            goto err;
        }

        if (!do_replace) {
            /* The new block is superseded by the old; nothing to do. */
            return 0;
        }

        /*
         * update the existing hash entry to reference the new flash location
         */
        entry->nhe_flash_loc = nffs_flash_loc(area_idx, area_offset);

    } else {
        entry = nffs_block_entry_alloc();
        if (entry == NULL) {
            rc = FS_ENOMEM;
            goto err;
        }
        new_block = 1;
        entry->nhe_id = disk_block->ndb_id;
        entry->nhe_flash_loc = nffs_flash_loc(area_idx, area_offset);

        /* The block is ready to be inserted into the hash. */

        nffs_hash_insert(entry);

        if (disk_block->ndb_id >= nffs_hash_next_block_id) {
            nffs_hash_next_block_id = disk_block->ndb_id + 1;
        }
    }

    /* Make sure the maximum block data size is not set lower than the size of
     * an existing block.
     */
    if (disk_block->ndb_data_len > nffs_restore_largest_block_data_len) {
        nffs_restore_largest_block_data_len = disk_block->ndb_data_len;
    }

    NFFS_LOG(DEBUG, "restoring block; id=0x%08x seq=%u inode_id=%u prev_id=%u "
             "data_len=%u\n",
             (unsigned int)disk_block->ndb_id,
             (unsigned int)disk_block->ndb_seq,
             (unsigned int)disk_block->ndb_inode_id,
             (unsigned int)disk_block->ndb_prev_id,
             disk_block->ndb_data_len);

    inode_entry = nffs_hash_find_inode(disk_block->ndb_inode_id);

    if (inode_entry == NULL) {
        /*
         * Owning inode not yet restored.
         * Allocate a dummy inode which temporarily owns this block.
         * It is not yet linked to a parent.
         */
        rc = nffs_restore_dummy_inode(disk_block->ndb_inode_id, &inode_entry);
        if (rc != 0) {
            goto err;
        }
        /*
         * Record that this inode was created because a block was restored
         * before the inode
         */
        nffs_inode_setflags(inode_entry, NFFS_INODE_FLAG_DUMMYINOBLK);
        inode_entry->nie_last_block_entry = entry;
    } else {
        if (nffs_inode_getflags(inode_entry, NFFS_INODE_FLAG_DELETED)) {
            /*
             * don't restore blocks for deleted inodes
             */
            rc = FS_ENOENT;
            goto err;
        }
    }

    return 0;

err:
    if (new_block) {
        nffs_hash_remove(entry);
        nffs_block_entry_free(entry);
    }
    return rc;
}