1 // SPDX-License-Identifier: CDDL-1.0
2 /*
3 * This file and its contents are supplied under the terms of the
4 * Common Development and Distribution License ("CDDL"), version 1.0.
5 * You may only use this file in accordance with the terms of version
6 * 1.0 of the CDDL.
7 *
8 * A full copy of the text of the CDDL should have accompanied this
9 * source. A copy of the CDDL is also available via the Internet at
10 * https://opensource.org/license/CDDL-1.0.
11 */
12
13 /*
14 * Copyright 2022 Axcient. All rights reserved.
15 * Use is subject to license terms.
16 *
17 * Copyright (c) 2024, Klara, Inc.
18 * Copyright (c) 2026 by Garth Snyder
19 */
20
21 #include <err.h>
22 #include <errno.h>
23 #include <search.h>
24 #include <stdint.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <sys/stdtypes.h>
29 #include <sys/zfs_ioctl.h>
30 #include <sys/zio_compress.h>
31 #include <unistd.h>
32
33 #include "zstream.h"
34 #include "zstream_modules.h"
35 #include "zstream_util.h"
36
37 #define KEYSIZE 64
38
39 static disposition_t
chain_decompress_named_writes(void * item_in,void * context)40 chain_decompress_named_writes(void *item_in, void *context)
41 {
42 (void) context;
43 drr_packet_t *item = (drr_packet_t *)item_in;
44
45 if (item == NULL) {
46 return (D_OK);
47 }
48
49 dmu_replay_record_t *drr = &item->dp_drr;
50 struct drr_write *drrw = &drr->drr_u.drr_write;
51 char key[KEYSIZE];
52 uint8_t *dcbuff;
53
54 if (drr->drr_type != DRR_WRITE) {
55 return (D_OK);
56 }
57
58 snprintf(key, KEYSIZE, "%llu,%llu",
59 (u_longlong_t)drrw->drr_object, (u_longlong_t)drrw->drr_offset);
60 ENTRY e = { .key = key };
61 ENTRY *p = hsearch(e, FIND);
62 if (p == NULL) {
63 return (D_OK);
64 }
65
66 enum zio_compress ctype = (enum zio_compress)(intptr_t)p->data;
67 if (ctype == ZIO_COMPRESS_INHERIT) {
68 /* Unspecified */
69 ctype = drrw->drr_compressiontype;
70 }
71 if (ctype_is_uncompressed(ctype)) {
72 drrw->drr_compressiontype = 0;
73 drrw->drr_logical_size = drrw->drr_compressed_size;
74 drrw->drr_compressed_size = 0;
75 if (OPTION_ENABLED(CA_VERBOSE)) {
76 fprintf(stderr,
77 "Resetting compression type to "
78 "off for ino %llu offset %llu\n",
79 (u_longlong_t)drrw->drr_object,
80 (u_longlong_t)drrw->drr_offset);
81 }
82 return (D_OK);
83 }
84
85 if (write_is_encrypted(drrw)) {
86 warnx("the write for ino %llu offset %llu is marked "
87 "as encrypted. Attempting decompression anyway...",
88 (u_longlong_t)drrw->drr_object,
89 (u_longlong_t)drrw->drr_offset);
90 }
91
92 dcbuff = decompress_buffer(item->dp_payload, item->dp_payload_size,
93 drrw->drr_logical_size, ctype);
94
95 if (dcbuff == NULL) {
96 /*
97 * The block must not be compressed, at least not with this
98 * compression type, possibly because it gets written
99 * multiple times in this stream.
100 */
101 warnx("decompression failed for ino %llu offset %llu",
102 (u_longlong_t)drrw->drr_object,
103 (u_longlong_t)drrw->drr_offset);
104 } else {
105 set_payload(item, dcbuff, drrw->drr_logical_size);
106 drrw->drr_compressiontype = 0;
107 drrw->drr_compressed_size = 0;
108 if (OPTION_ENABLED(CA_VERBOSE)) {
109 fprintf(stderr,
110 "Successfully decompressed ino %llu offset %llu\n",
111 (u_longlong_t)drrw->drr_object,
112 (u_longlong_t)drrw->drr_offset);
113 }
114 }
115 return (D_OK);
116 }
117
118 static chain_step_t
serial_decompress_named_writes(void)119 serial_decompress_named_writes(void)
120 {
121 chain_step_t step = {
122 .cs_type = CS_SERIAL,
123 .cs_in_size = sizeof (drr_packet_t),
124 .cs_out_size = sizeof (drr_packet_t),
125 .cs_context = NULL,
126 .cs_serial = {
127 .process = chain_decompress_named_writes
128 }
129 };
130 return (step);
131 }
132
133 int
zstream_do_decompress(int argc,char * argv[])134 zstream_do_decompress(int argc, char *argv[])
135 {
136 chain_attrs_t attrs = {0};
137 int c;
138
139 while ((c = getopt(argc, argv, "v")) != -1) {
140 switch (c) {
141 case 'v':
142 ENABLE_OPTION(&attrs, CA_VERBOSE);
143 break;
144 case '?':
145 fprintf(stderr, "invalid option '%c'\n", optopt);
146 zstream_usage();
147 }
148 }
149
150 argc -= optind;
151 argv += optind;
152
153 if (argc < 0)
154 zstream_usage();
155 if (hcreate(argc) == 0)
156 errx(1, "hcreate failed");
157
158 for (int i = 0; i < argc; i++) {
159 uint64_t object, offset;
160 char *obj_str;
161 char *offset_str;
162 char *key;
163 char *end;
164 enum zio_compress type = ZIO_COMPRESS_INHERIT;
165
166 obj_str = strsep(&argv[i], ",");
167 if (argv[i] == NULL)
168 zstream_usage();
169 errno = 0;
170 object = strtoull(obj_str, &end, 0);
171 if (errno || *end != '\0')
172 errx(1, "invalid value for object");
173 offset_str = strsep(&argv[i], ",");
174 offset = strtoull(offset_str, &end, 0);
175 if (errno || *end != '\0')
176 errx(1, "invalid value for offset");
177 if (argv[i]) {
178 if (0 == strcmp("off", argv[i]))
179 type = ZIO_COMPRESS_OFF;
180 else if (0 == strcmp("lz4", argv[i]))
181 type = ZIO_COMPRESS_LZ4;
182 else if (0 == strcmp("lzjb", argv[i]))
183 type = ZIO_COMPRESS_LZJB;
184 else if (0 == strcmp("gzip", argv[i]))
185 type = ZIO_COMPRESS_GZIP_1;
186 else if (0 == strcmp("zle", argv[i]))
187 type = ZIO_COMPRESS_ZLE;
188 else if (0 == strcmp("zstd", argv[i]))
189 type = ZIO_COMPRESS_ZSTD;
190 else {
191 errx(2, "invalid compression type %s. "
192 "Supported types are off, lz4, lzjb, gzip, "
193 "zle, and zstd", argv[i]);
194 }
195 }
196
197 int n_chars = asprintf(&key, "%llu,%llu", (u_longlong_t)object,
198 (u_longlong_t)offset);
199 if (n_chars < 0)
200 err(1, "asprintf");
201 ENTRY e = { .key = key };
202 ENTRY *p;
203 p = hsearch(e, ENTER);
204 if (p == NULL)
205 errx(1, "hsearch failed");
206 p->data = (void *)(intptr_t)type;
207 }
208
209 ENABLE_OPTION(&attrs, CA_FORBID_DEDUP);
210
211 zstream_chain_t decompress_chain = {
212 STANDARD_INPUT_STACK(NULL),
213 serial_decompress_named_writes(),
214 STANDARD_OUTPUT_STACK(NULL)
215 };
216 zstream_chain_exec(decompress_chain, &attrs);
217
218 hdestroy();
219 return (0);
220 }
221