/** * 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; }
int nffs_block_read_data(const struct nffs_block *block, uint16_t offset, uint16_t length, void *dst) { uint32_t area_offset; uint8_t area_idx; int rc; nffs_flash_loc_expand(block->nb_hash_entry->nhe_flash_loc, &area_idx, &area_offset); area_offset += sizeof (struct nffs_disk_block); area_offset += offset; rc = nffs_flash_read(area_idx, area_offset, dst, length); if (rc != 0) { return rc; } 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; }
static int nffs_gc_copy_object(struct nffs_hash_entry *entry, uint16_t object_size, uint8_t to_area_idx) { uint32_t from_area_offset; uint32_t to_area_offset; uint8_t from_area_idx; int rc; nffs_flash_loc_expand(entry->nhe_flash_loc, &from_area_idx, &from_area_offset); to_area_offset = nffs_areas[to_area_idx].na_cur; rc = nffs_flash_copy(from_area_idx, from_area_offset, to_area_idx, to_area_offset, object_size); if (rc != 0) { return rc; } entry->nhe_flash_loc = nffs_flash_loc(to_area_idx, to_area_offset); 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; }
static int nffs_gc_inode_blocks(struct nffs_inode_entry *inode_entry, uint8_t from_area_idx, uint8_t to_area_idx, struct nffs_hash_entry **inout_next) { struct nffs_hash_entry *last_entry; struct nffs_hash_entry *entry; struct nffs_block block; uint32_t prospective_data_len; uint32_t area_offset; uint32_t data_len; uint8_t area_idx; int multiple_blocks; int rc; assert(nffs_hash_id_is_file(inode_entry->nie_hash_entry.nhe_id)); data_len = 0; last_entry = NULL; multiple_blocks = 0; entry = inode_entry->nie_last_block_entry; while (entry != NULL) { rc = nffs_block_from_hash_entry(&block, entry); if (rc != 0) { return rc; } nffs_flash_loc_expand(entry->nhe_flash_loc, &area_idx, &area_offset); if (area_idx == from_area_idx) { if (last_entry == NULL) { last_entry = entry; } prospective_data_len = data_len + block.nb_data_len; if (prospective_data_len <= nffs_block_max_data_sz) { data_len = prospective_data_len; if (last_entry != entry) { multiple_blocks = 1; } } else { rc = nffs_gc_block_chain(last_entry, multiple_blocks, data_len, to_area_idx, inout_next); if (rc != 0) { return rc; } last_entry = entry; data_len = block.nb_data_len; multiple_blocks = 0; } } else { if (last_entry != NULL) { rc = nffs_gc_block_chain(last_entry, multiple_blocks, data_len, to_area_idx, inout_next); if (rc != 0) { return rc; } last_entry = NULL; data_len = 0; multiple_blocks = 0; } } entry = block.nb_prev; } if (last_entry != NULL) { rc = nffs_gc_block_chain(last_entry, multiple_blocks, data_len, to_area_idx, inout_next); if (rc != 0) { return rc; } } return 0; }
/** * Moves a chain of blocks from one area to another. This function attempts to * collate the blocks into a single new block in the destination area. * * @param last_entry The last block entry in the chain. * @param data_len The total length of data to collate. * @param to_area_idx The index of the area to copy to. * @param inout_next This parameter is only necessary if you are * calling this function during an iteration * of the entire hash table; pass null * otherwise. * On input, this points to the next hash entry * you plan on processing. * On output, this points to the next hash entry * that should be processed. * * @return 0 on success; * FS_ENOMEM if there is insufficient heap; * other nonzero on failure. */ static int nffs_gc_block_chain_collate(struct nffs_hash_entry *last_entry, uint32_t data_len, uint8_t to_area_idx, struct nffs_hash_entry **inout_next) { struct nffs_disk_block disk_block; struct nffs_hash_entry *entry; struct nffs_area *to_area; struct nffs_block last_block; struct nffs_block block; uint32_t to_area_offset; uint32_t from_area_offset; uint32_t data_offset; uint8_t *data; uint8_t from_area_idx; int rc; memset(&last_block, 0, sizeof last_block); data = malloc(data_len); if (data == NULL) { rc = FS_ENOMEM; goto done; } memset(&last_block, 0, sizeof(last_block)); to_area = nffs_areas + to_area_idx; entry = last_entry; data_offset = data_len; while (data_offset > 0) { rc = nffs_block_from_hash_entry(&block, entry); if (rc != 0) { goto done; } data_offset -= block.nb_data_len; nffs_flash_loc_expand(block.nb_hash_entry->nhe_flash_loc, &from_area_idx, &from_area_offset); from_area_offset += sizeof disk_block; STATS_INC(nffs_stats, nffs_readcnt_gccollate); rc = nffs_flash_read(from_area_idx, from_area_offset, data + data_offset, block.nb_data_len); if (rc != 0) { goto done; } if (entry != last_entry) { if (inout_next != NULL && *inout_next == entry) { *inout_next = SLIST_NEXT(entry, nhe_next); } nffs_block_delete_from_ram(entry); } else { last_block = block; } entry = block.nb_prev; } /* we had better have found the last block */ assert(last_block.nb_hash_entry); /* The resulting block should inherit its ID from its last constituent * block (this is the ID referenced by the parent inode and subsequent data * block). The previous ID gets inherited from the first constituent * block. */ memset(&disk_block, 0, sizeof disk_block); disk_block.ndb_id = last_block.nb_hash_entry->nhe_id; disk_block.ndb_seq = last_block.nb_seq + 1; disk_block.ndb_inode_id = last_block.nb_inode_entry->nie_hash_entry.nhe_id; if (entry == NULL) { disk_block.ndb_prev_id = NFFS_ID_NONE; } else { disk_block.ndb_prev_id = entry->nhe_id; } disk_block.ndb_data_len = data_len; nffs_crc_disk_block_fill(&disk_block, data); to_area_offset = to_area->na_cur; rc = nffs_flash_write(to_area_idx, to_area_offset, &disk_block, sizeof disk_block); if (rc != 0) { goto done; } rc = nffs_flash_write(to_area_idx, to_area_offset + sizeof disk_block, data, data_len); if (rc != 0) { goto done; } last_entry->nhe_flash_loc = nffs_flash_loc(to_area_idx, to_area_offset); rc = 0; ASSERT_IF_TEST(nffs_crc_disk_block_validate(&disk_block, to_area_idx, to_area_offset) == 0); done: free(data); return rc; }
/** * 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; }
/** * Moves a chain of blocks from one area to another. This function attempts to * collate the blocks into a single new block in the destination area. * * @param last_entry The last block entry in the chain. * @param data_len The total length of data to collate. * @param to_area_idx The index of the area to copy to. * @param inout_next This parameter is only necessary if you are * calling this function during an iteration * of the entire hash table; pass null * otherwise. * On input, this points to the next hash entry * you plan on processing. * On output, this points to the next hash entry * that should be processed. * * @return 0 on success; * FS_ENOMEM if there is insufficient heap; * other nonzero on failure. */ static int nffs_gc_block_chain_collate(struct nffs_hash_entry *last_entry, uint32_t data_len, uint8_t to_area_idx, struct nffs_hash_entry **inout_next) { struct nffs_disk_block disk_block; struct nffs_hash_entry *entry; struct nffs_area *to_area; struct nffs_block block; uint32_t to_area_offset; uint32_t from_area_offset; uint32_t data_offset; uint8_t *data; uint8_t from_area_idx; int rc; data = malloc(data_len); if (data == NULL) { rc = FS_ENOMEM; goto done; } to_area = nffs_areas + to_area_idx; entry = last_entry; data_offset = data_len; while (data_offset > 0) { rc = nffs_block_from_hash_entry(&block, entry); if (rc != 0) { goto done; } data_offset -= block.nb_data_len; nffs_flash_loc_expand(block.nb_hash_entry->nhe_flash_loc, &from_area_idx, &from_area_offset); from_area_offset += sizeof disk_block; rc = nffs_flash_read(from_area_idx, from_area_offset, data + data_offset, block.nb_data_len); if (rc != 0) { goto done; } if (entry != last_entry) { if (inout_next != NULL && *inout_next == entry) { *inout_next = SLIST_NEXT(entry, nhe_next); } nffs_block_delete_from_ram(entry); } entry = block.nb_prev; } memset(&disk_block, 0, sizeof disk_block); disk_block.ndb_magic = NFFS_BLOCK_MAGIC; disk_block.ndb_id = block.nb_hash_entry->nhe_id; disk_block.ndb_seq = block.nb_seq + 1; disk_block.ndb_inode_id = block.nb_inode_entry->nie_hash_entry.nhe_id; if (entry == NULL) { disk_block.ndb_prev_id = NFFS_ID_NONE; } else { disk_block.ndb_prev_id = entry->nhe_id; } disk_block.ndb_data_len = data_len; nffs_crc_disk_block_fill(&disk_block, data); to_area_offset = to_area->na_cur; rc = nffs_flash_write(to_area_idx, to_area_offset, &disk_block, sizeof disk_block); if (rc != 0) { goto done; } rc = nffs_flash_write(to_area_idx, to_area_offset + sizeof disk_block, data, data_len); if (rc != 0) { goto done; } last_entry->nhe_flash_loc = nffs_flash_loc(to_area_idx, to_area_offset); rc = 0; ASSERT_IF_TEST(nffs_crc_disk_block_validate(&disk_block, to_area_idx, to_area_offset) == 0); done: free(data); return rc; }
/** * Overwrites an existing data block. The resulting block has the same ID as * the old one, but it supersedes it with a greater sequence number. * * @param entry The data block to overwrite. * @param left_copy_len The number of bytes of existing data to retain * before the new data begins. * @param new_data The new data to write to the block. * @param new_data_len The number of new bytes to write to the block. * If this value plus left_copy_len is less * than the existing block's data length, * previous data at the end of the block is * retained. * * @return 0 on success; nonzero on failure. */ static int nffs_write_over_block(struct nffs_hash_entry *entry, uint16_t left_copy_len, const void *new_data, uint16_t new_data_len) { struct nffs_disk_block disk_block; struct nffs_block block; uint32_t src_area_offset; uint32_t dst_area_offset; uint16_t right_copy_len; uint16_t block_off; uint8_t src_area_idx; uint8_t dst_area_idx; int rc; rc = nffs_block_from_hash_entry(&block, entry); if (rc != 0) { return rc; } assert(left_copy_len <= block.nb_data_len); /* Determine how much old data at the end of the block needs to be * retained. If the new data doesn't extend to the end of the block, the * the rest of the block retains its old contents. */ if (left_copy_len + new_data_len > block.nb_data_len) { right_copy_len = 0; } else { right_copy_len = block.nb_data_len - left_copy_len - new_data_len; } block.nb_seq++; block.nb_data_len = left_copy_len + new_data_len + right_copy_len; nffs_block_to_disk(&block, &disk_block); nffs_flash_loc_expand(entry->nhe_flash_loc, &src_area_idx, &src_area_offset); rc = nffs_write_fill_crc16_overwrite(&disk_block, src_area_idx, src_area_offset, left_copy_len, right_copy_len, new_data, new_data_len); if (rc != 0) { return rc; } rc = nffs_misc_reserve_space(sizeof disk_block + disk_block.ndb_data_len, &dst_area_idx, &dst_area_offset); if (rc != 0) { return rc; } block_off = 0; /* Write the block header. */ rc = nffs_flash_write(dst_area_idx, dst_area_offset + block_off, &disk_block, sizeof disk_block); if (rc != 0) { return rc; } block_off += sizeof disk_block; /* Copy data from the start of the old block, in case the new data starts * at a non-zero offset. */ if (left_copy_len > 0) { rc = nffs_flash_copy(src_area_idx, src_area_offset + block_off, dst_area_idx, dst_area_offset + block_off, left_copy_len); if (rc != 0) { return rc; } block_off += left_copy_len; } /* Write the new data into the data block. This may extend the block's * length beyond its old value. */ rc = nffs_flash_write(dst_area_idx, dst_area_offset + block_off, new_data, new_data_len); if (rc != 0) { return rc; } block_off += new_data_len; /* Copy data from the end of the old block, in case the new data doesn't * extend to the end of the block. */ if (right_copy_len > 0) { rc = nffs_flash_copy(src_area_idx, src_area_offset + block_off, dst_area_idx, dst_area_offset + block_off, right_copy_len); if (rc != 0) { return rc; } block_off += right_copy_len; } assert(block_off == sizeof disk_block + block.nb_data_len); entry->nhe_flash_loc = nffs_flash_loc(dst_area_idx, dst_area_offset); ASSERT_IF_TEST(nffs_crc_disk_block_validate(&disk_block, dst_area_idx, dst_area_offset) == 0); return 0; }
/** * 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; }