xref: /freebsd/sys/contrib/openzfs/cmd/zstream/zstream_raw.c (revision 22649d4dba730d46244fd2dff4fd174903c8379f)
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, &sectorsize) == 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