Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package libostree for openSUSE:Factory 
checked in at 2026-04-15 16:03:21
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/libostree (Old)
 and      /work/SRC/openSUSE:Factory/.libostree.new.21863 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "libostree"

Wed Apr 15 16:03:21 2026 rev:53 rq:1346716 version:2026.1

Changes:
--------
--- /work/SRC/openSUSE:Factory/libostree/libostree.changes      2025-11-18 
15:33:47.416139689 +0100
+++ /work/SRC/openSUSE:Factory/.libostree.new.21863/libostree.changes   
2026-04-15 16:04:03.533736696 +0200
@@ -1,0 +2,11 @@
+Sat Apr 11 10:47:17 UTC 2026 - Andreas Stieger <[email protected]>
+
+- Update to 2026.1:
+  * fix soft-reboot handling for var, sysroot, and boot mounts,
+  * preserve extension BLS keys across staged deployments.
+  * libarchive integration now correctly handles UTF-8 filenames
+    without locale dependency
+  * ostree admin status --json now includes the deployment origin
+    refspec.
+
+-------------------------------------------------------------------

Old:
----
  libostree-2025.7.tar.xz

New:
----
  _scmsync.obsinfo
  build.specials.obscpio
  libostree-2026.1.tar.xz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ libostree.spec ++++++
--- /var/tmp/diff_new_pack.3NuBpO/_old  2026-04-15 16:04:04.209764488 +0200
+++ /var/tmp/diff_new_pack.3NuBpO/_new  2026-04-15 16:04:04.213764653 +0200
@@ -2,7 +2,7 @@
 # spec file for package libostree
 #
 # Copyright (c) 2025 SUSE LLC
-# Copyright (c) 2025 Andreas Stieger <[email protected]>
+# Copyright (c) 2026 Andreas Stieger <[email protected]>
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
@@ -24,7 +24,7 @@
 %bcond_without     ed25519
 %bcond_with        tests
 Name:           libostree
-Version:        2025.7
+Version:        2026.1
 Release:        0
 Summary:        Git for operating system binaries
 License:        LGPL-2.0-or-later

++++++ _scmsync.obsinfo ++++++
mtime: 1775904797
commit: bfd49d4da3003e8b2231dbd7d90b3e5efce6a996fe32206d5a889658d2eec589
url: https://src.opensuse.org/GNOME/libostree
revision: bfd49d4da3003e8b2231dbd7d90b3e5efce6a996fe32206d5a889658d2eec589
projectscmsync: https://src.opensuse.org/GNOME/_ObsPrj

++++++ build.specials.obscpio ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/.gitignore new/.gitignore
--- old/.gitignore      1970-01-01 01:00:00.000000000 +0100
+++ new/.gitignore      2026-04-13 10:04:03.000000000 +0200
@@ -0,0 +1,4 @@
+*.obscpio
+*.osc
+_build.*
+.pbuild

++++++ libostree-2025.7.tar.xz -> libostree-2026.1.tar.xz ++++++
++++ 2021 lines of diff (skipped)
++++    retrying with extended exclude list
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude 
config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 
--exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh 
old/libostree-2025.7/apidoc/html/index.html 
new/libostree-2026.1/apidoc/html/index.html
--- old/libostree-2025.7/apidoc/html/index.html 2025-11-12 00:20:29.000000000 
+0100
+++ new/libostree-2026.1/apidoc/html/index.html 2026-04-10 21:57:07.000000000 
+0200
@@ -14,7 +14,7 @@
 <div class="titlepage">
 <div>
 <div><table class="navigation" id="top" width="100%" cellpadding="2" 
cellspacing="0"><tr><th valign="middle"><p class="title">OSTree API 
references</p></th></tr></table></div>
-<div><p class="releaseinfo">for OSTree 2025.7</p></div>
+<div><p class="releaseinfo">for OSTree 2026.1</p></div>
 </div>
 <hr>
 </div>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude 
config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 
--exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh 
old/libostree-2025.7/apidoc/html/ostree-ostree-version.html 
new/libostree-2026.1/apidoc/html/ostree-ostree-version.html
--- old/libostree-2025.7/apidoc/html/ostree-ostree-version.html 2025-11-12 
00:20:29.000000000 +0100
+++ new/libostree-2026.1/apidoc/html/ostree-ostree-version.html 2026-04-10 
21:57:07.000000000 +0200
@@ -118,7 +118,7 @@
 <a name="ostree-ostree-version.other_details"></a><h2>Types and Values</h2>
 <div class="refsect2">
 <a name="OSTREE-YEAR-VERSION:CAPS"></a><h3>OSTREE_YEAR_VERSION</h3>
-<pre class="programlisting">#define OSTREE_YEAR_VERSION (2025)
+<pre class="programlisting">#define OSTREE_YEAR_VERSION (2026)
 </pre>
 <p>ostree year version component (e.g. 2017 if <a class="link" 
href="ostree-ostree-version.html#OSTREE-VERSION:CAPS" 
title="OSTREE_VERSION"><code class="literal">OSTREE_VERSION</code></a> is 
2017.2)</p>
 <p class="since">Since: 2017.4</p>
@@ -126,7 +126,7 @@
 <hr>
 <div class="refsect2">
 <a name="OSTREE-RELEASE-VERSION:CAPS"></a><h3>OSTREE_RELEASE_VERSION</h3>
-<pre class="programlisting">#define OSTREE_RELEASE_VERSION (7)
+<pre class="programlisting">#define OSTREE_RELEASE_VERSION (1)
 </pre>
 <p>ostree release version component (e.g. 2 if <a class="link" 
href="ostree-ostree-version.html#OSTREE-VERSION:CAPS" 
title="OSTREE_VERSION"><code class="literal">OSTREE_VERSION</code></a> is 
2017.2)</p>
 <p class="since">Since: 2017.4</p>
@@ -134,7 +134,7 @@
 <hr>
 <div class="refsect2">
 <a name="OSTREE-VERSION:CAPS"></a><h3>OSTREE_VERSION</h3>
-<pre class="programlisting">#define OSTREE_VERSION (2025.7)
+<pre class="programlisting">#define OSTREE_VERSION (2026.1)
 </pre>
 <p>ostree version.</p>
 <p class="since">Since: 2017.4</p>
@@ -142,7 +142,7 @@
 <hr>
 <div class="refsect2">
 <a name="OSTREE-VERSION-S:CAPS"></a><h3>OSTREE_VERSION_S</h3>
-<pre class="programlisting">#define OSTREE_VERSION_S "2025.7"
+<pre class="programlisting">#define OSTREE_VERSION_S "2026.1"
 </pre>
 <p>ostree version, encoded as a string, useful for printing and
 concatenation.</p>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude 
