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