summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--fs/smb/client/Kconfig14
-rw-r--r--fs/smb/client/Makefile2
-rw-r--r--fs/smb/client/cifs_debug.c7
-rw-r--r--fs/smb/client/cifsfs.c12
-rw-r--r--fs/smb/client/cifsglob.h4
-rw-r--r--fs/smb/client/compress.c50
-rw-r--r--fs/smb/client/compress.h106
-rw-r--r--fs/smb/client/compress/lz77.c211
-rw-r--r--fs/smb/client/compress/lz77.h286
-rw-r--r--fs/smb/client/fs_context.c7
-rw-r--r--fs/smb/client/smb2ops.c34
-rw-r--r--fs/smb/client/smb2pdu.c16
-rw-r--r--fs/smb/client/transport.c130
13 files changed, 861 insertions, 18 deletions
diff --git a/fs/smb/client/Kconfig b/fs/smb/client/Kconfig
index 2927bd174a88..9f05f94e265a 100644
--- a/fs/smb/client/Kconfig
+++ b/fs/smb/client/Kconfig
@@ -203,4 +203,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 226d4835c92d..6d67cc280948 100644
--- a/fs/smb/client/cifs_debug.c
+++ b/fs/smb/client/cifs_debug.c
@@ -348,6 +348,9 @@ static int cifs_debug_data_proc_show(struct seq_file *m, void *v)
#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);
seq_printf(m, "Active VFS Requests: %d\n", GlobalTotalActiveXid);
@@ -473,7 +476,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/cifsfs.c b/fs/smb/client/cifsfs.c
index 1bfd1b4c41d9..bc160a4eb1cb 100644
--- a/fs/smb/client/cifsfs.c
+++ b/fs/smb/client/cifsfs.c
@@ -160,6 +160,7 @@ struct workqueue_struct *decrypt_wq;
struct workqueue_struct *fileinfo_put_wq;
struct workqueue_struct *cifsoplockd_wq;
struct workqueue_struct *deferredclose_wq;
+struct workqueue_struct *compress_wq;
__u32 cifs_lock_secret;
/*
@@ -1895,9 +1896,15 @@ init_cifs(void)
goto out_destroy_cifsoplockd_wq;
}
+ compress_wq = alloc_workqueue("smb3compressd", WQ_CPU_INTENSIVE|WQ_FREEZABLE|WQ_MEM_RECLAIM, 0);
+ if (!compress_wq) {
+ rc = -ENOMEM;
+ goto out_destroy_deferredclose_wq;
+ }
+
rc = cifs_init_inodecache();
if (rc)
- goto out_destroy_deferredclose_wq;
+ goto out_destroy_compress_wq;
rc = init_mids();
if (rc)
@@ -1959,6 +1966,8 @@ out_destroy_mids:
destroy_mids();
out_destroy_inodecache:
cifs_destroy_inodecache();
+out_destroy_compress_wq:
+ destroy_workqueue(compress_wq);
out_destroy_deferredclose_wq:
destroy_workqueue(deferredclose_wq);
out_destroy_cifsoplockd_wq:
@@ -1994,6 +2003,7 @@ exit_cifs(void)
cifs_destroy_request_bufs();
destroy_mids();
cifs_destroy_inodecache();
+ destroy_workqueue(compress_wq);
destroy_workqueue(deferredclose_wq);
destroy_workqueue(cifsoplockd_wq);
destroy_workqueue(decrypt_wq);
diff --git a/fs/smb/client/cifsglob.h b/fs/smb/client/cifsglob.h
index 8be62ed053a2..3b7a162080ba 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);
@@ -1909,6 +1909,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
@@ -2102,6 +2103,7 @@ extern struct workqueue_struct *decrypt_wq;
extern struct workqueue_struct *fileinfo_put_wq;
extern struct workqueue_struct *cifsoplockd_wq;
extern struct workqueue_struct *deferredclose_wq;
+extern struct workqueue_struct *compress_wq;
extern __u32 cifs_lock_secret;
extern mempool_t *cifs_mid_poolp;
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..38a4ac1c92dd
--- /dev/null
+++ b/fs/smb/client/compress.h
@@ -0,0 +1,106 @@
+/* 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 */
+#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..57dacadd6a58
--- /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 <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);
+#else /* CONFIG_CIFS_COMPRESSION */
+#define lz77_compress(arg1, arg2, arg3, arg4) (-EOPNOTSUPP)
+#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 bdcbe6ff2739..06e45c488b58 100644
--- a/fs/smb/client/fs_context.c
+++ b/fs/smb/client/fs_context.c
@@ -963,9 +963,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;
diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c
index 6ee22d0dbc00..adced44632c9 100644
--- a/fs/smb/client/smb2ops.c
+++ b/fs/smb/client/smb2ops.c
@@ -29,6 +29,7 @@
#include "fs_context.h"
#include "cached_dir.h"
#include "reparse.h"
+#include "compress.h"
/* Change credits for different ops and return the total number of credits */
static int
@@ -4072,12 +4073,34 @@ smb2_dir_needs_close(struct cifsFileInfo *cfile)
return !cfile->invalidHandle;
}
+static __always_inline bool has_compress_hdr(void *buf)
+{
+ struct smb2_compression_hdr *hdr = buf;
+
+ if (hdr->ProtocolId == SMB2_COMPRESSION_TRANSFORM_ID)
+ return true;
+
+ return false;
+}
+
+static __always_inline struct smb2_hdr *get_shdr(void *buf)
+{
+ struct smb2_hdr *shdr = buf;
+
+ if (shdr->ProtocolId == SMB2_PROTO_NUMBER)
+ return buf;
+
+ if (has_compress_hdr(buf))
+ return buf + SMB_COMPRESS_HDR_LEN;
+
+ BUG();
+}
+
static void
fill_transform_hdr(struct smb2_transform_hdr *tr_hdr, unsigned int orig_len,
struct smb_rqst *old_rq, __le16 cipher_type)
{
- struct smb2_hdr *shdr =
- (struct smb2_hdr *)old_rq->rq_iov[0].iov_base;
+ struct smb2_hdr *shdr = get_shdr(old_rq->rq_iov[0].iov_base);
memset(tr_hdr, 0, sizeof(struct smb2_transform_hdr));
tr_hdr->ProtocolId = SMB2_TRANSFORM_PROTO_NUM;
@@ -4395,13 +4418,10 @@ smb3_init_transform_rq(struct TCP_Server_Info *server, int num_rqst,
rc = crypt_message(server, num_rqst, new_rq, 1);
cifs_dbg(FYI, "Encrypt message returned %d\n", rc);
+err_free:
if (rc)
- goto err_free;
-
- return rc;
+ smb3_free_compound_rqst(num_rqst - 1, &new_rq[1]);
-err_free:
- smb3_free_compound_rqst(num_rqst - 1, &new_rq[1]);
return rc;
}
diff --git a/fs/smb/client/smb2pdu.c b/fs/smb/client/smb2pdu.c
index e5e6b14f8cae..8f351a9a73d4 100644
--- a/fs/smb/client/smb2pdu.c
+++ b/fs/smb/client/smb2pdu.c
@@ -40,6 +40,7 @@
#include "dfs_cache.h"
#endif
#include "cached_dir.h"
+#include "compress.h"
/*
* The following table defines the expected "StructureSize" of SMB2 requests
@@ -588,10 +589,12 @@ build_compression_ctxt(struct smb2_compression_capabilities_context *pneg_ctxt)
pneg_ctxt->DataLength =
cpu_to_le16(sizeof(struct smb2_compression_capabilities_context)
- sizeof(struct smb2_neg_context));
- pneg_ctxt->CompressionAlgorithmCount = cpu_to_le16(3);
+ pneg_ctxt->CompressionAlgorithmCount = cpu_to_le16(1);
+ /*
+ * Send the only algorithm we support (XXX: add others as they're
+ * implemented).
+ */
pneg_ctxt->CompressionAlgorithms[0] = SMB3_COMPRESS_LZ77;
- pneg_ctxt->CompressionAlgorithms[1] = SMB3_COMPRESS_LZ77_HUFF;
- pneg_ctxt->CompressionAlgorithms[2] = SMB3_COMPRESS_LZNT1;
}
static unsigned int
@@ -799,9 +802,7 @@ static void decode_compress_ctx(struct TCP_Server_Info *server,
}
alg = ctxt->CompressionAlgorithms[0];
-
- /* 'NONE' (0) compressor type is never negotiated */
- if (alg == 0 || le16_to_cpu(alg) > 3) {
+ if (!smb_compress_alg_valid(alg, false)) {
pr_warn_once("invalid compression algorithm '%u'\n", alg);
return;
}
@@ -4937,6 +4938,9 @@ smb2_async_writev(struct cifs_writedata *wdata,
flags |= CIFS_HAS_CREDITS;
}
+ if (should_compress(tcon, req))
+ flags |= CIFS_COMPRESS_REQ;
+
kref_get(&wdata->refcount);
rc = cifs_call_async(server, &rqst, NULL, smb2_writev_callback, NULL,
wdata, flags, &wdata->credits);
diff --git a/fs/smb/client/transport.c b/fs/smb/client/transport.c
index 994d70193432..92cae9079880 100644
--- a/fs/smb/client/transport.c
+++ b/fs/smb/client/transport.c
@@ -28,6 +28,7 @@
#include "cifs_debug.h"
#include "smb2proto.h"
#include "smbdirect.h"
+#include "compress.h"
/* Max number of iovectors we can use off the stack when sending requests. */
#define CIFS_MAX_IOV_SIZE 8
@@ -417,6 +418,132 @@ out:
return rc;
}
+static int __compress_next_cpu = -1;
+
+static int compress_next_cpu(void)
+{
+ int cpu;
+
+ /*
+ * Find the next online CPU (will wrap), globally accounted by
+ * %__compress_next_cpu, so async decompression works spread
+ * evenly across the system.
+ */
+ cpu = cpumask_next_wrap(__compress_next_cpu, cpu_online_mask, -1, true);
+ if (unlikely(cpu >= nr_cpu_ids)) {
+ cpu = cpumask_next(raw_smp_processor_id(), cpu_online_mask);
+ if (unlikely(cpu >= nr_cpu_ids))
+ /* last resort */
+ cpu = raw_smp_processor_id();
+ }
+
+ __compress_next_cpu = cpu;
+ return cpu;
+}
+
+static void compress_thread(struct work_struct *work)
+{
+ struct smb_compress_ctx *ctx = container_of(work, struct smb_compress_ctx, work);
+ struct TCP_Server_Info *server = ctx->server;
+ struct cifs_writedata *wdata = ctx->mid->callback_data;
+ struct smb_rqst rq = { 0 };
+ struct kvec iov[2] = { 0 };
+ size_t len = ctx->len;
+ int ret;
+
+ ret = smb_compress(ctx->buf, ctx->data, &len);
+ if (!ret) {
+ iov[0].iov_base = ctx->buf;
+ iov[0].iov_len = len;
+ rq.rq_iov = &iov[0];
+ rq.rq_nvec = 1;
+ } else {
+ if (ret != -ENODATA && ret != -EMSGSIZE)
+ goto out;
+
+ /* non-fatal error, send original (uncompressed) request instead */
+ iov[0].iov_base = ctx->buf + SMB_COMPRESS_HDR_LEN;
+ iov[0].iov_len = sizeof(struct smb2_write_req);
+ iov[1].iov_base = ctx->data;
+ iov[1].iov_len = ctx->len;
+ rq.rq_iov = &iov[0];
+ rq.rq_nvec = 2;
+ }
+
+ ret = __smb_send_rqst(server, 1, &rq);
+out:
+ wdata->result = ret;
+ kref_put(&wdata->refcount, cifs_writedata_release);
+ release_mid(ctx->mid);
+
+ kvfree(ctx->buf);
+ kvfree(ctx->data);
+ kfree(ctx);
+}
+
+static int compress_async(struct TCP_Server_Info *server, struct smb_rqst *rqst)
+{
+ struct smb_compress_ctx *ctx;
+ struct iov_iter tmp = rqst->rq_iter;
+ int ret = -ENOMEM;
+
+ if (WARN(rqst->rq_iov->iov_len != sizeof(struct smb2_write_req),
+ "%s: unexpected buf len %zu\n", __func__, rqst->rq_iov->iov_len))
+ return -EIO;
+
+ if (iov_iter_count(&rqst->rq_iter) < SMB_COMPRESS_MIN_LEN)
+ return -ENODATA;
+
+ ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ goto err_free;
+
+ ctx->mid = server->ops->find_mid(server, rqst->rq_iov->iov_base);
+ if (!ctx->mid) {
+ /* XXX: better error handling here */
+ pr_warn("%s: can't find mid to compress\n", __func__);
+ ret = -EIO;
+ goto err_free;
+ }
+
+ if (!ctx->mid->callback_data) {
+ /* XXX: better error handling here */
+ pr_warn("%s: can't find write data to compress\n", __func__);
+ ret = -EIO;
+ goto err_free;
+ }
+
+ ctx->len = iov_iter_count(&rqst->rq_iter);