1*22649d4dSMartin Matuska // SPDX-License-Identifier: CDDL-1.0
2*22649d4dSMartin Matuska /*
3*22649d4dSMartin Matuska * This file and its contents are supplied under the terms of the
4*22649d4dSMartin Matuska * Common Development and Distribution License ("CDDL"), version 1.0.
5*22649d4dSMartin Matuska * You may only use this file in accordance with the terms of version
6*22649d4dSMartin Matuska * 1.0 of the CDDL.
7*22649d4dSMartin Matuska *
8*22649d4dSMartin Matuska * A full copy of the text of the CDDL should have accompanied this
9*22649d4dSMartin Matuska * source. A copy of the CDDL is also available via the Internet at
10*22649d4dSMartin Matuska * https://opensource.org/license/CDDL-1.0.
11*22649d4dSMartin Matuska */
12*22649d4dSMartin Matuska
13*22649d4dSMartin Matuska /*
14*22649d4dSMartin Matuska * Copyright 2026 Klara, Inc.
15*22649d4dSMartin Matuska */
16*22649d4dSMartin Matuska
17*22649d4dSMartin Matuska #ifdef __FreeBSD__
18*22649d4dSMartin Matuska #include <sys/disk.h>
19*22649d4dSMartin Matuska #endif
20*22649d4dSMartin Matuska #include <sys/dmu.h>
21*22649d4dSMartin Matuska #include <sys/ioctl.h>
22*22649d4dSMartin Matuska #include <sys/spa.h>
23*22649d4dSMartin Matuska #include <sys/stat.h>
24*22649d4dSMartin Matuska #include <sys/uio.h>
25*22649d4dSMartin Matuska #include <sys/zap.h>
26*22649d4dSMartin Matuska #include <sys/zap_impl.h>
27*22649d4dSMartin Matuska #include <sys/zfs_ioctl.h>
28*22649d4dSMartin Matuska #include <sys/zio.h>
29*22649d4dSMartin Matuska #include <sys/zstd/zstd.h>
30*22649d4dSMartin Matuska #include <sys/zvol.h>
31*22649d4dSMartin Matuska #include <err.h>
32*22649d4dSMartin Matuska #include <libnvpair.h>
33*22649d4dSMartin Matuska #ifdef __linux__
34*22649d4dSMartin Matuska #include <linux/falloc.h>
35*22649d4dSMartin Matuska #include <linux/fs.h>
36*22649d4dSMartin Matuska #endif
37*22649d4dSMartin Matuska #include <stddef.h>
38*22649d4dSMartin Matuska #include <stdio.h>
39*22649d4dSMartin Matuska #include <stdlib.h>
40*22649d4dSMartin Matuska #include <string.h>
41*22649d4dSMartin Matuska #include <unistd.h>
42*22649d4dSMartin Matuska
43*22649d4dSMartin Matuska #include "zstream.h"
44*22649d4dSMartin Matuska #include "zstream_modules.h"
45*22649d4dSMartin Matuska #include "zstream_util.h"
46*22649d4dSMartin Matuska
47*22649d4dSMartin Matuska /*
48*22649d4dSMartin Matuska * Supported feature flags (in drr_versioninfo)
49*22649d4dSMartin Matuska */
50*22649d4dSMartin Matuska #define SUPPORTED_FEATURES (DMU_BACKUP_FEATURE_EMBED_DATA | \
51*22649d4dSMartin Matuska DMU_BACKUP_FEATURE_LZ4 | DMU_BACKUP_FEATURE_LARGE_BLOCKS | \
52*22649d4dSMartin Matuska DMU_BACKUP_FEATURE_COMPRESSED | DMU_BACKUP_FEATURE_ZSTD)
53*22649d4dSMartin Matuska
54*22649d4dSMartin Matuska typedef struct {
55*22649d4dSMartin Matuska struct raw_stream {
56*22649d4dSMartin Matuska uint64_t guid;
57*22649d4dSMartin Matuska boolean_t inprop;
58*22649d4dSMartin Matuska boolean_t inzvol;
59*22649d4dSMartin Matuska } stream;
60*22649d4dSMartin Matuska struct raw_volume {
61*22649d4dSMartin Matuska int fd;
62*22649d4dSMartin Matuska size_t size;
63*22649d4dSMartin Matuska uint64_t sectorsize;
64*22649d4dSMartin Matuska boolean_t isreg;
65*22649d4dSMartin Matuska boolean_t punch_holes;
66*22649d4dSMartin Matuska unsigned long freeop;
67*22649d4dSMartin Matuska } volume;
68*22649d4dSMartin Matuska struct raw_limits {
69*22649d4dSMartin Matuska long iov_max;
70*22649d4dSMartin Matuska long buffers_max;
71*22649d4dSMartin Matuska long pagesize;
72*22649d4dSMartin Matuska } limits;
73*22649d4dSMartin Matuska struct raw_zeros {
74*22649d4dSMartin Matuska struct iovec *iov;
75*22649d4dSMartin Matuska } zeros;
76*22649d4dSMartin Matuska struct raw_buffer {
77*22649d4dSMartin Matuska struct iovec *iov;
78*22649d4dSMartin Matuska off_t position;
79*22649d4dSMartin Matuska size_t length;
80*22649d4dSMartin Matuska int iovcnt;
81*22649d4dSMartin Matuska } buffer;
82*22649d4dSMartin Matuska } raw_context_t;
83*22649d4dSMartin Matuska
84*22649d4dSMartin Matuska static void
write_zeros(raw_context_t * context,off_t offset,size_t length)85*22649d4dSMartin Matuska write_zeros(raw_context_t *context, off_t offset, size_t length)
86*22649d4dSMartin Matuska {
87*22649d4dSMartin Matuska ASSERT3U(offset + length, >=, offset);
88*22649d4dSMartin Matuska
89*22649d4dSMartin Matuska struct raw_limits *limits = &context->limits;
90*22649d4dSMartin Matuska int iovcnt = MIN(howmany(length, limits->pagesize), limits->iov_max);
91*22649d4dSMartin Matuska if (iovcnt == 0)
92*22649d4dSMartin Matuska return;
93*22649d4dSMartin Matuska
94*22649d4dSMartin Matuska int fd = context->volume.fd;
95*22649d4dSMartin Matuska long pagesize = limits->pagesize;
96*22649d4dSMartin Matuska struct iovec *iov = context->zeros.iov;
97*22649d4dSMartin Matuska size_t resid = length;
98*22649d4dSMartin Matuska while (resid > 0) {
99*22649d4dSMartin Matuska size_t iovsz = resid;
100*22649d4dSMartin Matuska int i;
101*22649d4dSMartin Matuska
102*22649d4dSMartin Matuska for (i = 0; i < iovcnt && resid > 0; i++) {
103*22649d4dSMartin Matuska iov[i].iov_len = MIN(resid, pagesize);
104*22649d4dSMartin Matuska resid -= iov[i].iov_len;
105*22649d4dSMartin Matuska }
106*22649d4dSMartin Matuska ssize_t res = pwritev(fd, iov, i, offset);
107*22649d4dSMartin Matuska if (res < 0)
108*22649d4dSMartin Matuska err(EXIT_FAILURE, "pwritev");
109*22649d4dSMartin Matuska iovsz -= resid;
110*22649d4dSMartin Matuska VERIFY3U(res, ==, iovsz);
111*22649d4dSMartin Matuska offset += iovsz;
112*22649d4dSMartin Matuska }
113*22649d4dSMartin Matuska ASSERT0(resid);
114*22649d4dSMartin Matuska }
115*22649d4dSMartin Matuska
116*22649d4dSMartin Matuska /*
117*22649d4dSMartin Matuska * buffer_write - pwrite with buffer vectoring and error handling
118*22649d4dSMartin Matuska *
119*22649d4dSMartin Matuska * Appends buf to a buffer vector, issuing the pending vector if not contiguous.
120*22649d4dSMartin Matuska * Ownership of buf is taken; it will be freed after issuing the write.
121*22649d4dSMartin Matuska */
122*22649d4dSMartin Matuska static void
buffer_write(raw_context_t * context,void * buf,size_t nbytes,off_t offset)123*22649d4dSMartin Matuska buffer_write(raw_context_t *context, void *buf, size_t nbytes, off_t offset)
124*22649d4dSMartin Matuska {
125*22649d4dSMartin Matuska struct raw_limits *limits = &context->limits;
126*22649d4dSMartin Matuska struct raw_buffer *buffer = &context->buffer;
127*22649d4dSMartin Matuska struct iovec *iov = buffer->iov;
128*22649d4dSMartin Matuska
129*22649d4dSMartin Matuska if (buffer->iovcnt == 0)
130*22649d4dSMartin Matuska buffer->position = offset;
131*22649d4dSMartin Matuska else if (buffer->position + buffer->length != offset ||
132*22649d4dSMartin Matuska buffer->iovcnt == limits->buffers_max) {
133*22649d4dSMartin Matuska ASSERT3U(offset + nbytes, >=, offset);
134*22649d4dSMartin Matuska ASSERT3U(buffer->iovcnt, >, 0);
135*22649d4dSMartin Matuska ssize_t res = pwritev(context->volume.fd, iov, buffer->iovcnt,
136*22649d4dSMartin Matuska buffer->position);
137*22649d4dSMartin Matuska if (res < 0)
138*22649d4dSMartin Matuska err(EXIT_FAILURE, "pwritev");
139*22649d4dSMartin Matuska VERIFY3U(res, ==, buffer->length);
140*22649d4dSMartin Matuska buffer->position = offset;
141*22649d4dSMartin Matuska buffer->length = 0;
142*22649d4dSMartin Matuska for (int i = 0; i < buffer->iovcnt; i++) {
143*22649d4dSMartin Matuska free(iov[i].iov_base);
144*22649d4dSMartin Matuska iov[i].iov_base = NULL;
145*22649d4dSMartin Matuska }
146*22649d4dSMartin Matuska buffer->iovcnt = 0;
147*22649d4dSMartin Matuska }
148*22649d4dSMartin Matuska if (buf == NULL) {
149*22649d4dSMartin Matuska /* Sentinel buf for flush. */
150*22649d4dSMartin Matuska ASSERT0(buffer->iovcnt);
151*22649d4dSMartin Matuska return;
152*22649d4dSMartin Matuska }
153*22649d4dSMartin Matuska iov[buffer->iovcnt].iov_base = buf;
154*22649d4dSMartin Matuska iov[buffer->iovcnt].iov_len = nbytes;
155*22649d4dSMartin Matuska buffer->length += nbytes;
156*22649d4dSMartin Matuska buffer->iovcnt++;
157*22649d4dSMartin Matuska }
158*22649d4dSMartin Matuska
159*22649d4dSMartin Matuska static inline void
buffer_finish(raw_context_t * context)160*22649d4dSMartin Matuska buffer_finish(raw_context_t *context)
161*22649d4dSMartin Matuska {
162*22649d4dSMartin Matuska buffer_write(context, NULL, 0, 0);
163*22649d4dSMartin Matuska if (fsync(context->volume.fd) != 0)
164*22649d4dSMartin Matuska err(EXIT_FAILURE, "fsync");
165*22649d4dSMartin Matuska }
166*22649d4dSMartin Matuska
167*22649d4dSMartin Matuska static inline void
resize(raw_context_t * context,size_t size)168*22649d4dSMartin Matuska resize(raw_context_t *context, size_t size)
169*22649d4dSMartin Matuska {
170*22649d4dSMartin Matuska if (ftruncate(context->volume.fd, size) < 0)
171*22649d4dSMartin Matuska err(EXIT_FAILURE, "ftruncate");
172*22649d4dSMartin Matuska }
173*22649d4dSMartin Matuska
174*22649d4dSMartin Matuska static inline void
free_tail(raw_context_t * context,off_t offset)175*22649d4dSMartin Matuska free_tail(raw_context_t *context, off_t offset)
176*22649d4dSMartin Matuska {
177*22649d4dSMartin Matuska /*
178*22649d4dSMartin Matuska * This style of FREE is frequently a large range covering most of the
179*22649d4dSMartin Matuska * volume from the offset to the end. Truncating the file and extending
180*22649d4dSMartin Matuska * it back out works cheaply even on filesystems that do not support
181*22649d4dSMartin Matuska * hole punching, avoiding the need to write zeros.
182*22649d4dSMartin Matuska */
183*22649d4dSMartin Matuska resize(context, offset);
184*22649d4dSMartin Matuska if (offset < context->volume.size)
185*22649d4dSMartin Matuska resize(context, context->volume.size);
186*22649d4dSMartin Matuska else
187*22649d4dSMartin Matuska /* Volume size unknown, but it must be at least this big. */
188*22649d4dSMartin Matuska context->volume.size = offset;
189*22649d4dSMartin Matuska }
190*22649d4dSMartin Matuska
191*22649d4dSMartin Matuska static inline boolean_t
punch_hole(raw_context_t * context,off_t offset,size_t length)192*22649d4dSMartin Matuska punch_hole(raw_context_t *context, off_t offset, size_t length)
193*22649d4dSMartin Matuska {
194*22649d4dSMartin Matuska int fd = context->volume.fd;
195*22649d4dSMartin Matuska
196*22649d4dSMartin Matuska #if defined(__FreeBSD__)
197*22649d4dSMartin Matuska struct spacectl_range range = { offset, length };
198*22649d4dSMartin Matuska return (fspacectl(fd, SPACECTL_DEALLOC, &range, 0, NULL) == 0);
199*22649d4dSMartin Matuska #elif defined(__linux__)
200*22649d4dSMartin Matuska int mode = FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE;
201*22649d4dSMartin Matuska return (fallocate(fd, mode, offset, length) == 0);
202*22649d4dSMartin Matuska #else
203*22649d4dSMartin Matuska (void) fd, (void) offset, (void) length;
204*22649d4dSMartin Matuska return (B_FALSE);
205*22649d4dSMartin Matuska #endif
206*22649d4dSMartin Matuska }
207*22649d4dSMartin Matuska
208*22649d4dSMartin Matuska static boolean_t
free_blocks(raw_context_t * context,off_t offset,size_t length)209*22649d4dSMartin Matuska free_blocks(raw_context_t *context, off_t offset, size_t length)
210*22649d4dSMartin Matuska {
211*22649d4dSMartin Matuska /*
212*22649d4dSMartin Matuska * Ensure sector alignment in case the source zvol's sector size is
213*22649d4dSMartin Matuska * smaller than the target device's.
214*22649d4dSMartin Matuska *
215*22649d4dSMartin Matuska * TODO: Optional secure erase.
216*22649d4dSMartin Matuska */
217*22649d4dSMartin Matuska uint64_t sectorsize = context->volume.sectorsize;
218*22649d4dSMartin Matuska uint64_t start = P2ROUNDUP(offset, sectorsize);
219*22649d4dSMartin Matuska uint64_t limit = offset + length;
220*22649d4dSMartin Matuska uint64_t end = P2ALIGN_TYPED(limit, sectorsize, uint64_t);
221*22649d4dSMartin Matuska if (offset < start)
222*22649d4dSMartin Matuska write_zeros(context, offset, start - offset);
223*22649d4dSMartin Matuska if (start < end) {
224*22649d4dSMartin Matuska #ifdef __FreeBSD__
225*22649d4dSMartin Matuska off_t range[2];
226*22649d4dSMartin Matuska #else
227*22649d4dSMartin Matuska uint64_t range[2];
228*22649d4dSMartin Matuska #endif
229*22649d4dSMartin Matuska int fd = context->volume.fd;
230*22649d4dSMartin Matuska
231*22649d4dSMartin Matuska range[0] = start;
232*22649d4dSMartin Matuska range[1] = end - start;
233*22649d4dSMartin Matuska if (ioctl(fd, context->volume.freeop, range) != 0) {
234*22649d4dSMartin Matuska ASSERT3U(errno, ==, EOPNOTSUPP);
235*22649d4dSMartin Matuska context->volume.freeop = B_FALSE;
236*22649d4dSMartin Matuska return (B_FALSE);
237*22649d4dSMartin Matuska }
238*22649d4dSMartin Matuska }
239*22649d4dSMartin Matuska if (end < limit)
240*22649d4dSMartin Matuska write_zeros(context, end, limit - end);
241*22649d4dSMartin Matuska return (B_TRUE);
242*22649d4dSMartin Matuska }
243*22649d4dSMartin Matuska
244*22649d4dSMartin Matuska static void
free_range(raw_context_t * context,off_t offset,size_t length)245*22649d4dSMartin Matuska free_range(raw_context_t *context, off_t offset, size_t length)
246*22649d4dSMartin Matuska {
247*22649d4dSMartin Matuska if (context->volume.isreg) {
248*22649d4dSMartin Matuska if (length == (size_t)-1) {
249*22649d4dSMartin Matuska free_tail(context, offset);
250*22649d4dSMartin Matuska return;
251*22649d4dSMartin Matuska }
252*22649d4dSMartin Matuska if (context->volume.punch_holes) {
253*22649d4dSMartin Matuska if (punch_hole(context, offset, length))
254*22649d4dSMartin Matuska return;
255*22649d4dSMartin Matuska context->volume.punch_holes = B_FALSE;
256*22649d4dSMartin Matuska }
257*22649d4dSMartin Matuska }
258*22649d4dSMartin Matuska if (length == (size_t)-1)
259*22649d4dSMartin Matuska length = context->volume.size - offset;
260*22649d4dSMartin Matuska if (context->volume.freeop && free_blocks(context, offset, length))
261*22649d4dSMartin Matuska return;
262*22649d4dSMartin Matuska /* If all else fails, the range must be zeroed the slow way. */
263*22649d4dSMartin Matuska write_zeros(context, offset, length);
264*22649d4dSMartin Matuska }
265*22649d4dSMartin Matuska
266*22649d4dSMartin Matuska /*
267*22649d4dSMartin Matuska * apply_properties - read the properties zap to adjust file size.
268*22649d4dSMartin Matuska *
269*22649d4dSMartin Matuska * Returns the value of the "size" property.
270*22649d4dSMartin Matuska */
271*22649d4dSMartin Matuska static uint64_t
apply_properties(raw_context_t * context,void * buf,size_t len)272*22649d4dSMartin Matuska apply_properties(raw_context_t *context, void *buf, size_t len)
273*22649d4dSMartin Matuska {
274*22649d4dSMartin Matuska const mzap_phys_t *mzap = buf;
275*22649d4dSMartin Matuska
276*22649d4dSMartin Matuska ASSERT(context->volume.isreg);
277*22649d4dSMartin Matuska ASSERT3U(len, >=, sizeof (*mzap));
278*22649d4dSMartin Matuska ASSERT3U(MZAP_ENT_LEN, ==, sizeof (mzap_ent_phys_t));
279*22649d4dSMartin Matuska
280*22649d4dSMartin Matuska if (mzap->mz_block_type == BSWAP_64(ZBT_MICRO))
281*22649d4dSMartin Matuska zap_byteswap(buf, len);
282*22649d4dSMartin Matuska
283*22649d4dSMartin Matuska ASSERT3U(mzap->mz_block_type, ==, ZBT_MICRO);
284*22649d4dSMartin Matuska ASSERT0(strcmp(mzap->mz_chunk[0].mze_name, "size"));
285*22649d4dSMartin Matuska
286*22649d4dSMartin Matuska uint64_t size = mzap->mz_chunk[0].mze_value;
287*22649d4dSMartin Matuska resize(context, size);
288*22649d4dSMartin Matuska return (size);
289*22649d4dSMartin Matuska }
290*22649d4dSMartin Matuska
291*22649d4dSMartin Matuska static disposition_t
chain_replay_raw(void * item_in,void * context_in)292*22649d4dSMartin Matuska chain_replay_raw(void *item_in, void *context_in)
293*22649d4dSMartin Matuska {
294*22649d4dSMartin Matuska drr_packet_t *item = item_in;
295*22649d4dSMartin Matuska if (item == NULL)
296*22649d4dSMartin Matuska return (D_OK);
297*22649d4dSMartin Matuska
298*22649d4dSMartin Matuska raw_context_t *context = context_in;
299*22649d4dSMartin Matuska dmu_replay_record_t *drr = &item->dp_drr;
300*22649d4dSMartin Matuska
301*22649d4dSMartin Matuska switch (drr->drr_type) {
302*22649d4dSMartin Matuska case DRR_BEGIN: {
303*22649d4dSMartin Matuska struct drr_begin *drrb = &drr->drr_u.drr_begin;
304*22649d4dSMartin Matuska
305*22649d4dSMartin Matuska uint64_t featureflags, unsupported_features;
306*22649d4dSMartin Matuska featureflags = DMU_GET_FEATUREFLAGS(drrb->drr_versioninfo);
307*22649d4dSMartin Matuska unsupported_features = featureflags & ~SUPPORTED_FEATURES;
308*22649d4dSMartin Matuska if (unsupported_features != 0)
309*22649d4dSMartin Matuska errx(EXIT_FAILURE, "unsupported stream features: "
310*22649d4dSMartin Matuska "%#llx of %#llx, aborting...",
311*22649d4dSMartin Matuska (u_longlong_t)unsupported_features,
312*22649d4dSMartin Matuska (u_longlong_t)featureflags);
313*22649d4dSMartin Matuska
314*22649d4dSMartin Matuska int hdrtype = DMU_GET_STREAM_HDRTYPE(drrb->drr_versioninfo);
315*22649d4dSMartin Matuska if (hdrtype == DMU_SUBSTREAM) {
316*22649d4dSMartin Matuska uint64_t guid = context->stream.guid;
317*22649d4dSMartin Matuska if (guid != 0 && drrb->drr_fromguid != guid)
318*22649d4dSMartin Matuska errx(EXIT_FAILURE, "wrong fromguid: "
319*22649d4dSMartin Matuska "%llu != %llu, aborting...",
320*22649d4dSMartin Matuska (u_longlong_t)drrb->drr_fromguid,
321*22649d4dSMartin Matuska (u_longlong_t)guid);
322*22649d4dSMartin Matuska context->stream.guid = drrb->drr_toguid;
323*22649d4dSMartin Matuska }
324*22649d4dSMartin Matuska break;
325*22649d4dSMartin Matuska }
326*22649d4dSMartin Matuska case DRR_OBJECT: {
327*22649d4dSMartin Matuska struct drr_object *drro = &drr->drr_u.drr_object;
328*22649d4dSMartin Matuska
329*22649d4dSMartin Matuska context->stream.inzvol = drro->drr_object == ZVOL_OBJ &&
330*22649d4dSMartin Matuska drro->drr_type == DMU_OT_ZVOL;
331*22649d4dSMartin Matuska context->stream.inprop = drro->drr_object == ZVOL_ZAP_OBJ &&
332*22649d4dSMartin Matuska drro->drr_type == DMU_OT_ZVOL_PROP;
333*22649d4dSMartin Matuska break;
334*22649d4dSMartin Matuska }
335*22649d4dSMartin Matuska case DRR_WRITE: {
336*22649d4dSMartin Matuska struct drr_write *drrw = &drr->drr_u.drr_write;
337*22649d4dSMartin Matuska
338*22649d4dSMartin Matuska if (context->stream.inzvol) {
339*22649d4dSMartin Matuska buffer_write(context, item->dp_payload,
340*22649d4dSMartin Matuska item->dp_payload_size, drrw->drr_offset);
341*22649d4dSMartin Matuska /*
342*22649d4dSMartin Matuska * The buffer is no longer owned by the chain. We will
343*22649d4dSMartin Matuska * free it when safe.
344*22649d4dSMartin Matuska */
345*22649d4dSMartin Matuska item->dp_payload = NULL;
346*22649d4dSMartin Matuska } else if (context->volume.isreg && context->stream.inprop) {
347*22649d4dSMartin Matuska ASSERT0(drrw->drr_offset);
348*22649d4dSMartin Matuska context->volume.size = apply_properties(context,
349*22649d4dSMartin Matuska item->dp_payload, item->dp_payload_size);
350*22649d4dSMartin Matuska }
351*22649d4dSMartin Matuska break;
352*22649d4dSMartin Matuska }
353*22649d4dSMartin Matuska case DRR_FREE: {
354*22649d4dSMartin Matuska if (!context->stream.inzvol)
355*22649d4dSMartin Matuska break;
356*22649d4dSMartin Matuska
357*22649d4dSMartin Matuska struct drr_free *drrf = &drr->drr_u.drr_free;
358*22649d4dSMartin Matuska free_range(context, drrf->drr_offset, drrf->drr_length);
359*22649d4dSMartin Matuska break;
360*22649d4dSMartin Matuska }
361*22649d4dSMartin Matuska case DRR_WRITE_EMBEDDED: {
362*22649d4dSMartin Matuska struct drr_write_embedded *drrwe =
363*22649d4dSMartin Matuska &drr->drr_u.drr_write_embedded;
364*22649d4dSMartin Matuska
365*22649d4dSMartin Matuska if (!ctype_is_uncompressed(drrwe->drr_compression)) {
366*22649d4dSMartin Matuska uint8_t *buffer = item->dp_payload;
367*22649d4dSMartin Matuska uint32_t lsize = drrwe->drr_lsize;
368*22649d4dSMartin Matuska
369*22649d4dSMartin Matuska ASSERT3U(item->dp_payload_size, <=, lsize);
370*22649d4dSMartin Matuska
371*22649d4dSMartin Matuska item->dp_payload = decompress_buffer(buffer,
372*22649d4dSMartin Matuska item->dp_payload_size, lsize,
373*22649d4dSMartin Matuska drrwe->drr_compression);
374*22649d4dSMartin Matuska if (item->dp_payload == NULL)
375*22649d4dSMartin Matuska errx(EXIT_FAILURE,
376*22649d4dSMartin Matuska "decompression failed at offset %llu",
377*22649d4dSMartin Matuska (u_longlong_t)drrwe->drr_offset);
378*22649d4dSMartin Matuska item->dp_payload_size = lsize;
379*22649d4dSMartin Matuska free(buffer);
380*22649d4dSMartin Matuska }
381*22649d4dSMartin Matuska if (context->stream.inzvol) {
382*22649d4dSMartin Matuska buffer_write(context, item->dp_payload,
383*22649d4dSMartin Matuska item->dp_payload_size, drrwe->drr_offset);
384*22649d4dSMartin Matuska /*
385*22649d4dSMartin Matuska * The buffer is no longer owned by the chain. We will
386*22649d4dSMartin Matuska * free it when safe.
387*22649d4dSMartin Matuska */
388*22649d4dSMartin Matuska item->dp_payload = NULL;
389*22649d4dSMartin Matuska } else if (context->volume.isreg && context->stream.inprop) {
390*22649d4dSMartin Matuska ASSERT0(drrwe->drr_offset);
391*22649d4dSMartin Matuska context->volume.size = apply_properties(context,
392*22649d4dSMartin Matuska item->dp_payload, item->dp_payload_size);
393*22649d4dSMartin Matuska }
394*22649d4dSMartin Matuska break;
395*22649d4dSMartin Matuska }
396*22649d4dSMartin Matuska default:
397*22649d4dSMartin Matuska break;
398*22649d4dSMartin Matuska }
399*22649d4dSMartin Matuska return (D_OK);
400*22649d4dSMartin Matuska }
401*22649d4dSMartin Matuska
402*22649d4dSMartin Matuska /* Keep this small enough to not accidentally run systems out of memory. */
403*22649d4dSMartin Matuska #define BUFFERS_MAX_DEFAULT 32
404*22649d4dSMartin Matuska
405*22649d4dSMartin Matuska int
zstream_do_raw(int argc,char * argv[])406*22649d4dSMartin Matuska zstream_do_raw(int argc, char *argv[])
407*22649d4dSMartin Matuska {
408*22649d4dSMartin Matuska raw_context_t context = { 0 };
409*22649d4dSMartin Matuska context.limits.buffers_max = BUFFERS_MAX_DEFAULT;
410*22649d4dSMartin Matuska
411*22649d4dSMartin Matuska chain_attrs_t attrs = { 0 };
412*22649d4dSMartin Matuska ENABLE_OPTION(&attrs, CA_FORBID_DEDUP);
413*22649d4dSMartin Matuska
414*22649d4dSMartin Matuska int c;
415*22649d4dSMartin Matuska while ((c = getopt(argc, argv, ":b:g:v")) != -1) {
416*22649d4dSMartin Matuska switch (c) {
417*22649d4dSMartin Matuska case 'b':
418*22649d4dSMartin Matuska context.limits.buffers_max = strtol(optarg, NULL, 0);
419*22649d4dSMartin Matuska if (context.limits.buffers_max <= 0) {
420*22649d4dSMartin Matuska warnx("invalid number of buffers");
421*22649d4dSMartin Matuska zstream_usage();
422*22649d4dSMartin Matuska }
423*22649d4dSMartin Matuska break;
424*22649d4dSMartin Matuska case 'g':
425*22649d4dSMartin Matuska context.stream.guid = strtoull(optarg, NULL, 0);
426*22649d4dSMartin Matuska if (context.stream.guid == 0) {
427*22649d4dSMartin Matuska warnx("invalid guid");
428*22649d4dSMartin Matuska zstream_usage();
429*22649d4dSMartin Matuska }
430*22649d4dSMartin Matuska break;
431*22649d4dSMartin Matuska case 'v':
432*22649d4dSMartin Matuska ENABLE_OPTION(&attrs, CA_VERBOSE);
433*22649d4dSMartin Matuska ENABLE_OPTION(&attrs, CA_DUMP_ALL_RECORDS);
434*22649d4dSMartin Matuska ENABLE_OPTION(&attrs, CA_DUMP_CHECKSUMS);
435*22649d4dSMartin Matuska break;
436*22649d4dSMartin Matuska case ':':
437*22649d4dSMartin Matuska warnx("missing argument for '%c' option", optopt);
438*22649d4dSMartin Matuska zstream_usage();
439*22649d4dSMartin Matuska case '?':
440*22649d4dSMartin Matuska warnx("invalid option '%c'", optopt);
441*22649d4dSMartin Matuska zstream_usage();
442*22649d4dSMartin Matuska }
443*22649d4dSMartin Matuska }
444*22649d4dSMartin Matuska argc -= optind;
445*22649d4dSMartin Matuska argv += optind;
446*22649d4dSMartin Matuska if (argc < 1) {
447*22649d4dSMartin Matuska warnx("missing path to raw volume");
448*22649d4dSMartin Matuska zstream_usage();
449*22649d4dSMartin Matuska }
450*22649d4dSMartin Matuska
451*22649d4dSMartin Matuska const char *raw_path = argv[0];
452*22649d4dSMartin Matuska int fd = open(raw_path, O_WRONLY | O_CREAT, 0666);
453*22649d4dSMartin Matuska if (fd < 0)
454*22649d4dSMartin Matuska err(EXIT_FAILURE, "error while opening file '%s'", raw_path);
455*22649d4dSMartin Matuska context.volume.fd = fd;
456*22649d4dSMartin Matuska struct stat64 st;
457*22649d4dSMartin Matuska if (fstat64_blk(fd, &st) < 0)
458*22649d4dSMartin Matuska err(EXIT_FAILURE, "fstat64_blk");
459*22649d4dSMartin Matuska context.volume.size = st.st_size;
460*22649d4dSMartin Matuska context.volume.isreg = S_ISREG(st.st_mode);
461*22649d4dSMartin Matuska if (!context.volume.isreg) {
462*22649d4dSMartin Matuska #if defined(__FreeBSD__)
463*22649d4dSMartin Matuska uint_t sectorsize;
464*22649d4dSMartin Matuska
465*22649d4dSMartin Matuska if (ioctl(fd, DIOCGSECTORSIZE, §orsize) == 0) {
466*22649d4dSMartin Matuska context.volume.sectorsize = sectorsize;
467*22649d4dSMartin Matuska context.volume.freeop = DIOCGDELETE;
468*22649d4dSMartin Matuska }
469*22649d4dSMartin Matuska #elif defined(__linux__)
470*22649d4dSMartin Matuska if (ioctl(fd, BLKSSZGET, &context.volume.sectorsize) == 0) {
471*22649d4dSMartin Matuska /* TODO: optional BLKSECDISCARD/BLKZEROOUT */
472*22649d4dSMartin Matuska context.volume.freeop = BLKDISCARD;
473*22649d4dSMartin Matuska }
474*22649d4dSMartin Matuska #endif
475*22649d4dSMartin Matuska }
476*22649d4dSMartin Matuska
477*22649d4dSMartin Matuska long iov_max = sysconf(_SC_IOV_MAX);
478*22649d4dSMartin Matuska long pagesize = sysconf(_SC_PAGESIZE);
479*22649d4dSMartin Matuska
480*22649d4dSMartin Matuska context.limits.iov_max = iov_max;
481*22649d4dSMartin Matuska context.limits.buffers_max = MIN(context.limits.buffers_max, iov_max);
482*22649d4dSMartin Matuska context.limits.pagesize = pagesize;
483*22649d4dSMartin Matuska
484*22649d4dSMartin Matuska context.buffer.iov = safe_calloc(context.limits.buffers_max *
485*22649d4dSMartin Matuska sizeof (struct iovec));
486*22649d4dSMartin Matuska
487*22649d4dSMartin Matuska context.zeros.iov = safe_malloc(iov_max * sizeof (struct iovec));
488*22649d4dSMartin Matuska void *zero_page = safe_calloc(pagesize);
489*22649d4dSMartin Matuska for (int i = 0; i < iov_max; i++)
490*22649d4dSMartin Matuska context.zeros.iov[i].iov_base = zero_page;
491*22649d4dSMartin Matuska
492*22649d4dSMartin Matuska uint32_t drop_mask = DROP_END | DROP_FREEOBJECTS | DROP_OBJECT_RANGE |
493*22649d4dSMartin Matuska DROP_REDACT | DROP_SPILL;
494*22649d4dSMartin Matuska zstream_chain_t raw_chain = {
495*22649d4dSMartin Matuska STANDARD_INPUT_STACK((argc > 1) ? argv[1] : NULL),
496*22649d4dSMartin Matuska serial_dump_records(),
497*22649d4dSMartin Matuska serial_drop_record_types(drop_mask),
498*22649d4dSMartin Matuska parallel_decompress_writes(NULL),
499*22649d4dSMartin Matuska {
500*22649d4dSMartin Matuska .cs_type = CS_SERIAL,
501*22649d4dSMartin Matuska .cs_in_size = sizeof (drr_packet_t),
502*22649d4dSMartin Matuska .cs_out_size = sizeof (drr_packet_t),
503*22649d4dSMartin Matuska .cs_context = &context,
504*22649d4dSMartin Matuska .cs_serial = {.process = chain_replay_raw},
505*22649d4dSMartin Matuska },
506*22649d4dSMartin Matuska NULL_OUTPUT_STACK()
507*22649d4dSMartin Matuska };
508*22649d4dSMartin Matuska zstream_chain_exec(raw_chain, &attrs);
509*22649d4dSMartin Matuska
510*22649d4dSMartin Matuska buffer_finish(&context);
511*22649d4dSMartin Matuska free(zero_page);
512*22649d4dSMartin Matuska free(context.zeros.iov);
513*22649d4dSMartin Matuska free(context.buffer.iov);
514*22649d4dSMartin Matuska
515*22649d4dSMartin Matuska if (OPTION_ENABLED(CA_VERBOSE))
516*22649d4dSMartin Matuska (void) printf("now at guid %llu\n",
517*22649d4dSMartin Matuska (u_longlong_t)context.stream.guid);
518*22649d4dSMartin Matuska else
519*22649d4dSMartin Matuska (void) printf("%llu\n", (u_longlong_t)context.stream.guid);
520*22649d4dSMartin Matuska
521*22649d4dSMartin Matuska return (EXIT_SUCCESS);
522*22649d4dSMartin Matuska }
523