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