/** * 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; }
/** * 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; }
/** * 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; }
/** * Reads a data block header from flash. * * @param area_idx The index of the area to read from. * @param area_offset The offset within the area to read from. * @param out_disk_block On success, the block header is writteh here. * * @return 0 on success; * 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_read_disk(uint8_t area_idx, uint32_t area_offset, struct nffs_disk_block *out_disk_block) { int rc; rc = nffs_flash_read(area_idx, area_offset, out_disk_block, sizeof *out_disk_block); if (rc != 0) { return rc; } if (!nffs_hash_id_is_block(out_disk_block->ndb_id)) { return FS_EUNEXP; } 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; }
void nffs_block_entry_free(struct nffs_hash_entry *entry) { assert(nffs_hash_id_is_block(entry->nhe_id)); os_memblock_put(&nffs_block_entry_pool, entry); }
/** * 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; }
/** * 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); } } } }