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