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