/** * 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; }
/** * 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; }