xref: /freebsd/stand/libsa/xzfs.c (revision 86d719ae68aa5f6db055e1fa7ede24b70e612ec7)
1 /*
2  * Copyright (c) 1998 Michael Smith.
3  * All rights reserved.
4  * Copyright (c) 2026 Netflix, Inc.
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 .xz compressed files, structured like gzipfs.c and
30  * bzipfs.c above it.
31  */
32 
33 #include "stand.h"
34 
35 #include <sys/stat.h>
36 #include <string.h>
37 #include <xz.h>
38 
39 #define XZ_BUFSIZE 2048	/* XXX larger? */
40 
41 struct xz_file
42 {
43     int			xzf_rawfd;
44     struct xz_dec	*xzf_strm;
45     struct xz_buf	xzf_buf;
46     unsigned char	xzf_inbuf[XZ_BUFSIZE];
47     int			xzf_endseen;
48     off_t		xzf_total_out;
49 };
50 
51 static int	xzf_fill(struct xz_file *xzf);
52 static int	xzf_open(const char *path, struct open_file *f);
53 static int	xzf_close(struct open_file *f);
54 static int	xzf_read(struct open_file *f, void *buf, size_t size, size_t *resid);
55 static off_t	xzf_seek(struct open_file *f, off_t offset, int where);
56 static int	xzf_stat(struct open_file *f, struct stat *sb);
57 
58 struct fs_ops xzfs_fsops = {
59 	.fs_name = "xz",
60 	.fs_flags = 0,
61 	.fo_open = xzf_open,
62 	.fo_close = xzf_close,
63 	.fo_read = xzf_read,
64 	.fo_write = null_write,
65 	.fo_seek = xzf_seek,
66 	.fo_stat = xzf_stat,
67 	.fo_readdir = null_readdir,
68 };
69 
70 static int
xzf_fill(struct xz_file * xzf)71 xzf_fill(struct xz_file *xzf)
72 {
73     int		result;
74     int		avail_in;
75     int		req;
76 
77     avail_in = xzf->xzf_buf.in_size - xzf->xzf_buf.in_pos;
78     req = XZ_BUFSIZE - avail_in;
79     result = 0;
80 
81     /* If we need more */
82     if (req > 0) {
83 	/* move old data to bottom of buffer */
84 	if (avail_in > 0)
85 	    bcopy(xzf->xzf_inbuf + xzf->xzf_buf.in_pos, xzf->xzf_inbuf, avail_in);
86 
87 	/* read to fill buffer and update availibility data */
88 	result = read(xzf->xzf_rawfd, xzf->xzf_inbuf + avail_in, req);
89 	xzf->xzf_buf.in = xzf->xzf_inbuf;
90 	xzf->xzf_buf.in_pos = 0;
91 	xzf->xzf_buf.in_size = avail_in + (result >= 0 ? result : 0);
92     }
93     return (result);
94 }
95 
96 static const unsigned char xz_magic[6] = {0xfd, '7', 'z', 'X', 'Z', 0x00};
97 
98 /*
99  * Peek at the fixed-size .xz magic without consuming it, so the header
100  * remains for xz_dec_run() to parse normally on the first read.
101  *
102  * Returns 0 if the header is OK, nonzero if not.
103  */
104 static int
check_header(struct xz_file * xzf)105 check_header(struct xz_file *xzf)
106 {
107     if (xzf->xzf_buf.in_size - xzf->xzf_buf.in_pos < (int)sizeof(xz_magic) &&
108 	xzf_fill(xzf) == -1)
109 	return (1);
110     if (xzf->xzf_buf.in_size - xzf->xzf_buf.in_pos < (int)sizeof(xz_magic))
111 	return (1);
112     return (memcmp(xzf->xzf_buf.in + xzf->xzf_buf.in_pos, xz_magic,
113 	sizeof(xz_magic)) != 0);
114 }
115 
116 static int
xzf_open(const char * fname,struct open_file * f)117 xzf_open(const char *fname, struct open_file *f)
118 {
119     static char		*xzfname;
120     int			rawfd;
121     struct xz_file	*xzf;
122     char		*cp;
123     struct stat		sb;
124 
125     /* Have to be in "just read it" mode */
126     if (f->f_flags != F_READ)
127 	return(EPERM);
128 
129     /* If the name already ends in a known compressed suffix, ignore it */
130     if ((cp = strrchr(fname, '.')) && (!strcmp(cp, ".gz")
131 	    || !strcmp(cp, ".bz2") || !strcmp(cp, ".xz")
132 	    || !strcmp(cp, ".zst") || !strcmp(cp, ".split")))
133 	return(ENOENT);
134 
135     /* Construct new name */
136     xzfname = malloc(strlen(fname) + 4);
137     if (xzfname == NULL)
138         return(ENOMEM);
139     sprintf(xzfname, "%s.xz", fname);
140 
141     /* Try to open the compressed datafile */
142     rawfd = open(xzfname, O_RDONLY);
143     free(xzfname);
144     if (rawfd == -1)
145 	return(ENOENT);
146 
147     if (fstat(rawfd, &sb) < 0) {
148 	printf("xzf_open: stat failed\n");
149 	close(rawfd);
150 	return(ENOENT);
151     }
152     if (!S_ISREG(sb.st_mode)) {
153 	printf("xzf_open: not a file\n");
154 	close(rawfd);
155 	return(EISDIR);			/* best guess */
156     }
157 
158     /* Allocate an xz_file structure, populate it */
159     xzf = malloc(sizeof(struct xz_file));
160     if (xzf == NULL) {
161 	close(rawfd);
162         return(ENOMEM);
163     }
164     bzero(xzf, sizeof(struct xz_file));
165     xzf->xzf_rawfd = rawfd;
166     xzf->xzf_buf.in = xzf->xzf_inbuf;
167 
168     /*
169      * The embedded xz decoder's internal CRC32/CRC64 tables start out
170      * zeroed and must be built before any stream can be decoded, or the
171      * Stream Header CRC32 check fails on the very first read.
172      */
173     xz_crc32_init();
174     xz_crc64_init();
175 
176     /* Verify that the file is xz compressed */
177     if (check_header(xzf)) {
178 	close(xzf->xzf_rawfd);
179 	free(xzf);
180 	return(EFTYPE);
181     }
182 
183     /* Initialise the inflation engine */
184     xzf->xzf_strm = xz_dec_init(XZ_DYNALLOC, (uint32_t)-1);
185     if (xzf->xzf_strm == NULL) {
186 	close(xzf->xzf_rawfd);
187 	free(xzf);
188 	return(EIO);
189     }
190 
191     /* Looks OK, we'll take it */
192     f->f_fsdata = xzf;
193     return(0);
194 }
195 
196 static int
xzf_close(struct open_file * f)197 xzf_close(struct open_file *f)
198 {
199     struct xz_file	*xzf = (struct xz_file *)f->f_fsdata;
200 
201     xz_dec_end(xzf->xzf_strm);
202     close(xzf->xzf_rawfd);
203     free(xzf);
204     return(0);
205 }
206 
207 static int
xzf_read(struct open_file * f,void * buf,size_t size,size_t * resid)208 xzf_read(struct open_file *f, void *buf, size_t size, size_t *resid)
209 {
210     struct xz_file	*xzf = (struct xz_file *)f->f_fsdata;
211     enum xz_ret		ret;
212 
213     xzf->xzf_buf.out = buf;			/* where and how much */
214     xzf->xzf_buf.out_pos = 0;
215     xzf->xzf_buf.out_size = size;
216 
217     while (xzf->xzf_buf.out_pos < xzf->xzf_buf.out_size && xzf->xzf_endseen == 0) {
218 	if ((xzf->xzf_buf.in_pos == xzf->xzf_buf.in_size) && (xzf_fill(xzf) == -1)) {
219 	    printf("xzf_read: fill error\n");
220 	    return(EIO);
221 	}
222 	if (xzf->xzf_buf.in_pos == xzf->xzf_buf.in_size) {	/* oops, unexpected EOF */
223 	    printf("xzf_read: unexpected EOF\n");
224 	    if (xzf->xzf_buf.out_pos == 0)
225 		return(EIO);
226 	    break;
227 	}
228 
229 	ret = xz_dec_run(xzf->xzf_strm, &xzf->xzf_buf);	/* decompression pass */
230 	if (ret == XZ_STREAM_END) {			/* EOF, all done */
231 	    xzf->xzf_endseen = 1;
232 	    break;
233 	}
234 	if (ret != XZ_OK) {				/* argh, decompression error */
235 	    printf("xzf_read: xz_dec_run returned %d\n", ret);
236 	    return(EIO);
237 	}
238     }
239     xzf->xzf_total_out += xzf->xzf_buf.out_pos;
240     if (resid != NULL)
241 	*resid = xzf->xzf_buf.out_size - xzf->xzf_buf.out_pos;
242     return(0);
243 }
244 
245 static int
xzf_rewind(struct open_file * f)246 xzf_rewind(struct open_file *f)
247 {
248     struct xz_file	*xzf = (struct xz_file *)f->f_fsdata;
249 
250     if (lseek(xzf->xzf_rawfd, 0, SEEK_SET) == -1)
251 	return(-1);
252     xzf->xzf_buf.in = xzf->xzf_inbuf;
253     xzf->xzf_buf.in_pos = 0;
254     xzf->xzf_buf.in_size = 0;
255     xzf->xzf_endseen = 0;
256     xzf->xzf_total_out = 0;
257     xz_dec_reset(xzf->xzf_strm);
258 
259     return(0);
260 }
261 
262 static off_t
xzf_seek(struct open_file * f,off_t offset,int where)263 xzf_seek(struct open_file *f, off_t offset, int where)
264 {
265     struct xz_file	*xzf = (struct xz_file *)f->f_fsdata;
266     off_t		target;
267     char		discard[16];
268 
269     switch (where) {
270     case SEEK_SET:
271 	target = offset;
272 	break;
273     case SEEK_CUR:
274 	target = offset + xzf->xzf_total_out;
275 	break;
276     default:
277 	errno = EINVAL;
278 	return(-1);
279     }
280 
281     /* rewind if required */
282     if (target < xzf->xzf_total_out && xzf_rewind(f) != 0)
283 	return(-1);
284 
285     /* skip forwards if required */
286     while (target > xzf->xzf_total_out) {
287 	errno = xzf_read(f, discard, min(sizeof(discard),
288 	    target - xzf->xzf_total_out), NULL);
289 	if (errno)
290 	    return(-1);
291 	/* Break out of loop if end of file has been reached. */
292 	if (xzf->xzf_endseen)
293 	    break;
294     }
295     /* This is where we are (be honest if we overshot) */
296     return(xzf->xzf_total_out);
297 }
298 
299 static int
xzf_stat(struct open_file * f,struct stat * sb)300 xzf_stat(struct open_file *f, struct stat *sb)
301 {
302     struct xz_file	*xzf = (struct xz_file *)f->f_fsdata;
303     int			result;
304 
305     /* stat as normal, but indicate that size is unknown */
306     if ((result = fstat(xzf->xzf_rawfd, sb)) == 0)
307 	sb->st_size = -1;
308     return(result);
309 }
310