/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include #include #include #include #include #include "block_cache.h" #include "block_cache_priv.h" #include "crypt.h" #include "debug.h" #include "debug_stats.h" #include "error_reporting.h" #include "transaction.h" static bool print_cache_lookup = false; static bool print_cache_lookup_verbose = false; static bool print_block_ops = false; static bool print_block_load = false; static bool print_block_store = false; static bool print_block_move = false; static bool print_block_decrypt_encrypt = false; static bool print_clean_transaction = false; static bool print_mac_update = false; static bool print_cache_get_ref_block_count = true; #define BLOCK_CACHE_GUARD_1 (0xdead0001dead0003) #define BLOCK_CACHE_GUARD_2 (0xdead0005dead0007) static struct list_node block_cache_lru = LIST_INITIAL_VALUE(block_cache_lru); static struct block_cache_entry block_cache_entries[BLOCK_CACHE_SIZE]; static bool block_cache_init_called = false; static bool block_cache_entry_data_is_valid( const struct block_cache_entry* entry) { return entry->state == BLOCK_ENTRY_DATA_CLEAN_DECRYPTED || entry->state == BLOCK_ENTRY_DATA_CLEAN_ENCRYPTED || entry->state == BLOCK_ENTRY_DATA_DIRTY_DECRYPTED || entry->state == BLOCK_ENTRY_DATA_DIRTY_ENCRYPTED; } static bool block_cache_entry_data_is_dirty( const struct block_cache_entry* entry) { return entry->state == BLOCK_ENTRY_DATA_DIRTY_DECRYPTED || entry->state == BLOCK_ENTRY_DATA_DIRTY_ENCRYPTED; } static bool block_cache_entry_data_is_encrypted( const struct block_cache_entry* entry) { return entry->state == BLOCK_ENTRY_DATA_CLEAN_ENCRYPTED || entry->state == BLOCK_ENTRY_DATA_DIRTY_ENCRYPTED; } static bool block_cache_entry_data_is_decrypted( const struct block_cache_entry* entry) { return entry->state == BLOCK_ENTRY_DATA_CLEAN_DECRYPTED || entry->state == BLOCK_ENTRY_DATA_DIRTY_DECRYPTED; } static const char* block_cache_entry_data_state_name( enum block_cache_entry_data_state state) { switch (state) { case BLOCK_ENTRY_DATA_INVALID: return "BLOCK_ENTRY_DATA_INVALID"; case BLOCK_ENTRY_DATA_LOADING: return "BLOCK_ENTRY_DATA_LOADING"; case BLOCK_ENTRY_DATA_LOAD_FAILED: return "BLOCK_ENTRY_DATA_LOAD_FAILED"; case BLOCK_ENTRY_DATA_NOT_FOUND: return "BLOCK_ENTRY_DATA_NOT_FOUND"; case BLOCK_ENTRY_DATA_CLEAN_DECRYPTED: return "BLOCK_ENTRY_DATA_CLEAN_DECRYPTED"; case BLOCK_ENTRY_DATA_CLEAN_ENCRYPTED: return "BLOCK_ENTRY_DATA_CLEAN_ENCRYPTED"; case BLOCK_ENTRY_DATA_DIRTY_DECRYPTED: return "BLOCK_ENTRY_DATA_DIRTY_DECRYPTED"; case BLOCK_ENTRY_DATA_DIRTY_ENCRYPTED: return "BLOCK_ENTRY_DATA_DIRTY_ENCRYPTED"; } } /** * block_cache_queue_io_op - Helper function to start a read or write operation * @entry: Cache entry. * @io_op: BLOCK_CACHE_IO_OP_READ or BLOCK_CACHE_IO_OP_WRITE. * * Set io_op for cache entry and add it to the tail of the io_ops for the * block device that the cache entry belongs to. */ static void block_cache_queue_io_op(struct block_cache_entry* entry, int io_op) { assert(io_op == BLOCK_CACHE_IO_OP_READ || io_op == BLOCK_CACHE_IO_OP_WRITE); assert(entry->io_op == BLOCK_CACHE_IO_OP_NONE); assert(entry->dev); assert(!list_in_list(&entry->io_op_node)); entry->io_op = io_op; list_add_tail(&entry->dev->io_ops, &entry->io_op_node); } /** * block_cache_queue_read - Start a read operation * @entry: Cache entry. */ static void block_cache_queue_read(struct block_cache_entry* entry) { assert(!block_cache_entry_data_is_dirty(entry)); entry->state = BLOCK_ENTRY_DATA_LOADING; block_cache_queue_io_op(entry, BLOCK_CACHE_IO_OP_READ); stats_timer_start(STATS_CACHE_START_READ); entry->dev->start_read(entry->dev, entry->block); stats_timer_stop(STATS_CACHE_START_READ); } /** * block_cache_queue_write - Start a write operation * @entry: Cache entry. */ static void block_cache_queue_write(struct block_cache_entry* entry, const void* encrypted_data) { block_cache_queue_io_op(entry, BLOCK_CACHE_IO_OP_WRITE); stats_timer_start(STATS_CACHE_START_WRITE); entry->dev->start_write(entry->dev, entry->block, encrypted_data, entry->block_size, entry->is_superblock); stats_timer_stop(STATS_CACHE_START_WRITE); } /** * block_cache_complete_io - Wait for io operation on block device to complete * @dev: Block device to wait for */ static void block_cache_complete_io(struct block_device* dev) { while (!list_is_empty(&dev->io_ops)) { assert(dev->wait_for_io); dev->wait_for_io(dev); } } /** * block_cache_pop_io_op - Get cache entry for completed read or write operation * @dev: Block device * @block: Block number * @io_op: BLOCK_CACHE_IO_OP_READ or BLOCK_CACHE_IO_OP_WRITE. * * Finds block cache entry that matches @dev and @block and remove it from * the io_ops queue of the block device. * * This is a helper function for block_cache_complete_read and * block_cache_complete_write. * * Return: Matching block cache entry. */ static struct block_cache_entry* block_cache_pop_io_op(struct block_device* dev, data_block_t block, unsigned int io_op) { struct block_cache_entry* entry; list_for_every_entry(&dev->io_ops, entry, struct block_cache_entry, io_op_node) { if (entry->block == block) { assert(entry->dev == dev); assert(entry->io_op == io_op); entry->io_op = BLOCK_CACHE_IO_OP_NONE; list_delete(&entry->io_op_node); return entry; } assert(false); /* Out of order completion not expected */ } assert(false); /* No matching entry found */ return NULL; } /** * block_cache_complete_read - Read complete callback from block device * @dev: Block device * @block: Block number * @data: Pointer to encrypted data, only valid if @res is * &block_read_error.BLOCK_READ_SUCCESS * @data_size: Data size, must match block size of device. * @res: &block_read_error.BLOCK_READ_SUCCESS if read operation was * successful, otherwise describes the error. * * Calculates mac and decrypts data into cache entry. Does not validate mac. */ void block_cache_complete_read(struct block_device* dev, data_block_t block, const void* data, size_t data_size, enum block_read_error res) { int ret; struct block_cache_entry* entry; assert(data_size <= sizeof(entry->data)); assert(data_size == dev->block_size); entry = block_cache_pop_io_op(dev, block, BLOCK_CACHE_IO_OP_READ); assert(entry->state == BLOCK_ENTRY_DATA_LOADING); switch (res) { case BLOCK_READ_SUCCESS: /* handled below */ break; case BLOCK_READ_IO_ERROR: printf("%s: load block %" PRIu64 " failed\n", __func__, entry->block); entry->state = BLOCK_ENTRY_DATA_LOAD_FAILED; return; case BLOCK_READ_NO_DATA: printf("%s: load block %" PRIu64 " failed, no data\n", __func__, entry->block); entry->state = BLOCK_ENTRY_DATA_NOT_FOUND; return; } assert(res == BLOCK_READ_SUCCESS); entry->block_size = data_size; /* TODO: change decrypt function to take separate in/out buffers */ memcpy(entry->data, data, data_size); stats_timer_start(STATS_FS_READ_BLOCK_CALC_MAC); ret = calculate_mac(entry->key, &entry->mac, entry->data, entry->block_size); stats_timer_stop(STATS_FS_READ_BLOCK_CALC_MAC); assert(!ret); /* TODO: check mac here instead of when getting data from the cache? */ if (print_block_load) { printf("%s: load/decrypt block %" PRIu64 " complete\n", __func__, entry->block); } entry->state = BLOCK_ENTRY_DATA_CLEAN_ENCRYPTED; } /** * block_cache_complete_write - Write complete callback from block device * @dev: Block device * @block: Block number * @failed: true if write operation failed, and data is not on disc. If * block device has tamper detection, e.g. rpmb, passing false here * means that the secure side block device code has verified that * the data was written to disk. */ void block_cache_complete_write(struct block_device* dev, data_block_t block, enum block_write_error res) { struct block_cache_entry* entry; entry = block_cache_pop_io_op(dev, block, BLOCK_CACHE_IO_OP_WRITE); if (print_block_store) { printf("%s: write block %" PRIu64 " complete\n", __func__, entry->block); } assert(entry->dirty_tr); if (res == BLOCK_WRITE_SUCCESS) { entry->dirty_tr = NULL; entry->pinned = false; } else { pr_err("write block %" PRIu64 " failed, fail transaction\n", entry->block); transaction_fail(entry->dirty_tr); if (res == BLOCK_WRITE_SYNC_FAILED) { /* * We have to fail ALL pending transactions here because an fsync * failed and we don't know which write caused that failure. * * TODO: Should we fail only transactions that write to non-secure * devices? I.e. not fail TP transactions? * * TODO: storageproxy could track which file failed to sync and * communicate this back so we only have to fail transactions that * touched that backing file. */ pr_err("An fsync failed, fail all pending transactions\n"); fs_fail_all_transactions(); } /* * Failing the transaction must not clear the block number, as we rely * on the block number + pinned flag to reserve and reuse the block * cache entry when reinitializing a special transaction. */ assert(block == entry->block); if (res == BLOCK_WRITE_FAILED_UNKNOWN_STATE) { /* * We don't know what was written, force superblock to be rewritten. * This must be done after we have failed the transaction in case we * need to reuse block that was part of this transaction. */ fs_unknown_super_block_state_all(); } } } /** * block_cache_entry_has_refs - Check if cache entry is referenced * @entry: Cache entry * * Return: true if there are no references to @entry. */ static bool block_cache_entry_has_refs(struct block_cache_entry* entry) { return !list_is_empty(&entry->obj.ref_list); } /** * block_cache_entry_has_one_ref - Check if cache entry is referenced once * @entry: Cache entry * * Return: true if there is a single reference to @entry. */ static bool block_cache_entry_has_one_ref(struct block_cache_entry* entry) { return list_length(&entry->obj.ref_list) == 1; } /** * block_cache_entry_decrypt - Decrypt cache entry * @entry: Cache entry */ static void block_cache_entry_decrypt(struct block_cache_entry* entry) { int ret; const struct iv* iv = NULL; /* TODO: support external iv */ void* decrypt_data; size_t decrypt_size; assert(block_cache_entry_data_is_encrypted(entry)); decrypt_data = entry->data; decrypt_size = entry->block_size; if (!iv) { iv = (void*)entry->data; assert(decrypt_size > sizeof(*iv)); decrypt_data += sizeof(*iv); decrypt_size -= sizeof(*iv); } stats_timer_start(STATS_FS_READ_BLOCK_DECRYPT); ret = storage_decrypt(entry->key, decrypt_data, decrypt_size, iv); stats_timer_stop(STATS_FS_READ_BLOCK_DECRYPT); assert(!ret); if (print_block_decrypt_encrypt) { printf("%s: decrypt block %" PRIu64 " complete\n", __func__, entry->block); } if (entry->state == BLOCK_ENTRY_DATA_CLEAN_ENCRYPTED) { entry->state = BLOCK_ENTRY_DATA_CLEAN_DECRYPTED; } else if (entry->state == BLOCK_ENTRY_DATA_DIRTY_ENCRYPTED) { /* * We leave blocks in DIRTY_ENCRYPTED state after computing a MAC but * before flushing the block from the cache. We may decrypt a block * again to read it before write back, which is fine as it will be * re-encrypted (with the same IV) when flushed for write back. */ entry->state = BLOCK_ENTRY_DATA_DIRTY_DECRYPTED; } else { /* Covered by assert that the entry was encrypted above. */ assert(false); } } /** * block_cache_entry_encrypt - Encrypt cache entry and update mac * @entry: Cache entry */ static void block_cache_entry_encrypt(struct block_cache_entry* entry) { int ret; void* encrypt_data; size_t encrypt_size; struct mac mac; struct iv* iv = NULL; /* TODO: support external iv */ assert(entry->state == BLOCK_ENTRY_DATA_DIRTY_DECRYPTED); assert(!block_cache_entry_has_refs(entry)); encrypt_data = entry->data; encrypt_size = entry->block_size; if (!iv) { iv = (void*)entry->data; assert(encrypt_size > sizeof(*iv)); encrypt_data += sizeof(*iv); encrypt_size -= sizeof(*iv); } stats_timer_start(STATS_FS_WRITE_BLOCK_ENCRYPT); ret = storage_encrypt(entry->key, encrypt_data, encrypt_size, iv); stats_timer_stop(STATS_FS_WRITE_BLOCK_ENCRYPT); assert(!ret); entry->state = BLOCK_ENTRY_DATA_DIRTY_ENCRYPTED; if (print_block_decrypt_encrypt) { printf("%s: encrypt block %" PRIu64 " complete\n", __func__, entry->block); } if (!entry->dirty_mac) { mac = entry->mac; } stats_timer_start(STATS_FS_WRITE_BLOCK_CALC_MAC); ret = calculate_mac(entry->key, &entry->mac, entry->data, entry->block_size); stats_timer_stop(STATS_FS_WRITE_BLOCK_CALC_MAC); assert(!ret); if (!entry->dirty_mac) { assert(!CRYPTO_memcmp(&mac, &entry->mac, sizeof(mac))); } entry->dirty_mac = false; // assert(!entry->parent || entry->parent->ref_count); // assert(!entry->parent || entry->parent->dirty_ref); } /** * block_cache_entry_clean - Write dirty cache entry to disc * @entry: Cache entry * * Does not wait for write to complete. */ static void block_cache_entry_clean(struct block_cache_entry* entry) { if (!block_cache_entry_data_is_dirty(entry)) { return; } if (print_block_store) { printf("%s: encrypt block %" PRIu64 "\n", __func__, entry->block); } assert(entry->block_size <= sizeof(entry->data)); if (entry->state == BLOCK_ENTRY_DATA_DIRTY_DECRYPTED) { block_cache_entry_encrypt(entry); } assert(entry->state == BLOCK_ENTRY_DATA_DIRTY_ENCRYPTED); /* TODO: release ref to parent */ assert(entry->dirty_tr); /* * We have to save the current transaction for this entry because we need it * to check for transaction failure after queueing the write. Transactions * are managed by the storage client layer, and thus will outlive this * function, which is internal to the block cache. */ struct transaction* tr = entry->dirty_tr; assert(entry->dirty_tr->fs); struct transaction* itr = entry->dirty_tr->fs->initial_super_block_tr; /* * Block(s) in fs->initial_super_block_tr must be written before any other * blocks to the same filesystem. */ if (itr && itr != entry->dirty_tr) { printf("%s: write initial superblock before block %" PRIu64 "\n", __func__, entry->block); transaction_initial_super_block_complete(itr); /* * Check that initial_super_block_tr was cleared. If it was not, it must * have failed to write the initial super block and the transaction * that entry belongs to must also fail. */ if (entry->dirty_tr->fs->initial_super_block_tr) { /* * transaction_initial_super_block_complete() always reinitialize * initial_super_block_tr if the write failed. */ assert(!entry->dirty_tr->fs->initial_super_block_tr->failed); transaction_fail(entry->dirty_tr); assert(entry->state == BLOCK_ENTRY_DATA_INVALID); return; } } block_cache_queue_write(entry, entry->data); /* * If we fail the transaction in block_cache_complete_write(), which is * currently called during block_cache_queue_write(), we will clear the * dirty flag on all cache entries associate with the transaction, including * the one we're currently trying to clean. * * We can't redundantly clear the flag again here if the transaction has * failed, because the write failure may have forced us to trigger * fs_unknown_super_block_state_all(). Triggering this function creates * writes for the current superblock state of each filesystem, and this may * have reused the (now) clean entry we are trying to clean. If so, * entry->dirty must stay set. */ if (!tr->failed) { assert(entry->state == BLOCK_ENTRY_DATA_DIRTY_ENCRYPTED); entry->state = BLOCK_ENTRY_DATA_CLEAN_ENCRYPTED; } } /** * block_cache_entry_score - Get a keep score * @entry: Block cache entry to check * @index: Number of available entries before @entry in lru. * * Return: A score value indicating in what order entries that are close in the * lru should be replaced. */ static unsigned int block_cache_entry_score(struct block_cache_entry* entry, unsigned int index) { if (!entry->dev) { return UINT_MAX; } return index * (block_cache_entry_data_is_dirty(entry) ? (entry->dirty_tmp ? 1 : 2) : 4); } /** * block_cache_entry_discard_dirty - Discard cache entry (can be dirty). * @entry: Block cache entry to discard */ static void block_cache_entry_discard_dirty(struct block_cache_entry* entry) { assert(!entry->dirty_ref); assert(!list_in_list(&entry->io_op_node)); entry->state = BLOCK_ENTRY_DATA_INVALID; entry->dev = NULL; entry->block = DATA_BLOCK_INVALID; entry->dirty_tr = NULL; /* We have to unpin here because we're clearing the block number */ entry->pinned = false; entry->is_superblock = false; entry->dirty_mac = false; } /** * block_cache_entry_discard - Discard cache entry (must be clean and unused). * @entry: Block cache entry to discard */ static void block_cache_entry_discard(struct block_cache_entry* entry) { assert(!block_cache_entry_has_refs(entry)); assert(!entry->dirty_ref); assert(!entry->dirty_tr); assert(!list_in_list(&entry->io_op_node)); block_cache_entry_discard_dirty(entry); } /** * block_cache_lookup - Get cache entry for a specific block * @fs: File system state object, or %NULL is @allocate is %false. * @dev: Block device object. * @block: Block number * @allocate: If true, assign an unused entry to the specified @dev,@block * if no matching entry is found. * * Return: cache entry matching @dev and @block. If no matching entry is found, * and @allocate is true, pick an unused entry and update it to match. If no * entry can be used, return NULL. */ static struct block_cache_entry* block_cache_lookup(struct fs* fs, struct block_device* dev, data_block_t block, bool allocate) { struct block_cache_entry* entry; struct block_cache_entry* unused_entry = NULL; unsigned int unused_entry_score = 0; unsigned int score; unsigned int available = 0; unsigned int in_use = 0; assert(dev); assert(fs || !allocate); stats_timer_start(STATS_CACHE_LOOKUP); /* * We may need to attempt to find and flush a cache entry multiple times * before finding one that we could successfully use that was not reused * during the clean. This relies on the block cache being large enough to * hold a super block for each filesystem plus all currently referenced * blocks (which is less than the maximum block path length). We cap the * number of retries here to avoid an infinite loop, but we should only need * one retry attempt since the block cache is LRU and the fresh super block * will be the most recently used entry. */ for (int retry = 0; retry < BLOCK_CACHE_SIZE; ++retry) { unused_entry = NULL; unused_entry_score = 0; available = 0; in_use = 0; list_for_every_entry(&block_cache_lru, entry, struct block_cache_entry, lru_node) { assert(entry->guard1 == BLOCK_CACHE_GUARD_1); assert(entry->guard2 == BLOCK_CACHE_GUARD_2); if (entry->dev == dev && entry->block == block) { if (print_cache_lookup) { printf("%s: block %" PRIu64 ", found cache entry %zd, state %s\n", __func__, block, entry - block_cache_entries, block_cache_entry_data_state_name(entry->state)); } stats_timer_start(STATS_CACHE_LOOKUP_FOUND); stats_timer_stop(STATS_CACHE_LOOKUP_FOUND); goto done; } /* * Do not select any cache entries that have active references as * they aren't ready to flush, and do not select any pinned entries. * Pinned entries can only be flushed by * transaction_initial_super_block_complete() and may not be flushed * by another transaction. We need to keep special superblock writes * pinned in the cache because otherwise we might fill the cache up * with other data, flushing the special superblock, which might * fail to write. In this case we would leave no room to recreate * the write later, since the cache is full of data which can't be * flushed until the initial superblock write is completed. */ if (!block_cache_entry_has_refs(entry) && !entry->pinned) { score = block_cache_entry_score(entry, available); available++; if (score >= unused_entry_score) { unused_entry = entry; unused_entry_score = score; } if (print_cache_lookup_verbose) { printf("%s: block %" PRIu64 ", cache entry %zd available last used for %" PRIu64 "\n", __func__, block, entry - block_cache_entries, entry->block); } } else { /* * Pinned entries must have a valid block number so they can be * reused. */ if (entry->pinned) { assert(entry->block != DATA_BLOCK_INVALID); } if (print_cache_lookup_verbose) { printf("%s: block %" PRIu64 ", cache entry %zd in use for %" PRIu64 "\n", __func__, block, entry - block_cache_entries, entry->block); } in_use++; } } entry = unused_entry; if (!entry || !allocate) { if (print_cache_lookup) { printf("%s: block %" PRIu64 ", no available entries, %u in use, allocate %d\n", __func__, block, in_use, allocate); } entry = NULL; goto done; } if (print_cache_lookup) { printf("%s: block %" PRIu64 ", use cache entry %zd, state %s, %u available, %u in_use\n", __func__, block, entry - block_cache_entries, block_cache_entry_data_state_name(entry->state), available, in_use); } assert(!entry->dirty_ref); if (block_cache_entry_data_is_dirty(entry)) { stats_timer_start(STATS_CACHE_LOOKUP_CLEAN); block_cache_entry_clean(entry); block_cache_complete_io(entry->dev); stats_timer_stop(STATS_CACHE_LOOKUP_CLEAN); } /* * The chosen entry we are flushing can't have been a special superblock * write because we do not select pinned entries, however, any RPMB data * write may create a new pinned superblock entry if the RPMB write * failed but the write counter was incremented. In this case * block_cache_entry_clean() will create a new superblock write by * calling fs_unknown_super_block_state_all(). This new write may reuse * the block cache entry we just chose and cleaned, resulting in our * chosen entry now being pinned for a different transaction. In this * case we restart the search for a cache entry and try to pick (and if * needed clean) a new entry. */ if (!entry->pinned) { /* We found a clean entry to use */ break; } pr_warn("%s: Retrying attempt to lookup and (if needed) free a block cache entry. " "Entry block %" PRIu64 " was reused during cleaning.\n", __func__, entry->block); } assert(!block_cache_entry_data_is_dirty(entry)); assert(!entry->dirty_mac); assert(!entry->dirty_tr); entry->dev = dev; entry->block = block; assert(dev->block_size <= sizeof(entry->data)); entry->block_size = dev->block_size; entry->key = fs->key; entry->state = BLOCK_ENTRY_DATA_INVALID; entry->is_superblock = false; done: stats_timer_stop(STATS_CACHE_LOOKUP); return entry; } enum cache_load_result { CACHE_LOAD_SUCCESS = 0, CACHE_LOAD_IO_FAILED, CACHE_LOAD_NO_DATA, CACHE_LOAD_MAC_MISMATCH, }; /** * block_cache_load_entry - Get cache entry for a specific block * @entry: Block cache entry to load. * @mac: Optional mac. * @mac_size: Size of @mac. * * If entry is not already loaded, attempt to load the block and optionally * compare with the expected @mac, if provided. * * Return: &cache_load_result.CACHE_LOAD_SUCCESS if the block (matching @mac, if * provided) was already in cache or was loaded successfully. Otherwise return a * relevant error. */ static enum cache_load_result block_cache_load_entry( struct block_cache_entry* entry, const void* mac, size_t mac_size) { if (!block_cache_entry_data_is_valid(entry)) { assert(!block_cache_entry_has_refs(entry)); if (print_block_load) { printf("%s: request load block %" PRIu64 "\n", __func__, entry->block); } block_cache_queue_read(entry); block_cache_complete_io(entry->dev); } if (!block_cache_entry_data_is_valid(entry)) { printf("%s: failed to load block %" PRIu64 ", state: %d\n", __func__, entry->block, entry->state); switch (entry->state) { case BLOCK_ENTRY_DATA_LOAD_FAILED: return CACHE_LOAD_IO_FAILED; case BLOCK_ENTRY_DATA_NOT_FOUND: return CACHE_LOAD_NO_DATA; default: assert(false && "Unexpected entry state"); } } if (mac) { if (CRYPTO_memcmp(&entry->mac, mac, mac_size)) { printf("%s: block %" PRIu64 ", mac mismatch\n", __func__, entry->block); return CACHE_LOAD_MAC_MISMATCH; } } /* * We eagerly encrypt a block when releasing it so that we can compute the * block's mac. If we re-load the same block before flushing it from the * cache, we may end up decrypting a dirty block here, so we want to allow * decryption of both clean and dirty blocks. */ if (block_cache_entry_data_is_encrypted(entry)) { block_cache_entry_decrypt(entry); } assert(block_cache_entry_data_is_decrypted(entry)); return CACHE_LOAD_SUCCESS; } /** * block_cache_get - Get cache entry for a specific block and add a reference * @fs: File system state object. * @dev: Block device object. * @block: Block number. * @load: If true, load data if needed. * @mac: Optional mac. Unused if @load is false. * @mac_size: Size of @mac. * @ref: Pointer to store reference in. * @load_result: Optional output pointer to store load result in. May be %NULL. * If not %NULL, @load must be %true. * * Find cache entry, optionally load then add a reference to it. * * Return: cache entry matching dev in @tr and @block. Can return NULL if @load * is true and entry could not be loaded or does not match provided mac. */ static struct block_cache_entry* block_cache_get( struct fs* fs, struct block_device* dev, data_block_t block, bool load, const void* mac, size_t mac_size, struct obj_ref* ref, enum cache_load_result* load_result) { enum cache_load_result res; struct block_cache_entry* entry; assert(dev); assert(!load_result || load); if (block >= dev->block_count) { printf("%s: bad block num %" PRIu64 " >= %" PRIu64 "\n", __func__, block, dev->block_count); if (load_result) { *load_result = CACHE_LOAD_NO_DATA; } return NULL; } assert(block < dev->block_count); entry = block_cache_lookup(fs, dev, block, true); assert(entry); if (load) { res = block_cache_load_entry(entry, mac, mac_size); if (res == CACHE_LOAD_MAC_MISMATCH) { error_report_block_mac_mismatch(fs->name, TRUSTY_BLOCKTYPE_UNKNOWN); } if (load_result) { *load_result = res; } if (res != CACHE_LOAD_SUCCESS) { return NULL; } } assert(!entry->dirty_ref); obj_add_ref_allow_unreferenced_obj(&entry->obj, ref); if (print_block_ops) { printf("%s: block %" PRIu64 ", cache entry %zd, state %s\n", __func__, block, entry - block_cache_entries, block_cache_entry_data_state_name(entry->state)); } return entry; } /** * block_cache_get_data - Call block_cache_get and return data pointer * @fs: File system state object. * @dev: Block device object. * @block: Block number. * @load: If true, load data if needed. * @mac: Optional mac. Unused if @load is false. * @mac_size: Size of @mac. * @ref: Pointer to store reference in. * @load_result: Optional output pointer to store load result in. May be %NULL. * Only updated if @load is %true. * * Return: block data pointer, or NULL if block_cache_get returned NULL. */ static void* block_cache_get_data(struct fs* fs, struct block_device* dev, data_block_t block, bool load, const void* mac, size_t mac_size, struct obj_ref* ref, enum cache_load_result* load_result) { struct block_cache_entry* entry; entry = block_cache_get(fs, dev, block, load, mac, mac_size, ref, load_result); if (!entry) { return NULL; } return entry->data; } /** * data_to_block_cache_entry - Get cache entry from data pointer * @data: Pointer to data member of cache entry. * * Return: cache entry matching @data. */ static struct block_cache_entry* data_to_block_cache_entry(const void* data) { struct block_cache_entry* entry; assert(data); entry = containerof(data, struct block_cache_entry, data); assert(entry >= block_cache_entries); assert(entry < &block_cache_entries[BLOCK_CACHE_SIZE]); assert(((uintptr_t)entry - (uintptr_t)entry) % sizeof(entry[0]) == 0); return entry; } /** * data_to_block_cache_entry_or_null - Get cache entry or NULL from data pointer * @data: Pointer to data member of cache entry or NULL. * * Return: cache entry matching @data, or NULL is data is NULL. */ static struct block_cache_entry* data_to_block_cache_entry_or_null( const void* data) { return data ? data_to_block_cache_entry(data) : NULL; } /** * block_cache_entry_destroy - Callback function for obj_del_ref * @obj: Pointer to obj member of cache entry. * * Callback called by reference tracking code when the last reference to a * cache entry has been released. Since this is a cache, and not a normal heap * allocated object, the cache entry is not destroyed here. It is instead left * in a state where block_cache_lookup can reuse it. */ static void block_cache_entry_destroy(struct obj* obj) { struct block_cache_entry* entry = containerof(obj, struct block_cache_entry, obj); list_delete(&entry->lru_node); list_add_head(&block_cache_lru, &entry->lru_node); if (entry->dirty_mac) { block_cache_entry_encrypt(entry); } } /** * block_cache_init - Allocate and initialize block cache */ void block_cache_init(void) { int i; struct obj_ref ref; assert(!block_cache_init_called); block_cache_init_called = true; full_assert(memset(block_cache_entries, 1, sizeof(block_cache_entries))); for (i = 0; i < BLOCK_CACHE_SIZE; i++) { block_cache_entries[i].guard1 = BLOCK_CACHE_GUARD_1; block_cache_entries[i].guard2 = BLOCK_CACHE_GUARD_2; block_cache_entries[i].dev = NULL; block_cache_entries[i].block = DATA_BLOCK_INVALID; block_cache_entries[i].state = BLOCK_ENTRY_DATA_INVALID; block_cache_entries[i].dirty_ref = false; block_cache_entries[i].dirty_mac = false; block_cache_entries[i].pinned = false; block_cache_entries[i].is_superblock = false; block_cache_entries[i].dirty_tr = NULL; block_cache_entries[i].io_op = BLOCK_CACHE_IO_OP_NONE; obj_init(&block_cache_entries[i].obj, &ref); list_clear_node(&block_cache_entries[i].io_op_node); list_add_head(&block_cache_lru, &block_cache_entries[i].lru_node); obj_del_ref(&block_cache_entries[i].obj, &ref, block_cache_entry_destroy); } } /** * block_cache_dev_destroy - Discard all blocks associated with device * @dev: Block device to remove */ void block_cache_dev_destroy(struct block_device* dev) { int i; for (i = 0; i < BLOCK_CACHE_SIZE; i++) { if (block_cache_entries[i].dev == dev) { block_cache_entry_discard(&block_cache_entries[i]); } } } /** * block_cache_clean_transaction - Clean blocks modified by transaction * @tr: Transaction */ void block_cache_clean_transaction(struct transaction* tr) { struct block_cache_entry* entry; struct block_device* dev = NULL; stats_timer_start(STATS_CACHE_CLEAN_TRANSACTION); list_for_every_entry(&block_cache_lru, entry, struct block_cache_entry, lru_node) { assert(entry->guard1 == BLOCK_CACHE_GUARD_1); assert(entry->guard2 == BLOCK_CACHE_GUARD_2); if (entry->dirty_tr != tr) { continue; } assert(block_cache_entry_data_is_dirty(entry)); assert(!entry->dirty_ref); if (entry->dirty_tmp) { continue; } if (!dev) { dev = entry->dev; assert(dev == tr->fs->dev || dev == tr->fs->super_dev); } assert(entry->dev == dev); if (print_clean_transaction) { #if TLOG_LVL >= TLOG_LVL_DEBUG printf("%s: tr %p, block %" PRIu64 "\n", __func__, tr, entry->block); #else printf("%s: transaction block %" PRIu64 "\n", __func__, entry->block); #endif } assert(!block_cache_entry_has_refs(entry)); stats_timer_start(STATS_CACHE_CLEAN_TRANSACTION_ENT_CLN); block_cache_entry_clean(entry); stats_timer_stop(STATS_CACHE_CLEAN_TRANSACTION_ENT_CLN); assert(entry->dirty_tr != tr); if (!tr->failed) { /* * If the write failed we may have reused this block cache entry for * a super block write and it therefore might not be clean. */ assert(!block_cache_entry_data_is_dirty(entry)); assert(!entry->dirty_tr); } } if (dev) { stats_timer_start(STATS_CACHE_CLEAN_TRANSACTION_WAIT_IO); block_cache_complete_io(dev); stats_timer_stop(STATS_CACHE_CLEAN_TRANSACTION_WAIT_IO); } stats_timer_stop(STATS_CACHE_CLEAN_TRANSACTION); } /** * block_cache_discard_transaction - Discard blocks modified by transaction * @tr: Transaction * @discard_all: If true, discard all dirty blocks modified by @tr. If false, * discard tmp dirty blocks modified by @tr. * * If @discard_all is %false, only tmp blocks should be dirty. @discard_all * therefore only affects errors checks. */ void block_cache_discard_transaction(struct transaction* tr, bool discard_all) { struct block_cache_entry* entry; struct block_device* dev = NULL; list_for_every_entry(&block_cache_lru, entry, struct block_cache_entry, lru_node) { assert(entry->guard1 == BLOCK_CACHE_GUARD_1); assert(entry->guard2 == BLOCK_CACHE_GUARD_2); if (entry->dirty_tr != tr) { continue; } if (entry->dirty_tmp) { /* tmp blocks should never be on the superblock device */ assert(entry->dev == tr->fs->dev); } else { /* * An transaction should never have dirty non-tmp blocks both * devices at the same time. */ if (!dev) { dev = entry->dev; assert(dev == tr->fs->dev || dev == tr->fs->super_dev); } assert(entry->dev == dev); } assert(block_cache_entry_data_is_dirty(entry)); if (print_clean_transaction) { #if TLOG_LVL >= TLOG_LVL_DEBUG printf("%s: tr %p, block %" PRIu64 ", tmp %d\n", __func__, tr, entry->block, entry->dirty_tmp); #else printf("%s: transaction block %" PRIu64 ", tmp %d\n", __func__, entry->block, entry->dirty_tmp); #endif } if (block_cache_entry_has_refs(entry)) { #if TLOG_LVL >= TLOG_LVL_DEBUG pr_warn("tr %p, block %" PRIu64 " has ref (dirty_ref %d)\n", tr, entry->block, entry->dirty_ref); #else pr_warn("transaction block %" PRIu64 " has ref (dirty_ref %d)\n", entry->block, entry->dirty_ref); #endif } else { assert(!entry->dirty_ref); } if (!discard_all) { assert(!block_cache_entry_has_refs(entry)); assert(entry->dirty_tmp); } entry->dirty_tr = NULL; entry->state = BLOCK_ENTRY_DATA_INVALID; assert(!entry->dirty_tr); } } /** * block_get_no_read - Get block data without read * @tr: Transaction to get device from * @block: Block number * @ref: Pointer to store reference in. * * Return: Const block data pointer. * * This is only useful if followed by block_dirty. */ const void* block_get_no_read(struct transaction* tr, data_block_t block, struct obj_ref* ref) { assert(tr); assert(tr->fs); return block_cache_get_data(tr->fs, tr->fs->dev, block, false, NULL, 0, ref, NULL); } /** * block_get_super - Get super block data without checking mac * @fs: File system state object. * @block: Block number. * @ref: Pointer to store reference in. * * Return: Const block data pointer. */ const void* block_get_super(struct fs* fs, data_block_t block, struct obj_ref* ref) { assert(fs); assert(fs->super_dev); assert((fs->allow_tampering && !fs->super_dev->tamper_detecting) || (!fs->allow_tampering && fs->super_dev->tamper_detecting)); return block_cache_get_data(fs, fs->super_dev, block, true, NULL, 0, ref, NULL); } /** * block_get_no_tr_fail - Get block data * @tr: Transaction to get device from * @block_mac: Block number and mac * @iv: Initial vector used to decrypt block, or NULL. If NULL, the * start of the loaded block data is used as the iv. * Only NULL is currently supported. * @ref: Pointer to store reference in. * * Return: Const block data pointer, or NULL if mac of loaded data does not mac * in @block_mac or a read error was reported by the block device when loading * the data. */ const void* block_get_no_tr_fail(struct transaction* tr, const struct block_mac* block_mac, const struct iv* iv, struct obj_ref* ref) { data_block_t block; void* data; enum cache_load_result load_result = CACHE_LOAD_NO_DATA; assert(tr); assert(tr->fs); assert(block_mac); block = block_mac_to_block(tr, block_mac); assert(block); data = block_cache_get_data(tr->fs, tr->fs->dev, block, true, block_mac_to_mac(tr, block_mac), tr->fs->mac_size, ref, &load_result); if (load_result == CACHE_LOAD_MAC_MISMATCH || load_result == CACHE_LOAD_NO_DATA) { tr->invalid_block_found = true; } return data; } /** * block_get - Get block data * @tr: Transaction to get device from * @block_mac: Block number and mac * @iv: Initial vector used to decrypt block, or NULL. If NULL, the * start of the loaded block data is used as the iv. * Only NULL is currently supported. * @ref: Pointer to store reference in. * * Return: Const block data pointer, or NULL if the transaction has failed. A * transaction failure is triggered if mac of loaded data does not mac in * @block_mac or a read error was reported by the block device when loading the * data. */ const void* block_get(struct transaction* tr, const struct block_mac* block_mac, const struct iv* iv, struct obj_ref* ref) { const void* data; assert(tr); if (tr->failed) { pr_warn("transaction already failed\n"); return NULL; } data = block_get_no_tr_fail(tr, block_mac, iv, ref); if (!data && !tr->failed) { pr_warn("transaction failed\n"); transaction_fail(tr); if (tr->invalid_block_found) { fs_mark_scan_required(tr->fs); } } return data; } /** * block_dirty - Mark cache entry dirty and return non-const block data pointer. * @tr: Transaction * @data: Const block data pointer * @is_tmp: If true, data is only needed until @tr is commited. * * Return: Non-const block data pointer. */ void* block_dirty(struct transaction* tr, const void* data, bool is_tmp) { struct block_cache_entry* entry = data_to_block_cache_entry(data); assert(tr); assert(list_in_list(&tr->node)); /* transaction must be active */ assert(!entry->dirty_tr || entry->dirty_tr == tr); assert(!entry->dirty_ref); assert(fs_is_writable(tr->fs)); if (block_cache_entry_data_is_encrypted(entry)) { if (print_block_ops) { printf("%s: skip decrypt block %" PRIu64 "\n", __func__, entry->block); } } else if (entry->state != BLOCK_ENTRY_DATA_CLEAN_DECRYPTED) { if (print_block_ops) { printf("%s: Dirtying block %" PRIu64 " that was not loaded. Previous state: %s\n", __func__, entry->block, block_cache_entry_data_state_name(entry->state)); } } assert(block_cache_entry_has_one_ref(entry)); entry->state = BLOCK_ENTRY_DATA_DIRTY_DECRYPTED; entry->dirty_ref = true; entry->dirty_tmp = is_tmp; entry->dirty_tr = tr; return (void*)data; } /** * block_is_clean - Check if block is clean * @dev: Block device * @block: Block number * * Return: %true if there is no matching dirty cache entry, %false if the cache * contains a dirty block matching @dev and @block. */ bool block_is_clean(struct block_device* dev, data_block_t block) { struct block_cache_entry* entry; entry = block_cache_lookup(NULL, dev, block, false); return !entry || !block_cache_entry_data_is_dirty(entry); } /** * block_discard_dirty - Discard dirty cache data. * @data: Block data pointer */ void block_discard_dirty(const void* data) { struct block_cache_entry* entry = data_to_block_cache_entry(data); if (block_cache_entry_data_is_dirty(entry)) { assert(entry->dev); block_cache_entry_discard_dirty(entry); } } /** * block_discard_dirty_by_block - Discard cache entry if dirty. * @dev: Block device * @block: Block number */ void block_discard_dirty_by_block(struct block_device* dev, data_block_t block) { struct block_cache_entry* entry; entry = block_cache_lookup(NULL, dev, block, false); if (!entry) { return; } assert(!entry->dirty_ref); assert(!block_cache_entry_has_refs(entry)); if (!block_cache_entry_data_is_dirty(entry)) { return; } block_discard_dirty(entry->data); } /** * block_put_dirty - Release reference to dirty block. * @tr: Transaction * @data: Block data pointer * @data_ref: Reference pointer to release * @block_mac: block_mac pointer to update after encryting block * @block_mac_ref: Block data pointer that @block_mac belongs to, or NULL if * @block_mac points to a memory only location. * * Helper function to for block_put_dirty, block_put_dirty_no_mac and * block_put_dirty_discard. */ static void block_put_dirty_etc(struct transaction* tr, void* data, struct obj_ref* data_ref, struct block_mac* block_mac, void* block_mac_ref) { int ret; struct block_cache_entry* entry = data_to_block_cache_entry(data); struct block_cache_entry* parent = data_to_block_cache_entry_or_null(block_mac_ref); struct iv* iv = (void*)entry->data; /* TODO: support external iv */ if (tr) { assert(block_mac); assert(entry->state == BLOCK_ENTRY_DATA_DIRTY_DECRYPTED); assert(entry->dirty_ref); } else { assert(!block_mac); } assert(entry->guard1 == BLOCK_CACHE_GUARD_1); assert(entry->guard2 == BLOCK_CACHE_GUARD_2); entry->dirty_ref = false; if (block_cache_entry_data_is_dirty(entry)) { entry->dirty_mac = true; ret = generate_iv(iv); assert(!ret); } else { pr_warn("block %" PRIu64 ", not dirty\n", entry->block); assert(entry->dirty_tr == NULL); assert(!tr); } block_put(data, data_ref); /* TODO: fix clients to support lazy write */ assert(block_cache_entry_data_is_encrypted(entry) || !tr); assert(!entry->dirty_mac); if (block_mac) { assert(block_mac_to_block(tr, block_mac) == entry->block); block_mac_set_mac(tr, block_mac, &entry->mac); } #if TLOG_LVL >= TLOG_LVL_DEBUG if (print_mac_update) { printf("%s: block %" PRIu64 ", update parent mac, %p, block %" PRIu64 "\n", __func__, entry->block, block_mac, parent ? parent->block : 0); } #endif } /** * block_put_dirty - Release reference to dirty block. * @tr: Transaction * @data: Block data pointer * @data_ref: Reference pointer to release * @block_mac: block_mac pointer to update after encryting block * @block_mac_ref: Block data pointer that @block_mac belongs to, or NULL if * @block_mac points to a memory only location. */ void block_put_dirty(struct transaction* tr, void* data, struct obj_ref* data_ref, struct block_mac* block_mac, void* block_mac_ref) { assert(tr); assert(block_mac); block_put_dirty_etc(tr, data, data_ref, block_mac, block_mac_ref); } /** * block_put_dirty_no_mac - Release reference to dirty super block. * @data: Block data pointer * @data_ref: Reference pointer to release * @allow_tampering: %true if this file system does not require tamper-proof * super block storage, %false if tamper detection must be * required. * * Similar to block_put_dirty except no transaction or block_mac is needed. */ void block_put_dirty_no_mac(void* data, struct obj_ref* data_ref, bool allow_tampering) { struct block_cache_entry* entry = data_to_block_cache_entry(data); assert(entry->dev); assert((allow_tampering && !entry->dev->tamper_detecting) || (!allow_tampering && entry->dev->tamper_detecting)); block_put_dirty_etc(NULL, data, data_ref, NULL, NULL); } /** * block_put_dirty_discard - Release reference to dirty block. * @data: Block data pointer * @data_ref: Reference pointer to release * * Similar to block_put_dirty except data can be discarded. */ void block_put_dirty_discard(void* data, struct obj_ref* data_ref) { block_put_dirty_etc(NULL, data, data_ref, NULL, NULL); block_discard_dirty(data); } /** * block_get_write_no_read - Get block data without read for write * @tr: Transaction * @block: Block number * @is_tmp: If true, data is only needed until @tr is commited. * @ref: Pointer to store reference in. * * Same as block_get_no_read followed by block_dirty. * * Return: Block data pointer. */ void* block_get_write_no_read(struct transaction* tr, data_block_t block, bool is_tmp, struct obj_ref* ref) { const void* data_ro = block_get_no_read(tr, block, ref); return block_dirty(tr, data_ro, is_tmp); } /** * block_get_write - Get block data for write * @tr: Transaction * @block_mac: Block number and mac * @iv: Initial vector used to decrypt block, or NULL. If NULL, the * start of the loaded block data is used as the iv. * Only NULL is currently supported. * @is_tmp: If true, data is only needed until @tr is commited. * @ref: Pointer to store reference in. * * Same as block_get followed by block_dirty. * * Return: Block data pointer. */ void* block_get_write(struct transaction* tr, const struct block_mac* block_mac, const struct iv* iv, bool is_tmp, struct obj_ref* ref) { const void* data_ro = block_get(tr, block_mac, iv, ref); if (!data_ro) { return NULL; } return block_dirty(tr, data_ro, is_tmp); } /** * block_get_cleared - Get block cleared data for write * @tr: Transaction * @block: Block number * @is_tmp: If true, data is only needed until @tr is commited. * @ref: Pointer to store reference in. * * Return: Block data pointer. */ void* block_get_cleared(struct transaction* tr, data_block_t block, bool is_tmp, struct obj_ref* ref) { void* data = block_get_write_no_read(tr, block, is_tmp, ref); memset(data, 0, MAX_BLOCK_SIZE); return data; } /** * block_get_cleared_super - Get block with cleared data for write on super_dev * @tr: Transaction * @block: Block number * @ref: Pointer to store reference in. * @pinned: Pin this block in the cache until it is successfully written * * Return: Block data pointer. */ void* block_get_cleared_super(struct transaction* tr, data_block_t block, struct obj_ref* ref, bool pinned) { void* data_rw; const void* data_ro = block_cache_get_data(tr->fs, tr->fs->super_dev, block, false, NULL, 0, ref, NULL); /* * We should never end up in a situation where there is a dirty copy of a * super block in the cache while we are trying to rewrite that super block. * If a super block entry was created via write_current_super_block(), it * must be flushed before the necessary data writes go through to write new * root nodes. If we are trying to commit an empty transaction (i.e. no data * blocks changed), we skip the super block update in * transaction_complete(). The only other way to write a new super block, * write_current_super_block(), will be a no-op if there is already a * pending super block rewrite. */ assert(data_ro); struct block_cache_entry* entry = data_to_block_cache_entry(data_ro); assert(!block_cache_entry_data_is_dirty(entry)); entry->pinned = pinned; entry->is_superblock = true; data_rw = block_dirty(tr, data_ro, false); assert(tr->fs->super_dev->block_size <= MAX_BLOCK_SIZE); memset(data_rw, 0, tr->fs->super_dev->block_size); return data_rw; } /** * block_get_copy - Get block for write with data copied from another block. * @tr: Transaction * @data: Block data pointer * @block: New block number * @is_tmp: If true, data is only needed until @tr is commited. * @new_ref: Pointer to store reference to new block in. * * Return: Block data pointer. */ void* block_get_copy(struct transaction* tr, const void* data, data_block_t block, bool is_tmp, struct obj_ref* new_ref) { void* dst_data; struct block_cache_entry* src_entry = data_to_block_cache_entry(data); assert(block); assert(block < tr->fs->dev->block_count); dst_data = block_get_write_no_read(tr, block, is_tmp, new_ref); memcpy(dst_data, data, src_entry->block_size); return dst_data; } /** * block_move - Get block for write and move to new location * @tr: Transaction * @data: Block data pointer * @block: New block number * @is_tmp: If true, data is only needed until @tr is commited. * * Change block number of cache entry mark new block dirty. Useful for * copy-on-write. * * Return: Non-const block data pointer. */ void* block_move(struct transaction* tr, const void* data, data_block_t block, bool is_tmp) { struct block_cache_entry* dest_entry; struct block_cache_entry* entry = data_to_block_cache_entry(data); assert(block_cache_entry_has_one_ref(entry)); assert(!block_cache_entry_data_is_dirty(entry)); assert(entry->dev == tr->fs->dev); if (print_block_move) { printf("%s: move cache entry %zd, from block %" PRIu64 " to %" PRIu64 "\n", __func__, entry - block_cache_entries, entry->block, block); } dest_entry = block_cache_lookup(NULL, tr->fs->dev, block, false); if (dest_entry) { assert(!block_cache_entry_has_refs(dest_entry)); assert(!dest_entry->dirty_ref); assert(!dest_entry->dirty_tr || dest_entry->dirty_tr == tr); assert(!list_in_list(&dest_entry->io_op_node)); assert(dest_entry->block == block); if (print_block_move) { printf("%s: clear old cache entry for block %" PRIu64 ", %zd\n", __func__, block, dest_entry - block_cache_entries); } /* TODO: Use block_cache_entry_discard instead? */ block_cache_entry_discard_dirty(dest_entry); } entry->block = block; return block_dirty(tr, data, is_tmp); } /** * block_put - Release reference to block. * @data: Block data pointer * @data_ref: Reference pointer to release */ void block_put(const void* data, struct obj_ref* ref) { struct block_cache_entry* entry = data_to_block_cache_entry(data); if (print_block_ops) { printf("%s: block %" PRIu64 ", cache entry %zd, state %s\n", __func__, entry->block, entry - block_cache_entries, block_cache_entry_data_state_name(entry->state)); } assert(!entry->dirty_ref); obj_del_ref(&entry->obj, ref, block_cache_entry_destroy); } /** * block_probe - Verify that the given block is loadable and its mac is correct * @fs: Filesystem containing the block to probe * @block_mac: Block to probe * @allow_invalid: If %true, an invalid (i.e. zero) @block_mac will not be * probed and this function will return true * * Return: %false if the block is not valid or does not match the expected mac. * Returns %true if the block was readable, valid and matched the expected mac. * If @allow_invalid is %true, also return %true if @block_mac is invalid. Also * returns %true if an I/O error was encountered which does not positively * confirm a corrupted block. */ bool block_probe(struct fs* fs, const struct block_mac* block_mac, bool allow_invalid) { struct transaction probe_tr; struct obj_ref probe_ref = OBJ_REF_INITIAL_VALUE(probe_ref); const void* probe_block; /* * Assume the block is valid unless we get positive confirmation of an * invalid block. */ bool valid = true; transaction_init(&probe_tr, fs, true); if (block_mac_valid(&probe_tr, block_mac)) { probe_block = block_get_no_tr_fail(&probe_tr, block_mac, NULL, &probe_ref); if (probe_block) { block_put(probe_block, &probe_ref); } else if (probe_tr.invalid_block_found) { valid = false; } } else if (allow_invalid) { valid = true; } transaction_fail(&probe_tr); transaction_free(&probe_tr); return valid; } /** * data_to_block_num - Get block number from block data pointer * @data: Block data pointer * * Only used for debug code. * * Return: block number. */ data_block_t data_to_block_num(const void* data) { struct block_cache_entry* entry = data_to_block_cache_entry(data); return entry->block; } /** * block_cache_debug_get_ref_block_count - Get number of blocks that have * references * * Only used for debug code. * * Return: number of blocks in cache that have references. */ unsigned int block_cache_debug_get_ref_block_count(void) { unsigned int count = 0; struct block_cache_entry* entry; list_for_every_entry(&block_cache_lru, entry, struct block_cache_entry, lru_node) { assert(entry->guard1 == BLOCK_CACHE_GUARD_1); assert(entry->guard2 == BLOCK_CACHE_GUARD_2); if (block_cache_entry_has_refs(entry)) { if (print_cache_get_ref_block_count) { #if TLOG_LVL >= TLOG_LVL_DEBUG printf("%s: cache entry %zd in use for %" PRIu64 ", dev %p\n", __func__, entry - block_cache_entries, entry->block, entry->dev); #else printf("%s: cache entry %zd in use for %" PRIu64 "\n", __func__, entry - block_cache_entries, entry->block); #endif } count++; } } return count; }