/**
 * 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)
{
    int rc;

    rc = nffs_flash_read(area_idx, area_offset,
                         &out_disk_object->ndo_un_obj,
                         sizeof(out_disk_object->ndo_un_obj));
    if (rc != 0) {
        return rc;
    }
    STATS_INC(nffs_stats, nffs_readcnt_object);

    if (nffs_hash_id_is_inode(out_disk_object->ndo_disk_inode.ndi_id)) {
        out_disk_object->ndo_type = NFFS_OBJECT_TYPE_INODE;

    } else if (nffs_hash_id_is_block(out_disk_object->ndo_disk_block.ndb_id)) {
        out_disk_object->ndo_type = NFFS_OBJECT_TYPE_BLOCK;

    } else if (out_disk_object->ndo_disk_block.ndb_id == NFFS_ID_NONE) {
        return FS_EEMPTY;

    } else {
        return FS_ECORRUPT;
    }

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

    return 0;
}
Esempio n. 2
0
/**
 * Triggers a garbage collection cycle.  This is implemented as follows:
 *
 *  (1) The non-scratch area with the lowest garbage collection sequence
 *      number is selected as the "source area."  If there are other areas
 *      with the same sequence number, the first one encountered is selected.
 *
 *  (2) The source area's ID is written to the scratch area's header,
 *      transforming it into a non-scratch ID.  The former scratch area is now
 *      known as the "destination area."
 *
 *  (3) The RAM representation is exhaustively searched for objects which are
 *      resident in the source area.  The copy is accomplished as follows:
 *
 *      For each inode:
 *          (a) If the inode is resident in the source area, copy the inode
 *              record to the destination area.
 *
 *          (b) Walk the inode's list of data blocks, starting with the last
 *              block in the file.  Each block that is resident in the source
 *              area is copied to the destination area.  If there is a run of
 *              two or more blocks that are resident in the source area, they
 *              are consolidated and copied to the destination area as a single
 *              new block.
 *
 *  (4) The source area is reformatted as a scratch sector (i.e., its header
 *      indicates an ID of 0xffff).  The area's garbage collection sequence
 *      number is incremented prior to rewriting the header.  This area is now
 *      the new scratch sector.
 *
 * NOTE:
 *     Garbage collection invalidates all cached data blocks.  Whenever this
 *     function is called, all existing nffs_cache_block pointers are rendered
 *     invalid.  If you maintain any such pointers, you need to reset them
 *     after calling this function.  Cached inodes are not invalidated by
 *     garbage collection.
 *
 *     If a parent function potentially calls this function, the caller of the
 *     parent function needs to explicitly check if garbage collection
 *     occurred.  This is done by inspecting the nffs_gc_count variable before
 *     and after calling the function.
 *
 * @param out_area_idx      On success, the ID of the cleaned up area gets
 *                              written here.  Pass null if you do not need
 *                              this information.
 *
 * @return                  0 on success; nonzero on error.
 */
