1 /* 2 * Copyright (c) 1998 Michael Smith. 3 * Copyright (c) 2026 Netflix, Inc. 4 * All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25 * SUCH DAMAGE. 26 */ 27 28 /* 29 * Stacked filesystem for .zst compressed files, structured like gzipfs.c and 30 * bzipfs.c above it. Only built when the zstd decompressor is already being 31 * pulled into libsa for ZFS (see libsa/Makefile), since that's the only 32 * source of the ZSTD_* symbols in this environment. 33 */ 34 35 #include "stand.h" 36 37 #include <sys/stat.h> 38 #include <string.h> 39 #include <zstd.h> 40 41 #define ZSTD_BUFSIZE 2048 /* XXX larger? */ 42 43 struct zstd_file 44 { 45 int zstdf_rawfd; 46 ZSTD_DStream *zstdf_strm; 47 ZSTD_inBuffer zstdf_in; 48 unsigned char zstdf_inbuf[ZSTD_BUFSIZE]; 49 int zstdf_endseen; 50 off_t zstdf_total_out; 51 }; 52 53 static int zstdf_fill(struct zstd_file *zstdf); 54 static int zstdf_open(const char *path, struct open_file *f); 55 static int zstdf_close(struct open_file *f); 56 static int zstdf_read(struct open_file *f, void *buf, size_t size, size_t *resid); 57 static off_t zstdf_seek(struct open_file *f, off_t offset, int where); 58 static int zstdf_stat(struct open_file *f, struct stat *sb); 59 60 struct fs_ops zstdfs_fsops = { 61 .fs_name = "zstd", 62 .fs_flags = 0, 63 .fo_open = zstdf_open, 64 .fo_close = zstdf_close, 65 .fo_read = zstdf_read, 66 .fo_write = null_write, 67 .fo_seek = zstdf_seek, 68 .fo_stat = zstdf_stat, 69 .fo_readdir = null_readdir, 70 }; 71 72 static int 73 zstdf_fill(struct zstd_file *zstdf) 74 { 75 int result; 76 int avail_in; 77 int req; 78 79 avail_in = zstdf->zstdf_in.size - zstdf->zstdf_in.pos; 80 req = ZSTD_BUFSIZE - avail_in; 81 result = 0; 82 83 /* If we need more */ 84 if (req > 0) { 85 /* move old data to bottom of buffer */ 86 if (avail_in > 0) 87 bcopy(zstdf->zstdf_inbuf + zstdf->zstdf_in.pos, zstdf->zstdf_inbuf, avail_in); 88 89 /* read to fill buffer and update availibility data */ 90 result = read(zstdf->zstdf_rawfd, zstdf->zstdf_inbuf + avail_in, req); 91 zstdf->zstdf_in.src = zstdf->zstdf_inbuf; 92 zstdf->zstdf_in.pos = 0; 93 zstdf->zstdf_in.size = avail_in + (result >= 0 ? result : 0); 94 } 95 return (result); 96 } 97 98 static const unsigned char zstd_magic[4] = {0x28, 0xb5, 0x2f, 0xfd}; 99 100 /* 101 * Peek at the fixed-size zstd frame magic without consuming it, so the 102 * header remains for ZSTD_decompressStream() to parse normally on the 103 * first read. 104 * 105 * Returns 0 if the header is OK, nonzero if not. 106 */ 107 static int 108 check_header(struct zstd_file *zstdf) 109 { 110 if (zstdf->zstdf_in.size - zstdf->zstdf_in.pos < sizeof(zstd_magic) && 111 zstdf_fill(zstdf) == -1) 112 return (1); 113 if (zstdf->zstdf_in.size - zstdf->zstdf_in.pos < sizeof(zstd_magic)) 114 return (1); 115 return (memcmp((const unsigned char *)zstdf->zstdf_in.src + zstdf->zstdf_in.pos, 116 zstd_magic, sizeof(zstd_magic)) != 0); 117 } 118 119 static int 120 zstdf_open(const char *fname, struct open_file *f) 121 { 122 static char *zstdfname; 123 int rawfd; 124 struct zstd_file *zstdf; 125 char *cp; 126 struct stat sb; 127 128 /* Have to be in "just read it" mode */ 129 if (f->f_flags != F_READ) 130 return(EPERM); 131 132 /* If the name already ends in a known compressed suffix, ignore it */ 133 if ((cp = strrchr(fname, '.')) && (!strcmp(cp, ".gz") 134 || !strcmp(cp, ".bz2") || !strcmp(cp, ".xz") 135 || !strcmp(cp, ".zst") || !strcmp(cp, ".split"))) 136 return(ENOENT); 137 138 /* Construct new name */ 139 zstdfname = malloc(strlen(fname) + 5); 140 if (zstdfname == NULL) 141 return(ENOMEM); 142 sprintf(zstdfname, "%s.zst", fname); 143 144 /* Try to open the compressed datafile */ 145 rawfd = open(zstdfname, O_RDONLY); 146 free(zstdfname); 147 if (rawfd == -1) 148 return(ENOENT); 149 150 if (fstat(rawfd, &sb) < 0) { 151 printf("zstdf_open: stat failed\n"); 152 close(rawfd); 153 return(ENOENT); 154 } 155 if (!S_ISREG(sb.st_mode)) { 156 printf("zstdf_open: not a file\n"); 157 close(rawfd); 158 return(EISDIR); /* best guess */ 159 } 160 161 /* Allocate a zstd_file structure, populate it */ 162 zstdf = malloc(sizeof(struct zstd_file)); 163 if (zstdf == NULL) { 164 close(rawfd); 165 return(ENOMEM); 166 } 167 bzero(zstdf, sizeof(struct zstd_file)); 168 zstdf->zstdf_rawfd = rawfd; 169 zstdf->zstdf_in.src = zstdf->zstdf_inbuf; 170 171 /* Verify that the file is zstd compressed */ 172 if (check_header(zstdf)) { 173 close(zstdf->zstdf_rawfd); 174 free(zstdf); 175 return(EFTYPE); 176 } 177 178 /* Initialise the inflation engine */ 179 zstdf->zstdf_strm = ZSTD_createDStream(); 180 if (zstdf->zstdf_strm == NULL) { 181 close(zstdf->zstdf_rawfd); 182 free(zstdf); 183 return(ENOMEM); 184 } 185 if (ZSTD_isError(ZSTD_initDStream(zstdf->zstdf_strm))) { 186 ZSTD_freeDStream(zstdf->zstdf_strm); 187 close(zstdf->zstdf_rawfd); 188 free(zstdf); 189 return(EIO); 190 } 191 192 /* Looks OK, we'll take it */ 193 f->f_fsdata = zstdf; 194 return(0); 195 } 196 197 static int 198 zstdf_close(struct open_file *f) 199 { 200 struct zstd_file *zstdf = (struct zstd_file *)f->f_fsdata; 201 202 ZSTD_freeDStream(zstdf->zstdf_strm); 203 close(zstdf->zstdf_rawfd); 204 free(zstdf); 205 return(0); 206 } 207 208 static int 209 zstdf_read(struct open_file *f, void *buf, size_t size, size_t *resid) 210 { 211 struct zstd_file *zstdf = (struct zstd_file *)f->f_fsdata; 212 ZSTD_outBuffer out = { buf, size, 0 }; 213 size_t ret; 214 215 while (out.pos < out.size && zstdf->zstdf_endseen == 0) { 216 if ((zstdf->zstdf_in.pos == zstdf->zstdf_in.size) && (zstdf_fill(zstdf) == -1)) { 217 printf("zstdf_read: fill error\n"); 218 return(EIO); 219 } 220 if (zstdf->zstdf_in.pos == zstdf->zstdf_in.size) { /* oops, unexpected EOF */ 221 printf("zstdf_read: unexpected EOF\n"); 222 if (out.pos == 0) 223 return(EIO); 224 break; 225 } 226 227 ret = ZSTD_decompressStream(zstdf->zstdf_strm, &out, &zstdf->zstdf_in); 228 if (ZSTD_isError(ret)) { /* argh, decompression error */ 229 printf("zstdf_read: %s\n", ZSTD_getErrorName(ret)); 230 return(EIO); 231 } 232 if (ret == 0) { /* EOF, all done */ 233 zstdf->zstdf_endseen = 1; 234 break; 235 } 236 } 237 zstdf->zstdf_total_out += out.pos; 238 if (resid != NULL) 239 *resid = out.size - out.pos; 240 return(0); 241 } 242 243 static int 244 zstdf_rewind(struct open_file *f) 245 { 246 struct zstd_file *zstdf = (struct zstd_file *)f->f_fsdata; 247 248 if (lseek(zstdf->zstdf_rawfd, 0, SEEK_SET) == -1) 249 return(-1); 250 zstdf->zstdf_in.src = zstdf->zstdf_inbuf; 251 zstdf->zstdf_in.pos = 0; 252 zstdf->zstdf_in.size = 0; 253 zstdf->zstdf_endseen = 0; 254 zstdf->zstdf_total_out = 0; 255 if (ZSTD_isError(ZSTD_initDStream(zstdf->zstdf_strm))) 256 return(-1); 257 258 return(0); 259 } 260 261 static off_t 262 zstdf_seek(struct open_file *f, off_t offset, int where) 263 { 264 struct zstd_file *zstdf = (struct zstd_file *)f->f_fsdata; 265 off_t target; 266 char discard[16]; 267 268 switch (where) { 269 case SEEK_SET: 270 target = offset; 271 break; 272 case SEEK_CUR: 273 target = offset + zstdf->zstdf_total_out; 274 break; 275 default: 276 errno = EINVAL; 277 return(-1); 278 } 279 280 /* rewind if required */ 281 if (target < zstdf->zstdf_total_out && zstdf_rewind(f) != 0) 282 return(-1); 283 284 /* skip forwards if required */ 285 while (target > zstdf->zstdf_total_out) { 286 errno = zstdf_read(f, discard, min(sizeof(discard), 287 target - zstdf->zstdf_total_out), NULL); 288 if (errno) 289 return(-1); 290 /* Break out of loop if end of file has been reached. */ 291 if (zstdf->zstdf_endseen) 292 break; 293 } 294 /* This is where we are (be honest if we overshot) */ 295 return(zstdf->zstdf_total_out); 296 } 297 298 static int 299 zstdf_stat(struct open_file *f, struct stat *sb) 300 { 301 struct zstd_file *zstdf = (struct zstd_file *)f->f_fsdata; 302 int result; 303 304 /* stat as normal, but indicate that size is unknown */ 305 if ((result = fstat(zstdf->zstdf_rawfd, sb)) == 0) 306 sb->st_size = -1; 307 return(result); 308 } 309