345 lines
7.9 KiB
C++
345 lines
7.9 KiB
C++
/*
|
|||
|
|
* Copyright 2004-2007, Haiku Inc. All rights reserved.
|
||
|
|
* Distributed under the terms of the MIT License.
|
||
|
|
*
|
||
|
|
* Authors:
|
||
|
|
* Axel Dörfler <[email protected]>
|
||
|
|
* Ingo Weinhold <[email protected]>
|
||
|
|
*/
|
||
|
|
|
||
|
|
#include "fssh_fs_cache.h"
|
||
|
|
|
||
|
|
#include <new>
|
||
|
|
|
||
|
|
#include <stdlib.h>
|
||
|
|
|
||
|
|
#include "DoublyLinkedList.h"
|
||
|
|
#include "fssh_kernel_export.h"
|
||
|
|
#include "fssh_stdio.h"
|
||
|
|
#include "fssh_string.h"
|
||
|
|
#include "fssh_uio.h"
|
||
|
|
#include "fssh_unistd.h"
|
||
|
|
#include "hash.h"
|
||
|
|
#include "lock.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 {
|
||
|
|
mutex lock;
|
||
|
|
fssh_mount_id mountID;
|
||
|
|
fssh_vnode_id nodeID;
|
||
|
|
void* 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;
|
||
|
|||
|
|
mutex_unlock(&ref->lock);
|
||
|
|
|
||
fssh_status_t status = vfs_read_pages(ref->node, cookie, offset, &vec, 1,
|
|||
|
|
&bufferSize, false);
|
||
|
|||
|
|
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;
|
||
|
|||
|
|
mutex_unlock(&ref->lock);
|
||
|
|
|
||
fssh_status_t status = vfs_write_pages(ref->node, cookie, offset, &vec, 1,
|
|||
|
|
&bufferSize, false);
|
||
|
|||
|
|
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 = %Ld, buffer = %p, size = %lu, %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 (offset + pageOffset + size > 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 = %ld, vnodeID = %Ld, size = %Ld, fd = %d)\n", mountID, vnodeID, size, fd));
|
||
|
|
|
||
|
|
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:%lld: %s\n",
|
||
|
|
mountID, vnodeID, fssh_strerror(error));
|
||
|
|
delete ref;
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
// create lock
|
||
|
|
char buffer[32];
|
||
|
|
fssh_snprintf(buffer, sizeof(buffer), "file cache %d:%lld", (int)mountID, vnodeID);
|
||
|
|
error = mutex_init(&ref->lock, buffer);
|
||
|
|
if (error != FSSH_B_OK) {
|
||
|
|
fssh_dprintf("file_cache_create(): Failed to init mutex: %s\n",
|
||
|
|
fssh_strerror(error));
|
||
|
|
delete ref;
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
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));
|
||
|
|
|
||
|
|
mutex_lock(&ref->lock);
|
||
|
|
mutex_destroy(&ref->lock);
|
||
|
|
|
||
|
|
delete ref;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
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 = %Ld)\n", ref, size));
|
||
|
|
|
||
|
|
if (ref == NULL)
|
||
|
|
return FSSH_B_OK;
|
||
|
|
|
||
|
|
mutex_lock(&ref->lock);
|
||
ref->virtual_size = size;
|
|||
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 = %Ld, buffer = %p, size = %lu)\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<void *>(buffer), _size, true);
|
||
TRACE(("file_cache_write(ref = %p, offset = %Ld, buffer = %p, size = %lu) = %ld\n",
|
|||
|
|
ref, offset, buffer, *_size, status));
|
||
|
|
|
||
|
|
return status;
|
||
|
|
}
|
||
|
|
|