file-cache.c revision 72767afd6cd99439a4d93fcba1621bfcac5e791c
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen/* Copyright (c) 2004 Timo Sirainen */
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen#include "lib.h"
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen#include "buffer.h"
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen#include "mmap-util.h"
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen#include "file-cache.h"
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen#include <sys/stat.h>
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainenstruct file_cache {
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen int fd;
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen buffer_t *page_bitmask;
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen void *mmap_base;
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen size_t mmap_length;
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen size_t read_highwater;
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen};
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainenstruct file_cache *file_cache_new(int fd)
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen{
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen struct file_cache *cache;
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen cache = i_new(struct file_cache, 1);
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen cache->fd = fd;
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen cache->page_bitmask = buffer_create_dynamic(default_pool, 128);
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen return cache;
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen}
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainenvoid file_cache_free(struct file_cache *cache)
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen{
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen if (cache->mmap_base != NULL) {
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen if (munmap_anon(cache->mmap_base, cache->mmap_length) < 0)
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen i_error("munmap_anon() failed: %m");
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen }
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen buffer_free(cache->page_bitmask);
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen i_free(cache);
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen}
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainenvoid file_cache_set_fd(struct file_cache *cache, int fd)
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen{
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen cache->fd = fd;
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen file_cache_invalidate(cache, 0, cache->mmap_length);
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen}
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainenssize_t file_cache_read(struct file_cache *cache, uoff_t offset, size_t size)
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen{
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen size_t page_size = mmap_get_page_size();
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen size_t poffset, psize, mmap_needed, dest_offset, dest_size;
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen unsigned char *bits, *dest;
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen ssize_t ret;
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen i_assert(size < INT_MAX);
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen if (offset + size > cache->mmap_length &&
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen offset + size - cache->mmap_length > 1024*1024) {
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen /* growing more than a megabyte, make sure that the
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen file is large enough so we don't allocate memory
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen more than needed */
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen struct stat st;
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen if (fstat(cache->fd, &st) < 0) {
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen i_error("fstat(file_cache) failed: %m");
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen return -1;
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen }
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen if (offset + size > (uoff_t)st.st_size) {
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen if (offset >= (uoff_t)st.st_size)
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen return 0;
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen size = (uoff_t)st.st_size - offset;
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen }
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen }
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen poffset = offset / page_size;
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen psize = (offset + size + page_size-1) / page_size - poffset;
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen i_assert(psize > 0);
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen mmap_needed = (poffset + psize) * page_size;
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen if (mmap_needed > cache->mmap_length) {
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen /* grow mmaping */
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen if (cache->mmap_base == NULL) {
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen cache->mmap_base = mmap_anon(mmap_needed);
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen if (cache->mmap_base == MAP_FAILED) {
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen i_error("mmap_anon(%"PRIuSIZE_T") failed: %m",
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen mmap_needed);
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen return -1;
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen }
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen } else {
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen cache->mmap_base = mremap_anon(cache->mmap_base,
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen cache->mmap_length,
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen mmap_needed,
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen MREMAP_MAYMOVE);
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen if (cache->mmap_base == MAP_FAILED) {
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen i_error("mremap_anon(%"PRIuSIZE_T") failed: %m",
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen mmap_needed);
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen return -1;
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen }
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen }
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen cache->mmap_length = mmap_needed;
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen }
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen bits = buffer_get_space_unsafe(cache->page_bitmask, poffset / CHAR_BIT,
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen (psize + CHAR_BIT - 1) / CHAR_BIT);
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen dest_offset = poffset * page_size;
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen dest = PTR_OFFSET(cache->mmap_base, dest_offset);
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen dest_size = page_size;
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen poffset %= CHAR_BIT;
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen while (psize > 0) {
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen if (bits[poffset / CHAR_BIT] & (1 << (poffset % CHAR_BIT))) {
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen /* page is already in cache */
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen psize--; poffset++;
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen dest += page_size;
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen dest_offset += page_size;
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen continue;
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen }
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen ret = pread(cache->fd, dest, dest_size, dest_offset);
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen if (ret <= 0) {
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen if (ret < 0)
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen return -1;
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen /* EOF */
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen /* FIXME: we should mark the last block cached and
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen invalidate it only when trying to read past the
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen file */
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen return dest_offset <= offset ? 0 :
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen dest_offset - offset < size ?
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen dest_offset - offset : size;
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen }
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen dest += ret;
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen dest_offset += ret;
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen if (cache->read_highwater < dest_offset)
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen cache->read_highwater = dest_offset;
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen if ((size_t)ret != dest_size) {
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen /* partial read - probably EOF but make sure. */
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen dest_size -= ret;
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen continue;
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen }
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen bits[poffset / CHAR_BIT] |= 1 << (poffset % CHAR_BIT);
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen dest_size = page_size;
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen psize--; poffset++;
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen }
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen return size;
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen}
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainenconst void *file_cache_get_map(struct file_cache *cache, size_t *size_r)
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen{
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen *size_r = cache->read_highwater;
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen return cache->mmap_base;
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen}
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainenvoid file_cache_invalidate(struct file_cache *cache, uoff_t offset, size_t size)
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen{
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen size_t page_size = mmap_get_page_size();
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen unsigned char *bits, mask;
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen unsigned int i;
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen if (offset >= cache->read_highwater)
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen return;
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen if (size > cache->read_highwater - offset)
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen size = cache->read_highwater - offset;
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen size = (offset + size + page_size-1) / page_size;
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen offset /= page_size;
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen size -= offset;
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen
72767afd6cd99439a4d93fcba1621bfcac5e791cTimo Sirainen if (size != 1) {
72767afd6cd99439a4d93fcba1621bfcac5e791cTimo Sirainen /* tell operating system that we don't need the memory anymore
72767afd6cd99439a4d93fcba1621bfcac5e791cTimo Sirainen and it may free it. don't bother to do it for single pages,
72767afd6cd99439a4d93fcba1621bfcac5e791cTimo Sirainen there's a good chance that they get re-read back
72767afd6cd99439a4d93fcba1621bfcac5e791cTimo Sirainen immediately. */
72767afd6cd99439a4d93fcba1621bfcac5e791cTimo Sirainen (void)madvise(PTR_OFFSET(cache->mmap_base, offset * page_size),
72767afd6cd99439a4d93fcba1621bfcac5e791cTimo Sirainen size * page_size, MADV_DONTNEED);
72767afd6cd99439a4d93fcba1621bfcac5e791cTimo Sirainen }
72767afd6cd99439a4d93fcba1621bfcac5e791cTimo Sirainen
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen bits = buffer_get_space_unsafe(cache->page_bitmask, offset / CHAR_BIT,
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen (size + CHAR_BIT - 1) / CHAR_BIT);
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen /* set the first byte */
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen for (i = offset % CHAR_BIT, mask = 0; i < CHAR_BIT && size > 0; i++) {
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen mask |= 1 << i;
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen size--;
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen }
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen *bits++ &= ~mask;
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen /* set the middle bytes */
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen memset(bits, 0, size / CHAR_BIT);
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen bits += size / CHAR_BIT;
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen size %= CHAR_BIT;
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen /* set the last byte */
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen if (size > 0) {
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen mask = 0;
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen for (i = 0, mask = 0; i < size; i++)
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen mask |= 1 << i;
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen *bits &= ~mask;
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen }
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen}