xref: /freebsd/stand/libsa/zstdfs.c (revision df9d6403caa6426e92f5e100602f4d2be474bbae)
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
zstdf_fill(struct zstd_file * zstdf)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
check_header(struct zstd_file * zstdf)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
zstdf_open(const char * fname,struct open_file * f)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
zstdf_close(struct open_file * f)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
zstdf_read(struct open_file * f,void * buf,size_t size,size_t * resid)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
zstdf_rewind(struct open_file * f)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
zstdf_seek(struct open_file * f,off_t offset,int where)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
zstdf_stat(struct open_file * f,struct stat * sb)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