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