Files
haiku-beta6/src/tools/fs_shell/file_cache.cpp
T
Ingo Weinhold a38a92c955 Beginnings of a new, better portable FS shell with Haiku FS interface.
Doesn't do anything ATM, but already provides the required system
interface (VFS, caches, POSIX functions).


git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@20859 a95241bf-73f2-0310-859d-f6bbb57e9c96
2007-04-27 12:17:22 +00:00

1873 lines
47 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, file_cache_ref, vm_cache_ref and vm_cache
// have been unified, and the complete interface to the VM (vm_*()) and the
// VFS (vfs_*()) has been re-implemented to suit our needs.
//
// The PagePool class is a data structure used for managing the pages (vm_page)
// allocated and assigned to caches (file_cache_ref). It has a list for free
// pages, i.e. those that are not assigned to any cache and can be reused at
// once. A second list contains those pages that belong to a cache, but are
// not in use by any of the functions. These pages have a reference count of
// 0. They will be stolen from the owning cache when a usable page is needed,
// there are no free pages anymore, and the limit of pages that shall be used
// at maximum has already been reached.
//
// The only purpose of the page reference count (vm_page::ref_count) is to
// indicate whether the page is in use (i.e. known to one or more of the cache
// functions currently being executed). vm_page_get_page(),
// vm_page_allocate_page(), and vm_cache_lookup_page acquire a reference to
// the page, vm_page_put_page() releases a reference.
// vm_page::state indicates in what state
// a page currently is. PAGE_STATE_FREE is only encountered for pages not
// belonging to a cache (being in page pool's free pages list, or just having
// been removed or not yet added). PAGE_STATE_ACTIVE/MODIFIED indicate that
// the page contains valid file data, in the latter case not yet written to
// disk. PAGE_STATE_BUSY means that the page is currently being manipulated by
// an operation, usually meaning that page data are being read from/written to
// disk. The required behavior when encountering a busy page, is to put the
// page, release the page pool and cache locks, wait a short time, and
// retry again later.
//
// Changing the page state requires having a reference to the page,
// holding the lock of the owning cache (if any) and the lock of the page pool
// (in case the page is not newly allocated).
//
// A page is in up to three lists. The free or unused list in the page pool
// (guarded by the page pool lock), the pages list of the cache the page
// belongs to, and the modified pages list of the cache (both cache lists
// are guarded by both the page pool and the cache lock). The modified pages
// list only contains PAGE_STATE_MODIFIED or PAGE_STATE_BUSY pages.
// 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 CACHED_FILE_EXTENTS 2
// must be smaller than MAX_FILE_IO_VECS
// ToDo: find out how much of these are typically used
#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 {
enum {
PAGE_STATE_ACTIVE = 0,
PAGE_STATE_BUSY,
PAGE_STATE_MODIFIED,
PAGE_STATE_FREE
};
enum {
PHYSICAL_PAGE_NO_WAIT = 0,
PHYSICAL_PAGE_CAN_WAIT,
};
struct vm_page;
struct file_cache_ref;
typedef DoublyLinkedListLink<vm_page> page_link;
// vm page
struct vm_page {
vm_page* next;
page_link link_cache;
page_link link_cache_modified;
page_link link_pool;
file_cache_ref* cache;
fssh_off_t offset;
void* data;
uint8_t state;
uint32_t ref_count;
vm_page()
: next(NULL),
cache(NULL),
offset(0),
data(malloc(FSSH_B_PAGE_SIZE)),
state(PAGE_STATE_FREE),
ref_count(1)
{
}
~vm_page()
{
free(data);
}
fssh_addr_t Address() const { return (fssh_addr_t)data; }
static int Compare(void *_cacheEntry, const void *_key);
static uint32_t Hash(void *_cacheEntry, const void *_key, uint32_t range);
};
struct file_extent {
fssh_off_t offset;
fssh_file_io_vec disk;
};
struct file_map {
file_map();
~file_map();
file_extent *operator[](uint32_t index);
file_extent *ExtentAt(uint32_t index);
fssh_status_t Add(fssh_file_io_vec *vecs, fssh_size_t vecCount, fssh_off_t &lastOffset);
void Free();
union {
file_extent direct[CACHED_FILE_EXTENTS];
file_extent *array;
};
fssh_size_t count;
};
static vm_page *vm_page_allocate_page(int state);
static void vm_page_get_page(vm_page* page);
static fssh_status_t vm_page_put_page(vm_page* page);
static fssh_status_t vm_page_set_state(vm_page *page, int state);
static void vm_cache_insert_page(file_cache_ref *cacheRef, vm_page *page,
fssh_off_t offset);
static void vm_cache_remove_page(file_cache_ref *cacheRef, vm_page *page);
static vm_page *vm_cache_lookup_page(file_cache_ref *cacheRef, fssh_off_t page);
static fssh_status_t vm_cache_resize(file_cache_ref *cacheRef, fssh_off_t newSize);
static fssh_status_t vm_cache_write_modified(file_cache_ref *ref, bool fsReenter);
static fssh_status_t vfs_read_pages(int fd, fssh_off_t pos, const fssh_iovec *vecs,
fssh_size_t count, fssh_size_t *_numBytes, bool fsReenter);
static fssh_status_t vfs_write_pages(int fd, fssh_off_t pos, const fssh_iovec *vecs,
fssh_size_t count, fssh_size_t *_numBytes, bool fsReenter);
static fssh_status_t pages_io(file_cache_ref *ref, fssh_off_t offset, const fssh_iovec *vecs,
fssh_size_t count, fssh_size_t *_numBytes, bool doWrite);
typedef DoublyLinkedList<vm_page,
DoublyLinkedListMemberGetLink<vm_page,
&vm_page::link_cache_modified> > cache_modified_page_list;
typedef DoublyLinkedList<vm_page,
DoublyLinkedListMemberGetLink<vm_page,
&vm_page::link_cache> > cache_page_list;
struct file_cache_ref {
mutex lock;
fssh_mount_id mountID;
fssh_vnode_id nodeID;
void* node;
int deviceFD;
fssh_off_t virtual_size;
cache_page_list pages;
cache_modified_page_list modifiedPages;
file_map map;
};
struct page_hash_key {
page_hash_key() {}
page_hash_key(int fd, fssh_vnode_id id, fssh_off_t offset)
: deviceFD(fd),
nodeID(id),
offset(offset)
{
}
int deviceFD;
fssh_vnode_id nodeID;
fssh_off_t offset;
};
typedef DoublyLinkedList<vm_page,
DoublyLinkedListMemberGetLink<vm_page,
&vm_page::link_pool> > pool_page_list;
struct PagePool {
PagePool()
: pageHash(NULL),
unusedPages(),
freePages(),
allocatedPages(0)
{
}
~PagePool()
{
}
fssh_status_t Init()
{
fssh_status_t error = recursive_lock_init(&lock, "page pool");
if (error != FSSH_B_OK) {
fssh_panic("PagePool: Failed to init lock\n");
return error;
}
pageHash = hash_init(256, 0, &vm_page::Compare, &vm_page::Hash);
if (pageHash == NULL) {
fssh_panic("PagePool: Failed to create page hash\n");
return FSSH_B_NO_MEMORY;
}
return FSSH_B_OK;
}
bool Lock() { return (recursive_lock_lock(&lock) == FSSH_B_OK); }
void Unlock() { recursive_lock_unlock(&lock); }
recursive_lock lock;
hash_table* pageHash;
pool_page_list unusedPages;
pool_page_list freePages;
uint32_t allocatedPages;
};
struct PagePutter {
PagePutter(vm_page* page)
: fPage(page)
{
}
~PagePutter()
{
if (fPage)
vm_page_put_page(fPage);
}
private:
vm_page* fPage;
};
static PagePool sPagePool;
static const uint32_t kMaxAllocatedPages = 1024;
int
vm_page::Compare(void *_cacheEntry, const void *_key)
{
vm_page *page = (vm_page*)_cacheEntry;
const page_hash_key *key = (const page_hash_key *)_key;
// device FD
if (page->cache->deviceFD != key->deviceFD)
return page->cache->deviceFD - key->deviceFD;
// node ID
if (page->cache->nodeID < key->nodeID)
return -1;
if (page->cache->nodeID > key->nodeID)
return 1;
// offset
if (page->offset < key->offset)
return -1;
if (page->offset > key->offset)
return 1;
return 0;
}
uint32_t
vm_page::Hash(void *_cacheEntry, const void *_key, uint32_t range)
{
vm_page *page = (vm_page*)_cacheEntry;
const page_hash_key *key = (const page_hash_key *)_key;
int fd = (page ? page->cache->deviceFD : key->deviceFD);
fssh_vnode_id id = (page ? page->cache->nodeID : key->nodeID);
fssh_off_t offset = (page ? page->offset : key->offset);
uint32_t value = fd;
value = value * 17 + id;
value = value * 17 + offset;
return value % range;
}
vm_page *
vm_page_allocate_page(int state)
{
AutoLocker<PagePool> poolLocker(sPagePool);
// is a queued free page available?
vm_page* page = sPagePool.freePages.RemoveHead();
if (page) {
page->ref_count++;
return page;
}
// no free page
#if 0
// TODO: Nice idea in principle, but causes a serious locking problem.
// vm_page_allocate_page() is always invoked with some cached locked at the
// same time. Thus locking a cache here to steal a page can cause a deadlock.
// If the limit for allocated pages has been reached, we try to steal an
// unused page.
if (sPagePool.allocatedPages >= kMaxAllocatedPages
&& !sPagePool.unusedPages.IsEmpty()) {
// we grab the first non-busy page
for (pool_page_list::Iterator it(&sPagePool.unusedPages);
vm_page* currentPage = it.Next();) {
if (currentPage->state != PAGE_STATE_BUSY) {
it.Remove();
page = currentPage;
page->ref_count++;
break;
}
}
// If we've found a suitable page, we need to mark it busy, write it
// if it was modified, and finally remove it from its cache.
if (page != NULL) {
bool modified = (page->state == PAGE_STATE_MODIFIED);
// mark the page busy
page->state = PAGE_STATE_BUSY;
// We don't need the pool lock anymore.
poolLocker.Unlock();
file_cache_ref* cache = page->cache;
// If the page is modified, we write it to the disk.
if (modified) {
// find out, how much to write, and remove the page from the
// cache's modified pages list
MutexLocker cacheLocker(cache->lock);
fssh_size_t bytes = fssh_min_c(FSSH_B_PAGE_SIZE,
cache->virtual_size - page->offset);
cache->modifiedPages.Remove(page);
cacheLocker.Unlock();
// if we need to write anything, do it now
if (bytes > 0) {
fssh_iovec vecs[1];
vecs[0].iov_base = page->data;
vecs[0].iov_len = bytes;
fssh_status_t error = pages_io(cache, page->offset, vecs, 1,
&bytes, true);
if (error != FSSH_B_OK) {
// failed to write the page: bad, but we can't do
// much about it
fssh_dprintf("vm_page_allocate_page(): Failed to write "
"page %p (cache %p, offset: %lld).\n", page,
cache, page->offset);
}
}
}
// remove the page from the cache
MutexLocker cacheLocker(cache->lock);
vm_cache_remove_page(cache, page);
// now it's ours
page->state = PAGE_STATE_FREE;
return page;
}
}
#endif // 0
// no page yet -- allocate a new one
page = new(nothrow) vm_page;
if (!page || !page->data) {
delete page;
return NULL;
}
sPagePool.allocatedPages++;
return page;
}
static void
vm_page_get_page(vm_page* page)
{
if (page) {
AutoLocker<PagePool> _(sPagePool);
// increase ref count
page->ref_count++;
// if the page was unused before, remove it from the unused pages list
if (page->ref_count == 1)
sPagePool.unusedPages.Remove(page);
}
}
static fssh_status_t
vm_page_put_page(vm_page* page)
{
if (!page)
return FSSH_B_BAD_VALUE;
AutoLocker<PagePool> _(sPagePool);
if (page->ref_count <= 0) {
fssh_panic("vm_page_put_page(): page %p already unreferenced!\n", page);
return FSSH_B_BAD_VALUE;
}
// decrease ref count
page->ref_count--;
if (page->ref_count > 0)
return FSSH_B_OK;
// the page is unreference now: add it to the unused or free pages list
if (page->state == PAGE_STATE_FREE) {
// page is free
// if we've maxed out the allowed allocated page, free this one,
// otherwise add it to the free list
if (sPagePool.allocatedPages > kMaxAllocatedPages) {
delete page;
sPagePool.allocatedPages--;
} else
sPagePool.freePages.Add(page);
} else {
// page is is not free; add to unused pages list
sPagePool.unusedPages.Add(page);
}
return FSSH_B_OK;
}
fssh_status_t
vm_page_set_state(vm_page *page, int state)
{
AutoLocker<PagePool> _(sPagePool);
if (page->ref_count <= 0) {
fssh_panic("vm_page_set_state(): page %p is already unreferenced!\n",
page);
return FSSH_B_BAD_VALUE;
}
if (state == page->state)
return FSSH_B_OK;
// If it was modified before, remove the page from the cache's modified
// pages list.
if (page->state == PAGE_STATE_MODIFIED && page->cache)
page->cache->modifiedPages.Remove(page);
switch (state) {
case PAGE_STATE_ACTIVE:
case PAGE_STATE_BUSY:
case PAGE_STATE_FREE:
page->state = state;
break;
case PAGE_STATE_MODIFIED:
{
page->state = state;
// add page to the modified list of the cache
if (!page->cache) {
fssh_panic("vm_page_set_state(): setting page state of page %p "
"to PAGE_STATE_MODIFIED, but page is not in a cache\n",
page);
return FSSH_B_BAD_VALUE;
}
page->cache->modifiedPages.Add(page);
break;
}
default:
fssh_panic("vm_page_set_state(): invalid page state: %d\n", state);
return FSSH_B_BAD_VALUE;
}
return FSSH_B_OK;
}
// cache must be locked
//
void
vm_cache_insert_page(file_cache_ref *cache, vm_page *page, fssh_off_t offset)
{
AutoLocker<PagePool> _(sPagePool);
if (page->cache != NULL) {
fssh_panic("vm_cache_insert_page(%p, %p): page already in cache %p\n",
cache, page, page->cache);
return;
}
page->cache = cache;
page->offset = offset;
// insert page into hash
fssh_status_t error = hash_insert(sPagePool.pageHash, page);
if (error != FSSH_B_OK) {
fssh_panic("vm_cache_insert_page(): Failed to insert page %p into hash!\n",
page);
page->cache = NULL;
page->offset = 0;
return;
}
// add page to cache page list
cache->pages.Add(page);
}
// cache must be locked
//
void
vm_cache_remove_page(file_cache_ref *cache, vm_page *page)
{
if (cache != page->cache) {
fssh_panic("vm_cache_remove_page(%p, %p): page is in cache %p\n",
cache, page, page->cache);
return;
}
AutoLocker<PagePool> _(sPagePool);
if (page->state == PAGE_STATE_MODIFIED)
cache->modifiedPages.Remove(page);
cache->pages.Remove(page);
hash_remove(sPagePool.pageHash, page);
page->cache = NULL;
page->offset = 0;
}
vm_page *
vm_cache_lookup_page(file_cache_ref *cache, fssh_off_t offset)
{
if (!cache)
return NULL;
AutoLocker<PagePool> _(sPagePool);
page_hash_key key(cache->deviceFD, cache->nodeID, offset);
vm_page* page = (vm_page*)hash_lookup(sPagePool.pageHash, &key);
if (page)
vm_page_get_page(page);
return page;
}
// cache must be locked
//
fssh_status_t
vm_cache_resize(file_cache_ref *cache, fssh_off_t newSize)
{
fssh_off_t oldAlignedSize = PAGE_ALIGN(cache->virtual_size);
fssh_off_t newAlignedSize = PAGE_ALIGN(newSize);
cache->virtual_size = newSize;
// if the aligned cache size increased or remained the same, we're done
if (newAlignedSize >= oldAlignedSize)
return FSSH_B_OK;
// the file shrinks, so we need to get rid of excess pages
// Hold the page pool lock virtually all the time from now on.
AutoLocker<PagePool> poolLocker(sPagePool);
// For sake of efficiency we sort the cache's list of pages so that all
// pages that need to be removed are at the beginning of the list.
vm_page* page = cache->pages.Head();
if (newAlignedSize > 0) {
while (page) {
vm_page* nextPage = cache->pages.GetNext(page);
if (page->offset >= newAlignedSize) {
// move to the beginning of the list
cache->pages.Remove(page);
cache->pages.Add(page, false);
}
page = nextPage;
}
}
// now we remove and free the excess pages one by one
while (true) {
// get the first page in the list to remove
// (since we sorted the list, this is usually very cheap)
for (cache_page_list::Iterator it(&cache->pages); (page = it.Next());) {
if (page->offset >= newAlignedSize)
break;
}
// no more pages? -- then we're done
if (!page)
return FSSH_B_OK;
if (page->state == PAGE_STATE_BUSY) {
// the page is busy -- wait a while and try again
poolLocker.Unlock();
mutex_unlock(&cache->lock);
fssh_snooze(20000);
mutex_lock(&cache->lock);
poolLocker.Lock();
} else {
// the page isn't busy -- get rid of it
vm_page_get_page(page);
vm_cache_remove_page(cache, page);
vm_page_set_state(page, PAGE_STATE_FREE);
vm_page_put_page(page);
}
}
return FSSH_B_OK;
}
fssh_status_t
vm_cache_write_modified(file_cache_ref *cache, bool fsReenter)
{
// TODO: Write more than one page at a time. To avoid deadlocks, when a
// busy page is encountered the previously collected pages need to be
// written. Otherwise as many pages as our on-stack array can contain
// can be processed at once.
MutexLocker locker(cache->lock);
while (true) {
// get the next modified page and mark it busy
vm_page* page = NULL;
while (true) {
// get the first modified page
AutoLocker<PagePool> poolLocker(sPagePool);
page = cache->modifiedPages.Head();
if (!page)
return FSSH_B_OK;
// if not marked busy, remove it and mark it busy
if (page->state != PAGE_STATE_BUSY) {
cache->modifiedPages.Remove(page);
vm_page_get_page(page);
page->state = PAGE_STATE_BUSY;
break;
}
// page is busy -- wait a while
vm_page_put_page(page);
poolLocker.Unlock();
locker.Unlock();
fssh_snooze(20000);
locker.Lock();
}
locker.Unlock();
// write the page
fssh_size_t bytes = fssh_min_c(FSSH_B_PAGE_SIZE, cache->virtual_size - page->offset);
fssh_iovec vecs[1];
vecs[0].iov_base = page->data;
vecs[0].iov_len = bytes;
fssh_status_t error = pages_io(cache, page->offset, vecs, 1, &bytes, true);
if (error != FSSH_B_OK)
return error;
locker.Lock();
vm_page_set_state(page, PAGE_STATE_ACTIVE);
vm_page_put_page(page);
}
return FSSH_B_OK;
}
fssh_status_t
vfs_read_pages(int fd, fssh_off_t pos, const fssh_iovec *vecs, fssh_size_t count,
fssh_size_t *_numBytes, bool fsReenter)
{
// check how much the iovecs allow us to read
fssh_size_t toRead = 0;
for (fssh_size_t i = 0; i < count; i++)
toRead += vecs[i].iov_len;
fssh_iovec* newVecs = NULL;
if (*_numBytes < toRead) {
// We're supposed to read less than specified by the vecs. Since
// readv_pos() doesn't support this, we need to clone the vecs.
newVecs = new(nothrow) fssh_iovec[count];
if (!newVecs)
return FSSH_B_NO_MEMORY;
fssh_size_t newCount = 0;
for (fssh_size_t i = 0; i < count && toRead > 0; i++) {
fssh_size_t vecLen = fssh_min_c(vecs[i].iov_len, toRead);
newVecs[i].iov_base = vecs[i].iov_base;
newVecs[i].iov_len = vecLen;
toRead -= vecLen;
newCount++;
}
vecs = newVecs;
count = newCount;
}
fssh_ssize_t bytesRead = fssh_readv_pos(fd, pos, vecs, count);
delete[] newVecs;
if (bytesRead < 0)
return bytesRead;
*_numBytes = bytesRead;
return FSSH_B_OK;
}
fssh_status_t
vfs_write_pages(int fd, fssh_off_t pos, const fssh_iovec *vecs, fssh_size_t count,
fssh_size_t *_numBytes, bool fsReenter)
{
// check how much the iovecs allow us to write
fssh_size_t toWrite = 0;
for (fssh_size_t i = 0; i < count; i++)
toWrite += vecs[i].iov_len;
fssh_iovec* newVecs = NULL;
if (*_numBytes < toWrite) {
// We're supposed to write less than specified by the vecs. Since
// writev_pos() doesn't support this, we need to clone the vecs.
newVecs = new(nothrow) fssh_iovec[count];
if (!newVecs)
return FSSH_B_NO_MEMORY;
fssh_size_t newCount = 0;
for (fssh_size_t i = 0; i < count && toWrite > 0; i++) {
fssh_size_t vecLen = fssh_min_c(vecs[i].iov_len, toWrite);
newVecs[i].iov_base = vecs[i].iov_base;
newVecs[i].iov_len = vecLen;
toWrite -= vecLen;
newCount++;
}
vecs = newVecs;
count = newCount;
}
fssh_ssize_t bytesWritten = fssh_writev_pos(fd, pos, vecs, count);
delete[] newVecs;
if (bytesWritten < 0)
return bytesWritten;
*_numBytes = bytesWritten;
return FSSH_B_OK;
}
fssh_status_t
file_cache_init()
{
fssh_status_t error = sPagePool.Init();
if (error != FSSH_B_OK)
return error;
return FSSH_B_OK;
}
// #pragma mark -
file_map::file_map()
{
array = NULL;
count = 0;
}
file_map::~file_map()
{
Free();
}
file_extent *
file_map::operator[](uint32_t index)
{
return ExtentAt(index);
}
file_extent *
file_map::ExtentAt(uint32_t index)
{
if (index >= count)
return NULL;
if (count > CACHED_FILE_EXTENTS)
return &array[index];
return &direct[index];
}
fssh_status_t
file_map::Add(fssh_file_io_vec *vecs, fssh_size_t vecCount, fssh_off_t &lastOffset)
{
TRACE(("file_map::Add(vecCount = %ld)\n", vecCount));
fssh_off_t offset = 0;
if (vecCount <= CACHED_FILE_EXTENTS && count == 0) {
// just use the reserved area in the file_cache_ref structure
} else {
// TODO: once we can invalidate only parts of the file map,
// we might need to copy the previously cached file extends
// from the direct range
file_extent *newMap = (file_extent *)realloc(array,
(count + vecCount) * sizeof(file_extent));
if (newMap == NULL)
return FSSH_B_NO_MEMORY;
array = newMap;
if (count != 0) {
file_extent *extent = ExtentAt(count - 1);
offset = extent->offset + extent->disk.length;
}
}
int32_t start = count;
count += vecCount;
for (uint32_t i = 0; i < vecCount; i++) {
file_extent *extent = ExtentAt(start + i);
extent->offset = offset;
extent->disk = vecs[i];
offset += extent->disk.length;
}
#ifdef TRACE_FILE_CACHE
for (uint32_t i = 0; i < count; i++) {
file_extent *extent = ExtentAt(i);
fssh_dprintf("[%ld] extend offset %Ld, disk offset %Ld, length %Ld\n",
i, extent->offset, extent->disk.offset, extent->disk.length);
}
#endif
lastOffset = offset;
return FSSH_B_OK;
}
void
file_map::Free()
{
if (count > CACHED_FILE_EXTENTS)
free(array);
array = NULL;
count = 0;
}
// #pragma mark -
static void
add_to_iovec(fssh_iovec *vecs, int32_t &index, int32_t max, fssh_addr_t address, fssh_size_t size)
{
if (index > 0 && (fssh_addr_t)vecs[index - 1].iov_base + vecs[index - 1].iov_len == address) {
// the iovec can be combined with the previous one
vecs[index - 1].iov_len += size;
return;
}
if (index == max)
fssh_panic("no more space for iovecs!");
// we need to start a new iovec
vecs[index].iov_base = (void *)address;
vecs[index].iov_len = size;
index++;
}
static file_extent *
find_file_extent(file_cache_ref *ref, fssh_off_t offset, uint32_t *_index)
{
// TODO: do binary search
for (uint32_t index = 0; index < ref->map.count; index++) {
file_extent *extent = ref->map[index];
if (extent->offset <= offset
&& extent->offset + extent->disk.length > offset) {
if (_index)
*_index = index;
return extent;
}
}
return NULL;
}
static fssh_status_t
get_file_map(file_cache_ref *ref, fssh_off_t offset, fssh_size_t size,
fssh_file_io_vec *vecs, fssh_size_t *_count)
{
fssh_size_t maxVecs = *_count;
fssh_status_t status = FSSH_B_OK;
if (ref->map.count == 0) {
// we don't yet have the map of this file, so let's grab it
// (ordered by offset, so that we can do a binary search on them)
mutex_lock(&ref->lock);
// the file map could have been requested in the mean time
if (ref->map.count == 0) {
fssh_size_t vecCount = maxVecs;
fssh_off_t mapOffset = 0;
while (true) {
status = vfs_get_file_map(ref->node, mapOffset, ~0UL, vecs,
&vecCount);
if (status < FSSH_B_OK && status != FSSH_B_BUFFER_OVERFLOW) {
mutex_unlock(&ref->lock);
return status;
}
fssh_status_t addStatus = ref->map.Add(vecs, vecCount, mapOffset);
if (addStatus != FSSH_B_OK) {
// only clobber the status in case of failure
status = addStatus;
}
if (status != FSSH_B_BUFFER_OVERFLOW)
break;
// when we are here, the map has been stored in the array, and
// the array size was still too small to cover the whole file
vecCount = maxVecs;
}
}
mutex_unlock(&ref->lock);
}
if (status != FSSH_B_OK) {
// We must invalidate the (part of the) map we already
// have, as we cannot know if it's complete or not
ref->map.Free();
return status;
}
// We now have cached the map of this file, we now need to
// translate it for the requested access.
uint32_t index;
file_extent *fileExtent = find_file_extent(ref, offset, &index);
if (fileExtent == NULL) {
// access outside file bounds? But that's not our problem
*_count = 0;
return FSSH_B_OK;
}
offset -= fileExtent->offset;
vecs[0].offset = fileExtent->disk.offset + offset;
vecs[0].length = fileExtent->disk.length - offset;
if (vecs[0].length >= size || index >= ref->map.count - 1) {
*_count = 1;
return FSSH_B_OK;
}
// copy the rest of the vecs
size -= vecs[0].length;
for (index = 1; index < ref->map.count;) {
fileExtent++;
vecs[index] = fileExtent->disk;
index++;
if (size <= fileExtent->disk.length)
break;
if (index >= maxVecs) {
*_count = index;
return FSSH_B_BUFFER_OVERFLOW;
}
size -= fileExtent->disk.length;
}
*_count = index;
return FSSH_B_OK;
}
/*!
Does the dirty work of translating the request into actual disk offsets
and reads to or writes from the supplied iovecs as specified by \a doWrite.
*/
static fssh_status_t
pages_io(file_cache_ref *ref, fssh_off_t offset, const fssh_iovec *vecs, fssh_size_t count,
fssh_size_t *_numBytes, bool doWrite)
{
TRACE(("pages_io: ref = %p, offset = %Ld, size = %lu, vecCount = %lu, %s\n",
ref, offset, *_numBytes, count, doWrite ? "write" : "read"));
// translate the iovecs into direct device accesses
fssh_file_io_vec fileVecs[MAX_FILE_IO_VECS];
fssh_size_t fileVecCount = MAX_FILE_IO_VECS;
fssh_size_t numBytes = *_numBytes;
fssh_status_t status = get_file_map(ref, offset, numBytes, fileVecs,
&fileVecCount);
if (status < FSSH_B_OK && status != FSSH_B_BUFFER_OVERFLOW) {
TRACE(("get_file_map(offset = %Ld, numBytes = %lu) failed: %s\n",
offset, numBytes, fssh_strerror(status)));
return status;
}
bool bufferOverflow = status == FSSH_B_BUFFER_OVERFLOW;
#ifdef TRACE_FILE_CACHE
fssh_dprintf("got %lu file vecs for %Ld:%lu%s:\n", fileVecCount, offset,
numBytes, bufferOverflow ? " (array too small)" : "");
for (fssh_size_t i = 0; i < fileVecCount; i++) {
fssh_dprintf(" [%lu] offset = %Ld, size = %Ld\n",
i, fileVecs[i].offset, fileVecs[i].length);
}
#endif
if (fileVecCount == 0) {
// There are no file vecs at this offset, so we're obviously trying
// to access the file outside of its bounds
TRACE(("pages_io: access outside of vnode %p at offset %Ld\n",
ref->vnode, offset));
return FSSH_B_BAD_VALUE;
}
uint32_t fileVecIndex;
fssh_size_t size;
if (!doWrite) {
// now directly read the data from the device
// the first file_io_vec can be read directly
size = fileVecs[0].length;
if (size > numBytes)
size = numBytes;
status = vfs_read_pages(ref->deviceFD, fileVecs[0].offset, vecs,
count, &size, false);
if (status < FSSH_B_OK)
return status;
// TODO: this is a work-around for buggy device drivers!
// When our own drivers honour the length, we can:
// a) also use this direct I/O for writes (otherwise, it would
// overwrite precious data)
// b) panic if the term below is true (at least for writes)
if (size > fileVecs[0].length) {
//fssh_dprintf("warning: device driver %p doesn't respect total length in read_pages() call!\n", ref->device);
size = fileVecs[0].length;
}
ASSERT(size <= fileVecs[0].length);
// If the file portion was contiguous, we're already done now
if (size == numBytes)
return FSSH_B_OK;
// if we reached the end of the file, we can return as well
if (size != fileVecs[0].length) {
*_numBytes = size;
return FSSH_B_OK;
}
fileVecIndex = 1;
} else {
fileVecIndex = 0;
size = 0;
}
// Too bad, let's process the rest of the file_io_vecs
fssh_size_t totalSize = size;
// first, find out where we have to continue in our iovecs
uint32_t i = 0;
for (; i < count; i++) {
if (size < vecs[i].iov_len)
break;
size -= vecs[i].iov_len;
}
fssh_size_t vecOffset = size;
fssh_size_t bytesLeft = numBytes - size;
while (true) {
for (; fileVecIndex < fileVecCount; fileVecIndex++) {
fssh_file_io_vec &fileVec = fileVecs[fileVecIndex];
fssh_off_t fileOffset = fileVec.offset;
fssh_off_t fileLeft = fssh_min_c(fileVec.length, bytesLeft);
TRACE(("FILE VEC [%lu] length %Ld\n", fileVecIndex, fileLeft));
// process the complete fileVec
while (fileLeft > 0) {
fssh_iovec tempVecs[MAX_TEMP_IO_VECS];
uint32_t tempCount = 0;
// size tracks how much of what is left of the current fileVec
// (fileLeft) has been assigned to tempVecs
size = 0;
// assign what is left of the current fileVec to the tempVecs
for (size = 0; size < fileLeft && i < count
&& tempCount < MAX_TEMP_IO_VECS;) {
// try to satisfy one iovec per iteration (or as much as
// possible)
// bytes left of the current iovec
fssh_size_t vecLeft = vecs[i].iov_len - vecOffset;
if (vecLeft == 0) {
vecOffset = 0;
i++;
continue;
}
TRACE(("fill vec %ld, offset = %lu, size = %lu\n",
i, vecOffset, size));
// actually available bytes
fssh_size_t tempVecSize = fssh_min_c(vecLeft, fileLeft - size);
tempVecs[tempCount].iov_base
= (void *)((fssh_addr_t)vecs[i].iov_base + vecOffset);
tempVecs[tempCount].iov_len = tempVecSize;
tempCount++;
size += tempVecSize;
vecOffset += tempVecSize;
}
fssh_size_t bytes = size;
if (doWrite) {
status = vfs_write_pages(ref->deviceFD, fileOffset,
tempVecs, tempCount, &bytes, false);
} else {
status = vfs_read_pages(ref->deviceFD, fileOffset,
tempVecs, tempCount, &bytes, false);
}
if (status < FSSH_B_OK)
return status;
totalSize += bytes;
bytesLeft -= size;
fileOffset += size;
fileLeft -= size;
//fssh_dprintf("-> file left = %Lu\n", fileLeft);
if (size != bytes || i >= count) {
// there are no more bytes or iovecs, let's bail out
*_numBytes = totalSize;
return FSSH_B_OK;
}
}
}
if (bufferOverflow) {
status = get_file_map(ref, offset + totalSize, bytesLeft, fileVecs,
&fileVecCount);
if (status < FSSH_B_OK && status != FSSH_B_BUFFER_OVERFLOW) {
TRACE(("get_file_map(offset = %Ld, numBytes = %lu) failed: %s\n",
offset, numBytes, fssh_strerror(status)));
return status;
}
bufferOverflow = status == FSSH_B_BUFFER_OVERFLOW;
fileVecIndex = 0;
#ifdef TRACE_FILE_CACHE
fssh_dprintf("got %lu file vecs for %Ld:%lu%s:\n", fileVecCount,
offset + totalSize, numBytes,
bufferOverflow ? " (array too small)" : "");
for (fssh_size_t i = 0; i < fileVecCount; i++) {
fssh_dprintf(" [%lu] offset = %Ld, size = %Ld\n",
i, fileVecs[i].offset, fileVecs[i].length);
}
#endif
} else
break;
}
*_numBytes = totalSize;
return FSSH_B_OK;
}
/*!
This function is called by read_into_cache() (and from there only) - it
can only handle a certain amount of bytes, and read_into_cache() makes
sure that it matches that criterion.
*/
static inline fssh_status_t
read_chunk_into_cache(file_cache_ref *ref, fssh_off_t offset, fssh_size_t numBytes,
int32_t pageOffset, fssh_addr_t buffer, fssh_size_t bufferSize)
{
TRACE(("read_chunk(offset = %Ld, size = %lu, pageOffset = %ld, buffer = %#lx, bufferSize = %lu\n",
offset, size, pageOffset, buffer, bufferSize));
fssh_iovec vecs[MAX_IO_VECS];
int32_t vecCount = 0;
vm_page *pages[MAX_IO_VECS];
int32_t pageIndex = 0;
// allocate pages for the cache and mark them busy
for (fssh_size_t pos = 0; pos < numBytes; pos += FSSH_B_PAGE_SIZE) {
vm_page *page = pages[pageIndex++] = vm_page_allocate_page(PAGE_STATE_FREE);
if (page == NULL)
fssh_panic("no more pages!");
page->state = PAGE_STATE_BUSY;
vm_cache_insert_page(ref, page, offset + pos);
fssh_addr_t virtualAddress = page->Address();
add_to_iovec(vecs, vecCount, MAX_IO_VECS, virtualAddress, FSSH_B_PAGE_SIZE);
// ToDo: check if the array is large enough!
}
mutex_unlock(&ref->lock);
// read file into reserved pages
fssh_status_t status = pages_io(ref, offset, vecs, vecCount, &numBytes, false);
if (status < FSSH_B_OK) {
// reading failed, free allocated pages
fssh_dprintf("file_cache: read pages failed: %s\n", fssh_strerror(status));
mutex_lock(&ref->lock);
for (int32_t i = 0; i < pageIndex; i++) {
vm_cache_remove_page(ref, pages[i]);
vm_page_set_state(pages[i], PAGE_STATE_FREE);
vm_page_put_page(pages[i]);
}
return status;
}
// copy the pages and unmap them again
for (int32_t i = 0; i < vecCount; i++) {
fssh_addr_t base = (fssh_addr_t)vecs[i].iov_base;
fssh_size_t size = vecs[i].iov_len;
// copy to user buffer if necessary
if (bufferSize != 0) {
fssh_size_t bytes = fssh_min_c(bufferSize, size - pageOffset);
user_memcpy((void *)buffer, (void *)(base + pageOffset), bytes);
buffer += bytes;
bufferSize -= bytes;
pageOffset = 0;
}
}
mutex_lock(&ref->lock);
// make the pages accessible in the cache
for (int32_t i = pageIndex; i-- > 0;) {
vm_page_set_state(pages[i], PAGE_STATE_ACTIVE);
vm_page_put_page(pages[i]);
}
return FSSH_B_OK;
}
/*!
This function reads \a size bytes directly from the file into the cache.
If \a bufferSize does not equal zero, \a bufferSize bytes from the data
read in are also copied to the provided \a buffer.
This function always allocates all pages; it is the responsibility of the
calling function to only ask for yet uncached ranges.
The cache_ref lock must be hold when calling this function.
*/
static fssh_status_t
read_into_cache(file_cache_ref *ref, fssh_off_t offset, fssh_size_t size, fssh_addr_t buffer, fssh_size_t bufferSize)
{
TRACE(("read_from_cache: ref = %p, offset = %Ld, size = %lu, buffer = %p, bufferSize = %lu\n",
ref, offset, size, (void *)buffer, bufferSize));
// do we have to read in anything at all?
if (size == 0)
return FSSH_B_OK;
// make sure "offset" is page aligned - but also remember the page offset
int32_t pageOffset = offset & (FSSH_B_PAGE_SIZE - 1);
size = PAGE_ALIGN(size + pageOffset);
offset -= pageOffset;
while (true) {
fssh_size_t chunkSize = size;
if (chunkSize > (MAX_IO_VECS * FSSH_B_PAGE_SIZE))
chunkSize = MAX_IO_VECS * FSSH_B_PAGE_SIZE;
fssh_status_t status = read_chunk_into_cache(ref, offset, chunkSize, pageOffset,
buffer, bufferSize);
if (status != FSSH_B_OK)
return status;
if ((size -= chunkSize) == 0)
return FSSH_B_OK;
if (chunkSize >= bufferSize) {
bufferSize = 0;
buffer = 0;
} else {
bufferSize -= chunkSize - pageOffset;
buffer += chunkSize - pageOffset;
}
offset += chunkSize;
pageOffset = 0;
}
return FSSH_B_OK;
}
/** Like read_chunk_into_cache() but writes data into the cache */
static inline fssh_status_t
write_chunk_to_cache(file_cache_ref *ref, fssh_off_t offset, fssh_size_t numBytes,
int32_t pageOffset, fssh_addr_t buffer, fssh_size_t bufferSize)
{
fssh_iovec vecs[MAX_IO_VECS];
int32_t vecCount = 0;
vm_page *pages[MAX_IO_VECS];
int32_t pageIndex = 0;
fssh_status_t status = FSSH_B_OK;
// ToDo: this should be settable somewhere
bool writeThrough = false;
// allocate pages for the cache and mark them busy
for (fssh_size_t pos = 0; pos < numBytes; pos += FSSH_B_PAGE_SIZE) {
// ToDo: if space is becoming tight, and this cache is already grown
// big - shouldn't we better steal the pages directly in that case?
// (a working set like approach for the file cache)
vm_page *page = pages[pageIndex++] = vm_page_allocate_page(PAGE_STATE_FREE);
page->state = PAGE_STATE_BUSY;
vm_cache_insert_page(ref, page, offset + pos);
fssh_addr_t virtualAddress = page->Address();
add_to_iovec(vecs, vecCount, MAX_IO_VECS, virtualAddress, FSSH_B_PAGE_SIZE);
// ToDo: check if the array is large enough!
}
mutex_unlock(&ref->lock);
// copy contents (and read in partially written pages first)
if (pageOffset != 0) {
// This is only a partial write, so we have to read the rest of the page
// from the file to have consistent data in the cache
fssh_iovec readVec = { vecs[0].iov_base, FSSH_B_PAGE_SIZE };
fssh_size_t bytesRead = FSSH_B_PAGE_SIZE;
status = pages_io(ref, offset, &readVec, 1, &bytesRead, false);
// ToDo: handle errors for real!
if (status < FSSH_B_OK)
fssh_panic("1. pages_io() failed: %s!\n", fssh_strerror(status));
}
fssh_addr_t lastPageOffset = (pageOffset + bufferSize) & (FSSH_B_PAGE_SIZE - 1);
if (lastPageOffset != 0) {
// get the last page in the I/O vectors
fssh_addr_t last = (fssh_addr_t)vecs[vecCount - 1].iov_base
+ vecs[vecCount - 1].iov_len - FSSH_B_PAGE_SIZE;
if (offset + pageOffset + bufferSize == ref->virtual_size) {
// the space in the page after this write action needs to be cleaned
fssh_memset((void *)(last + lastPageOffset), 0, FSSH_B_PAGE_SIZE - lastPageOffset);
} else if (vecCount > 1) {
// the end of this write does not happen on a page boundary, so we
// need to fetch the last page before we can update it
fssh_iovec readVec = { (void *)last, FSSH_B_PAGE_SIZE };
fssh_size_t bytesRead = FSSH_B_PAGE_SIZE;
status = pages_io(ref, offset + numBytes - FSSH_B_PAGE_SIZE, &readVec, 1,
&bytesRead, false);
// ToDo: handle errors for real!
if (status < FSSH_B_OK)
fssh_panic("pages_io() failed: %s!\n", fssh_strerror(status));
}
}
for (int32_t i = 0; i < vecCount; i++) {
fssh_addr_t base = (fssh_addr_t)vecs[i].iov_base;
fssh_size_t bytes = fssh_min_c(bufferSize, fssh_size_t(vecs[i].iov_len - pageOffset));
// copy data from user buffer
user_memcpy((void *)(base + pageOffset), (void *)buffer, bytes);
bufferSize -= bytes;
if (bufferSize == 0)
break;
buffer += bytes;
pageOffset = 0;
}
if (writeThrough) {
// write cached pages back to the file if we were asked to do that
fssh_status_t status = pages_io(ref, offset, vecs, vecCount, &numBytes, true);
if (status < FSSH_B_OK) {
// ToDo: remove allocated pages, ...?
fssh_panic("file_cache: remove allocated pages! write pages failed: %s\n",
fssh_strerror(status));
}
}
mutex_lock(&ref->lock);
// unmap the pages again
// make the pages accessible in the cache
for (int32_t i = pageIndex; i-- > 0;) {
if (writeThrough)
vm_page_set_state(pages[i], PAGE_STATE_ACTIVE);
else
vm_page_set_state(pages[i], PAGE_STATE_MODIFIED);
vm_page_put_page(pages[i]);
}
return status;
}
/** Like read_into_cache() but writes data into the cache. To preserve data consistency,
* it might also read pages into the cache, though, if only a partial page gets written.
* The cache_ref lock must be hold when calling this function.
*/
static fssh_status_t
write_to_cache(file_cache_ref *ref, fssh_off_t offset, fssh_size_t size, fssh_addr_t buffer, fssh_size_t bufferSize)
{
TRACE(("write_to_cache: ref = %p, offset = %Ld, size = %lu, buffer = %p, bufferSize = %lu\n",
ref, offset, size, (void *)buffer, bufferSize));
// make sure "offset" is page aligned - but also remember the page offset
int32_t pageOffset = offset & (FSSH_B_PAGE_SIZE - 1);
size = PAGE_ALIGN(size + pageOffset);
offset -= pageOffset;
while (true) {
fssh_size_t chunkSize = size;
if (chunkSize > (MAX_IO_VECS * FSSH_B_PAGE_SIZE))
chunkSize = MAX_IO_VECS * FSSH_B_PAGE_SIZE;
fssh_status_t status = write_chunk_to_cache(ref, offset, chunkSize, pageOffset, buffer, bufferSize);
if (status != FSSH_B_OK)
return status;
if ((size -= chunkSize) == 0)
return FSSH_B_OK;
if (chunkSize >= bufferSize) {
bufferSize = 0;
buffer = 0;
} else {
bufferSize -= chunkSize - pageOffset;
buffer += chunkSize - pageOffset;
}
offset += chunkSize;
pageOffset = 0;
}
return FSSH_B_OK;
}
static fssh_status_t
satisfy_cache_io(file_cache_ref *ref, fssh_off_t offset, fssh_addr_t buffer, fssh_addr_t lastBuffer,
bool doWrite)
{
fssh_size_t requestSize = buffer - lastBuffer;
if (doWrite)
return write_to_cache(ref, offset, requestSize, lastBuffer, requestSize);
return read_into_cache(ref, offset, requestSize, lastBuffer, requestSize);
}
static fssh_status_t
cache_io(void *_cacheRef, 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;
}
// "offset" and "lastOffset" are always aligned to FSSH_B_PAGE_SIZE,
// the "last*" variables always point to the end of the last
// satisfied request part
fssh_size_t bytesLeft = size, lastLeft = size;
int32_t lastPageOffset = pageOffset;
fssh_addr_t lastBuffer = buffer;
fssh_off_t lastOffset = offset;
mutex_lock(&ref->lock);
for (; bytesLeft > 0; offset += FSSH_B_PAGE_SIZE) {
// check if this page is already in memory
restart:
vm_page *page = vm_cache_lookup_page(ref, offset);
PagePutter pagePutter(page);
if (page != NULL) {
// The page is busy - since we need to unlock the cache sometime
// in the near future, we need to satisfy the request of the pages
// we didn't get yet (to make sure no one else interferes in the
// mean time).
fssh_status_t status = FSSH_B_OK;
if (lastBuffer != buffer) {
status = satisfy_cache_io(ref, lastOffset + lastPageOffset,
buffer, lastBuffer, doWrite);
if (status == FSSH_B_OK) {
lastBuffer = buffer;
lastLeft = bytesLeft;
lastOffset = offset;
lastPageOffset = 0;
pageOffset = 0;
}
}
if (status != FSSH_B_OK) {
mutex_unlock(&ref->lock);
return status;
}
if (page->state == PAGE_STATE_BUSY) {
mutex_unlock(&ref->lock);
// ToDo: don't wait forever!
fssh_snooze(20000);
mutex_lock(&ref->lock);
goto restart;
}
}
fssh_size_t bytesInPage = fssh_min_c(fssh_size_t(FSSH_B_PAGE_SIZE - pageOffset), bytesLeft);
fssh_addr_t virtualAddress;
TRACE(("lookup page from offset %Ld: %p, size = %lu, pageOffset = %lu\n", offset, page, bytesLeft, pageOffset));
if (page != NULL) {
virtualAddress = page->Address();
// and copy the contents of the page already in memory
if (doWrite) {
user_memcpy((void *)(virtualAddress + pageOffset), (void *)buffer, bytesInPage);
// make sure the page is in the modified list
if (page->state != PAGE_STATE_MODIFIED)
vm_page_set_state(page, PAGE_STATE_MODIFIED);
} else
user_memcpy((void *)buffer, (void *)(virtualAddress + pageOffset), bytesInPage);
if (bytesLeft <= bytesInPage) {
// we've read the last page, so we're done!
mutex_unlock(&ref->lock);
return FSSH_B_OK;
}
// prepare a potential gap request
lastBuffer = buffer + bytesInPage;
lastLeft = bytesLeft - bytesInPage;
lastOffset = offset + FSSH_B_PAGE_SIZE;
lastPageOffset = 0;
}
if (bytesLeft <= bytesInPage)
break;
buffer += bytesInPage;
bytesLeft -= bytesInPage;
pageOffset = 0;
}
// fill the last remaining bytes of the request (either write or read)
fssh_status_t status;
if (doWrite)
status = write_to_cache(ref, lastOffset + lastPageOffset, lastLeft, lastBuffer, lastLeft);
else
status = read_into_cache(ref, lastOffset + lastPageOffset, lastLeft, lastBuffer, lastLeft);
mutex_unlock(&ref->lock);
return status;
}
} // 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, int fd)
{
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->deviceFD = fd;
ref->virtual_size = size;
// get vnode
fssh_status_t error = vfs_get_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));
vfs_put_vnode(ref->node);
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));
// write all modified pages and resize the cache to 0 to free all pages
// it contains
vm_cache_write_modified(ref, false);
mutex_lock(&ref->lock);
vm_cache_resize(ref, 0);
mutex_destroy(&ref->lock);
vfs_put_vnode(ref->node);
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;
fssh_file_cache_invalidate_file_map(_cacheRef, 0, size);
// ToDo: make this better (we would only need to extend or shrink the map)
mutex_lock(&ref->lock);
fssh_status_t status = vm_cache_resize(ref, size);
mutex_unlock(&ref->lock);
return status;
}
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 vm_cache_write_modified(ref, true);
}
fssh_status_t
fssh_file_cache_read_pages(void *_cacheRef, fssh_off_t offset, const fssh_iovec *vecs, fssh_size_t count, fssh_size_t *_numBytes)
{
file_cache_ref *ref = (file_cache_ref *)_cacheRef;
return pages_io(ref, offset, vecs, count, _numBytes, false);
}
fssh_status_t
fssh_file_cache_write_pages(void *_cacheRef, fssh_off_t offset, const fssh_iovec *vecs, fssh_size_t count, fssh_size_t *_numBytes)
{
file_cache_ref *ref = (file_cache_ref *)_cacheRef;
fssh_status_t status = pages_io(ref, offset, vecs, count, _numBytes, true);
TRACE(("file_cache_write_pages(ref = %p, offset = %Ld, vecs = %p, count = %lu, bytes = %lu) = %ld\n",
ref, offset, vecs, count, *_numBytes, status));
return status;
}
fssh_status_t
fssh_file_cache_read(void *_cacheRef, 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, offset, (fssh_addr_t)bufferBase, _size, false);
}
fssh_status_t
fssh_file_cache_write(void *_cacheRef, 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, 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;
}
fssh_status_t
fssh_file_cache_invalidate_file_map(void *_cacheRef, fssh_off_t offset, fssh_off_t size)
{
file_cache_ref *ref = (file_cache_ref *)_cacheRef;
// ToDo: honour offset/size parameters
TRACE(("file_cache_invalidate_file_map(offset = %Ld, size = %Ld)\n", offset, size));
mutex_lock(&ref->lock);
ref->map.Free();
mutex_unlock(&ref->lock);
return FSSH_B_OK;
}