/* * Copyright 2004-2008, Haiku Inc. All rights reserved. * Distributed under the terms of the MIT License. * * Authors: * Axel Dörfler * Ingo Weinhold */ #include "fssh_fs_cache.h" #include #include #include "DoublyLinkedList.h" #include "fssh_kernel_export.h" #include "fssh_lock.h" #include "fssh_stdio.h" #include "fssh_string.h" #include "fssh_uio.h" #include "fssh_unistd.h" #include "hash.h" #include "vfs.h" #undef TRACE //#define TRACE_FILE_CACHE #ifdef TRACE_FILE_CACHE # define TRACE(x) fssh_dprintf x #else # define TRACE(x) ; #endif using std::nothrow; // This is a hacked version of the kernel file cache implementation. The main // part of the implementation didn't change that much -- some code not needed // in userland has been removed, most notably everything on the page level. // On the downside, the cache now never caches anything, but will always // directly work on the underlying device. Since that is usually cached by the // host operating system, it shouldn't hurt much, though. // maximum number of iovecs per request #define MAX_IO_VECS 64 // 256 kB #define MAX_FILE_IO_VECS 32 #define MAX_TEMP_IO_VECS 8 #define user_memcpy(a, b, c) fssh_memcpy(a, b, c) #define PAGE_ALIGN(x) (((x) + (FSSH_B_PAGE_SIZE - 1)) & ~(FSSH_B_PAGE_SIZE - 1)) #define ASSERT(x) namespace FSShell { struct file_cache_ref; typedef fssh_status_t (*cache_func)(file_cache_ref *ref, void *cookie, fssh_off_t offset, int32_t pageOffset, fssh_addr_t buffer, fssh_size_t bufferSize); struct file_cache_ref { fssh_mutex lock; fssh_mount_id mountID; fssh_vnode_id nodeID; struct vnode* node; fssh_off_t virtual_size; }; fssh_status_t file_cache_init() { return FSSH_B_OK; } // #pragma mark - static fssh_status_t read_from_file(file_cache_ref *ref, void *cookie, fssh_off_t offset, int32_t pageOffset, fssh_addr_t buffer, fssh_size_t bufferSize) { fssh_iovec vec; vec.iov_base = (void *)buffer; vec.iov_len = bufferSize; fssh_mutex_unlock(&ref->lock); fssh_status_t status = vfs_read_pages(ref->node, cookie, offset + pageOffset, &vec, 1, &bufferSize); fssh_mutex_lock(&ref->lock); return status; } static fssh_status_t write_to_file(file_cache_ref *ref, void *cookie, fssh_off_t offset, int32_t pageOffset, fssh_addr_t buffer, fssh_size_t bufferSize) { fssh_iovec vec; vec.iov_base = (void *)buffer; vec.iov_len = bufferSize; fssh_mutex_unlock(&ref->lock); fssh_status_t status = vfs_write_pages(ref->node, cookie, offset + pageOffset, &vec, 1, &bufferSize); fssh_mutex_lock(&ref->lock); return status; } static inline fssh_status_t satisfy_cache_io(file_cache_ref *ref, void *cookie, cache_func function, fssh_off_t offset, fssh_addr_t buffer, int32_t &pageOffset, fssh_size_t bytesLeft, fssh_off_t &lastOffset, fssh_addr_t &lastBuffer, int32_t &lastPageOffset, fssh_size_t &lastLeft) { if (lastBuffer == buffer) return FSSH_B_OK; fssh_size_t requestSize = buffer - lastBuffer; fssh_status_t status = function(ref, cookie, lastOffset, lastPageOffset, lastBuffer, requestSize); if (status == FSSH_B_OK) { lastBuffer = buffer; lastLeft = bytesLeft; lastOffset = offset; lastPageOffset = 0; pageOffset = 0; } return status; } static fssh_status_t cache_io(void *_cacheRef, void *cookie, fssh_off_t offset, fssh_addr_t buffer, fssh_size_t *_size, bool doWrite) { if (_cacheRef == NULL) fssh_panic("cache_io() called with NULL ref!\n"); file_cache_ref *ref = (file_cache_ref *)_cacheRef; fssh_off_t fileSize = ref->virtual_size; TRACE(("cache_io(ref = %p, offset = %lld, buffer = %p, size = %u, %s)\n", ref, offset, (void *)buffer, *_size, doWrite ? "write" : "read")); // out of bounds access? if (offset >= fileSize || offset < 0) { *_size = 0; return FSSH_B_OK; } int32_t pageOffset = offset & (FSSH_B_PAGE_SIZE - 1); fssh_size_t size = *_size; offset -= pageOffset; if ((uint64_t)offset + pageOffset + size > (uint64_t)fileSize) { // adapt size to be within the file's offsets size = fileSize - pageOffset - offset; *_size = size; } if (size == 0) return FSSH_B_OK; cache_func function; if (doWrite) { // in low memory situations, we bypass the cache beyond a // certain I/O size function = write_to_file; } else { function = read_from_file; } // "offset" and "lastOffset" are always aligned to B_PAGE_SIZE, // the "last*" variables always point to the end of the last // satisfied request part const uint32_t kMaxChunkSize = MAX_IO_VECS * FSSH_B_PAGE_SIZE; fssh_size_t bytesLeft = size, lastLeft = size; int32_t lastPageOffset = pageOffset; fssh_addr_t lastBuffer = buffer; fssh_off_t lastOffset = offset; MutexLocker locker(ref->lock); while (bytesLeft > 0) { // check if this page is already in memory fssh_size_t bytesInPage = fssh_min_c( fssh_size_t(FSSH_B_PAGE_SIZE - pageOffset), bytesLeft); if (bytesLeft <= bytesInPage) break; buffer += bytesInPage; bytesLeft -= bytesInPage; pageOffset = 0; offset += FSSH_B_PAGE_SIZE; if (buffer - lastBuffer + lastPageOffset >= kMaxChunkSize) { fssh_status_t status = satisfy_cache_io(ref, cookie, function, offset, buffer, pageOffset, bytesLeft, lastOffset, lastBuffer, lastPageOffset, lastLeft); if (status != FSSH_B_OK) return status; } } // fill the last remaining bytes of the request (either write or read) return function(ref, cookie, lastOffset, lastPageOffset, lastBuffer, lastLeft); } } // namespace FSShell // #pragma mark - public FS API using namespace FSShell; void * fssh_file_cache_create(fssh_mount_id mountID, fssh_vnode_id vnodeID, fssh_off_t size) { TRACE(("file_cache_create(mountID = %d, vnodeID = %lld, size = %lld)\n", mountID, vnodeID, size)); file_cache_ref *ref = new(nothrow) file_cache_ref; if (ref == NULL) return NULL; ref->mountID = mountID; ref->nodeID = vnodeID; ref->virtual_size = size; // get vnode fssh_status_t error = vfs_lookup_vnode(mountID, vnodeID, &ref->node); if (error != FSSH_B_OK) { fssh_dprintf("file_cache_create(): Failed get vnode %d:%" FSSH_B_PRIdINO ": %s\n", mountID, vnodeID, fssh_strerror(error)); delete ref; return NULL; } // create lock char buffer[32]; fssh_snprintf(buffer, sizeof(buffer), "file cache %d:%" FSSH_B_PRIdINO, (int)mountID, vnodeID); fssh_mutex_init(&ref->lock, buffer); return ref; } void fssh_file_cache_delete(void *_cacheRef) { file_cache_ref *ref = (file_cache_ref *)_cacheRef; if (ref == NULL) return; TRACE(("file_cache_delete(ref = %p)\n", ref)); fssh_mutex_lock(&ref->lock); fssh_mutex_destroy(&ref->lock); delete ref; } void fssh_file_cache_enable(void *_cacheRef) { fssh_panic("fssh_file_cache_enable() called"); } fssh_status_t fssh_file_cache_disable(void *_cacheRef) { fssh_panic("fssh_file_cache_disable() called"); return FSSH_B_ERROR; } bool fssh_file_cache_is_enabled(void *_cacheRef) { return true; } fssh_status_t fssh_file_cache_set_size(void *_cacheRef, fssh_off_t size) { file_cache_ref *ref = (file_cache_ref *)_cacheRef; TRACE(("file_cache_set_size(ref = %p, size = %lld)\n", ref, size)); if (ref == NULL) return FSSH_B_OK; fssh_mutex_lock(&ref->lock); ref->virtual_size = size; fssh_mutex_unlock(&ref->lock); return FSSH_B_OK; } fssh_status_t fssh_file_cache_sync(void *_cacheRef) { file_cache_ref *ref = (file_cache_ref *)_cacheRef; if (ref == NULL) return FSSH_B_BAD_VALUE; return FSSH_B_OK; } fssh_status_t fssh_file_cache_read(void *_cacheRef, void *cookie, fssh_off_t offset, void *bufferBase, fssh_size_t *_size) { file_cache_ref *ref = (file_cache_ref *)_cacheRef; TRACE(("file_cache_read(ref = %p, offset = %lld, buffer = %p, size = %u)\n", ref, offset, bufferBase, *_size)); return cache_io(ref, cookie, offset, (fssh_addr_t)bufferBase, _size, false); } fssh_status_t fssh_file_cache_write(void *_cacheRef, void *cookie, fssh_off_t offset, const void *buffer, fssh_size_t *_size) { file_cache_ref *ref = (file_cache_ref *)_cacheRef; fssh_status_t status = cache_io(ref, cookie, offset, (fssh_addr_t)const_cast(buffer), _size, true); TRACE(("file_cache_write(ref = %p, offset = %lld, buffer = %p, size = %u) = %d\n", ref, offset, buffer, *_size, status)); return status; }