From 95a43fc99f147282c64e68baf1ea18041c7755ea Mon Sep 17 00:00:00 2001 From: Paul Lawrence Date: Wed, 14 Oct 2020 07:42:33 -0700 Subject: [PATCH] ANDROID: Incremental fs: Add zstd compression support Bug: 160634783 Test: incfs_test passes Signed-off-by: Paul Lawrence Change-Id: Iba28b535d2d5183859ffc721204b036434132d9b --- fs/incfs/Kconfig | 1 + fs/incfs/data_mgmt.c | 94 +++++++++++++++++-- fs/incfs/data_mgmt.h | 7 ++ fs/incfs/format.h | 6 +- include/uapi/linux/incrementalfs.h | 3 +- .../selftests/filesystems/incfs/Makefile | 2 +- .../selftests/filesystems/incfs/incfs_test.c | 34 ++++++- 7 files changed, 131 insertions(+), 16 deletions(-) diff --git a/fs/incfs/Kconfig b/fs/incfs/Kconfig index 8c0e8ae2030f..5f1500072cd7 100644 --- a/fs/incfs/Kconfig +++ b/fs/incfs/Kconfig @@ -2,6 +2,7 @@ config INCREMENTAL_FS tristate "Incremental file system support" depends on BLOCK select DECOMPRESS_LZ4 + select DECOMPRESS_ZSTD select CRYPTO_SHA256 help Incremental FS is a read-only virtual file system that facilitates execution diff --git a/fs/incfs/data_mgmt.c b/fs/incfs/data_mgmt.c index b6a8b47b4f6d..48ab7428d627 100644 --- a/fs/incfs/data_mgmt.c +++ b/fs/incfs/data_mgmt.c @@ -28,6 +28,19 @@ static void log_wake_up_all(struct work_struct *work) wake_up_all(&rl->ml_notif_wq); } +static void zstd_free_workspace(struct work_struct *work) +{ + struct delayed_work *dw = container_of(work, struct delayed_work, work); + struct mount_info *mi = + container_of(dw, struct mount_info, mi_zstd_cleanup_work); + + mutex_lock(&mi->mi_zstd_workspace_mutex); + kvfree(mi->mi_zstd_workspace); + mi->mi_zstd_workspace = NULL; + mi->mi_zstd_stream = NULL; + mutex_unlock(&mi->mi_zstd_workspace_mutex); +} + struct mount_info *incfs_alloc_mount_info(struct super_block *sb, struct mount_options *options, struct path *backing_dir_path) @@ -53,6 +66,8 @@ struct mount_info *incfs_alloc_mount_info(struct super_block *sb, spin_lock_init(&mi->pending_read_lock); INIT_LIST_HEAD(&mi->mi_reads_list_head); spin_lock_init(&mi->mi_per_uid_read_timeouts_lock); + mutex_init(&mi->mi_zstd_workspace_mutex); + INIT_DELAYED_WORK(&mi->mi_zstd_cleanup_work, zstd_free_workspace); error = incfs_realloc_mount_info(mi, options); if (error) @@ -112,11 +127,13 @@ void incfs_free_mount_info(struct mount_info *mi) return; flush_delayed_work(&mi->mi_log.ml_wakeup_work); + flush_delayed_work(&mi->mi_zstd_cleanup_work); dput(mi->mi_index_dir); dput(mi->mi_incomplete_dir); path_put(&mi->mi_backing_dir_path); mutex_destroy(&mi->mi_dir_struct_mutex); + mutex_destroy(&mi->mi_zstd_workspace_mutex); put_cred(mi->mi_owner); kfree(mi->mi_log.rl_ring_buf); kfree(mi->log_xattr); @@ -357,16 +374,73 @@ void incfs_free_dir_file(struct dir_file *dir) kfree(dir); } -static ssize_t decompress(struct mem_range src, struct mem_range dst) +static ssize_t zstd_decompress_safe(struct mount_info *mi, + struct mem_range src, struct mem_range dst) { - int result = LZ4_decompress_safe(src.data, dst.data, src.len, dst.len); + ssize_t result; + ZSTD_inBuffer inbuf = {.src = src.data, .size = src.len}; + ZSTD_outBuffer outbuf = {.dst = dst.data, .size = dst.len}; - if (result < 0) - return -EBADMSG; + result = mutex_lock_interruptible(&mi->mi_zstd_workspace_mutex); + if (result) + return result; + if (!mi->mi_zstd_stream) { + unsigned int workspace_size = ZSTD_DStreamWorkspaceBound( + INCFS_DATA_FILE_BLOCK_SIZE); + void *workspace = kvmalloc(workspace_size, GFP_NOFS); + ZSTD_DStream *stream; + + if (!workspace) { + result = -ENOMEM; + goto out; + } + + stream = ZSTD_initDStream(INCFS_DATA_FILE_BLOCK_SIZE, workspace, + workspace_size); + if (!stream) { + kvfree(workspace); + result = -EIO; + goto out; + } + + mi->mi_zstd_workspace = workspace; + mi->mi_zstd_stream = stream; + } + + result = ZSTD_decompressStream(mi->mi_zstd_stream, &outbuf, &inbuf) ? + -EBADMSG : outbuf.pos; + + mod_delayed_work(system_wq, &mi->mi_zstd_cleanup_work, + msecs_to_jiffies(5000)); + +out: + mutex_unlock(&mi->mi_zstd_workspace_mutex); return result; } +static ssize_t decompress(struct mount_info *mi, + struct mem_range src, struct mem_range dst, int alg) +{ + int result; + + switch (alg) { + case INCFS_BLOCK_COMPRESSED_LZ4: + result = LZ4_decompress_safe(src.data, dst.data, src.len, + dst.len); + if (result < 0) + return -EBADMSG; + return result; + + case INCFS_BLOCK_COMPRESSED_ZSTD: + return zstd_decompress_safe(mi, src, dst); + + default: + WARN_ON(true); + return -EOPNOTSUPP; + } +} + static void log_read_one_record(struct read_log *rl, struct read_log_state *rs) { union log_record *record = @@ -636,9 +710,7 @@ static void convert_data_file_block(struct incfs_blockmap_entry *bme, res_block->db_backing_file_data_offset |= le32_to_cpu(bme->me_data_offset_lo); res_block->db_stored_size = le16_to_cpu(bme->me_data_size); - res_block->db_comp_alg = (flags & INCFS_BLOCK_COMPRESSED_LZ4) ? - COMPRESSION_LZ4 : - COMPRESSION_NONE; + res_block->db_comp_alg = flags & INCFS_BLOCK_COMPRESSED_MASK; } static int get_data_file_block(struct data_file *df, int index, @@ -1089,7 +1161,8 @@ ssize_t incfs_read_data_file_block(struct mem_range dst, struct file *f, result = incfs_kread(bf, tmp.data, bytes_to_read, pos); if (result == bytes_to_read) { result = - decompress(range(tmp.data, bytes_to_read), dst); + decompress(mi, range(tmp.data, bytes_to_read), + dst, block.db_comp_alg); if (result < 0) { const char *name = bf->f_path.dentry->d_name.name; @@ -1139,8 +1212,13 @@ int incfs_process_new_data_block(struct data_file *df, segment = get_file_segment(df, block->block_index); if (!segment) return -EFAULT; + if (block->compression == COMPRESSION_LZ4) flags |= INCFS_BLOCK_COMPRESSED_LZ4; + else if (block->compression == COMPRESSION_ZSTD) + flags |= INCFS_BLOCK_COMPRESSED_ZSTD; + else if (block->compression) + return -EINVAL; error = down_read_killable(&segment->rwsem); if (error) diff --git a/fs/incfs/data_mgmt.h b/fs/incfs/data_mgmt.h index 7bbaf6ddbba4..a63af708fa6d 100644 --- a/fs/incfs/data_mgmt.h +++ b/fs/incfs/data_mgmt.h @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -166,6 +167,12 @@ struct mount_info { spinlock_t mi_per_uid_read_timeouts_lock; struct incfs_per_uid_read_timeouts *mi_per_uid_read_timeouts; int mi_per_uid_read_timeouts_size; + + /* zstd workspace */ + struct mutex mi_zstd_workspace_mutex; + void *mi_zstd_workspace; + ZSTD_DStream *mi_zstd_stream; + struct delayed_work mi_zstd_cleanup_work; }; struct data_file_block { diff --git a/fs/incfs/format.h b/fs/incfs/format.h index 7b991c35dd19..81e3c1a04ce5 100644 --- a/fs/incfs/format.h +++ b/fs/incfs/format.h @@ -195,7 +195,11 @@ struct incfs_file_header { } __packed; enum incfs_block_map_entry_flags { - INCFS_BLOCK_COMPRESSED_LZ4 = (1 << 0), + INCFS_BLOCK_COMPRESSED_LZ4 = 1, + INCFS_BLOCK_COMPRESSED_ZSTD = 2, + + /* Reserve 3 bits for compression alg */ + INCFS_BLOCK_COMPRESSED_MASK = 7, }; /* Block map entry pointing to an actual location of the data block. */ diff --git a/include/uapi/linux/incrementalfs.h b/include/uapi/linux/incrementalfs.h index 0cd6b3403222..482cebd67c0d 100644 --- a/include/uapi/linux/incrementalfs.h +++ b/include/uapi/linux/incrementalfs.h @@ -143,7 +143,8 @@ enum incfs_compression_alg { COMPRESSION_NONE = 0, - COMPRESSION_LZ4 = 1 + COMPRESSION_LZ4 = 1, + COMPRESSION_ZSTD = 2, }; enum incfs_block_flags { diff --git a/tools/testing/selftests/filesystems/incfs/Makefile b/tools/testing/selftests/filesystems/incfs/Makefile index 83e2ecb7bad9..f3798029247a 100644 --- a/tools/testing/selftests/filesystems/incfs/Makefile +++ b/tools/testing/selftests/filesystems/incfs/Makefile @@ -1,6 +1,6 @@ # SPDX-License-Identifier: GPL-2.0 CFLAGS += -D_FILE_OFFSET_BITS=64 -Wall -Werror -I../.. -I../../../../.. -LDLIBS := -llz4 -lcrypto -lpthread +LDLIBS := -llz4 -lzstd -lcrypto -lpthread TEST_GEN_PROGS := incfs_test incfs_stress incfs_perf include ../../lib.mk diff --git a/tools/testing/selftests/filesystems/incfs/incfs_test.c b/tools/testing/selftests/filesystems/incfs/incfs_test.c index 9abfb97425d7..8741a3d1795a 100644 --- a/tools/testing/selftests/filesystems/incfs/incfs_test.c +++ b/tools/testing/selftests/filesystems/incfs/incfs_test.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -321,6 +322,12 @@ static bool same_id(incfs_uuid_t *id1, incfs_uuid_t *id2) return !memcmp(id1->bytes, id2->bytes, sizeof(id1->bytes)); } +ssize_t ZSTD_compress_default(char *data, char *comp_data, size_t data_size, + size_t comp_size) +{ + return ZSTD_compress(comp_data, comp_size, data, data_size, 1); +} + static int emit_test_blocks(const char *mnt_dir, struct test_file *file, int blocks[], int count) { @@ -345,7 +352,8 @@ static int emit_test_blocks(const char *mnt_dir, struct test_file *file, for (i = 0; i < block_count; i++) { int block_index = blocks[i]; - bool compress = (file->index + block_index) % 2 == 0; + bool compress_zstd = (file->index + block_index) % 4 == 2; + bool compress_lz4 = (file->index + block_index) % 4 == 0; int seed = get_file_block_seed(file->index, block_index); off_t block_offset = ((off_t)block_index) * INCFS_DATA_FILE_BLOCK_SIZE; @@ -362,10 +370,10 @@ static int emit_test_blocks(const char *mnt_dir, struct test_file *file, block_size = file->size - block_offset; rnd_buf(data, block_size, seed); - if (compress) { - size_t comp_size = LZ4_compress_default( - (char *)data, (char *)comp_data, block_size, - ARRAY_SIZE(comp_data)); + if (compress_lz4) { + size_t comp_size = LZ4_compress_default((char *)data, + (char *)comp_data, block_size, + ARRAY_SIZE(comp_data)); if (comp_size <= 0) { error = -EBADMSG; @@ -378,6 +386,22 @@ static int emit_test_blocks(const char *mnt_dir, struct test_file *file, memcpy(current_data, comp_data, comp_size); block_size = comp_size; block_buf[i].compression = COMPRESSION_LZ4; + } else if (compress_zstd) { + size_t comp_size = ZSTD_compress(comp_data, + ARRAY_SIZE(comp_data), data, block_size, + 1); + + if (comp_size <= 0) { + error = -EBADMSG; + break; + } + if (current_data + comp_size > data_end) { + error = -ENOMEM; + break; + } + memcpy(current_data, comp_data, comp_size); + block_size = comp_size; + block_buf[i].compression = COMPRESSION_ZSTD; } else { if (current_data + block_size > data_end) { error = -ENOMEM;