static int
nffs_block_from_disk(struct nffs_block *out_block,
                     const struct nffs_disk_block *disk_block,
                     uint8_t area_idx, uint32_t area_offset)
{
    nffs_block_from_disk_no_ptrs(out_block, disk_block);

    out_block->nb_inode_entry = nffs_hash_find_inode(disk_block->ndb_inode_id);
    if (out_block->nb_inode_entry == NULL) {
        return FS_ECORRUPT;
    }

    if (disk_block->ndb_prev_id != NFFS_ID_NONE) {
        out_block->nb_prev = nffs_hash_find_block(disk_block->ndb_prev_id);
        if (out_block->nb_prev == NULL) {
            return FS_ECORRUPT;
        }
    }

    return 0;
}
/**
 * Constructs a block representation from a disk record.  If the disk block
 * 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             The resulting block is written here (regardless
 *                                  of this function's return code).
 * @param disk_block            The source disk record to convert.
 *
 * @return                      0 if the block was successfully constructed;
 *                              FS_ECORRUPT if one or more pointers could not
 *                                  be filled in due to file system corruption.
 */
static int
nffs_block_from_disk(struct nffs_block *out_block,
                     const struct nffs_disk_block *disk_block)
{
    int rc;

    rc = 0;

    nffs_block_from_disk_no_ptrs(out_block, disk_block);

    out_block->nb_inode_entry = nffs_hash_find_inode(disk_block->ndb_inode_id);
    if (out_block->nb_inode_entry == NULL) {
        rc = FS_ECORRUPT;
    }

    if (disk_block->ndb_prev_id != NFFS_ID_NONE) {
        out_block->nb_prev = nffs_hash_find_block(disk_block->ndb_prev_id);
        if (out_block->nb_prev == NULL) {
            rc = FS_ECORRUPT;
        }
    }

    return rc;
}
/**
 * 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) {
            goto err;
        }

        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;
        }

        nffs_block_delete_from_ram(entry);
    }

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

    inode_entry = nffs_hash_find_inode(disk_block->ndb_inode_id);
    if (inode_entry == NULL) {
        rc = nffs_restore_dummy_inode(disk_block->ndb_inode_id, &inode_entry);
        if (rc != 0) {
            goto err;
        }
    }

    if (inode_entry->nie_last_block_entry == NULL ||
        inode_entry->nie_last_block_entry->nhe_id == disk_block->ndb_prev_id) {

        inode_entry->nie_last_block_entry = entry;
    }

    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;
    }

    return 0;

err:
    if (new_block) {
        nffs_block_entry_free(entry);
    }
    return rc;
}
/**
 * Determines if the specified inode should be added to the RAM representation
 * and adds it if appropriate.
 *
 * @param disk_inode            The inode just read from flash.
 * @param area_idx              The index of the area containing the inode.
 * @param area_offset           The offset within the area of the inode.
 *
 * @return                      0 on success; nonzero on failure.
 */
