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