]> git.ipfire.org Git - thirdparty/grub.git/commitdiff
io/zstdio: Implement zstdio decompression
authorLogan Gunthorpe <logang@deltatee.com>
Tue, 21 Oct 2025 15:56:25 +0000 (09:56 -0600)
committerDaniel Kiper <daniel.kiper@oracle.com>
Fri, 24 Oct 2025 17:37:55 +0000 (19:37 +0200)
Add zstd based io decompression.

Based largely on the existing xzio, implement the same features using
the zstd library already included in the project.

Signed-off-by: Logan Gunthorpe <logang@deltatee.com>
Reviewed-by: Avnish Chouhan <avnish@linux.ibm.com>
Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
Makefile.util.def
grub-core/Makefile.core.def
grub-core/io/zstdio.c [new file with mode: 0644]
include/grub/file.h

index 0196911e640d24af2c55335a52f9a23f19b30ecd..91720e627afa44bae7660097f454a04de194b9e5 100644 (file)
@@ -164,6 +164,7 @@ library = {
   common = grub-core/io/gzio.c;
   common = grub-core/io/xzio.c;
   common = grub-core/io/lzopio.c;
+  common = grub-core/io/zstdio.c;
   common = grub-core/kern/ia64/dl_helper.c;
   common = grub-core/kern/arm/dl_helper.c;
   common = grub-core/kern/arm64/dl_helper.c;
index 3d4b1a3b4d5ae3b0188c8b0e2704215eda311afa..fa4bc54aa9981eb64bd8b35894f595fdbf3e755f 100644 (file)
@@ -2524,6 +2524,13 @@ module = {
   cppflags = '-I$(srcdir)/lib/posix_wrap -I$(srcdir)/lib/minilzo -DMINILZO_HAVE_CONFIG_H';
 };
 
+module = {
+  name = zstdio;
+  common = io/zstdio.c;
+  cppflags = '-I$(srcdir)/lib/posix_wrap -I$(srcdir)/lib/zstd';
+  cflags='-Wno-unreachable-code';
+};
+
 module = {
   name = testload;
   common = commands/testload.c;
diff --git a/grub-core/io/zstdio.c b/grub-core/io/zstdio.c
new file mode 100644 (file)
index 0000000..379eba0
--- /dev/null
@@ -0,0 +1,251 @@
+/* zstdio.c - decompression support for zstd */
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *  Copyright (C) 2025  Free Software Foundation, Inc.
+ *
+ *  GRUB is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  GRUB is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/err.h>
+#include <grub/mm.h>
+#include <grub/misc.h>
+#include <grub/file.h>
+#include <grub/fs.h>
+#include <grub/dl.h>
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+#include "zstd.h"
+
+#define STREAM_HEADER_SIZE 16
+
+struct zstdio
+{
+  grub_file_t file;
+  ZSTD_DCtx *dctx;
+  grub_size_t insize;
+  grub_size_t outsize;
+
+  ZSTD_outBuffer output;
+  ZSTD_inBuffer input;
+
+  grub_off_t saved_offset;
+  grub_uint8_t bufs[];
+};
+typedef struct zstdio *zstdio_t;
+
+static struct grub_fs zstdio_fs;
+
+static bool
+test_header (grub_file_t file)
+{
+  zstdio_t zstdio = file->data;
+  size_t zret;
+
+  zstdio->input.pos = 0;
+  zstdio->output.pos = 0;
+  zstdio->output.size = zstdio->outsize;
+  zstdio->input.size = grub_file_read (zstdio->file, zstdio->bufs,
+                                       STREAM_HEADER_SIZE);
+  if (zstdio->input.size != STREAM_HEADER_SIZE)
+    return false;
+
+  zret = ZSTD_decompressStream (zstdio->dctx, &zstdio->output, &zstdio->input);
+  if (ZSTD_isError (zret))
+    return false;
+
+  return true;
+}
+
+static grub_file_t
+grub_zstdio_open (grub_file_t io, enum grub_file_type type)
+{
+  grub_file_t file;
+  zstdio_t zstdio;
+
+  if (type & GRUB_FILE_TYPE_NO_DECOMPRESS)
+    return io;
+
+  file = (grub_file_t) grub_zalloc (sizeof (*file));
+  if (file == NULL)
+    return NULL;
+
+  zstdio = grub_zalloc (sizeof (zstdio_t) + ZSTD_DStreamInSize () +
+                        ZSTD_DStreamOutSize ());
+  if (zstdio == NULL)
+    {
+      grub_free (file);
+      return NULL;
+    }
+
+  zstdio->file = io;
+  zstdio->insize = ZSTD_DStreamInSize ();
+  zstdio->outsize = ZSTD_DStreamOutSize ();
+  zstdio->input.src = zstdio->bufs;
+  zstdio->output.dst = &zstdio->bufs[zstdio->insize];
+
+  file->device = io->device;
+  file->data = zstdio;
+  file->fs = &zstdio_fs;
+  file->size = GRUB_FILE_SIZE_UNKNOWN;
+  file->not_easily_seekable = 1;
+
+  if (grub_file_tell (zstdio->file) != 0)
+    if (grub_file_seek (zstdio->file, 0) == (grub_off_t) -1)
+      {
+       grub_free (file);
+       grub_free (zstdio);
+       return NULL;
+      }
+
+  zstdio->dctx = ZSTD_createDCtx ();
+  if (zstdio->dctx == NULL)
+    {
+      grub_free (file);
+      grub_free (zstdio);
+      return NULL;
+    }
+
+  if (test_header (file) == false)
+    {
+      grub_errno = GRUB_ERR_NONE;
+      if (grub_file_seek (io, 0) == (grub_off_t) -1)
+       {
+         grub_free (file);
+         grub_free (zstdio);
+         return NULL;
+       }
+
+      ZSTD_freeDCtx (zstdio->dctx);
+      grub_free (zstdio);
+      grub_free (file);
+
+      return io;
+    }
+
+  return file;
+}
+
+static grub_ssize_t
+grub_zstdio_read (grub_file_t file, char *buf, grub_size_t len)
+{
+  zstdio_t zstdio = file->data;
+  grub_ssize_t ret = 0;
+  grub_ssize_t readret;
+  grub_off_t current_offset;
+  grub_off_t new_offset;
+  grub_size_t delta;
+  grub_size_t zret;
+
+  /* If seek backward need to reset decoder and start from beginning of file. */
+  if (file->offset < zstdio->saved_offset)
+    {
+      ZSTD_initDStream (zstdio->dctx);
+      zstdio->input.pos = 0;
+      zstdio->input.size = 0;
+      zstdio->output.pos = 0;
+      zstdio->saved_offset = 0;
+      grub_file_seek (zstdio->file, 0);
+    }
+
+  current_offset = zstdio->saved_offset;
+
+  while (len > 0)
+    {
+      zstdio->output.size = file->offset + ret + len - current_offset;
+      if (zstdio->output.size > zstdio->outsize)
+        zstdio->output.size = zstdio->outsize;
+      if (zstdio->input.pos == zstdio->input.size)
+        {
+          readret = grub_file_read (zstdio->file, zstdio->bufs,
+                                    zstdio->insize);
+          if (readret < 0)
+            return -1;
+
+          zstdio->input.size = readret;
+          zstdio->input.pos = 0;
+        }
+
+      zret = ZSTD_decompressStream (zstdio->dctx, &zstdio->output,
+                                    &zstdio->input);
+      if (ZSTD_isError (zret))
+        {
+          grub_error (GRUB_ERR_BAD_COMPRESSED_DATA,
+                      N_("zstd file corrupted or unsupported block options"));
+          return -1;
+        }
+
+      new_offset = current_offset + zstdio->output.pos;
+
+      /* Store first chunk of data in buffer.  */
+      if (file->offset <= new_offset)
+        {
+          delta = new_offset - (file->offset + ret);
+          grub_memmove (buf, (grub_uint8_t *) zstdio->output.dst +
+                        (zstdio->output.pos - delta),
+                        delta);
+          len -= delta;
+          buf += delta;
+          ret += delta;
+        }
+        current_offset = new_offset;
+
+        zstdio->output.pos = 0;
+
+        if (zstdio->input.pos == 0 && zstdio->output.pos == 0)
+          break;
+    }
+
+  if (ret >= 0)
+    zstdio->saved_offset = file->offset + ret;
+
+  return ret;
+}
+
+/* Release everything, including the underlying file object.  */
+static grub_err_t
+grub_zstdio_close (grub_file_t file)
+{
+  zstdio_t zstdio = file->data;
+
+  ZSTD_freeDCtx (zstdio->dctx);
+
+  grub_file_close (zstdio->file);
+  grub_free (zstdio);
+
+  /* Device must not be closed twice.  */
+  file->device = 0;
+  file->name = 0;
+  return grub_errno;
+}
+
+static struct grub_fs zstdio_fs = {
+  .name = "zstdio",
+  .fs_dir = 0,
+  .fs_open = 0,
+  .fs_read = grub_zstdio_read,
+  .fs_close = grub_zstdio_close,
+  .fs_label = 0,
+  .next = 0
+};
+
+GRUB_MOD_INIT (zstdio)
+{
+  grub_file_filter_register (GRUB_FILE_FILTER_ZSTDIO, grub_zstdio_open);
+}
+
+GRUB_MOD_FINI (zstdio)
+{
+  grub_file_filter_unregister (GRUB_FILE_FILTER_ZSTDIO);
+}
index 16a4b7d26437c1d071a7195f4893047b4a119ecf..d51834e6abd28be8a7a80860350af0b42273fbff 100644 (file)
@@ -190,9 +190,10 @@ typedef enum grub_file_filter_id
     GRUB_FILE_FILTER_GZIO,
     GRUB_FILE_FILTER_XZIO,
     GRUB_FILE_FILTER_LZOPIO,
+    GRUB_FILE_FILTER_ZSTDIO,
     GRUB_FILE_FILTER_MAX,
     GRUB_FILE_FILTER_COMPRESSION_FIRST = GRUB_FILE_FILTER_GZIO,
-    GRUB_FILE_FILTER_COMPRESSION_LAST = GRUB_FILE_FILTER_LZOPIO,
+    GRUB_FILE_FILTER_COMPRESSION_LAST = GRUB_FILE_FILTER_ZSTDIO,
   } grub_file_filter_id_t;
 
 typedef grub_file_t (*grub_file_filter_t) (grub_file_t in, enum grub_file_type type);