diff --git a/Documentation/mkfs.btrfs.rst b/Documentation/mkfs.btrfs.rst index 77c37553a..f70b6ebe2 100644 --- a/Documentation/mkfs.btrfs.rst +++ b/Documentation/mkfs.btrfs.rst @@ -214,8 +214,8 @@ OPTIONS --compress : Try to compress files when using *--rootdir*. Supported values for *algo* are - *no* (the default) and *zlib*. The optional value *level* is a - compression level, from 1 to 9 for ZLIB. + *no* (the default), *zlib*, and *zstd*. The optional value *level* is a + compression level, from 1 to 9 for ZLIB and from 1 to 15 for ZSTD. As with the kernel, :command:`mkfs.btrfs` won't write compressed extents when they would be larger than the uncompressed versions, and will mark a file as diff --git a/mkfs/main.c b/mkfs/main.c index ffd7cb427..91b338db4 100644 --- a/mkfs/main.c +++ b/mkfs/main.c @@ -443,7 +443,7 @@ static const char * const mkfs_usage[] = { OPTLINE("-u|--subvol TYPE:SUBDIR", "create SUBDIR as subvolume rather than normal directory, can be specified multiple times"), OPTLINE("--shrink", "(with --rootdir) shrink the filled filesystem to minimal size"), OPTLINE("-K|--nodiscard", "do not perform whole device TRIM"), - OPTLINE("--compress ALGO:LEVEL", "compression algorithm and level to use; ALGO can be no (default), zlib"), + OPTLINE("--compress ALGO:LEVEL", "compression algorithm and level to use; ALGO can be no (default), zlib, zstd"), OPTLINE("-f|--force", "force overwrite of existing filesystem"), "General:", OPTLINE("-q|--quiet", "no messages except errors"), diff --git a/mkfs/rootdir.c b/mkfs/rootdir.c index fd06bb478..4b1f6d6ee 100644 --- a/mkfs/rootdir.c +++ b/mkfs/rootdir.c @@ -27,6 +27,10 @@ #include #include #include +#if COMPRESSION_ZSTD +#include +#include +#endif #include #include "kernel-lib/sizes.h" #include "kernel-shared/accessors.h" @@ -49,6 +53,9 @@ #define ZLIB_BTRFS_DEFAULT_LEVEL 3 #define ZLIB_BTRFS_MAX_LEVEL 9 +#define ZSTD_BTRFS_DEFAULT_LEVEL 3 +#define ZSTD_BTRFS_MAX_LEVEL 15 + static u32 fs_block_size; /* @@ -438,6 +445,107 @@ static ssize_t zlib_compress_extent(struct btrfs_inode_item *btrfs_inode, return 0; } +#if COMPRESSION_ZSTD +static ssize_t zstd_compress_extent(struct btrfs_inode_item *btrfs_inode, + u32 sectorsize, const void *in_buf, + size_t in_size, void *out_buf) +{ + ZSTD_CCtx *zstd_ctx; + ZSTD_inBuffer input; + ZSTD_outBuffer output; + size_t zstd_ret; + ssize_t ret; + + zstd_ctx = ZSTD_createCCtx(); + if (!zstd_ctx) { + error_msg(ERROR_MSG_MEMORY, NULL); + return -ENOMEM; + } + + zstd_ret = ZSTD_CCtx_setParameter(zstd_ctx, ZSTD_c_compressionLevel, + g_compression_level); + if (ZSTD_isError(zstd_ret)) { + error("ZSTD_CCtx_setParameter failed: %s", + ZSTD_getErrorName(zstd_ret)); + ret = -EINVAL; + goto out; + } + + zstd_ret = ZSTD_CCtx_setPledgedSrcSize(zstd_ctx, in_size); + if (ZSTD_isError(zstd_ret)) { + error("ZSTD_CCtx_setPledgedSrcSize failed: %s", + ZSTD_getErrorName(zstd_ret)); + ret = -EINVAL; + goto out; + } + + output.dst = out_buf; + output.size = BTRFS_MAX_COMPRESSED; + output.pos = 0; + + input.src = in_buf; + input.pos = 0; + + /* Try to compress the first sector - if it would be larger, mark the + * inode as nocompress and return. */ + if (!(btrfs_stack_inode_flags(btrfs_inode) & BTRFS_INODE_COMPRESS)) { + input.size = sectorsize; + + zstd_ret = ZSTD_compressStream2(zstd_ctx, &output, &input, + ZSTD_e_flush); + + if (ZSTD_isError(zstd_ret)) { + error("ZSTD_compressStream2 failed: %s", + ZSTD_getErrorName(zstd_ret)); + ret = -EINVAL; + goto out; + } + + if (zstd_ret != 0 || output.pos > sectorsize) { + u64 flags; + + flags = btrfs_stack_inode_flags(btrfs_inode); + flags |= BTRFS_INODE_NOCOMPRESS; + btrfs_set_stack_inode_flags(btrfs_inode, flags); + + ret = 0; + goto out; + } + } + + input.size = in_size; + + zstd_ret = ZSTD_compressStream2(zstd_ctx, &output, &input, ZSTD_e_end); + + if (ZSTD_isError(zstd_ret)) { + error("ZSTD_compressStream2 failed: %s", + ZSTD_getErrorName(zstd_ret)); + ret = -EINVAL; + goto out; + } + + if (zstd_ret == 0 && output.pos <= in_size - sectorsize) { + ret = output.pos; + goto out; + } + + if (!(btrfs_stack_inode_flags(btrfs_inode) & BTRFS_INODE_COMPRESS)) { + u64 flags; + + flags = btrfs_stack_inode_flags(btrfs_inode); + flags |= BTRFS_INODE_NOCOMPRESS; + btrfs_set_stack_inode_flags(btrfs_inode, flags); + } + + ret = 0; + +out: + ZSTD_freeCCtx(zstd_ctx); + + return ret; +} +#endif + /* * keep our extent size at 1MB max, this makes it easier to work * inside the tiny block groups created during mkfs @@ -493,6 +601,13 @@ static int read_and_write_extent(struct btrfs_trans_handle *trans, buf, bytes_read, comp_buf); break; +#if COMPRESSION_ZSTD + case BTRFS_COMPRESS_ZSTD: + comp_ret = zstd_compress_extent(btrfs_inode, sectorsize, + buf, bytes_read, + comp_buf); + break; +#endif default: comp_ret = -EINVAL; break; @@ -528,7 +643,7 @@ static int read_and_write_extent(struct btrfs_trans_handle *trans, } if (do_comp) { - u64 flags; + u64 flags, features; to_write = round_up(comp_ret, sectorsize); write_buf = comp_buf; @@ -537,6 +652,13 @@ static int read_and_write_extent(struct btrfs_trans_handle *trans, flags = btrfs_stack_inode_flags(btrfs_inode); flags |= BTRFS_INODE_COMPRESS; btrfs_set_stack_inode_flags(btrfs_inode, flags); + + if (g_compression == BTRFS_COMPRESS_ZSTD) { + features = btrfs_super_incompat_flags(trans->fs_info->super_copy); + features |= BTRFS_FEATURE_INCOMPAT_COMPRESS_ZSTD; + btrfs_set_super_incompat_flags(trans->fs_info->super_copy, + features); + } } else { to_write = round_up(to_read, sectorsize); write_buf = buf; @@ -632,6 +754,82 @@ static int zlib_compress_inline_extent(char *buf, u64 size, char **comp_buf, return ret; } +#if COMPRESSION_ZSTD +static int zstd_compress_inline_extent(char *buf, u64 size, char **comp_buf, + u64 *comp_size) +{ + ZSTD_CCtx *zstd_ctx; + ZSTD_inBuffer input; + ZSTD_outBuffer output; + size_t zstd_ret; + ssize_t ret; + char *out = NULL; + + zstd_ctx = ZSTD_createCCtx(); + if (!zstd_ctx) { + error_msg(ERROR_MSG_MEMORY, NULL); + return -ENOMEM; + } + + zstd_ret = ZSTD_CCtx_setParameter(zstd_ctx, ZSTD_c_compressionLevel, + g_compression_level); + if (ZSTD_isError(zstd_ret)) { + error("ZSTD_CCtx_setParameter failed: %s", + ZSTD_getErrorName(zstd_ret)); + ret = -EINVAL; + goto out; + } + + zstd_ret = ZSTD_CCtx_setPledgedSrcSize(zstd_ctx, size); + if (ZSTD_isError(zstd_ret)) { + error("ZSTD_CCtx_setPledgedSrcSize failed: %s", + ZSTD_getErrorName(zstd_ret)); + ret = -EINVAL; + goto out; + } + + out = malloc(size); + if (!out) { + error_msg(ERROR_MSG_MEMORY, NULL); + ret = -ENOMEM; + goto out; + } + + output.dst = out; + output.size = size; + output.pos = 0; + + input.src = buf; + input.pos = 0; + input.size = size; + + zstd_ret = ZSTD_compressStream2(zstd_ctx, &output, &input, ZSTD_e_end); + + if (ZSTD_isError(zstd_ret)) { + error("ZSTD_compressStream2 failed: %s", + ZSTD_getErrorName(zstd_ret)); + ret = -EINVAL; + goto out; + } + + if (zstd_ret == 0 && output.pos < size) { + ret = 1; + *comp_buf = out; + *comp_size = output.pos; + } else { + ret = 0; + } + +out: + if (ret != 1) + free(out); + + ZSTD_freeCCtx(zstd_ctx); + + return ret; +} +#endif + static int add_file_items(struct btrfs_trans_handle *trans, struct btrfs_root *root, struct btrfs_inode_item *btrfs_inode, u64 objectid, @@ -679,6 +877,14 @@ static int add_file_items(struct btrfs_trans_handle *trans, if (ret < 0) goto end; break; +#if COMPRESSION_ZSTD + case BTRFS_COMPRESS_ZSTD: + ret = zstd_compress_inline_extent(buffer, st->st_size, + &comp_buf, &comp_size); + if (ret < 0) + goto end; + break; +#endif default: ret = 0; break; @@ -1153,6 +1359,17 @@ int btrfs_mkfs_fill_dir(struct btrfs_trans_handle *trans, const char *source_dir else if (compression_level == 0) compression_level = ZLIB_BTRFS_DEFAULT_LEVEL; break; + case BTRFS_COMPRESS_ZSTD: +#if !COMPRESSION_ZSTD + error("zstd support not compiled in"); + return -EINVAL; +#else + if (compression_level > ZSTD_BTRFS_MAX_LEVEL) + compression_level = ZSTD_BTRFS_MAX_LEVEL; + else if (compression_level == 0) + compression_level = ZSTD_BTRFS_DEFAULT_LEVEL; + break; +#endif default: error("unsupported compression type"); return -EINVAL;