int
nffs_gc(uint8_t *out_area_idx)
{
    struct nffs_hash_entry *entry;
    struct nffs_hash_entry *next;
    struct nffs_area *from_area;
    struct nffs_area *to_area;
    struct nffs_inode_entry *inode_entry;
    uint32_t area_offset;
    uint8_t from_area_idx;
    uint8_t area_idx;
    int rc;
    int i;

    from_area_idx = nffs_gc_select_area();
    from_area = nffs_areas + from_area_idx;
    to_area = nffs_areas + nffs_scratch_area_idx;

    rc = nffs_format_from_scratch_area(nffs_scratch_area_idx,
                                       from_area->na_id);
    if (rc != 0) {
        return rc;
    }

    for (i = 0; i < NFFS_HASH_SIZE; i++) {
        entry = SLIST_FIRST(nffs_hash + i);
        while (entry != NULL) {
            next = SLIST_NEXT(entry, nhe_next);

            if (nffs_hash_id_is_inode(entry->nhe_id)) {
                /* The inode gets copied if it is in the source area. */
                nffs_flash_loc_expand(entry->nhe_flash_loc,
                                      &area_idx, &area_offset);
                inode_entry = (struct nffs_inode_entry *)entry;
                if (area_idx == from_area_idx) {
                    rc = nffs_gc_copy_inode(inode_entry,
                                            nffs_scratch_area_idx);
                    if (rc != 0) {
                        return rc;
                    }
                }

                /* If the inode is a file, all constituent data blocks that are
                 * resident in the source area get copied.
                 */
                if (nffs_hash_id_is_file(entry->nhe_id)) {
                    rc = nffs_gc_inode_blocks(inode_entry, from_area_idx,
                                              nffs_scratch_area_idx, &next);
                    if (rc != 0) {
                        return rc;
                    }
                }
            }

            entry = next;
        }
    }

    /* The amount of written data should never increase as a result of a gc
     * cycle.
     */
    assert(to_area->na_cur <= from_area->na_cur);

    /* Turn the source area into the new scratch area. */
    from_area->na_gc_seq++;
    rc = nffs_format_area(from_area_idx, 1);
    if (rc != 0) {
        return rc;
    }

    if (out_area_idx != NULL) {
        *out_area_idx = nffs_scratch_area_idx;
    }

    nffs_scratch_area_idx = from_area_idx;

    /* Garbage collection renders the cache invalid:
     *     o All cached blocks are now invalid; drop them.
     *     o Flash locations of inodes may have changed; the cached inodes need
     *       updated to reflect this.
     */
    rc = nffs_cache_inode_refresh();
    if (rc != 0) {
        return rc;
    }

    /* Increment the garbage collection counter so that client code knows to
     * reset its pointers to cached objects.
     */
    nffs_gc_count++;
    STATS_INC(nffs_stats, nffs_gccnt);

    return 0;
}
/**
 * Triggers a garbage collection cycle.  This is implemented as follows:
 *
 *  (1) The non-scratch area with the lowest garbage collection sequence
 *      number is selected as the "source area."  If there are other areas
 *      with the same sequence number, the first one encountered is selected.
 *
 *  (2) The source area's ID is written to the scratch area's header,
 *      transforming it into a non-scratch ID.  The former scratch area is now
 *      known as the "destination area."
 *
 *  (3) The RAM representation is exhaustively searched for objects which are
 *      resident in the source area.  The copy is accomplished as follows:
 *
 *      For each inode:
 *          (a) If the inode is resident in the source area, copy the inode
 *              record to the destination area.
 *
 *          (b) Walk the inode's list of data blocks, starting with the last
 *              block in the file.  Each block that is resident in the source
 *              area is copied to the destination area.  If there is a run of
 *              two or more blocks that are resident in the source area, they
 *              are consolidated and copied to the destination area as a single
 *              new block.
 *
 *  (4) The source area is reformatted as a scratch sector (i.e., its header
 *      indicates an ID of 0xffff).  The area's garbage collection sequence
 *      number is incremented prior to rewriting the header.  This area is now
 *      the new scratch sector.
 *
 * @param out_area_idx      On success, the ID of the cleaned up area gets
 *                              written here.  Pass null if you do not need
 *                              this information.
 *
 * @return                  0 on success; nonzero on error.
 */
int
nffs_gc(uint8_t *out_area_idx)
{
    struct nffs_hash_entry *entry;
    struct nffs_hash_entry *next;
    struct nffs_area *from_area;
    struct nffs_area *to_area;
    struct nffs_inode_entry *inode_entry;
    uint32_t area_offset;
    uint8_t from_area_idx;
    uint8_t area_idx;
    int rc;
    int i;

    from_area_idx = nffs_gc_select_area();
    from_area = nffs_areas + from_area_idx;
    to_area = nffs_areas + nffs_scratch_area_idx;

    rc = nffs_format_from_scratch_area(nffs_scratch_area_idx,
                                       from_area->na_id);
    if (rc != 0) {
        return rc;
    }

    for (i = 0; i < NFFS_HASH_SIZE; i++) {
        entry = SLIST_FIRST(nffs_hash + i);
        while (entry != NULL) {
            next = SLIST_NEXT(entry, nhe_next);

            if (nffs_hash_id_is_inode(entry->nhe_id)) {
                /* The inode gets copied if it is in the source area. */
                nffs_flash_loc_expand(entry->nhe_flash_loc,
                                      &area_idx, &area_offset);
                inode_entry = (struct nffs_inode_entry *)entry;
                if (area_idx == from_area_idx) {
                    rc = nffs_gc_copy_inode(inode_entry,
                                            nffs_scratch_area_idx);
                    if (rc != 0) {
                        return rc;
                    }
                }

                /* If the inode is a file, all constituent data blocks that are
                 * resident in the source area get copied.
                 */
                if (nffs_hash_id_is_file(entry->nhe_id)) {
                    rc = nffs_gc_inode_blocks(inode_entry, from_area_idx,
                                              nffs_scratch_area_idx, &next);
                    if (rc != 0) {
                        return rc;
                    }
                }
            }

            entry = next;
        }
    }

    /* The amount of written data should never increase as a result of a gc
     * cycle.
     */
    assert(to_area->na_cur <= from_area->na_cur);

    /* Turn the source area into the new scratch area. */
    from_area->na_gc_seq++;
    rc = nffs_format_area(from_area_idx, 1);
    if (rc != 0) {
        return rc;
    }

    if (out_area_idx != NULL) {
        *out_area_idx = nffs_scratch_area_idx;
    }

    nffs_scratch_area_idx = from_area_idx;

    return 0;
}
/**
 * Performs a sweep of the RAM representation at the end of a successful
 * restore.  The sweep phase performs the following actions of each inode in
 * the file system:
 *     1. If the inode is a dummy directory, its children are migrated to the
 *        lost+found directory.
 *     2. Else if the inode is a dummy file, it is fully deleted from RAM.
 *     3. Else, a CRC check is performed on each of the inode's constituent
 *        blocks.  If corruption is detected, the inode is fully deleted from
 *        RAM.
 *
 * @return                      0 on success; nonzero on failure.
 */
