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