/*! Removes a reference from the specified \a block. If this was the last reference, the block is moved into the unused list. In low memory situations, it will also free some blocks from that list, but not necessarily the \a block it just released. */ static void put_cached_block(block_cache* cache, cached_block* block) { #ifdef DEBUG_CHANGED if (!block->is_dirty && block->compare != NULL && memcmp(block->current_data, block->compare, cache->block_size)) { fssh_dprintf("new block:\n"); fssh_dump_block((const char*)block->current_data, 256, " "); fssh_dprintf("unchanged block:\n"); fssh_dump_block((const char*)block->compare, 256, " "); write_cached_block(cache, block); fssh_panic("block_cache: supposed to be clean block was changed!\n"); cache->Free(block->compare); block->compare = NULL; } #endif if (--block->ref_count == 0 && block->transaction == NULL && block->previous_transaction == NULL) { // This block is not used anymore, and not part of any transaction if (block->discard) { cache->RemoveBlock(block); } else { // put this block in the list of unused blocks block->unused = true; cache->unused_blocks.Add(block); } } if (cache->allocated_block_count > kMaxBlockCount) { cache->RemoveUnusedBlocks(INT32_MAX, cache->allocated_block_count - kMaxBlockCount); } }
static void put_cached_block(block_cache* cache, fssh_off_t blockNumber) { if (blockNumber < 0 || blockNumber >= cache->max_blocks) { fssh_panic("put_cached_block: invalid block number %" FSSH_B_PRIdOFF " (max %" FSSH_B_PRIdOFF ")", blockNumber, cache->max_blocks - 1); } cached_block* block = (cached_block*)hash_lookup(cache->hash, &blockNumber); if (block != NULL) put_cached_block(cache, block); }
/*! Retrieves the block \a blockNumber from the hash table, if it's already there, or reads it from the disk. \param _allocated tells you whether or not a new block has been allocated to satisfy your request. \param readBlock if \c false, the block will not be read in case it was not already in the cache. The block you retrieve may contain random data. */ static cached_block* get_cached_block(block_cache* cache, fssh_off_t blockNumber, bool* _allocated, bool readBlock = true) { if (blockNumber < 0 || blockNumber >= cache->max_blocks) { fssh_panic("get_cached_block: invalid block number %" FSSH_B_PRIdOFF " (max %" FSSH_B_PRIdOFF ")", blockNumber, cache->max_blocks - 1); return NULL; } cached_block* block = (cached_block*)hash_lookup(cache->hash, &blockNumber); *_allocated = false; if (block == NULL) { // read block into cache block = cache->NewBlock(blockNumber); if (block == NULL) return NULL; hash_insert(cache->hash, block); *_allocated = true; } if (*_allocated && readBlock) { int32_t blockSize = cache->block_size; if (fssh_read_pos(cache->fd, blockNumber * blockSize, block->current_data, blockSize) < blockSize) { cache->RemoveBlock(block); FATAL(("could not read block %" FSSH_B_PRIdOFF "\n", blockNumber)); return NULL; } } if (block->unused) { //TRACE(("remove block %Ld from unused\n", blockNumber)); block->unused = false; cache->unused_blocks.Remove(block); } block->ref_count++; block->accessed++; return block; }
void block_cache::FreeBlock(cached_block* block) { Free(block->current_data); if (block->original_data != NULL || block->parent_data != NULL) { fssh_panic("block_cache::FreeBlock(): %" FSSH_B_PRIdOFF ", original %p, parent %p\n", block->block_number, block->original_data, block->parent_data); } #ifdef DEBUG_CHANGED Free(block->compare); #endif delete block; }
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 = %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); }
void fssh_kernel_debugger(const char *message) { fssh_panic("%s", message); }
/*! Returns the writable block data for the requested blockNumber. If \a cleared is true, the block is not read from disk; an empty block is returned. This is the only method to insert a block into a transaction. It makes sure that the previous block contents are preserved in that case. */ static void* get_writable_cached_block(block_cache* cache, fssh_off_t blockNumber, fssh_off_t base, fssh_off_t length, int32_t transactionID, bool cleared) { TRACE(("get_writable_cached_block(blockNumber = %Ld, transaction = %d)\n", blockNumber, transactionID)); if (blockNumber < 0 || blockNumber >= cache->max_blocks) { fssh_panic("get_writable_cached_block: invalid block number %" FSSH_B_PRIdOFF " (max %" FSSH_B_PRIdOFF ")", blockNumber, cache->max_blocks - 1); } bool allocated; cached_block* block = get_cached_block(cache, blockNumber, &allocated, !cleared); if (block == NULL) return NULL; block->discard = false; // if there is no transaction support, we just return the current block if (transactionID == -1) { if (cleared) fssh_memset(block->current_data, 0, cache->block_size); block->is_dirty = true; // mark the block as dirty return block->current_data; } cache_transaction* transaction = block->transaction; if (transaction != NULL && transaction->id != transactionID) { // TODO: we have to wait here until the other transaction is done. // Maybe we should even panic, since we can't prevent any deadlocks. fssh_panic("get_writable_cached_block(): asked to get busy writable block (transaction %d)\n", (int)transaction->id); put_cached_block(cache, block); return NULL; } if (transaction == NULL && transactionID != -1) { // get new transaction transaction = lookup_transaction(cache, transactionID); if (transaction == NULL) { fssh_panic("get_writable_cached_block(): invalid transaction %d!\n", (int)transactionID); put_cached_block(cache, block); return NULL; } if (!transaction->open) { fssh_panic("get_writable_cached_block(): transaction already done!\n"); put_cached_block(cache, block); return NULL; } block->transaction = transaction; // attach the block to the transaction block list block->transaction_next = transaction->first_block; transaction->first_block = block; transaction->num_blocks++; } bool wasUnchanged = block->original_data == NULL || block->previous_transaction != NULL; if (!(allocated && cleared) && block->original_data == NULL) { // we already have data, so we need to preserve it block->original_data = cache->Allocate(); if (block->original_data == NULL) { FATAL(("could not allocate original_data\n")); put_cached_block(cache, block); return NULL; } fssh_memcpy(block->original_data, block->current_data, cache->block_size); } if (block->parent_data == block->current_data) { // remember any previous contents for the parent transaction block->parent_data = cache->Allocate(); if (block->parent_data == NULL) { // TODO: maybe we should just continue the current transaction in this case... FATAL(("could not allocate parent\n")); put_cached_block(cache, block); return NULL; } fssh_memcpy(block->parent_data, block->current_data, cache->block_size); transaction->sub_num_blocks++; } else if (transaction != NULL && transaction->has_sub_transaction && block->parent_data == NULL && wasUnchanged) transaction->sub_num_blocks++; if (cleared) fssh_memset(block->current_data, 0, cache->block_size); block->is_dirty = true; return block->current_data; }