config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 
--exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh 
old/libostree-2025.7/apidoc/html/style.css 
new/libostree-2026.1/apidoc/html/style.css
--- old/libostree-2025.7/apidoc/html/style.css  2025-11-12 00:20:29.000000000 
+0100
+++ new/libostree-2026.1/apidoc/html/style.css  2026-04-10 21:57:07.000000000 
+0200
@@ -462,9 +462,9 @@
 span.linenos.special { color: #000000; background-color: #ffffc0; 
padding-left: 5px; padding-right: 5px; }
 .hll { background-color: #ffffcc }
 .c { color: #3D7B7B; font-style: italic } /* Comment */
-.err { border: 1px solid #FF0000 } /* Error */
+.err { border: 1px solid #F00 } /* Error */
 .k { color: #008000; font-weight: bold } /* Keyword */
-.o { color: #666666 } /* Operator */
+.o { color: #666 } /* Operator */
 .ch { color: #3D7B7B; font-style: italic } /* Comment.Hashbang */
 .cm { color: #3D7B7B; font-style: italic } /* Comment.Multiline */
 .cp { color: #9C6500 } /* Comment.Preproc */
@@ -481,34 +481,34 @@
 .gp { color: #000080; font-weight: bold } /* Generic.Prompt */
 .gs { font-weight: bold } /* Generic.Strong */
 .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
-.gt { color: #0044DD } /* Generic.Traceback */
+.gt { color: #04D } /* Generic.Traceback */
 .kc { color: #008000; font-weight: bold } /* Keyword.Constant */
 .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */
 .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */
 .kp { color: #008000 } /* Keyword.Pseudo */
 .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */
 .kt { color: #B00040 } /* Keyword.Type */
-.m { color: #666666 } /* Literal.Number */
+.m { color: #666 } /* Literal.Number */
 .s { color: #BA2121 } /* Literal.String */
 .na { color: #687822 } /* Name.Attribute */
 .nb { color: #008000 } /* Name.Builtin */
-.nc { color: #0000FF; font-weight: bold } /* Name.Class */
-.no { color: #880000 } /* Name.Constant */
-.nd { color: #AA22FF } /* Name.Decorator */
+.nc { color: #00F; font-weight: bold } /* Name.Class */
+.no { color: #800 } /* Name.Constant */
+.nd { color: #A2F } /* Name.Decorator */
 .ni { color: #717171; font-weight: bold } /* Name.Entity */
 .ne { color: #CB3F38; font-weight: bold } /* Name.Exception */
-.nf { color: #0000FF } /* Name.Function */
+.nf { color: #00F } /* Name.Function */
 .nl { color: #767600 } /* Name.Label */
-.nn { color: #0000FF; font-weight: bold } /* Name.Namespace */
+.nn { color: #00F; font-weight: bold } /* Name.Namespace */
 .nt { color: #008000; font-weight: bold } /* Name.Tag */
 .nv { color: #19177C } /* Name.Variable */
-.ow { color: #AA22FF; font-weight: bold } /* Operator.Word */
-.w { color: #bbbbbb } /* Text.Whitespace */
-.mb { color: #666666 } /* Literal.Number.Bin */
-.mf { color: #666666 } /* Literal.Number.Float */
-.mh { color: #666666 } /* Literal.Number.Hex */
-.mi { color: #666666 } /* Literal.Number.Integer */
-.mo { color: #666666 } /* Literal.Number.Oct */
+.ow { color: #A2F; font-weight: bold } /* Operator.Word */
+.w { color: #BBB } /* Text.Whitespace */
+.mb { color: #666 } /* Literal.Number.Bin */
+.mf { color: #666 } /* Literal.Number.Float */
+.mh { color: #666 } /* Literal.Number.Hex */
+.mi { color: #666 } /* Literal.Number.Integer */
+.mo { color: #666 } /* Literal.Number.Oct */
 .sa { color: #BA2121 } /* Literal.String.Affix */
 .sb { color: #BA2121 } /* Literal.String.Backtick */
 .sc { color: #BA2121 } /* Literal.String.Char */
@@ -523,9 +523,9 @@
 .s1 { color: #BA2121 } /* Literal.String.Single */
 .ss { color: #19177C } /* Literal.String.Symbol */
 .bp { color: #008000 } /* Name.Builtin.Pseudo */
-.fm { color: #0000FF } /* Name.Function.Magic */
+.fm { color: #00F } /* Name.Function.Magic */
 .vc { color: #19177C } /* Name.Variable.Class */
 .vg { color: #19177C } /* Name.Variable.Global */
 .vi { color: #19177C } /* Name.Variable.Instance */
 .vm { color: #19177C } /* Name.Variable.Magic */
-.il { color: #666666 } /* Literal.Number.Integer.Long */
\ No newline at end of file
+.il { color: #666 } /* Literal.Number.Integer.Long */
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude 
config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 
--exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh 
old/libostree-2025.7/apidoc/version.xml new/libostree-2026.1/apidoc/version.xml
--- old/libostree-2025.7/apidoc/version.xml     2025-11-12 00:20:16.000000000 
+0100
+++ new/libostree-2026.1/apidoc/version.xml     2026-04-10 21:56:59.000000000 
+0200
@@ -1 +1 @@
-2025.7
\ No newline at end of file
+2026.1
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude 
config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 
--exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh 
old/libostree-2025.7/build-aux/compile new/libostree-2026.1/build-aux/compile
--- old/libostree-2025.7/build-aux/compile      2025-11-12 00:20:01.000000000 
+0100
+++ new/libostree-2026.1/build-aux/compile      2026-04-10 21:56:44.000000000 
+0200
@@ -1,9 +1,9 @@
 #! /bin/sh
 # Wrapper for compilers which do not understand '-c -o'.
 
-scriptversion=2024-06-19.01; # UTC
+scriptversion=2025-06-18.21; # UTC
 
-# Copyright (C) 1999-2024 Free Software Foundation, Inc.
+# Copyright (C) 1999-2025 Free Software Foundation, Inc.
 # Written by Tom Tromey <[email protected]>.
 #
 # This program is free software; you can redistribute it and/or modify
@@ -37,11 +37,11 @@
 
 file_conv=
 
-# func_file_conv build_file lazy
+# func_file_conv build_file unneeded_conversions
 # Convert a $build file to $host form and store it in $file
 # Currently only supports Windows hosts. If the determined conversion
-# type is listed in (the comma separated) LAZY, no conversion will
-# take place.
+# type is listed in (the comma separated) UNNEEDED_CONVERSIONS, no
+# conversion will take place.
 func_file_conv ()
 {
   file=$1
@@ -51,9 +51,20 @@
        # lazily determine how to convert abs files
        case `uname -s` in
          MINGW*)
-           file_conv=mingw
+           if test -n "$MSYSTEM" && (cygpath --version) >/dev/null 2>&1; then
+             # MSYS2 environment.
+             file_conv=cygwin
+           else
+             # Original MinGW environment.
+             file_conv=mingw
+           fi
            ;;
-         CYGWIN* | MSYS*)
+         MSYS*)
+           # Old MSYS environment, or MSYS2 with 32-bit MSYS2 shell.
+           file_conv=cygwin
+           ;;
+         CYGWIN*)
+           # Cygwin environment.
            file_conv=cygwin
            ;;
          *)
@@ -63,12 +74,14 @@
       fi
       case $file_conv/,$2, in
        *,$file_conv,*)
+         # This is the optimization mentioned above:
+         # If UNNEEDED_CONVERSIONS contains $file_conv, don't convert.
          ;;
        mingw/*)
          file=`cmd //C echo "$file " | sed -e 's/"\(.*\) " *$/\1/'`
          ;;
-       cygwin/* | msys/*)
-         file=`cygpath -m "$file" || echo "$file"`
+       cygwin/*)
+         file=`cygpath -w "$file" || echo "$file"`
          ;;
        wine/*)
          file=`winepath -w "$file" || echo "$file"`
@@ -343,9 +356,9 @@
 # Local Variables:
 # mode: shell-script
 # sh-indentation: 2
-# eval: (add-hook 'before-save-hook 'time-stamp)
+# eval: (add-hook 'before-save-hook 'time-stamp nil t)
 # time-stamp-start: "scriptversion="
-# time-stamp-format: "%:y-%02m-%02d.%02H"
+# time-stamp-format: "%Y-%02m-%02d.%02H"
 # time-stamp-time-zone: "UTC0"
 # time-stamp-end: "; # UTC"
 # End:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude 
config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 
--exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh 
old/libostree-2025.7/build-aux/missing new/libostree-2026.1/build-aux/missing
--- old/libostree-2025.7/build-aux/missing      2025-11-12 00:20:01.000000000 
+0100
+++ new/libostree-2026.1/build-aux/missing      2026-04-10 21:56:44.000000000 
+0200
@@ -1,11 +1,11 @@
 #! /bin/sh
 # Common wrapper for a few potentially missing GNU and other programs.
 
-scriptversion=2024-06-07.14; # UTC
+scriptversion=2025-06-18.21; # UTC
 
 # shellcheck disable=SC2006,SC2268 # we must support pre-POSIX shells
 
-# Copyright (C) 1996-2024 Free Software Foundation, Inc.
+# Copyright (C) 1996-2025 Free Software Foundation, Inc.
 # Originally written by Fran,cois Pinard <[email protected]>, 1996.
 
 # This program is free software; you can redistribute it and/or modify
@@ -228,9 +228,9 @@
 exit $st
 
 # Local variables:
-# eval: (add-hook 'before-save-hook 'time-stamp)
+# eval: (add-hook 'before-save-hook 'time-stamp nil t)
 # time-stamp-start: "scriptversion="
-# time-stamp-format: "%:y-%02m-%02d.%02H"
+# time-stamp-format: "%Y-%02m-%02d.%02H"
 # time-stamp-time-zone: "UTC0"
 # time-stamp-end: "; # UTC"
 # End:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude 
config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 
--exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh 
old/libostree-2025.7/build-aux/test-driver 
new/libostree-2026.1/build-aux/test-driver
--- old/libostree-2025.7/build-aux/test-driver  2025-11-12 00:20:02.000000000 
+0100
+++ new/libostree-2026.1/build-aux/test-driver  2026-04-10 21:56:45.000000000 
+0200
@@ -1,9 +1,9 @@
 #! /bin/sh
 # test-driver - basic testsuite driver script.
 
-scriptversion=2024-06-19.01; # UTC
+scriptversion=2025-06-18.21; # UTC
 
-# Copyright (C) 2011-2024 Free Software Foundation, Inc.
+# Copyright (C) 2011-2025 Free Software Foundation, Inc.
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -152,9 +152,9 @@
 # Local Variables:
 # mode: shell-script
 # sh-indentation: 2
-# eval: (add-hook 'before-save-hook 'time-stamp)
+# eval: (add-hook 'before-save-hook 'time-stamp nil t)
 # time-stamp-start: "scriptversion="
-# time-stamp-format: "%:y-%02m-%02d.%02H"
+# time-stamp-format: "%Y-%02m-%02d.%02H"
 # time-stamp-time-zone: "UTC0"
 # time-stamp-end: "; # UTC"
 # End:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude 
config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 
--exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh 
old/libostree-2025.7/configure.ac new/libostree-2026.1/configure.ac
--- old/libostree-2025.7/configure.ac   2025-11-12 00:19:44.000000000 +0100
+++ new/libostree-2026.1/configure.ac   2026-04-10 21:55:51.000000000 +0200
@@ -1,7 +1,7 @@
 AC_PREREQ([2.63])
 dnl To perform a release, follow the instructions in `docs/CONTRIBUTING.md`.
-m4_define([year_version], [2025])
-m4_define([release_version], [7])
+m4_define([year_version], [2026])
+m4_define([release_version], [1])
 m4_define([package_version], [year_version.release_version])
 AC_INIT([libostree], [package_version], [[email protected]])
 is_release_build=yes
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude 
config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 
--exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh 
old/libostree-2025.7/src/boot/dracut/module-setup.sh 
new/libostree-2026.1/src/boot/dracut/module-setup.sh
--- old/libostree-2025.7/src/boot/dracut/module-setup.sh        2025-11-12 
00:19:26.000000000 +0100
+++ new/libostree-2026.1/src/boot/dracut/module-setup.sh        2026-04-07 
20:20:02.000000000 +0200
@@ -46,7 +46,7 @@
         inst_simple "/etc/ostree/initramfs-root-binding.key"
     fi
     inst_simple "${systemdsystemunitdir}/ostree-prepare-root.service"
-    mkdir -p "${initdir}${systemdsystemconfdir}/initrd-root-fs.target.wants"
+    mkdir -p "${initdir}${systemdsystemunitdir}/initrd-root-fs.target.wants"
     ln_r "${systemdsystemunitdir}/ostree-prepare-root.service" \
-        
"${systemdsystemconfdir}/initrd-root-fs.target.wants/ostree-prepare-root.service"
+        
"${systemdsystemunitdir}/initrd-root-fs.target.wants/ostree-prepare-root.service"
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude 
config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 
--exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh 
old/libostree-2025.7/src/libostree/ostree-bootconfig-parser-private.h 
new/libostree-2026.1/src/libostree/ostree-bootconfig-parser-private.h
--- old/libostree-2025.7/src/libostree/ostree-bootconfig-parser-private.h       
2025-11-12 00:19:26.000000000 +0100
+++ new/libostree-2026.1/src/libostree/ostree-bootconfig-parser-private.h       
2026-04-07 02:00:12.000000000 +0200
@@ -8,4 +8,6 @@
 
 const char *_ostree_bootconfig_parser_filename (OstreeBootconfigParser *self);
 
+GVariant *_ostree_bootconfig_parser_get_extra_keys_variant 
(OstreeBootconfigParser *self);
+
 G_END_DECLS
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude 
config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 
--exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh 
old/libostree-2025.7/src/libostree/ostree-bootconfig-parser.c 
new/libostree-2026.1/src/libostree/ostree-bootconfig-parser.c
--- old/libostree-2025.7/src/libostree/ostree-bootconfig-parser.c       
2025-11-12 00:19:26.000000000 +0100
+++ new/libostree-2026.1/src/libostree/ostree-bootconfig-parser.c       
2026-04-07 02:00:12.000000000 +0200
@@ -339,6 +339,60 @@
                                             cancellable, error);
 }
 
+/* Standard BLS keys that are managed by ostree's own deployment code.
+ * These are rebuilt from scratch during staged deployment finalization
+ * (title, version, linux, initrd from the deployment; options from the
+ * serialized kargs), so they must NOT be duplicated into bootconfig-extra.
+ */
+static const char *const standard_bls_keys[]
+    = { "title", "version", "options", "linux", "initrd", "devicetree", NULL };
+
+static gboolean
+is_standard_bls_key (const char *key)
+{
+  for (const char *const *p = standard_bls_keys; *p != NULL; p++)
+    {
+      if (strcmp (key, *p) == 0)
+        return TRUE;
+    }
+  return FALSE;
+}
+
+/**
+ * _ostree_bootconfig_parser_get_extra_keys_variant:
+ * @self: Parser
+ *
+ * Returns a GVariant of type "a{ss}" containing all bootconfig keys
+ * that are not part of the standard BLS set managed by ostree.  These
+ * are extension keys set by consumers like bootc (e.g.
+ * "x-options-source-tuned") that need to survive the staged deployment
+ * serialization roundtrip.
+ *
+ * Returns: (transfer full) (nullable): A new floating GVariant, or NULL if
+ *          there are no extra keys
+ */
+GVariant *
+_ostree_bootconfig_parser_get_extra_keys_variant (OstreeBootconfigParser *self)
+{
+  g_auto (GVariantBuilder) builder = OT_VARIANT_BUILDER_INITIALIZER;
+  gboolean has_entries = FALSE;
+
+  g_variant_builder_init (&builder, (GVariantType *)"a{ss}");
+
+  GLNX_HASH_TABLE_FOREACH_KV (self->options, const char *, k, const char *, v)
+    {
+      if (is_standard_bls_key (k))
+        continue;
+      g_variant_builder_add (&builder, "{ss}", k, v);
+      has_entries = TRUE;
+    }
+
+  if (!has_entries)
+    return NULL;
+
+  return g_variant_builder_end (&builder);
+}
+
 static void
 ostree_bootconfig_parser_finalize (GObject *object)
 {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude 
config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 
--exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh 
old/libostree-2025.7/src/libostree/ostree-impl-system-generator.c 
new/libostree-2026.1/src/libostree/ostree-impl-system-generator.c
--- old/libostree-2025.7/src/libostree/ostree-impl-system-generator.c   
2025-11-12 00:19:26.000000000 +0100
+++ new/libostree-2026.1/src/libostree/ostree-impl-system-generator.c   
2026-04-10 16:34:18.000000000 +0200
@@ -150,6 +150,177 @@
   return TRUE;
 }
 
+/* Generate a drop-in for a mount unit to set DefaultDependencies=no.
+ *
+ * By default, systemd auto-generates mount units from /proc/self/mountinfo 
with
+ * DefaultDependencies=yes, which includes Conflicts=umount.target. This causes
+ * the mount to be unmounted during soft-reboot shutdown. Since soft-reboot 
doesn't
+ * re-run the initramfs, these mounts are never remounted, breaking 
bootc/ostree.
+ *
+ * By generating a drop-in, we override just the DefaultDependencies setting
+ * while letting systemd handle the actual mount parameters (What=, etc.) from
+ * the existing mount in /proc/self/mountinfo.
+ *
+ * @mount_unit: The mount unit name (e.g., "sysroot.mount", "boot.mount")
+ * @mount_point: The mount point path for the comment (e.g., "/sysroot", 
"/boot")
+ */
+static gboolean
+generate_mount_unit_dropin (int normal_dir_dfd, const char *mount_unit, const 
char *mount_point,
+                            GError **error)
+{
+  GCancellable *cancellable = NULL;
+
+  /* Create the drop-in directory (e.g., sysroot.mount.d) */
+  g_autofree char *dropin_dir = g_strdup_printf ("%s.d", mount_unit);
+  if (!glnx_shutil_mkdir_p_at (normal_dir_dfd, dropin_dir, 0755, cancellable, 
error))
+    return FALSE;
+
+  g_auto (GLnxTmpfile) tmpf = {
+    0,
+  };
+  if (!glnx_open_tmpfile_linkable_at (normal_dir_dfd, ".", O_WRONLY | 
O_CLOEXEC, &tmpf, error))
+    return FALSE;
+  g_autoptr (GOutputStream) outstream = g_unix_output_stream_new (tmpf.fd, 
FALSE);
+  gsize bytes_written;
+
+  /* Generate drop-in to set DefaultDependencies=no.
+   *
+   * Key points:
+   * - DefaultDependencies=no prevents Conflicts=umount.target from being added
+   * - This allows the mount to survive soft-reboot
+   * - The actual mount (What=, Where=, etc.) comes from /proc/self/mountinfo
+   */
+  if (!g_output_stream_printf (outstream, &bytes_written, cancellable, error,
+                               "##\n# Automatically generated by 
ostree-system-generator\n"
+                               "# Preserve %s across soft-reboot\n##\n\n"
+                               "[Unit]\n"
+                               "DefaultDependencies=no\n"
+                               "After=local-fs-pre.target\n"
+                               "Before=local-fs.target\n",
+                               mount_point))
+    return FALSE;
+  if (!g_output_stream_flush (outstream, cancellable, error))
+    return FALSE;
+  g_clear_object (&outstream);
+  if (!glnx_fchmod (tmpf.fd, 0644, error))
+    return FALSE;
+
+  g_autofree char *dropin_path = g_strdup_printf ("%s/ostree-softreboot.conf", 
dropin_dir);
+  if (!glnx_link_tmpfile_at (&tmpf, GLNX_LINK_TMPFILE_NOREPLACE, 
normal_dir_dfd, dropin_path,
+                             error))
+    return FALSE;
+
+  return TRUE;
+}
+
+/* Generate a drop-in for sysroot.mount to preserve it across soft-reboot.
+ *
+ * /sysroot is special: it's mounted in the initramfs and can never be
+ * remounted without re-running the initramfs. Since soft-reboot skips
+ * the initramfs, we must prevent systemd from unmounting it.
+ */
+static gboolean
+sysroot_mount_generator (const char *normal_dir, GError **error)
+{
+  glnx_autofd int normal_dir_dfd = -1;
+
+  if (!glnx_opendirat (AT_FDCWD, normal_dir, TRUE, &normal_dir_dfd, error))
+    return FALSE;
+
+  if (!generate_mount_unit_dropin (normal_dir_dfd, "sysroot.mount", 
"/sysroot", error))
+    return FALSE;
+
+  return TRUE;
+}
+
+/* Generate boot.mount for /boot when it's on the same partition as /sysroot.
+ *
+ * When /boot is on a separate partition, systemd auto-generates a mount unit
+ * from /proc/self/mountinfo and will remount it after soft-reboot. No action
+ * needed in that case.
+ *
+ * When /boot is on the same partition (detected by /sysroot/boot/loader being
+ * a symlink), a bind mount from /sysroot/boot is needed. Previously this was
+ * done in the initramfs (ostree-prepare-root), but that breaks on bare
+ * soft-reboot since the initramfs doesn't re-run. By generating boot.mount
+ * here, the generator handles all three boot scenarios:
+ *   1. Normal boot (generator runs from initramfs)
+ *   2. Bare soft-reboot (generator re-runs)
+ *   3. Staged deployment soft-reboot (generator re-runs for new root)
+ *
+ * See: https://github.com/ostreedev/ostree/pull/3487
+ *      https://github.com/ostreedev/ostree/pull/3571
+ */
+static gboolean
+boot_mount_generator (const char *normal_dir, GError **error)
+{
+  GCancellable *cancellable = NULL;
+  static const char boot_path[] = "/boot";
+  struct stat stbuf;
+
+  /* Check if /boot is on the same partition as /sysroot by looking for
+   * /sysroot/boot/loader as a symlink. This is the same check used by
+   * otcore_mount_boot() in the initramfs path.
+   */
+  if (!(lstat ("/sysroot/boot/loader", &stbuf) == 0 && S_ISLNK 
(stbuf.st_mode)))
+    return TRUE; /* /boot is a separate partition, systemd handles it */
+
+  /* Verify the target /boot directory exists */
+  if (!(lstat ("/boot", &stbuf) == 0 && S_ISDIR (stbuf.st_mode)))
+    return TRUE;
+
+  glnx_autofd int normal_dir_dfd = -1;
+  if (!glnx_opendirat (AT_FDCWD, normal_dir, TRUE, &normal_dir_dfd, error))
+    return FALSE;
+
+  g_auto (GLnxTmpfile) tmpf = {
+    0,
+  };
+  if (!glnx_open_tmpfile_linkable_at (normal_dir_dfd, ".", O_WRONLY | 
O_CLOEXEC, &tmpf, error))
+    return FALSE;
+  g_autoptr (GOutputStream) outstream = g_unix_output_stream_new (tmpf.fd, 
FALSE);
+  gsize bytes_written;
+
+  /* Generate a boot.mount unit that bind-mounts /sysroot/boot to /boot.
+   *
+   * We use DefaultDependencies=no for the same reasons as var.mount:
+   * to avoid implicit device ordering that can stall after soft-reboot.
+   * Since this is a bind mount from /sysroot, we only need sysroot.mount.
+   */
+  if (!g_output_stream_printf (outstream, &bytes_written, cancellable, error,
+                               "##\n# Automatically generated by 
ostree-system-generator\n"
+                               "# Bind mount /boot from /sysroot/boot (same 
partition)\n##\n\n"
+                               "[Unit]\n"
+                               "Documentation=man:ostree(1)\n"
+                               "DefaultDependencies=no\n"
+                               "After=local-fs-pre.target sysroot.mount\n"
+                               "Before=local-fs.target\n"
+                               "\n"
+                               "[Mount]\n"
+                               "Where=%s\n"
+                               "What=/sysroot/boot\n"
+                               "Options=bind\n",
+                               boot_path))
+    return FALSE;
+  if (!g_output_stream_flush (outstream, cancellable, error))
+    return FALSE;
+  g_clear_object (&outstream);
+  if (!glnx_fchmod (tmpf.fd, 0644, error))
+    return FALSE;
+  if (!glnx_link_tmpfile_at (&tmpf, GLNX_LINK_TMPFILE_NOREPLACE, 
normal_dir_dfd, "boot.mount",
+                             error))
+    return FALSE;
+
+  /* Ensure it's pulled in by local-fs.target */
+  if (!glnx_shutil_mkdir_p_at (normal_dir_dfd, "local-fs.target.requires", 
0755, cancellable,
+                               error))
+    return FALSE;
+  if (symlinkat ("../boot.mount", normal_dir_dfd, 
"local-fs.target.requires/boot.mount") < 0)
+    return glnx_throw_errno_prefix (error, "symlinkat");
+
+  return TRUE;
+}
+
 /* Generate var.mount */
 static gboolean
 fstab_generator (const char *ostree_target, const bool is_aboot, const char 
*normal_dir,
@@ -237,12 +408,33 @@
    * Documentation/filesystems/sharedsubtree.txt and
    * https://github.com/ostreedev/ostree/issues/2086. This also happens in
    * ostree-prepare-root.c for the INITRAMFS_MOUNT_VAR case.
+   *
+   * We use DefaultDependencies=no to avoid implicit ordering dependencies that
+   * can cause the mount to stall after a bare `systemctl soft-reboot`. Without
+   * this, systemd may add implicit After= dependencies on device units, which
+   * can get stuck in 'tentative' state while udev restarts after soft-reboot.
+   * Since this is a bind mount from /sysroot (which survives soft-reboot), we
+   * only need to wait for sysroot.mount and local-fs-pre.target.
+   *
+   * Note: We intentionally do NOT add Conflicts=umount.target or
+   * Before=umount.target here. Adding those creates a dependency deadlock
+   * with ostree-remount.service during soft-reboot shutdown, because:
+   * - ostree-remount.service has After=var.mount and Before=local-fs.target
+   * - Adding Conflicts=umount.target to var.mount creates circular ordering
+   *   where umount.target waits for ostree-remount.service, which waits for
+   *   local-fs.target, which waits for var.mount
+   * Since /var survives soft-reboot (as a bind mount from /sysroot), systemd
+   * catches it up from mountinfo and it doesn't need special unmount handling.
+   *
+   * See https://issues.redhat.com/browse/RHEL-154075
    */
   if (!g_output_stream_printf (outstream, &bytes_written, cancellable, error,
                                "##\n# Automatically generated by 
ostree-system-generator\n##\n\n"
                                "[Unit]\n"
                                "Documentation=man:ostree(1)\n"
+                               "DefaultDependencies=no\n"
                                "ConditionKernelCommandLine=!systemd.volatile\n"
+                               "After=local-fs-pre.target sysroot.mount\n"
                                "Before=local-fs.target\n"
                                "\n"
                                "[Mount]\n"
@@ -320,6 +512,10 @@
 
   if (!require_internal_units (normal_dir, early_dir, late_dir, error))
     return FALSE;
+  if (!sysroot_mount_generator (normal_dir, error))
+    return FALSE;
+  if (!boot_mount_generator (normal_dir, error))
+    return FALSE;
   if (!fstab_generator (ostree_target, is_aboot, normal_dir, early_dir, 
late_dir, error))
     return FALSE;
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude 
config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 
--exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh 
old/libostree-2025.7/src/libostree/ostree-repo-libarchive.c 
new/libostree-2026.1/src/libostree/ostree-repo-libarchive.c
--- old/libostree-2025.7/src/libostree/ostree-repo-libarchive.c 2025-11-12 
00:19:26.000000000 +0100
+++ new/libostree-2026.1/src/libostree/ostree-repo-libarchive.c 2026-04-07 
02:00:06.000000000 +0200
@@ -44,6 +44,74 @@
   g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "%s", 
archive_error_string (a));
 }
 
+/*
+ * Get pathname from archive entry as UTF-8.
+ *
+ * libarchive attempts to convert filenames to the current locale's charset,
+ * which fails in POSIX/C locale for non-ASCII UTF-8 characters. This function
+ * uses archive_entry_pathname_utf8() to bypass locale conversion, falling back
+ * to archive_entry_pathname() with explicit UTF-8 validation.
+ *
+ * Returns NULL and sets error if the pathname is missing or not valid UTF-8.
+ */
+static const char *
+archive_entry_require_pathname_utf8 (struct archive_entry *entry, GError 
**error)
+{
+  /* Try the UTF-8 accessor first - this returns the UTF-8 form directly
+   * without locale conversion. */
+  const char *pathname = archive_entry_pathname_utf8 (entry);
+  if (pathname != NULL)
+    return pathname;
+
+  /* Fall back to regular accessor. When libarchive's charset conversion
+   * fails (e.g., in POSIX locale), it falls back to copying raw bytes,
+   * which for OCI/Docker tarballs should be valid UTF-8. */
+  pathname = archive_entry_pathname (entry);
+  if (pathname == NULL)
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Archive entry has no 
pathname");
+      return NULL;
+    }
+
+  if (!g_utf8_validate (pathname, -1, NULL))
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
+                   "Archive entry pathname is not valid UTF-8");
+      return NULL;
+    }
+
+  return pathname;
+}
+
+/*
+ * Get symlink target from archive entry, validating it is UTF-8.
+ * Returns NULL (without error) if entry is not a symlink.
+ * Returns NULL with error set if symlink target is not valid UTF-8.
+ */
+static const char *
+archive_entry_require_symlink_utf8 (struct archive_entry *entry, GError 
**error)
+{
+  /* Try the UTF-8 accessor first - this returns the UTF-8 form directly
+   * without locale conversion. */
+  const char *target = archive_entry_symlink_utf8 (entry);
+  if (target != NULL)
+    return target;
+
+  /* Fall back to regular accessor with explicit UTF-8 validation */
+  target = archive_entry_symlink (entry);
+  if (target == NULL)
+    return NULL;
+
+  if (!g_utf8_validate (target, -1, NULL))
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
+                   "Archive entry symlink target is not valid UTF-8");
+      return NULL;
+    }
+
+  return target;
+}
+
 static const char *
 path_relative (const char *src, GError **error)
 {
@@ -131,21 +199,35 @@
     stbuf->st_mode |= S_IFREG;
 }
 
-/* Create a GFileInfo from archive_entry_stat() */
+/* Create a GFileInfo from archive_entry_stat().
+ *
+ * For symlinks, validates that the target is valid UTF-8.
+ * Returns NULL with error set on failure.
+ */
 static GFileInfo *
-file_info_from_archive_entry (struct archive_entry *entry)
+file_info_from_archive_entry (struct archive_entry *entry, GError **error)
 {
   struct stat stbuf;
   read_archive_entry_stat (entry, &stbuf);
 
-  g_autoptr (GFileInfo) info = _ostree_stbuf_to_gfileinfo (&stbuf);
+  /* For symlinks, validate and get the UTF-8 target */
+  const char *symlink_target = NULL;
   if (S_ISLNK (stbuf.st_mode))
     {
-      const char *target = archive_entry_symlink (entry);
-      if (target != NULL)
-        g_file_info_set_attribute_byte_string (info, 
"standard::symlink-target", target);
+      symlink_target = archive_entry_require_symlink_utf8 (entry, error);
+      /* A symlink without a target is an error */
+      if (symlink_target == NULL)
+        {
+          if (error != NULL && *error == NULL)
+            g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Symlink entry 
has no target");
+          return NULL;
+        }
     }
 
+  g_autoptr (GFileInfo) info = _ostree_stbuf_to_gfileinfo (&stbuf);
+  if (symlink_target != NULL)
+    g_file_info_set_attribute_byte_string (info, "standard::symlink-target", 
symlink_target);
+
   return g_steal_pointer (&info);
 }
 
@@ -222,6 +304,7 @@
   OstreeMutableTree *root;
   struct archive *archive;
   struct archive_entry *entry;
+  GFileInfo *file_info; /* Cached file info for current entry, set by 
aic_import_entry */
   GHashTable *deferred_hardlinks;
   OstreeRepoCommitModifier *modifier;
 } OstreeRepoArchiveImportContext;
@@ -255,7 +338,10 @@
 static inline char *
 aic_get_final_entry_pathname (OstreeRepoArchiveImportContext *ctx, GError 
**error)
 {
-  const char *pathname = archive_entry_pathname (ctx->entry);
+  const char *pathname = archive_entry_require_pathname_utf8 (ctx->entry, 
error);
+  if (pathname == NULL)
+    return NULL;
+
   g_autofree char *final = aic_get_final_path (ctx, pathname, error);
   if (final == NULL)
     return NULL;
@@ -288,12 +374,11 @@
 aic_apply_modifier_filter (OstreeRepoArchiveImportContext *ctx, const char 
*relpath,
                            GFileInfo **out_file_info)
 {
-  g_autoptr (GFileInfo) file_info = NULL;
   g_autofree char *abspath = NULL;
   const char *cb_path = NULL;
 
   if (ctx->opts->callback_with_entry_pathname)
-    cb_path = archive_entry_pathname (ctx->entry);
+    cb_path = archive_entry_pathname_utf8 (ctx->entry);
   else
     {
       /* the user expects an abspath (where the dir to commit represents /) */
@@ -301,9 +386,11 @@
       cb_path = abspath;
     }
 
-  file_info = file_info_from_archive_entry (ctx->entry);
+  /* Use the pre-validated file_info from ctx, computed by aic_import_entry
+   * which has proper error propagation for UTF-8 validation failures. */
+  g_assert (ctx->file_info != NULL);
 
-  return _ostree_repo_commit_modifier_apply (ctx->repo, ctx->modifier, 
cb_path, file_info,
+  return _ostree_repo_commit_modifier_apply (ctx->repo, ctx->modifier, 
cb_path, ctx->file_info,
                                              out_file_info);
 }
 
@@ -444,7 +531,7 @@
     }
 
   if (ctx->opts->callback_with_entry_pathname)
-    cb_path = archive_entry_pathname (ctx->entry);
+    cb_path = archive_entry_pathname_utf8 (ctx->entry);
 
   if (ctx->modifier && ctx->modifier->xattr_callback)
     {
@@ -625,8 +712,21 @@
   if (path == NULL)
     return FALSE;
 
+  /* Compute file info early while we have error propagation. This validates
+   * symlink targets are UTF-8 and caches the result in ctx->file_info for
+   * use by aic_apply_modifier_filter which cannot propagate errors.
+   * The struct owns the reference until we clear it. */
+  g_assert (ctx->file_info == NULL);
+  ctx->file_info = file_info_from_archive_entry (ctx->entry, error);
+  if (ctx->file_info == NULL)
+    return FALSE;
+
   g_autoptr (GFileInfo) fi = NULL;
-  if (aic_apply_modifier_filter (ctx, path, &fi) == 
OSTREE_REPO_COMMIT_FILTER_SKIP)
+  OstreeRepoCommitFilterResult filter_result = aic_apply_modifier_filter (ctx, 
path, &fi);
+
+  g_clear_object (&ctx->file_info);
+
+  if (filter_result == OSTREE_REPO_COMMIT_FILTER_SKIP)
     return TRUE;
 
   g_autoptr (OstreeMutableTree) parent = NULL;
@@ -799,11 +899,17 @@
       int r = archive_read_next_header (a, &aictx.entry);
       if (r == ARCHIVE_EOF)
         break;
-      if (r != ARCHIVE_OK)
+      /* Accept ARCHIVE_WARN: libarchive returns this for "partial success"
+       * conditions like charset conversion failures (e.g., UTF-8 to ASCII
+       * in POSIX locale). The entry is still fully populated; we validate
+       * filenames as UTF-8 ourselves. Fail only on ARCHIVE_FATAL/FAILED. */
+      if (r != ARCHIVE_OK && r != ARCHIVE_WARN)
         {
           propagate_libarchive_error (error, a);
           goto out;
         }
+      if (r == ARCHIVE_WARN)
+        g_debug ("libarchive warning: %s", archive_error_string (a));
 
       if (g_cancellable_set_error_if_cancelled (cancellable, error))
         goto out;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude 
config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 
--exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh 
old/libostree-2025.7/src/libostree/ostree-sysroot-deploy.c 
new/libostree-2026.1/src/libostree/ostree-sysroot-deploy.c
--- old/libostree-2025.7/src/libostree/ostree-sysroot-deploy.c  2025-11-12 
00:19:26.000000000 +0100
+++ new/libostree-2026.1/src/libostree/ostree-sysroot-deploy.c  2026-04-07 
02:00:12.000000000 +0200
@@ -3884,6 +3884,33 @@
     g_variant_builder_add (builder, "{sv}", "overlay-initrds",
                            g_variant_new_strv ((const char *const 
*)opts->overlay_initrds, -1));
 
+  /* Serialize any extension BLS keys (e.g. x-options-source-tuned).
+   * These are custom keys set by consumers like bootc and need to survive
+   * the staging roundtrip so they are preserved during finalization at 
shutdown.
+   *
+   * First check the new deployment's bootconfig (in case the caller set keys
+   * on it directly).  If none found, fall back to the merge deployment's
+   * bootconfig, which carries the keys from the currently deployed BLS entry.
+   * This ensures that x-prefixed keys are inherited across staged deployments
+   * even though _ostree_deployment_set_bootconfig_from_kargs() creates a fresh
+   * bootconfig containing only the "options" key.
+   */
+  {
+    GVariant *extra = NULL;
+    OstreeBootconfigParser *bootconfig = ostree_deployment_get_bootconfig 
(deployment);
+    if (bootconfig)
+      extra = _ostree_bootconfig_parser_get_extra_keys_variant (bootconfig);
+    if (!extra && merge_deployment)
+      {
+        OstreeBootconfigParser *merge_bootconfig
+            = ostree_deployment_get_bootconfig (merge_deployment);
+        if (merge_bootconfig)
+          extra = _ostree_bootconfig_parser_get_extra_keys_variant 
(merge_bootconfig);
+      }
+    if (extra)
+      g_variant_builder_add (builder, "{sv}", "bootconfig-extra", extra);
+  }
+
   const char *parent = dirname (strdupa (_OSTREE_SYSROOT_RUNSTATE_STAGED));
   if (!glnx_shutil_mkdir_p_at (AT_FDCWD, parent, 0755, cancellable, error))
     return FALSE;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude 
config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 
--exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh 
old/libostree-2025.7/src/libostree/ostree-sysroot.c 
new/libostree-2026.1/src/libostree/ostree-sysroot.c
--- old/libostree-2025.7/src/libostree/ostree-sysroot.c 2025-11-12 
00:19:26.000000000 +0100
+++ new/libostree-2026.1/src/libostree/ostree-sysroot.c 2026-04-07 
02:00:12.000000000 +0200
@@ -1225,6 +1225,24 @@
 
           _ostree_deployment_set_overlay_initrds (staged, overlay_initrds);
 
+          /* Restore any extension BLS keys (e.g. x-options-source-tuned)
+           * that were serialized during staging. This preserves custom keys
+           * set by consumers like bootc through the staging roundtrip.
+           */
+          {
+            g_autoptr (GVariant) bootconfig_extra = NULL;
+            if (g_variant_dict_lookup (staged_deployment_dict, 
"bootconfig-extra", "@a{ss}",
+                                       &bootconfig_extra))
+              {
+                OstreeBootconfigParser *bootconfig = 
ostree_deployment_get_bootconfig (staged);
+                GVariantIter iter;
+                const char *key, *value;
+                g_variant_iter_init (&iter, bootconfig_extra);
+                while (g_variant_iter_next (&iter, "{&s&s}", &key, &value))
+                  ostree_bootconfig_parser_set (bootconfig, key, value);
+              }
+          }
+
           self->staged_deployment = g_steal_pointer (&staged);
           self->staged_deployment_data = g_steal_pointer 
(&staged_deployment_data);
           /* We set this flag for ostree_deployment_is_staged() because that 
API
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude 
config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 
--exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh 
old/libostree-2025.7/src/libostree/ostree-version.h 
new/libostree-2026.1/src/libostree/ostree-version.h
--- old/libostree-2025.7/src/libostree/ostree-version.h 2025-11-12 
00:20:12.000000000 +0100
+++ new/libostree-2026.1/src/libostree/ostree-version.h 2026-04-10 
21:56:56.000000000 +0200
@@ -32,7 +32,7 @@
  *
  * Since: 2017.4
  */
-#define OSTREE_YEAR_VERSION (2025)
+#define OSTREE_YEAR_VERSION (2026)
 
 /**
  * OSTREE_RELEASE_VERSION:
@@ -41,7 +41,7 @@
  *
  * Since: 2017.4
  */
-#define OSTREE_RELEASE_VERSION (7)
+#define OSTREE_RELEASE_VERSION (1)
 
 /**
  * OSTREE_VERSION
@@ -50,7 +50,7 @@
  *
  * Since: 2017.4
  */
-#define OSTREE_VERSION (2025.7)
+#define OSTREE_VERSION (2026.1)
 
 /**
  * OSTREE_VERSION_S:
@@ -60,7 +60,7 @@
  *
  * Since: 2017.4
  */
-#define OSTREE_VERSION_S "2025.7"
+#define OSTREE_VERSION_S "2026.1"
 
 #define OSTREE_ENCODE_VERSION(year,release) \
         ((year) << 16 | (release))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude 
config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 
--exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh 
old/libostree-2025.7/src/ostree/ot-admin-builtin-state-overlay.c 
new/libostree-2026.1/src/ostree/ot-admin-builtin-state-overlay.c
--- old/libostree-2025.7/src/ostree/ot-admin-builtin-state-overlay.c    
2025-11-12 00:19:27.000000000 +0100
+++ new/libostree-2026.1/src/ostree/ot-admin-builtin-state-overlay.c    
2026-04-07 02:00:06.000000000 +0200
@@ -23,6 +23,7 @@
 #include <sched.h>
 #include <stdlib.h>
 #include <sys/mount.h>
+#include <sys/xattr.h>
 
 #include "glnx-errors.h"
 #include "glnx-fdio.h"
@@ -62,20 +63,68 @@
   return TRUE;
 }
 
-/* XXX: upstream to libglnx */
-static gboolean
-lgetxattrat_allow_noent (int dfd, const char *path, const char *attribute, 
GBytes **out_bytes,
-                         GError **error)
-{
-  g_autoptr (GError) local_error = NULL;
-  *out_bytes = glnx_lgetxattrat (dfd, path, attribute, &local_error);
-  if (!*out_bytes)
+/* Based on glnx_lgetxattrat() from libglnx, modified to treat ENODATA
+ * (xattr not set) as success with *out_bytes = NULL. We check errno
+ * immediately after the lgetxattr syscall, before any GLib calls can
+ * clobber it. This avoids depending on GLib's g_io_error_from_errno()
+ * mapping, which only maps ENODATA to G_IO_ERROR_NOT_FOUND since GLib 2.74.
+ *
+ * This implementation handles the TOCTOU race condition where the xattr size
+ * may change between the size query and the data read by retrying with ERANGE.
+ * It also handles the case where the xattr is deleted between calls (ENODATA
+ * on second call). Zero-length xattrs are handled without allocating a buffer.
+ *
+ * TODO: Upstream to libglnx. */
+static gboolean
+lgetxattrat_allow_nodata (int dfd, const char *path, const char *attribute, 
GBytes **out_bytes,
+                          GError **error)
+{
+  char pathbuf[PATH_MAX];
+  int n = snprintf (pathbuf, sizeof (pathbuf), "/proc/self/fd/%d/%s", dfd, 
path);
+  if (n < 0 || n >= sizeof (pathbuf))
+    return glnx_throw (error, "Path truncated for fd %d, path %s", dfd, path);
+
+  ssize_t bytes_read;
+  ssize_t real_size;
+  g_autofree guint8 *buf = NULL;
+
+again:
+  errno = 0;
+  bytes_read = TEMP_FAILURE_RETRY (lgetxattr (pathbuf, attribute, NULL, 0));
+  if (bytes_read < 0)
+    {
+      if (errno == ENODATA)
+        {
+          *out_bytes = NULL;
+          return TRUE; /* xattr not set; that's fine */
+        }
+      return glnx_throw_errno_prefix (error, "lgetxattr(%s)", attribute);
+    }
+
+  if (bytes_read == 0)
     {
-      if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA))
-        return TRUE;
-      g_propagate_error (error, g_steal_pointer (&local_error));
-      return FALSE;
+      *out_bytes = g_bytes_new_static ("", 0);
+      return TRUE;
     }
+
+  buf = g_malloc (bytes_read);
+  real_size = TEMP_FAILURE_RETRY (lgetxattr (pathbuf, attribute, buf, 
bytes_read));
+  if (real_size < 0)
+    {
+      if (errno == ERANGE)
+        {
+          g_clear_pointer (&buf, g_free);
+          goto again;
+        }
+      if (errno == ENODATA)
+        {
+          *out_bytes = NULL;
+          return TRUE; /* xattr was deleted between calls */
+        }
+      return glnx_throw_errno_prefix (error, "lgetxattr(%s)", attribute);
+    }
+
+  *out_bytes = g_bytes_new_take (g_steal_pointer (&buf), real_size);
   return TRUE;
 }
 
@@ -83,7 +132,7 @@
 is_opaque_dir (int dfd, const char *dname, gboolean *out_is_opaque, GError 
**error)
 {
   g_autoptr (GBytes) data = NULL;
-  if (!lgetxattrat_allow_noent (dfd, dname, OVERLAYFS_DIR_XATTR_OPAQUE, &data, 
error))
+  if (!lgetxattrat_allow_nodata (dfd, dname, OVERLAYFS_DIR_XATTR_OPAQUE, 
&data, error))
     return FALSE;
 
   if (!data)
@@ -203,8 +252,8 @@
                                  GError **error)
 {
   g_autoptr (GBytes) bytes = NULL;
-  if (!lgetxattrat_allow_noent (overlay_dfd, OSTREE_STATEOVERLAY_UPPER_DIR,
-                                OSTREE_STATEOVERLAY_XATTR_DEPLOYMENT_CSUM, 
&bytes, error))
+  if (!lgetxattrat_allow_nodata (overlay_dfd, OSTREE_STATEOVERLAY_UPPER_DIR,
+                                 OSTREE_STATEOVERLAY_XATTR_DEPLOYMENT_CSUM, 
&bytes, error))
     return FALSE;
   if (!bytes)
     return TRUE; /* probably newly created */
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude 
config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 
--exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh 
old/libostree-2025.7/src/ostree/ot-admin-builtin-status.c 
new/libostree-2026.1/src/ostree/ot-admin-builtin-status.c
--- old/libostree-2025.7/src/ostree/ot-admin-builtin-status.c   2025-11-12 
00:19:27.000000000 +0100
+++ new/libostree-2026.1/src/ostree/ot-admin-builtin-status.c   2026-04-07 
02:00:06.000000000 +0200
@@ -197,11 +197,16 @@
                        gboolean is_booted, gboolean is_pending, gboolean 
is_rollback,
                        struct ul_jsonwrt *jo, GCancellable *cancellable, 
GError **error)
 {
+  GKeyFile *origin = ostree_deployment_get_origin (deployment);
+  g_autofree char *origin_refspec
+      = origin ? g_key_file_get_string (origin, "origin", "refspec", NULL) : 
NULL;
+
   ul_jsonwrt_object_open (jo, NULL);
 
   const char *ref = ostree_deployment_get_csum (deployment);
   ul_jsonwrt_value_s (jo, "checksum", ref);
   ul_jsonwrt_value_s (jo, "stateroot", ostree_deployment_get_osname 
(deployment));
+  ul_jsonwrt_value_s (jo, "refspec", origin_refspec);
   ul_jsonwrt_value_u64 (jo, "serial", ostree_deployment_get_deployserial 
(deployment));
   ul_jsonwrt_value_u64 (jo, "index", ostree_deployment_get_index (deployment));
   ul_jsonwrt_value_boolean (jo, "booted", is_booted);
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude 
config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 
--exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh 
old/libostree-2025.7/src/switchroot/ostree-prepare-root-static.c 
new/libostree-2026.1/src/switchroot/ostree-prepare-root-static.c
--- old/libostree-2025.7/src/switchroot/ostree-prepare-root-static.c    
2025-11-12 00:19:27.000000000 +0100
+++ new/libostree-2026.1/src/switchroot/ostree-prepare-root-static.c    
2026-04-10 16:34:18.000000000 +0200
@@ -237,7 +237,12 @@
 
   /* Prepare /boot.
    * If /boot is on the same partition, use a bind mount to make it visible
-   * at /boot inside the deployment. */
+   * at /boot inside the deployment.
+   *
+   * Note: The composefs/systemd path (ostree-prepare-root.c) no longer does 
this -
+   * it's handled by ostree-system-generator's boot.mount unit instead, which
+   * supports soft-reboot. But this static path is used without systemd, so the
+   * generator doesn't run and we must still do it here. */
   if (snprintf (srcpath, sizeof (srcpath), "%s/boot/loader", root_mountpoint) 
< 0)
     err (EXIT_FAILURE, "failed to assemble /boot/loader path");
   if (lstat (srcpath, &stbuf) == 0 && S_ISLNK (stbuf.st_mode))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude 
config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 
--exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh 
old/libostree-2025.7/src/switchroot/ostree-prepare-root.c 
new/libostree-2026.1/src/switchroot/ostree-prepare-root.c
--- old/libostree-2025.7/src/switchroot/ostree-prepare-root.c   2025-11-12 
00:19:27.000000000 +0100
+++ new/libostree-2026.1/src/switchroot/ostree-prepare-root.c   2026-04-10 
16:34:18.000000000 +0200
@@ -280,8 +280,9 @@
   g_variant_builder_add (&metadata_builder, "{sv}", 
OTCORE_RUN_BOOTED_KEY_SYSROOT_RO,
                          g_variant_new_boolean (sysroot_readonly));
 
-  if (!otcore_mount_boot (root_mountpoint, TMP_SYSROOT, &error))
-    errx (EXIT_FAILURE, "%s", error->message);
+  /* /boot is handled by ostree-system-generator which generates a boot.mount
+   * unit when /boot is on the same partition. This works for all boot 
scenarios
+   * including soft-reboot. See ostree-impl-system-generator.c */
 
   /* Prepare /etc.
    * No action required if sysroot is writable. Otherwise, a bind-mount for
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude 
config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 
--exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh 
old/libostree-2025.7/tests/libostreetest.c 
new/libostree-2026.1/tests/libostreetest.c
--- old/libostree-2025.7/tests/libostreetest.c  2025-11-12 00:19:27.000000000 
+0100
+++ new/libostree-2026.1/tests/libostreetest.c  2026-04-07 02:00:12.000000000 
+0200
@@ -24,6 +24,15 @@
 #include "libglnx.h"
 #include "libostreetest.h"
 
+/* Return a newly-allocated mkdtemp/g_mkdtemp template path under TEST_TMPDIR
+ * (falling back to /var/tmp), e.g. "ostree-xattrs-test.XXXXXX". */
+char *
+ot_test_tmpdir_template (const char *basename_template)
+{
+  const char *test_tmpdir = g_getenv ("TEST_TMPDIR") ?: "/var/tmp";
+  return g_build_filename (test_tmpdir, basename_template, NULL);
+}
+
 /* This function hovers in a quantum superposition of horrifying and
  * beautiful.  Future generations may interpret it as modern art.
  */
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude 
config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 
--exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh 
old/libostree-2025.7/tests/libostreetest.h 
new/libostree-2026.1/tests/libostreetest.h
--- old/libostree-2025.7/tests/libostreetest.h  2025-11-12 00:19:27.000000000 
+0100
+++ new/libostree-2026.1/tests/libostreetest.h  2026-04-07 02:00:12.000000000 
+0200
@@ -28,6 +28,8 @@
 
 gboolean ot_test_run_libtest (const char *cmd, GError **error);
 
+char *ot_test_tmpdir_template (const char *basename_template);
+
 OstreeRepo *ot_test_setup_repo (GCancellable *cancellable, GError **error);
 
 gboolean ot_check_relabeling (gboolean *can_relabel, GError **error);
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude 
config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 
--exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh 
old/libostree-2025.7/tests/test-basic-c.c 
new/libostree-2026.1/tests/test-basic-c.c
--- old/libostree-2025.7/tests/test-basic-c.c   2025-11-12 00:19:27.000000000 
+0100
+++ new/libostree-2026.1/tests/test-basic-c.c   2026-04-07 02:00:12.000000000 
+0200
@@ -525,8 +525,9 @@
   g_auto (GLnxTmpDir) tmpd = {
     0,
   };
-  // Use /var/tmp to hope we get xattr support
-  glnx_mkdtempat (AT_FDCWD, "/var/tmp/ostree-xattrs-test.XXXXXX", 0700, &tmpd, 
error);
+  // Use TEST_TMPDIR (or /var/tmp as fallback) to hope we get xattr support
+  g_autofree char *tmpd_tmpl = ot_test_tmpdir_template 
("ostree-xattrs-test.XXXXXX");
+  glnx_mkdtempat (AT_FDCWD, tmpd_tmpl, 0700, &tmpd, error);
   g_assert_no_error (local_error);
 
   const char value[] = "foo";
@@ -539,8 +540,8 @@
 
     if (r != 0)
       {
-        g_autofree gchar *message = g_strdup_printf (
-            "Unable to set extended attributes in /var/tmp: %s", g_strerror 
(errno));
+        g_autofree gchar *message = g_strdup_printf ("Unable to set extended 
attributes in %s: %s",
+                                                     tmpd_tmpl, g_strerror 
(errno));
         g_test_skip (message);
         return;
       }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude 
config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 
--exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh 
old/libostree-2025.7/tests/test-bootconfig-parser-internals.c 
new/libostree-2026.1/tests/test-bootconfig-parser-internals.c
--- old/libostree-2025.7/tests/test-bootconfig-parser-internals.c       
2025-11-12 00:19:27.000000000 +0100
+++ new/libostree-2026.1/tests/test-bootconfig-parser-internals.c       
2026-04-07 02:00:12.000000000 +0200
@@ -49,6 +49,211 @@
   g_assert_cmpuint (done, ==, 0);
 }
 
+static void
+test_extra_keys_variant_empty (void)
+{
+  /* A bootconfig with no x-prefixed keys should return NULL */
+  OstreeBootconfigParser *parser = ostree_bootconfig_parser_new ();
+  ostree_bootconfig_parser_set (parser, "title", "Fedora Linux 43");
+  ostree_bootconfig_parser_set (parser, "version", "1");
+  ostree_bootconfig_parser_set (parser, "options", "root=UUID=abc rw quiet");
+  ostree_bootconfig_parser_set (parser, "linux", "/vmlinuz-6.8.0");
+  ostree_bootconfig_parser_set (parser, "initrd", "/initramfs-6.8.0.img");
+
+  GVariant *extra = _ostree_bootconfig_parser_get_extra_keys_variant (parser);
+  g_assert_null (extra);
+
+  g_object_unref (parser);
+}
+
+static void
+test_extra_keys_variant_with_extension_keys (void)
+{
+  /* Standard keys should be excluded, only extension keys returned */
+  OstreeBootconfigParser *parser = ostree_bootconfig_parser_new ();
+  ostree_bootconfig_parser_set (parser, "title", "Fedora Linux 43");
+  ostree_bootconfig_parser_set (parser, "options", "root=UUID=abc rw");
+  ostree_bootconfig_parser_set (parser, "linux", "/vmlinuz-6.8.0");
+  ostree_bootconfig_parser_set (parser, "x-options-source-tuned", "nohz=full 
isolcpus=1-3");
+  ostree_bootconfig_parser_set (parser, "x-options-source-dracut", 
"rd.driver.pre=vfio-pci");
+
+  GVariant *extra = _ostree_bootconfig_parser_get_extra_keys_variant (parser);
+  g_assert_nonnull (extra);
+
+  GVariant *extra_owned = g_variant_ref_sink (extra);
+
+  /* Should be a{ss} with exactly 2 entries */
+  g_assert_true (g_variant_is_of_type (extra_owned, G_VARIANT_TYPE ("a{ss}")));
+  g_assert_cmpuint (g_variant_n_children (extra_owned), ==, 2);
+
+  /* Verify the contents */
+  GVariantIter iter;
+  const char *key, *val;
+  gboolean found_tuned = FALSE, found_dracut = FALSE;
+  g_variant_iter_init (&iter, extra_owned);
+  while (g_variant_iter_next (&iter, "{&s&s}", &key, &val))
+    {
+      if (g_str_equal (key, "x-options-source-tuned"))
+        {
+          g_assert_cmpstr (val, ==, "nohz=full isolcpus=1-3");
+          found_tuned = TRUE;
+        }
+      else if (g_str_equal (key, "x-options-source-dracut"))
+        {
+          g_assert_cmpstr (val, ==, "rd.driver.pre=vfio-pci");
+          found_dracut = TRUE;
+        }
+      else
+        {
+          g_assert_not_reached ();
+        }
+    }
+  g_assert_true (found_tuned);
+  g_assert_true (found_dracut);
+
+  g_variant_unref (extra_owned);
+  g_object_unref (parser);
+}
+
+static void
+test_extra_keys_variant_standard_excluded (void)
+{
+  /* Standard BLS keys (title, version, options, linux, initrd, devicetree)
+   * should be excluded. All other keys should be preserved.
+   */
+  OstreeBootconfigParser *parser = ostree_bootconfig_parser_new ();
+  ostree_bootconfig_parser_set (parser, "title", "Test");
+  ostree_bootconfig_parser_set (parser, "version", "1.0");
+  ostree_bootconfig_parser_set (parser, "options", "root=UUID=abc");
+  ostree_bootconfig_parser_set (parser, "linux", "/vmlinuz");
+  ostree_bootconfig_parser_set (parser, "initrd", "/initramfs.img");
+  ostree_bootconfig_parser_set (parser, "devicetree", "/dtb");
+
+  /* Only standard keys -- should return NULL */
+  GVariant *extra = _ostree_bootconfig_parser_get_extra_keys_variant (parser);
+  g_assert_null (extra);
+
+  /* Add non-standard keys -- all should be preserved */
+  ostree_bootconfig_parser_set (parser, "my-custom-key", "some-value");
+  ostree_bootconfig_parser_set (parser, "x-options-source-tuned", "nohz=full");
+
+  extra = _ostree_bootconfig_parser_get_extra_keys_variant (parser);
+  g_assert_nonnull (extra);
+  GVariant *extra_owned = g_variant_ref_sink (extra);
+
+  g_assert_cmpuint (g_variant_n_children (extra_owned), ==, 2);
+
+  GVariantIter iter;
+  const char *key, *val;
+  gboolean found_custom = FALSE, found_tuned = FALSE;
+  g_variant_iter_init (&iter, extra_owned);
+  while (g_variant_iter_next (&iter, "{&s&s}", &key, &val))
+    {
+      if (g_str_equal (key, "my-custom-key"))
+        {
+          g_assert_cmpstr (val, ==, "some-value");
+          found_custom = TRUE;
+        }
+      else if (g_str_equal (key, "x-options-source-tuned"))
+        {
+          g_assert_cmpstr (val, ==, "nohz=full");
+          found_tuned = TRUE;
+        }
+      else
+        g_assert_not_reached ();
+    }
+  g_assert_true (found_custom);
+  g_assert_true (found_tuned);
+
+  g_variant_unref (extra_owned);
+  g_object_unref (parser);
+}
+
+static void
+test_extra_keys_roundtrip (void)
+{
+  /* Test that extra keys can be serialized to a variant and restored */
+  OstreeBootconfigParser *original = ostree_bootconfig_parser_new ();
+  ostree_bootconfig_parser_set (original, "options", "root=UUID=abc rw");
+  ostree_bootconfig_parser_set (original, "linux", "/vmlinuz");
+  ostree_bootconfig_parser_set (original, "x-options-source-tuned", "nohz=full 
isolcpus=1-3");
+
+  /* Serialize */
+  GVariant *extra = _ostree_bootconfig_parser_get_extra_keys_variant 
(original);
+  g_assert_nonnull (extra);
+  GVariant *extra_owned = g_variant_ref_sink (extra);
+
+  /* Create a new parser (simulating deserialization) with only standard keys 
*/
+  OstreeBootconfigParser *restored = ostree_bootconfig_parser_new ();
+  ostree_bootconfig_parser_set (restored, "options", "root=UUID=abc rw 
nohz=full isolcpus=1-3");
+
+  /* Restore extra keys from variant */
+  GVariantIter iter;
+  const char *key, *value;
+  g_variant_iter_init (&iter, extra_owned);
+  while (g_variant_iter_next (&iter, "{&s&s}", &key, &value))
+    ostree_bootconfig_parser_set (restored, key, value);
+
+  /* Verify the extension key survived the roundtrip */
+  g_assert_cmpstr (ostree_bootconfig_parser_get (restored, 
"x-options-source-tuned"), ==,
+                   "nohz=full isolcpus=1-3");
+  /* Standard keys should also be present */
+  g_assert_cmpstr (ostree_bootconfig_parser_get (restored, "options"), ==,
+                   "root=UUID=abc rw nohz=full isolcpus=1-3");
+
+  g_variant_unref (extra_owned);
+  g_object_unref (original);
+  g_object_unref (restored);
+}
+
+static void
+test_extra_keys_parse_write_roundtrip (void)
+{
+  /* Test that x-prefixed keys survive a parse -> write -> parse roundtrip
+   * via the BLS file format.
+   */
+  const char *bls_content = "title Fedora Linux 43\n"
+                            "version 6.8.0-300.fc40.x86_64\n"
+                            "linux /vmlinuz-6.8.0\n"
+                            "initrd /initramfs-6.8.0.img\n"
+                            "options root=UUID=abc rw nohz=full\n"
+                            "x-options-source-tuned nohz=full\n";
+
+  /* Write the BLS content to a temp file */
+  g_autofree char *tmpdir = g_dir_make_tmp ("ostree-test-XXXXXX", NULL);
+  g_assert_nonnull (tmpdir);
+  g_autofree char *tmpfile = g_build_filename (tmpdir, "ostree-test.conf", 
NULL);
+  g_assert_true (g_file_set_contents (tmpfile, bls_content, -1, NULL));
+
+  /* Parse */
+  OstreeBootconfigParser *parser = ostree_bootconfig_parser_new ();
+  g_assert_true (ostree_bootconfig_parser_parse_at (parser, AT_FDCWD, tmpfile, 
NULL, NULL));
+
+  /* The x-prefixed key should have been parsed */
+  g_assert_cmpstr (ostree_bootconfig_parser_get (parser, 
"x-options-source-tuned"), ==,
+                   "nohz=full");
+  g_assert_cmpstr (ostree_bootconfig_parser_get (parser, "options"), ==,
+                   "root=UUID=abc rw nohz=full");
+
+  /* Write it back out */
+  g_autofree char *outfile = g_build_filename (tmpdir, "ostree-test-out.conf", 
NULL);
+  g_assert_true (ostree_bootconfig_parser_write_at (parser, AT_FDCWD, outfile, 
NULL, NULL));
+
+  /* Parse the output and verify the key survived */
+  OstreeBootconfigParser *parser2 = ostree_bootconfig_parser_new ();
+  g_assert_true (ostree_bootconfig_parser_parse_at (parser2, AT_FDCWD, 
outfile, NULL, NULL));
+  g_assert_cmpstr (ostree_bootconfig_parser_get (parser2, 
"x-options-source-tuned"), ==,
+                   "nohz=full");
+  g_assert_cmpstr (ostree_bootconfig_parser_get (parser2, "options"), ==,
+                   "root=UUID=abc rw nohz=full");
+
+  g_object_unref (parser);
+  g_object_unref (parser2);
+  (void)unlink (tmpfile);
+  (void)unlink (outfile);
+  (void)rmdir (tmpdir);
+}
+
 int
 main (int argc, char *argv[])
 {
@@ -56,5 +261,13 @@
 
   g_test_add_func ("/bootconfig-parser/tries/valid", test_parse_tries_valid);
   g_test_add_func ("/bootconfig-parser/tries/invalid", 
test_parse_tries_invalid);
+  g_test_add_func ("/bootconfig-parser/extra-keys/empty", 
test_extra_keys_variant_empty);
+  g_test_add_func ("/bootconfig-parser/extra-keys/with-extension-keys",
+                   test_extra_keys_variant_with_extension_keys);
+  g_test_add_func ("/bootconfig-parser/extra-keys/standard-excluded",
+                   test_extra_keys_variant_standard_excluded);
+  g_test_add_func ("/bootconfig-parser/extra-keys/roundtrip", 
test_extra_keys_roundtrip);
+  g_test_add_func ("/bootconfig-parser/extra-keys/parse-write-roundtrip",
+                   test_extra_keys_parse_write_roundtrip);
   return g_test_run ();
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude 
config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 
--exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh 
old/libostree-2025.7/tests/test-libarchive-import.c 
new/libostree-2026.1/tests/test-libarchive-import.c
--- old/libostree-2025.7/tests/test-libarchive-import.c 2025-11-12 
00:19:27.000000000 +0100
+++ new/libostree-2026.1/tests/test-libarchive-import.c 2026-04-07 
02:00:12.000000000 +0200
@@ -24,6 +24,7 @@
 #include <stdlib.h>
 #include <string.h>
 
+#include "libostreetest.h"
 #include "ostree-libarchive-private.h"
 #include <archive.h>
 #include <archive_entry.h>
@@ -47,7 +48,7 @@
   uid_t uid = getuid ();
   gid_t gid = getgid ();
 
-  td->tmpd = g_mkdtemp (g_strdup ("/var/tmp/test-libarchive-import-XXXXXX"));
+  td->tmpd = g_mkdtemp (ot_test_tmpdir_template 
("test-libarchive-import-XXXXXX"));
   g_assert_cmpint (0, ==, chdir (td->tmpd));
 
   td->fd = openat (AT_FDCWD, "foo.tar.gz", O_CREAT | O_EXCL | O_RDWR | 
O_CLOEXEC, 0644);
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude 
config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 
--exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh 
old/libostree-2025.7/tests/test-libarchive.sh 
new/libostree-2026.1/tests/test-libarchive.sh
--- old/libostree-2025.7/tests/test-libarchive.sh       2025-11-12 
00:19:27.000000000 +0100
+++ new/libostree-2026.1/tests/test-libarchive.sh       2026-04-07 
02:00:06.000000000 +0200
@@ -23,7 +23,7 @@
 
 skip_without_ostree_feature libarchive
 
-echo "1..18"
+echo "1..19"
 
 setup_test_repository "bare"
 
@@ -243,3 +243,36 @@
 assert_file_has_content sizes.txt 'Unpacked size (needed/total): 0[ ย 
]bytes/921[ ย ]bytes'
 assert_file_has_content sizes.txt 'Number of objects (needed/total): 0/14'
 echo "ok tar sizes metadata"
+
+# Test UTF-8 filenames work in POSIX/C locale (where libarchive's charset
+# conversion fails). This reproduces the issue from
+# https://github.com/ostreedev/ostree/issues/3431 where Python 3.14's
+# venv creates a symlink named "๐œ‹thon" (U+1D70B, Mathematical Italic Small Pi).
+cd ${test_tmpdir}
+rm -rf utf8-test
+mkdir utf8-test
+cd utf8-test
+mkdir -p usr/bin
+echo "#!/bin/sh" > usr/bin/python3
+chmod +x usr/bin/python3
+# Create symlink with non-ASCII UTF-8 name (๐œ‹ = 4-byte UTF-8: F0 9D 9C 8B)
+# and symlink target with non-ASCII UTF-8
+ln -s python3 'usr/bin/๐œ‹thon'
+ln -s '๐œ‹thon' 'usr/bin/๐œ‹-link'
+tar -c -f ../utf8.tar .
+cd ..
+
+# Import with POSIX locale - this previously failed with:
+# "Pathname can't be converted from UTF-8 to current locale"
+LC_ALL=C $OSTREE commit -s "from tar with utf8" -b test-tar-utf8 \
+  --tar-autocreate-parents \
+  --tree=tar=utf8.tar
+# Verify the files exist with correct names
+$OSTREE ls test-tar-utf8 /usr/bin/๐œ‹thon >/dev/null
+$OSTREE ls test-tar-utf8 /usr/bin/๐œ‹-link >/dev/null
+# Verify symlink targets are correct
+rm -rf utf8-checkout
+$OSTREE checkout test-tar-utf8 utf8-checkout
+test "$(readlink utf8-checkout/usr/bin/๐œ‹thon)" = "python3"
+test "$(readlink utf8-checkout/usr/bin/๐œ‹-link)" = "๐œ‹thon"
+echo "ok tar commit with utf8 filenames in POSIX locale"

Reply via email to