[PATCH 05/10] smb: client: compress: enable Pattern_V1 + chained compression

Enzo Matsumiya <[email protected]>
Newsgroups org.kernel.vger.linux-cifs
Message-ID <[email protected]>
Negotiate Pattern_V1 and LZ77 compression algorithms by default (if
'compress' mount option is set).

Changes:
- drop TCP_Server_Info::compression::requested, rely on ::enabled only
- add smb_compress_alloc_size() helper to compute compressed buffer
  size, to account for Pattern_V1 payloads as well
- call smb_compression_compress() with SMB2 header as @uncomp

Signed-off-by: Enzo Matsumiya <[email protected]>
---
 fs/smb/client/cifs_debug.c        | 18 ++++++++++------
 fs/smb/client/cifsglob.h          |  1 -
 fs/smb/client/compress.c          | 36 ++++++++++++++-----------------
 fs/smb/client/connect.c           |  2 +-
 fs/smb/client/sess.c              |  2 +-
 fs/smb/client/smb2pdu.c           | 25 ++++++++++++++-------
 fs/smb/common/compress/compress.h | 28 ++++++++++++++++++++++++
 fs/smb/server/compress.c          |  5 +----
 8 files changed, 75 insertions(+), 42 deletions(-)

diff --git a/fs/smb/client/cifs_debug.c b/fs/smb/client/cifs_debug.c
index 4ed4f55a0bb7..36b4b7345c04 100644
--- a/fs/smb/client/cifs_debug.c
+++ b/fs/smb/client/cifs_debug.c
@@ -552,14 +552,18 @@ static int cifs_debug_data_proc_show(struct seq_file *m, void *v)
 		}
 
 		seq_puts(m, "\nCompression: ");
-		if (!IS_ENABLED(CONFIG_CIFS_COMPRESSION))
+		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));
-		else
-			seq_puts(m, "disabled (not supported by this server)");
+		} else if (!server->compression.enabled) {
+			seq_puts(m, "disabled"); /* XXX: reason? */
+		} else {
+			seq_printf(m, "enabled, chained: %s, algs: ",
+				   str_yes_no(server->compression.chained));
+
+			seq_printf(m, "%s ", compression_alg_str(server->compression.alg));
+			if (server->compression.pattern)
+				seq_printf(m, "%s ", compression_alg_str(SMB3_COMPRESS_PATTERN));
+		}
 
 		/* Show negotiated encryption cipher, even if not required */
 		seq_puts(m, "\nEncryption: ");