static int
nffs_restore_inode(const struct nffs_disk_inode *disk_inode, uint8_t area_idx,
                   uint32_t area_offset)
{
    struct nffs_inode_entry *inode_entry;
    struct nffs_inode_entry *parent;
    struct nffs_inode inode;
    int new_inode;
    int do_add;
    int rc;

    new_inode = 0;

    /* Check the inode's CRC.  If the inode is corrupt, discard it. */
    rc = nffs_crc_disk_inode_validate(disk_inode, area_idx, area_offset);
    if (rc != 0) {
        goto err;
    }

    inode_entry = nffs_hash_find_inode(disk_inode->ndi_id);
    if (inode_entry != NULL) {
        rc = nffs_restore_inode_gets_replaced(inode_entry, disk_inode,
                                              &do_add);
        if (rc != 0) {
            goto err;
        }

        if (do_add) {
            if (inode_entry->nie_hash_entry.nhe_flash_loc !=
                NFFS_FLASH_LOC_NONE) {

                rc = nffs_inode_from_entry(&inode, inode_entry);
                if (rc != 0) {
                    return rc;
                }
                if (inode.ni_parent != NULL) {
                    nffs_inode_remove_child(&inode);
                }
            }
 
            inode_entry->nie_hash_entry.nhe_flash_loc =
                nffs_flash_loc(area_idx, area_offset);
        }
    } else {
        inode_entry = nffs_inode_entry_alloc();
        if (inode_entry == NULL) {
            rc = FS_ENOMEM;
            goto err;
        }
        new_inode = 1;
        do_add = 1;

        inode_entry->nie_hash_entry.nhe_id = disk_inode->ndi_id;
        inode_entry->nie_hash_entry.nhe_flash_loc =
            nffs_flash_loc(area_idx, area_offset);

        nffs_hash_insert(&inode_entry->nie_hash_entry);
    }

    if (do_add) {
        inode_entry->nie_refcnt = 1;

        if (disk_inode->ndi_parent_id != NFFS_ID_NONE) {
            parent = nffs_hash_find_inode(disk_inode->ndi_parent_id);
            if (parent == NULL) {
                rc = nffs_restore_dummy_inode(disk_inode->ndi_parent_id,
                                             &parent);
                if (rc != 0) {
                    goto err;
                }
            }

            rc = nffs_inode_add_child(parent, inode_entry);
            if (rc != 0) {
                goto err;
            }
        } 


        if (inode_entry->nie_hash_entry.nhe_id == NFFS_ID_ROOT_DIR) {
            nffs_root_dir = inode_entry;
        }
    }

    if (nffs_hash_id_is_file(inode_entry->nie_hash_entry.nhe_id)) {
        if (inode_entry->nie_hash_entry.nhe_id >= nffs_hash_next_file_id) {
            nffs_hash_next_file_id = inode_entry->nie_hash_entry.nhe_id + 1;
        }
    } else {
        if (inode_entry->nie_hash_entry.nhe_id >= nffs_hash_next_dir_id) {
            nffs_hash_next_dir_id = inode_entry->nie_hash_entry.nhe_id + 1;
        }
    }

    return 0;

err:
    if (new_inode) {
        nffs_inode_entry_free(inode_entry);
    }
    return rc;
}
/**
 * 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;
}
/**
 * Determines if the specified inode should be added to the RAM representation
 * and adds it if appropriate.
 *
 * @param disk_inode            The inode just read from flash.
 * @param area_idx              The index of the area containing the inode.
 * @param area_offset           The offset within the area of the inode.
 *
 * @return                      0 on success; nonzero on failure.
 */
