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"