diff --git a/fs/smb/client/cifsglob.h b/fs/smb/client/cifsglob.h
index 99f9e6dca62b..a08aa7fc84f4 100644
--- a/fs/smb/client/cifsglob.h
+++ b/fs/smb/client/cifsglob.h
@@ -787,7 +787,6 @@ struct TCP_Server_Info {
 	unsigned int	rdma_readwrite_threshold;
 	unsigned int	retrans;
 	struct {
-		bool requested; /* "compress" mount option set*/
 		bool enabled; /* actually negotiated with server */
 		bool chained; /* chained transforms were negotiated */
 		bool pattern; /* Pattern_V1 chained payloads were negotiated */
diff --git a/fs/smb/client/compress.c b/fs/smb/client/compress.c
index 7c08e2b843c2..72ac727250f6 100644
--- a/fs/smb/client/compress.c
+++ b/fs/smb/client/compress.c
@@ -338,8 +338,9 @@ bool should_compress(const struct cifs_tcon *tcon, const struct smb_rqst *rq)
 int smb_compress(struct TCP_Server_Info *server, struct smb_rqst *rq, compress_send_fn send_fn)
 {
 	struct iov_iter iter;
-	u32 slen, dlen;
+	u32 slen, dlen, shdr_len;
 	void *src, *dst = NULL;
+	bool use_pattern;
 	int ret;
 
 	if (!server || !rq || !rq->rq_iov || !rq->rq_iov->iov_base)
@@ -380,32 +381,27 @@ int smb_compress(struct TCP_Server_Info *server, struct smb_rqst *rq, compress_s
 		goto err_free;
 	}
 
-	dlen = smb_lz77_compressed_alloc_size(slen);
+	use_pattern = server->compression.pattern;
+	shdr_len = rq->rq_iov[0].iov_len;
+	dlen = smb_compress_alloc_size(slen, use_pattern) + shdr_len;
 	dst = kvzalloc(dlen, GFP_KERNEL);
 	if (!dst) {
 		ret = -ENOMEM;
 		goto err_free;
 	}
 
-	ret = smb_lz77_compress(src, slen, dst, &dlen);
+	ret = smb_compression_compress(SMB3_COMPRESS_LZ77, server->compression.chained, use_pattern,
+				       src, slen, dst, &dlen, rq->rq_iov[0].iov_base, shdr_len);
 	if (!ret) {
-		struct smb2_compression_hdr hdr = { 0 };
-		struct smb_rqst comp_rq = { .rq_nvec = 3, };
-		struct kvec iov[3];
-
-		hdr.ProtocolId = SMB2_COMPRESSION_TRANSFORM_ID;
-		hdr.OriginalCompressedSegmentSize = cpu_to_le32(slen);
-		hdr.CompressionAlgorithm = SMB3_COMPRESS_LZ77;
-		hdr.Flags = SMB2_COMPRESSION_FLAG_NONE;
-		hdr.Offset = cpu_to_le32(rq->rq_iov[0].iov_len);
-
-		iov[0].iov_base = &hdr;
-		iov[0].iov_len = sizeof(hdr);
-		iov[1] = rq->rq_iov[0];
-		iov[2].iov_base = dst;
-		iov[2].iov_len = dlen;
-
-		comp_rq.rq_iov = iov;
+		struct smb_rqst comp_rq = { .rq_nvec = 1, };
+		struct kvec iov = {
+			.iov_base = dst,
+			.iov_len = dlen,
+		};
+
+		iov.iov_base = dst;
+		iov.iov_len = dlen;
+		comp_rq.rq_iov = &iov;
 
 		ret = send_fn(server, 1, &comp_rq);
 	} else if (ret == -EMSGSIZE || dlen >= slen) {
diff --git a/fs/smb/client/connect.c b/fs/smb/client/connect.c
index 324452bc0437..dd35abdac334 100644
--- a/fs/smb/client/connect.c
+++ b/fs/smb/client/connect.c
@@ -1830,7 +1830,7 @@ cifs_get_tcp_session(struct smb3_fs_context *ctx,
 	tcp_ses->channel_sequence_num = 0; /* only tracked for primary channel */
 	tcp_ses->reconnect_instance = 1;
 	tcp_ses->lstrp = jiffies;
-	tcp_ses->compression.requested = ctx->compress;
+	tcp_ses->compression.enabled = ctx->compress;
 	spin_lock_init(&tcp_ses->req_lock);
 	spin_lock_init(&tcp_ses->srv_lock);
 	spin_lock_init(&tcp_ses->mid_queue_lock);
diff --git a/fs/smb/client/sess.c b/fs/smb/client/sess.c
index de2012cc9cf3..83f4b5d107f6 100644
--- a/fs/smb/client/sess.c
+++ b/fs/smb/client/sess.c
@@ -552,7 +552,7 @@ cifs_ses_add_channel(struct cifs_ses *ses,
 	ctx->echo_interval = ses->server->echo_interval / HZ;
 	ctx->max_credits = ses->server->max_credits;
 	ctx->min_offload = ses->server->min_offload;
-	ctx->compress = ses->server->compression.requested;
+	ctx->compress = ses->server->compression.enabled;
 	ctx->dfs_conn = ses->server->dfs_conn;
 	ctx->ignore_signature = ses->server->ignore_signature;
 	ctx->leaf_fullpath = ses->server->leaf_fullpath;
diff --git a/fs/smb/client/smb2pdu.c b/fs/smb/client/smb2pdu.c
index d058584b8f05..41cdc9f5e692 100644
--- a/fs/smb/client/smb2pdu.c
+++ b/fs/smb/client/smb2pdu.c
@@ -785,7 +785,7 @@ assemble_neg_contexts(struct smb2_negotiate_req *req,
 	pneg_ctxt += sizeof(struct smb2_posix_neg_context);
 	neg_context_count++;
 
-	if (server->compression.requested) {
+	if (server->compression.enabled) {
 		build_compression_ctxt((struct smb2_compression_capabilities_context *)
 				pneg_ctxt);
 		ctxt_len = ALIGN(sizeof(struct smb2_compression_capabilities_context), 8);
@@ -835,11 +835,6 @@ static void decode_compress_ctx(struct TCP_Server_Info *server,
 	unsigned int len = le16_to_cpu(ctxt->DataLength);
 	unsigned int count, i;
 
-	server->compression.enabled = false;
-	server->compression.chained = false;
-	server->compression.pattern = false;
-	server->compression.alg = SMB3_COMPRESS_NONE;
-
 	/*
 	 * Caller checked that DataLength remains within SMB boundary. We still
 	 * need to confirm that one CompressionAlgorithms member is accounted
@@ -876,6 +871,12 @@ static void decode_compress_ctx(struct TCP_Server_Info *server,
 	/*
 	 * Pattern_V1 cannot appear in an unchained transform even if a broken
 	 * peer lists it in the algorithm array.
+	 *
+	 * OTOH, the chained flag might have been negotiated without either/both peers having not
+	 * advertised Pattern_V1 algorithm support.
+	 *
+	 * (which doesn't make much sense, but we'll still have to send compressed payloads with
+	 * the chained flag set in "chained && !pattern_v1" cases)
 	 */
 	server->compression.chained =
 		ctxt->Flags == SMB2_COMPRESSION_CAPABILITIES_FLAG_CHAINED;
@@ -1336,11 +1337,19 @@ SMB2_negotiate(const unsigned int xid,
 	}
 
 	if (server->dialect == SMB311_PROT_ID) {
-		if (rsp->NegotiateContextCount)
+		bool compression_enabled = server->compression.enabled;
+
+		/* Reset compression settings in case of server not supporting it */
+		memset(&server->compression, 0, sizeof(server->compression));
+
+		if (rsp->NegotiateContextCount) {
 			rc = smb311_decode_neg_context(rsp, server,
 						       rsp_iov.iov_len);
-		else
+			if (!rc && compression_enabled && !server->compression.enabled)
+				cifs_server_dbg(VFS, "Server doesn't support compression, disabling\n");
+		} else {
 			cifs_server_dbg(VFS, "Missing expected negotiate contexts\n");
+		}
 	}
 
 	if (server->cipher_type && !rc)
diff --git a/fs/smb/common/compress/compress.h b/fs/smb/common/compress/compress.h
index 6760862de04c..e1fad498d567 100644
--- a/fs/smb/common/compress/compress.h
+++ b/fs/smb/common/compress/compress.h
@@ -145,6 +145,34 @@ static __always_inline bool smb_compress_alg_valid(__le16 alg, bool valid_none)
 	return alg == SMB3_COMPRESS_LZ77 || alg == SMB3_COMPRESS_PATTERN;
 }
 
+/**
+ * smb_compress_alloc_size() - Compute total allocation size required for compressed (dst) buffer.
+ * @size:		uncompressed size
+ * @use_pattern:	if Pattern_V1 is enabled
+ *
+ * For any case:
+ * - SMB2 compression hdr
+ * - LZ* payload
+ *
+ * If @use_pattern, also account for:
+ * - 2x payload hdr for Pattern_V1
+ * - 2x Pattern_V1 payloads
+ * - 1x NONE payload hdr
+ *
+ * (possible uncompressed leftovers are included in LZ alloc size)
+ */
+static __always_inline u32 smb_compress_alloc_size(const u32 size, const bool use_pattern)
+{
+	u32 alloc_size;
+
+	alloc_size = sizeof(struct smb2_compression_hdr) + smb_lz77_compressed_alloc_size(size);
+	if (use_pattern)
+		alloc_size += (SMB2_COMPRESSION_PAYLOAD_BASE_LEN * 3) +
+			(sizeof(struct smb2_compression_pattern_v1) * 2);
+
+	return alloc_size;
+}
+
 int smb_compression_decompress(__le16 alg, bool allow_chained,
 			       const void *src, u32 slen, void *dst, u32 dlen);
 int smb_compression_compress(__le16 alg, bool chained, bool allow_pattern,
diff --git a/fs/smb/server/compress.c b/fs/smb/server/compress.c
index 32027a15f7ab..e5851d603469 100644
--- a/fs/smb/server/compress.c
+++ b/fs/smb/server/compress.c
@@ -132,10 +132,7 @@ int ksmbd_compress_response(struct ksmbd_work *work)
 		goto out;
 	}
 
-	max_dst_len = smb_lz77_compressed_alloc_size(src_len) +
-		sizeof(struct smb2_compression_hdr) +
-		3 * sizeof(struct smb2_compression_payload_hdr) +
-		2 * sizeof(struct smb2_compression_pattern_v1);
+	max_dst_len = smb_compress_alloc_size(src_len, work->conn->compress_pattern);
 	out = kvzalloc(sizeof(__be32) + max_dst_len,
 		       KSMBD_DEFAULT_GFP);
 	if (!out) {
-- 
2.54.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.