static int nffs_gc_block_chain_copy(struct nffs_hash_entry *last_entry, uint32_t data_len, uint8_t to_area_idx) { struct nffs_hash_entry *entry; struct nffs_block block; uint32_t data_bytes_copied; uint16_t copy_len; int rc; data_bytes_copied = 0; entry = last_entry; while (data_bytes_copied < data_len) { assert(entry != NULL); rc = nffs_block_from_hash_entry(&block, entry); if (rc != 0) { return rc; } copy_len = sizeof (struct nffs_disk_block) + block.nb_data_len; rc = nffs_gc_copy_object(entry, copy_len, to_area_idx); if (rc != 0) { return rc; } data_bytes_copied += block.nb_data_len; entry = block.nb_prev; } 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; }
/** * Deletes the specified block entry from the nffs RAM representation. * * @param block_entry The block entry to delete. * * @return 0 on success; nonzero on failure. */ int nffs_block_delete_from_ram(struct nffs_hash_entry *block_entry) { struct nffs_block block; int rc; rc = nffs_block_from_hash_entry(&block, block_entry); if (rc != 0) { return rc; } assert(block.nb_inode_entry != NULL); if (block.nb_inode_entry->nie_last_block_entry == block_entry) { block.nb_inode_entry->nie_last_block_entry = block.nb_prev; } nffs_hash_remove(block_entry); nffs_block_entry_free(block_entry); return 0; }
/** * Deletes the specified block entry from the nffs RAM representation. * * @param block_entry The block entry to delete. * * @return 0 on success; nonzero on failure. */ int nffs_block_delete_from_ram(struct nffs_hash_entry *block_entry) { struct nffs_inode_entry *inode_entry; struct nffs_block block; int rc; if (nffs_hash_entry_is_dummy(block_entry)) { /* * it's very limited to what we can do here as the block doesn't have * any way to get to the inode via hash entry. Just delete the * block and return FS_ECORRUPT */ nffs_hash_remove(block_entry); nffs_block_entry_free(block_entry); return FS_ECORRUPT; } rc = nffs_block_from_hash_entry(&block, block_entry); if (rc == 0 || rc == FS_ECORRUPT) { /* If file system corruption was detected, the resulting block is still * valid and can be removed from RAM. * Note that FS_CORRUPT can occur because the owning inode was not * found in the hash table - this can occur during the sweep where * the inodes were deleted ahead of the blocks. */ inode_entry = block.nb_inode_entry; if (inode_entry != NULL && inode_entry->nie_last_block_entry == block_entry) { inode_entry->nie_last_block_entry = block.nb_prev; } nffs_hash_remove(block_entry); nffs_block_entry_free(block_entry); } return rc; }
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; }
/** * 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; }
/** * 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; }
static void nffs_log_contents(void) { #if MYNEWT_VAL(LOG_LEVEL) > LOG_LEVEL_DEBUG return; #endif struct nffs_inode_entry *inode_entry; struct nffs_hash_entry *entry; struct nffs_hash_entry *next; struct nffs_block block; struct nffs_inode inode; int rc; int i; NFFS_HASH_FOREACH(entry, i, next) { if (nffs_hash_id_is_block(entry->nhe_id)) { rc = nffs_block_from_hash_entry(&block, entry); assert(rc == 0 || rc == FS_ENOENT); NFFS_LOG(DEBUG, "block; id=%u inode_id=", (unsigned int)entry->nhe_id); if (block.nb_inode_entry == NULL) { NFFS_LOG(DEBUG, "null "); } else { NFFS_LOG(DEBUG, "%u ", (unsigned int)block.nb_inode_entry->nie_hash_entry.nhe_id); } NFFS_LOG(DEBUG, "prev_id="); if (block.nb_prev == NULL) { NFFS_LOG(DEBUG, "null "); } else { NFFS_LOG(DEBUG, "%u ", (unsigned int)block.nb_prev->nhe_id); } NFFS_LOG(DEBUG, "data_len=%u\n", block.nb_data_len); } else { inode_entry = (void *)entry; rc = nffs_inode_from_entry(&inode, inode_entry); if (rc == FS_ENOENT) { NFFS_LOG(DEBUG, "DUMMY file; id=%x ref=%d block_id=", (unsigned int)entry->nhe_id, inode_entry->nie_refcnt); if (inode_entry->nie_last_block_entry == NULL) { NFFS_LOG(DEBUG, "null"); } else { NFFS_LOG(DEBUG, "%x", (unsigned int)inode_entry->nie_last_block_entry->nhe_id); } } else if (rc != 0) { continue; } /*assert(rc == 0);*/ if (nffs_hash_id_is_file(entry->nhe_id)) { NFFS_LOG(DEBUG, "file; id=%u name=%.3s block_id=", (unsigned int)entry->nhe_id, inode.ni_filename); if (inode_entry->nie_last_block_entry == NULL) { NFFS_LOG(DEBUG, "null"); } else { NFFS_LOG(DEBUG, "%u", (unsigned int)inode_entry->nie_last_block_entry->nhe_id); } NFFS_LOG(DEBUG, "\n"); } else { inode_entry = (void *)entry; NFFS_LOG(DEBUG, "dir; id=%u name=%.3s\n", (unsigned int)entry->nhe_id, inode.ni_filename); } } } }