xref: /freebsd/sys/contrib/openzfs/cmd/zstream/zstream_drop_record.c (revision 80aae8a3f8aa70712930664572be9e6885dc0be7)
1 // SPDX-License-Identifier: CDDL-1.0
2 /*
3  * CDDL HEADER START
4  *
5  * The contents of this file are subject to the terms of the
6  * Common Development and Distribution License (the "License").
7  * You may not use this file except in compliance with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or https://opensource.org/licenses/CDDL-1.0.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 
23 /*
24  * Copyright 2026 ConnectWise.  All rights reserved.
25  * Use is subject to license terms.
26  */
27 
28 #include <err.h>
29 #include <search.h>
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <unistd.h>
33 #include <sys/zfs_ioctl.h>
34 #include <sys/zio_checksum.h>
35 #include <sys/zstd/zstd.h>
36 #include "zfs_fletcher.h"
37 #include "zstream.h"
38 #include "zstream_util.h"
39 
40 int
zstream_do_drop_record(int argc,char * argv[])41 zstream_do_drop_record(int argc, char *argv[])
42 {
43 	const int KEYSIZE = 64;
44 	int bufsz = SPA_MAXBLOCKSIZE;
45 	char *buf = safe_malloc(bufsz);
46 	dmu_replay_record_t thedrr;
47 	dmu_replay_record_t *drr = &thedrr;
48 	zio_cksum_t stream_cksum;
49 	int c;
50 	boolean_t verbose = B_FALSE;
51 
52 	while ((c = getopt(argc, argv, "v")) != -1) {
53 		switch (c) {
54 		case 'v':
55 			verbose = B_TRUE;
56 			break;
57 		case '?':
58 			(void) fprintf(stderr, "invalid option '%c'\n",
59 			    optopt);
60 			zstream_usage();
61 			break;
62 		}
63 	}
64 
65 	argc -= optind;
66 	argv += optind;
67 
68 	if (argc < 0)
69 		zstream_usage();
70 
71 	if (hcreate(argc) == 0)
72 		errx(1, "hcreate");
73 	for (int i = 0; i < argc; i++) {
74 		uint64_t object, offset;
75 		char *obj_str;
76 		char *offset_str;
77 		char *key;
78 		char *end;
79 
80 		obj_str = strsep(&argv[i], ",");
81 		if (argv[i] == NULL) {
82 			zstream_usage();
83 			exit(2);
84 		}
85 		errno = 0;
86 		object = strtoull(obj_str, &end, 0);
87 		if (errno || *end != '\0')
88 			errx(1, "invalid value for object");
89 		offset_str = strsep(&argv[i], ",");
90 		offset = strtoull(offset_str, &end, 0);
91 		if (errno || *end != '\0')
92 			errx(1, "invalid value for offset");
93 
94 		if (asprintf(&key, "%llu,%llu", (u_longlong_t)object,
95 		    (u_longlong_t)offset) < 0) {
96 			err(1, "asprintf");
97 		}
98 		ENTRY e = {.key = key};
99 		ENTRY *p;
100 
101 		p = hsearch(e, ENTER);
102 		if (p == NULL)
103 			errx(1, "hsearch");
104 		p->data = (void*)(intptr_t)B_TRUE;
105 	}
106 
107 	if (isatty(STDIN_FILENO)) {
108 		(void) fprintf(stderr,
109 		    "Error: The send stream is a binary format "
110 		    "and can not be read from a\n"
111 		    "terminal.  Standard input must be redirected.\n");
112 		exit(1);
113 	}
114 
115 	fletcher_4_init();
116 	int begin = 0;
117 	boolean_t seen = B_FALSE;
118 	while (sfread(drr, sizeof (*drr), stdin) != 0) {
119 		struct drr_write *drrw;
120 		uint64_t payload_size = 0;
121 
122 		/*
123 		 * We need to regenerate the checksum.
124 		 */
125 		if (drr->drr_type != DRR_BEGIN) {
126 			memset(&drr->drr_u.drr_checksum.drr_checksum, 0,
127 			    sizeof (drr->drr_u.drr_checksum.drr_checksum));
128 		}
129 
130 		switch (drr->drr_type) {
131 		case DRR_BEGIN:
132 		{
133 			ZIO_SET_CHECKSUM(&stream_cksum, 0, 0, 0, 0);
134 			VERIFY0(begin++);
135 			seen = B_TRUE;
136 
137 			uint32_t sz = drr->drr_payloadlen;
138 
139 			VERIFY3U(sz, <=, 1U << 28);
140 
141 			if (sz != 0) {
142 				if (sz > bufsz) {
143 					buf = realloc(buf, sz);
144 					if (buf == NULL)
145 						err(1, "realloc");
146 					bufsz = sz;
147 				}
148 				(void) sfread(buf, sz, stdin);
149 			}
150 			payload_size = sz;
151 			break;
152 		}
153 		case DRR_END:
154 		{
155 			struct drr_end *drre = &drr->drr_u.drr_end;
156 			/*
157 			 * We would prefer to just check --begin == 0, but
158 			 * replication streams have an end of stream END
159 			 * record, so we must avoid tripping it.
160 			 */
161 			VERIFY3B(seen, ==, B_TRUE);
162 			begin--;
163 			/*
164 			 * Use the recalculated checksum, unless this is
165 			 * the END record of a stream package, which has
166 			 * no checksum.
167 			 */
168 			if (!ZIO_CHECKSUM_IS_ZERO(&drre->drr_checksum))
169 				drre->drr_checksum = stream_cksum;
170 			break;
171 		}
172 
173 		case DRR_OBJECT:
174 		{
175 			struct drr_object *drro = &drr->drr_u.drr_object;
176 			VERIFY3S(begin, ==, 1);
177 
178 			if (drro->drr_bonuslen > 0) {
179 				payload_size = DRR_OBJECT_PAYLOAD_SIZE(drro);
180 				(void) sfread(buf, payload_size, stdin);
181 			}
182 			break;
183 		}
184 
185 		case DRR_SPILL:
186 		{
187 			struct drr_spill *drrs = &drr->drr_u.drr_spill;
188 			VERIFY3S(begin, ==, 1);
189 			payload_size = DRR_SPILL_PAYLOAD_SIZE(drrs);
190 			(void) sfread(buf, payload_size, stdin);
191 			break;
192 		}
193 
194 		case DRR_WRITE_BYREF:
195 			VERIFY3S(begin, ==, 1);
196 			fprintf(stderr,
197 			    "Deduplicated streams are not supported\n");
198 			exit(1);
199 			break;
200 
201 		case DRR_WRITE:
202 		{
203 			VERIFY3S(begin, ==, 1);
204 			drrw = &thedrr.drr_u.drr_write;
205 			payload_size = DRR_WRITE_PAYLOAD_SIZE(drrw);
206 			ENTRY *p;
207 			char key[KEYSIZE];
208 
209 			snprintf(key, KEYSIZE, "%llu,%llu",
210 			    (u_longlong_t)drrw->drr_object,
211 			    (u_longlong_t)drrw->drr_offset);
212 			ENTRY e = {.key = key};
213 
214 			(void) sfread(buf, payload_size, stdin);
215 			p = hsearch(e, FIND);
216 			if (p == NULL) {
217 				/*
218 				 * Dump the contents of the block unaltered
219 				 */
220 			} else {
221 				/*
222 				 * Read and discard the block
223 				 */
224 				if (verbose)
225 					fprintf(stderr,
226 					    "Dropping WRITE record for object "
227 					    "%llu offset %llu\n",
228 					    (u_longlong_t)drrw->drr_object,
229 					    (u_longlong_t)drrw->drr_offset);
230 				continue;
231 			}
232 			break;
233 		}
234 
235 		case DRR_WRITE_EMBEDDED:
236 		{
237 			ENTRY *p;
238 			char key[KEYSIZE];
239 
240 			VERIFY3S(begin, ==, 1);
241 			struct drr_write_embedded *drrwe =
242 			    &drr->drr_u.drr_write_embedded;
243 			payload_size =
244 			    P2ROUNDUP((uint64_t)drrwe->drr_psize, 8);
245 
246 			snprintf(key, KEYSIZE, "%llu,%llu",
247 			    (u_longlong_t)drrwe->drr_object,
248 			    (u_longlong_t)drrwe->drr_offset);
249 			ENTRY e = {.key = key};
250 
251 			(void) sfread(buf, payload_size, stdin);
252 			p = hsearch(e, FIND);
253 			if (p == NULL) {
254 				/*
255 				 * Dump the contents of the block unaltered
256 				 */
257 			} else {
258 				/*
259 				 * Read and discard the block
260 				 */
261 				if (verbose)
262 					fprintf(stderr,
263 					    "Dropping WRITE_EMBEDDED record for"
264 					    " object %llu offset %llu\n",
265 					    (u_longlong_t)drrwe->drr_object,
266 					    (u_longlong_t)drrwe->drr_offset);
267 				continue;
268 			}
269 			break;
270 		}
271 
272 		case DRR_FREEOBJECTS:
273 		case DRR_FREE:
274 		case DRR_OBJECT_RANGE:
275 			VERIFY3S(begin, ==, 1);
276 			break;
277 
278 		default:
279 			(void) fprintf(stderr, "INVALID record type 0x%x\n",
280 			    drr->drr_type);
281 			/* should never happen, so assert */
282 			assert(B_FALSE);
283 		}
284 
285 		if (feof(stdout)) {
286 			fprintf(stderr, "Error: unexpected end-of-file\n");
287 			exit(1);
288 		}
289 		if (ferror(stdout)) {
290 			fprintf(stderr, "Error while reading file: %s\n",
291 			    strerror(errno));
292 			exit(1);
293 		}
294 
295 		/*
296 		 * We need to recalculate the checksum, and it needs to be
297 		 * initially zero to do that.  BEGIN records don't have
298 		 * a checksum.
299 		 */
300 		if (drr->drr_type != DRR_BEGIN) {
301 			memset(&drr->drr_u.drr_checksum.drr_checksum, 0,
302 			    sizeof (drr->drr_u.drr_checksum.drr_checksum));
303 		}
304 		if (dump_record(drr, buf, payload_size,
305 		    &stream_cksum, STDOUT_FILENO) != 0)
306 			break;
307 		if (drr->drr_type == DRR_END) {
308 			/*
309 			 * Typically the END record is either the last
310 			 * thing in the stream, or it is followed
311 			 * by a BEGIN record (which also zeros the checksum).
312 			 * However, a stream package ends with two END
313 			 * records.  The last END record's checksum starts
314 			 * from zero.
315 			 */
316 			ZIO_SET_CHECKSUM(&stream_cksum, 0, 0, 0, 0);
317 		}
318 	}
319 	free(buf);
320 	fletcher_4_fini();
321 	hdestroy();
322 
323 	return (0);
324 }
325