| // SPDX-License-Identifier: GPL-2.0 |
| |
| #include <linux/crc32.h> |
| #include "messages.h" |
| #include "fs.h" |
| #include "accessors.h" |
| #include "volumes.h" |
| |
| static const struct btrfs_csums { |
| u16 size; |
| const char name[10]; |
| } btrfs_csums[] = { |
| [BTRFS_CSUM_TYPE_CRC32] = { .size = 4, .name = "crc32c" }, |
| [BTRFS_CSUM_TYPE_XXHASH] = { .size = 8, .name = "xxhash64" }, |
| [BTRFS_CSUM_TYPE_SHA256] = { .size = 32, .name = "sha256" }, |
| [BTRFS_CSUM_TYPE_BLAKE2] = { .size = 32, .name = "blake2b" }, |
| }; |
| |
| /* This exists for btrfs-progs usages. */ |
| u16 btrfs_csum_type_size(u16 type) |
| { |
| return btrfs_csums[type].size; |
| } |
| |
| int btrfs_super_csum_size(const struct btrfs_super_block *s) |
| { |
| u16 t = btrfs_super_csum_type(s); |
| |
| /* csum type is validated at mount time. */ |
| return btrfs_csum_type_size(t); |
| } |
| |
| const char *btrfs_super_csum_name(u16 csum_type) |
| { |
| /* csum type is validated at mount time. */ |
| return btrfs_csums[csum_type].name; |
| } |
| |
| size_t __attribute_const__ btrfs_get_num_csums(void) |
| { |
| return ARRAY_SIZE(btrfs_csums); |
| } |
| |
| void btrfs_csum(u16 csum_type, const u8 *data, size_t len, u8 *out) |
| { |
| switch (csum_type) { |
| case BTRFS_CSUM_TYPE_CRC32: |
| put_unaligned_le32(~crc32c(~0, data, len), out); |
| break; |
| case BTRFS_CSUM_TYPE_XXHASH: |
| put_unaligned_le64(xxh64(data, len, 0), out); |
| break; |
| case BTRFS_CSUM_TYPE_SHA256: |
| sha256(data, len, out); |
| break; |
| case BTRFS_CSUM_TYPE_BLAKE2: |
| blake2b(NULL, 0, data, len, out, 32); |
| break; |
| default: |
| /* Checksum type is validated at mount time. */ |
| BUG(); |
| } |
| } |
| |
| void btrfs_csum_init(struct btrfs_csum_ctx *ctx, u16 csum_type) |
| { |
| ctx->csum_type = csum_type; |
| switch (ctx->csum_type) { |
| case BTRFS_CSUM_TYPE_CRC32: |
| ctx->crc32 = ~0; |
| break; |
| case BTRFS_CSUM_TYPE_XXHASH: |
| xxh64_reset(&ctx->xxh64, 0); |
| break; |
| case BTRFS_CSUM_TYPE_SHA256: |
| sha256_init(&ctx->sha256); |
| break; |
| case BTRFS_CSUM_TYPE_BLAKE2: |
| blake2b_init(&ctx->blake2b, 32); |
| break; |
| default: |
| /* Checksume type is validated at mount time. */ |
| BUG(); |
| } |
| } |
| |
| void btrfs_csum_update(struct btrfs_csum_ctx *ctx, const u8 *data, size_t len) |
| { |
| switch (ctx->csum_type) { |
| case BTRFS_CSUM_TYPE_CRC32: |
| ctx->crc32 = crc32c(ctx->crc32, data, len); |
| break; |
| case BTRFS_CSUM_TYPE_XXHASH: |
| xxh64_update(&ctx->xxh64, data, len); |
| break; |
| case BTRFS_CSUM_TYPE_SHA256: |
| sha256_update(&ctx->sha256, data, len); |
| break; |
| case BTRFS_CSUM_TYPE_BLAKE2: |
| blake2b_update(&ctx->blake2b, data, len); |
| break; |
| default: |
| /* Checksum type is validated at mount time. */ |
| BUG(); |
| } |
| } |
| |
| void btrfs_csum_final(struct btrfs_csum_ctx *ctx, u8 *out) |
| { |
| switch (ctx->csum_type) { |
| case BTRFS_CSUM_TYPE_CRC32: |
| put_unaligned_le32(~ctx->crc32, out); |
| break; |
| case BTRFS_CSUM_TYPE_XXHASH: |
| put_unaligned_le64(xxh64_digest(&ctx->xxh64), out); |
| break; |
| case BTRFS_CSUM_TYPE_SHA256: |
| sha256_final(&ctx->sha256, out); |
| break; |
| case BTRFS_CSUM_TYPE_BLAKE2: |
| blake2b_final(&ctx->blake2b, out); |
| break; |
| default: |
| /* Checksum type is validated at mount time. */ |
| BUG(); |
| } |
| } |
| |
| /* |
| * We support the following block sizes for all systems: |
| * |
| * - 4K |
| * This is the most common block size. For PAGE SIZE > 4K cases the subpage |
| * mode is used. |
| * |
| * - PAGE_SIZE |
| * The straightforward block size to support. |
| * |
| * And extra support for the following block sizes based on the kernel config: |
| * |
| * - MIN_BLOCKSIZE |
| * This is either 4K (regular builds) or 2K (debug builds) |
| * This allows testing subpage routines on x86_64. |
| */ |
| bool __attribute_const__ btrfs_supported_blocksize(u32 blocksize) |
| { |
| /* @blocksize should be validated first. */ |
| ASSERT(is_power_of_2(blocksize) && blocksize >= BTRFS_MIN_BLOCKSIZE && |
| blocksize <= BTRFS_MAX_BLOCKSIZE); |
| |
| if (blocksize == PAGE_SIZE || blocksize == SZ_4K || blocksize == BTRFS_MIN_BLOCKSIZE) |
| return true; |
| #ifdef CONFIG_BTRFS_EXPERIMENTAL |
| /* |
| * For bs > ps support it's done by specifying a minimal folio order |
| * for filemap, thus implying large data folios. |
| * For HIGHMEM systems, we can not always access the content of a (large) |
| * folio in one go, but go through them page by page. |
| * |
| * A lot of features don't implement a proper PAGE sized loop for large |
| * folios, this includes: |
| * |
| * - compression |
| * - verity |
| * - encoded write |
| * |
| * Considering HIGHMEM is such a pain to deal with and it's going |
| * to be deprecated eventually, just reject HIGHMEM && bs > ps cases. |
| */ |
| if (IS_ENABLED(CONFIG_HIGHMEM) && blocksize > PAGE_SIZE) |
| return false; |
| return true; |
| #endif |
| return false; |
| } |
| |
| /* |
| * Start exclusive operation @type, return true on success. |
| */ |
| bool btrfs_exclop_start(struct btrfs_fs_info *fs_info, |
| enum btrfs_exclusive_operation type) |
| { |
| bool ret = false; |
| |
| spin_lock(&fs_info->super_lock); |
| if (fs_info->exclusive_operation == BTRFS_EXCLOP_NONE) { |
| fs_info->exclusive_operation = type; |
| ret = true; |
| } |
| spin_unlock(&fs_info->super_lock); |
| |
| return ret; |
| } |
| |
| /* |
| * Conditionally allow to enter the exclusive operation in case it's compatible |
| * with the running one. This must be paired with btrfs_exclop_start_unlock() |
| * and btrfs_exclop_finish(). |
| * |
| * Compatibility: |
| * - the same type is already running |
| * - when trying to add a device and balance has been paused |
| * - not BTRFS_EXCLOP_NONE - this is intentionally incompatible and the caller |
| * must check the condition first that would allow none -> @type |
| */ |
| bool btrfs_exclop_start_try_lock(struct btrfs_fs_info *fs_info, |
| enum btrfs_exclusive_operation type) |
| { |
| spin_lock(&fs_info->super_lock); |
| if (fs_info->exclusive_operation == type || |
| (fs_info->exclusive_operation == BTRFS_EXCLOP_BALANCE_PAUSED && |
| type == BTRFS_EXCLOP_DEV_ADD)) |
| return true; |
| |
| spin_unlock(&fs_info->super_lock); |
| return false; |
| } |
| |
| void btrfs_exclop_start_unlock(struct btrfs_fs_info *fs_info) |
| { |
| spin_unlock(&fs_info->super_lock); |
| } |
| |
| void btrfs_exclop_finish(struct btrfs_fs_info *fs_info) |
| { |
| spin_lock(&fs_info->super_lock); |
| WRITE_ONCE(fs_info->exclusive_operation, BTRFS_EXCLOP_NONE); |
| spin_unlock(&fs_info->super_lock); |
| sysfs_notify(&fs_info->fs_devices->fsid_kobj, NULL, "exclusive_operation"); |
| } |
| |
| void btrfs_exclop_balance(struct btrfs_fs_info *fs_info, |
| enum btrfs_exclusive_operation op) |
| { |
| switch (op) { |
| case BTRFS_EXCLOP_BALANCE_PAUSED: |
| spin_lock(&fs_info->super_lock); |
| ASSERT(fs_info->exclusive_operation == BTRFS_EXCLOP_BALANCE || |
| fs_info->exclusive_operation == BTRFS_EXCLOP_DEV_ADD || |
| fs_info->exclusive_operation == BTRFS_EXCLOP_NONE || |
| fs_info->exclusive_operation == BTRFS_EXCLOP_BALANCE_PAUSED); |
| fs_info->exclusive_operation = BTRFS_EXCLOP_BALANCE_PAUSED; |
| spin_unlock(&fs_info->super_lock); |
| break; |
| case BTRFS_EXCLOP_BALANCE: |
| spin_lock(&fs_info->super_lock); |
| ASSERT(fs_info->exclusive_operation == BTRFS_EXCLOP_BALANCE_PAUSED); |
| fs_info->exclusive_operation = BTRFS_EXCLOP_BALANCE; |
| spin_unlock(&fs_info->super_lock); |
| break; |
| default: |
| btrfs_warn(fs_info, |
| "invalid exclop balance operation %d requested", op); |
| } |
| } |
| |
| void __btrfs_set_fs_incompat(struct btrfs_fs_info *fs_info, u64 flag, |
| const char *name) |
| { |
| struct btrfs_super_block *disk_super; |
| u64 features; |
| |
| disk_super = fs_info->super_copy; |
| features = btrfs_super_incompat_flags(disk_super); |
| if (!(features & flag)) { |
| spin_lock(&fs_info->super_lock); |
| features = btrfs_super_incompat_flags(disk_super); |
| if (!(features & flag)) { |
| features |= flag; |
| btrfs_set_super_incompat_flags(disk_super, features); |
| btrfs_info(fs_info, |
| "setting incompat feature flag for %s (0x%llx)", |
| name, flag); |
| } |
| spin_unlock(&fs_info->super_lock); |
| set_bit(BTRFS_FS_FEATURE_CHANGED, &fs_info->flags); |
| } |
| } |
| |
| void __btrfs_clear_fs_incompat(struct btrfs_fs_info *fs_info, u64 flag, |
| const char *name) |
| { |
| struct btrfs_super_block *disk_super; |
| u64 features; |
| |
| disk_super = fs_info->super_copy; |
| features = btrfs_super_incompat_flags(disk_super); |
| if (features & flag) { |
| spin_lock(&fs_info->super_lock); |
| features = btrfs_super_incompat_flags(disk_super); |
| if (features & flag) { |
| features &= ~flag; |
| btrfs_set_super_incompat_flags(disk_super, features); |
| btrfs_info(fs_info, |
| "clearing incompat feature flag for %s (0x%llx)", |
| name, flag); |
| } |
| spin_unlock(&fs_info->super_lock); |
| set_bit(BTRFS_FS_FEATURE_CHANGED, &fs_info->flags); |
| } |
| } |
| |
| void __btrfs_set_fs_compat_ro(struct btrfs_fs_info *fs_info, u64 flag, |
| const char *name) |
| { |
| struct btrfs_super_block *disk_super; |
| u64 features; |
| |
| disk_super = fs_info->super_copy; |
| features = btrfs_super_compat_ro_flags(disk_super); |
| if (!(features & flag)) { |
| spin_lock(&fs_info->super_lock); |
| features = btrfs_super_compat_ro_flags(disk_super); |
| if (!(features & flag)) { |
| features |= flag; |
| btrfs_set_super_compat_ro_flags(disk_super, features); |
| btrfs_info(fs_info, |
| "setting compat-ro feature flag for %s (0x%llx)", |
| name, flag); |
| } |
| spin_unlock(&fs_info->super_lock); |
| set_bit(BTRFS_FS_FEATURE_CHANGED, &fs_info->flags); |
| } |
| } |
| |
| void __btrfs_clear_fs_compat_ro(struct btrfs_fs_info *fs_info, u64 flag, |
| const char *name) |
| { |
| struct btrfs_super_block *disk_super; |
| u64 features; |
| |
| disk_super = fs_info->super_copy; |
| features = btrfs_super_compat_ro_flags(disk_super); |
| if (features & flag) { |
| spin_lock(&fs_info->super_lock); |
| features = btrfs_super_compat_ro_flags(disk_super); |
| if (features & flag) { |
| features &= ~flag; |
| btrfs_set_super_compat_ro_flags(disk_super, features); |
| btrfs_info(fs_info, |
| "clearing compat-ro feature flag for %s (0x%llx)", |
| name, flag); |
| } |
| spin_unlock(&fs_info->super_lock); |
| set_bit(BTRFS_FS_FEATURE_CHANGED, &fs_info->flags); |
| } |
| } |