diff mbox series

[SMB3] mark compression as CONFIG_EXPERIMENTAL and fix missing compression operation

Message ID CAH2r5mvMr9YgvLds8_OCMVV6FOzuBUgwn8_X6Pbz3hq_oakSWw@mail.gmail.com
State New
Headers show
Series [SMB3] mark compression as CONFIG_EXPERIMENTAL and fix missing compression operation | expand

Commit Message

Steve French July 26, 2024, 11:53 p.m. UTC
Since some of Enzo's compression patch series has to be changed due to
David Howell's netfs changes to cifs.ko over the past two releases, I split
out the obvious parts of his patch to implement the client side of SMB3.1.1
compression on write requests (see
https://git.exis.tech/linux.git/patch/?id=40414c6a34081b372e45c7ce5060a6d34779f6ba
for the original patch of Enzo's) but left out the final piece of the patch
(the calls from the write path).

This moves SMB3.1.1 compression code into experimental config option,
and fixes the compress mount option to require that config option.
Also implements unchained LZ77 "plain" compression algorithm as per
MS-XCA specification section "2.3 Plain LZ77 Compression Algorithm Details".

See attached

--
Thanks,

Steve

Comments

Steve French July 28, 2024, 12:39 a.m. UTC | #1
Had to add one line due to an error in some configurations pointed out
by the kernel test robot:

index 0cc9c6abf840..3d0d3eaa8ffb 100644
--- a/fs/smb/client/compress/lz77.h
+++ b/fs/smb/client/compress/lz77.h
@@ -9,6 +9,7 @@
 #ifndef _SMB_COMPRESS_LZ77_H
 #define _SMB_COMPRESS_LZ77_H

+#include <linux/uaccess.h>
 #ifdef CONFIG_CIFS_COMPRESSION
 #include <asm/ptrace.h>
 #include <linux/kernel.h>

to address:

   In file included from fs/smb/client/compress/lz77.c:10:
>> fs/smb/client/compress/lz77.h:127:9: error: call to undeclared function 'copy_from_kernel_nofault'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
     127 |         return copy_from_kernel_nofault(dst, src, count);
         |                ^
   1 error generated.


vim +/copy_from_kernel_nofault +127 fs/smb/client/compress/lz77.h

   124
   125  static __always_inline long lz77_copy(u8 *dst, const u8 *src,
size_t count)
   126  {
 > 127           return copy_from_kernel_nofault(dst, src, count);
   128  }
   129  #else /* CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS */
   130  static __always_inline u8 lz77_read8(const void *ptr)
   131  {
   132          return get_unaligned((u8 *)ptr);
   133  }
   134

On Fri, Jul 26, 2024 at 6:53 PM Steve French <smfrench@gmail.com> wrote:
>
> Since some of Enzo's compression patch series has to be changed due to David Howell's netfs changes to cifs.ko over the past two releases, I split out the obvious parts of his patch to implement the client side of SMB3.1.1 compression on write requests (see https://git.exis.tech/linux.git/patch/?id=40414c6a34081b372e45c7ce5060a6d34779f6ba for the original patch of Enzo's) but left out the final piece of the patch (the calls from the write path).
>
> This moves SMB3.1.1 compression code into experimental config option,
> and fixes the compress mount option to require that config option.
> Also implements unchained LZ77 "plain" compression algorithm as per
> MS-XCA specification section "2.3 Plain LZ77 Compression Algorithm Details".
>
> See attached
>
> --
> Thanks,
>
> Steve
diff mbox series

Patch

From 7db9a00c4d9bf4996177e4946241885234afb5c0 Mon Sep 17 00:00:00 2001
From: Steve French <stfrench@microsoft.com>
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 <ematsumiya@suse.de>
Signed-off-by: Steve French <stfrench@microsoft.com>
---
 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 | 285 ++++++++++++++++++++++++++++++++++
 fs/smb/client/fs_context.c    |   7 +-
 9 files changed, 684 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 c71ae5c04306..c3cfa8f7a337 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 @@  static int cifs_debug_data_proc_show(struct seq_file *m, void *v)
 		}
 
 		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 8e86fec7dcd2..3afe89f70214 100644
--- a/fs/smb/client/cifsglob.h
+++ b/fs/smb/client/cifsglob.h
@@ -556,7 +556,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);
@@ -1899,6 +1899,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 <ematsumiya@suse.de>
+ *
+ * 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 <linux/slab.h>
+#include <linux/kernel.h>
+#include <linux/uio.h>
+
+#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 <ematsumiya@suse.de>
+ *
+ * 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 <linux/uio.h>
+#include <linux/kernel.h>
+#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 <ematsumiya@suse.de>
+ *
+ * Implementation of the LZ77 "plain" compression algorithm, as per MS-XCA spec.
+ */
+#include <linux/slab.h>
+#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..0cc9c6abf840
--- /dev/null
+++ b/fs/smb/client/compress/lz77.h
@@ -0,0 +1,285 @@ 
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2024, SUSE LLC
+ *
+ * Authors: Enzo Matsumiya <ematsumiya@suse.de>
+ *
+ * Definitions and optmized helpers for LZ77 compression.
+ */
+#ifndef _SMB_COMPRESS_LZ77_H
+#define _SMB_COMPRESS_LZ77_H
+
+#ifdef CONFIG_CIFS_COMPRESSION
+#include <asm/ptrace.h>
+#include <linux/kernel.h>
+#include <linux/string.h>
+#ifndef CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS
+#include <asm-generic/unaligned.h>
+#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;
-- 
2.43.0