diff options
Diffstat (limited to 'fs/smb/client/compress.c')
-rw-r--r-- | fs/smb/client/compress.c | 208 |
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; +} |