/**
 * Turns a scratch area into a non-scratch area.  If the specified area is not
 * actually a scratch area, this function falls back to a slower full format
 * operation.
 */
int
nffs_format_from_scratch_area(uint8_t area_idx, uint8_t area_id)
{
    struct nffs_disk_area disk_area;
    int rc;

    assert(area_idx < nffs_num_areas);
    rc = nffs_flash_read(area_idx, 0, &disk_area, sizeof disk_area);
    if (rc != 0) {
        return rc;
    }

    nffs_areas[area_idx].na_id = area_id;
    if (!nffs_area_is_scratch(&disk_area)) {
        rc = nffs_format_area(area_idx, 0);
        if (rc != 0) {
            return rc;
        }
    } else {
        disk_area.nda_id = area_id;
        rc = nffs_flash_write(area_idx, NFFS_AREA_OFFSET_ID,
                             &disk_area.nda_id, sizeof disk_area.nda_id);
        if (rc != 0) {
            return rc;
        }
    }

    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.
 *
 * 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;
}
/**
 * Erases all the specified areas and initializes them with a clean nffs
 * file system.
 *
 * @param area_descs        The set of areas to format.
 *
 * @return                  0 on success;
 *                          nonzero on failure.
 */
int
nffs_format_full(const struct nffs_area_desc *area_descs)
{
    int rc;
    int i;

    /* Start from a clean state. */
    nffs_misc_reset();

    /* Select largest area to be the initial scratch area. */
    nffs_scratch_area_idx = 0;
    for (i = 1; area_descs[i].nad_length != 0; i++) {
        if (i >= NFFS_MAX_AREAS) {
            rc = FS_EINVAL;
            goto err;
        }

        if (area_descs[i].nad_length >
            area_descs[nffs_scratch_area_idx].nad_length) {

            nffs_scratch_area_idx = i;
        }
    }

    rc = nffs_misc_set_num_areas(i);
    if (rc != 0) {
        goto err;
    }

    for (i = 0; i < nffs_num_areas; i++) {
        nffs_areas[i].na_offset = area_descs[i].nad_offset;
        nffs_areas[i].na_length = area_descs[i].nad_length;
        nffs_areas[i].na_flash_id = area_descs[i].nad_flash_id;
        nffs_areas[i].na_cur = 0;
        nffs_areas[i].na_gc_seq = 0;

        if (i == nffs_scratch_area_idx) {
            nffs_areas[i].na_id = NFFS_AREA_ID_NONE;
        } else {
            nffs_areas[i].na_id = i;
        }

        rc = nffs_format_area(i, i == nffs_scratch_area_idx);
        if (rc != 0) {
            goto err;
        }
    }

    rc = nffs_misc_validate_scratch();
    if (rc != 0) {
        goto err;
    }

    /* Create root directory. */
    rc = nffs_file_new(NULL, "", 0, 1, &nffs_root_dir);
    if (rc != 0) {
        goto err;
    }

    /* Create "lost+found" directory. */
    rc = nffs_misc_create_lost_found_dir();
    if (rc != 0) {
        goto err;
    }

    rc = nffs_misc_validate_root_dir();
    if (rc != 0) {
        goto err;
    }

    rc = nffs_misc_set_max_block_data_len(0);
    if (rc != 0) {
        goto err;
    }

    return 0;

err:
    nffs_misc_reset();
    return rc;
}
/**
 * Repairs the effects of a corrupt scratch area.  Scratch area corruption can
 * occur when the system resets while a garbage collection cycle is in
 * progress.
 *
 * @return                      0 on success; nonzero on failure.
 */
static int
nffs_restore_corrupt_scratch(void)
{
    struct nffs_inode_entry *inode_entry;
    struct nffs_hash_entry *entry;
    struct nffs_hash_entry *next;
    uint32_t area_offset;
    uint16_t good_idx;
    uint16_t bad_idx;
    uint8_t area_idx;
    int rc;
    int i;

    /* Search for a pair of areas with identical IDs.  If found, these areas
     * represent the source and destination areas of a garbage collection
     * cycle.  The shorter of the two areas was the destination area.  Since
     * the garbage collection cycle did not finish, the source area contains a
     * more complete set of objects than the destination area.
     *
     * good_idx = index of source area.
     * bad_idx  = index of destination area; this will be turned into the
     *            scratch area.
     */
    rc = nffs_area_find_corrupt_scratch(&good_idx, &bad_idx);
    if (rc != 0) {
        return rc;
    }

    /* Invalidate all objects resident in the bad area. */
    for (i = 0; i < NFFS_HASH_SIZE; i++) {
        entry = SLIST_FIRST(&nffs_hash[i]);
        while (entry != NULL) {
            next = SLIST_NEXT(entry, nhe_next);

            nffs_flash_loc_expand(entry->nhe_flash_loc,
                                 &area_idx, &area_offset);
            if (area_idx == bad_idx) {
                if (nffs_hash_id_is_block(entry->nhe_id)) {
                    rc = nffs_block_delete_from_ram(entry);
                    if (rc != 0) {
                        return rc;
                    }
                } else {
                    inode_entry = (struct nffs_inode_entry *)entry;
                    inode_entry->nie_refcnt = 0;
                }
            }

            entry = next;
        }
    }

    /* Now that the objects in the scratch area have been invalidated, reload
     * everything from the good area.
     */
    rc = nffs_restore_area_contents(good_idx);
    if (rc != 0) {
        return rc;
    }

    /* Convert the bad area into a scratch area. */
    rc = nffs_format_area(bad_idx, 1);
    if (rc != 0) {
        return rc;
    }
    nffs_scratch_area_idx = bad_idx;

    return 0;
}