xref: /linux/tools/perf/util/zstd.c (revision b2d4225695e6977cd7802fe425d8caa5822a5e7d)
1 // SPDX-License-Identifier: GPL-2.0
2 
3 #include <string.h>
4 #include <linux/perf_event.h>
5 
6 #include "util/compress.h"
7 #include "util/debug.h"
8 
9 int zstd_init(struct zstd_data *data, int level)
10 {
11 	data->comp_level = level;
12 	data->dstream = NULL;
13 	data->cstream = NULL;
14 	return 0;
15 }
16 
17 int zstd_fini(struct zstd_data *data)
18 {
19 	if (data->dstream) {
20 		ZSTD_freeDStream(data->dstream);
21 		data->dstream = NULL;
22 	}
23 
24 	if (data->cstream) {
25 		ZSTD_freeCStream(data->cstream);
26 		data->cstream = NULL;
27 	}
28 
29 	return 0;
30 }
31 
32 ssize_t zstd_compress_stream_to_records(struct zstd_data *data, void *dst, size_t dst_size,
33 				       void *src, size_t src_size, size_t max_record_size,
34 				       ssize_t process_header(void *record, size_t dst_size,
35 							      size_t data_size))
36 {
37 	size_t ret, compressed = 0;
38 	ssize_t size;
39 	ZSTD_inBuffer input = { src, src_size, 0 };
40 	ZSTD_outBuffer output;
41 	void *record;
42 
43 	if (!data->cstream) {
44 		data->cstream = ZSTD_createCStream();
45 		if (data->cstream == NULL) {
46 			pr_err("Couldn't create compression stream.\n");
47 			return -1;
48 		}
49 
50 		ret = ZSTD_initCStream(data->cstream, data->comp_level);
51 		if (ZSTD_isError(ret)) {
52 			pr_err("Failed to initialize compression stream: %s\n",
53 				ZSTD_getErrorName(ret));
54 			return -1;
55 		}
56 	}
57 
58 	while (input.pos < input.size) {
59 		record = dst;
60 		size = process_header(record, dst_size, 0);
61 		/* Output buffer full — cannot fit even the record header */
62 		if (size < 0)
63 			goto reset;
64 		compressed += size;
65 		dst += size;
66 		dst_size -= size;
67 		output = (ZSTD_outBuffer){ dst, (dst_size > max_record_size) ?
68 						max_record_size : dst_size, 0 };
69 		ret = ZSTD_compressStream(data->cstream, &output, &input);
70 		ZSTD_flushStream(data->cstream, &output);
71 		if (ZSTD_isError(ret)) {
72 			pr_err("failed to compress %ld bytes: %s\n",
73 				(long)src_size, ZSTD_getErrorName(ret));
74 			goto reset;
75 		}
76 		compressed += output.pos;
77 		dst += output.pos;
78 		dst_size -= output.pos;
79 		/*
80 		 * No progress: ZSTD couldn't emit any bytes into the
81 		 * remaining output buffer.  Calling process_header
82 		 * with output.pos=0 would re-trigger header initialization,
83 		 * double-subtracting the header size from dst_size and
84 		 * underflowing the unsigned counter.
85 		 */
86 		if (output.pos == 0)
87 			goto reset;
88 		size = process_header(record, dst_size, output.pos);
89 		if (size < 0)
90 			goto reset;
91 		compressed += size;
92 		dst += size;
93 		dst_size -= size;
94 	}
95 
96 	return compressed;
97 
98 reset:
99 	/* Reset so the context is usable if the caller retries */
100 	ret = ZSTD_initCStream(data->cstream, data->comp_level);
101 	if (ZSTD_isError(ret))
102 		pr_err("failed to reset compression context: %s\n",
103 			ZSTD_getErrorName(ret));
104 	return -1;
105 }
106 
107 size_t zstd_decompress_stream(struct zstd_data *data, void *src, size_t src_size,
108 			      void *dst, size_t dst_size)
109 {
110 	size_t ret;
111 	ZSTD_inBuffer input = { src, src_size, 0 };
112 	ZSTD_outBuffer output = { dst, dst_size, 0 };
113 
114 	if (!data->dstream) {
115 		data->dstream = ZSTD_createDStream();
116 		if (data->dstream == NULL) {
117 			pr_err("Couldn't create decompression stream.\n");
118 			return 0;
119 		}
120 
121 		ret = ZSTD_initDStream(data->dstream);
122 		if (ZSTD_isError(ret)) {
123 			pr_err("Failed to initialize decompression stream: %s\n",
124 				ZSTD_getErrorName(ret));
125 			return 0;
126 		}
127 	}
128 	while (input.pos < input.size) {
129 		size_t prev_in = input.pos;
130 		size_t prev_out = output.pos;
131 
132 		ret = ZSTD_decompressStream(data->dstream, &output, &input);
133 		if (ZSTD_isError(ret)) {
134 			pr_err("failed to decompress (B): %zd -> %zd, dst_size %zd : %s\n",
135 			       src_size, output.pos, dst_size, ZSTD_getErrorName(ret));
136 			return 0;
137 		}
138 		/*
139 		 * Neither stream advanced — decompression is stuck.
140 		 * Return 0 (error) rather than partial output: perf
141 		 * uses ZSTD_flushStream (not ZSTD_endStream), so the
142 		 * stream is continuous across compressed events.
143 		 * Discarding unconsumed input would desynchronize the
144 		 * decompressor, causing the next call to produce
145 		 * garbage that could be misinterpreted as valid events.
146 		 */
147 		if (input.pos == prev_in && output.pos == prev_out)
148 			return 0;
149 	}
150 
151 	return output.pos;
152 }
153