| /* |
| * Copyright (C) 2022 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 "checkpoint.h" |
| #include "block_allocator.h" |
| #include "block_cache.h" |
| #include "block_mac.h" |
| #include "debug.h" |
| #include "transaction.h" |
| |
| #define CHECKPOINT_MAGIC (0x0063797473757274) /* trustyc\0 */ |
| |
| /** |
| * struct checkpoint - On-disk block containing the checkpoint metadata |
| * @iv: Initial value used for encrypt/decrypt |
| * @magic: CHECKPOINT_MAGIC |
| * @files: Block and mac of checkpointed files tree root node |
| * @free: Block and mac of checkpointed free set root node. When a |
| * checkpoint is active blocks may only be allocated if they |
| * are marked as free in both the filesystem free set and this |
| * checkpointed free set. |
| */ |
| struct checkpoint { |
| struct iv iv; |
| uint64_t magic; |
| struct block_mac files; |
| struct block_mac free; |
| }; |
| |
| /** |
| * checkpoint_get_new_block - Get a new, writable copy of the checkpoint block |
| * metadata |
| * @tr: Transaction object. |
| * @new_checkpoint_ref: Output pointer to hold the block reference for the new |
| * block |
| * @checkpoint_mac: Pointer to the current checkpoint block mac. |
| * Updated with the block number of the new checkpoint block on success. |
| * |
| * Returns a new, writable copy of the checkpoint metadata block, or %NULL on |
| * failure (tr->failed will be set). The returned pointer should then be passed |
| * to checkpoint_update_roots() after the file tree and free set are finalized. |
| * We have to split this operation in two so that the newly allocated block will |
| * be removed from the free set. |
| * |
| * Caller takes ownership of the returned new, dirty block and is responsible |
| * for releasing @new_checkpoint_ref. |
| */ |
| struct checkpoint* checkpoint_get_new_block(struct transaction* tr, |
| struct obj_ref* new_checkpoint_ref, |
| struct block_mac* checkpoint_mac) { |
| data_block_t new_checkpoint_block; |
| struct checkpoint* new_checkpoint; |
| |
| new_checkpoint_block = block_allocate(tr); |
| if (tr->failed) { |
| pr_warn("transaction failed, abort\n"); |
| return NULL; |
| } |
| assert(new_checkpoint_block); |
| |
| if (block_mac_valid(tr, checkpoint_mac)) { |
| block_free(tr, block_mac_to_block(tr, checkpoint_mac)); |
| } |
| new_checkpoint = block_get_cleared(tr, new_checkpoint_block, false, |
| new_checkpoint_ref); |
| |
| block_mac_set_block(tr, checkpoint_mac, new_checkpoint_block); |
| |
| new_checkpoint->magic = CHECKPOINT_MAGIC; |
| |
| return new_checkpoint; |
| } |
| |
| /** |
| * checkpoint_update_roots - Update the files and free blocks of a checkpoint |
| * @tr: Transaction object. |
| * @new_checkpoint: Pointer to a checkpoint metadata block returned by |
| * checkpoint_get_new_block() |
| * @files: New checkpoint files tree root node. |
| * @free: New checkpoint free set root node. |
| */ |
| void checkpoint_update_roots(struct transaction* tr, |
| struct checkpoint* new_checkpoint, |
| const struct block_mac* files, |
| const struct block_mac* free) { |
| new_checkpoint->files = *files; |
| new_checkpoint->free = *free; |
| } |
| |
| /** |
| * checkpoint_read - Initialize root blocks from a checkpoint page |
| * @fs: File-system to initialize checkpoint state in. |
| * @checkpoint: Checkpoint root page block and mac. Must be a valid block. |
| * @files: New checkpoint file tree. May be %NULL. |
| * @free: New checkpoint free set. May be %NULL. |
| * |
| * Returns %true if the @files and @free nodes were properly populated from the |
| * fields in @checkpoint. Either @files or @free may be %NULL; %NULL out params |
| * will not be set. Returns %false and does not change @files or @free if the |
| * @checkpoint metadata page exists but could not be read. |
| * |
| * Example: checkpoint_read(tr, &tr->fs->checkpoint, &files, |
| * &tr->fs->checkpoint_free) |
| */ |
| bool checkpoint_read(struct transaction* tr, |
| const struct block_mac* checkpoint, |
| struct block_tree* files, |
| struct block_set* free) { |
| const struct checkpoint* checkpoint_ro; |
| struct obj_ref checkpoint_ro_ref = OBJ_REF_INITIAL_VALUE(checkpoint_ro_ref); |
| |
| assert(block_mac_valid(tr, checkpoint)); |
| |
| checkpoint_ro = block_get(tr, checkpoint, NULL, &checkpoint_ro_ref); |
| if (tr->failed) { |
| goto err_block_get; |
| } |
| |
| if (checkpoint_ro->magic != CHECKPOINT_MAGIC) { |
| pr_err("Checkpoint magic mismatch!\n"); |
| transaction_fail(tr); |
| goto err_magic_mismatch; |
| } |
| |
| if (files) { |
| files->root = checkpoint_ro->files; |
| } |
| if (free) { |
| free->block_tree.root = checkpoint_ro->free; |
| block_range_clear(&free->initial_range); |
| } |
| |
| err_magic_mismatch: |
| block_put(checkpoint_ro, &checkpoint_ro_ref); |
| err_block_get: |
| return !tr->failed; |
| } |
| |
| /** |
| * checkpoint_commit - Save the current file-system state as a checkpoint |
| * @fs: File-system to checkpoint. |
| * |
| * Create and commit a checkpoint of the current state of @fs. |
| * |
| * Returns %true if the checkpoint was created and committed successfully, |
| * %false otherwise. |
| */ |
| bool checkpoint_commit(struct fs* fs) { |
| struct transaction tr; |
| bool success; |
| |
| assert(fs); |
| transaction_init(&tr, fs, true); |
| transaction_complete_etc(&tr, true); |
| success = !tr.failed; |
| if (success) { |
| pr_init("Automatically created a checkpoint for filesystem %s\n", |
| fs->name); |
| } else { |
| pr_err("Failed to commit checkpoint for filesystem %s\n", fs->name); |
| } |
| transaction_free(&tr); |
| return success; |
| } |