/** * Appends a new block to an inode block chain. * * @param inode_entry The inode to append a block to. * @param data The contents of the new block. * @param len The number of bytes of data to write. * * @return 0 on success; nonzero on failure. */ static int nffs_write_append(struct nffs_cache_inode *cache_inode, const void *data, uint16_t len) { struct nffs_inode_entry *inode_entry; struct nffs_hash_entry *entry; struct nffs_disk_block disk_block; uint32_t area_offset; uint8_t area_idx; int rc; rc = nffs_block_entry_reserve(&entry); if (entry == NULL) { return FS_ENOMEM; } inode_entry = cache_inode->nci_inode.ni_inode_entry; disk_block.ndb_magic = NFFS_BLOCK_MAGIC; disk_block.ndb_id = nffs_hash_next_block_id++; disk_block.ndb_seq = 0; disk_block.ndb_inode_id = inode_entry->nie_hash_entry.nhe_id; if (inode_entry->nie_last_block_entry == NULL) { disk_block.ndb_prev_id = NFFS_ID_NONE; } else { disk_block.ndb_prev_id = inode_entry->nie_last_block_entry->nhe_id; } disk_block.ndb_data_len = len; nffs_crc_disk_block_fill(&disk_block, data); rc = nffs_block_write_disk(&disk_block, data, &area_idx, &area_offset); if (rc != 0) { return rc; } entry->nhe_id = disk_block.ndb_id; entry->nhe_flash_loc = nffs_flash_loc(area_idx, area_offset); nffs_hash_insert(entry); inode_entry->nie_last_block_entry = entry; /* Update cached inode with the new file size. */ cache_inode->nci_file_size += len; /* Add appended block to the cache. */ nffs_cache_seek(cache_inode, cache_inode->nci_file_size - 1, NULL); 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; }
/** * Creates a new empty file and writes it to the file system. If a file with * the specified path already exists, the behavior is undefined. * * @param parent The parent directory to insert the new file in. * @param filename The name of the file to create. * @param filename_len The length of the filename, in characters. * @param is_dir 1 if this is a directory; 0 if it is a normal * file. * @param out_inode_entry On success, this points to the inode * corresponding to the new file. * * @return 0 on success; nonzero on failure. */ int nffs_file_new(struct nffs_inode_entry *parent, const char *filename, uint8_t filename_len, int is_dir, struct nffs_inode_entry **out_inode_entry) { struct nffs_disk_inode disk_inode; struct nffs_inode_entry *inode_entry; uint32_t offset; uint8_t area_idx; int rc; inode_entry = nffs_inode_entry_alloc(); if (inode_entry == NULL) { rc = FS_ENOMEM; goto err; } rc = nffs_misc_reserve_space(sizeof disk_inode + filename_len, &area_idx, &offset); if (rc != 0) { goto err; } memset(&disk_inode, 0xff, sizeof disk_inode); disk_inode.ndi_magic = NFFS_INODE_MAGIC; if (is_dir) { disk_inode.ndi_id = nffs_hash_next_dir_id++; } else { disk_inode.ndi_id = nffs_hash_next_file_id++; } disk_inode.ndi_seq = 0; if (parent == NULL) { disk_inode.ndi_parent_id = NFFS_ID_NONE; } else { disk_inode.ndi_parent_id = parent->nie_hash_entry.nhe_id; } disk_inode.ndi_filename_len = filename_len; nffs_crc_disk_inode_fill(&disk_inode, filename); rc = nffs_inode_write_disk(&disk_inode, filename, area_idx, offset); if (rc != 0) { goto err; } inode_entry->nie_hash_entry.nhe_id = disk_inode.ndi_id; inode_entry->nie_hash_entry.nhe_flash_loc = nffs_flash_loc(area_idx, offset); inode_entry->nie_refcnt = 1; if (parent != NULL) { rc = nffs_inode_add_child(parent, inode_entry); if (rc != 0) { goto err; } } else { assert(disk_inode.ndi_id == NFFS_ID_ROOT_DIR); } nffs_hash_insert(&inode_entry->nie_hash_entry); *out_inode_entry = inode_entry; return 0; err: nffs_inode_entry_free(inode_entry); 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 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; }
/** * Populates the nffs RAM state with the memory representation of the specified * disk data block. * * @param disk_block The source disk block to insert. * @param area_idx The ID of the area containing the block. * @param area_offset The area_offset within the area of the block. * * @return 0 on success; nonzero on failure. */ static int nffs_restore_block(const struct nffs_disk_block *disk_block, uint8_t area_idx, uint32_t area_offset) { struct nffs_inode_entry *inode_entry; struct nffs_hash_entry *entry; struct nffs_block block; int do_replace; int new_block; int rc; new_block = 0; /* Check the block's CRC. If the block is corrupt, discard it. If this * block would have superseded another, the old block becomes current. */ rc = nffs_crc_disk_block_validate(disk_block, area_idx, area_offset); if (rc != 0) { goto err; } entry = nffs_hash_find_block(disk_block->ndb_id); if (entry != NULL) { rc = nffs_block_from_hash_entry_no_ptrs(&block, entry); if (rc != 0) { goto err; } rc = nffs_restore_block_gets_replaced(&block, disk_block, &do_replace); if (rc != 0) { goto err; } if (!do_replace) { /* The new block is superseded by the old; nothing to do. */ return 0; } nffs_block_delete_from_ram(entry); } entry = nffs_block_entry_alloc(); if (entry == NULL) { rc = FS_ENOMEM; goto err; } new_block = 1; entry->nhe_id = disk_block->ndb_id; entry->nhe_flash_loc = nffs_flash_loc(area_idx, area_offset); /* The block is ready to be inserted into the hash. */ inode_entry = nffs_hash_find_inode(disk_block->ndb_inode_id); if (inode_entry == NULL) { rc = nffs_restore_dummy_inode(disk_block->ndb_inode_id, &inode_entry); if (rc != 0) { goto err; } } if (inode_entry->nie_last_block_entry == NULL || inode_entry->nie_last_block_entry->nhe_id == disk_block->ndb_prev_id) { inode_entry->nie_last_block_entry = entry; } nffs_hash_insert(entry); if (disk_block->ndb_id >= nffs_hash_next_block_id) { nffs_hash_next_block_id = disk_block->ndb_id + 1; } /* Make sure the maximum block data size is not set lower than the size of * an existing block. */ if (disk_block->ndb_data_len > nffs_restore_largest_block_data_len) { nffs_restore_largest_block_data_len = disk_block->ndb_data_len; } return 0; err: if (new_block) { nffs_block_entry_free(entry); } return rc; }
/** * Determines if the specified inode should be added to the RAM representation * and adds it if appropriate. * * @param disk_inode The inode just read from flash. * @param area_idx The index of the area containing the inode. * @param area_offset The offset within the area of the inode. * * @return 0 on success; nonzero on failure. */ static int nffs_restore_inode(const struct nffs_disk_inode *disk_inode, uint8_t area_idx, uint32_t area_offset) { struct nffs_inode_entry *inode_entry; struct nffs_inode_entry *parent; struct nffs_inode inode; int new_inode; int do_add; int rc; new_inode = 0; /* Check the inode's CRC. If the inode is corrupt, discard it. */ rc = nffs_crc_disk_inode_validate(disk_inode, area_idx, area_offset); if (rc != 0) { goto err; } inode_entry = nffs_hash_find_inode(disk_inode->ndi_id); if (inode_entry != NULL) { rc = nffs_restore_inode_gets_replaced(inode_entry, disk_inode, &do_add); if (rc != 0) { goto err; } if (do_add) { if (inode_entry->nie_hash_entry.nhe_flash_loc != NFFS_FLASH_LOC_NONE) { rc = nffs_inode_from_entry(&inode, inode_entry); if (rc != 0) { return rc; } if (inode.ni_parent != NULL) { nffs_inode_remove_child(&inode); } } inode_entry->nie_hash_entry.nhe_flash_loc = nffs_flash_loc(area_idx, area_offset); } } else { inode_entry = nffs_inode_entry_alloc(); if (inode_entry == NULL) { rc = FS_ENOMEM; goto err; } new_inode = 1; do_add = 1; inode_entry->nie_hash_entry.nhe_id = disk_inode->ndi_id; inode_entry->nie_hash_entry.nhe_flash_loc = nffs_flash_loc(area_idx, area_offset); nffs_hash_insert(&inode_entry->nie_hash_entry); } if (do_add) { inode_entry->nie_refcnt = 1; if (disk_inode->ndi_parent_id != NFFS_ID_NONE) { parent = nffs_hash_find_inode(disk_inode->ndi_parent_id); if (parent == NULL) { rc = nffs_restore_dummy_inode(disk_inode->ndi_parent_id, &parent); if (rc != 0) { goto err; } } rc = nffs_inode_add_child(parent, inode_entry); if (rc != 0) { goto err; } } if (inode_entry->nie_hash_entry.nhe_id == NFFS_ID_ROOT_DIR) { nffs_root_dir = inode_entry; } } if (nffs_hash_id_is_file(inode_entry->nie_hash_entry.nhe_id)) { if (inode_entry->nie_hash_entry.nhe_id >= nffs_hash_next_file_id) { nffs_hash_next_file_id = inode_entry->nie_hash_entry.nhe_id + 1; } } else { if (inode_entry->nie_hash_entry.nhe_id >= nffs_hash_next_dir_id) { nffs_hash_next_dir_id = inode_entry->nie_hash_entry.nhe_id + 1; } } return 0; err: if (new_inode) { nffs_inode_entry_free(inode_entry); } return rc; }
/** * Populates the nffs RAM state with the memory representation of the specified * disk data block. * * @param disk_block The source disk block to insert. * @param area_idx The ID of the area containing the block. * @param area_offset The area_offset within the area of the block. * * @return 0 on success; nonzero on failure. */ static int nffs_restore_block(const struct nffs_disk_block *disk_block, uint8_t area_idx, uint32_t area_offset) { struct nffs_inode_entry *inode_entry; struct nffs_hash_entry *entry; struct nffs_block block; int do_replace; int new_block; int rc; new_block = 0; /* Check the block's CRC. If the block is corrupt, discard it. If this * block would have superseded another, the old block becomes current. */ rc = nffs_crc_disk_block_validate(disk_block, area_idx, area_offset); if (rc != 0) { goto err; } entry = nffs_hash_find_block(disk_block->ndb_id); if (entry != NULL) { rc = nffs_block_from_hash_entry_no_ptrs(&block, entry); if (rc != 0 && rc != FS_ENOENT) { goto err; } /* * If the old block reference is for a 'dummy' block, it was added * because the owning inode's lastblock was not yet restored. * Update the block hash entry and inode to reference the entry. */ if (nffs_block_is_dummy(entry)) { assert(entry->nhe_id == disk_block->ndb_id); /* * Entry is no longer dummy as it references the correct location */ entry->nhe_flash_loc = nffs_flash_loc(area_idx, area_offset); inode_entry = nffs_hash_find_inode(disk_block->ndb_inode_id); /* * Turn off flags in previously restored inode recording the * allocation of a dummy block */ if (inode_entry) { nffs_inode_unsetflags(inode_entry, NFFS_INODE_FLAG_DUMMYLSTBLK); } } rc = nffs_restore_block_gets_replaced(&block, disk_block, &do_replace); if (rc != 0) { goto err; } if (!do_replace) { /* The new block is superseded by the old; nothing to do. */ return 0; } /* * update the existing hash entry to reference the new flash location */ entry->nhe_flash_loc = nffs_flash_loc(area_idx, area_offset); } else { entry = nffs_block_entry_alloc(); if (entry == NULL) { rc = FS_ENOMEM; goto err; } new_block = 1; entry->nhe_id = disk_block->ndb_id; entry->nhe_flash_loc = nffs_flash_loc(area_idx, area_offset); /* The block is ready to be inserted into the hash. */ nffs_hash_insert(entry); if (disk_block->ndb_id >= nffs_hash_next_block_id) { nffs_hash_next_block_id = disk_block->ndb_id + 1; } } /* Make sure the maximum block data size is not set lower than the size of * an existing block. */ if (disk_block->ndb_data_len > nffs_restore_largest_block_data_len) { nffs_restore_largest_block_data_len = disk_block->ndb_data_len; } NFFS_LOG(DEBUG, "restoring block; id=0x%08x seq=%u inode_id=%u prev_id=%u " "data_len=%u\n", (unsigned int)disk_block->ndb_id, (unsigned int)disk_block->ndb_seq, (unsigned int)disk_block->ndb_inode_id, (unsigned int)disk_block->ndb_prev_id, disk_block->ndb_data_len); inode_entry = nffs_hash_find_inode(disk_block->ndb_inode_id); if (inode_entry == NULL) { /* * Owning inode not yet restored. * Allocate a dummy inode which temporarily owns this block. * It is not yet linked to a parent. */ rc = nffs_restore_dummy_inode(disk_block->ndb_inode_id, &inode_entry); if (rc != 0) { goto err; } /* * Record that this inode was created because a block was restored * before the inode */ nffs_inode_setflags(inode_entry, NFFS_INODE_FLAG_DUMMYINOBLK); inode_entry->nie_last_block_entry = entry; } else { if (nffs_inode_getflags(inode_entry, NFFS_INODE_FLAG_DELETED)) { /* * don't restore blocks for deleted inodes */ rc = FS_ENOENT; goto err; } } return 0; err: if (new_block) { nffs_hash_remove(entry); nffs_block_entry_free(entry); } return rc; }
/** * Determines if the specified inode should be added to the RAM representation * and adds it if appropriate. * * @param disk_inode The inode just read from flash. * @param area_idx The index of the area containing the inode. * @param area_offset The offset within the area of the inode. * * @return 0 on success; nonzero on failure. */ static int nffs_restore_inode(const struct nffs_disk_inode *disk_inode, uint8_t area_idx, uint32_t area_offset) { struct nffs_inode_entry *inode_entry; struct nffs_inode_entry *parent; struct nffs_inode inode; struct nffs_hash_entry *lastblock_entry = NULL; int new_inode; int do_add; int rc; new_inode = 0; /* Check the inode's CRC. If the inode is corrupt, discard it. */ rc = nffs_crc_disk_inode_validate(disk_inode, area_idx, area_offset); if (rc != 0) { goto err; } inode_entry = nffs_hash_find_inode(disk_inode->ndi_id); /* * Inode has already been restored. Determine whether this version * from disk should replace the previous version referenced in RAM. */ if (inode_entry != NULL) { if (disk_inode->ndi_flags & NFFS_INODE_FLAG_DELETED) { /* * Restore this inode even though deleted on disk * so the additional restored blocks have a place to go */ NFFS_LOG(DEBUG, "restoring deleted inode %x\n", (unsigned int)disk_inode->ndi_id); nffs_inode_setflags(inode_entry, NFFS_INODE_FLAG_DELETED); } /* * inodes get replaced if they're dummy entries (i.e. allocated * as place holders until the actual inode is restored), or this is * a more recent version of the inode which supercedes the old. */ rc = nffs_restore_inode_gets_replaced(inode_entry, disk_inode, &do_add); if (rc != 0) { goto err; } if (do_add) { /* replace in this case */ if (!nffs_inode_is_dummy(inode_entry)) { /* * if it's not a dummy, read block from flash */ rc = nffs_inode_from_entry(&inode, inode_entry); if (rc != 0) { return rc; } /* * inode is known to be obsolete */ if (nffs_inode_getflags(inode_entry, NFFS_INODE_FLAG_OBSOLETE)) { nffs_inode_unsetflags(inode_entry, NFFS_INODE_FLAG_OBSOLETE); } if (inode.ni_parent != NULL) { nffs_inode_remove_child(&inode); } /* * If this is a delete record, subsequent inode and restore * records from flash may be ignored. * If the parent is NULL, this inode has been deleted. (old) * XXX if we could count on delete records for every inode, * we wouldn't need to check for the root directory looking * like a delete record because of it's parent ID. */ if (inode_entry->nie_hash_entry.nhe_id != NFFS_ID_ROOT_DIR) { if (disk_inode->ndi_flags & NFFS_INODE_FLAG_DELETED || disk_inode->ndi_parent_id == NFFS_ID_NONE) { nffs_inode_setflags(inode_entry, NFFS_INODE_FLAG_DELETED); } } } else { /* * The existing inode entry was added as dummy. * The restore operation clears that state. */ /* If it's a directory, it was added as a parent to * one of it's children who were restored first. */ if (nffs_inode_getflags(inode_entry, NFFS_INODE_FLAG_DUMMYPARENT)) { assert(nffs_hash_id_is_dir(inode_entry->nie_hash_entry.nhe_id)); nffs_inode_unsetflags(inode_entry, NFFS_INODE_FLAG_DUMMYPARENT); } /* * If it's a file, it was added to store a lastblock */ if (nffs_inode_getflags(inode_entry, NFFS_INODE_FLAG_DUMMYINOBLK)) { assert(nffs_hash_id_is_file(inode_entry->nie_hash_entry.nhe_id)); nffs_inode_unsetflags(inode_entry, NFFS_INODE_FLAG_DUMMYINOBLK); } /* * Also, since it's a dummy, clear this flag too */ if (nffs_inode_getflags(inode_entry, NFFS_INODE_FLAG_DUMMY)) { nffs_inode_unsetflags(inode_entry, NFFS_INODE_FLAG_DUMMY); } } /* * Update location to reference new location in flash */ inode_entry->nie_hash_entry.nhe_flash_loc = nffs_flash_loc(area_idx, area_offset); } } else { inode_entry = nffs_inode_entry_alloc(); if (inode_entry == NULL) { rc = FS_ENOMEM; goto err; } new_inode = 1; do_add = 1; inode_entry->nie_hash_entry.nhe_id = disk_inode->ndi_id; inode_entry->nie_hash_entry.nhe_flash_loc = nffs_flash_loc(area_idx, area_offset); inode_entry->nie_last_block_entry = NULL; /* for now */ nffs_hash_insert(&inode_entry->nie_hash_entry); } /* * inode object has been restored and the entry is in the hash * Check whether the lastblock and parent have also been restored * and link up or allocate dummy entries as appropriate. */ if (do_add) { inode_entry->nie_refcnt = 1; if (disk_inode->ndi_flags & NFFS_INODE_FLAG_DELETED) { /* * Restore this inode even though deleted on disk * so the additional restored blocks have a place to go */ NFFS_LOG(DEBUG, "restoring deleted inode %x\n", (unsigned int)disk_inode->ndi_id); nffs_inode_setflags(inode_entry, NFFS_INODE_FLAG_DELETED); } /* * Inode has a lastblock on disk. * Add reference to last block entry if in hash table * otherwise add a dummy block entry for later update */ if (disk_inode->ndi_lastblock_id != NFFS_ID_NONE && nffs_hash_id_is_file(inode_entry->nie_hash_entry.nhe_id)) { lastblock_entry = nffs_hash_find_block(disk_inode->ndi_lastblock_id); /* * Lastblock has already been restored. */ if (lastblock_entry != NULL) { if (lastblock_entry->nhe_id == disk_inode->ndi_lastblock_id) { inode_entry->nie_last_block_entry = lastblock_entry; } } else { /* * Insert a temporary reference to a 'dummy' block entry * When block is restored, it will update this dummy and * the entry of this inode is updated to flash location */ rc = nffs_block_entry_reserve(&lastblock_entry); if (lastblock_entry == NULL) { rc = FS_ENOMEM; goto err; } lastblock_entry->nhe_id = disk_inode->ndi_lastblock_id; lastblock_entry->nhe_flash_loc = NFFS_FLASH_LOC_NONE; inode_entry->nie_last_block_entry = lastblock_entry; nffs_inode_setflags(inode_entry, NFFS_INODE_FLAG_DUMMYLSTBLK); nffs_hash_insert(lastblock_entry); if (lastblock_entry->nhe_id >= nffs_hash_next_block_id) { nffs_hash_next_block_id = lastblock_entry->nhe_id + 1; } } } if (disk_inode->ndi_parent_id != NFFS_ID_NONE) { parent = nffs_hash_find_inode(disk_inode->ndi_parent_id); /* * The parent directory for this inode hasn't been restored yet. * Add a dummy directory so it can be added as a child. * When the parent inode is restored, it's hash entry will be * updated with the flash location. */ if (parent == NULL) { rc = nffs_restore_dummy_inode(disk_inode->ndi_parent_id, &parent); /* * Set the dummy parent flag in the new parent. * It's turned off above when restored. */ nffs_inode_setflags(parent, NFFS_INODE_FLAG_DUMMYPARENT); if (rc != 0) { goto err; } } rc = nffs_inode_add_child(parent, inode_entry); if (rc != 0) { goto err; } } if (inode_entry->nie_hash_entry.nhe_id == NFFS_ID_ROOT_DIR) { nffs_root_dir = inode_entry; nffs_inode_setflags(nffs_root_dir, NFFS_INODE_FLAG_INTREE); } } if (nffs_hash_id_is_file(inode_entry->nie_hash_entry.nhe_id)) { NFFS_LOG(DEBUG, "restoring file; id=0x%08x\n", (unsigned int)inode_entry->nie_hash_entry.nhe_id); if (inode_entry->nie_hash_entry.nhe_id >= nffs_hash_next_file_id) { nffs_hash_next_file_id = inode_entry->nie_hash_entry.nhe_id + 1; } } else { NFFS_LOG(DEBUG, "restoring dir; id=0x%08x\n", (unsigned int)inode_entry->nie_hash_entry.nhe_id); if (inode_entry->nie_hash_entry.nhe_id >= nffs_hash_next_dir_id) { nffs_hash_next_dir_id = inode_entry->nie_hash_entry.nhe_id + 1; } } return 0; err: if (new_inode) { assert(nffs_inode_getflags(inode_entry, NFFS_INODE_FLAG_INHASH)); nffs_hash_remove(&inode_entry->nie_hash_entry); nffs_inode_entry_free(inode_entry); } return rc; }