summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--fs/smb/client/compress.c6
-rw-r--r--fs/smb/client/compress/lz77.c37
-rw-r--r--fs/smb/client/compress/lz77.h1
3 files changed, 28 insertions, 16 deletions
diff --git a/fs/smb/client/compress.c b/fs/smb/client/compress.c
index db709f5cd2e1..db18c93f8ee8 100644
--- a/fs/smb/client/compress.c
+++ b/fs/smb/client/compress.c
@@ -314,11 +314,7 @@ int smb_compress(struct TCP_Server_Info *server, struct smb_rqst *rq, compress_s
goto err_free;
}
- /*
- * This is just overprovisioning, as the algorithm will error out if @dst reaches 7/8
- * of @slen.
- */
- dlen = slen;
+ dlen = lz77_calc_dlen(slen);
dst = kvzalloc(dlen, GFP_KERNEL);
if (!dst) {
ret = -ENOMEM;
diff --git a/fs/smb/client/compress/lz77.c b/fs/smb/client/compress/lz77.c
index 80c57175769d..1d0cb4a22f5f 100644
--- a/fs/smb/client/compress/lz77.c
+++ b/fs/smb/client/compress/lz77.c
@@ -131,6 +131,26 @@ static __always_inline void *lz77_write_match(void *dst, void **nib, u32 dist, u
return dst + 4;
}
+/*
+ * Compute compressed (dst) buffer length based on uncompressed (src) length.
+ *
+ * Accounts for metadata and overprovision for the worst case scenario.
+ *
+ * Metadata is a 4-byte flag.
+ * A flag is written:
+ * - on dst begin (pos 0)
+ * - when flag count is 32 (1 literal or 1 match increments counter)
+ * - on end-of-stream (end of dst)
+ *
+ * Worst case scenario is an all-literal compression, this means:
+ * metadata_bytes = 4 + ((@slen / 32) * 4) + 4, or reduced (@slen >> 3) + 8
+ * -> @slen + metadata_bytes
+ */
+u32 lz77_calc_dlen(u32 slen)
+{
+ return slen + (slen >> 3) + 8;
+}
+
noinline int lz77_compress(const void *src, u32 slen, void *dst, u32 *dlen)
{
const void *srcp, *end;
@@ -150,7 +170,12 @@ noinline int lz77_compress(const void *src, u32 slen, void *dst, u32 *dlen)
if (!htable)
return -ENOMEM;
- /* Main loop. */
+ /*
+ * Main loop.
+ *
+ * Assuming @dlen was computed with lz77_calc_dlen(), we can run without bound checking
+ * @dst.
+ */
do {
u32 dist, len = 0;
const void *wnd;
@@ -182,15 +207,6 @@ noinline int lz77_compress(const void *src, u32 slen, void *dst, u32 *dlen)
continue;
}
- /*
- * Bail out if @dstp reached >= 7/8 of @slen -- already compressed badly, not worth
- * going further.
- */
- if (unlikely(dstp - dst >= slen - (slen >> 3))) {
- *dlen = slen;
- goto out;
- }
-
dstp = lz77_write_match(dstp, &nib, dist, len);
srcp += len;
@@ -227,7 +243,6 @@ noinline int lz77_compress(const void *src, u32 slen, void *dst, u32 *dlen)
lz77_write32(flag_pos, flag);
*dlen = dstp - dst;
-out:
kvfree(htable);
if (*dlen < slen)
diff --git a/fs/smb/client/compress/lz77.h b/fs/smb/client/compress/lz77.h
index cdcb191b48a2..3c75b70b51b0 100644
--- a/fs/smb/client/compress/lz77.h
+++ b/fs/smb/client/compress/lz77.h
@@ -11,5 +11,6 @@
#include <linux/kernel.h>
+u32 lz77_calc_dlen(u32 slen);
int lz77_compress(const void *src, u32 slen, void *dst, u32 *dlen);
#endif /* _SMB_COMPRESS_LZ77_H */