/** * 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 "os/os_malloc.h" #include "nffs/nffs.h" #include "nffs_priv.h" /** * Determines if the file system contains a valid root directory. For the root * directory to be valid, it must be present and have the following traits: * o ID equal to NFFS_ID_ROOT_DIR. * o No parent inode. * * @return 0 if there is a valid root directory; * FS_ECORRUPT if there is not a valid root * directory; * nonzero on other error. */ int nffs_misc_validate_root_dir(void) { struct nffs_inode inode; int rc; if (nffs_root_dir == NULL) { return FS_ECORRUPT; } if (nffs_root_dir->nie_hash_entry.nhe_id != NFFS_ID_ROOT_DIR) { return FS_ECORRUPT; } rc = nffs_inode_from_entry(&inode, nffs_root_dir); /* * nffs_root_dir is automatically flagged a "dummy" inode but it's special */ if (rc != 0 && rc != FS_ENOENT) { return rc; } if (inode.ni_parent != NULL) { return FS_ECORRUPT; } return 0; } /** * Determines if the system contains a valid scratch area. For a scratch area * to be valid, it must be at least as large as the other areas in the file * system. * * @return 0 if there is a valid scratch area; * FS_ECORRUPT otherwise. */ int nffs_misc_validate_scratch(void) { uint32_t scratch_len; int i; if (nffs_scratch_area_idx == NFFS_AREA_ID_NONE) { /* No scratch area. */ return FS_ECORRUPT; } scratch_len = nffs_areas[nffs_scratch_area_idx].na_length; for (i = 0; i < nffs_num_areas; i++) { if (nffs_areas[i].na_length > scratch_len) { return FS_ECORRUPT; } } return 0; } /** * Performs a garbage cycle to free up memory, if necessary. This function * should be called repeatedly until either: * o The subsequent allocation is successful, or * o Garbage collection is not successfully performed (indicated by a return * code other than FS_EAGAIN). * * This function determines if garbage collection is necessary by inspecting * the value of the supplied "resource" parameter. If resource is null, that * implies that allocation failed. * * This function will not initiate garbage collection if all areas have already * been collected in an attempt to free memory for the allocation in question. * * @param resource The result of the allocation attempt; null * implies that garbage collection is * necessary. * @param out_rc The status of this operation gets written here. * 0: garbage collection was successful or * unnecessary. * FS_EFULL: Garbage collection was not * performed because all areas have * already been collected. * Other nonzero: garbage collection failed. * * @return FS_EAGAIN if garbage collection was * successfully performed and the allocation * should be retried; * Other value if the allocation should not be * retried; the value of the out_rc parameter * indicates whether allocation was successful * or there was an error. */ int nffs_misc_gc_if_oom(void *resource, int *out_rc) { /** * Keeps track of the number of repeated garbage collection cycles. * Necessary for ensuring GC stops after all areas have been collected. */ static uint8_t total_gc_cycles; if (resource != NULL) { /* Allocation succeeded. Reset cycle count in preparation for the next * allocation failure. */ total_gc_cycles = 0; *out_rc = 0; return 0; } /* If every area has already been garbage collected, there is nothing else * that can be done ("- 1" to account for the scratch area). */ if (total_gc_cycles >= nffs_num_areas - 1) { *out_rc = FS_ENOMEM; return 0; } /* Attempt a garbage collection on the next area. */ *out_rc = nffs_gc(NULL); total_gc_cycles++; if (*out_rc != 0) { return 0; } /* Indicate that garbage collection was successfully performed. */ return 1; } /** * Reserves the specified number of bytes within the specified area. * * @param area_idx The index of the area to reserve from. * @param space The number of bytes of free space required. * @param out_area_offset On success, the offset within the area gets * written here. * * @return 0 on success; * FS_EFULL if the area has insufficient free * space. */ static int nffs_misc_reserve_space_area(uint8_t area_idx, uint16_t space, uint32_t *out_area_offset) { const struct nffs_area *area; uint32_t available; area = nffs_areas + area_idx; available = area->na_length - area->na_cur; if (available >= space) { *out_area_offset = area->na_cur; return 0; } return FS_EFULL; } /** * Finds an area that can accommodate an object of the specified size. If no * such area exists, this function performs a garbage collection cycle. * * @param space The number of bytes of free space required. * @param out_area_idx On success, the index of the suitable area gets * written here. * @param out_area_offset On success, the offset within the suitable area * gets written here. * * @return 0 on success; nonzero on failure. */ int nffs_misc_reserve_space(uint16_t space, uint8_t *out_area_idx, uint32_t *out_area_offset) { uint8_t area_idx; int rc; int i; /* Find the first area with sufficient free space. */ for (i = 0; i < nffs_num_areas; i++) { if (i != nffs_scratch_area_idx) { rc = nffs_misc_reserve_space_area(i, space, out_area_offset); if (rc == 0) { *out_area_idx = i; return 0; } } } /* No area can accommodate the request. Garbage collect until an area * has enough space. */ rc = nffs_gc_until(space, &area_idx); if (rc != 0) { return rc; } /* Now try to reserve space. If insufficient space was reclaimed with * garbage collection, the above call would have failed, so this should * succeed. */ rc = nffs_misc_reserve_space_area(area_idx, space, out_area_offset); assert(rc == 0); *out_area_idx = area_idx; return rc; } int nffs_misc_set_num_areas(uint8_t num_areas) { if (num_areas == 0) { free(nffs_areas); nffs_areas = NULL; } else { nffs_areas = realloc(nffs_areas, num_areas * sizeof *nffs_areas); if (nffs_areas == NULL) { return FS_ENOMEM; } } nffs_num_areas = num_areas; return 0; } /** * Calculates the data length of the largest block that could fit in an area of * the specified size. */ static uint32_t nffs_misc_area_capacity_one(uint32_t area_length) { return area_length - sizeof (struct nffs_disk_area) - sizeof (struct nffs_disk_block); } /** * Calculates the data length of the largest block that could fit as a pair in * an area of the specified size. */ static uint32_t nffs_misc_area_capacity_two(uint32_t area_length) { return (area_length - sizeof (struct nffs_disk_area)) / 2 - sizeof (struct nffs_disk_block); } /** * Calculates and sets the maximum block data length that the system supports. * The result of the calculation is the greatest number which satisfies all of * the following restrictions: * o No more than half the size of the smallest area. * o No more than 2048. * o No smaller than the data length of any existing data block. * * @param min_size The minimum allowed data length. This is the * data length of the largest block currently * in the file system. * * @return 0 on success; nonzero on failure. */ int nffs_misc_set_max_block_data_len(uint16_t min_data_len) { uint32_t smallest_area; uint32_t half_smallest; int i; smallest_area = -1; for (i = 0; i < nffs_num_areas; i++) { if (nffs_areas[i].na_length < smallest_area) { smallest_area = nffs_areas[i].na_length; } } /* Don't allow a data block size bigger than the smallest area. */ if (nffs_misc_area_capacity_one(smallest_area) < min_data_len) { return FS_ECORRUPT; } half_smallest = nffs_misc_area_capacity_two(smallest_area); if (half_smallest < NFFS_BLOCK_MAX_DATA_SZ_MAX) { nffs_block_max_data_sz = half_smallest; } else { nffs_block_max_data_sz = NFFS_BLOCK_MAX_DATA_SZ_MAX; } if (nffs_block_max_data_sz < min_data_len) { nffs_block_max_data_sz = min_data_len; } return 0; } int nffs_misc_create_lost_found_dir(void) { int rc; rc = nffs_path_new_dir("/lost+found", &nffs_lost_found_dir); switch (rc) { case 0: return 0; case FS_EEXIST: rc = nffs_path_find_inode_entry("/lost+found", &nffs_lost_found_dir); return rc; default: return rc; } } /** * Fully resets the nffs RAM representation. * * @return 0 on success; nonzero on failure. */ int nffs_misc_reset(void) { int rc; nffs_cache_clear(); rc = os_mempool_init(&nffs_file_pool, nffs_config.nc_num_files, sizeof (struct nffs_file), nffs_file_mem, "nffs_file_pool"); if (rc != 0) { return FS_EOS; } rc = os_mempool_init(&nffs_inode_entry_pool, nffs_config.nc_num_inodes, sizeof (struct nffs_inode_entry), nffs_inode_mem, "nffs_inode_entry_pool"); if (rc != 0) { return FS_EOS; } rc = os_mempool_init(&nffs_block_entry_pool, nffs_config.nc_num_blocks, sizeof (struct nffs_hash_entry), nffs_block_entry_mem, "nffs_block_entry_pool"); if (rc != 0) { return FS_EOS; } rc = os_mempool_init(&nffs_cache_inode_pool, nffs_config.nc_num_cache_inodes, sizeof (struct nffs_cache_inode), nffs_cache_inode_mem, "nffs_cache_inode_pool"); if (rc != 0) { return FS_EOS; } rc = os_mempool_init(&nffs_cache_block_pool, nffs_config.nc_num_cache_blocks, sizeof (struct nffs_cache_block), nffs_cache_block_mem, "nffs_cache_block_pool"); if (rc != 0) { return FS_EOS; } rc = os_mempool_init(&nffs_dir_pool, nffs_config.nc_num_dirs, sizeof (struct nffs_dir), nffs_dir_mem, "nffs_dir_pool"); if (rc != 0) { return FS_EOS; } rc = nffs_hash_init(); if (rc != 0) { return rc; } free(nffs_areas); nffs_areas = NULL; nffs_num_areas = 0; nffs_root_dir = NULL; nffs_lost_found_dir = NULL; nffs_scratch_area_idx = NFFS_AREA_ID_NONE; nffs_hash_next_file_id = NFFS_ID_FILE_MIN; nffs_hash_next_dir_id = NFFS_ID_DIR_MIN; nffs_hash_next_block_id = NFFS_ID_BLOCK_MIN; return 0; } /** * Indicates whether a valid filesystem has been initialized, either via * detection or formatting. * * @return 1 if a file system is present; 0 otherwise. */ int nffs_misc_ready(void) { return nffs_root_dir != NULL; }