| /* | 
 |  * 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 <assert.h> | 
 | #include <string.h> | 
 | #include <nffs/nffs.h> | 
 | #include <nffs/os.h> | 
 |  | 
 | TAILQ_HEAD(nffs_cache_inode_list, nffs_cache_inode); | 
 | static struct nffs_cache_inode_list nffs_cache_inode_list = | 
 |     TAILQ_HEAD_INITIALIZER(nffs_cache_inode_list); | 
 |  | 
 | static void nffs_cache_reclaim_blocks(void); | 
 |  | 
 | static struct nffs_cache_block * | 
 | nffs_cache_block_alloc(void) | 
 | { | 
 |     struct nffs_cache_block *entry; | 
 |  | 
 |     entry = nffs_os_mempool_get(&nffs_cache_block_pool); | 
 |     if (entry != NULL) { | 
 |         memset(entry, 0, sizeof *entry); | 
 |     } | 
 |  | 
 |     return entry; | 
 | } | 
 |  | 
 | static void | 
 | nffs_cache_block_free(struct nffs_cache_block *entry) | 
 | { | 
 |     if (entry != NULL) { | 
 |         nffs_os_mempool_free(&nffs_cache_block_pool, entry); | 
 |     } | 
 | } | 
 |  | 
 | static struct nffs_cache_block * | 
 | nffs_cache_block_acquire(void) | 
 | { | 
 |     struct nffs_cache_block *cache_block; | 
 |  | 
 |     cache_block = nffs_cache_block_alloc(); | 
 |     if (cache_block == NULL) { | 
 |         nffs_cache_reclaim_blocks(); | 
 |         cache_block = nffs_cache_block_alloc(); | 
 |     } | 
 |  | 
 |     assert(cache_block != NULL); | 
 |  | 
 |     return cache_block; | 
 | } | 
 |  | 
 | static int | 
 | nffs_cache_block_populate(struct nffs_cache_block *cache_block, | 
 |                           struct nffs_hash_entry *block_entry, | 
 |                           uint32_t end_offset) | 
 | { | 
 |     int rc; | 
 |  | 
 |     rc = nffs_block_from_hash_entry(&cache_block->ncb_block, block_entry); | 
 |     if (rc != 0) { | 
 |         return rc; | 
 |     } | 
 |  | 
 |     cache_block->ncb_file_offset = end_offset - | 
 |                                    cache_block->ncb_block.nb_data_len; | 
 |  | 
 |     return 0; | 
 | } | 
 |  | 
 | static struct nffs_cache_inode * | 
 | nffs_cache_inode_alloc(void) | 
 | { | 
 |     struct nffs_cache_inode *entry; | 
 |  | 
 |     entry = nffs_os_mempool_get(&nffs_cache_inode_pool); | 
 |     if (entry != NULL) { | 
 |         memset(entry, 0, sizeof *entry); | 
 |         TAILQ_INIT(&entry->nci_block_list); | 
 |     } | 
 |  | 
 |     return entry; | 
 | } | 
 |  | 
 | static void | 
 | nffs_cache_inode_free_blocks(struct nffs_cache_inode *cache_inode) | 
 | { | 
 |     struct nffs_cache_block *cache_block; | 
 |  | 
 |     while ((cache_block = TAILQ_FIRST(&cache_inode->nci_block_list)) != NULL) { | 
 |         TAILQ_REMOVE(&cache_inode->nci_block_list, cache_block, ncb_link); | 
 |         nffs_cache_block_free(cache_block); | 
 |     } | 
 | } | 
 |  | 
 | static void | 
 | nffs_cache_inode_free(struct nffs_cache_inode *entry) | 
 | { | 
 |     if (entry != NULL) { | 
 |         nffs_cache_inode_free_blocks(entry); | 
 |         nffs_os_mempool_free(&nffs_cache_inode_pool, entry); | 
 |     } | 
 | } | 
 |  | 
 | static struct nffs_cache_inode * | 
 | nffs_cache_inode_acquire(void) | 
 | { | 
 |     struct nffs_cache_inode *entry; | 
 |  | 
 |     entry = nffs_cache_inode_alloc(); | 
 |     if (entry == NULL) { | 
 |         entry = TAILQ_LAST(&nffs_cache_inode_list, nffs_cache_inode_list); | 
 |         assert(entry != NULL); | 
 |  | 
 |         TAILQ_REMOVE(&nffs_cache_inode_list, entry, nci_link); | 
 |         nffs_cache_inode_free(entry); | 
 |  | 
 |         entry = nffs_cache_inode_alloc(); | 
 |     } | 
 |  | 
 |     assert(entry != NULL); | 
 |  | 
 |     return entry; | 
 | } | 
 |  | 
 | static int | 
 | nffs_cache_inode_populate(struct nffs_cache_inode *cache_inode, | 
 |                          struct nffs_inode_entry *inode_entry) | 
 | { | 
 |     int rc; | 
 |  | 
 |     memset(cache_inode, 0, sizeof *cache_inode); | 
 |  | 
 |     rc = nffs_inode_from_entry(&cache_inode->nci_inode, inode_entry); | 
 |     if (rc != 0) { | 
 |         return rc; | 
 |     } | 
 |  | 
 |     rc = nffs_inode_calc_data_length(cache_inode->nci_inode.ni_inode_entry, | 
 |                                      &cache_inode->nci_file_size); | 
 |     if (rc != 0) { | 
 |         return rc; | 
 |     } | 
 |  | 
 |     return 0; | 
 | } | 
 |  | 
 | /** | 
 |  * Retrieves the block entry corresponding to the last cached block in the | 
 |  * specified inode's list.  If the inode has no cached blocks, this function | 
 |  * returns null. | 
 |  */ | 
 | static struct nffs_hash_entry * | 
 | nffs_cache_inode_last_entry(struct nffs_cache_inode *cache_inode) | 
 | { | 
 |     struct nffs_cache_block *cache_block; | 
 |  | 
 |     if (TAILQ_EMPTY(&cache_inode->nci_block_list)) { | 
 |         return NULL; | 
 |     } | 
 |  | 
 |     cache_block = TAILQ_LAST(&cache_inode->nci_block_list, | 
 |                              nffs_cache_block_list); | 
 |     return cache_block->ncb_block.nb_hash_entry; | 
 | } | 
 |  | 
 | static struct nffs_cache_inode * | 
 | nffs_cache_inode_find(const struct nffs_inode_entry *inode_entry) | 
 | { | 
 |     struct nffs_cache_inode *cur; | 
 |  | 
 |     TAILQ_FOREACH(cur, &nffs_cache_inode_list, nci_link) { | 
 |         if (cur->nci_inode.ni_inode_entry == inode_entry) { | 
 |             return cur; | 
 |         } | 
 |     } | 
 |  | 
 |     return NULL; | 
 | } | 
 |  | 
 | void | 
 | nffs_cache_inode_range(const struct nffs_cache_inode *cache_inode, | 
 |                       uint32_t *out_start, uint32_t *out_end) | 
 | { | 
 |     struct nffs_cache_block *cache_block; | 
 |  | 
 |     cache_block = TAILQ_FIRST(&cache_inode->nci_block_list); | 
 |     if (cache_block == NULL) { | 
 |         *out_start = 0; | 
 |         *out_end = 0; | 
 |         return; | 
 |     } | 
 |  | 
 |     *out_start = cache_block->ncb_file_offset; | 
 |  | 
 |     cache_block = TAILQ_LAST(&cache_inode->nci_block_list, | 
 |                              nffs_cache_block_list); | 
 |     *out_end = cache_block->ncb_file_offset + | 
 |                cache_block->ncb_block.nb_data_len; | 
 | } | 
 |  | 
 | static void | 
 | nffs_cache_reclaim_blocks(void) | 
 | { | 
 |     struct nffs_cache_inode *cache_inode; | 
 |  | 
 |     TAILQ_FOREACH_REVERSE(cache_inode, &nffs_cache_inode_list, | 
 |                           nffs_cache_inode_list, nci_link) { | 
 |         if (!TAILQ_EMPTY(&cache_inode->nci_block_list)) { | 
 |             nffs_cache_inode_free_blocks(cache_inode); | 
 |             return; | 
 |         } | 
 |     } | 
 |  | 
 |     assert(0); | 
 | } | 
 |  | 
 | void | 
 | nffs_cache_inode_delete(const struct nffs_inode_entry *inode_entry) | 
 | { | 
 |     struct nffs_cache_inode *entry; | 
 |  | 
 |     entry = nffs_cache_inode_find(inode_entry); | 
 |     if (entry == NULL) { | 
 |         return; | 
 |     } | 
 |  | 
 |     TAILQ_REMOVE(&nffs_cache_inode_list, entry, nci_link); | 
 |     nffs_cache_inode_free(entry); | 
 | } | 
 |  | 
 | int | 
 | nffs_cache_inode_ensure(struct nffs_cache_inode **out_cache_inode, | 
 |                         struct nffs_inode_entry *inode_entry) | 
 | { | 
 |     struct nffs_cache_inode *cache_inode; | 
 |     int rc; | 
 |  | 
 |     cache_inode = nffs_cache_inode_find(inode_entry); | 
 |     if (cache_inode != NULL) { | 
 |         rc = 0; | 
 |         goto done; | 
 |     } | 
 |  | 
 |     cache_inode = nffs_cache_inode_acquire(); | 
 |     rc = nffs_cache_inode_populate(cache_inode, inode_entry); | 
 |     if (rc != 0) { | 
 |         goto done; | 
 |     } | 
 |  | 
 |     TAILQ_INSERT_HEAD(&nffs_cache_inode_list, cache_inode, nci_link); | 
 |  | 
 |     rc = 0; | 
 |  | 
 | done: | 
 |     if (rc == 0) { | 
 |         *out_cache_inode = cache_inode; | 
 |     } else { | 
 |         nffs_cache_inode_free(cache_inode); | 
 |         *out_cache_inode = NULL; | 
 |     } | 
 |     return rc; | 
 | } | 
 |  | 
 | /** | 
 |  * Recaches all cached inodes.  All cached blocks are deleted from the cache | 
 |  * during this operation.  This function should be called after garbage | 
 |  * collection occurs to ensure the cache is consistent. | 
 |  * | 
 |  * @return                      0 on success; nonzero on failure. | 
 |  */ | 
 | int | 
 | nffs_cache_inode_refresh(void) | 
 | { | 
 |     struct nffs_cache_inode *cache_inode; | 
 |     struct nffs_inode_entry *inode_entry; | 
 |     int rc; | 
 |  | 
 |     TAILQ_FOREACH(cache_inode, &nffs_cache_inode_list, nci_link) { | 
 |         /* Clear entire block list. */ | 
 |         nffs_cache_inode_free_blocks(cache_inode); | 
 |  | 
 |         inode_entry = cache_inode->nci_inode.ni_inode_entry; | 
 |         rc = nffs_inode_from_entry(&cache_inode->nci_inode, inode_entry); | 
 |         if (rc != 0) { | 
 |             return rc; | 
 |         } | 
 |  | 
 |         /* File size remains valid. */ | 
 |     } | 
 |  | 
 |     return 0; | 
 | } | 
 |  | 
 | static void | 
 | nffs_cache_log_block(struct nffs_cache_inode *cache_inode, | 
 |                      struct nffs_cache_block *cache_block) | 
 | { | 
 |     NFFS_LOG(DEBUG, "id=%u inode=%u flash_off=0x%08x " | 
 |                     "file_off=%u len=%d (entry=%p)\n", | 
 |              (unsigned int)cache_block->ncb_block.nb_hash_entry->nhe_id, | 
 |              (unsigned int)cache_inode->nci_inode.ni_inode_entry->nie_hash_entry.nhe_id, | 
 |              (unsigned int)cache_block->ncb_block.nb_hash_entry->nhe_flash_loc, | 
 |              (unsigned int)cache_block->ncb_file_offset, | 
 |              cache_block->ncb_block.nb_data_len, | 
 |              cache_block->ncb_block.nb_hash_entry); | 
 | } | 
 |  | 
 | static void | 
 | nffs_cache_log_insert_block(struct nffs_cache_inode *cache_inode, | 
 |                             struct nffs_cache_block *cache_block, | 
 |                             int tail) | 
 | { | 
 |     NFFS_LOG(DEBUG, "caching block (%s): ", tail ? "tail" : "head"); | 
 |     nffs_cache_log_block(cache_inode, cache_block); | 
 | } | 
 |  | 
 | void | 
 | nffs_cache_insert_block(struct nffs_cache_inode *cache_inode, | 
 |                         struct nffs_cache_block *cache_block, | 
 |                         int tail) | 
 | { | 
 |     if (tail) { | 
 |         TAILQ_INSERT_TAIL(&cache_inode->nci_block_list, cache_block, ncb_link); | 
 |     } else { | 
 |         TAILQ_INSERT_HEAD(&cache_inode->nci_block_list, cache_block, ncb_link); | 
 |     } | 
 |  | 
 |     nffs_cache_log_insert_block(cache_inode, cache_block, tail); | 
 | } | 
 |  | 
 | /** | 
 |  * Finds the data block containing the specified offset within a file inode. | 
 |  * If the block is not yet cached, it gets cached as a result of this | 
 |  * operation.  This function modifies the inode's cached block list according | 
 |  * to the following procedure: | 
 |  * | 
 |  *  1. If none of the owning inode's blocks are currently cached, allocate a | 
 |  *     cached block entry and insert it into the inode's list. | 
 |  *  2. Else if the requested file offset is less than that of the first cached | 
 |  *     block, bridge the gap between the inode's sequence of cached blocks and | 
 |  *     the block that now needs to be cached.  This is accomplished by caching | 
 |  *     each block in the gap, finishing with the requested block. | 
 |  *  3. Else (the requested offset is beyond the end of the cache), | 
 |  *      a. If the requested offset belongs to the block that immediately | 
 |  *         follows the end of the cache, cache the block and append it to the | 
 |  *         list. | 
 |  *      b. Else, clear the cache, and populate it with the single entry | 
 |  *         corresponding to the requested block. | 
 |  * | 
 |  * @param cache_inode           The cached file inode to seek within. | 
 |  * @param seek_offset           The file offset to seek to. | 
 |  * @param out_cache_block       On success, the requested cached block gets | 
 |  *                                  written here; pass null if you don't need | 
 |  *                                  this. | 
 |  * | 
 |  * @return                      0 on success; nonzero on failure. | 
 |  */ | 
 | int | 
 | nffs_cache_seek(struct nffs_cache_inode *cache_inode, uint32_t seek_offset, | 
 |                 struct nffs_cache_block **out_cache_block) | 
 | { | 
 |     struct nffs_cache_block *cache_block; | 
 |     struct nffs_hash_entry *last_cached_entry; | 
 |     struct nffs_hash_entry *block_entry; | 
 |     struct nffs_hash_entry *pred_entry; | 
 |     struct nffs_block block; | 
 |     uint32_t cache_start; | 
 |     uint32_t cache_end; | 
 |     uint32_t block_start; | 
 |     uint32_t block_end; | 
 |     int rc; | 
 |  | 
 |     /* Empty files have no blocks that can be cached. */ | 
 |     if (cache_inode->nci_file_size == 0) { | 
 |         return FS_ENOENT; | 
 |     } | 
 |  | 
 |     nffs_cache_inode_range(cache_inode, &cache_start, &cache_end); | 
 |     if (cache_end != 0 && seek_offset < cache_start) { | 
 |         /* Seeking prior to cache.  Iterate backwards from cache start. */ | 
 |         cache_block = TAILQ_FIRST(&cache_inode->nci_block_list); | 
 |         block_entry = cache_block->ncb_block.nb_prev; | 
 |         block_end = cache_block->ncb_file_offset; | 
 |         cache_block = NULL; | 
 |     } else if (seek_offset < cache_end) { | 
 |         /* Seeking within cache.  Iterate backwards from cache end. */ | 
 |         cache_block = TAILQ_LAST(&cache_inode->nci_block_list, | 
 |                                  nffs_cache_block_list); | 
 |         block_entry = cache_block->ncb_block.nb_hash_entry; | 
 |         block_end = cache_end; | 
 |     } else { | 
 |         /* Seeking beyond end of cache.  Iterate backwards from file end.  If | 
 |          * sought-after block is adjacent to cache end, its cache entry will | 
 |          * get appended to the current cache.  Otherwise, the current cache | 
 |          * will be freed and replaced with the single requested block. | 
 |          */ | 
 |         cache_block = NULL; | 
 |         block_entry = | 
 |             cache_inode->nci_inode.ni_inode_entry->nie_last_block_entry; | 
 |         block_end = cache_inode->nci_file_size; | 
 |     } | 
 |  | 
 |     /* Scan backwards until we find the block containing the seek offest. */ | 
 |     while (1) { | 
 |         if (block_end <= cache_start) { | 
 |             /* We are looking before the start of the cache.  Allocate a new | 
 |              * cache block and prepend it to the cache. | 
 |              */ | 
 |             assert(cache_block == NULL); | 
 |             cache_block = nffs_cache_block_acquire(); | 
 |             rc = nffs_cache_block_populate(cache_block, block_entry, | 
 |                                            block_end); | 
 |             if (rc != 0) { | 
 |                 return rc; | 
 |             } | 
 |  | 
 |             nffs_cache_insert_block(cache_inode, cache_block, 0); | 
 |         } | 
 |  | 
 |         /* Calculate the file offset of the start of this block.  This is used | 
 |          * to determine if this block contains the sought-after offset. | 
 |          */ | 
 |         if (cache_block != NULL) { | 
 |             /* Current block is cached. */ | 
 |             block_start = cache_block->ncb_file_offset; | 
 |             pred_entry = cache_block->ncb_block.nb_prev; | 
 |         } else { | 
 |             /* We are looking beyond the end of the cache.  Read the data block | 
 |              * from flash. | 
 |              */ | 
 |             rc = nffs_block_from_hash_entry(&block, block_entry); | 
 |             if (rc != 0) { | 
 |                 return rc; | 
 |             } | 
 |  | 
 |             block_start = block_end - block.nb_data_len; | 
 |             pred_entry = block.nb_prev; | 
 |         } | 
 |  | 
 |         if (block_start <= seek_offset) { | 
 |             /* This block contains the requested address; iteration is | 
 |              * complete. | 
 |              */ | 
 |            if (cache_block == NULL) { | 
 |                 /* The block isn't cached, so it must come after the cache end. | 
 |                  * Append it to the cache if it directly follows.  Otherwise, | 
 |                  * erase the current cache and populate it with this single | 
 |                  * block. | 
 |                  */ | 
 |                 cache_block = nffs_cache_block_acquire(); | 
 |                 cache_block->ncb_block = block; | 
 |                 cache_block->ncb_file_offset = block_start; | 
 |  | 
 |                 last_cached_entry = nffs_cache_inode_last_entry(cache_inode); | 
 |                 if (last_cached_entry != NULL && | 
 |                     last_cached_entry == pred_entry) { | 
 |  | 
 |                     nffs_cache_insert_block(cache_inode, cache_block, 1); | 
 |                 } else { | 
 |                     nffs_cache_inode_free_blocks(cache_inode); | 
 |                     nffs_cache_insert_block(cache_inode, cache_block, 0); | 
 |                 } | 
 |             } | 
 |  | 
 |             if (out_cache_block != NULL) { | 
 |                 *out_cache_block = cache_block; | 
 |             } | 
 |             break; | 
 |         } | 
 |  | 
 |         /* Prepare for next iteration. */ | 
 |         if (cache_block != NULL) { | 
 |             cache_block = TAILQ_PREV(cache_block, nffs_cache_block_list, | 
 |                                      ncb_link); | 
 |         } | 
 |         block_entry = pred_entry; | 
 |         block_end = block_start; | 
 |     } | 
 |  | 
 |     return 0; | 
 | } | 
 |  | 
 | /** | 
 |  * Frees all cached inodes and blocks. | 
 |  */ | 
 | void | 
 | nffs_cache_clear(void) | 
 | { | 
 |     struct nffs_cache_inode *entry; | 
 |  | 
 |     while ((entry = TAILQ_FIRST(&nffs_cache_inode_list)) != NULL) { | 
 |         TAILQ_REMOVE(&nffs_cache_inode_list, entry, nci_link); | 
 |         nffs_cache_inode_free(entry); | 
 |     } | 
 | } |