| /* |
| * 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 <stddef.h> |
| #include <string.h> |
| #include <assert.h> |
| #include <nffs/nffs.h> |
| #include <nffs/os.h> |
| |
| /* Partition the flash buffer into two equal halves; used for filename |
| * comparisons. |
| */ |
| #define NFFS_INODE_FILENAME_BUF_SZ (sizeof nffs_flash_buf / 2) |
| static uint8_t *nffs_inode_filename_buf0 = nffs_flash_buf; |
| static uint8_t *nffs_inode_filename_buf1 = |
| nffs_flash_buf + NFFS_INODE_FILENAME_BUF_SZ; |
| |
| /** A list of directory inodes with pending unlink operations. */ |
| static struct nffs_hash_list nffs_inode_unlink_list; |
| |
| struct nffs_inode_entry * |
| nffs_inode_entry_alloc(void) |
| { |
| struct nffs_inode_entry *inode_entry; |
| |
| inode_entry = nffs_os_mempool_get(&nffs_inode_entry_pool); |
| if (inode_entry != NULL) { |
| memset(inode_entry, 0, sizeof *inode_entry); |
| } |
| |
| return inode_entry; |
| } |
| |
| void |
| nffs_inode_entry_free(struct nffs_inode_entry *inode_entry) |
| { |
| if (inode_entry != NULL) { |
| assert(!nffs_inode_getflags(inode_entry, NFFS_INODE_FLAG_INHASH)); |
| assert(nffs_hash_id_is_inode(inode_entry->nie_hash_entry.nhe_id)); |
| nffs_os_mempool_free(&nffs_inode_entry_pool, inode_entry); |
| } |
| } |
| |
| /** |
| * Allocates a inode entry. If allocation fails due to memory exhaustion, |
| * garbage collection is performed and the allocation is retried. This |
| * process is repeated until allocation is successful or all areas have been |
| * garbage collected. |
| * |
| * @param out_inode_entry On success, the address of the allocated |
| * inode gets written here. |
| * |
| * @return 0 on successful allocation; |
| * FS_ENOMEM on memory exhaustion; |
| * other nonzero on garbage collection error. |
| */ |
| int |
| nffs_inode_entry_reserve(struct nffs_inode_entry **out_inode_entry) |
| { |
| int rc; |
| |
| do { |
| *out_inode_entry = nffs_inode_entry_alloc(); |
| } while (nffs_misc_gc_if_oom(*out_inode_entry, &rc)); |
| |
| return rc; |
| } |
| |
| uint32_t |
| nffs_inode_disk_size(const struct nffs_inode *inode) |
| { |
| return sizeof (struct nffs_disk_inode) + inode->ni_filename_len; |
| } |
| |
| int |
| nffs_inode_read_disk(uint8_t area_idx, uint32_t offset, |
| struct nffs_disk_inode *out_disk_inode) |
| { |
| int rc; |
| |
| STATS_INC(nffs_stats, nffs_readcnt_inode); |
| rc = nffs_flash_read(area_idx, offset, out_disk_inode, |
| sizeof *out_disk_inode); |
| if (rc != 0) { |
| return rc; |
| } |
| if (!nffs_hash_id_is_inode(out_disk_inode->ndi_id)) { |
| return FS_EUNEXP; |
| } |
| |
| return 0; |
| } |
| |
| int |
| nffs_inode_write_disk(const struct nffs_disk_inode *disk_inode, |
| const char *filename, uint8_t area_idx, |
| uint32_t area_offset) |
| { |
| int rc; |
| |
| /* Only the DELETED flag is ever written out to flash */ |
| assert((disk_inode->ndi_flags & ~NFFS_INODE_FLAG_DELETED) == 0); |
| |
| rc = nffs_flash_write(area_idx, area_offset, disk_inode, |
| sizeof *disk_inode); |
| if (rc != 0) { |
| return rc; |
| } |
| |
| if (disk_inode->ndi_filename_len != 0) { |
| rc = nffs_flash_write(area_idx, area_offset + sizeof *disk_inode, |
| filename, disk_inode->ndi_filename_len); |
| if (rc != 0) { |
| return rc; |
| } |
| } |
| |
| ASSERT_IF_TEST(nffs_crc_disk_inode_validate(disk_inode, area_idx, |
| area_offset) == 0); |
| |
| return 0; |
| } |
| |
| int |
| nffs_inode_calc_data_length(struct nffs_inode_entry *inode_entry, |
| uint32_t *out_len) |
| { |
| struct nffs_hash_entry *cur; |
| struct nffs_block block; |
| int rc; |
| |
| assert(nffs_hash_id_is_file(inode_entry->nie_hash_entry.nhe_id)); |
| |
| *out_len = 0; |
| |
| inode_entry->nie_blkcnt = 0; |
| cur = inode_entry->nie_last_block_entry; |
| while (cur != NULL) { |
| rc = nffs_block_from_hash_entry(&block, cur); |
| if (rc != 0) { |
| return rc; |
| } |
| |
| *out_len += block.nb_data_len; |
| inode_entry->nie_blkcnt++; |
| |
| cur = block.nb_prev; |
| } |
| |
| return 0; |
| } |
| |
| int |
| nffs_inode_data_len(struct nffs_inode_entry *inode_entry, uint32_t *out_len) |
| { |
| struct nffs_cache_inode *cache_inode; |
| int rc; |
| |
| rc = nffs_cache_inode_ensure(&cache_inode, inode_entry); |
| if (rc != 0) { |
| return rc; |
| } |
| |
| *out_len = cache_inode->nci_file_size; |
| |
| return 0; |
| } |
| |
| static void |
| nffs_inode_restore_from_dummy_entry(struct nffs_inode *out_inode, |
| struct nffs_inode_entry *inode_entry) |
| { |
| memset(out_inode, 0, sizeof *out_inode); |
| out_inode->ni_inode_entry = inode_entry; |
| } |
| |
| int |
| nffs_inode_from_entry(struct nffs_inode *out_inode, |
| struct nffs_inode_entry *entry) |
| { |
| struct nffs_disk_inode disk_inode; |
| uint32_t area_offset; |
| uint8_t area_idx; |
| int cached_name_len; |
| int rc; |
| |
| memset(out_inode, 0, sizeof *out_inode); |
| |
| if (nffs_inode_is_dummy(entry)) { |
| nffs_inode_restore_from_dummy_entry(out_inode, entry); |
| return FS_ENOENT; |
| } |
| |
| nffs_flash_loc_expand(entry->nie_hash_entry.nhe_flash_loc, |
| &area_idx, &area_offset); |
| |
| rc = nffs_inode_read_disk(area_idx, area_offset, &disk_inode); |
| if (rc != 0) { |
| return rc; |
| } |
| |
| out_inode->ni_inode_entry = entry; |
| out_inode->ni_seq = disk_inode.ndi_seq; |
| |
| /* |
| * Relink to parent if possible |
| * XXX does this belong here? |
| */ |
| if (disk_inode.ndi_parent_id == NFFS_ID_NONE) { |
| out_inode->ni_parent = NULL; |
| } else { |
| out_inode->ni_parent = nffs_hash_find_inode(disk_inode.ndi_parent_id); |
| } |
| out_inode->ni_filename_len = disk_inode.ndi_filename_len; |
| |
| if (out_inode->ni_filename_len > NFFS_SHORT_FILENAME_LEN) { |
| cached_name_len = NFFS_SHORT_FILENAME_LEN; |
| } else { |
| cached_name_len = out_inode->ni_filename_len; |
| } |
| if (cached_name_len != 0) { |
| STATS_INC(nffs_stats, nffs_readcnt_inodeent); |
| rc = nffs_flash_read(area_idx, area_offset + sizeof disk_inode, |
| out_inode->ni_filename, cached_name_len); |
| if (rc != 0) { |
| return rc; |
| } |
| } |
| |
| if (disk_inode.ndi_flags & NFFS_INODE_FLAG_DELETED) { |
| nffs_inode_setflags(out_inode->ni_inode_entry, NFFS_INODE_FLAG_DELETED); |
| nffs_inode_setflags(entry, NFFS_INODE_FLAG_DELETED); |
| } |
| |
| return 0; |
| } |
| |
| uint32_t |
| nffs_inode_parent_id(const struct nffs_inode *inode) |
| { |
| if (inode->ni_parent == NULL) { |
| return NFFS_ID_NONE; |
| } else { |
| return inode->ni_parent->nie_hash_entry.nhe_id; |
| } |
| } |
| |
| static int |
| nffs_inode_delete_blocks_from_ram(struct nffs_inode_entry *inode_entry) |
| { |
| int rc; |
| |
| assert(nffs_hash_id_is_file(inode_entry->nie_hash_entry.nhe_id)); |
| |
| while (inode_entry->nie_last_block_entry != NULL) { |
| rc = nffs_block_delete_from_ram(inode_entry->nie_last_block_entry); |
| if (rc != 0) { |
| return rc; |
| } |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * Deletes the specified inode entry from the RAM representation. |
| * |
| * @param inode_entry The inode entry to delete. |
| * @param ignore_corruption Whether to proceed, despite file system |
| * corruption errors (0/1). This should only |
| * be used when the file system is in a known |
| * bad state (e.g., during the sweep phase of |
| * the restore process). If corruption |
| * detected and ignored, this function deletes |
| * what it can and returns a success code. |
| * |
| * @return 0 on success; nonzero on failure. |
| */ |
| static int |
| nffs_inode_delete_from_ram(struct nffs_inode_entry *inode_entry, |
| int ignore_corruption) |
| { |
| int rc; |
| |
| if (nffs_hash_id_is_file(inode_entry->nie_hash_entry.nhe_id)) { |
| /* |
| * Record the intention to delete the file |
| */ |
| nffs_inode_setflags(inode_entry, NFFS_INODE_FLAG_DELETED); |
| |
| rc = nffs_inode_delete_blocks_from_ram(inode_entry); |
| if (rc == FS_ECORRUPT && ignore_corruption) { |
| inode_entry->nie_last_block_entry = NULL; |
| } else if (rc != 0) { |
| return rc; |
| } |
| } |
| |
| nffs_cache_inode_delete(inode_entry); |
| /* |
| * XXX Not deleting empty inode delete records from hash could prevent |
| * a case where we could lose delete records in a gc operation |
| */ |
| nffs_hash_remove(&inode_entry->nie_hash_entry); |
| nffs_inode_entry_free(inode_entry); |
| |
| return 0; |
| } |
| |
| /** |
| * Inserts the specified inode entry into the unlink list. Because a hash |
| * entry only has a single 'next' pointer, this function removes the entry from |
| * the hash table prior to inserting it into the unlink list. |
| * |
| * @param inode_entry The inode entry to insert. |
| */ |
| static void |
| nffs_inode_insert_unlink_list(struct nffs_inode_entry *inode_entry) |
| { |
| nffs_hash_remove(&inode_entry->nie_hash_entry); |
| SLIST_INSERT_HEAD(&nffs_inode_unlink_list, &inode_entry->nie_hash_entry, |
| nhe_next); |
| } |
| |
| static int |
| nffs_inode_dec_refcnt_priv(struct nffs_inode_entry *inode_entry, |
| int ignore_corruption) |
| { |
| int rc; |
| |
| assert(inode_entry); |
| assert(inode_entry->nie_refcnt > 0); |
| |
| inode_entry->nie_refcnt--; |
| if (inode_entry->nie_refcnt == 0) { |
| if (nffs_hash_id_is_file(inode_entry->nie_hash_entry.nhe_id)) { |
| rc = nffs_inode_delete_from_ram(inode_entry, ignore_corruption); |
| if (rc != 0) { |
| return rc; |
| } |
| } else { |
| nffs_inode_insert_unlink_list(inode_entry); |
| } |
| } |
| |
| return 0; |
| } |
| |
| int |
| nffs_inode_inc_refcnt(struct nffs_inode_entry *inode_entry) |
| { |
| assert(inode_entry); |
| inode_entry->nie_refcnt++; |
| return 0; |
| } |
| |
| /** |
| * Decrements the reference count of the specified inode entry. |
| * |
| * @param inode_entry The inode entry whose reference count |
| * should be decremented. |
| */ |
| int |
| nffs_inode_dec_refcnt(struct nffs_inode_entry *inode_entry) |
| { |
| int rc; |
| |
| rc = nffs_inode_dec_refcnt_priv(inode_entry, 0); |
| return rc; |
| } |
| |
| /** |
| * Unlinks every directory inode entry present in the unlink list. After this |
| * function completes: |
| * o Each descendant directory is deleted from RAM. |
| * o Each descendant file has its reference count decremented (and deleted |
| * from RAM if its reference count reaches zero). |
| * |
| * @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. |
| */ |
| static int |
| nffs_inode_process_unlink_list(struct nffs_hash_entry **inout_next, |
| int ignore_corruption) |
| { |
| struct nffs_inode_entry *inode_entry; |
| struct nffs_inode_entry *child_next; |
| struct nffs_inode_entry *child; |
| struct nffs_hash_entry *hash_entry; |
| int rc; |
| |
| while ((hash_entry = SLIST_FIRST(&nffs_inode_unlink_list)) != NULL) { |
| assert(nffs_hash_id_is_dir(hash_entry->nhe_id)); |
| |
| SLIST_REMOVE_HEAD(&nffs_inode_unlink_list, nhe_next); |
| |
| inode_entry = (struct nffs_inode_entry *)hash_entry; |
| |
| /* Recursively unlink each child. */ |
| child = SLIST_FIRST(&inode_entry->nie_child_list); |
| while (child != NULL) { |
| child_next = SLIST_NEXT(child, nie_sibling_next); |
| |
| if (inout_next != NULL && *inout_next == &child->nie_hash_entry) { |
| *inout_next = &child_next->nie_hash_entry; |
| } |
| |
| rc = nffs_inode_dec_refcnt_priv(child, ignore_corruption); |
| if (rc != 0) { |
| return rc; |
| } |
| |
| child = child_next; |
| } |
| |
| |
| |
| /* The directory is already removed from the hash table; just free its |
| * memory. |
| */ |
| nffs_inode_entry_free(inode_entry); |
| } |
| |
| return 0; |
| } |
| |
| int |
| nffs_inode_delete_from_disk(struct nffs_inode *inode) |
| { |
| struct nffs_disk_inode disk_inode; |
| uint32_t offset; |
| uint8_t area_idx; |
| int rc; |
| |
| /* Make sure it isn't already deleted. */ |
| assert(inode->ni_parent != NULL); |
| |
| rc = nffs_misc_reserve_space(sizeof disk_inode, &area_idx, &offset); |
| if (rc != 0) { |
| return rc; |
| } |
| |
| inode->ni_seq++; |
| |
| memset(&disk_inode, 0, sizeof disk_inode); |
| disk_inode.ndi_id = inode->ni_inode_entry->nie_hash_entry.nhe_id; |
| disk_inode.ndi_seq = inode->ni_seq; |
| disk_inode.ndi_parent_id = NFFS_ID_NONE; |
| disk_inode.ndi_flags = NFFS_INODE_FLAG_DELETED; |
| if (inode->ni_inode_entry->nie_last_block_entry) { |
| disk_inode.ndi_lastblock_id = |
| inode->ni_inode_entry->nie_last_block_entry->nhe_id; |
| } else { |
| disk_inode.ndi_lastblock_id = NFFS_ID_NONE; |
| } |
| disk_inode.ndi_filename_len = 0; |
| nffs_crc_disk_inode_fill(&disk_inode, ""); |
| |
| rc = nffs_inode_write_disk(&disk_inode, "", area_idx, offset); |
| NFFS_LOG(DEBUG, "inode_del_disk: wrote unlinked ino %x to disk ref %d\n", |
| (unsigned int)disk_inode.ndi_id, |
| inode->ni_inode_entry->nie_refcnt); |
| |
| /* |
| * Flag the incore inode as deleted to the inode won't get updated to |
| * disk. This could happen if the refcnt > 0 and there are future appends |
| * XXX only do this for files and not directories |
| */ |
| if (nffs_hash_id_is_file(inode->ni_inode_entry->nie_hash_entry.nhe_id)) { |
| nffs_inode_setflags(inode->ni_inode_entry, NFFS_INODE_FLAG_DELETED); |
| NFFS_LOG(DEBUG, "inode_delete_from_disk: ino %x flag DELETE\n", |
| (unsigned int)inode->ni_inode_entry->nie_hash_entry.nhe_id); |
| |
| } |
| |
| if (rc != 0) { |
| return rc; |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * Determines if an inode is an ancestor of another. |
| * |
| * NOTE: If the two inodes are equal, a positive result is indicated. |
| * |
| * @param anc The potential ancestor. |
| * @param des The potential descendent. |
| * @param out_is_ancestor On success, the result gets written here |
| * (1 if an ancestor relationship is |
| * detected). |
| * |
| * @return 0 on success; FS_E[...] on error. |
| */ |
| static int |
| nffs_inode_is_ancestor(struct nffs_inode_entry *anc, |
| struct nffs_inode_entry *des, |
| int *out_is_ancestor) |
| { |
| struct nffs_inode_entry *cur; |
| struct nffs_inode inode; |
| int rc; |
| |
| /* Only directories can be ancestors. */ |
| if (!nffs_hash_id_is_dir(anc->nie_hash_entry.nhe_id)) { |
| *out_is_ancestor = 0; |
| return 0; |
| } |
| |
| /* Trace des's heritage until we hit anc or there are no more parents. */ |
| cur = des; |
| while (cur != NULL && cur != anc) { |
| rc = nffs_inode_from_entry(&inode, cur); |
| if (rc != 0) { |
| *out_is_ancestor = 0; |
| return rc; |
| } |
| cur = inode.ni_parent; |
| } |
| |
| *out_is_ancestor = cur == anc; |
| return 0; |
| } |
| |
| int |
| nffs_inode_rename(struct nffs_inode_entry *inode_entry, |
| struct nffs_inode_entry *new_parent, |
| const char *new_filename) |
| { |
| struct nffs_disk_inode disk_inode; |
| struct nffs_inode inode; |
| uint32_t area_offset; |
| uint8_t area_idx; |
| int filename_len; |
| int ancestor; |
| int rc; |
| |
| /* Don't allow a directory to be moved into a descendent directory. */ |
| rc = nffs_inode_is_ancestor(inode_entry, new_parent, &ancestor); |
| if (rc != 0) { |
| return rc; |
| } |
| if (ancestor) { |
| return FS_EINVAL; |
| } |
| |
| rc = nffs_inode_from_entry(&inode, inode_entry); |
| if (rc != 0) { |
| return rc; |
| } |
| |
| if (inode.ni_parent != new_parent) { |
| if (inode.ni_parent != NULL) { |
| nffs_inode_remove_child(&inode); |
| } |
| if (new_parent != NULL) { |
| rc = nffs_inode_add_child(new_parent, inode.ni_inode_entry); |
| if (rc != 0) { |
| return rc; |
| } |
| } |
| inode.ni_parent = new_parent; |
| } |
| |
| if (new_filename != NULL) { |
| filename_len = strlen(new_filename); |
| } else { |
| filename_len = inode.ni_filename_len; |
| nffs_flash_loc_expand(inode_entry->nie_hash_entry.nhe_flash_loc, |
| &area_idx, &area_offset); |
| |
| STATS_INC(nffs_stats, nffs_readcnt_rename); |
| rc = nffs_flash_read(area_idx, |
| area_offset + sizeof (struct nffs_disk_inode), |
| nffs_flash_buf, filename_len); |
| if (rc != 0) { |
| return rc; |
| } |
| |
| new_filename = (char *)nffs_flash_buf; |
| } |
| |
| rc = nffs_misc_reserve_space(sizeof disk_inode + filename_len, |
| &area_idx, &area_offset); |
| if (rc != 0) { |
| return rc; |
| } |
| |
| memset(&disk_inode, 0, sizeof disk_inode); |
| disk_inode.ndi_id = inode_entry->nie_hash_entry.nhe_id; |
| disk_inode.ndi_seq = inode.ni_seq + 1; |
| disk_inode.ndi_parent_id = nffs_inode_parent_id(&inode); |
| disk_inode.ndi_flags = 0; |
| disk_inode.ndi_filename_len = filename_len; |
| if (inode_entry->nie_last_block_entry && |
| inode_entry->nie_last_block_entry->nhe_id != NFFS_ID_NONE) |
| disk_inode.ndi_lastblock_id = inode_entry->nie_last_block_entry->nhe_id; |
| else |
| disk_inode.ndi_lastblock_id = NFFS_ID_NONE; |
| nffs_crc_disk_inode_fill(&disk_inode, new_filename); |
| |
| rc = nffs_inode_write_disk(&disk_inode, new_filename, area_idx, |
| area_offset); |
| if (rc != 0) { |
| return rc; |
| } |
| |
| inode_entry->nie_hash_entry.nhe_flash_loc = |
| nffs_flash_loc(area_idx, area_offset); |
| |
| return 0; |
| } |
| |
| int |
| nffs_inode_update(struct nffs_inode_entry *inode_entry) |
| { |
| struct nffs_disk_inode disk_inode; |
| struct nffs_inode inode; |
| uint32_t area_offset; |
| uint8_t area_idx; |
| char *filename; |
| int filename_len; |
| int rc; |
| |
| rc = nffs_inode_from_entry(&inode, inode_entry); |
| /* |
| * if rc == FS_ENOENT, file is dummy is unlinked and so |
| * can not be updated to disk. |
| */ |
| if (rc == FS_ENOENT) |
| assert(nffs_inode_is_dummy(inode_entry)); |
| if (rc != 0) { |
| return rc; |
| } |
| |
| assert(inode_entry->nie_hash_entry.nhe_flash_loc != NFFS_FLASH_LOC_NONE); |
| |
| filename_len = inode.ni_filename_len; |
| nffs_flash_loc_expand(inode_entry->nie_hash_entry.nhe_flash_loc, |
| &area_idx, &area_offset); |
| STATS_INC(nffs_stats, nffs_readcnt_update); |
| rc = nffs_flash_read(area_idx, |
| area_offset + sizeof (struct nffs_disk_inode), |
| nffs_flash_buf, filename_len); |
| if (rc != 0) { |
| return rc; |
| } |
| |
| filename = (char *)nffs_flash_buf; |
| |
| rc = nffs_misc_reserve_space(sizeof disk_inode + filename_len, |
| &area_idx, &area_offset); |
| if (rc != 0) { |
| return rc; |
| } |
| |
| memset(&disk_inode, 0, sizeof disk_inode); |
| disk_inode.ndi_id = inode_entry->nie_hash_entry.nhe_id; |
| disk_inode.ndi_seq = inode.ni_seq + 1; |
| disk_inode.ndi_parent_id = nffs_inode_parent_id(&inode); |
| disk_inode.ndi_flags = 0; |
| disk_inode.ndi_filename_len = filename_len; |
| |
| assert(nffs_hash_id_is_block(inode_entry->nie_last_block_entry->nhe_id)); |
| disk_inode.ndi_lastblock_id = inode_entry->nie_last_block_entry->nhe_id; |
| |
| nffs_crc_disk_inode_fill(&disk_inode, filename); |
| |
| NFFS_LOG(DEBUG, "nffs_inode_update writing inode %x last block %x\n", |
| (unsigned int)disk_inode.ndi_id, |
| (unsigned int)disk_inode.ndi_lastblock_id); |
| |
| rc = nffs_inode_write_disk(&disk_inode, filename, area_idx, |
| area_offset); |
| if (rc != 0) { |
| return rc; |
| } |
| |
| inode_entry->nie_hash_entry.nhe_flash_loc = |
| nffs_flash_loc(area_idx, area_offset); |
| return 0; |
| } |
| |
| static int |
| nffs_inode_read_filename_chunk(const struct nffs_inode *inode, |
| uint8_t filename_offset, void *buf, int len) |
| { |
| uint32_t area_offset; |
| uint8_t area_idx; |
| int rc; |
| |
| assert(filename_offset + len <= inode->ni_filename_len); |
| |
| nffs_flash_loc_expand(inode->ni_inode_entry->nie_hash_entry.nhe_flash_loc, |
| &area_idx, &area_offset); |
| area_offset += sizeof (struct nffs_disk_inode) + filename_offset; |
| |
| STATS_INC(nffs_stats, nffs_readcnt_filename); |
| rc = nffs_flash_read(area_idx, area_offset, buf, len); |
| if (rc != 0) { |
| return rc; |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * Retrieves the filename of the specified inode. The retrieved |
| * filename is always null-terminated. To ensure enough space to hold the full |
| * filename plus a null-termintor, a destination buffer of size |
| * (NFFS_FILENAME_MAX_LEN + 1) should be used. |
| * |
| * @param inode_entry The inode entry to query. |
| * @param max_len The size of the "out_name" character buffer. |
| * @param out_name On success, the entry's filename is written |
| * here; always null-terminated. |
| * @param out_name_len On success, contains the actual length of the |
| * filename, NOT including the |
| * null-terminator. |
| * |
| * @return 0 on success; nonzero on failure. |
| */ |
| int |
| nffs_inode_read_filename(struct nffs_inode_entry *inode_entry, size_t max_len, |
| char *out_name, uint8_t *out_full_len) |
| { |
| struct nffs_inode inode; |
| int read_len; |
| int rc; |
| |
| rc = nffs_inode_from_entry(&inode, inode_entry); |
| if (rc != 0) { |
| return rc; |
| } |
| |
| if (max_len > inode.ni_filename_len) { |
| read_len = inode.ni_filename_len; |
| } else { |
| read_len = max_len - 1; |
| } |
| |
| rc = nffs_inode_read_filename_chunk(&inode, 0, out_name, read_len); |
| if (rc != 0) { |
| return rc; |
| } |
| |
| out_name[read_len] = '\0'; |
| |
| return 0; |
| } |
| |
| int |
| nffs_inode_add_child(struct nffs_inode_entry *parent, |
| struct nffs_inode_entry *child) |
| { |
| struct nffs_inode_entry *prev; |
| struct nffs_inode_entry *cur; |
| struct nffs_inode child_inode; |
| struct nffs_inode cur_inode; |
| int cmp; |
| int rc; |
| |
| assert(nffs_hash_id_is_dir(parent->nie_hash_entry.nhe_id)); |
| assert(!nffs_inode_getflags(child, NFFS_INODE_FLAG_INTREE)); |
| |
| rc = nffs_inode_from_entry(&child_inode, child); |
| if (rc != 0) { |
| return rc; |
| } |
| |
| prev = NULL; |
| SLIST_FOREACH(cur, &parent->nie_child_list, nie_sibling_next) { |
| assert(cur != child); |
| rc = nffs_inode_from_entry(&cur_inode, cur); |
| if (rc != 0) { |
| return rc; |
| } |
| |
| rc = nffs_inode_filename_cmp_flash(&child_inode, &cur_inode, &cmp); |
| if (rc != 0) { |
| return rc; |
| } |
| |
| if (cmp < 0) { |
| break; |
| } |
| |
| prev = cur; |
| } |
| |
| if (prev == NULL) { |
| SLIST_INSERT_HEAD(&parent->nie_child_list, child, nie_sibling_next); |
| } else { |
| SLIST_INSERT_AFTER(prev, child, nie_sibling_next); |
| } |
| nffs_inode_setflags(child, NFFS_INODE_FLAG_INTREE); |
| |
| return 0; |
| } |
| |
| void |
| nffs_inode_remove_child(struct nffs_inode *child) |
| { |
| struct nffs_inode_entry *parent; |
| |
| assert(nffs_inode_getflags(child->ni_inode_entry, NFFS_INODE_FLAG_INTREE)); |
| parent = child->ni_parent; |
| assert(parent != NULL); |
| assert(nffs_hash_id_is_dir(parent->nie_hash_entry.nhe_id)); |
| SLIST_REMOVE(&parent->nie_child_list, child->ni_inode_entry, |
| nffs_inode_entry, nie_sibling_next); |
| SLIST_NEXT(child->ni_inode_entry, nie_sibling_next) = NULL; |
| nffs_inode_unsetflags(child->ni_inode_entry, NFFS_INODE_FLAG_INTREE); |
| } |
| |
| int |
| nffs_inode_filename_cmp_ram(const struct nffs_inode *inode, |
| const char *name, int name_len, |
| int *result) |
| { |
| int short_len; |
| int chunk_len; |
| int rem_len; |
| int off; |
| int rc; |
| |
| if (name_len < inode->ni_filename_len) { |
| short_len = name_len; |
| } else { |
| short_len = inode->ni_filename_len; |
| } |
| |
| if (short_len <= NFFS_SHORT_FILENAME_LEN) { |
| chunk_len = short_len; |
| } else { |
| chunk_len = NFFS_SHORT_FILENAME_LEN; |
| } |
| *result = strncmp((char *)inode->ni_filename, name, chunk_len); |
| |
| off = chunk_len; |
| while (*result == 0 && off < short_len) { |
| rem_len = short_len - off; |
| if (rem_len > NFFS_INODE_FILENAME_BUF_SZ) { |
| chunk_len = NFFS_INODE_FILENAME_BUF_SZ; |
| } else { |
| chunk_len = rem_len; |
| } |
| |
| rc = nffs_inode_read_filename_chunk(inode, off, |
| nffs_inode_filename_buf0, |
| chunk_len); |
| if (rc != 0) { |
| return rc; |
| } |
| |
| *result = strncmp((char *)nffs_inode_filename_buf0, name + off, |
| chunk_len); |
| off += chunk_len; |
| } |
| |
| if (*result == 0) { |
| *result = inode->ni_filename_len - name_len; |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * Compare filenames in flash |
| */ |
| int |
| nffs_inode_filename_cmp_flash(const struct nffs_inode *inode1, |
| const struct nffs_inode *inode2, |
| int *result) |
| { |
| int short_len; |
| int chunk_len; |
| int rem_len; |
| int off; |
| int rc; |
| |
| if (inode1->ni_filename_len < inode2->ni_filename_len) { |
| short_len = inode1->ni_filename_len; |
| } else { |
| short_len = inode2->ni_filename_len; |
| } |
| |
| if (short_len <= NFFS_SHORT_FILENAME_LEN) { |
| chunk_len = short_len; |
| } else { |
| chunk_len = NFFS_SHORT_FILENAME_LEN; |
| } |
| *result = strncmp((char *)inode1->ni_filename, |
| (char *)inode2->ni_filename, |
| chunk_len); |
| |
| off = chunk_len; |
| while (*result == 0 && off < short_len) { |
| rem_len = short_len - off; |
| if (rem_len > NFFS_INODE_FILENAME_BUF_SZ) { |
| chunk_len = NFFS_INODE_FILENAME_BUF_SZ; |
| } else { |
| chunk_len = rem_len; |
| } |
| |
| rc = nffs_inode_read_filename_chunk(inode1, off, |
| nffs_inode_filename_buf0, |
| chunk_len); |
| if (rc != 0) { |
| return rc; |
| } |
| |
| rc = nffs_inode_read_filename_chunk(inode2, off, |
| nffs_inode_filename_buf1, |
| chunk_len); |
| if (rc != 0) { |
| return rc; |
| } |
| |
| *result = strncmp((char *)nffs_inode_filename_buf0, |
| (char *)nffs_inode_filename_buf1, |
| chunk_len); |
| off += chunk_len; |
| } |
| |
| if (*result == 0) { |
| *result = inode1->ni_filename_len - inode2->ni_filename_len; |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * Finds the set of blocks composing the specified address range within the |
| * supplied file inode. This information is returned in the form of a |
| * seek_info object. |
| * |
| * @param inode_entry The file inode to seek within. |
| * @param offset The start address of the region to seek to. |
| * @param length The length of the region to seek to. |
| * @param out_seek_info On success, this gets populated with the result |
| * of the seek operation. |
| * |
| * @return 0 on success; nonzero on failure. |
| */ |
| int |
| nffs_inode_seek(struct nffs_inode_entry *inode_entry, uint32_t offset, |
| uint32_t length, struct nffs_seek_info *out_seek_info) |
| { |
| struct nffs_cache_inode *cache_inode; |
| struct nffs_hash_entry *cur_entry; |
| struct nffs_block block; |
| uint32_t block_start; |
| uint32_t cur_offset; |
| uint32_t seek_end; |
| int rc; |
| |
| assert(length > 0); |
| assert(nffs_hash_id_is_file(inode_entry->nie_hash_entry.nhe_id)); |
| |
| rc = nffs_cache_inode_ensure(&cache_inode, inode_entry); |
| if (rc != 0) { |
| return rc; |
| } |
| |
| if (offset > cache_inode->nci_file_size) { |
| return FS_EOFFSET; |
| } |
| if (offset == cache_inode->nci_file_size) { |
| memset(&out_seek_info->nsi_last_block, 0, |
| sizeof out_seek_info->nsi_last_block); |
| out_seek_info->nsi_last_block.nb_hash_entry = NULL; |
| out_seek_info->nsi_block_file_off = 0; |
| out_seek_info->nsi_file_len = cache_inode->nci_file_size; |
| return 0; |
| } |
| |
| seek_end = offset + length; |
| |
| cur_entry = inode_entry->nie_last_block_entry; |
| cur_offset = cache_inode->nci_file_size; |
| |
| while (1) { |
| rc = nffs_block_from_hash_entry(&block, cur_entry); |
| if (rc != 0) { |
| return rc; |
| } |
| |
| block_start = cur_offset - block.nb_data_len; |
| if (seek_end > block_start) { |
| out_seek_info->nsi_last_block = block; |
| out_seek_info->nsi_block_file_off = block_start; |
| out_seek_info->nsi_file_len = cache_inode->nci_file_size; |
| return 0; |
| } |
| |
| cur_offset = block_start; |
| cur_entry = block.nb_prev; |
| } |
| } |
| |
| /** |
| * Reads data from the specified file inode. |
| * |
| * @param inode_entry The inode to read from. |
| * @param offset The offset within the file to start the read |
| * at. |
| * @param len The number of bytes to attempt to read. |
| * @param out_data On success, the read data gets written here. |
| * @param out_len On success, the number of bytes actually read |
| * gets written here. |
| * |
| * @return 0 on success; nonzero on failure. |
| */ |
| int |
| nffs_inode_read(struct nffs_inode_entry *inode_entry, uint32_t offset, |
| uint32_t len, void *out_data, uint32_t *out_len) |
| { |
| struct nffs_cache_inode *cache_inode; |
| struct nffs_cache_block *cache_block; |
| uint32_t block_end; |
| uint32_t dst_off; |
| uint32_t src_off; |
| uint32_t src_end; |
| uint16_t block_off; |
| uint16_t chunk_sz; |
| uint8_t *dptr; |
| int rc; |
| |
| if (len == 0) { |
| if (out_len != NULL) { |
| *out_len = 0; |
| } |
| return 0; |
| } |
| |
| rc = nffs_cache_inode_ensure(&cache_inode, inode_entry); |
| if (rc != 0) { |
| return rc; |
| } |
| |
| src_end = offset + len; |
| if (src_end > cache_inode->nci_file_size) { |
| src_end = cache_inode->nci_file_size; |
| } |
| |
| /* Initialize variables for the first iteration. */ |
| dst_off = src_end - offset; |
| src_off = src_end; |
| dptr = out_data; |
| cache_block = NULL; |
| |
| /* Read each relevant block into the destination buffer, iterating in |
| * reverse. |
| */ |
| while (dst_off > 0) { |
| if (cache_block == NULL) { |
| rc = nffs_cache_seek(cache_inode, src_off - 1, &cache_block); |
| if (rc != 0) { |
| return rc; |
| } |
| } |
| |
| if (cache_block->ncb_file_offset < offset) { |
| block_off = offset - cache_block->ncb_file_offset; |
| } else { |
| block_off = 0; |
| } |
| |
| block_end = cache_block->ncb_file_offset + |
| cache_block->ncb_block.nb_data_len; |
| chunk_sz = cache_block->ncb_block.nb_data_len - block_off; |
| if (block_end > src_end) { |
| chunk_sz -= block_end - src_end; |
| } |
| |
| dst_off -= chunk_sz; |
| src_off -= chunk_sz; |
| |
| rc = nffs_block_read_data(&cache_block->ncb_block, block_off, chunk_sz, |
| dptr + dst_off); |
| if (rc != 0) { |
| return rc; |
| } |
| |
| cache_block = TAILQ_PREV(cache_block, nffs_cache_block_list, ncb_link); |
| } |
| |
| if (out_len != NULL) { |
| *out_len = src_end - offset; |
| } |
| |
| return 0; |
| } |
| |
| static int |
| nffs_inode_unlink_from_ram_priv(struct nffs_inode *inode, |
| int ignore_corruption, |
| struct nffs_hash_entry **out_next) |
| { |
| int rc; |
| |
| if (inode->ni_parent != NULL) { |
| nffs_inode_remove_child(inode); |
| } |
| |
| /* |
| * Regardless of whether the inode is removed from hashlist, we record |
| * the intention to delete it here. |
| */ |
| nffs_inode_setflags(inode->ni_inode_entry, NFFS_INODE_FLAG_DELETED); |
| |
| if (nffs_hash_id_is_dir(inode->ni_inode_entry->nie_hash_entry.nhe_id)) { |
| nffs_inode_insert_unlink_list(inode->ni_inode_entry); |
| rc = nffs_inode_process_unlink_list(out_next, ignore_corruption); |
| } else { |
| rc = nffs_inode_dec_refcnt_priv(inode->ni_inode_entry, |
| ignore_corruption); |
| } |
| if (rc != 0) { |
| return rc; |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * Unlinks the specified inode from the RAM representation. If this procedure |
| * causes the inode's reference count to drop to zero, the inode is deleted |
| * from RAM. This function does not write anything to disk. |
| * |
| * @param inode The inode to unlink. |
| * @param out_next This parameter is only necessary if you are |
| * calling this function during an iteration |
| * of the entire hash table; pass null |
| * otherwise. |
| * On output, this points to the next hash entry |
| * that should be processed. |
| * |
| * @return 0 on success; nonzero on failure. |
| */ |
| int |
| nffs_inode_unlink_from_ram(struct nffs_inode *inode, |
| struct nffs_hash_entry **out_next) |
| { |
| int rc; |
| |
| rc = nffs_inode_unlink_from_ram_priv(inode, 0, out_next); |
| return rc; |
| } |
| |
| /** |
| * Deletes the specified inode entry from the RAM representation. If file |
| * system corruption is detected, this function completes as much of the unlink |
| * process as it cam and returns a success code. This should only be used when |
| * the file system is in a known bad state (e.g., during the sweep phase of the |
| * restore process). |
| * |
| * @param inode_entry The inode entry to delete. |
| * @param out_next This parameter is only necessary if you are |
| * calling this function during an iteration |
| * of the entire hash table; pass null |
| * otherwise. |
| * On output, this points to the next hash entry |
| * that should be processed. |
| * |
| * @return 0 on success; nonzero on failure. |
| */ |
| int |
| nffs_inode_unlink_from_ram_corrupt_ok(struct nffs_inode *inode, |
| struct nffs_hash_entry **out_next) |
| { |
| int rc; |
| |
| rc = nffs_inode_unlink_from_ram_priv(inode, 1, out_next); |
| return rc; |
| } |
| |
| |
| /** |
| * Unlinks the file or directory represented by the specified inode. If the |
| * inode represents a directory, all the directory's descendants are |
| * recursively unlinked. Any open file handles refering to an unlinked file |
| * remain valid, and can be read from and written to. |
| * |
| * When an inode is unlinked, the following events occur: |
| * o inode deletion record is written to disk. |
| * o inode is removed from parent's child list. |
| * o inode's reference count is decreased (if this brings it to zero, the |
| * inode is fully deleted from RAM). |
| * |
| * @param inode The inode to unlink. |
| * |
| * @return 0 on success; nonzero on failure. |
| */ |
| int |
| nffs_inode_unlink(struct nffs_inode *inode) |
| { |
| int rc; |
| |
| rc = nffs_inode_delete_from_disk(inode); |
| if (rc != 0) { |
| return rc; |
| } |
| |
| rc = nffs_inode_unlink_from_ram(inode, NULL); |
| if (rc != 0) { |
| return rc; |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * Return true if inode is a dummy inode, that was allocated as a |
| * place holder in the case that an inode is restored before it's parent. |
| */ |
| int |
| nffs_inode_is_dummy(struct nffs_inode_entry *inode_entry) |
| { |
| if (inode_entry->nie_flash_loc == NFFS_FLASH_LOC_NONE) { |
| /* |
| * set if not already XXX can delete after debug |
| */ |
| nffs_inode_setflags(inode_entry, NFFS_INODE_FLAG_DUMMY); |
| return 1; |
| } |
| |
| if (inode_entry == nffs_root_dir) { |
| return 0; |
| } else { |
| return nffs_inode_getflags(inode_entry, NFFS_INODE_FLAG_DUMMY); |
| } |
| } |
| |
| /* |
| * Return true if inode is marked as deleted. |
| */ |
| int |
| nffs_inode_is_deleted(struct nffs_inode_entry *inode_entry) |
| { |
| assert(inode_entry); |
| |
| return nffs_inode_getflags(inode_entry, NFFS_INODE_FLAG_DELETED); |
| } |
| |
| int |
| nffs_inode_setflags(struct nffs_inode_entry *entry, uint8_t flag) |
| { |
| /* |
| * We shouldn't be setting flags to already deleted inodes |
| */ |
| entry->nie_flags |= flag; |
| return (int)entry->nie_flags; |
| } |
| |
| int |
| nffs_inode_unsetflags(struct nffs_inode_entry *entry, uint8_t flag) |
| { |
| entry->nie_flags &= ~flag; |
| return (int)entry->nie_flags; |
| } |
| |
| int |
| nffs_inode_getflags(struct nffs_inode_entry *entry, uint8_t flag) |
| { |
| return (int)(entry->nie_flags & flag); |
| } |