int
nffs_restore_sweep(void)
{
    struct nffs_inode_entry *inode_entry;
    struct nffs_hash_entry *entry;
    struct nffs_hash_entry *next;
    struct nffs_hash_list *list;
    struct nffs_inode inode;
    int del;
    int rc;
    int i;

    /* Iterate through every object in the hash table, deleting all inodes that
     * should be removed.
     */
    for (i = 0; i < NFFS_HASH_SIZE; i++) {
        list = nffs_hash + i;

        entry = SLIST_FIRST(list);
        while (entry != NULL) {
            next = SLIST_NEXT(entry, nhe_next);
            if (nffs_hash_id_is_inode(entry->nhe_id)) {
                inode_entry = (struct nffs_inode_entry *)entry;

                /* If this is a dummy inode directory, the file system is
                 * corrupted.  Move the directory's children inodes to the
                 * lost+found directory.
                 */
                rc = nffs_restore_migrate_orphan_children(inode_entry);
                if (rc != 0) {
                    return rc;
                }

                /* Determine if this inode needs to be deleted. */
                rc = nffs_restore_should_sweep_inode_entry(inode_entry, &del);
                if (rc != 0) {
                    return rc;
                }

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

                        nffs_restore_inode_from_dummy_entry(&inode,
                                                           inode_entry);
                    } else {
                        rc = nffs_inode_from_entry(&inode, inode_entry);
                        if (rc != 0) {
                            return rc;
                        }
                    }

                    /* Remove the inode and all its children from RAM. */
                    rc = nffs_inode_unlink_from_ram(&inode, &next);
                    if (rc != 0) {
                        return rc;
                    }
                    next = SLIST_FIRST(list);
                }
            }

            entry = next;
        }
    }

    return 0;
}
/**
 * Performs a sweep of the RAM representation at the end of a successful
 * restore.  The sweep phase performs the following actions of each inode in
 * the file system:
 *     1. If the inode is a dummy directory, its children are migrated to the
 *        lost+found directory.
 *     2. Else if the inode is a dummy file, it is fully deleted from RAM.
 *     3. Else, a CRC check is performed on each of the inode's constituent
 *        blocks.  If corruption is detected, the inode is fully deleted from
 *        RAM.
 *
 * @return                      0 on success; nonzero on failure.
 */
int
nffs_restore_sweep(void)
{
    struct nffs_inode_entry *inode_entry;
    struct nffs_hash_entry *entry;
    struct nffs_hash_entry *next;
    struct nffs_hash_list *list;
    struct nffs_inode inode;
    struct nffs_block block;
    int del = 0;
    int rc;
    int i;

    /* Iterate through every object in the hash table, deleting all inodes that
     * should be removed.
     */
    for (i = 0; i < NFFS_HASH_SIZE; i++) {
        list = nffs_hash + i;

        entry = SLIST_FIRST(list);
        while (entry != NULL) {
            next = SLIST_NEXT(entry, nhe_next);
            if (nffs_hash_id_is_inode(entry->nhe_id)) {
                inode_entry = (struct nffs_inode_entry *)entry;

                /*
                 * If this is a dummy inode directory, the file system
                 * is corrupt.  Move the directory's children inodes to
                 * the lost+found directory.
                 */
                rc = nffs_restore_migrate_orphan_children(inode_entry);
                if (rc != 0) {
                    return rc;
                }

                /* Determine if this inode needs to be deleted. */
                rc = nffs_restore_should_sweep_inode_entry(inode_entry, &del);
                if (rc != 0) {
                    return rc;
                }

                rc = nffs_inode_from_entry(&inode, inode_entry);
                if (rc != 0 && rc != FS_ENOENT) {
                    return rc;
                }

                if (del) {

                    /* Remove the inode and all its children from RAM.  We
                     * expect some file system corruption; the children are
                     * subject to garbage collection and may not exist in the
                     * hash.  Remove what is actually present and ignore
                     * corruption errors.
                     */
                    rc = nffs_inode_unlink_from_ram_corrupt_ok(&inode, &next);
                    if (rc != 0) {
                        return rc;
                    }
                    next = SLIST_FIRST(list);
                }
            } else if (nffs_hash_id_is_block(entry->nhe_id)) {
                if (nffs_hash_id_is_dummy(entry->nhe_id)) {
                    del = 1;
                    nffs_block_delete_from_ram(entry);
                } else {
                    rc = nffs_block_from_hash_entry(&block, entry);
                    if (rc != 0 && rc != FS_ENOENT) {
                        del = 1;
                        nffs_block_delete_from_ram(entry);
                    }
                }
                if (del) {
                    del = 0;
                    next = SLIST_FIRST(list);
                }
            }

            entry = next;
        }
    }

    return 0;
}