From d14bbfff259cadb5af84413658699159556da156 Mon Sep 17 00:00:00 2001 From: Steve French Date: Fri, 26 Jul 2024 16:30:23 -0500 Subject: [PATCH] smb3: mark compression as CONFIG_EXPERIMENTAL and fix missing compression operation Move SMB3.1.1 compression code into experimental config option, and fix the compress mount option. Implement unchained LZ77 "plain" compression algorithm as per MS-XCA specification section "2.3 Plain LZ77 Compression Algorithm Details". Signed-off-by: Enzo Matsumiya Signed-off-by: Steve French --- fs/smb/client/Kconfig | 14 ++ fs/smb/client/Makefile | 2 + fs/smb/client/cifs_debug.c | 7 +- fs/smb/client/cifsglob.h | 3 +- fs/smb/client/compress.c | 50 ++++++ fs/smb/client/compress.h | 109 +++++++++++++ fs/smb/client/compress/lz77.c | 211 +++++++++++++++++++++++++ fs/smb/client/compress/lz77.h | 286 ++++++++++++++++++++++++++++++++++ fs/smb/client/fs_context.c | 7 +- 9 files changed, 685 insertions(+), 4 deletions(-) create mode 100644 fs/smb/client/compress.c create mode 100644 fs/smb/client/compress.h create mode 100644 fs/smb/client/compress/lz77.c create mode 100644 fs/smb/client/compress/lz77.h diff --git a/fs/smb/client/Kconfig b/fs/smb/client/Kconfig index 2517dc242386..2aff6d1395ce 100644 --- a/fs/smb/client/Kconfig +++ b/fs/smb/client/Kconfig @@ -204,4 +204,18 @@ config CIFS_ROOT Most people say N here. +config CIFS_COMPRESSION + bool "SMB message compression (Experimental)" + depends on CIFS + default n + help + Enables over-the-wire message compression for SMB 3.1.1 + mounts when negotiated with the server. + + Only write requests with data size >= PAGE_SIZE will be + compressed to avoid wasting resources. + + Say Y here if you want SMB traffic to be compressed. + If unsure, say N. + endif diff --git a/fs/smb/client/Makefile b/fs/smb/client/Makefile index e11985f2460b..22023e30915b 100644 --- a/fs/smb/client/Makefile +++ b/fs/smb/client/Makefile @@ -33,3 +33,5 @@ cifs-$(CONFIG_CIFS_SMB_DIRECT) += smbdirect.o cifs-$(CONFIG_CIFS_ROOT) += cifsroot.o cifs-$(CONFIG_CIFS_ALLOW_INSECURE_LEGACY) += smb1ops.o cifssmb.o + +cifs-$(CONFIG_CIFS_COMPRESSION) += compress.o compress/lz77.o diff --git a/fs/smb/client/cifs_debug.c b/fs/smb/client/cifs_debug.c index 4a20e92474b2..e03c890de0a0 100644 --- a/fs/smb/client/cifs_debug.c +++ b/fs/smb/client/cifs_debug.c @@ -349,6 +349,9 @@ static int cifs_debug_data_proc_show(struct seq_file *m, void *v) seq_printf(m, ",ACL"); #ifdef CONFIG_CIFS_SWN_UPCALL seq_puts(m, ",WITNESS"); +#endif +#ifdef CONFIG_CIFS_COMPRESSION + seq_puts(m, ",COMPRESSION"); #endif seq_putc(m, '\n'); seq_printf(m, "CIFSMaxBufSize: %d\n", CIFSMaxBufSize); @@ -475,7 +478,9 @@ skip_rdma: } seq_puts(m, "\nCompression: "); - if (!server->compression.requested) + if (!IS_ENABLED(CONFIG_CIFS_COMPRESSION)) + seq_puts(m, "no built-in support"); + else if (!server->compression.requested) seq_puts(m, "disabled on mount"); else if (server->compression.enabled) seq_printf(m, "enabled (%s)", compression_alg_str(server->compression.alg)); diff --git a/fs/smb/client/cifsglob.h b/fs/smb/client/cifsglob.h index 5249b7a9cd04..939ef5844571 100644 --- a/fs/smb/client/cifsglob.h +++ b/fs/smb/client/cifsglob.h @@ -555,7 +555,7 @@ struct smb_version_operations { bool (*dir_needs_close)(struct cifsFileInfo *); long (*fallocate)(struct file *, struct cifs_tcon *, int, loff_t, loff_t); - /* init transform request - used for encryption for now */ + /* init transform (compress/encrypt) request */ int (*init_transform_rq)(struct TCP_Server_Info *, int num_rqst, struct smb_rqst *, struct smb_rqst *); int (*is_transform_hdr)(void *buf); @@ -1876,6 +1876,7 @@ static inline bool is_replayable_error(int error) #define CIFS_HAS_CREDITS 0x0400 /* already has credits */ #define CIFS_TRANSFORM_REQ 0x0800 /* transform request before sending */ #define CIFS_NO_SRV_RSP 0x1000 /* there is no server response */ +#define CIFS_COMPRESS_REQ 0x4000 /* compress request before sending */ /* Security Flags: indicate type of session setup needed */ #define CIFSSEC_MAY_SIGN 0x00001 diff --git a/fs/smb/client/compress.c b/fs/smb/client/compress.c new file mode 100644 index 000000000000..4efbccbd40bf --- /dev/null +++ b/fs/smb/client/compress.c @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2024, SUSE LLC + * + * Authors: Enzo Matsumiya + * + * This file implements I/O compression support for SMB2 messages (SMB 3.1.1 only). + * See compress/ for implementation details of each algorithm. + * + * References: + * MS-SMB2 "3.1.4.4 Compressing the Message" + * MS-SMB2 "3.1.5.3 Decompressing the Chained Message" + * MS-XCA - for details of the supported algorithms + */ +#include +#include +#include + +#include "cifsglob.h" +#include "../common/smb2pdu.h" +#include "cifsproto.h" +#include "smb2proto.h" + +#include "compress/lz77.h" +#include "compress.h" + +int smb_compress(void *buf, const void *data, size_t *len) +{ + struct smb2_compression_hdr *hdr; + size_t buf_len, data_len; + int ret; + + buf_len = sizeof(struct smb2_write_req); + data_len = *len; + *len = 0; + + hdr = buf; + hdr->ProtocolId = SMB2_COMPRESSION_TRANSFORM_ID; + hdr->OriginalCompressedSegmentSize = cpu_to_le32(data_len); + hdr->Offset = cpu_to_le32(buf_len); + hdr->Flags = SMB2_COMPRESSION_FLAG_NONE; + hdr->CompressionAlgorithm = SMB3_COMPRESS_LZ77; + + /* XXX: add other algs here as they're implemented */ + ret = lz77_compress(data, data_len, buf + SMB_COMPRESS_HDR_LEN + buf_len, &data_len); + if (!ret) + *len = SMB_COMPRESS_HDR_LEN + buf_len + data_len; + + return ret; +} diff --git a/fs/smb/client/compress.h b/fs/smb/client/compress.h new file mode 100644 index 000000000000..c0dabe0a60d8 --- /dev/null +++ b/fs/smb/client/compress.h @@ -0,0 +1,109 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2024, SUSE LLC + * + * Authors: Enzo Matsumiya + * + * This file implements I/O compression support for SMB2 messages (SMB 3.1.1 only). + * See compress/ for implementation details of each algorithm. + * + * References: + * MS-SMB2 "3.1.4.4 Compressing the Message" - for compression details + * MS-SMB2 "3.1.5.3 Decompressing the Chained Message" - for decompression details + * MS-XCA - for details of the supported algorithms + */ +#ifndef _SMB_COMPRESS_H +#define _SMB_COMPRESS_H + +#include +#include +#include "../common/smb2pdu.h" +#include "cifsglob.h" + +/* sizeof(smb2_compression_hdr) - sizeof(OriginalPayloadSize) */ +#define SMB_COMPRESS_HDR_LEN 16 +/* sizeof(smb2_compression_payload_hdr) - sizeof(OriginalPayloadSize) */ +#define SMB_COMPRESS_PAYLOAD_HDR_LEN 8 +#define SMB_COMPRESS_MIN_LEN PAGE_SIZE + +struct smb_compress_ctx { + struct TCP_Server_Info *server; + struct work_struct work; + struct mid_q_entry *mid; + + void *buf; /* compressed data */ + void *data; /* uncompressed data */ + size_t len; +}; + +#ifdef CONFIG_CIFS_COMPRESSION +int smb_compress(void *buf, const void *data, size_t *len); + +/** + * smb_compress_alg_valid() - Validate a compression algorithm. + * @alg: Compression algorithm to check. + * @valid_none: Conditional check whether NONE algorithm should be + * considered valid or not. + * + * If @alg is SMB3_COMPRESS_NONE, this function returns @valid_none. + * + * Note that 'NONE' (0) compressor type is considered invalid in protocol + * negotiation, as it's never requested to/returned from the server. + * + * Return: true if @alg is valid/supported, false otherwise. + */ +static __always_inline int smb_compress_alg_valid(__le16 alg, bool valid_none) +{ + if (alg == SMB3_COMPRESS_NONE) + return valid_none; + + if (alg == SMB3_COMPRESS_LZ77 || alg == SMB3_COMPRESS_PATTERN) + return true; + + return false; +} + +/** + * should_compress() - Determines if a request (write) or the response to a + * request (read) should be compressed. + * @tcon: tcon of the request is being sent to + * @buf: buffer with an SMB2 READ/WRITE request + * + * Return: true iff: + * - compression was successfully negotiated with server + * - server has enabled compression for the share + * - it's a read or write request + * - if write, request length is >= SMB_COMPRESS_MIN_LEN + * + * Return false otherwise. + */ +static __always_inline bool should_compress(const struct cifs_tcon *tcon, const void *buf) +{ + const struct smb2_hdr *shdr = buf; + + if (!tcon || !tcon->ses || !tcon->ses->server) + return false; + + if (!tcon->ses->server->compression.enabled) + return false; + + if (!(tcon->share_flags & SMB2_SHAREFLAG_COMPRESS_DATA)) + return false; + + if (shdr->Command == SMB2_WRITE) { + const struct smb2_write_req *req = buf; + + return (req->Length >= SMB_COMPRESS_MIN_LEN); + } + + return (shdr->Command == SMB2_READ); +} +/* + * #else !CONFIG_CIFS_COMPRESSION ... + * These routines should not be called when CONFIG_CIFS_COMPRESSION disabled + * #define smb_compress(arg1, arg2, arg3) (-EOPNOTSUPP) + * #define smb_compress_alg_valid(arg1, arg2) (-EOPNOTSUPP) + * #define should_compress(arg1, arg2) (false) + */ +#endif /* !CONFIG_CIFS_COMPRESSION */ +#endif /* _SMB_COMPRESS_H */ diff --git a/fs/smb/client/compress/lz77.c b/fs/smb/client/compress/lz77.c new file mode 100644 index 000000000000..2b8d548f9492 --- /dev/null +++ b/fs/smb/client/compress/lz77.c @@ -0,0 +1,211 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2024, SUSE LLC + * + * Authors: Enzo Matsumiya + * + * Implementation of the LZ77 "plain" compression algorithm, as per MS-XCA spec. + */ +#include +#include "lz77.h" + +static __always_inline u32 hash3(const u8 *ptr) +{ + return lz77_hash32(lz77_read32(ptr) & 0xffffff, LZ77_HASH_LOG); +} + +static u8 *write_match(u8 *dst, u8 **nib, u32 dist, u32 len) +{ + len -= 3; + dist--; + dist <<= 3; + + if (len < 7) { + lz77_write16(dst, dist + len); + return dst + 2; + } + + dist |= 7; + lz77_write16(dst, dist); + dst += 2; + len -= 7; + + if (!*nib) { + *nib = dst; + lz77_write8(dst, min_t(unsigned int, len, 15)); + dst++; + } else { + **nib |= min_t(unsigned int, len, 15) << 4; + *nib = NULL; + } + + if (len < 15) + return dst; + + len -= 15; + if (len < 255) { + lz77_write8(dst, len); + return dst + 1; + } + + lz77_write8(dst, 0xff); + dst++; + + len += 7 + 15; + if (len <= 0xffff) { + lz77_write16(dst, len); + return dst + 2; + } + + lz77_write16(dst, 0); + dst += 2; + lz77_write32(dst, len); + + return dst + 4; +} + +static u8 *write_literals(u8 *dst, const u8 *dst_end, const u8 *src, size_t count, + struct lz77_flags *flags) +{ + const u8 *end = src + count; + + while (src < end) { + size_t c = lz77_min(count, 32 - flags->count); + + if (dst + c >= dst_end) + return ERR_PTR(-EFAULT); + + if (lz77_copy(dst, src, c)) + return ERR_PTR(-EFAULT); + + dst += c; + src += c; + count -= c; + + flags->val <<= c; + flags->count += c; + if (flags->count == 32) { + lz77_write32(flags->pos, flags->val); + flags->count = 0; + flags->pos = dst; + dst += 4; + } + } + + return dst; +} + +static __always_inline bool is_valid_match(const u32 dist, const u32 len) +{ + return (dist >= LZ77_MATCH_MIN_DIST && dist < LZ77_MATCH_MAX_DIST) && + (len >= LZ77_MATCH_MIN_LEN && len < LZ77_MATCH_MAX_LEN); +} + +static __always_inline const u8 *find_match(u32 *htable, const u8 *base, const u8 *cur, + const u8 *end, u32 *best_len) +{ + const u8 *match; + u32 hash; + size_t offset; + + hash = hash3(cur); + offset = cur - base; + + if (htable[hash] >= offset) + return cur; + + match = base + htable[hash]; + *best_len = lz77_match(match, cur, end); + if (is_valid_match(cur - match, *best_len)) + return match; + + return cur; +} + +int lz77_compress(const u8 *src, size_t src_len, u8 *dst, size_t *dst_len) +{ + const u8 *srcp, *src_end, *anchor; + struct lz77_flags flags = { 0 }; + u8 *dstp, *dst_end, *nib; + u32 *htable; + int ret; + + srcp = src; + anchor = srcp; + src_end = src + src_len; + + dstp = dst; + dst_end = dst + *dst_len; + flags.pos = dstp; + nib = NULL; + + memset(dstp, 0, *dst_len); + dstp += 4; + + htable = kvcalloc(LZ77_HASH_SIZE, sizeof(u32), GFP_KERNEL); + if (!htable) + return -ENOMEM; + + /* fill hashtable with invalid offsets */ + memset(htable, 0xff, LZ77_HASH_SIZE * sizeof(u32)); + + /* from here on, any error is because @dst_len reached >= @src_len */ + ret = -EMSGSIZE; + + /* main loop */ + while (srcp < src_end) { + u32 hash, dist, len; + const u8 *match; + + while (srcp + 3 < src_end) { + len = LZ77_MATCH_MIN_LEN - 1; + match = find_match(htable, src, srcp, src_end, &len); + hash = hash3(srcp); + htable[hash] = srcp - src; + + if (likely(match < srcp)) { + dist = srcp - match; + break; + } + + srcp++; + } + + dstp = write_literals(dstp, dst_end, anchor, srcp - anchor, &flags); + if (IS_ERR(dstp)) + goto err_free; + + if (srcp + 3 >= src_end) + goto leftovers; + + dstp = write_match(dstp, &nib, dist, len); + srcp += len; + anchor = srcp; + + flags.val = (flags.val << 1) | 1; + flags.count++; + if (flags.count == 32) { + lz77_write32(flags.pos, flags.val); + flags.count = 0; + flags.pos = dstp; + dstp += 4; + } + } +leftovers: + if (srcp < src_end) { + dstp = write_literals(dstp, dst_end, srcp, src_end - srcp, &flags); + if (IS_ERR(dstp)) + goto err_free; + } + + flags.val <<= (32 - flags.count); + flags.val |= (1 << (32 - flags.count)) - 1; + lz77_write32(flags.pos, flags.val); + + *dst_len = dstp - dst; + ret = 0; +err_free: + kvfree(htable); + + return ret; +} diff --git a/fs/smb/client/compress/lz77.h b/fs/smb/client/compress/lz77.h new file mode 100644 index 000000000000..3d0d3eaa8ffb --- /dev/null +++ b/fs/smb/client/compress/lz77.h @@ -0,0 +1,286 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2024, SUSE LLC + * + * Authors: Enzo Matsumiya + * + * Definitions and optmized helpers for LZ77 compression. + */ +#ifndef _SMB_COMPRESS_LZ77_H +#define _SMB_COMPRESS_LZ77_H + +#include +#ifdef CONFIG_CIFS_COMPRESSION +#include +#include +#include +#ifndef CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS +#include +#endif + +#define LZ77_HASH_LOG 13 +#define LZ77_HASH_SIZE (1 << LZ77_HASH_LOG) +#define LZ77_HASH_MASK lz77_hash_mask(LZ77_HASH_LOG) + +/* We can increase this for better compression (but worse performance). */ +#define LZ77_MATCH_MIN_LEN 3 +/* From MS-XCA, but it's arbitrarily chosen. */ +#define LZ77_MATCH_MAX_LEN S32_MAX +/* + * Check this to ensure we don't match the current position, which would + * end up doing a verbatim copy of the input, and actually overflowing + * the output buffer because of the encoded metadata. + */ +#define LZ77_MATCH_MIN_DIST 1 +/* How far back in the buffer can we try to find a match (i.e. window size) */ +#define LZ77_MATCH_MAX_DIST 8192 + +#define LZ77_STEPSIZE_16 sizeof(u16) +#define LZ77_STEPSIZE_32 sizeof(u32) +#define LZ77_STEPSIZE_64 sizeof(u64) + +struct lz77_flags { + u8 *pos; + size_t count; + long val; +}; + +static __always_inline u32 lz77_hash_mask(const unsigned int log2) +{ + return ((1 << log2) - 1); +} + +static __always_inline u32 lz77_hash64(const u64 v, const unsigned int log2) +{ + const u64 prime5bytes = 889523592379ULL; + + return (u32)(((v << 24) * prime5bytes) >> (64 - log2)); +} + +static __always_inline u32 lz77_hash32(const u32 v, const unsigned int log2) +{ + return ((v * 2654435769LL) >> (32 - log2)) & lz77_hash_mask(log2); +} + +static __always_inline u32 lz77_log2(unsigned int x) +{ + return x ? ((u32)(31 - __builtin_clz(x))) : 0; +} + +#ifdef CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS +static __always_inline u8 lz77_read8(const void *ptr) +{ + return *(u8 *)ptr; +} + +static __always_inline u16 lz77_read16(const void *ptr) +{ + return *(u16 *)ptr; +} + +static __always_inline u32 lz77_read32(const void *ptr) +{ + return *(u32 *)ptr; +} + +static __always_inline u64 lz77_read64(const void *ptr) +{ + return *(u64 *)ptr; +} + +static __always_inline void lz77_write8(void *ptr, const u8 v) +{ + *(u8 *)ptr = v; +} + +static __always_inline void lz77_write16(void *ptr, const u16 v) +{ + *(u16 *)ptr = v; +} + +static __always_inline void lz77_write32(void *ptr, const u32 v) +{ + *(u32 *)ptr = v; +} + +static __always_inline void lz77_write64(void *ptr, const u64 v) +{ + *(u64 *)ptr = v; +} + +static __always_inline void lz77_write_ptr16(void *ptr, const void *vp) +{ + *(u16 *)ptr = *(const u16 *)vp; +} + +static __always_inline void lz77_write_ptr32(void *ptr, const void *vp) +{ + *(u32 *)ptr = *(const u32 *)vp; +} + +static __always_inline void lz77_write_ptr64(void *ptr, const void *vp) +{ + *(u64 *)ptr = *(const u64 *)vp; +} + +static __always_inline long lz77_copy(u8 *dst, const u8 *src, size_t count) +{ + return copy_from_kernel_nofault(dst, src, count); +} +#else /* CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS */ +static __always_inline u8 lz77_read8(const void *ptr) +{ + return get_unaligned((u8 *)ptr); +} + +static __always_inline u16 lz77_read16(const void *ptr) +{ + return lz77_read8(ptr) | (lz77_read8(ptr + 1) << 8); +} + +static __always_inline u32 lz77_read32(const void *ptr) +{ + return lz77_read16(ptr) | (lz77_read16(ptr + 2) << 16); +} + +static __always_inline u64 lz77_read64(const void *ptr) +{ + return lz77_read32(ptr) | ((u64)lz77_read32(ptr + 4) << 32); +} + +static __always_inline void lz77_write8(void *ptr, const u8 v) +{ + put_unaligned(v, (u8 *)ptr); +} + +static __always_inline void lz77_write16(void *ptr, const u16 v) +{ + lz77_write8(ptr, v & 0xff); + lz77_write8(ptr + 1, (v >> 8) & 0xff); +} + +static __always_inline void lz77_write32(void *ptr, const u32 v) +{ + lz77_write16(ptr, v & 0xffff); + lz77_write16(ptr + 2, (v >> 16) & 0xffff); +} + +static __always_inline void lz77_write64(void *ptr, const u64 v) +{ + lz77_write32(ptr, v & 0xffffffff); + lz77_write32(ptr + 4, (v >> 32) & 0xffffffff); +} + +static __always_inline void lz77_write_ptr16(void *ptr, const void *vp) +{ + const u16 v = lz77_read16(vp); + + lz77_write16(ptr, v); +} + +static __always_inline void lz77_write_ptr32(void *ptr, const void *vp) +{ + const u32 v = lz77_read32(vp); + + lz77_write32(ptr, v); +} + +static __always_inline void lz77_write_ptr64(void *ptr, const void *vp) +{ + const u64 v = lz77_read64(vp); + + lz77_write64(ptr, v); +} +static __always_inline long lz77_copy(u8 *dst, const u8 *src, size_t count) +{ + memcpy(dst, src, count); + return 0; +} +#endif /* !CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS */ + +static __always_inline unsigned int __count_common_bytes(const unsigned long diff) +{ +#ifdef __has_builtin +# if __has_builtin(__builtin_ctzll) + return (unsigned int)__builtin_ctzll(diff) >> 3; +# endif +#else + /* count trailing zeroes */ + unsigned long bits = 0, i, z = 0; + + bits |= diff; + for (i = 0; i < 64; i++) { + if (bits[i]) + break; + z++; + } + + return (unsigned int)z >> 3; +#endif +} + +static __always_inline size_t lz77_match(const u8 *match, const u8 *cur, const u8 *end) +{ + const u8 *start = cur; + + if (cur == match) + return 0; + + if (likely(cur < end - (LZ77_STEPSIZE_64 - 1))) { + u64 const diff = lz77_read64(cur) ^ lz77_read64(match); + + if (!diff) { + cur += LZ77_STEPSIZE_64; + match += LZ77_STEPSIZE_64; + } else { + return __count_common_bytes(diff); + } + } + + while (likely(cur < end - (LZ77_STEPSIZE_64 - 1))) { + u64 const diff = lz77_read64(cur) ^ lz77_read64(match); + + if (!diff) { + cur += LZ77_STEPSIZE_64; + match += LZ77_STEPSIZE_64; + continue; + } + + cur += __count_common_bytes(diff); + return (size_t)(cur - start); + } + + if (cur < end - 3 && !(lz77_read32(cur) ^ lz77_read32(match))) { + cur += LZ77_STEPSIZE_32; + match += LZ77_STEPSIZE_32; + } + + if (cur < end - 1 && lz77_read16(cur) == lz77_read16(match)) { + cur += LZ77_STEPSIZE_16; + match += LZ77_STEPSIZE_16; + } + + if (cur < end && *cur == *match) + cur++; + + return (size_t)(cur - start); +} + +static __always_inline unsigned long lz77_max(unsigned long a, unsigned long b) +{ + int m = (a < b) - 1; + + return (a & m) | (b & ~m); +} + +static __always_inline unsigned long lz77_min(unsigned long a, unsigned long b) +{ + int m = (a > b) - 1; + + return (a & m) | (b & ~m); +} + +int lz77_compress(const u8 *src, size_t src_len, u8 *dst, size_t *dst_len); +/* when CONFIG_CIFS_COMPRESSION not set lz77_compress() is not called */ +#endif /* !CONFIG_CIFS_COMPRESSION */ +#endif /* _SMB_COMPRESS_LZ77_H */ diff --git a/fs/smb/client/fs_context.c b/fs/smb/client/fs_context.c index bc926ab2555b..20dc31a07a56 100644 --- a/fs/smb/client/fs_context.c +++ b/fs/smb/client/fs_context.c @@ -978,9 +978,12 @@ static int smb3_fs_context_parse_param(struct fs_context *fc, switch (opt) { case Opt_compress: + if (!IS_ENABLED(CONFIG_CIFS_COMPRESSION)) { + cifs_errorf(fc, "CONFIG_CIFS_COMPRESSION kernel config option is unset\n"); + goto cifs_parse_mount_err; + } ctx->compress = true; - cifs_dbg(VFS, - "SMB3 compression support is experimental\n"); + cifs_dbg(VFS, "SMB3 compression support is experimental\n"); break; case Opt_nodfs: ctx->nodfs = 1;