aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--stand/efi/loader/conf.c1
-rw-r--r--stand/i386/loader/conf.c3
-rw-r--r--stand/libsa/Makefile3
-rw-r--r--stand/libsa/bzipfs.c5
-rw-r--r--stand/libsa/gzipfs.c5
-rw-r--r--stand/libsa/stand.h1
-rw-r--r--stand/libsa/xzfs.c309
-rw-r--r--stand/loader.mk4
-rw-r--r--stand/powerpc/ofw/conf.c3
-rw-r--r--stand/uboot/arch/arm/conf.c3
-rw-r--r--stand/uboot/arch/powerpc/conf.c3
-rw-r--r--stand/userboot/userboot/conf.c1
12 files changed, 336 insertions, 5 deletions
diff --git a/stand/efi/loader/conf.c b/stand/efi/loader/conf.c
index 0a0476873fe1..4f257dc5ba23 100644
--- a/stand/efi/loader/conf.c
+++ b/stand/efi/loader/conf.c
@@ -66,6 +66,7 @@ struct fs_ops *file_system[] = {
&nfs_fsops,
&gzipfs_fsops,
&bzipfs_fsops,
+ &xzfs_fsops,
NULL
};
diff --git a/stand/i386/loader/conf.c b/stand/i386/loader/conf.c
index bba33a2707d5..643990699ca1 100644
--- a/stand/i386/loader/conf.c
+++ b/stand/i386/loader/conf.c
@@ -87,6 +87,9 @@ struct fs_ops *file_system[] = {
#ifdef LOADER_BZIP2_SUPPORT
&bzipfs_fsops,
#endif
+#ifdef LOADER_XZ_SUPPORT
+ &xzfs_fsops,
+#endif
#ifdef LOADER_SPLIT_SUPPORT
&splitfs_fsops,
#endif
diff --git a/stand/libsa/Makefile b/stand/libsa/Makefile
index ab7a0f5a59f2..2bb33060ee2a 100644
--- a/stand/libsa/Makefile
+++ b/stand/libsa/Makefile
@@ -142,7 +142,7 @@ SRCS+= arp.c ether.c ip.c inet_ntoa.c in_cksum.c net.c udp.c netif.c rpc.c
SRCS+= bootp.c rarp.c bootparam.c
# boot filesystems
-SRCS+= ufs.c nfs.c cd9660.c tftp.c gzipfs.c bzipfs.c
+SRCS+= ufs.c nfs.c cd9660.c tftp.c gzipfs.c bzipfs.c xzfs.c
SRCS+= dosfs.c ext2fs.c
SRCS+= splitfs.c
SRCS+= pkgfs.c
@@ -168,6 +168,7 @@ CFLAGS.ffs_subr.c+= -DSTANDALONE_SMALL
CFLAGS.gzipfs.c+= ${ZLIB_CFLAGS}
CFLAGS.pkgfs.c+= ${ZLIB_CFLAGS}
CFLAGS.bzipfs.c+= ${BZIP2_CFLAGS}
+CFLAGS.xzfs.c+= ${XZ_CFLAGS}
# explicit_bzero and calculate_crc32c
.PATH: ${SYSDIR}/libkern
diff --git a/stand/libsa/bzipfs.c b/stand/libsa/bzipfs.c
index ff7ec16e7dc6..f548475f32f2 100644
--- a/stand/libsa/bzipfs.c
+++ b/stand/libsa/bzipfs.c
@@ -158,9 +158,10 @@ bzf_open(const char *fname, struct open_file *f)
if (f->f_flags != F_READ)
return(EPERM);
- /* If the name already ends in .gz or .bz2, ignore it */
+ /* If the name already ends in a known compressed suffix, ignore it */
if ((cp = strrchr(fname, '.')) && (!strcmp(cp, ".gz")
- || !strcmp(cp, ".bz2") || !strcmp(cp, ".split")))
+ || !strcmp(cp, ".bz2") || !strcmp(cp, ".xz")
+ || !strcmp(cp, ".zst") || !strcmp(cp, ".split")))
return(ENOENT);
/* Construct new name */
diff --git a/stand/libsa/gzipfs.c b/stand/libsa/gzipfs.c
index 6b22f750f3ef..c81335bc0321 100644
--- a/stand/libsa/gzipfs.c
+++ b/stand/libsa/gzipfs.c
@@ -167,9 +167,10 @@ zf_open(const char *fname, struct open_file *f)
if (f->f_flags != F_READ)
return(EPERM);
- /* If the name already ends in .gz or .bz2, ignore it */
+ /* If the name already ends in a known compressed suffix, ignore it */
if ((cp = strrchr(fname, '.')) && (!strcmp(cp, ".gz")
- || !strcmp(cp, ".bz2") || !strcmp(cp, ".split")))
+ || !strcmp(cp, ".bz2") || !strcmp(cp, ".xz")
+ || !strcmp(cp, ".zst") || !strcmp(cp, ".split")))
return(ENOENT);
/* Construct new name */
diff --git a/stand/libsa/stand.h b/stand/libsa/stand.h
index 4f7f21867cea..3cb227ce2079 100644
--- a/stand/libsa/stand.h
+++ b/stand/libsa/stand.h
@@ -130,6 +130,7 @@ extern struct fs_ops nfs_fsops;
extern struct fs_ops cd9660_fsops;
extern struct fs_ops gzipfs_fsops;
extern struct fs_ops bzipfs_fsops;
+extern struct fs_ops xzfs_fsops;
extern struct fs_ops dosfs_fsops;
extern struct fs_ops ext2fs_fsops;
extern struct fs_ops splitfs_fsops;
diff --git a/stand/libsa/xzfs.c b/stand/libsa/xzfs.c
new file mode 100644
index 000000000000..8880203ddeb2
--- /dev/null
+++ b/stand/libsa/xzfs.c
@@ -0,0 +1,309 @@
+/*
+ * Copyright (c) 1998 Michael Smith.
+ * All rights reserved.
+ * Copyright (c) 2026 Netflix, Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Stacked filesystem for .xz compressed files, structured like gzipfs.c and
+ * bzipfs.c above it.
+ */
+
+#include "stand.h"
+
+#include <sys/stat.h>
+#include <string.h>
+#include <xz.h>
+
+#define XZ_BUFSIZE 2048 /* XXX larger? */
+
+struct xz_file
+{
+ int xzf_rawfd;
+ struct xz_dec *xzf_strm;
+ struct xz_buf xzf_buf;
+ unsigned char xzf_inbuf[XZ_BUFSIZE];
+ int xzf_endseen;
+ off_t xzf_total_out;
+};
+
+static int xzf_fill(struct xz_file *xzf);
+static int xzf_open(const char *path, struct open_file *f);
+static int xzf_close(struct open_file *f);
+static int xzf_read(struct open_file *f, void *buf, size_t size, size_t *resid);
+static off_t xzf_seek(struct open_file *f, off_t offset, int where);
+static int xzf_stat(struct open_file *f, struct stat *sb);
+
+struct fs_ops xzfs_fsops = {
+ .fs_name = "xz",
+ .fs_flags = 0,
+ .fo_open = xzf_open,
+ .fo_close = xzf_close,
+ .fo_read = xzf_read,
+ .fo_write = null_write,
+ .fo_seek = xzf_seek,
+ .fo_stat = xzf_stat,
+ .fo_readdir = null_readdir,
+};
+
+static int
+xzf_fill(struct xz_file *xzf)
+{
+ int result;
+ int avail_in;
+ int req;
+
+ avail_in = xzf->xzf_buf.in_size - xzf->xzf_buf.in_pos;
+ req = XZ_BUFSIZE - avail_in;
+ result = 0;
+
+ /* If we need more */
+ if (req > 0) {
+ /* move old data to bottom of buffer */
+ if (avail_in > 0)
+ bcopy(xzf->xzf_inbuf + xzf->xzf_buf.in_pos, xzf->xzf_inbuf, avail_in);
+
+ /* read to fill buffer and update availibility data */
+ result = read(xzf->xzf_rawfd, xzf->xzf_inbuf + avail_in, req);
+ xzf->xzf_buf.in = xzf->xzf_inbuf;
+ xzf->xzf_buf.in_pos = 0;
+ xzf->xzf_buf.in_size = avail_in + (result >= 0 ? result : 0);
+ }
+ return (result);
+}
+
+static const unsigned char xz_magic[6] = {0xfd, '7', 'z', 'X', 'Z', 0x00};
+
+/*
+ * Peek at the fixed-size .xz magic without consuming it, so the header
+ * remains for xz_dec_run() to parse normally on the first read.
+ *
+ * Returns 0 if the header is OK, nonzero if not.
+ */
+static int
+check_header(struct xz_file *xzf)
+{
+ if (xzf->xzf_buf.in_size - xzf->xzf_buf.in_pos < (int)sizeof(xz_magic) &&
+ xzf_fill(xzf) == -1)
+ return (1);
+ if (xzf->xzf_buf.in_size - xzf->xzf_buf.in_pos < (int)sizeof(xz_magic))
+ return (1);
+ return (memcmp(xzf->xzf_buf.in + xzf->xzf_buf.in_pos, xz_magic,
+ sizeof(xz_magic)) != 0);
+}
+
+static int
+xzf_open(const char *fname, struct open_file *f)
+{
+ static char *xzfname;
+ int rawfd;
+ struct xz_file *xzf;
+ char *cp;
+ struct stat sb;
+
+ /* Have to be in "just read it" mode */
+ if (f->f_flags != F_READ)
+ return(EPERM);
+
+ /* If the name already ends in a known compressed suffix, ignore it */
+ if ((cp = strrchr(fname, '.')) && (!strcmp(cp, ".gz")
+ || !strcmp(cp, ".bz2") || !strcmp(cp, ".xz")
+ || !strcmp(cp, ".zst") || !strcmp(cp, ".split")))
+ return(ENOENT);
+
+ /* Construct new name */
+ xzfname = malloc(strlen(fname) + 4);
+ if (xzfname == NULL)
+ return(ENOMEM);
+ sprintf(xzfname, "%s.xz", fname);
+
+ /* Try to open the compressed datafile */
+ rawfd = open(xzfname, O_RDONLY);
+ free(xzfname);
+ if (rawfd == -1)
+ return(ENOENT);
+
+ if (fstat(rawfd, &sb) < 0) {
+ printf("xzf_open: stat failed\n");
+ close(rawfd);
+ return(ENOENT);
+ }
+ if (!S_ISREG(sb.st_mode)) {
+ printf("xzf_open: not a file\n");
+ close(rawfd);
+ return(EISDIR); /* best guess */
+ }
+
+ /* Allocate an xz_file structure, populate it */
+ xzf = malloc(sizeof(struct xz_file));
+ if (xzf == NULL) {
+ close(rawfd);
+ return(ENOMEM);
+ }
+ bzero(xzf, sizeof(struct xz_file));
+ xzf->xzf_rawfd = rawfd;
+ xzf->xzf_buf.in = xzf->xzf_inbuf;
+
+ /*
+ * The embedded xz decoder's internal CRC32/CRC64 tables start out
+ * zeroed and must be built before any stream can be decoded, or the
+ * Stream Header CRC32 check fails on the very first read.
+ */
+ xz_crc32_init();
+ xz_crc64_init();
+
+ /* Verify that the file is xz compressed */
+ if (check_header(xzf)) {
+ close(xzf->xzf_rawfd);
+ free(xzf);
+ return(EFTYPE);
+ }
+
+ /* Initialise the inflation engine */
+ xzf->xzf_strm = xz_dec_init(XZ_DYNALLOC, (uint32_t)-1);
+ if (xzf->xzf_strm == NULL) {
+ close(xzf->xzf_rawfd);
+ free(xzf);
+ return(EIO);
+ }
+
+ /* Looks OK, we'll take it */
+ f->f_fsdata = xzf;
+ return(0);
+}
+
+static int
+xzf_close(struct open_file *f)
+{
+ struct xz_file *xzf = (struct xz_file *)f->f_fsdata;
+
+ xz_dec_end(xzf->xzf_strm);
+ close(xzf->xzf_rawfd);
+ free(xzf);
+ return(0);
+}
+
+static int
+xzf_read(struct open_file *f, void *buf, size_t size, size_t *resid)
+{
+ struct xz_file *xzf = (struct xz_file *)f->f_fsdata;
+ enum xz_ret ret;
+
+ xzf->xzf_buf.out = buf; /* where and how much */
+ xzf->xzf_buf.out_pos = 0;
+ xzf->xzf_buf.out_size = size;
+
+ while (xzf->xzf_buf.out_pos < xzf->xzf_buf.out_size && xzf->xzf_endseen == 0) {
+ if ((xzf->xzf_buf.in_pos == xzf->xzf_buf.in_size) && (xzf_fill(xzf) == -1)) {
+ printf("xzf_read: fill error\n");
+ return(EIO);
+ }
+ if (xzf->xzf_buf.in_pos == xzf->xzf_buf.in_size) { /* oops, unexpected EOF */
+ printf("xzf_read: unexpected EOF\n");
+ if (xzf->xzf_buf.out_pos == 0)
+ return(EIO);
+ break;
+ }
+
+ ret = xz_dec_run(xzf->xzf_strm, &xzf->xzf_buf); /* decompression pass */
+ if (ret == XZ_STREAM_END) { /* EOF, all done */
+ xzf->xzf_endseen = 1;
+ break;
+ }
+ if (ret != XZ_OK) { /* argh, decompression error */
+ printf("xzf_read: xz_dec_run returned %d\n", ret);
+ return(EIO);
+ }
+ }
+ xzf->xzf_total_out += xzf->xzf_buf.out_pos;
+ if (resid != NULL)
+ *resid = xzf->xzf_buf.out_size - xzf->xzf_buf.out_pos;
+ return(0);
+}
+
+static int
+xzf_rewind(struct open_file *f)
+{
+ struct xz_file *xzf = (struct xz_file *)f->f_fsdata;
+
+ if (lseek(xzf->xzf_rawfd, 0, SEEK_SET) == -1)
+ return(-1);
+ xzf->xzf_buf.in = xzf->xzf_inbuf;
+ xzf->xzf_buf.in_pos = 0;
+ xzf->xzf_buf.in_size = 0;
+ xzf->xzf_endseen = 0;
+ xzf->xzf_total_out = 0;
+ xz_dec_reset(xzf->xzf_strm);
+
+ return(0);
+}
+
+static off_t
+xzf_seek(struct open_file *f, off_t offset, int where)
+{
+ struct xz_file *xzf = (struct xz_file *)f->f_fsdata;
+ off_t target;
+ char discard[16];
+
+ switch (where) {
+ case SEEK_SET:
+ target = offset;
+ break;
+ case SEEK_CUR:
+ target = offset + xzf->xzf_total_out;
+ break;
+ default:
+ errno = EINVAL;
+ return(-1);
+ }
+
+ /* rewind if required */
+ if (target < xzf->xzf_total_out && xzf_rewind(f) != 0)
+ return(-1);
+
+ /* skip forwards if required */
+ while (target > xzf->xzf_total_out) {
+ errno = xzf_read(f, discard, min(sizeof(discard),
+ target - xzf->xzf_total_out), NULL);
+ if (errno)
+ return(-1);
+ /* Break out of loop if end of file has been reached. */
+ if (xzf->xzf_endseen)
+ break;
+ }
+ /* This is where we are (be honest if we overshot) */
+ return(xzf->xzf_total_out);
+}
+
+static int
+xzf_stat(struct open_file *f, struct stat *sb)
+{
+ struct xz_file *xzf = (struct xz_file *)f->f_fsdata;
+ int result;
+
+ /* stat as normal, but indicate that size is unknown */
+ if ((result = fstat(xzf->xzf_rawfd, sb)) == 0)
+ sb->st_size = -1;
+ return(result);
+}
diff --git a/stand/loader.mk b/stand/loader.mk
index b34ba08a8e5d..0ff5bd144d93 100644
--- a/stand/loader.mk
+++ b/stand/loader.mk
@@ -53,6 +53,7 @@ SRCS+= metadata.c
# LOADER_NFS_SUPPORT Add NFS support
# LOADER_TFTP_SUPPORT Add TFTP support
# LOADER_UFS_SUPPORT Add support for UFS filesystems
+# LOADER_XZ_SUPPORT Add support for xz compressed files
# LOADER_ZFS_SUPPORT Add support for ZFS filesystems
#
@@ -130,6 +131,9 @@ CFLAGS+= -DLOADER_GZIP_SUPPORT
.if ${LOADER_BZIP2_SUPPORT:Uno} == "yes"
CFLAGS+= -DLOADER_BZIP2_SUPPORT
.endif
+.if ${LOADER_XZ_SUPPORT:Uno} == "yes"
+CFLAGS+= -DLOADER_XZ_SUPPORT
+.endif
# Network related things
.if ${LOADER_NET_SUPPORT:Uno} == "yes"
diff --git a/stand/powerpc/ofw/conf.c b/stand/powerpc/ofw/conf.c
index c96048b5a09c..a77afe203719 100644
--- a/stand/powerpc/ofw/conf.c
+++ b/stand/powerpc/ofw/conf.c
@@ -81,6 +81,9 @@ struct fs_ops *file_system[] = {
#if defined(LOADER_BZIP2_SUPPORT)
&bzipfs_fsops,
#endif
+#if defined(LOADER_XZ_SUPPORT)
+ &xzfs_fsops,
+#endif
NULL
};
diff --git a/stand/uboot/arch/arm/conf.c b/stand/uboot/arch/arm/conf.c
index 7d36e6d6629e..535c3b494e3c 100644
--- a/stand/uboot/arch/arm/conf.c
+++ b/stand/uboot/arch/arm/conf.c
@@ -68,6 +68,9 @@ struct fs_ops *file_system[] = {
#if defined(LOADER_BZIP2_SUPPORT)
&bzipfs_fsops,
#endif
+#if defined(LOADER_XZ_SUPPORT)
+ &xzfs_fsops,
+#endif
NULL
};
diff --git a/stand/uboot/arch/powerpc/conf.c b/stand/uboot/arch/powerpc/conf.c
index 963a4ded4dcf..543b4fb643b2 100644
--- a/stand/uboot/arch/powerpc/conf.c
+++ b/stand/uboot/arch/powerpc/conf.c
@@ -77,6 +77,9 @@ struct fs_ops *file_system[] = {
#if defined(LOADER_BZIP2_SUPPORT)
&bzipfs_fsops,
#endif
+#if defined(LOADER_XZ_SUPPORT)
+ &xzfs_fsops,
+#endif
NULL
};
diff --git a/stand/userboot/userboot/conf.c b/stand/userboot/userboot/conf.c
index 63f64cb97fbe..8d37f241c9bf 100644
--- a/stand/userboot/userboot/conf.c
+++ b/stand/userboot/userboot/conf.c
@@ -70,6 +70,7 @@ struct fs_ops *file_system[] = {
#endif
&gzipfs_fsops,
&bzipfs_fsops,
+ &xzfs_fsops,
NULL
};