| /* |
| * Copyright (C) 2015 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 <assert.h> |
| #include <inttypes.h> |
| #include <lk/macros.h> |
| #include <stdbool.h> |
| #include <stdint.h> |
| #include <stdio.h> |
| #include <string.h> |
| |
| #include "block_allocator.h" |
| #include "block_map.h" |
| #include "crypt.h" |
| #include "debug.h" |
| #include "file.h" |
| #include "transaction.h" |
| |
| #define BIT_MASK(x) \ |
| (((x) >= sizeof(uint64_t) * 8) ? (~0ULL) : ((1ULL << (x)) - 1)) |
| |
| #define FILE_ENTRY_MAGIC (0x0066797473757274) /* trustyf\0 */ |
| |
| /** |
| * struct file_entry - On-disk file entry |
| * @iv: initial value used for encrypt/decrypt |
| * @magic: FILE_ENTRY_MAGIC. |
| * @block_map: Block and mac of file block map root node. |
| * @size: File size in bytes. |
| * @reserved: Reserved for future use. Write 0, read ignore. |
| * @path: File path and name. |
| */ |
| struct file_entry { |
| struct iv iv; |
| uint64_t magic; |
| struct block_mac block_map; |
| struct file_info info; |
| }; |
| |
| static bool file_tree_lookup(struct block_mac* block_mac_out, |
| struct transaction* tr, |
| struct block_tree* tree, |
| struct block_tree_path* tree_path, |
| const char* file_path, |
| bool remove); |
| |
| /** |
| * path_hash - Convert a path string to hash value |
| * @tr: Transaction object. |
| * @path: String to calculate hash for. |
| * |
| * Return: non-zero hash value that fits in @tr->fs->block_num_size bytes |
| * computed from @path |
| */ |
| static data_block_t path_hash(struct transaction* tr, const char* path) { |
| data_block_t hash = str_hash(path); |
| |
| hash &= BIT_MASK(tr->fs->block_num_size * 8); |
| if (!hash) { |
| hash++; /* 0 key is not supported by block_tree */ |
| } |
| |
| return hash; |
| } |
| |
| /** |
| * file_block_map_init - Initialize in-memory block map state from file entry |
| * @tr: Transaction object. |
| * @block_map: Block map object. |
| * @file: Block number and mac of file entry. |
| * |
| * Load file entry at @file and initialize @block_map with root node stored |
| * there. |
| */ |
| void file_block_map_init(struct transaction* tr, |
| struct block_map* block_map, |
| const struct block_mac* file) { |
| const struct file_entry* file_entry_ro; |
| struct obj_ref file_entry_ro_ref = OBJ_REF_INITIAL_VALUE(file_entry_ro_ref); |
| |
| if (!block_mac_valid(tr, file)) { |
| goto err; |
| } |
| file_entry_ro = block_get(tr, file, NULL, &file_entry_ro_ref); |
| if (!file_entry_ro) { |
| goto err; |
| } |
| assert(file_entry_ro); |
| block_map_init(tr, block_map, &file_entry_ro->block_map, |
| tr->fs->dev->block_size); |
| block_put(file_entry_ro, &file_entry_ro_ref); |
| |
| return; |
| |
| err: |
| if (tr->failed) { |
| pr_warn("can't read file entry at %" PRIu64 ", transaction failed\n", |
| block_mac_to_block(tr, file)); |
| } else { |
| pr_err("can't read file entry at %" PRIu64 "\n", |
| block_mac_to_block(tr, file)); |
| transaction_fail(tr); |
| } |
| } |
| |
| /** |
| * file_print - Print information about a file |
| * @tr: Transaction object. |
| * @file: File handle object. |
| */ |
| void file_print(struct transaction* tr, |
| const struct storage_file_handle* file) { |
| const struct file_entry* file_entry_ro; |
| struct obj_ref file_entry_ro_ref = OBJ_REF_INITIAL_VALUE(file_entry_ro_ref); |
| struct block_map block_map; |
| |
| file_block_map_init(tr, &block_map, &file->block_mac); |
| file_entry_ro = block_get(tr, &file->block_mac, NULL, &file_entry_ro_ref); |
| if (!file_entry_ro) { |
| printf("can't read file entry at %" PRIu64 "\n", |
| block_mac_to_block(tr, &file->block_mac)); |
| return; |
| } |
| assert(file_entry_ro); |
| printf("file at %" PRIu64 ", path %s, size %" PRIu64 ", hash 0x%" PRIx64 |
| ", block map (tree)\n", |
| block_mac_to_block(tr, &file->block_mac), file_entry_ro->info.path, |
| file_entry_ro->info.size, path_hash(tr, file_entry_ro->info.path)); |
| block_put(file_entry_ro, &file_entry_ro_ref); |
| block_tree_print(tr, &block_map.tree); |
| } |
| |
| /** |
| * files_print - Print information about all committed files |
| * @tr: Transaction object. |
| */ |
| void files_print(struct transaction* tr) { |
| struct block_tree_path path; |
| struct storage_file_handle file; |
| |
| printf("%s: files:\n", __func__); |
| block_tree_print(tr, &tr->fs->files); |
| |
| block_tree_walk(tr, &tr->fs->files, 0, true, &path); |
| while (block_tree_path_get_key(&path)) { |
| file.block_mac = block_tree_path_get_data_block_mac(&path); |
| file_print(tr, &file); |
| block_tree_path_next(&path); |
| } |
| } |
| |
| /** |
| * file_check - Check the file block map for errors |
| * @tr: Transaction object. |
| * @file: File handle object. |
| * |
| * Return: %false if an unrecoverable error was detected in the file block map, |
| * %true if the block map was consistent. |
| */ |
| bool file_check(struct transaction* tr, |
| const struct storage_file_handle* file) { |
| struct block_map block_map; |
| data_block_t file_block_size = get_file_block_size(tr->fs); |
| |
| file_block_map_init(tr, &block_map, &file->block_mac); |
| return block_map_check(tr, &block_map, |
| DIV_ROUND_UP(file->size, file_block_size)); |
| } |
| |
| /** |
| * file_block_map_update - Update file entry with block_map or size changes |
| * @tr: Transaction object. |
| * @block_map: Block map object. |
| * @file: File handle object. |
| * |
| */ |
| static void file_block_map_update(struct transaction* tr, |
| struct block_map* block_map, |
| const char* new_path, |
| struct storage_file_handle* file) { |
| bool found; |
| data_block_t old_block; |
| data_block_t file_block; |
| data_block_t new_block; |
| struct block_mac block_mac; |
| const struct file_entry* file_entry_ro; |
| struct file_entry* file_entry_rw = NULL; |
| struct obj_ref file_entry_ref = OBJ_REF_INITIAL_VALUE(file_entry_ref); |
| struct obj_ref file_entry_copy_ref = OBJ_REF_INITIAL_VALUE(file_entry_ref); |
| struct block_tree_path tree_path; |
| |
| if (tr->failed) { |
| pr_warn("transaction failed, abort\n"); |
| return; |
| } |
| |
| assert(block_mac_valid(tr, &file->block_mac)); |
| |
| file_entry_ro = block_get(tr, &file->block_mac, NULL, &file_entry_ref); |
| if (!file_entry_ro) { |
| assert(tr->failed); |
| pr_warn("transaction failed, abort\n"); |
| return; |
| } |
| assert(file_entry_ro); |
| found = file_tree_lookup(&block_mac, tr, &tr->files_added, &tree_path, |
| file_entry_ro->info.path, !!new_path); |
| if (!found) { |
| found = file_tree_lookup(&block_mac, tr, &tr->fs->files, &tree_path, |
| file_entry_ro->info.path, false); |
| if (tr->failed) { |
| pr_warn("transaction failed, abort\n"); |
| goto err; |
| } |
| assert(found); |
| |
| if (new_path) { |
| block_tree_insert_block_mac(tr, &tr->files_removed, |
| block_mac_to_block(tr, &block_mac), |
| block_mac); |
| } |
| |
| old_block = block_mac_to_block(tr, &block_mac); |
| file_block = block_mac_to_block(tr, &file->block_mac); |
| if (transaction_block_need_copy(tr, file_block)) { |
| if (tr->failed) { |
| pr_warn("transaction failed, abort\n"); |
| goto err; |
| } |
| assert((block_map && block_map->tree.root_block_changed) || |
| file->size != file_entry_ro->info.size || new_path); |
| assert(old_block == file_block); |
| new_block = block_allocate(tr); |
| if (tr->failed) { |
| pr_warn("transaction failed, abort\n"); |
| goto err; |
| } |
| assert(new_block); |
| block_mac_set_block(tr, &block_mac, new_block); |
| |
| pr_write("copy file block %" PRIu64 " -> %" PRIu64 "\n", file_block, |
| new_block); |
| |
| /* |
| * Use block_get_copy instead of block_move since tr->fs->files |
| * is still used and not updated until file_transaction_complete. |
| */ |
| // TODO: fix |
| file_entry_rw = block_get_copy(tr, file_entry_ro, new_block, false, |
| &file_entry_copy_ref); |
| block_put(file_entry_ro, &file_entry_ref); |
| file_entry_ro = file_entry_rw; |
| obj_ref_transfer(&file_entry_ref, &file_entry_copy_ref); |
| if (tr->failed) { |
| pr_warn("transaction failed, abort\n"); |
| goto err; |
| } |
| /* TODO: insert mac */ |
| block_tree_insert(tr, &tr->files_updated, file_block, new_block); |
| block_free(tr, file_block); |
| file->block_mac = block_mac; |
| } |
| assert(!file_tree_lookup(NULL, tr, &tr->files_added, &tree_path, |
| file_entry_ro->info.path, false)); |
| |
| /* TODO: get path from insert operation */ |
| block_tree_walk(tr, &tr->files_updated, old_block, false, &tree_path); |
| if (tr->failed) { |
| pr_warn("transaction failed, abort\n"); |
| goto err; |
| } |
| assert(block_tree_path_get_key(&tree_path) == old_block); |
| block_mac = block_tree_path_get_data_block_mac(&tree_path); |
| assert(block_mac_to_block(tr, &block_mac) == |
| block_mac_to_block(tr, &file->block_mac)); |
| /* TODO: enable after insert mac TODO */ |
| // assert(block_mac_eq(tr, &block_mac, &file->block_mac)); |
| |
| if (new_path) { |
| /* TODO: remove by path or, when possible, skip insert instead */ |
| block_tree_remove(tr, &tr->files_updated, old_block, |
| block_mac_to_block(tr, &block_mac)); |
| } |
| } |
| if (!file_entry_rw) { |
| file_entry_rw = block_dirty(tr, file_entry_ro, false); |
| } |
| |
| if (block_map) { |
| pr_write("file at %" PRIu64 ": update block_map %" PRIu64 " -> %" PRIu64 |
| "\n", |
| block_mac_to_block(tr, &block_mac), |
| block_mac_to_block(tr, &file_entry_rw->block_map), |
| block_mac_to_block(tr, &block_map->tree.root)); |
| |
| file_entry_rw->block_map = block_map->tree.root; |
| } |
| |
| file_entry_rw->info.size = file->size; |
| if (new_path) { |
| strcpy(file_entry_rw->info.path, new_path); |
| block_put_dirty(tr, file_entry_rw, &file_entry_ref, &block_mac, NULL); |
| block_tree_insert_block_mac(tr, &tr->files_added, |
| path_hash(tr, new_path), block_mac); |
| if (tr->failed) { |
| pr_warn("transaction failed, abort\n"); |
| goto err; |
| } |
| block_mac_copy(tr, &file->block_mac, &block_mac); |
| } else { |
| block_tree_path_put_dirty(tr, &tree_path, tree_path.count, |
| file_entry_rw, &file_entry_ref); |
| /* TODO: add better api */ |
| file->block_mac = tree_path.entry[tree_path.count].block_mac; |
| } |
| |
| /* |
| * Move to head of list so opening the same file twice in a transaction |
| * commits the changes to the file_handle that was modified. Writing to |
| * both handles is not currently supported. |
| */ |
| list_delete(&file->node); |
| list_add_head(&tr->open_files, &file->node); |
| |
| return; |
| |
| err: |
| if (file_entry_rw) { |
| block_put_dirty_discard(file_entry_rw, &file_entry_ref); |
| } else { |
| block_put(file_entry_ro, &file_entry_ref); |
| } |
| } |
| |
| /** |
| * get_file_block_size - Get file data block size |
| * @fs: File system state object. |
| * |
| * Return: Get size of data block returned by file_get_block and |
| * file_get_block_write for @fs. |
| */ |
| size_t get_file_block_size(struct fs* fs) { |
| return fs->dev->block_size - sizeof(struct iv); |
| } |
| |
| /** |
| * file_get_block_etc - Helper function to get a file block for read or write |
| * @tr: Transaction object. |
| * @file: File handle object. |
| * @file_block: File block number. 0 based. |
| * @read: %true if data should be read from disk, %false otherwise. |
| * @write: %true if returned data should be writeable, %false otherwise. |
| * @ref: Pointer to store reference in. |
| * |
| * Return: Const block data pointer, or NULL if no valid block is found at |
| * @file_block or if @write is %true and a block could not be allocated. |
| */ |
| static const void* file_get_block_etc(struct transaction* tr, |
| struct storage_file_handle* file, |
| data_block_t file_block, |
| bool read, |
| bool write, |
| struct obj_ref* ref) { |
| bool found = false; |
| const void* data = NULL; |
| struct block_map block_map; |
| struct block_mac block_mac; |
| data_block_t old_disk_block; |
| data_block_t new_block; |
| bool dirty = false; |
| |
| if (tr->failed) { |
| pr_warn("transaction failed, ignore\n"); |
| goto err; |
| } |
| |
| file_block_map_init(tr, &block_map, &file->block_mac); |
| if (tr->failed) { |
| pr_warn("transaction failed, abort\n"); |
| goto err; |
| } |
| |
| file->used_by_tr = true; |
| |
| found = block_map_get(tr, &block_map, file_block, &block_mac); |
| if (found) { |
| if (read) { |
| data = block_get(tr, &block_mac, NULL, ref); /* TODO: pass iv? */ |
| } else { |
| data = block_get_no_read(tr, block_mac_to_block(tr, &block_mac), |
| ref); |
| } |
| /* TODO: handle mac mismatch better */ |
| } |
| |
| old_disk_block = found ? block_mac_to_block(tr, &block_mac) : 0; |
| if (write && (!found || transaction_block_need_copy(tr, old_disk_block))) { |
| new_block = block_allocate(tr); |
| if (tr->failed) { |
| pr_warn("transaction failed, abort\n"); |
| goto err; |
| } |
| assert(new_block); |
| block_mac_set_block(tr, &block_mac, new_block); |
| |
| if (found) { |
| data = block_move(tr, data, new_block, false); |
| dirty = true; |
| assert(!tr->failed); |
| block_free(tr, old_disk_block); |
| } else { |
| if (read) { |
| assert(write); |
| data = block_get_cleared(tr, new_block, false, ref); |
| dirty = true; |
| } else { |
| data = block_get_no_read(tr, new_block, ref); |
| } |
| } |
| if (tr->failed) { |
| pr_warn("transaction failed, abort\n"); |
| goto err; |
| } |
| /* TODO: get new mac */ |
| assert(!tr->failed); |
| block_map_set(tr, &block_map, file_block, &block_mac); |
| file_block_map_update(tr, &block_map, NULL, file); |
| if (tr->failed) { |
| pr_warn("transaction failed, abort\n"); |
| goto err; |
| } |
| } |
| if (write && !dirty) { |
| data = block_dirty(tr, data, false); |
| } |
| if (!data) { |
| return NULL; |
| } |
| return data + sizeof(struct iv); |
| |
| err: |
| if (dirty) { |
| block_put_dirty_discard((void*)data, ref); |
| } else if (data) { |
| block_put(data, ref); |
| } |
| return NULL; |
| } |
| |
| /** |
| * file_get_block - Get a file block for read |
| * @tr: Transaction object. |
| * @file: File handle object. |
| * @file_block: File block number. 0 based. |
| * @ref: Pointer to store reference in. |
| * |
| * Return: Const block data pointer, or NULL if no valid block is found at |
| * @file_block. |
| */ |
| const void* file_get_block(struct transaction* tr, |
| struct storage_file_handle* file, |
| data_block_t file_block, |
| struct obj_ref* ref) { |
| return file_get_block_etc(tr, file, file_block, true, false, ref); |
| } |
| |
| /** |
| * file_get_block_write - Helper function to get a file block for write |
| * @tr: Transaction object. |
| * @file: File handle object. |
| * @file_block: File block number. 0 based. |
| * @read: %true if data should be read from disk, %false otherwise. |
| * @ref: Pointer to store reference in. |
| * |
| * Return: Block data pointer, or NULL if a block could not be allocated. |
| */ |
| void* file_get_block_write(struct transaction* tr, |
| struct storage_file_handle* file, |
| data_block_t file_block, |
| bool read, |
| struct obj_ref* ref) { |
| return (void*)file_get_block_etc(tr, file, file_block, read, true, ref); |
| } |
| |
| /** |
| * file_block_put - Release reference to a block returned by file_get_block |
| * @data: File block data pointer |
| * @data_ref: Reference pointer to release |
| */ |
| void file_block_put(const void* data, struct obj_ref* data_ref) { |
| block_put(data - sizeof(struct iv), data_ref); |
| } |
| |
| /** |
| * file_block_put_dirty - Release reference to a dirty file block |
| * @tr: Transaction |
| * @file: File handle object. |
| * @file_block: File block number. 0 based. |
| * @data: File block data pointer |
| * @data_ref: Reference pointer to release |
| * |
| * Release reference to a file block previously returned by |
| * file_get_block_write, and update mac value in block_mac. |
| */ |
| void file_block_put_dirty(struct transaction* tr, |
| struct storage_file_handle* file, |
| data_block_t file_block, |
| void* data, |
| struct obj_ref* data_ref) { |
| struct block_map block_map; |
| |
| file_block_map_init(tr, &block_map, &file->block_mac); |
| |
| block_map_put_dirty(tr, &block_map, file_block, data - sizeof(struct iv), |
| data_ref); |
| |
| file_block_map_update(tr, &block_map, NULL, file); |
| } |
| |
| /** |
| * file_get_info - Get a file informaion for read |
| * @tr: Transaction object. |
| * @block_mac: File entry block_mac. |
| * @ref: Pointer to store reference in. |
| * |
| * Return: Const file info pointer, or NULL if no valid block is found at |
| * @block_mac. |
| */ |
| const struct file_info* file_get_info(struct transaction* tr, |
| const struct block_mac* block_mac, |
| struct obj_ref* ref) { |
| const struct file_entry* file_entry; |
| |
| file_entry = block_get(tr, block_mac, NULL, ref); |
| if (!file_entry) { |
| assert(tr->failed); |
| pr_warn("transaction failed, abort\n"); |
| return NULL; |
| } |
| assert(file_entry); |
| assert(file_entry->magic == FILE_ENTRY_MAGIC); |
| |
| return &file_entry->info; |
| } |
| |
| /** |
| * file_info_put - Release reference to a block returned by file_get_info |
| * @data: File info data pointer |
| * @data_ref: Reference pointer to release |
| */ |
| void file_info_put(const struct file_info* data, struct obj_ref* data_ref) { |
| const struct file_entry* entry = containerof(data, struct file_entry, info); |
| assert(entry->magic == FILE_ENTRY_MAGIC); |
| block_put(entry, data_ref); |
| } |
| |
| /** |
| * file_get_size - Get file size |
| * @tr: Transaction |
| * @file: File handle object. |
| * @size: Pointer to return size in. |
| * |
| * Return: %true if @size was set, %false if @size was not set because @file is |
| * invalid or @tr has failed. |
| */ |
| bool file_get_size(struct transaction* tr, |
| struct storage_file_handle* file, |
| data_block_t* size) { |
| if (tr->failed) { |
| pr_warn("transaction failed, ignore\n"); |
| return false; |
| } |
| |
| if (!block_mac_valid(tr, &file->block_mac)) { |
| pr_warn("invalid file handle\n"); |
| return false; |
| } |
| |
| file->used_by_tr = true; |
| *size = file->size; |
| |
| return true; |
| } |
| |
| /** |
| * file_set_size - Set file size |
| * @tr: Transaction |
| * @file: File handle object. |
| * @size: New file size. |
| * |
| * Set file size and free blocks that are not longer needed. Does not clear any |
| * partial block data. |
| */ |
| void file_set_size(struct transaction* tr, |
| struct storage_file_handle* file, |
| data_block_t size) { |
| data_block_t file_block; |
| struct block_map block_map; |
| size_t file_block_size = get_file_block_size(tr->fs); |
| |
| if (tr->failed) { |
| pr_warn("transaction failed, ignore\n"); |
| return; |
| } |
| |
| file_block_map_init(tr, &block_map, &file->block_mac); |
| if (tr->failed) { |
| pr_warn("transaction failed, abort\n"); |
| return; |
| } |
| if (size == file->size) { |
| return; |
| } |
| if (size < file->size) { |
| file_block = DIV_ROUND_UP(size, file_block_size); |
| block_map_truncate(tr, &block_map, file_block); |
| } |
| file->size = size; |
| file_block_map_update(tr, &block_map, NULL, file); |
| } |
| |
| /** |
| * file_tree_lookup - Helper function to search for a file in a specific tree |
| * @block_mac_out: Block-mac object to return block number and mac in. |
| * @tr: Transaction object. |
| * @tree: Tree object. |
| * @tree_path: Tree path object. |
| * @file_path: File path string. |
| * @remove: %true if found entry should be removed, %false otherwise. |
| * |
| * Return: %true if @file_path was found in @tree, %false otherwise. |
| */ |
| static bool file_tree_lookup(struct block_mac* block_mac_out, |
| struct transaction* tr, |
| struct block_tree* tree, |
| struct block_tree_path* tree_path, |
| const char* file_path, |
| bool remove) { |
| struct block_mac block_mac; |
| data_block_t hash = path_hash(tr, file_path); |
| const struct file_entry* file_entry; |
| struct obj_ref file_entry_ref = OBJ_REF_INITIAL_VALUE(file_entry_ref); |
| bool found = false; |
| |
| assert(strlen(file_path) < sizeof(file_entry->info.path)); |
| assert(sizeof(*file_entry) <= tr->fs->dev->block_size); |
| |
| block_tree_walk(tr, tree, hash - 1, false, tree_path); |
| while (block_tree_path_get_key(tree_path) && |
| block_tree_path_get_key(tree_path) < hash) { |
| block_tree_path_next(tree_path); |
| } |
| while (block_tree_path_get_key(tree_path) == hash) { |
| block_mac = block_tree_path_get_data_block_mac(tree_path); |
| if (!block_mac_to_block(tr, &block_mac)) { |
| if (LOCAL_TRACE >= TRACE_LEVEL_WARNING) { |
| pr_warn("got 0 block pointer for hash %" PRIu64 |
| " while looking for %s, hash 0x%" PRIx64 "\n", |
| block_tree_path_get_key(tree_path), file_path, hash); |
| block_tree_print(tr, tree); |
| } |
| block_tree_path_next(tree_path); |
| block_mac = block_tree_path_get_data_block_mac(tree_path); |
| pr_warn("next %" PRIu64 ", hash 0x%" PRIx64 "\n", |
| block_mac_to_block(tr, &block_mac), |
| block_tree_path_get_key(tree_path)); |
| } |
| if (tr->failed) { |
| pr_warn("transaction failed, abort\n"); |
| return false; |
| } |
| assert(block_mac_to_block(tr, &block_mac)); |
| file_entry = block_get(tr, &block_mac, NULL, &file_entry_ref); |
| if (!file_entry) { |
| assert(tr->failed); |
| pr_warn("transaction failed, abort\n"); |
| return false; |
| } |
| assert(file_entry); |
| assert(file_entry->magic == FILE_ENTRY_MAGIC); |
| found = !strcmp(file_path, file_entry->info.path); |
| |
| pr_read("block %" PRIu64 ", hash 0x%" PRIx64 " match, found %d\n", |
| block_mac_to_block(tr, &block_mac), hash, found); |
| |
| block_put(file_entry, &file_entry_ref); |
| if (found) { |
| if (remove) { |
| /* TODO: remove by path */ |
| // block_tree_remove_path(tr, tree_path); |
| /* TODO: pass mac */ |
| block_tree_remove(tr, tree, hash, |
| block_mac_to_block(tr, &block_mac)); |
| } |
| *block_mac_out = block_mac; |
| return true; |
| } |
| block_tree_path_next(tree_path); |
| } |
| if (LOCAL_TRACE >= TRACE_LEVEL_READ) { |
| pr_read("%s: hash 0x%" PRIx64 " does not match 0x%" PRIx64 "\n", |
| __func__, hash, block_tree_path_get_key(tree_path)); |
| block_tree_print(tr, tree); |
| } |
| return false; |
| } |
| |
| /** |
| * file_create - Helper function to create a new file |
| * @block_mac_out: Block-mac object to return block number and mac in. |
| * @tr: Transaction object. |
| * @path: File path string. |
| * |
| * Create a new file and insert it into &tr->files_added. Caller must make |
| * a file matching @path does not already exist. |
| * |
| * Return: %true if file was created, %false otherwise. |
| */ |
| static bool file_create(struct block_mac* block_mac_out, |
| struct transaction* tr, |
| const char* path) { |
| data_block_t block; |
| struct file_entry* file_entry; |
| struct obj_ref file_entry_ref = OBJ_REF_INITIAL_VALUE(file_entry_ref); |
| data_block_t hash; |
| |
| hash = path_hash(tr, path); |
| |
| block = block_allocate(tr); |
| |
| pr_write("create file, %s, hash 0x%" PRIx64 ", at block %" PRIu64 "\n", |
| path, hash, block); |
| |
| if (tr->failed) { |
| pr_warn("transaction failed, abort\n"); |
| goto err; |
| } |
| |
| assert(block); |
| block_mac_set_block(tr, block_mac_out, block); |
| |
| file_entry = block_get_cleared(tr, block, false, &file_entry_ref); |
| assert(file_entry); |
| file_entry->magic = FILE_ENTRY_MAGIC; |
| strcpy(file_entry->info.path, path); |
| block_put_dirty(tr, file_entry, &file_entry_ref, block_mac_out, NULL); |
| if (tr->failed) { |
| pr_warn("transaction failed, abort\n"); |
| goto err; |
| } |
| block_tree_insert_block_mac(tr, &tr->files_added, hash, *block_mac_out); |
| if (tr->failed) { |
| pr_warn("transaction failed, abort\n"); |
| goto err; |
| } |
| return true; |
| |
| err: |
| return false; |
| } |
| |
| /** |
| * file_is_removed - Helper function to check if transaction deletes file |
| * @tr: Transaction object. |
| * @block: Block number for file. |
| * |
| * Return: %true if file is removed in @tr, %false otherwise. |
| */ |
| static bool file_is_removed(struct transaction* tr, data_block_t block) { |
| struct block_tree_path tree_path; |
| |
| block_tree_walk(tr, &tr->files_removed, block, false, &tree_path); |
| |
| return block_tree_path_get_key(&tree_path) == block; |
| } |
| |
| /** |
| * file_check_updated - Helper function to check if transaction updated file |
| * @tr: Transaction object. |
| * @committed_block_mac: Block-mac object to check. |
| * @block_mac_out: Block-mac object to return block number and mac in |
| * for the current state of the file in @tr. |
| * |
| * Return: %true if file is updated in @tr, %false otherwise. |
| */ |
| static bool file_check_updated(struct transaction* tr, |
| const struct block_mac* committed_block_mac, |
| struct block_mac* block_mac_out) { |
| struct block_tree_path tree_path; |
| data_block_t block = block_mac_to_block(tr, committed_block_mac); |
| |
| block_tree_walk(tr, &tr->files_updated, block, false, &tree_path); |
| if (block_tree_path_get_key(&tree_path) == block) { |
| pr_read("%" PRIu64 |
| ", already updated in this transaction, use new copy %" PRIu64 |
| "\n", |
| block, block_tree_path_get_data(&tree_path)); |
| *block_mac_out = block_tree_path_get_data_block_mac(&tree_path); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| /** |
| * file_lookup_not_removed - Helper function to search for a file |
| * @block_mac_out: Block-mac object to return block number and mac |
| * in for the current state of the file. |
| * @committed_block_mac_out: Block-mac object to return block number and mac |
| * in for the committed state of the file. |
| * @tr: Transaction object. |
| * @tree_path: Tree path object. |
| * @file_path: File path string. |
| * |
| * Helper function to search for a file that existed before @tr was activated. |
| * |
| * Return: %true if @file_path was found in @tr->fs->files but not found in |
| * @tr->files_removed, %false otherwise. |
| */ |
| static bool file_lookup_not_removed(struct block_mac* block_mac_out, |
| struct block_mac* committed_block_mac_out, |
| struct transaction* tr, |
| struct block_tree_path* tree_path, |
| const char* file_path) { |
| bool found; |
| struct block_mac block_mac; |
| |
| found = file_tree_lookup(&block_mac, tr, &tr->fs->files, tree_path, |
| file_path, false); |
| if (!found || file_is_removed(tr, block_mac_to_block(tr, &block_mac))) { |
| if (found) { |
| pr_read("file %s, %" PRIu64 " in removed\n", file_path, |
| block_mac_to_block(tr, &block_mac)); |
| } else { |
| pr_read("file %s not in tr->fs->files\n", file_path); |
| } |
| return false; |
| } |
| *committed_block_mac_out = block_mac; |
| |
| block_tree_walk(tr, &tr->files_updated, block_mac_to_block(tr, &block_mac), |
| false, tree_path); |
| if (block_tree_path_get_key(tree_path) == |
| block_mac_to_block(tr, &block_mac)) { |
| pr_read("file %s, %" PRIu64 |
| ", already updated in this transaction, use new copy %" PRIu64 |
| "\n", |
| file_path, block_mac_to_block(tr, &block_mac), |
| block_tree_path_get_data(tree_path)); |
| block_mac = block_tree_path_get_data_block_mac(tree_path); |
| } |
| pr_read("file %s, %" PRIu64 ", found in tr->fs->files\n", file_path, |
| block_mac_to_block(tr, &block_mac)); |
| |
| /* |
| * TODO: mark file (or blocks) in_use so other writers to the same file |
| * will cause this transaction to fail. Currently conflicts are only |
| * detected after each transaction writes to the file. |
| */ |
| |
| *block_mac_out = block_mac; |
| return true; |
| } |
| |
| /** |
| * file_find_open - Check if transaction has file open and return it |
| * @tr: Transaction object. |
| * @block_mac: Current block and mac value for open file. |
| * |
| * Return: file handle of open file if file is open in @tr, NULL otherwise. |
| */ |
| static struct storage_file_handle* file_find_open( |
| struct transaction* tr, |
| const struct block_mac* block_mac) { |
| struct storage_file_handle* file; |
| |
| list_for_every_entry(&tr->open_files, file, struct storage_file_handle, |
| node) { |
| if (block_mac_same_block(tr, &file->block_mac, block_mac)) { |
| return file; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| /** |
| * file_iterate - Iterate over all files |
| * @tr: Transaction object. |
| * @start_path: First file to not iterate over. |
| * @added: %false to iterate over committed files, %true to iterate over |
| * uncommitted files added by @tr. |
| * @state: client state object containing function to call for each file. |
| * @allow_repaired: Accept a repaired file system state. |
| * |
| * Return: %FILE_OP_ERR_FS_REPAIRED if the file system has been repaired and |
| * @allow_repaired was false, %FILE_OP_ERR_FAILED if the file system is not |
| * readable or an internal error occurred, and %FILE_OP_SUCCESS otherwise. |
| */ |
| enum file_op_result file_iterate(struct transaction* tr, |
| const char* start_path, |
| bool added, |
| struct file_iterate_state* state, |
| bool allow_repaired) { |
| struct block_tree_path tree_path; |
| struct block_mac block_mac; |
| struct block_tree* tree = added ? &tr->files_added : &tr->fs->files; |
| bool found; |
| bool stop; |
| bool removed = false; |
| |
| assert(tr->fs); |
| if (!fs_is_readable(tr->fs)) { |
| return FILE_OP_ERR_FAILED; |
| } |
| if (!allow_repaired && (tr->repaired || fs_is_repaired(tr->fs))) { |
| return FILE_OP_ERR_FS_REPAIRED; |
| } |
| |
| if (start_path == NULL) { |
| block_tree_walk(tr, tree, 0, true, &tree_path); |
| } else { |
| found = file_tree_lookup(&block_mac, tr, tree, &tree_path, start_path, |
| false); |
| if (!found) { |
| return FILE_OP_ERR_FAILED; |
| } |
| block_tree_path_next(&tree_path); |
| } |
| if (tr->failed) { |
| return FILE_OP_ERR_FAILED; |
| } |
| |
| while (block_tree_path_get_key(&tree_path)) { |
| block_mac = block_tree_path_get_data_block_mac(&tree_path); |
| if (!added) { |
| removed = file_is_removed(tr, block_mac_to_block(tr, &block_mac)); |
| file_check_updated(tr, &block_mac, &block_mac); |
| } |
| if (tr->failed) { |
| return FILE_OP_ERR_FAILED; |
| } |
| |
| stop = state->file(state, tr, &block_mac, added, removed); |
| |
| if (tr->failed) { |
| return FILE_OP_ERR_FAILED; |
| } |
| if (stop) { |
| return FILE_OP_SUCCESS; |
| } |
| block_tree_path_next(&tree_path); |
| } |
| return FILE_OP_SUCCESS; |
| } |
| |
| /** |
| * file_open - Open file |
| * @tr: Transaction object. |
| * @path: Path to find or create file at. |
| * @file: File handle object. |
| * @create: FILE_OPEN_NO_CREATE, FILE_OPEN_CREATE or |
| * FILE_OPEN_CREATE_EXCLUSIVE. |
| * @allow_repaired: Accept a repaired file system state. If the file system has |
| * been repaired, missing files may have previously existed and |
| * are now rolled back. |
| * |
| * Return: %FILE_OP_ERR_FS_REPAIRED if the file system has been repaired and |
| * @allow_repaired was false, &enum file_op_result.FILE_OP_SUCCESS if file was |
| * opened, or an error describing the failure to open the file. |
| */ |
| enum file_op_result file_open(struct transaction* tr, |
| const char* path, |
| struct storage_file_handle* file, |
| enum file_create_mode create, |
| bool allow_repaired) { |
| bool found; |
| struct block_mac block_mac; |
| struct block_mac committed_block_mac = |
| BLOCK_MAC_INITIAL_VALUE(committed_block_mac); |
| struct block_tree_path tree_path; |
| const struct file_entry* file_entry_ro; |
| struct obj_ref file_entry_ref = OBJ_REF_INITIAL_VALUE(file_entry_ref); |
| |
| assert(tr->fs); |
| if (!allow_repaired && (tr->repaired || fs_is_repaired(tr->fs))) { |
| return FILE_OP_ERR_FS_REPAIRED; |
| } |
| |
| if (!fs_is_readable(tr->fs)) { |
| return FILE_OP_ERR_FAILED; |
| } |
| |
| found = file_tree_lookup(&block_mac, tr, &tr->files_added, &tree_path, path, |
| false); |
| if (found) { |
| goto found; |
| } |
| |
| found = file_lookup_not_removed(&block_mac, &committed_block_mac, tr, |
| &tree_path, path); |
| if (found) { |
| goto found; |
| } |
| if (tr->failed) { |
| return FILE_OP_ERR_FAILED; |
| } |
| |
| if (create != FILE_OPEN_NO_CREATE) { |
| found = file_create(&block_mac, tr, path); |
| } |
| if (found) { |
| goto created; |
| } |
| if (tr->failed) { |
| return FILE_OP_ERR_FAILED; |
| } else { |
| return FILE_OP_ERR_NOT_FOUND; |
| } |
| |
| found: |
| if (create == FILE_OPEN_CREATE_EXCLUSIVE) { |
| return FILE_OP_ERR_EXIST; |
| } |
| if (file_find_open(tr, &block_mac)) { |
| pr_warn("%s already open\n", path); |
| return FILE_OP_ERR_ALREADY_OPEN; |
| } |
| created: |
| file_entry_ro = block_get(tr, &block_mac, NULL, &file_entry_ref); |
| if (!file_entry_ro) { |
| assert(tr->failed); |
| pr_warn("transaction failed, abort\n"); |
| return FILE_OP_ERR_FAILED; |
| } |
| assert(file_entry_ro); |
| list_add_head(&tr->open_files, &file->node); |
| file->to_commit_block_mac = committed_block_mac; |
| file->committed_block_mac = committed_block_mac; |
| file->block_mac = block_mac; |
| file->size = file_entry_ro->info.size; |
| file->used_by_tr = false; |
| block_put(file_entry_ro, &file_entry_ref); |
| |
| return FILE_OP_SUCCESS; |
| } |
| |
| /** |
| * file_close - Close file |
| * @file: File handle object. |
| * |
| * Must be called before freeing @file after a successful file_open call. |
| */ |
| void file_close(struct storage_file_handle* file) { |
| list_delete(&file->node); |
| } |
| |
| /** |
| * file_delete - Delete file |
| * @tr: Transaction object. |
| * @path: Path to find file at. |
| * @allow_repaired: Accept a repaired file system state. |
| * |
| * Return: %FILE_OP_ERR_FS_REPAIRED if the file system has been repaired and |
| * @allow_repaired was false, %FILE_OP_SUCCESS if the file existed and was |
| * deleted, %FILE_OP_ERR_NOT_FOUND if the file did not exist, and |
| * %FILE_OP_ERR_FAILED if some other error occurred. |
| */ |
| enum file_op_result file_delete(struct transaction* tr, |
| const char* path, |
| bool allow_repaired) { |
| bool found; |
| struct block_mac block_mac; |
| struct block_mac old_block_mac; |
| bool in_files = false; |
| const struct file_entry* file_entry; |
| struct obj_ref file_entry_ref = OBJ_REF_INITIAL_VALUE(file_entry_ref); |
| struct block_map block_map = BLOCK_MAP_INITIAL_VALUE(block_map); |
| struct block_tree_path tree_path; |
| struct storage_file_handle* open_file; |
| |
| assert(tr->fs); |
| if (!fs_is_readable(tr->fs)) { |
| return FILE_OP_ERR_FAILED; |
| } |
| if (!allow_repaired && (tr->repaired || fs_is_repaired(tr->fs))) { |
| return FILE_OP_ERR_FS_REPAIRED; |
| } |
| |
| found = file_tree_lookup(&block_mac, tr, &tr->files_added, &tree_path, path, |
| true); |
| if (!found) { |
| pr_read("file %s not in tr->files_added\n", path); |
| found = file_lookup_not_removed(&block_mac, &old_block_mac, tr, |
| &tree_path, path); |
| if (!found) { |
| pr_warn("file %s not found\n", path); |
| return tr->failed ? FILE_OP_ERR_FAILED : FILE_OP_ERR_NOT_FOUND; |
| } |
| in_files = true; |
| } |
| |
| pr_write("delete file, %s, at block %" PRIu64 "\n", path, |
| block_mac_to_block(tr, &block_mac)); |
| |
| file_entry = block_get(tr, &block_mac, NULL, &file_entry_ref); |
| if (!file_entry) { |
| assert(tr->failed); |
| pr_warn("transaction failed, abort\n"); |
| return FILE_OP_ERR_FAILED; |
| } |
| assert(file_entry); |
| assert(!strcmp(file_entry->info.path, path)); |
| block_map_init(tr, &block_map, &file_entry->block_map, |
| tr->fs->dev->block_size); |
| if (!in_files || !block_mac_same_block(tr, &block_mac, &old_block_mac)) { |
| block_discard_dirty(file_entry); |
| } |
| block_put(file_entry, &file_entry_ref); |
| if (in_files) { |
| if (!block_mac_same_block(tr, &block_mac, &old_block_mac)) { |
| /* TODO: pass mac */ |
| block_tree_remove(tr, &tr->files_updated, |
| block_mac_to_block(tr, &old_block_mac), |
| block_mac_to_block(tr, &block_mac)); |
| if (tr->failed) { |
| pr_warn("transaction failed, abort\n"); |
| return FILE_OP_ERR_FAILED; |
| } |
| } |
| block_tree_insert_block_mac(tr, &tr->files_removed, |
| block_mac_to_block(tr, &old_block_mac), |
| old_block_mac); |
| } |
| block_free(tr, block_mac_to_block(tr, &block_mac)); |
| block_map_free(tr, &block_map); |
| |
| open_file = file_find_open(tr, &block_mac); |
| if (open_file) { |
| struct block_mac clear = BLOCK_MAC_INITIAL_VALUE(clear); |
| open_file->block_mac = clear; |
| open_file->size = 0; |
| } |
| return FILE_OP_SUCCESS; |
| } |
| |
| /** |
| * file_move - Move a file to a new path, optionally replacing an existing file |
| * @tr: Transaction object. |
| * @file: File handle object. |
| * @dest_path: Path to move file to. |
| * @dest_create: FILE_OPEN_NO_CREATE, FILE_OPEN_CREATE or |
| * FILE_OPEN_CREATE_EXCLUSIVE. |
| * @allow_repaired: Accept a repaired file system state. |
| * |
| * Return: %FILE_OP_SUCCESS if the operation was successful, |
| * %FILE_OP_ERR_FS_REPAIRED if the file system has been repaired and |
| * @allow_repaired was false, %FILE_OP_ERR_EXIST if the destination file already |
| * exists and %FILE_OPEN_CREATE_EXCLUSIVE was requested, %FILE_OP_ERR_NOT_FOUND |
| * if the destination file did not exist but %FILE_OPEN_NO_CREATE was required, |
| * and %FILE_OP_ERR_FAILED if some other error occurred. |
| */ |
| enum file_op_result file_move(struct transaction* tr, |
| struct storage_file_handle* file, |
| const char* dest_path, |
| enum file_create_mode dest_create, |
| bool allow_repaired) { |
| assert(tr->fs); |
| struct block_mac block_mac; |
| struct block_mac committed_block_mac; |
| struct block_tree_path tree_path; |
| bool dest_found; |
| |
| if (!fs_is_readable(tr->fs)) { |
| return FILE_OP_ERR_FAILED; |
| } |
| if (!allow_repaired && (tr->repaired || fs_is_repaired(tr->fs))) { |
| return FILE_OP_ERR_FS_REPAIRED; |
| } |
| |
| dest_found = file_tree_lookup(&block_mac, tr, &tr->files_added, &tree_path, |
| dest_path, false); |
| if (!dest_found) { |
| dest_found = file_lookup_not_removed(&block_mac, &committed_block_mac, |
| tr, &tree_path, dest_path); |
| } |
| if (tr->failed) { |
| pr_warn("transaction failed, abort\n"); |
| return FILE_OP_ERR_FAILED; |
| } |
| |
| if (dest_found) { |
| if (dest_create == FILE_OPEN_CREATE_EXCLUSIVE) { |
| return FILE_OP_ERR_EXIST; |
| } |
| if (block_mac_eq(tr, &file->block_mac, &block_mac)) { |
| return FILE_OP_SUCCESS; |
| } |
| file_delete(tr, dest_path, allow_repaired); |
| } else { |
| if (dest_create == FILE_OPEN_NO_CREATE) { |
| return FILE_OP_ERR_NOT_FOUND; |
| } |
| } |
| |
| if (tr->failed) { |
| pr_warn("transaction failed, abort\n"); |
| return FILE_OP_ERR_FAILED; |
| } |
| file_block_map_update(tr, NULL, dest_path, file); |
| if (tr->failed) { |
| pr_warn("transaction failed, abort\n"); |
| return FILE_OP_ERR_FAILED; |
| } |
| return FILE_OP_SUCCESS; |
| } |
| |
| /** |
| * file_for_each_open - Call function for every open file in every transaction |
| * @tr: Transaction object. |
| * @func: Function to call. |
| */ |
| static void file_for_each_open(struct transaction* tr, |
| void (*func)(struct transaction* tr, |
| struct transaction* file_tr, |
| struct storage_file_handle* file)) { |
| struct transaction* tmp_tr; |
| struct transaction* other_tr; |
| struct storage_file_handle* file; |
| |
| list_for_every_entry_safe(&tr->fs->transactions, other_tr, tmp_tr, |
| struct transaction, node) { |
| list_for_every_entry(&other_tr->open_files, file, |
| struct storage_file_handle, node) { |
| func(tr, other_tr, file); |
| } |
| } |
| } |
| |
| /** |
| * file_restore_to_commit - Restore to_commit state to commited state |
| * @tr: Current transaction. |
| * @file_tr: Transaction that @file was found in. |
| * @file: File handle object. |
| */ |
| static void file_restore_to_commit(struct transaction* tr, |
| struct transaction* file_tr, |
| struct storage_file_handle* file) { |
| struct block_mac* src = &file->committed_block_mac; |
| struct block_mac* dest = &file->to_commit_block_mac; |
| if (block_mac_same_block(tr, src, dest)) { |
| assert(block_mac_eq(tr, src, dest)); |
| return; |
| } |
| pr_write("abort block %" PRIu64 "/%" PRIu64 " -> %" PRIu64 ", size %" PRIu64 |
| " -> %" PRIu64 "\n", |
| block_mac_to_block(tr, &file->committed_block_mac), |
| block_mac_to_block(tr, &file->block_mac), |
| block_mac_to_block(tr, &file->to_commit_block_mac), file->size, |
| file->to_commit_size); |
| block_mac_copy(tr, dest, src); |
| } |
| |
| /** |
| * file_apply_to_commit - Apply to_commit state |
| * @tr: Current transaction. |
| * @file_tr: Transaction that @file was found in. |
| * @file: File handle object. |
| * |
| * Copies to_commit to commited and current state and fails conflicting |
| * transactions. |
| */ |
| static void file_apply_to_commit(struct transaction* tr, |
| struct transaction* file_tr, |
| struct storage_file_handle* file) { |
| struct block_mac* src = &file->to_commit_block_mac; |
| struct block_mac* dest = &file->committed_block_mac; |
| |
| if (tr == file_tr) { |
| file->used_by_tr = false; |
| } |
| |
| if (block_mac_same_block(tr, src, dest)) { |
| assert(block_mac_eq(tr, src, dest)); |
| pr_write("file handle %p, unchanged file at %" PRIu64 "\n", file, |
| block_mac_to_block(tr, &file->committed_block_mac)); |
| return; |
| } |
| |
| if (file_tr != tr) { |
| if (file->used_by_tr) { |
| pr_warn("conflict %" PRIu64 " != %" PRIu64 " || used_by_tr %d\n", |
| block_mac_to_block(tr, &file->committed_block_mac), |
| block_mac_to_block(tr, &file->block_mac), file->used_by_tr); |
| assert(!file_tr->failed); |
| transaction_fail(file_tr); |
| } |
| assert(block_mac_same_block(tr, dest, &file->block_mac)); |
| } |
| |
| pr_write("file handle %p, apply block %" PRIu64 "/%" PRIu64 " -> %" PRIu64 |
| ", size %" PRIu64 " -> %" PRIu64 "\n", |
| file, block_mac_to_block(tr, &file->committed_block_mac), |
| block_mac_to_block(tr, &file->block_mac), |
| block_mac_to_block(tr, &file->to_commit_block_mac), file->size, |
| file->to_commit_size); |
| |
| block_mac_copy(tr, dest, src); |
| if (tr == file_tr) { |
| assert(block_mac_eq(tr, &file->block_mac, src)); |
| assert(file->size == file->to_commit_size); |
| } else { |
| block_mac_copy(tr, &file->block_mac, src); |
| file->size = file->to_commit_size; |
| } |
| } |
| |
| /** |
| * file_transaction_complete_failed - Restore open files state |
| * @tr: Transaction object. |
| * |
| * Revert open file changes done by file_transaction_complete. |
| */ |
| void file_transaction_complete_failed(struct transaction* tr) { |
| file_for_each_open(tr, file_restore_to_commit); |
| } |
| |
| /** |
| * file_update_block_mac_tr - Update open files with committed changes |
| * @tr: Current transaction object. |
| * @other_tr: Transaction object to find files in. |
| * @old_block_mac: Block and mac of committed file. |
| * @old_block_no_mac: %true if @old_block_mac->max is invalid. |
| * @new_block_mac: New block and mac. |
| * @new_size: New size. |
| * |
| * Prepare update of open files referring to the file at @old_block_mac. |
| */ |
| static void file_update_block_mac_tr(struct transaction* tr, |
| struct transaction* other_tr, |
| const struct block_mac* old_block_mac, |
| bool old_block_no_mac, |
| const struct block_mac* new_block_mac, |
| data_block_t new_size) { |
| struct storage_file_handle* file; |
| |
| assert(block_mac_valid(tr, old_block_mac) || other_tr == tr); |
| list_for_every_entry(&other_tr->open_files, file, |
| struct storage_file_handle, node) { |
| if (block_mac_valid(tr, old_block_mac) |
| ? !block_mac_same_block(tr, &file->committed_block_mac, |
| old_block_mac) |
| : !block_mac_same_block(tr, &file->block_mac, |
| new_block_mac)) { |
| pr_write("file handle %p, unrelated %" PRIu64 " != %" PRIu64 "\n", |
| file, block_mac_to_block(tr, &file->committed_block_mac), |
| block_mac_to_block(tr, new_block_mac)); |
| continue; /*unrelated file */ |
| } |
| assert(old_block_no_mac || !block_mac_valid(tr, old_block_mac) || |
| block_mac_eq(tr, &file->committed_block_mac, old_block_mac)); |
| |
| pr_write("file handle %p, stage block %" PRIu64 "/%" PRIu64 |
| " -> %" PRIu64 ", size %" PRIu64 " -> %" PRIu64 "\n", |
| file, block_mac_to_block(tr, &file->committed_block_mac), |
| block_mac_to_block(tr, &file->block_mac), |
| block_mac_to_block(tr, new_block_mac), file->size, new_size); |
| |
| block_mac_copy(tr, &file->to_commit_block_mac, new_block_mac); |
| file->to_commit_size = new_size; |
| } |
| } |
| |
| /** |
| * file_update_block_mac_all - Update open files with committed changes |
| * @tr: Transaction object. |
| * @old_block_mac: Block and mac of committed file. |
| * @old_block_no_mac: %true if @old_block_mac->max is invalid. |
| * @new_block_mac: New block and mac. |
| * @new_size: New size. |
| * |
| * Update other open files referring to the same file as @src_file with the |
| * size, block and mac from @src_file. |
| */ |
| static void file_update_block_mac_all(struct transaction* tr, |
| const struct block_mac* old_block_mac, |
| bool old_block_no_mac, |
| const struct block_mac* new_block_mac, |
| data_block_t new_size) { |
| struct transaction* tmp_tr; |
| struct transaction* other_tr; |
| |
| list_for_every_entry_safe(&tr->fs->transactions, other_tr, tmp_tr, |
| struct transaction, node) { |
| file_update_block_mac_tr(tr, other_tr, old_block_mac, old_block_no_mac, |
| new_block_mac, new_size); |
| } |
| } |
| |
| /** |
| * file_transaction_complete - Update files |
| * @tr: Transaction object. |
| * @new_files_block_mac: Object to return block and mac of updated file tree |
| * |
| * Create a copy of @tr->fs->files and apply all file updates from @tr to it. |
| * Called by transaction_complete, which will update the super-block and |
| * @tr->fs->files if the transaction succeeds. |
| */ |
| void file_transaction_complete(struct transaction* tr, |
| struct block_mac* new_files_block_mac) { |
| bool found; |
| const struct file_entry* file_entry_ro; |
| struct obj_ref file_entry_ref = OBJ_REF_INITIAL_VALUE(file_entry_ref); |
| struct block_tree_path tree_path; |
| struct block_tree_path tmp_tree_path; |
| struct block_mac old_file; |
| struct block_mac file; |
| struct block_tree new_files; |
| data_block_t hash; |
| const struct block_mac clear_block_mac = BLOCK_MAC_INITIAL_VALUE(clear); |
| |
| block_tree_copy(&new_files, &tr->fs->files); |
| |
| block_tree_walk(tr, &tr->files_updated, 0, true, &tree_path); |
| while (true) { |
| file = block_tree_path_get_data_block_mac(&tree_path); |
| if (!block_mac_to_block(tr, &file)) { |
| break; |
| } |
| file_entry_ro = block_get(tr, &file, NULL, &file_entry_ref); |
| if (!file_entry_ro) { |
| assert(tr->failed); |
| pr_warn("transaction failed, abort\n"); |
| return; |
| } |
| assert(file_entry_ro); |
| block_mac_set_block(tr, &old_file, block_tree_path_get_key(&tree_path)); |
| |
| pr_write("update file at %" PRIu64 " -> %" PRIu64 ", %s\n", |
| block_mac_to_block(tr, &old_file), |
| block_mac_to_block(tr, &file), file_entry_ro->info.path); |
| |
| file_update_block_mac_all(tr, &old_file, true, &file, |
| file_entry_ro->info.size); |
| |
| hash = path_hash(tr, file_entry_ro->info.path); |
| |
| block_put(file_entry_ro, &file_entry_ref); |
| |
| if (tr->failed) { |
| pr_warn("transaction failed, abort\n"); |
| return; |
| } |
| |
| block_tree_update_block_mac(tr, &new_files, hash, old_file, hash, file); |
| |
| if (tr->failed) { |
| pr_warn("transaction failed, abort\n"); |
| return; |
| } |
| |
| assert(block_mac_to_block(tr, &old_file) != |
| block_mac_to_block(tr, &file)); |
| block_tree_path_next(&tree_path); |
| } |
| |
| block_tree_walk(tr, &tr->files_removed, 0, true, &tree_path); |
| while (true) { |
| file = block_tree_path_get_data_block_mac(&tree_path); |
| if (!block_mac_to_block(tr, &file)) { |
| break; |
| } |
| file_entry_ro = block_get(tr, &file, NULL, &file_entry_ref); |
| if (!file_entry_ro) { |
| assert(tr->failed); |
| pr_warn("transaction failed, abort\n"); |
| return; |
| } |
| assert(file_entry_ro); |
| |
| pr_write("delete file at %" PRIu64 ", %s\n", |
| block_mac_to_block(tr, &file), file_entry_ro->info.path); |
| |
| file_update_block_mac_all(tr, &file, false, &clear_block_mac, 0); |
| |
| found = file_tree_lookup(&old_file, tr, &new_files, &tmp_tree_path, |
| file_entry_ro->info.path, true); |
| block_put(file_entry_ro, &file_entry_ref); |
| |
| if (tr->failed) { |
| pr_warn("transaction failed, abort\n"); |
| return; |
| } |
| |
| assert(found); |
| assert(block_mac_to_block(tr, &old_file) == |
| block_mac_to_block(tr, &file)); |
| block_tree_path_next(&tree_path); |
| } |
| block_tree_walk(tr, &tr->files_added, 0, true, &tree_path); |
| while (true) { |
| file = block_tree_path_get_data_block_mac(&tree_path); |
| if (!block_mac_valid(tr, &file)) { |
| break; |
| } |
| file_entry_ro = block_get(tr, &file, NULL, &file_entry_ref); |
| if (!file_entry_ro) { |
| assert(tr->failed); |
| pr_warn("transaction failed, abort\n"); |
| return; |
| } |
| assert(file_entry_ro); |
| pr_write("add file at %" PRIu64 ", %s\n", block_mac_to_block(tr, &file), |
| file_entry_ro->info.path); |
| |
| if (file_tree_lookup(&old_file, tr, &new_files, &tmp_tree_path, |
| file_entry_ro->info.path, false)) { |
| block_put(file_entry_ro, &file_entry_ref); |
| pr_err("add file at %" PRIu64 |
| ", %s, failed, conflicts with %" PRIu64 "\n", |
| block_mac_to_block(tr, &file), file_entry_ro->info.path, |
| block_mac_to_block(tr, &old_file)); |
| transaction_fail(tr); |
| return; |
| } |
| |
| file_update_block_mac_tr(tr, tr, &clear_block_mac, false, &file, |
| file_entry_ro->info.size); |
| |
| hash = path_hash(tr, file_entry_ro->info.path); |
| block_put(file_entry_ro, &file_entry_ref); |
| |
| if (tr->failed) { |
| pr_warn("transaction failed, abort\n"); |
| return; |
| } |
| |
| block_tree_insert_block_mac(tr, &new_files, hash, file); |
| if (tr->failed) { |
| pr_warn("transaction failed, abort\n"); |
| return; |
| } |
| |
| block_tree_path_next(&tree_path); |
| } |
| *new_files_block_mac = new_files.root; |
| } |
| |
| /** |
| * transaction_changed_file - Check if file was modified by current transaction |
| * @tr: Transaction object. |
| * @file: File handle. |
| * |
| * Return: %true if @file was modified by @tr, %false otherwise. |
| */ |
| static bool transaction_changed_file(struct transaction* tr, |
| struct storage_file_handle* file) { |
| return !block_mac_same_block(tr, &file->committed_block_mac, |
| &file->block_mac); |
| } |
| |
| /** |
| * file_transaction_success - Update all file handles modified by transaction |
| * @tr: Transaction object. |
| */ |
| void file_transaction_success(struct transaction* tr) { |
| file_for_each_open(tr, file_apply_to_commit); |
| } |
| |
| /** |
| * file_read_size - Read file entry to get file size |
| * @tr: Transaction object. |
| * @block_mac: Block and mac of file entry to read. |
| * @sz: Pointer to store file size in. |
| * |
| * Return: %true if @sz was set, %false otherwise. |
| */ |
| static bool file_read_size(struct transaction* tr, |
| const struct block_mac* block_mac, |
| data_block_t* sz) { |
| const struct file_entry* file_entry_ro; |
| struct obj_ref ref = OBJ_REF_INITIAL_VALUE(ref); |
| |
| if (!block_mac_valid(tr, block_mac)) { |
| *sz = 0; |
| return true; |
| } |
| |
| file_entry_ro = block_get_no_tr_fail(tr, block_mac, NULL, &ref); |
| if (!file_entry_ro) { |
| return false; |
| } |
| *sz = file_entry_ro->info.size; |
| block_put(file_entry_ro, &ref); |
| return true; |
| } |
| |
| /** |
| * file_transaction_failed - Restore file handles after failed transaction |
| * @tr: Transaction object. |
| * |
| * Revert all file handles modified by @tr to the state of the files before @tr |
| * was activeted. File handles for files crreated by @tr become invalid. |
| */ |
| void file_transaction_failed(struct transaction* tr) { |
| struct storage_file_handle* file; |
| bool success; |
| |
| list_for_every_entry(&tr->open_files, file, struct storage_file_handle, |
| node) { |
| file->used_by_tr = false; |
| if (transaction_changed_file(tr, file)) { |
| file->block_mac = file->committed_block_mac; |
| success = file_read_size(tr, &file->block_mac, &file->size); |
| if (!success) { |
| pr_warn("failed to read block %" PRIu64 ", clear file handle\n", |
| block_mac_to_block(tr, &file->block_mac)); |
| block_mac_clear(tr, &file->block_mac); |
| block_mac_clear(tr, &file->committed_block_mac); |
| file->size = 0; |
| } |
| } |
| } |
| } |
| |
| /** |
| * file_rebuild_free_set - Remove a file's blocks from the input set |
| * @tr: Transaction object |
| * @new_free_set: Free set to modify |
| * @file: File handle whose blocks should be removed from |
| * @new_free_set |
| * |
| * Update @new_free_set by removing from it all blocks referenced by @file, i.e. |
| * the file metadata block, file block map nodes, and file data blocks. Caller |
| * must ensure that @new_free_set contained all these referenced blocks before |
| * calling this function. |
| */ |
| static void file_rebuild_free_set(struct transaction* tr, |
| struct block_set* new_free_set, |
| struct storage_file_handle* file) { |
| struct block_map block_map; |
| data_block_t block; |
| struct block_tree_path path; |
| int i; |
| |
| block_set_remove_block(tr, new_free_set, |
| block_mac_to_block(tr, &file->block_mac)); |
| |
| file_block_map_init(tr, &block_map, &file->block_mac); |
| block_tree_walk(tr, &block_map.tree, 0, true, &path); |
| while (block_tree_path_get_key(&path)) { |
| /* |
| * We only remove a tree node the first time we see it. Because we walk |
| * the tree in order, the first time we see a node we will visit its |
| * lowest child i.e. path.entry[i].index == 0. When index > 0 we have |
| * already visited and removed all parent nodes. |
| */ |
| for (i = path.count - 1; i >= 0 && path.entry[i].index == 0; i--) { |
| block = block_mac_to_block(tr, &path.entry[i].block_mac); |
| block_set_remove_block(tr, new_free_set, block); |
| } |
| block_set_remove_block(tr, new_free_set, |
| block_tree_path_get_data(&path)); |
| block_tree_path_next(&path); |
| } |
| } |
| |
| /** |
| * files_rebuild_free_set - Rebuild a free set from the files tree |
| * @tr: Transaction object |
| * @new_free_set: Free set to modify |
| * @files_root: Root block and mac of the files tree |
| * |
| * Update @new_free_set by removing from it all blocks referenced in the file |
| * tree rooted at @files_root, i.e. all files tree nodes, file metadata blocks, |
| * file block map nodes, and file data blocks. Caller must ensure that |
| * @new_free_set contained all these referenced blocks before calling this |
| * function. |
| */ |
| void files_rebuild_free_set(struct transaction* tr, |
| struct block_set* new_free_set, |
| struct block_mac* files_root) { |
| struct block_tree files; |
| data_block_t block; |
| struct block_tree_path path; |
| int i; |
| struct storage_file_handle file; |
| |
| fs_file_tree_init(tr->fs, &files); |
| block_mac_copy(tr, &files.root, files_root); |
| block_tree_walk(tr, &files, 0, true, &path); |
| if (!block_tree_path_get_key(&path)) { |
| /* No files, so we just need to track the tree root. */ |
| block_set_remove_block(tr, new_free_set, |
| block_mac_to_block(tr, files_root)); |
| return; |
| } |
| while (block_tree_path_get_key(&path)) { |
| /* |
| * We only remove a tree node the first time we see it. Because we walk |
| * the tree in order, the first time we see a node we will visit its |
| * lowest child i.e. path.entry[i].index == 0. When index > 0 we have |
| * already visited and removed all parent nodes. |
| */ |
| for (i = path.count - 1; i >= 0 && path.entry[i].index == 0; i--) { |
| block = block_mac_to_block(tr, &path.entry[i].block_mac); |
| block_set_remove_block(tr, new_free_set, block); |
| } |
| file.block_mac = block_tree_path_get_data_block_mac(&path); |
| file_rebuild_free_set(tr, new_free_set, &file); |
| block_tree_path_next(&path); |
| } |
| } |