From patchwork Fri Jul 26 23:53:18 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Steve French X-Patchwork-Id: 1965467 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; dkim=pass (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256 header.s=20230601 header.b=JYcYrN7S; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=vger.kernel.org (client-ip=2604:1380:4601:e00::3; helo=am.mirrors.kernel.org; envelope-from=linux-cifs+bounces-2355-incoming=patchwork.ozlabs.org@vger.kernel.org; receiver=patchwork.ozlabs.org) Received: from am.mirrors.kernel.org (am.mirrors.kernel.org [IPv6:2604:1380:4601:e00::3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (secp384r1) server-digest SHA384) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4WW4ZB4Sh6z1yY5 for ; Sat, 27 Jul 2024 10:02:42 +1000 (AEST) Received: from smtp.subspace.kernel.org (wormhole.subspace.kernel.org [52.25.139.140]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by am.mirrors.kernel.org (Postfix) with ESMTPS id A64AC1F21444 for ; Sat, 27 Jul 2024 00:02:39 +0000 (UTC) Received: from localhost.localdomain (localhost.localdomain [127.0.0.1]) by smtp.subspace.kernel.org (Postfix) with ESMTP id AB87C17E450; Fri, 26 Jul 2024 23:53:36 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="JYcYrN7S" X-Original-To: linux-cifs@vger.kernel.org Received: from mail-lf1-f47.google.com (mail-lf1-f47.google.com [209.85.167.47]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 5E19717D8B3 for ; Fri, 26 Jul 2024 23:53:33 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.167.47 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1722038016; cv=none; b=NeYG6VhmQB6a9LHCmixnffQ92cwDKN5PwP1JyjpwEEU3eZzigb6cSoNPofsXJISylkIXIdXsSiUIXkWvBF348pitSWhGUghvuOmWUdU7Zpg68/Y7ReJBbJmHWP36ano3xXKgn7mllFy8gQghdbl7tC5e6vgJ8GEwcHjX+HWOUhg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1722038016; c=relaxed/simple; bh=fXtpCa9jihWsqO8NdnOq4Q3SClK+H7AZ80FWLbKPHUE=; h=MIME-Version:From:Date:Message-ID:Subject:To:Cc:Content-Type; b=X6Bp+L+jpchJsEuMduBXvPexdEZy7+zGFlAeS0a3DLjh7HghlDqoTgc9mIZKOC1lFTfzO4ffoMmHToD6mR4k7S7wZLDrQgWaZRckeJnAkFPPNp5pbODlyOhqYFqrefjjnji/7z9zVaeCk40bNFXOjrjK5qZIXoZZ6B6nTclndwY= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=JYcYrN7S; arc=none smtp.client-ip=209.85.167.47 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Received: by mail-lf1-f47.google.com with SMTP id 2adb3069b0e04-52f04150796so2782338e87.3 for ; Fri, 26 Jul 2024 16:53:33 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1722038011; x=1722642811; darn=vger.kernel.org; h=cc:to:subject:message-id:date:from:mime-version:from:to:cc:subject :date:message-id:reply-to; bh=nnlIb8do12YhsqDoREzy6tkDteJQhUbA2FOMCTWaIjs=; b=JYcYrN7SlrEoB+719NIRsnnaNvr6JV4Mda5flC8fHkTz8y3z6SCRj2GFlD1P23/9tD JeDULbP0XidDIQ1XPblGQ8vhb92qf+fmes8Dt+3iMecq5gxQzhNPclyNP0MRqOZcL5g5 K/pfIwUMIbLY6CvYmlUboN/t5Gm8MtConFcmbh1P6O0wCJihb1x5tYuZWC4Ip9KoLhe0 8In8hDuZVQI7PErPKfQAQcqi4cfw+gOwCVbxOZVls5MpBcquQJoz1kPIJQn6MHVBxzvH /gxCzhqXhOQYcodKCz5cq1PY816D6b1mKupgb/rjV3SZdU12VAMLuVo3WaDjlNUkUG9/ urFQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1722038011; x=1722642811; h=cc:to:subject:message-id:date:from:mime-version:x-gm-message-state :from:to:cc:subject:date:message-id:reply-to; bh=nnlIb8do12YhsqDoREzy6tkDteJQhUbA2FOMCTWaIjs=; b=m0XAg/L7Xcu7krcEgwCIIUvFX1JtUYfXxv54fs/xFhEF9OlPfBDTDZMovmYLkCf/bU rxbpGX0/fvA+XrXZF9YW2CzV1UvwRb8rQ7hIX7O7kFZ226Tuoocscan8nEe0ag1D3C/B nGIC2+wPNIQWLxQqJ+qCLUZLuNFPaw99O/cSqMP7OQ3u3le+jhQGLDssBaaPdyPVzFhD u2Z9tZYq/V4Z2W5DtOusuhg3bw04fZ0A1tpFTLtq0RIDMrovY19LWQYWc9JtyGIfOsef ulbvAv1mgNxsZvoQjZ8S6fdz7ftuXFFz1/BH2sk5ulUI4sJaDStCMLXpu1qw3TdkaZf+ 2Fgg== X-Forwarded-Encrypted: i=1; AJvYcCVJFijehBL6ookE2RtRK/CNqUZHhgL49zGjnqx71H98i69hR5sbFCBxuhMNUeOjNV8GycU5YajcvrnCMt0zwNaWrUSRH4GosNGsSQ== X-Gm-Message-State: AOJu0Yz/NGgEMx77o14ANhDxEbc/H0lw1yGHtbteTpJg5gk0asc9Qb0m G5l9mac6V8QP1YqSeXED5dvTbZSTZkjmFtQE+QihZ5LipVytA1sfispapC4V1ViezarCgUa/bHS OMEw8Pm9KQPIKjR5YI7UOE67ojfQ= X-Google-Smtp-Source: AGHT+IG+6w+LL/GuKUJbWu9ZI954gcFSTMOgYv6qZOFqjYjPHxiCXiK3N8zVdFjj3sGzZ5UXLJbnM56Cb0PKLRdcgmw= X-Received: by 2002:a19:2d5b:0:b0:52e:f367:709b with SMTP id 2adb3069b0e04-5309b2c3056mr651160e87.42.1722038010826; Fri, 26 Jul 2024 16:53:30 -0700 (PDT) Precedence: bulk X-Mailing-List: linux-cifs@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 From: Steve French Date: Fri, 26 Jul 2024 18:53:18 -0500 Message-ID: Subject: [PATCH][SMB3] mark compression as CONFIG_EXPERIMENTAL and fix missing compression operation To: Enzo Matsumiya , CIFS Cc: samba-technical , Anthony Nandaa , Jun Ma 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 From 7db9a00c4d9bf4996177e4946241885234afb5c0 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 | 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 + * + * 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..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 + * + * Definitions and optmized helpers for LZ77 compression. + */ +#ifndef _SMB_COMPRESS_LZ77_H +#define _SMB_COMPRESS_LZ77_H + +#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; -- 2.43.0