static int
nffs_restore_inode(const struct nffs_disk_inode *disk_inode, uint8_t area_idx,
                   uint32_t area_offset)
{
    struct nffs_inode_entry *inode_entry;
    struct nffs_inode_entry *parent;
    struct nffs_inode inode;
    struct nffs_hash_entry *lastblock_entry = NULL;
    int new_inode;
    int do_add;
    int rc;

    new_inode = 0;

    /* Check the inode's CRC.  If the inode is corrupt, discard it. */
    rc = nffs_crc_disk_inode_validate(disk_inode, area_idx, area_offset);
    if (rc != 0) {
        goto err;
    }

    inode_entry = nffs_hash_find_inode(disk_inode->ndi_id);

    /*
     * Inode has already been restored. Determine whether this version
     * from disk should replace the previous version referenced in RAM.
     */
    if (inode_entry != NULL) {

        if (disk_inode->ndi_flags & NFFS_INODE_FLAG_DELETED) {
            /*
             * Restore this inode even though deleted on disk
             * so the additional restored blocks have a place to go
             */
            NFFS_LOG(DEBUG, "restoring deleted inode %x\n",
                     (unsigned int)disk_inode->ndi_id);
            nffs_inode_setflags(inode_entry, NFFS_INODE_FLAG_DELETED);
        }

        /*
         * inodes get replaced if they're dummy entries (i.e. allocated
         * as place holders until the actual inode is restored), or this is
         * a more recent version of the inode which supercedes the old.
         */
        rc = nffs_restore_inode_gets_replaced(inode_entry, disk_inode, &do_add);
        if (rc != 0) {
            goto err;
        }

        if (do_add) { /* replace in this case */
            if (!nffs_inode_is_dummy(inode_entry)) {
                /*
                 * if it's not a dummy, read block from flash
                 */
                rc = nffs_inode_from_entry(&inode, inode_entry);
                if (rc != 0) {
                    return rc;
                }

                /*
                 * inode is known to be obsolete
                 */
                if (nffs_inode_getflags(inode_entry, 
                                        NFFS_INODE_FLAG_OBSOLETE)) {
                    nffs_inode_unsetflags(inode_entry,
                                          NFFS_INODE_FLAG_OBSOLETE);
                }

                if (inode.ni_parent != NULL) {
                    nffs_inode_remove_child(&inode);
                }

                /*
                 * If this is a delete record, subsequent inode and restore
                 * records from flash may be ignored.
                 * If the parent is NULL, this inode has been deleted. (old)
                 * XXX if we could count on delete records for every inode,
                 * we wouldn't need to check for the root directory looking
                 * like a delete record because of it's parent ID.
                 */
                if (inode_entry->nie_hash_entry.nhe_id != NFFS_ID_ROOT_DIR) {
                    if (disk_inode->ndi_flags & NFFS_INODE_FLAG_DELETED ||
                        disk_inode->ndi_parent_id == NFFS_ID_NONE) {

                        nffs_inode_setflags(inode_entry,
                                            NFFS_INODE_FLAG_DELETED);
                    }
                }

            } else {
                /*
                 * The existing inode entry was added as dummy.
                 * The restore operation clears that state.
                 */

                /* If it's a directory, it was added as a parent to
                 * one of it's children who were restored first.
                 */
                if (nffs_inode_getflags(inode_entry, 
                                         NFFS_INODE_FLAG_DUMMYPARENT)) {
                    assert(nffs_hash_id_is_dir(inode_entry->nie_hash_entry.nhe_id));
                    nffs_inode_unsetflags(inode_entry, 
                                         NFFS_INODE_FLAG_DUMMYPARENT);
                }

                /*
                 * If it's a file, it was added to store a lastblock
                 */
                if (nffs_inode_getflags(inode_entry, 
                                         NFFS_INODE_FLAG_DUMMYINOBLK)) {
                    assert(nffs_hash_id_is_file(inode_entry->nie_hash_entry.nhe_id));
                    nffs_inode_unsetflags(inode_entry, 
                                         NFFS_INODE_FLAG_DUMMYINOBLK);
                }

                /*
                 * Also, since it's a dummy, clear this flag too
                 */
                if (nffs_inode_getflags(inode_entry, NFFS_INODE_FLAG_DUMMY)) {
                    nffs_inode_unsetflags(inode_entry, NFFS_INODE_FLAG_DUMMY);
                }
            }
 
            /*
             * Update location to reference new location in flash
             */
            inode_entry->nie_hash_entry.nhe_flash_loc =
                                    nffs_flash_loc(area_idx, area_offset);
        }
        
    } else {
        inode_entry = nffs_inode_entry_alloc();
        if (inode_entry == NULL) {
            rc = FS_ENOMEM;
            goto err;
        }
        new_inode = 1;
        do_add = 1;

        inode_entry->nie_hash_entry.nhe_id = disk_inode->ndi_id;
        inode_entry->nie_hash_entry.nhe_flash_loc =
                              nffs_flash_loc(area_idx, area_offset);
        inode_entry->nie_last_block_entry = NULL; /* for now */

        nffs_hash_insert(&inode_entry->nie_hash_entry);
    }

    /*
     * inode object has been restored and the entry is in the hash
     * Check whether the lastblock and parent have also been restored
     * and link up or allocate dummy entries as appropriate.
     */
    if (do_add) {
        inode_entry->nie_refcnt = 1;

        if (disk_inode->ndi_flags & NFFS_INODE_FLAG_DELETED) {
            /*
             * Restore this inode even though deleted on disk
             * so the additional restored blocks have a place to go
             */
            NFFS_LOG(DEBUG, "restoring deleted inode %x\n",
                     (unsigned int)disk_inode->ndi_id);
            nffs_inode_setflags(inode_entry, NFFS_INODE_FLAG_DELETED);
        }

        /*
         * Inode has a lastblock on disk.
         * Add reference to last block entry if in hash table
         * otherwise add a dummy block entry for later update
         */
        if (disk_inode->ndi_lastblock_id != NFFS_ID_NONE &&
                nffs_hash_id_is_file(inode_entry->nie_hash_entry.nhe_id)) {
            lastblock_entry =
              nffs_hash_find_block(disk_inode->ndi_lastblock_id);

            /*
             * Lastblock has already been restored.
             */
            if (lastblock_entry != NULL) {
                if (lastblock_entry->nhe_id == disk_inode->ndi_lastblock_id) {
                    inode_entry->nie_last_block_entry = lastblock_entry;
                }

            } else {
                /*
                 * Insert a temporary reference to a 'dummy' block entry
                 * When block is restored, it will update this dummy and
                 * the entry of this inode is updated to flash location
                 */
                rc = nffs_block_entry_reserve(&lastblock_entry);
                if (lastblock_entry == NULL) {
                    rc = FS_ENOMEM;
                    goto err;
                }

                lastblock_entry->nhe_id = disk_inode->ndi_lastblock_id;
                lastblock_entry->nhe_flash_loc = NFFS_FLASH_LOC_NONE;
                inode_entry->nie_last_block_entry = lastblock_entry;
                nffs_inode_setflags(inode_entry, NFFS_INODE_FLAG_DUMMYLSTBLK);
                nffs_hash_insert(lastblock_entry);

                if (lastblock_entry->nhe_id >= nffs_hash_next_block_id) {
                    nffs_hash_next_block_id = lastblock_entry->nhe_id + 1;
                }
            }
        }

        if (disk_inode->ndi_parent_id != NFFS_ID_NONE) {
            
            parent = nffs_hash_find_inode(disk_inode->ndi_parent_id);
            /*
             * The parent directory for this inode hasn't been restored yet.
             * Add a dummy directory so it can be added as a child.
             * When the parent inode is restored, it's hash entry will be
             * updated with the flash location.
             */
            if (parent == NULL) {
                rc = nffs_restore_dummy_inode(disk_inode->ndi_parent_id,
                                             &parent);
                /*
                 * Set the dummy parent flag in the new parent.
                 * It's turned off above when restored.
                 */
                nffs_inode_setflags(parent, NFFS_INODE_FLAG_DUMMYPARENT);
                if (rc != 0) {
                    goto err;
                }
            }

            rc = nffs_inode_add_child(parent, inode_entry);
            if (rc != 0) {
                goto err;
            }
        } 

        if (inode_entry->nie_hash_entry.nhe_id == NFFS_ID_ROOT_DIR) {
            nffs_root_dir = inode_entry;
            nffs_inode_setflags(nffs_root_dir, NFFS_INODE_FLAG_INTREE);
        }
    }

    if (nffs_hash_id_is_file(inode_entry->nie_hash_entry.nhe_id)) {
        NFFS_LOG(DEBUG, "restoring file; id=0x%08x\n",
                 (unsigned int)inode_entry->nie_hash_entry.nhe_id);
        if (inode_entry->nie_hash_entry.nhe_id >= nffs_hash_next_file_id) {
            nffs_hash_next_file_id = inode_entry->nie_hash_entry.nhe_id + 1;
        }
    } else {
        NFFS_LOG(DEBUG, "restoring dir; id=0x%08x\n",
                 (unsigned int)inode_entry->nie_hash_entry.nhe_id);
        if (inode_entry->nie_hash_entry.nhe_id >= nffs_hash_next_dir_id) {
            nffs_hash_next_dir_id = inode_entry->nie_hash_entry.nhe_id + 1;
        }
    }

    return 0;

err:
    if (new_inode) {
        assert(nffs_inode_getflags(inode_entry, NFFS_INODE_FLAG_INHASH));
        nffs_hash_remove(&inode_entry->nie_hash_entry);
        nffs_inode_entry_free(inode_entry);
    }
    return rc;
}