summaryrefslogtreecommitdiff
path: root/fs/smb/client/compress.c
diff options
context:
space:
mode:
Diffstat (limited to 'fs/smb/client/compress.c')
-rw-r--r--fs/smb/client/compress.c208
1 files changed, 135 insertions, 73 deletions
diff --git a/fs/smb/client/compress.c b/fs/smb/client/compress.c
index 766b4de13da7..a6324bb15b8e 100644
--- a/fs/smb/client/compress.c
+++ b/fs/smb/client/compress.c
@@ -16,17 +16,18 @@
#include <linux/kernel.h>
#include <linux/uio.h>
#include <linux/sort.h>
+#include <linux/iov_iter.h>
#include "cifsglob.h"
-#include "../common/smb2pdu.h"
-#include "cifsproto.h"
-#include "smb2proto.h"
#include "compress/lz77.h"
#include "compress.h"
+#define SAMPLE_CHUNK_LEN SZ_2K
+#define SAMPLE_MAX_LEN SZ_2M
+
/*
- * The heuristic_*() functions below try to determine data compressibility.
+ * The functions below try to determine data compressibility.
*
* Derived from fs/btrfs/compression.c, changing coding style, some parameters, and removing
* unused parts.
@@ -34,7 +35,8 @@
* Read that file for better and more detailed explanation of the calculations.
*
* The algorithms are ran in a collected sample of the input (uncompressed) data.
- * The sample is formed of 2K reads in PAGE_SIZE intervals, with a maximum size of 4M.
+ * The sample is formed of 2K reads in PAGE_SIZE intervals, with a maximum size of 2M.
+ * Those are adjusted according to R/W bufsizes negotiated with the server.
*
* Parsing the sample goes from "low-hanging fruits" (fastest algorithms, likely compressible)
* to "need more analysis" (likely uncompressible).
@@ -154,61 +156,39 @@ static int cmp_bkt(const void *_a, const void *_b)
return 1;
}
-/*
- * TODO:
- * Support other iter types, if required.
- * Only ITER_XARRAY is supported for now.
- */
-static int collect_sample(const struct iov_iter *iter, ssize_t max, u8 *sample)
+static size_t collect_step(void *base, size_t progress, size_t len, void *priv_iov, void *priv_len)
{
- struct folio *folios[16], *folio;
- unsigned int nr, i, j, npages;
- loff_t start = iter->xarray_start + iter->iov_offset;
- pgoff_t last, index = start / PAGE_SIZE;
- size_t len, off, foff;
- void *p;
- int s = 0;
-
- last = (start + max - 1) / PAGE_SIZE;
- do {
- nr = xa_extract(iter->xarray, (void **)folios, index, last, ARRAY_SIZE(folios),
- XA_PRESENT);
- if (nr == 0)
- return -EIO;
-
- for (i = 0; i < nr; i++) {
- folio = folios[i];
- npages = folio_nr_pages(folio);
- foff = start - folio_pos(folio);
- off = foff % PAGE_SIZE;
-
- for (j = foff / PAGE_SIZE; j < npages; j++) {
- size_t len2;
-
- len = min_t(size_t, max, PAGE_SIZE - off);
- len2 = min_t(size_t, len, SZ_2K);
-
- p = kmap_local_page(folio_page(folio, j));
- memcpy(&sample[s], p, len2);
- kunmap_local(p);
-
- s += len2;
-
- if (len2 < SZ_2K || s >= max - SZ_2K)
- return s;
-
- max -= len;
- if (max <= 0)
- return s;
-
- start += len;
- off = 0;
- index++;
- }
- }
- } while (nr == ARRAY_SIZE(folios));
-
- return s;
+ size_t plen, *cur_len = priv_len;
+ struct kvec *iov = priv_iov;
+
+ if (progress >= iov->iov_len)
+ return len;
+
+ plen = min_t(size_t, len, SAMPLE_CHUNK_LEN);
+ memcpy(iov->iov_base + *cur_len, base, plen);
+
+ *cur_len += plen;
+
+ if (len < SAMPLE_CHUNK_LEN)
+ return len;
+
+ return 0;
+}
+
+static int collect_sample(const struct iov_iter *iter, u8 *sample, size_t max_sample_len)
+{
+ size_t ret, len = iov_iter_count(iter);
+ struct iov_iter it = *iter;
+ struct kvec iov = {
+ .iov_base = sample,
+ .iov_len = max_sample_len,
+ };
+
+ ret = iterate_and_advance_kernel(&it, len, &iov, &max_sample_len, collect_step);
+ if (ret > len || ret > max_sample_len)
+ return -EIO;
+
+ return 0;
}
/**
@@ -220,22 +200,18 @@ static int collect_sample(const struct iov_iter *iter, ssize_t max, u8 *sample)
* Tests shows that this function is quite reliable in predicting data compressibility,
* matching close to 1:1 with the behaviour of LZ77 compression success and failures.
*/
-static bool is_compressible(const struct iov_iter *data)
+static __maybe_unused bool is_compressible(const struct iov_iter *data)
{
- const size_t read_size = SZ_2K, bkt_size = 256, max = SZ_4M;
+ const size_t bkt_size = 256;
struct bucket *bkt = NULL;
size_t len;
u8 *sample;
bool ret = false;
int i;
- /* Preventive double check -- already checked in should_compress(). */
len = iov_iter_count(data);
- if (unlikely(len < read_size))
- return ret;
-
- if (len - read_size > max)
- len = max;
+ if (len > SAMPLE_MAX_LEN)
+ len = SAMPLE_MAX_LEN;
sample = kvzalloc(len, GFP_KERNEL);
if (!sample) {
@@ -245,16 +221,15 @@ static bool is_compressible(const struct iov_iter *data)
}
/* Sample 2K bytes per page of the uncompressed data. */
- i = collect_sample(data, len, sample);
- if (i <= 0) {
- WARN_ON_ONCE(1);
+ i = collect_sample(data, sample, len);
+ if (i < 0) {
+ WARN_ONCE(1, "data len=%zu, max sample len=%zu\n", iov_iter_count(data), len);
goto out;
}
- len = i;
ret = true;
-
+ len = i;
if (has_repeated_data(sample, len))
goto out;
@@ -292,12 +267,15 @@ out:
bool should_compress(const struct cifs_tcon *tcon, const struct smb_rqst *rq)
{
+ struct TCP_Server_Info *server;
const struct smb2_hdr *shdr = rq->rq_iov->iov_base;
if (unlikely(!tcon || !tcon->ses || !tcon->ses->server))
return false;
- if (!tcon->ses->server->compression.enabled)
+ server = tcon->ses->server;
+
+ if (!server->compression.enabled)
return false;
if (!(tcon->share_flags & SMB2_SHAREFLAG_COMPRESS_DATA))
@@ -315,12 +293,36 @@ bool should_compress(const struct cifs_tcon *tcon, const struct smb_rqst *rq)
return (shdr->Command == SMB2_READ);
}
+bool should_decompress(struct TCP_Server_Info *server, const void *buf)
+{
+ u32 len;
+
+ if (!is_compress_hdr(buf))
+ return false;
+
+ len = decompressed_size(buf);
+ if (len < SMB_COMPRESS_HDR_LEN + server->vals->read_rsp_size) {
+ pr_warn("CIFS: Compressed message too small (%u bytes)\n", len);
+
+ return false;
+ }
+
+ if (len > SMB_DECOMPRESS_MAX_LEN(server)) {
+ pr_warn("CIFS: Uncompressed message too big (%u bytes)\n", len);
+
+ return false;
+ }
+
+ return true;
+}
+
int smb_compress(struct TCP_Server_Info *server, struct smb_rqst *rq, compress_send_fn send_fn)
{
struct iov_iter iter;
u32 slen, dlen;
void *src, *dst = NULL;
int ret;
+ //unsigned long start;
if (!server || !rq || !rq->rq_iov || !rq->rq_iov->iov_base)
return -EINVAL;
@@ -354,12 +356,16 @@ int smb_compress(struct TCP_Server_Info *server, struct smb_rqst *rq, compress_s
goto err_free;
}
+ //start = jiffies;
ret = lz77_compress(src, slen, dst, &dlen);
+ //pr_err("%s: compress runtime=%ums, ret=%d, dlen=%u\n", __func__, jiffies_to_msecs(jiffies - start), ret, dlen);
if (!ret) {
struct smb2_compression_hdr hdr = { 0 };
struct smb_rqst comp_rq = { .rq_nvec = 3, };
struct kvec iov[3];
+ //pr_err("%s: compress runtime=%ums, dlen=%u\n", __func__, jiffies_to_msecs(jiffies - start), dlen);
+
hdr.ProtocolId = SMB2_COMPRESSION_TRANSFORM_ID;
hdr.OriginalCompressedSegmentSize = cpu_to_le32(slen);
hdr.CompressionAlgorithm = SMB3_COMPRESS_LZ77;
@@ -384,3 +390,59 @@ err_free:
return ret;
}
+
+int smb_decompress(const void *src, u32 slen, void *dst, u32 *dlen)
+{
+ const struct smb2_compression_hdr *hdr = src;
+ struct smb2_hdr *shdr;
+ u32 buf_len, data_len;
+ int ret;
+
+ if (hdr->CompressionAlgorithm != SMB3_COMPRESS_LZ77)
+ return -EIO;
+
+ buf_len = le32_to_cpu(hdr->Offset);
+ data_len = le32_to_cpu(hdr->OriginalCompressedSegmentSize);
+ if (unlikely(*dlen != buf_len + data_len))
+ return -EINVAL;
+
+ /*
+ * Copy uncompressed data from the beginning of the payload.
+ * The remainder is all compressed data.
+ */
+ src += SMB_COMPRESS_HDR_LEN;
+ slen -= SMB_COMPRESS_HDR_LEN;
+ memcpy(dst, src, buf_len);
+
+ src += buf_len;
+ slen -= buf_len;
+ *dlen = data_len;
+
+ ret = lz77_decompress(src, slen, dst + buf_len, dlen);
+ if (ret)
+ return ret;
+
+ shdr = dst;
+
+ if (*dlen != data_len) {
+ pr_warn("CIFS: Decompressed size mismatch: got %u, expected %u (mid=%llu)\n",
+ *dlen, data_len, le64_to_cpu(shdr->MessageId));
+
+ return -EINVAL;
+ }
+
+ if (shdr->ProtocolId != SMB2_PROTO_NUMBER) {
+ pr_warn("CIFS: Decompressed buffer is not an SMB2 message: ProtocolId 0x%x (mid=%llu)\n",
+ le32_to_cpu(shdr->ProtocolId), le64_to_cpu(shdr->MessageId));
+
+ return -EINVAL;
+ }
+
+ /*
+ * @dlen contains only the decompressed data size, add back the previously copied
+ * hdr->Offset size.
+ */
+ *dlen += buf_len;
+
+ return 0;
+}