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