/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ #include #include #include #include "os/mynewt.h" #include "hal/hal_flash.h" #include "nffs/nffs.h" #include "nffs_priv.h" /** * The size of the largest data block encountered during detection. This is * used to ensure that the maximum block data size is not set lower than the * size of an existing block. */ static uint16_t nffs_restore_largest_block_data_len; /** * 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) { if (nffs_hash_entry_is_dummy(cur)) { return FS_ENOENT; } 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; } static void u32toa(char *dst, uint32_t val) { uint8_t tmp; int idx = 0; int i; int print = 0; for (i = 0; i < 8; i++) { tmp = val >> 28; if (tmp || i == 7) { print = 1; } if (tmp < 10) { tmp += '0'; } else { tmp += 'a' - 10; } if (print) { dst[idx++] = tmp; } val <<= 4; } dst[idx++] = '\0'; } /** * If the specified inode entry is a dummy directory, this function moves * all its children to the lost+found directory. * * @param inode_entry The parent inode to test and empty. * * @return 0 on success; nonzero on failure. */ static int nffs_restore_migrate_orphan_children(struct nffs_inode_entry *inode_entry) { struct nffs_inode_entry *lost_found_sub; struct nffs_inode_entry *child_entry; char buf[32]; int rc; if (!nffs_hash_id_is_dir(inode_entry->nie_hash_entry.nhe_id)) { /* Not a directory. */ return 0; } if (!nffs_inode_is_dummy(inode_entry)) { /* Not a dummy. */ return 0; } if (SLIST_EMPTY(&inode_entry->nie_child_list)) { /* No children to migrate. */ return 0; } /* Create a directory in lost+found to hold the dummy directory's * contents. */ strcpy(buf, "/lost+found/"); u32toa(&buf[strlen(buf)], inode_entry->nie_hash_entry.nhe_id); rc = nffs_path_new_dir(buf, &lost_found_sub); if (rc != 0 && rc != FS_EEXIST) { return rc; } /* Move each child into the new subdirectory. */ while ((child_entry = SLIST_FIRST(&inode_entry->nie_child_list)) != NULL) { rc = nffs_inode_rename(child_entry, lost_found_sub, NULL); if (rc != 0) { return rc; } } return 0; } static int nffs_restore_should_sweep_inode_entry(struct nffs_inode_entry *inode_entry, int *out_should_sweep) { struct nffs_inode inode; int rc; /* * if this inode was tagged to have a dummy block entry and the * flag is still set, that means we didn't find the block and so * should remove this inode and all associated blocks. */ if (nffs_inode_getflags(inode_entry, NFFS_INODE_FLAG_DUMMYLSTBLK)) { *out_should_sweep = 1; /*nffs_inode_inc_refcnt(inode_entry);*/ inode_entry->nie_refcnt = 1; assert(inode_entry->nie_refcnt >= 1); return 0; } /* * This inode was originally created to hold a block that was restored * before the owning inode. If the flag is still set, it means we never * restored the inode from disk and so this entry should be deleted. */ if (nffs_inode_getflags(inode_entry, NFFS_INODE_FLAG_DUMMYINOBLK)) { *out_should_sweep = 2; inode_entry->nie_refcnt = 1; assert(inode_entry->nie_refcnt >= 1); return 0; } /* * Determine if the inode is a dummy. Dummy inodes have a flash * location set to LOC_NONE and should have a flag set for the reason. * The presence of a dummy inode during the final sweep step indicates * file system corruption. It's assumed that directories have * previously migrated all children to /lost+found. */ if (nffs_inode_is_dummy(inode_entry)) { *out_should_sweep = 3; nffs_inode_inc_refcnt(inode_entry); assert(inode_entry->nie_refcnt >= 1); return 0; } /* Determine if the inode has been deleted. If an inode has no parent (and * it isn't the root directory), it has been deleted from the disk and * should be swept from the RAM representation. */ if (inode_entry->nie_hash_entry.nhe_id != NFFS_ID_ROOT_DIR) { rc = nffs_inode_from_entry(&inode, inode_entry); if (rc != 0 && rc != FS_ENOENT) { *out_should_sweep = 0; return rc; } if (inode.ni_parent == NULL) { *out_should_sweep = 4; return 0; } } /* * If this inode has been marked as deleted, we can unlink it here. * * XXX Note that the record of a deletion could be lost if garbage * collection erases the delete but leaves inode updates on other * partitions which can then be restored. */ if (nffs_inode_getflags(inode_entry, NFFS_INODE_FLAG_DELETED)) { rc = nffs_inode_from_entry(&inode, inode_entry); if (rc != 0) { *out_should_sweep = 0; return rc; } *out_should_sweep = 5; } /* If this is a file inode, verify that all of its constituent blocks are * present. */ if (nffs_hash_id_is_file(inode_entry->nie_hash_entry.nhe_id)) { rc = nffs_restore_validate_block_chain( inode_entry->nie_last_block_entry); if (rc == FS_ECORRUPT) { *out_should_sweep = 6; return 0; } else if (rc == FS_ENOENT) { *out_should_sweep = 7; return 0; } else if (rc != 0) { *out_should_sweep = 0; return rc; } } /* This is a valid inode; don't sweep it. */ *out_should_sweep = 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; } /** * Creates a dummy inode and inserts it into the hash table. A dummy inode is * a temporary placeholder for a real inode that has not been restored yet. * These are necessary so that the inter-object links can be maintained until * the absent inode is eventually restored. Dummy inodes are identified by a * inaccessible flash location (NFFS_FLASH_LOC_NONE). When the real inode * is restored, this flash location will be udpated. * * @param id The ID of the dummy inode to create. * @param out_inode_entry On success, the dummy inode gets written here. * * @return 0 on success; nonzero on failure. */ static int nffs_restore_dummy_inode(uint32_t id, struct nffs_inode_entry **out_inode_entry) { struct nffs_inode_entry *inode_entry; inode_entry = nffs_inode_entry_alloc(); if (inode_entry == NULL) { return FS_ENOMEM; } inode_entry->nie_hash_entry.nhe_id = id; inode_entry->nie_hash_entry.nhe_flash_loc = NFFS_FLASH_LOC_NONE; inode_entry->nie_refcnt = 0; inode_entry->nie_last_block_entry = NULL; /* lastblock not available yet */ nffs_inode_setflags(inode_entry, NFFS_INODE_FLAG_DUMMY); nffs_hash_insert(&inode_entry->nie_hash_entry); *out_inode_entry = inode_entry; return 0; } /** * Determines if an already-restored inode should be replaced by another inode * just read from flash. This function should only be called if both inodes * share the same ID. The existing inode gets replaced if: * o It is a dummy inode. * o Its sequence number is less than that of the new inode. * * @param old_inode_entry The already-restored inode to test. * @param disk_inode The inode just read from flash. * @param out_should_replace On success, 0=don't replace; 1=do replace. * * @return 0 on success; nonzero on failure. */ static int nffs_restore_inode_gets_replaced(struct nffs_inode_entry *old_inode_entry, const struct nffs_disk_inode *disk_inode, int *out_should_replace) { struct nffs_inode old_inode; int rc; assert(old_inode_entry->nie_hash_entry.nhe_id == disk_inode->ndi_id); if (nffs_inode_is_dummy(old_inode_entry)) { NFFS_LOG_DEBUG("inode_gets_replaced dummy!\n"); *out_should_replace = 1; return 0; } /* * inode is known to be obsolete and needs to be replaced no matter what */ if (nffs_inode_getflags(old_inode_entry, NFFS_INODE_FLAG_OBSOLETE)) { NFFS_LOG_DEBUG("inode_gets_replaced obsolete\n"); *out_should_replace = 2; return 0; } rc = nffs_inode_from_entry(&old_inode, old_inode_entry); if (rc != 0) { *out_should_replace = 0; return rc; } if (old_inode.ni_seq < disk_inode->ndi_seq) { NFFS_LOG_DEBUG("inode_gets_replaced seq\n"); *out_should_replace = 3; return 0; } if (old_inode.ni_seq == disk_inode->ndi_seq) { /* This is a duplicate of a previously-read inode. This should never * happen. */ *out_should_replace = 0; return FS_EEXIST; } *out_should_replace = 0; return 0; } /** * 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; } /** * Indicates whether the specified data block is superseded by the just-read * disk data block. A data block supersedes another if its ID is equal and its * sequence number is greater than that of the other block. * * @param out_should_replace On success, 0 or 1 gets written here, to * indicate whether replacement should occur. * @param old_block The data block which has already been read and * converted to its RAM representation. This * is the block that may be superseded. * @param disk_block The disk data block that was just read from * flash. This is the block which may * supersede the other. * * @return 0 on success; nonzero on failure. */ static int nffs_restore_block_gets_replaced(const struct nffs_block *old_block, const struct nffs_disk_block *disk_block, int *out_should_replace) { assert(old_block->nb_hash_entry->nhe_id == disk_block->ndb_id); if (nffs_block_is_dummy(old_block->nb_hash_entry)) { assert(0); *out_should_replace = 1; return 0; } if (old_block->nb_seq < disk_block->ndb_seq) { *out_should_replace = 2; return 0; } if (old_block->nb_seq == disk_block->ndb_seq) { /* This is a duplicate of an previously-read block. This should never * happen. */ return FS_EEXIST; } *out_should_replace = 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 && 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; } /** * Populates the nffs RAM state with the memory representation of the specified * disk object. * * @param disk_object The source disk object to convert. * * @return 0 on success; nonzero on failure. */ static int nffs_restore_object(const struct nffs_disk_object *disk_object) { int rc; switch (disk_object->ndo_type) { case NFFS_OBJECT_TYPE_INODE: rc = nffs_restore_inode(&disk_object->ndo_disk_inode, disk_object->ndo_area_idx, disk_object->ndo_offset); break; case NFFS_OBJECT_TYPE_BLOCK: rc = nffs_restore_block(&disk_object->ndo_disk_block, disk_object->ndo_area_idx, disk_object->ndo_offset); break; default: rc = FS_EINVAL; break; } return rc; } /** * 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; memset(out_disk_object, 0, sizeof *out_disk_object); 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; } /** * Calculates the disk space occupied by the specified disk object. * * @param disk_object */ static int nffs_restore_disk_object_size(const struct nffs_disk_object *disk_object) { switch (disk_object->ndo_type) { case NFFS_OBJECT_TYPE_INODE: return sizeof disk_object->ndo_disk_inode + disk_object->ndo_disk_inode.ndi_filename_len; case NFFS_OBJECT_TYPE_BLOCK: return sizeof disk_object->ndo_disk_block + disk_object->ndo_disk_block.ndb_data_len; default: assert(0); return 1; } } /** * Reads the specified area from disk and loads its contents into the RAM * representation. * * @param area_idx The index of the area to read. * * @return 0 on success; nonzero on failure. */ static int nffs_restore_area_contents(int area_idx) { struct nffs_disk_object disk_object; struct nffs_area *area; int rc; area = nffs_areas + area_idx; area->na_cur = sizeof (struct nffs_disk_area); while (1) { rc = nffs_restore_disk_object(area_idx, area->na_cur, &disk_object); switch (rc) { case 0: /* Valid object; restore it into the RAM representation. */ rc = nffs_restore_object(&disk_object); /* * If the restore fails the CRC check, the object length field * can't be trusted so just start looking for the next valid * object in the flash area. * XXX Deal with file system corruption */ if (rc == FS_ECORRUPT) { area->na_cur++; } else { STATS_INC(nffs_stats, nffs_object_count); /* restored objects */ area->na_cur += nffs_restore_disk_object_size(&disk_object); } break; case FS_ECORRUPT: /* * Invalid object; keep scanning for a valid object ID and CRC * Can nffs_restore_disk_object return FS_ECORRUPT? XXX */ area->na_cur++; break; case FS_EEMPTY: case FS_EOFFSET: /* End of disk encountered; area fully restored. */ return 0; default: return rc; } } } /** * Reads and parses one area header. This function does not read the area's * contents. * * @param out_is_scratch On success, 0 or 1 gets written here, * indicating whether the area is a scratch * area. * @param area_offset The flash offset of the start of the area. * * @return 0 on success; * nonzero on failure. */ static int nffs_restore_detect_one_area(uint8_t flash_id, uint32_t area_offset, struct nffs_disk_area *out_disk_area) { int rc; STATS_INC(nffs_stats, nffs_readcnt_detect); rc = hal_flash_read(flash_id, area_offset, out_disk_area, sizeof *out_disk_area); if (rc != 0) { return FS_EHW; } if (!nffs_area_magic_is_set(out_disk_area)) { return FS_ECORRUPT; } if (!nffs_area_is_current_version(out_disk_area)) { return FS_EUNEXP; } return 0; } /** * 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; nffs_inode_setflags(inode_entry, NFFS_INODE_FLAG_OBSOLETE); } } 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; } 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); } } } } /** * Searches for a valid nffs file system among the specified areas. This * function succeeds if a file system is detected among any subset of the * supplied areas. If the area set does not contain a valid file system, * a new one can be created via a call to nffs_format(). * * @param area_descs The area set to search. This array must be * terminated with a 0-length area. * * @return 0 on success; * FS_ECORRUPT if no valid file system was detected; * other nonzero on error. */ int nffs_restore_full(const struct nffs_area_desc *area_descs) { struct nffs_disk_area disk_area; int cur_area_idx; int use_area; int rc; int i; /* Start from a clean state. */ rc = nffs_misc_reset(); if (rc) { return rc; } nffs_restore_largest_block_data_len = 0; nffs_current_area_descs = (struct nffs_area_desc*) area_descs; /* Read each area from flash. */ for (i = 0; area_descs[i].nad_length != 0; i++) { if (i > NFFS_MAX_AREAS) { rc = FS_EINVAL; goto err; } rc = nffs_restore_detect_one_area(area_descs[i].nad_flash_id, area_descs[i].nad_offset, &disk_area); switch (rc) { case 0: use_area = 1; break; case FS_EUNEXP: /* not formatted with current on-disk NFFS format */ case FS_ECORRUPT: use_area = 0; break; default: goto err; } if (use_area) { if (disk_area.nda_id == NFFS_AREA_ID_NONE && nffs_scratch_area_idx != NFFS_AREA_ID_NONE) { /* Don't allow more than one scratch area. */ use_area = 0; } } if (use_area) { /* Populate RAM with a representation of this area. */ cur_area_idx = nffs_num_areas; rc = nffs_misc_set_num_areas(nffs_num_areas + 1); if (rc != 0) { goto err; } nffs_areas[cur_area_idx].na_offset = area_descs[i].nad_offset; nffs_areas[cur_area_idx].na_length = area_descs[i].nad_length; nffs_areas[cur_area_idx].na_flash_id = area_descs[i].nad_flash_id; nffs_areas[cur_area_idx].na_gc_seq = disk_area.nda_gc_seq; nffs_areas[cur_area_idx].na_id = disk_area.nda_id; if (disk_area.nda_id == NFFS_AREA_ID_NONE) { nffs_areas[cur_area_idx].na_cur = NFFS_AREA_OFFSET_ID; nffs_scratch_area_idx = cur_area_idx; } else { nffs_areas[cur_area_idx].na_cur = sizeof (struct nffs_disk_area); nffs_restore_area_contents(cur_area_idx); } } } /* All areas have been restored from flash. */ if (nffs_scratch_area_idx == NFFS_AREA_ID_NONE) { /* No scratch area. The system may have been rebooted in the middle of * a garbage collection cycle. Look for a candidate scratch area. */ rc = nffs_restore_corrupt_scratch(); if (rc != 0) { if (rc == FS_ENOENT) { rc = FS_ECORRUPT; } goto err; } } /* Ensure this file system contains a valid scratch area. */ rc = nffs_misc_validate_scratch(); if (rc != 0) { goto err; } /* Make sure the file system contains a valid root directory. */ rc = nffs_misc_validate_root_dir(); if (rc != 0) { goto err; } /* Ensure there is a "/lost+found" directory. */ rc = nffs_misc_create_lost_found_dir(); if (rc != 0) { goto err; } /* Delete from RAM any objects that were invalidated when subsequent areas * were restored. */ nffs_restore_sweep(); /* Set the maximum data block size according to the size of the smallest * area. */ rc = nffs_misc_set_max_block_data_len(nffs_restore_largest_block_data_len); if (rc != 0) { goto err; } NFFS_LOG_DEBUG("CONTENTS\n"); nffs_log_contents(); return 0; err: nffs_misc_reset(); return rc; }