Stephen Crane | 4e597d7 | 2022-10-17 17:38:10 -0700 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2022 The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | #include "checkpoint.h" |
| 18 | #include "block_allocator.h" |
| 19 | #include "block_cache.h" |
| 20 | #include "block_mac.h" |
| 21 | #include "debug.h" |
| 22 | #include "transaction.h" |
| 23 | |
| 24 | #define CHECKPOINT_MAGIC (0x0063797473757274) /* trustyc\0 */ |
| 25 | |
| 26 | /** |
| 27 | * struct checkpoint - On-disk block containing the checkpoint metadata |
| 28 | * @iv: Initial value used for encrypt/decrypt |
| 29 | * @magic: CHECKPOINT_MAGIC |
| 30 | * @files: Block and mac of checkpointed files tree root node |
| 31 | * @free: Block and mac of checkpointed free set root node. When a |
| 32 | * checkpoint is active blocks may only be allocated if they |
| 33 | * are marked as free in both the filesystem free set and this |
| 34 | * checkpointed free set. |
| 35 | */ |
| 36 | struct checkpoint { |
| 37 | struct iv iv; |
| 38 | uint64_t magic; |
| 39 | struct block_mac files; |
| 40 | struct block_mac free; |
| 41 | }; |
| 42 | |
| 43 | /** |
| 44 | * checkpoint_get_new_block - Get a new, writable copy of the checkpoint block |
| 45 | * metadata |
| 46 | * @tr: Transaction object. |
| 47 | * @new_checkpoint_ref: Output pointer to hold the block reference for the new |
| 48 | * block |
| 49 | * @checkpoint_mac: Pointer to the current checkpoint block mac. |
| 50 | * Updated with the block number of the new checkpoint block on success. |
| 51 | * |
| 52 | * Returns a new, writable copy of the checkpoint metadata block, or %NULL on |
| 53 | * failure (tr->failed will be set). The returned pointer should then be passed |
| 54 | * to checkpoint_update_roots() after the file tree and free set are finalized. |
| 55 | * We have to split this operation in two so that the newly allocated block will |
| 56 | * be removed from the free set. |
| 57 | * |
| 58 | * Caller takes ownership of the returned new, dirty block and is responsible |
| 59 | * for releasing @new_checkpoint_ref. |
| 60 | */ |
| 61 | struct checkpoint* checkpoint_get_new_block(struct transaction* tr, |
| 62 | struct obj_ref* new_checkpoint_ref, |
| 63 | struct block_mac* checkpoint_mac) { |
| 64 | data_block_t new_checkpoint_block; |
| 65 | struct checkpoint* new_checkpoint; |
| 66 | |
| 67 | new_checkpoint_block = block_allocate(tr); |
| 68 | if (tr->failed) { |
| 69 | pr_warn("transaction failed, abort\n"); |
| 70 | return NULL; |
| 71 | } |
| 72 | assert(new_checkpoint_block); |
| 73 | |
| 74 | if (block_mac_valid(tr, checkpoint_mac)) { |
| 75 | block_free(tr, block_mac_to_block(tr, checkpoint_mac)); |
| 76 | } |
| 77 | new_checkpoint = block_get_cleared(tr, new_checkpoint_block, false, |
| 78 | new_checkpoint_ref); |
| 79 | |
| 80 | block_mac_set_block(tr, checkpoint_mac, new_checkpoint_block); |
| 81 | |
| 82 | new_checkpoint->magic = CHECKPOINT_MAGIC; |
| 83 | |
| 84 | return new_checkpoint; |
| 85 | } |
| 86 | |
| 87 | /** |
| 88 | * checkpoint_update_roots - Update the files and free blocks of a checkpoint |
| 89 | * @tr: Transaction object. |
| 90 | * @new_checkpoint: Pointer to a checkpoint metadata block returned by |
| 91 | * checkpoint_get_new_block() |
| 92 | * @files: New checkpoint files tree root node. |
| 93 | * @free: New checkpoint free set root node. |
| 94 | */ |
| 95 | void checkpoint_update_roots(struct transaction* tr, |
| 96 | struct checkpoint* new_checkpoint, |
| 97 | const struct block_mac* files, |
| 98 | const struct block_mac* free) { |
| 99 | new_checkpoint->files = *files; |
| 100 | new_checkpoint->free = *free; |
| 101 | } |
| 102 | |
| 103 | /** |
| 104 | * checkpoint_read - Initialize root blocks from a checkpoint page |
| 105 | * @fs: File-system to initialize checkpoint state in. |
| 106 | * @checkpoint: Checkpoint root page block and mac. Must be a valid block. |
| 107 | * @files: New checkpoint file tree. May be %NULL. |
| 108 | * @free: New checkpoint free set. May be %NULL. |
| 109 | * |
| 110 | * Returns %true if the @files and @free nodes were properly populated from the |
| 111 | * fields in @checkpoint. Either @files or @free may be %NULL; %NULL out params |
| 112 | * will not be set. Returns %false and does not change @files or @free if the |
| 113 | * @checkpoint metadata page exists but could not be read. |
| 114 | * |
| 115 | * Example: checkpoint_read(tr, &tr->fs->checkpoint, &files, |
| 116 | * &tr->fs->checkpoint_free) |
| 117 | */ |
| 118 | bool checkpoint_read(struct transaction* tr, |
| 119 | const struct block_mac* checkpoint, |
| 120 | struct block_tree* files, |
| 121 | struct block_set* free) { |
| 122 | const struct checkpoint* checkpoint_ro; |
| 123 | struct obj_ref checkpoint_ro_ref = OBJ_REF_INITIAL_VALUE(checkpoint_ro_ref); |
| 124 | |
| 125 | assert(block_mac_valid(tr, checkpoint)); |
| 126 | |
| 127 | checkpoint_ro = block_get(tr, checkpoint, NULL, &checkpoint_ro_ref); |
| 128 | if (tr->failed) { |
Stephen Crane | b357576 | 2022-11-22 06:57:53 +0000 | [diff] [blame] | 129 | goto err_block_get; |
Stephen Crane | 4e597d7 | 2022-10-17 17:38:10 -0700 | [diff] [blame] | 130 | } |
| 131 | |
| 132 | if (checkpoint_ro->magic != CHECKPOINT_MAGIC) { |
| 133 | pr_err("Checkpoint magic mismatch!\n"); |
| 134 | transaction_fail(tr); |
Stephen Crane | b357576 | 2022-11-22 06:57:53 +0000 | [diff] [blame] | 135 | goto err_magic_mismatch; |
Stephen Crane | 4e597d7 | 2022-10-17 17:38:10 -0700 | [diff] [blame] | 136 | } |
| 137 | |
| 138 | if (files) { |
| 139 | files->root = checkpoint_ro->files; |
| 140 | } |
| 141 | if (free) { |
| 142 | free->block_tree.root = checkpoint_ro->free; |
| 143 | block_range_clear(&free->initial_range); |
| 144 | } |
| 145 | |
Stephen Crane | b357576 | 2022-11-22 06:57:53 +0000 | [diff] [blame] | 146 | err_magic_mismatch: |
Stephen Crane | 4e597d7 | 2022-10-17 17:38:10 -0700 | [diff] [blame] | 147 | block_put(checkpoint_ro, &checkpoint_ro_ref); |
Stephen Crane | b357576 | 2022-11-22 06:57:53 +0000 | [diff] [blame] | 148 | err_block_get: |
Stephen Crane | 4e597d7 | 2022-10-17 17:38:10 -0700 | [diff] [blame] | 149 | return !tr->failed; |
| 150 | } |
Stephen Crane | 008725f | 2023-02-13 23:38:33 +0000 | [diff] [blame] | 151 | |
| 152 | /** |
| 153 | * checkpoint_commit - Save the current file-system state as a checkpoint |
| 154 | * @fs: File-system to checkpoint. |
| 155 | * |
| 156 | * Create and commit a checkpoint of the current state of @fs. |
| 157 | * |
| 158 | * Returns %true if the checkpoint was created and committed successfully, |
| 159 | * %false otherwise. |
| 160 | */ |
| 161 | bool checkpoint_commit(struct fs* fs) { |
| 162 | struct transaction tr; |
| 163 | bool success; |
| 164 | |
| 165 | assert(fs); |
| 166 | transaction_init(&tr, fs, true); |
| 167 | transaction_complete_etc(&tr, true); |
| 168 | success = !tr.failed; |
| 169 | if (success) { |
| 170 | pr_init("Automatically created a checkpoint for filesystem %s\n", |
| 171 | fs->name); |
| 172 | } else { |
| 173 | pr_err("Failed to commit checkpoint for filesystem %s\n", fs->name); |
| 174 | } |
| 175 | transaction_free(&tr); |
| 176 | return success; |
| 177 | } |