Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package partclone for openSUSE:Factory 
checked in at 2026-09-14 16:22:37
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/partclone (Old)
 and      /work/SRC/openSUSE:Factory/.partclone.new.1265 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "partclone"

Mon Sep 14 16:22:37 2026 rev:31 rq:1377764 version:0.3.50

Changes:
--------
--- /work/SRC/openSUSE:Factory/partclone/partclone.changes      2026-09-07 
16:24:47.715692513 +0200
+++ /work/SRC/openSUSE:Factory/.partclone.new.1265/partclone.changes    
2026-09-14 16:22:39.878279167 +0200
@@ -1,0 +2,10 @@
+Sun Sep 13 16:54:05 UTC 2026 - Martin Pluskal <[email protected]>
+
+- Update to 0.3.50:
+  * btrfs: stop dropping interior nodes of multi-level trees from the
+    bitmap - clones of such filesystems silently lost whole subtrees
+  * btrfs: stop reading past the node buffer for inline/truncated file
+    extents ("bad eb member start")
+  * Run the new upstream btrfs_multilevel regression test in %check
+
+-------------------------------------------------------------------

Old:
----
  partclone-0.3.49.tar.gz

New:
----
  partclone-0.3.50.tar.gz

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

Other differences:
------------------
++++++ partclone.spec ++++++
--- /var/tmp/diff_new_pack.pflNrm/_old  2026-09-14 16:22:40.622310403 +0200
+++ /var/tmp/diff_new_pack.pflNrm/_new  2026-09-14 16:22:40.624310487 +0200
@@ -18,12 +18,14 @@
 
 
 Name:           partclone
-Version:        0.3.49
+Version:        0.3.50
 Release:        0
 Summary:        File System Clone Utilities
 License:        GPL-2.0-or-later
 URL:            https://partclone.org/
 Source:         
https://github.com/Thomas-Tsai/partclone/archive/refs/tags/%{version}.tar.gz#/%{name}-%{version}.tar.gz
+# Keep autoconf/automake as plain names: spec-cleaner --perl would explode
+# them into dozens of perl(...) provider lines.
 BuildRequires:  autoconf
 BuildRequires:  automake
 BuildRequires:  btrfsprogs
@@ -88,14 +90,14 @@
 %find_lang %{name}
 
 %check
-# Regression test for the BLOCK_GROUP_TREE chunk-root fix - operates on plain
-# files, no loop device or root needed.  The rest of the suite is not run: it
-# wants mkfs helpers Factory does not ship (hfsplus, apfs) or sudo/losetup
-# (btrfs_trees).
+# Regression tests for btrfs fixes - operate on plain files, no loop device
+# or root needed.  The rest of the suite is not run: it wants mkfs helpers
+# Factory does not ship (hfsplus, apfs) or sudo/losetup (btrfs_trees).
 # mkfs.btrfs and btrfs live in /usr/sbin, which is not on abuild's PATH.
 export PATH="%{_sbindir}:$PATH"
-# The test uses automake's exit 77 to signal "skip"; honour it outside the 
harness.
+# The tests use automake's exit 77 to signal "skip"; honour it outside the 
harness.
 bash tests/btrfs_bgt.test || test $? -eq 77
+bash tests/btrfs_multilevel.test || test $? -eq 77
 
 %files
 %license COPYING

++++++ partclone-0.3.49.tar.gz -> partclone-0.3.50.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/partclone-0.3.49/CONTRIBUTORS 
new/partclone-0.3.50/CONTRIBUTORS
--- old/partclone-0.3.49/CONTRIBUTORS   2026-09-06 09:39:15.000000000 +0200
+++ new/partclone-0.3.50/CONTRIBUTORS   2026-09-12 08:27:49.000000000 +0200
@@ -1,4 +1,4 @@
-   963 Thomas Tsai
+   967 Thomas Tsai
    257 thomas
     59 Patryck Rouleau
     54 Kenji Okimoto
@@ -31,6 +31,7 @@
      2 Hideki EIRAKU
      2 Jeremy Buisson
      2 Sebastian Roth
+     2 Steven Shiau
      2 YUCHIN TSAI (Thomas)
      2 dann frazier
      2 joergmlpts
@@ -46,7 +47,6 @@
      1 Martin Joerg
      1 Nick Garvey
      1 Rafael Fontenelle
-     1 Steven Shiau
      1 Thomas Kupper
      1 ThomasTsai
      1 Tran Ngoc Quan
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/partclone-0.3.49/ChangeLog 
new/partclone-0.3.50/ChangeLog
--- old/partclone-0.3.49/ChangeLog      2026-09-06 09:39:15.000000000 +0200
+++ new/partclone-0.3.50/ChangeLog      2026-09-12 08:27:49.000000000 +0200
@@ -1,3 +1,59 @@
+2026-09-11  Thomas Tsai  <[email protected]>
+
+       fix(btrfs): skip file extent member reads for inline/truncated items
+       fcbeab2 hoisted the disk_num_bytes read in dump_file_extent_item()
+       above the inline early-return, so the +29 member was read for inline
+       items too. A tiny inline extent at the very end of a packed leaf
+       (common on Fedora installs, e.g. inline symlinks) then read two
+       bytes past the 16K node buffer, tripping the accessor bounds check
+       ("bad eb member start") in the bundled btrfs accessors.
+
+       Move the read back behind the inline early-return and add an item
+       size guard so regular/prealloc member reads never cross the node
+       boundary.
+
+       Verified on Fedora 44 VM: 'bad eb member' warnings 0 (was 3),
+       md5sum 123604/123604 OK, btrfs check clean.
+
+       fix(btrfs): stop dropping interior nodes of multi-level trees from the 
bitmap
+       dump_start_leaf() marked interior (non-leaf) tree nodes with an
+       uninitialized `size`; check_extent_bitmap() rejects non-sectorsize-
+       aligned lengths, so nodes were dropped whenever the stack garbage
+       failed the check, silently omitting whole subtrees from the clone.
+       Found on Fedora 44 (btrfs-progs 6.19+ defaults, BLOCK_GROUP_TREE):
+       ~25% of files returned EIO after device-to-device clone (5 of 45
+       level-1 FS-tree nodes missing). Trees whose root is a leaf never
+       trigger this, which is why small test filesystems never caught it.
+
+       Also harden the same walk:
+       - check IS_ERR_OR_NULL() on read_tree_block(); the btrfs library
+         returns ERR_PTR() on failure and dereferencing it could crash.
+         Count read failures and abort the clone instead of writing an
+         incomplete image.
+       - handle BTRFS_METADATA_ITEM_KEY so metadata extents keep their
+         extent-tree backstop on skinny-metadata filesystems.
+       - map file extents through the chunk tree (check_extent_bitmap)
+         instead of marking the logical address as a physical offset.
+
+       Add tests/btrfs_multilevel.test (forces FS tree level >= 2, restores
+       onto zeros pre-filled target, byte-compares every source tree block,
+       diffs extracted file contents) and tests/btrfs_uninitvalgrind.test
+       (deterministic uninitialized-read detection where valgrind exists).
+
+       Verified on Fedora 44 VM: md5sum 123604/123604 OK (30559 failed
+       before), btrfs check clean.
+
+2026-09-06  Thomas Tsai  <[email protected]>
+
+       Merge pull request #307 from stevenshiau/static
+       static/dynamic linking coexist in same build conf.
+
+       Bump version to 0.3.49
+
+2026-09-06  Steven Shiau  <[email protected]>
+
+       static/dynamic linking coexist in same build conf.
+
 2026-09-03  Thomas Tsai  <[email protected]>
 
        Merge pull request #306 from Thomas-Tsai/fix/hfsplus-alt-vh
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/partclone-0.3.49/README.Packages/debian.sid/control 
new/partclone-0.3.50/README.Packages/debian.sid/control
--- old/partclone-0.3.49/README.Packages/debian.sid/control     2026-09-06 
09:39:15.000000000 +0200
+++ new/partclone-0.3.50/README.Packages/debian.sid/control     2026-09-12 
08:27:49.000000000 +0200
@@ -41,3 +41,16 @@
  .
  check the project website for more details
  http://partclone.org
+
+Package: partclone-static
+Architecture: any
+Depends: ${misc:Depends}
+Description: Utility to clone and restore a partition (statically linked)
+ Partclone is a project like the well-known backup utility 
+ "Partition Image" a.k.a. partimage. 
+ .
+ This package contains statically linked versions of the partclone
+ utilities, including partclone-static.ext4, partclone-static.fat, etc.
+ .
+ check the project website for more details
+ http://partclone.org
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/partclone-0.3.49/README.Packages/debian.sid/rules 
new/partclone-0.3.50/README.Packages/debian.sid/rules
--- old/partclone-0.3.49/README.Packages/debian.sid/rules       2026-09-06 
09:39:15.000000000 +0200
+++ new/partclone-0.3.50/README.Packages/debian.sid/rules       2026-09-12 
08:27:49.000000000 +0200
@@ -9,13 +9,28 @@
 # Uncomment this to turn on verbose mode.
 #export DH_VERBOSE=1
 
+# Check if PARTCLONE_STATIC is set to 1 OR if "static" is in DEB_BUILD_OPTIONS
+ifneq (,$(filter static,$(DEB_BUILD_OPTIONS)))
+    IS_STATIC = 1
+endif
+ifeq ($(PARTCLONE_STATIC),1)
+    IS_STATIC = 1
+endif
+
+ifeq ($(IS_STATIC),1)
+    PKG_NAME = partclone-static
+    STATIC_OPT = --enable-static-linking
+else
+    PKG_NAME = partclone
+    STATIC_OPT =
+endif
 
 %:
-       dh $@ --with autoreconf
+       dh $@ --with autoreconf -p$(PKG_NAME)
 
 
-FAILBOOTDIR = $(CURDIR)/debian/partclone/usr/share/partclone
-MAN8DIR = $(CURDIR)/debian/partclone/usr/share/man/man8
+FAILBOOTDIR = $(CURDIR)/debian/$(PKG_NAME)/usr/share/partclone
+MAN8DIR = $(CURDIR)/debian/$(PKG_NAME)/usr/share/man/man8
 
 # default configuration options
 CONFIG_OPTS = \
@@ -36,13 +51,14 @@
        --enable-btrfs \
        --enable-minix \
        --enable-f2fs \
-       --disable-nilfs2
+       --disable-nilfs2 \
+       $(STATIC_OPT)
 
 # isal support architectures
 ISAL_ARCHES = amd64 arm64 ppc64el riscv64 s390x
 
 override_dh_auto_configure:
-       ARCH=$(shell dpkg-architecture -qDEB_HOST_ARCH); \
+       ARCH=$$(dpkg-architecture -qDEB_HOST_ARCH); \
        if echo "$(ISAL_ARCHES)" | grep -w $$ARCH >/dev/null; then \
                dh_auto_configure -- $(CONFIG_OPTS) --enable-isal; \
        else \
@@ -83,20 +99,93 @@
        echo Skipping tests.
 
 override_dh_auto_install:
-       dh_auto_install
+       dh_auto_install -p$(PKG_NAME) -- DESTDIR=$(CURDIR)/debian/$(PKG_NAME)
+ifeq ($(PKG_NAME),partclone-static)
+       # Rename all regular binary files from partclone.* to 
partclone-static.* using POSIX parameter expansion
+       cd $(CURDIR)/debian/partclone-static/usr/sbin && \
+       for f in partclone.*; do \
+               if [ -f "$$f" ] && [ ! -L "$$f" ]; then \
+                       suffix="$${f#partclone.}"; \
+                       mv "$$f" "partclone-static.$$suffix"; \
+               fi; \
+       done
+       # Remove old symlinks in sbin
+       find $(CURDIR)/debian/partclone-static/usr/sbin -type l -delete
+       # Recreate binary symlinks with the new names
+       cd $(CURDIR)/debian/partclone-static/usr/sbin && \
+       if [ -f partclone-static.extfs ]; then \
+               ln -sf partclone-static.extfs partclone-static.ext2; \
+               ln -sf partclone-static.extfs partclone-static.ext3; \
+               ln -sf partclone-static.extfs partclone-static.ext4; \
+               ln -sf partclone-static.extfs partclone-static.ext4dev; \
+       fi && \
+       if [ -f partclone-static.hfsp ]; then \
+               ln -sf partclone-static.hfsp partclone-static.hfs+; \
+               ln -sf partclone-static.hfsp partclone-static.hfsplus; \
+       fi && \
+       if [ -f partclone-static.ntfsfixboot ]; then \
+               ln -sf partclone-static.ntfsfixboot partclone-static.ntfsreloc; 
\
+       fi && \
+       if [ -f partclone-static.fat ]; then \
+               ln -sf partclone-static.fat partclone-static.fat12; \
+               ln -sf partclone-static.fat partclone-static.fat16; \
+               ln -sf partclone-static.fat partclone-static.fat32; \
+               ln -sf partclone-static.fat partclone-static.vfat; \
+       fi && \
+       if [ -f partclone-static.vmfs ]; then \
+               ln -sf partclone-static.vmfs 
partclone-static.VMFS_volume_member; \
+               ln -sf partclone-static.vmfs partclone-static.vmfs3; \
+       fi
+       # Ensure the directory structures exist
+       install -d $(FAILBOOTDIR)
+       install -d $(MAN8DIR)
+       # Rename man pages from partclone.* to partclone-static.* using POSIX 
parameter expansion
+       cd $(MAN8DIR) && \
+       for f in partclone.*; do \
+               if [ -f "$$f" ] && [ ! -L "$$f" ]; then \
+                       suffix="$${f#partclone.}"; \
+                       mv "$$f" "partclone-static.$$suffix"; \
+               fi; \
+       done
+       # Remove old symlinks for man pages
+       find $(MAN8DIR) -type l -delete
+       # Recreate symlinks for man pages
+       cd $(MAN8DIR) && \
+         ln -s partclone-static.fat.8.gz partclone-static.vfat.8.gz; \
+         ln -s partclone-static.fat.8.gz partclone-static.fat12.8.gz; \
+         ln -s partclone-static.fat.8.gz partclone-static.fat16.8.gz; \
+         ln -s partclone-static.fat.8.gz partclone-static.fat32.8.gz; \
+         ln -s partclone-static.hfsp.8.gz partclone-static.hfs+.8.gz; \
+         ln -s partclone-static.hfsp.8.gz partclone-static.hfsplus.8.gz; \
+         ln -s partclone-static.extfs.8.gz partclone-static.ext2.8.gz; \
+         ln -s partclone-static.extfs.8.gz partclone-static.ext3.8.gz; \
+         ln -s partclone-static.extfs.8.gz partclone-static.ext4.8.gz; \
+         ln -s partclone-static.extfs.8.gz partclone-static.ext4dev.8.gz
+       # docs/partclone.dd.8 is a particular case, see bug #850623
+       rm -rf $(CURDIR)/debian/partclone-static/usr/share/man/dd
+else
+       # Ensure the directory structures exist
        install -d $(FAILBOOTDIR)
        install -d $(MAN8DIR)
-       cd $(MAN8DIR); \
-         ln -s partclone.fat.8.gz partclone.vfat.8.gz; \
-         ln -s partclone.fat.8.gz partclone.fat12.8.gz; \
-         ln -s partclone.fat.8.gz partclone.fat16.8.gz; \
-         ln -s partclone.fat.8.gz partclone.fat32.8.gz; \
-         ln -s partclone.hfsp.8.gz partclone.hfs+.8.gz; \
-         ln -s partclone.hfsp.8.gz partclone.hfsplus.8.gz; \
-         ln -s partclone.extfs.8.gz partclone.ext2.8.gz; \
-         ln -s partclone.extfs.8.gz partclone.ext3.8.gz; \
-         ln -s partclone.extfs.8.gz partclone.ext4.8.gz; \
-         ln -s partclone.extfs.8.gz partclone.ext4dev.8.gz
+       # Recreate symlinks for man pages
+       cd $(MAN8DIR) && \
+         ln -s partclone.fat.8.gz partclone.vfat.8.gz || true; \
+         ln -s partclone.fat.8.gz partclone.fat12.8.gz || true; \
+         ln -s partclone.fat.8.gz partclone.fat16.8.gz || true; \
+         ln -s partclone.fat.8.gz partclone.fat32.8.gz || true; \
+         ln -s partclone.hfsp.8.gz partclone.hfs+.8.gz || true; \
+         ln -s partclone.hfsp.8.gz partclone.hfsplus.8.gz || true; \
+         ln -s partclone.extfs.8.gz partclone.ext2.8.gz || true; \
+         ln -s partclone.extfs.8.gz partclone.ext3.8.gz || true; \
+         ln -s partclone.extfs.8.gz partclone.ext4.8.gz || true; \
+         ln -s partclone.extfs.8.gz partclone.ext4dev.8.gz || true
        # docs/partclone.dd.8 is a particular case, see bug #850623
        rm -rf $(CURDIR)/debian/partclone/usr/share/man/dd
+endif
 
+override_dh_installman:
+ifeq ($(PKG_NAME),partclone-static)
+       # Man pages are already installed and renamed in 
override_dh_auto_install
+else
+       dh_installman -p$(PKG_NAME)
+endif
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/partclone-0.3.49/configure.ac 
new/partclone-0.3.50/configure.ac
--- old/partclone-0.3.49/configure.ac   2026-09-06 09:39:15.000000000 +0200
+++ new/partclone-0.3.50/configure.ac   2026-09-12 08:27:49.000000000 +0200
@@ -1,4 +1,4 @@
-AC_INIT([Partclone],[0.3.49],[[email protected]])
+AC_INIT([Partclone],[0.3.50],[[email protected]])
 
 #condition for CPU target
 AC_CANONICAL_TARGET
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/partclone-0.3.49/po/de.po 
new/partclone-0.3.50/po/de.po
--- old/partclone-0.3.49/po/de.po       2026-09-06 09:39:15.000000000 +0200
+++ new/partclone-0.3.50/po/de.po       2026-09-12 08:27:49.000000000 +0200
@@ -9,7 +9,7 @@
 msgstr ""
 "Project-Id-Version: Partclone 0.3.3\n"
 "Report-Msgid-Bugs-To: [email protected]\n"
-"POT-Creation-Date: 2026-09-06 15:37+0800\n"
+"POT-Creation-Date: 2026-09-12 14:27+0800\n"
 "PO-Revision-Date: 2023-05-15 18:40+0300\n"
 "Last-Translator: Stanislav Bolshakov <[email protected]>\n"
 "Language-Team: Deutsch\n"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/partclone-0.3.49/po/fr_FR.po 
new/partclone-0.3.50/po/fr_FR.po
--- old/partclone-0.3.49/po/fr_FR.po    2026-09-06 09:39:15.000000000 +0200
+++ new/partclone-0.3.50/po/fr_FR.po    2026-09-12 08:27:49.000000000 +0200
@@ -8,7 +8,7 @@
 msgstr ""
 "Project-Id-Version: Partclone 0.2.9\n"
 "Report-Msgid-Bugs-To: [email protected]\n"
-"POT-Creation-Date: 2026-09-06 15:37+0800\n"
+"POT-Creation-Date: 2026-09-12 14:27+0800\n"
 "PO-Revision-Date: 2010-11-01 10:13+0100\n"
 "Last-Translator: \n"
 "Language-Team: French <[email protected]>\n"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/partclone-0.3.49/po/ka.po 
new/partclone-0.3.50/po/ka.po
--- old/partclone-0.3.49/po/ka.po       2026-09-06 09:39:15.000000000 +0200
+++ new/partclone-0.3.50/po/ka.po       2026-09-12 08:27:49.000000000 +0200
@@ -7,7 +7,7 @@
 msgstr ""
 "Project-Id-Version: partclone 0.3.46\n"
 "Report-Msgid-Bugs-To: [email protected]\n"
-"POT-Creation-Date: 2026-09-06 15:37+0800\n"
+"POT-Creation-Date: 2026-09-12 14:27+0800\n"
 "PO-Revision-Date: 2026-03-22 08:16+0100\n"
 "Last-Translator: Ekaterine Papava <[email protected]>\n"
 "Language-Team: Georgian <(nothing)>\n"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/partclone-0.3.49/po/partclone.pot 
new/partclone-0.3.50/po/partclone.pot
--- old/partclone-0.3.49/po/partclone.pot       2026-09-06 09:39:15.000000000 
+0200
+++ new/partclone-0.3.50/po/partclone.pot       2026-09-12 08:27:49.000000000 
+0200
@@ -6,9 +6,9 @@
 #, fuzzy
 msgid ""
 msgstr ""
-"Project-Id-Version: partclone 0.3.48\n"
+"Project-Id-Version: partclone 0.3.49\n"
 "Report-Msgid-Bugs-To: [email protected]\n"
-"POT-Creation-Date: 2026-09-06 15:37+0800\n"
+"POT-Creation-Date: 2026-09-12 14:27+0800\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <[email protected]>\n"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/partclone-0.3.49/po/pt_BR.po 
new/partclone-0.3.50/po/pt_BR.po
--- old/partclone-0.3.49/po/pt_BR.po    2026-09-06 09:39:15.000000000 +0200
+++ new/partclone-0.3.50/po/pt_BR.po    2026-09-12 08:27:49.000000000 +0200
@@ -8,7 +8,7 @@
 msgstr ""
 "Project-Id-Version: Partclone 0.3.44\n"
 "Report-Msgid-Bugs-To: [email protected]\n"
-"POT-Creation-Date: 2026-09-06 15:37+0800\n"
+"POT-Creation-Date: 2026-09-12 14:27+0800\n"
 "PO-Revision-Date: 2026-02-17 23:28-0300\n"
 "Last-Translator: Rafael Fontenelle <[email protected]>\n"
 "Language-Team: Brazilian Portuguese\n"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/partclone-0.3.49/po/ru.po 
new/partclone-0.3.50/po/ru.po
--- old/partclone-0.3.49/po/ru.po       2026-09-06 09:39:15.000000000 +0200
+++ new/partclone-0.3.50/po/ru.po       2026-09-12 08:27:49.000000000 +0200
@@ -9,7 +9,7 @@
 msgstr ""
 "Project-Id-Version: Partclone 0.3.3\n"
 "Report-Msgid-Bugs-To: [email protected]\n"
-"POT-Creation-Date: 2026-09-06 15:37+0800\n"
+"POT-Creation-Date: 2026-09-12 14:27+0800\n"
 "PO-Revision-Date: 2023-05-15 18:47+0300\n"
 "Last-Translator: Stanislav Bolshakov <[email protected]>\n"
 "Language-Team: Russian\n"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/partclone-0.3.49/po/vi.po 
new/partclone-0.3.50/po/vi.po
--- old/partclone-0.3.49/po/vi.po       2026-09-06 09:39:15.000000000 +0200
+++ new/partclone-0.3.50/po/vi.po       2026-09-12 08:27:49.000000000 +0200
@@ -7,7 +7,7 @@
 msgstr ""
 "Project-Id-Version: partclone-0.2.61\n"
 "Report-Msgid-Bugs-To: [email protected]\n"
-"POT-Creation-Date: 2026-09-06 15:37+0800\n"
+"POT-Creation-Date: 2026-09-12 14:27+0800\n"
 "PO-Revision-Date: 2013-06-13 09:03+0700\n"
 "Last-Translator: Trần Ngọc Quân <[email protected]>\n"
 "Language-Team: Vietnamese <[email protected]>\n"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/partclone-0.3.49/po/zh_CN.po 
new/partclone-0.3.50/po/zh_CN.po
--- old/partclone-0.3.49/po/zh_CN.po    2026-09-06 09:39:15.000000000 +0200
+++ new/partclone-0.3.50/po/zh_CN.po    2026-09-12 08:27:49.000000000 +0200
@@ -5,7 +5,7 @@
 msgstr ""
 "Project-Id-Version: Partclone 0.2.51\n"
 "Report-Msgid-Bugs-To: [email protected]\n"
-"POT-Creation-Date: 2026-09-06 15:37+0800\n"
+"POT-Creation-Date: 2026-09-12 14:27+0800\n"
 "PO-Revision-Date: 2007-11-01 11:35+0800\n"
 "Last-Translator: Peter Dave Hello\n"
 "Language-Team: Peter Dave Hello\n"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/partclone-0.3.49/po/zh_TW.po 
new/partclone-0.3.50/po/zh_TW.po
--- old/partclone-0.3.49/po/zh_TW.po    2026-09-06 09:39:15.000000000 +0200
+++ new/partclone-0.3.50/po/zh_TW.po    2026-09-12 08:27:49.000000000 +0200
@@ -7,7 +7,7 @@
 msgstr ""
 "Project-Id-Version: Partclone 0.2.51\n"
 "Report-Msgid-Bugs-To: [email protected]\n"
-"POT-Creation-Date: 2026-09-06 15:37+0800\n"
+"POT-Creation-Date: 2026-09-12 14:27+0800\n"
 "PO-Revision-Date: 2007-11-01 11:35+0800\n"
 "Last-Translator: Yu-Chin Tsai <[email protected]>\n"
 "Language-Team: Chinese (traditional) <[email protected]>\n"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/partclone-0.3.49/src/btrfsclone.c 
new/partclone-0.3.50/src/btrfsclone.c
--- old/partclone-0.3.49/src/btrfsclone.c       2026-09-06 09:39:15.000000000 
+0200
+++ new/partclone-0.3.50/src/btrfsclone.c       2026-09-12 08:27:49.000000000 
+0200
@@ -43,6 +43,11 @@
 uint64_t dev_size = 0;
 unsigned long long total_block = 0;
 
+/// number of tree blocks that could not be read while building the bitmap;
+/// any non-zero value means the resulting image would silently miss data,
+/// so read_bitmap() aborts at the end instead of producing a corrupt image.
+static unsigned long unreadable_tree_blocks = 0;
+
 ///set useb block
 static void set_bitmap(unsigned long* bitmap, uint64_t pos, uint64_t length){
     uint64_t block;
@@ -153,25 +158,40 @@
                                   int slot,
                                   struct btrfs_file_extent_item *fi)
 {
-       int extent_type = btrfs_file_extent_type(eb, fi);
-
+       int extent_type;
+       /* file extent disk_bytenr is a *logical* address; it must be mapped
+        * through the chunk tree to physical device offsets (and all mirrors)
+        * via check_extent_bitmap(). Marking the logical address directly is
+        * only correct when logical == physical, which is not guaranteed. */
+       u64 len;
+
+       /* Read the type first: inline extents have a variable, smaller header
+        * and their data can end exactly at the node boundary, so the fixed
+        * disk_* members must not be touched for them (the accessor bounds
+        * check warns on reads past the end of the node). */
+       extent_type = btrfs_file_extent_type(eb, fi);
        if (extent_type == BTRFS_FILE_EXTENT_INLINE) {
            return;
        }
 
+       /* Regular/preallocated items have a fixed size; guard against
+        * truncated items so member reads never cross the node boundary. */
+       if (btrfs_item_size(eb, slot) < sizeof(struct btrfs_file_extent_item))
+           return;
+
+       len = (u64)btrfs_file_extent_disk_num_bytes(eb, fi);
+
        if (extent_type == BTRFS_FILE_EXTENT_PREALLOC) {
-               log_mesg(3, 0, 0, fs_opt.debug, "%s: DUMP: prealloc data disk 
byte %llu nr %llu\n", __FILE__, 
+               log_mesg(3, 0, 0, fs_opt.debug, "%s: DUMP: prealloc data disk 
byte %llu nr %llu\n", __FILE__,
                  (unsigned long long)btrfs_file_extent_disk_bytenr(eb, fi),
                  (unsigned long long)btrfs_file_extent_disk_num_bytes(eb, fi));
-               set_bitmap(bitmap, (unsigned long 
long)btrfs_file_extent_disk_bytenr(eb, fi), 
-                                  (unsigned long 
long)btrfs_file_extent_disk_num_bytes(eb, fi) );
+               check_extent_bitmap(bitmap, 
(u64)btrfs_file_extent_disk_bytenr(eb, fi), &len, 0);
                return;
        }
        log_mesg(3, 0, 0, fs_opt.debug, "DUMP: extent data disk byte %llu nr 
%llu\n",
                (unsigned long long)btrfs_file_extent_disk_bytenr(eb, fi),
                (unsigned long long)btrfs_file_extent_disk_num_bytes(eb, fi));
-               set_bitmap(bitmap, (unsigned long 
long)btrfs_file_extent_disk_bytenr(eb, fi), 
-                                  (unsigned long 
long)btrfs_file_extent_disk_num_bytes(eb, fi) );
+       check_extent_bitmap(bitmap, (u64)btrfs_file_extent_disk_bytenr(eb, fi), 
&len, 0);
 }
 
 int csum_bitmap(unsigned long* bitmap, struct btrfs_root *root){
@@ -248,17 +268,22 @@
 
 void dump_start_leaf(unsigned long* bitmap, struct btrfs_root *root, struct 
extent_buffer *eb, int follow){
 
-    u64 bytenr;
-    u64 size;
-    u64 objectid;
-    u64 offset;
-    u32 type;
-    int i;
-    struct btrfs_disk_key disk_key;
-    struct btrfs_file_extent_item *fi;
+       u64 bytenr;
+       /* size must always be initialized to nodesize: it is used to mark
+        * this node and its children below. Leaving it uninitialized marks
+        * nodes with an indeterminate length, which is mostly rejected by
+        * check_extent_bitmap(), so interior nodes of multi-level trees are
+        * silently dropped from the bitmap. */
+       u64 size = (u64)root->fs_info->nodesize;
+       u64 objectid;
+       u64 offset;
+       u32 type;
+       int i;
+       struct btrfs_disk_key disk_key;
+       struct btrfs_file_extent_item *fi;
 
 
-    if (!eb)
+       if (!eb)
        return;
     u32 nr = btrfs_header_nritems(eb);
 
@@ -281,10 +306,10 @@
                        struct btrfs_file_extent_item);
                dump_file_extent_item(bitmap, eb, i, fi);
            }
-           if (type == BTRFS_EXTENT_ITEM_KEY){
+           if (type == BTRFS_EXTENT_ITEM_KEY || type == 
BTRFS_METADATA_ITEM_KEY){
                objectid = btrfs_disk_key_objectid(&disk_key);
                offset = btrfs_disk_key_offset(&disk_key);
-               log_mesg(3, 0, 0, fs_opt.debug, "%s: type == 
BTRFS_EXTENT_ITEM_KEY %llu %llu\n", __FILE__, objectid, offset);
+               log_mesg(3, 0, 0, fs_opt.debug, "%s: type == 
EXTENT/METADATA_ITEM_KEY(%u) %llu %llu\n", __FILE__, type, objectid, offset);
                check_extent_bitmap(bitmap, objectid, &offset, 1);
            }
 
@@ -322,12 +347,27 @@
         };
        struct extent_buffer *next = read_tree_block(root->fs_info,
                btrfs_node_blockptr(eb, i),  &check);
+       if (IS_ERR_OR_NULL(next)) {
+           /* read_tree_block() returns ERR_PTR() on failure; dereferencing
+            * it crashes. Mark the child address from the parent pointer so
+            * the block itself is not dropped, and count the loss loudly:
+            * the child's subtree cannot be walked, so the bitmap is
+            * incomplete. */
+           unreadable_tree_blocks++;
+           log_mesg(0, 0, 1, fs_opt.debug, "%s: failed to read tree block %llu 
in tree %llu\n", __FILE__,
+                   (unsigned long long)btrfs_node_blockptr(eb, i),
+                   (unsigned long long)btrfs_header_owner(eb));
+           check_extent_bitmap(bitmap, btrfs_node_blockptr(eb, i), &size, 0);
+           continue;
+       }
        bytenr = (unsigned long long)btrfs_header_bytenr(next);
        check_extent_bitmap(bitmap, bytenr, &size, 0);
        if (!extent_buffer_uptodate(next)) {
+           unreadable_tree_blocks++;
            log_mesg(0, 0, 1, fs_opt.debug, "%s: failed to read %llu in tree 
%llu\n", __FILE__,
                    (unsigned long long)btrfs_node_blockptr(eb, i),
                    (unsigned long long)btrfs_header_owner(eb));
+           free_extent_buffer(next);
            continue;
        }
        if (btrfs_is_leaf(next) && btrfs_header_level(eb) != 1)
@@ -500,8 +540,25 @@
            offset = btrfs_item_ptr_offset(leaf, slot);
            read_extent_buffer(leaf, &ri, offset, sizeof(ri));
            buf = read_tree_block(tree_root_scan->fs_info, 
btrfs_root_bytenr(&ri), &check);
-           if (!extent_buffer_uptodate(buf))
+           if (IS_ERR_OR_NULL(buf)) {
+               /* see the note in dump_start_leaf(): read_tree_block() may
+                * return ERR_PTR(); skipping silently would drop this whole
+                * tree from the bitmap. Its address is still marked below via
+                * the extent tree walk, but its contents are not. */
+               unreadable_tree_blocks++;
+               log_mesg(0, 0, 1, fs_opt.debug, "%s: failed to read root tree 
block %llu (objectid %llu)\n", __FILE__,
+                       (unsigned long long)btrfs_root_bytenr(&ri),
+                       (unsigned long long)found_key.objectid);
                goto next;
+           }
+           if (!extent_buffer_uptodate(buf)) {
+               unreadable_tree_blocks++;
+               log_mesg(0, 0, 1, fs_opt.debug, "%s: root tree block %llu 
(objectid %llu) is not uptodate\n", __FILE__,
+                       (unsigned long long)btrfs_root_bytenr(&ri),
+                       (unsigned long long)found_key.objectid);
+               free_extent_buffer(buf);
+               goto next;
+           }
            dump_start_leaf(bitmap, tree_root_scan, buf, 1);
            free_extent_buffer(buf);
        }
@@ -511,6 +568,8 @@
 no_node:
     //csum_bitmap(bitmap, root);
     btrfs_release_path(&path);
+    if (unreadable_tree_blocks)
+       log_mesg(0, 1, 1, fs_opt.debug, "%s: %lu tree block(s) could not be 
read; the bitmap would be incomplete, aborting instead of creating a corrupt 
image\n", __FILE__, unreadable_tree_blocks);
 }
 
 void read_super_blocks(char* device, file_system_info* fs_info)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/partclone-0.3.49/src/version.h 
new/partclone-0.3.50/src/version.h
--- old/partclone-0.3.49/src/version.h  2026-09-06 09:39:15.000000000 +0200
+++ new/partclone-0.3.50/src/version.h  2026-09-12 08:27:49.000000000 +0200
@@ -3,5 +3,5 @@
  * WHETHER THEY ARE BUILT BY OTHERS OR DURING DEVELOPMENT OR FOR THE
  * OFFICIAL PARTCLONE RELEASES.
  */
-#define git_version  "b01481d8"
+#define git_version  "520acd62"
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/partclone-0.3.49/tests/Makefile.am 
new/partclone-0.3.50/tests/Makefile.am
--- old/partclone-0.3.49/tests/Makefile.am      2026-09-06 09:39:15.000000000 
+0200
+++ new/partclone-0.3.50/tests/Makefile.am      2026-09-12 08:27:49.000000000 
+0200
@@ -12,6 +12,8 @@
 TESTS += btrfs.test
 TESTS += btrfs_bgt.test
 TESTS += btrfs_trees.test
+TESTS += btrfs_multilevel.test
+TESTS += btrfs_uninitvalgrind.test
 endif
 
 if ENABLE_FAT
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/partclone-0.3.49/tests/btrfs_multilevel.test 
new/partclone-0.3.50/tests/btrfs_multilevel.test
--- old/partclone-0.3.49/tests/btrfs_multilevel.test    1970-01-01 
01:00:00.000000000 +0100
+++ new/partclone-0.3.50/tests/btrfs_multilevel.test    2026-09-12 
08:27:49.000000000 +0200
@@ -0,0 +1,266 @@
+#!/bin/bash
+
+# 
==============================================================================
+# Partclone Btrfs Multi-Level Tree Regression Test
+#
+# Regression test for the bitmap gap on multi-level btrfs trees: 
partclone.btrfs
+# marked interior (non-leaf) tree nodes with an *uninitialized* length, so
+# check_extent_bitmap() rejected most of them and whole level-1 subtrees were
+# silently left out of the image. Only multi-level trees are affected (a tree
+# whose root is a leaf is fully covered by the leaf walk), which is why small
+# test filesystems never caught it. Found on a Fedora 44 (btrfs-progs 6.19+
+# defaults, BLOCK_GROUP_TREE) installation: ~25% of files returned EIO after
+# device-to-device clone.
+#
+# This test:
+#   1. Creates a btrfs filesystem with small nodesize (4K) and enough files to
+#      force the FS tree to grow interior nodes (root level >= 2)
+#   2. Clones it with partclone.btrfs and restores to a raw file pre-filled
+#      with zeros (so skipped blocks cannot masquerade as valid data)
+#   3. Verifies `btrfs check --readonly` passes on the restored filesystem
+#   4. Verifies the restored metadata tree shape is identical to the source
+#      (same number of leaf/node blocks)
+#   5. Verifies all file contents via rootless extraction with `btrfs restore`
+#
+# Requirements:
+#   - btrfs-progs (--rootdir, -n, check, restore, inspect-internal)
+#   - Compiled partclone.btrfs and partclone.restore in ../src/
+# 
==============================================================================
+
+set -e
+
+# --- Helper Functions for Colored Output ---
+function print_info() {
+    echo -e "\e[34m[INFO]\e[0m $1"
+}
+
+function print_pass() {
+    echo -e "\e[32m[PASS]\e[0m $1"
+}
+
+function print_fail() {
+    echo -e "\e[31m[FAIL]\e[0m $1"
+    cleanup
+    exit 1
+}
+
+# --- Configuration ---
+SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
+PTLDIR="$SCRIPT_DIR/../src"
+PTLBTRFS="$PTLDIR/partclone.btrfs"
+PTLRESTORE="$PTLDIR/partclone.restore"
+
+IMG_SIZE=$((256 * 1024 * 1024))   # 256 MiB
+NFILES=2500                        # enough inode/dir items to grow level-2 
trees at 4K nodesize
+RAW_SRC="$$_mlt_src.raw"
+CLONE_IMG="$$_mlt_clone.img"
+RAW_RESTORE="$$_mlt_restore.raw"
+LOG_FILE="$$_mlt_test.log"
+STAGING="$$_mlt_staging"
+EXTRACT_SRC="$$_mlt_extract_src"
+EXTRACT_RESTORE="$$_mlt_extract_restore"
+CHUNK_MAP="$$_mlt_chunk_map.txt"
+TREE_BLOCKS="$$_mlt_tree_blocks.txt"
+
+# --- Cleanup ---
+function cleanup() {
+    print_info "Cleaning up generated files..."
+    rm -rf "$RAW_SRC" "$CLONE_IMG" "$RAW_RESTORE" "$LOG_FILE" "$STAGING" 
"$EXTRACT_SRC" "$EXTRACT_RESTORE" "$CHUNK_MAP" "$TREE_BLOCKS"
+}
+
+# Ensure we are in the script's directory
+cd "$SCRIPT_DIR"
+
+# Register a trap to clean up files on script exit
+trap cleanup EXIT
+
+# --- Pre-flight Checks ---
+
+if [ ! -x "$PTLBTRFS" ]; then
+    print_fail "$PTLBTRFS not found. Please compile partclone first (e.g., 
'make' in the project root directory)."
+fi
+
+if [ ! -x "$PTLRESTORE" ]; then
+    print_fail "$PTLRESTORE not found. Please compile partclone first."
+fi
+
+MKFS_BTRFS=$(which mkfs.btrfs 2>/dev/null || true)
+if [ -z "$MKFS_BTRFS" ]; then
+    print_fail "mkfs.btrfs not found. Please install btrfs-progs."
+fi
+
+BTRFS_BIN=$(which btrfs 2>/dev/null || true)
+if [ -z "$BTRFS_BIN" ]; then
+    print_fail "btrfs not found. Please install btrfs-progs."
+fi
+
+print_info "==================================================="
+print_info "  Btrfs Multi-Level Tree Bitmap Regression Test    "
+print_info "==================================================="
+
+# --- Step 1: Create staging directory with many known files ---
+print_info "Step 1: Creating staging directory with $NFILES files..."
+
+mkdir -p "$STAGING"
+for i in $(seq 1 $NFILES); do
+    echo "partclone multilevel regression file $i" > "$STAGING/file_$i"
+done
+
+# --- Step 2: Format raw file as btrfs with 4K nodesize ---
+print_info "Step 2: Creating ${IMG_SIZE}-byte raw file, mkfs -n 4096 with 
--rootdir..."
+
+dd if=/dev/zero of="$RAW_SRC" bs=1M count=$((IMG_SIZE / 1024 / 1024)) 
status=none
+$MKFS_BTRFS -n 4096 -f --rootdir "$(cd "$STAGING" && pwd)" "$RAW_SRC" > 
/dev/null 2>&1
+print_pass "Source filesystem created."
+
+# --- Step 3: Verify the FS tree is multi-level (bug trigger condition) ---
+print_info "Step 3: Verifying FS tree root level >= 2 (bug trigger 
condition)..."
+
+FS_TREE_DUMP=$($BTRFS_BIN inspect-internal dump-tree -t 5 "$RAW_SRC" 2>&1)
+FS_TREE_LEVEL=$(echo "$FS_TREE_DUMP" | grep -E '^(node|leaf) [0-9]+ level' | 
head -1 | sed -n 's/^.* level \([0-9]\+\) .*/\1/p')
+if [ -z "$FS_TREE_LEVEL" ] || [ "$FS_TREE_LEVEL" -lt 2 ]; then
+    print_info "FS tree root level is '${FS_TREE_LEVEL:-unknown}', need >= 2 
to trigger this regression."
+    print_info "Increase NFILES or reduce nodesize. Skipping test."
+    exit 77
+fi
+print_pass "FS tree root level is $FS_TREE_LEVEL (multi-level, bug trigger 
condition met)."
+
+NODESIZE=$($BTRFS_BIN inspect-internal dump-super "$RAW_SRC" 2>&1 | sed -n 
's/^nodesize\t\+\([0-9]\+\)/\1/p')
+if [ -z "$NODESIZE" ]; then
+    print_fail "Could not read nodesize from source superblock."
+fi
+
+# Record the metadata tree shape of the source for later comparison
+SRC_SHAPE=$($BTRFS_BIN inspect-internal dump-tree "$RAW_SRC" 2>/dev/null | 
grep -cE '^(leaf|node) [0-9]+ (items|level)')
+print_info "Source metadata tree blocks (leaf+node lines): $SRC_SHAPE"
+
+# --- Step 4: Clone the source with partclone.btrfs ---
+print_info "Step 4: Cloning source with partclone.btrfs..."
+
+$PTLBTRFS -c -s "$RAW_SRC" -O "$CLONE_IMG" -F -L "$LOG_FILE" > /dev/null 2>&1
+if [ $? -ne 0 ]; then
+    print_fail "partclone.btrfs clone failed. See $LOG_FILE for details."
+fi
+print_pass "Clone completed successfully."
+
+# --- Step 5: Restore to a zeros pre-filled raw file ---
+# The pre-filled zeros guarantee that any block partclone failed to copy stays
+# detectably absent (zero blocks always fail btrfs checksum verification),
+# unlike restoring onto a previously-imaged disk where stale old-generation
+# copies of the same filesystem could masquerade as valid blocks.
+print_info "Step 5: Restoring image to zeros pre-filled raw file..."
+
+dd if=/dev/zero of="$RAW_RESTORE" bs=1M count=$((IMG_SIZE / 1024 / 1024)) 
status=none
+$PTLRESTORE -s "$CLONE_IMG" -O "$RAW_RESTORE" -C -F -L "$LOG_FILE" > /dev/null 
2>&1
+if [ $? -ne 0 ]; then
+    print_fail "partclone.restore failed. See $LOG_FILE for details."
+fi
+print_pass "Restore completed successfully."
+
+# --- Step 6: Verify the restored filesystem passes btrfs check ---
+print_info "Step 6: Running 'btrfs check --readonly' on restored filesystem..."
+
+CHECK_OUTPUT=$($BTRFS_BIN check --readonly "$RAW_RESTORE" 2>&1) || {
+    echo "$CHECK_OUTPUT"
+    print_fail "btrfs check failed on restored filesystem — interior tree 
nodes missing (regression detected)."
+}
+# On success 'btrfs check --readonly' ends with "..., no error found".
+if ! echo "$CHECK_OUTPUT" | grep -q "no error found"; then
+    echo "$CHECK_OUTPUT"
+    print_fail "btrfs check reported errors on restored filesystem."
+fi
+print_pass "btrfs check passed."
+
+# --- Step 7: Verify metadata tree shape is identical ---
+print_info "Step 7: Comparing metadata tree shape (leaf+node block count) 
source vs restored..."
+
+RESTORE_SHAPE=$($BTRFS_BIN inspect-internal dump-tree "$RAW_RESTORE" 
2>/dev/null | grep -cE '^(leaf|node) [0-9]+ (items|level)')
+if [ "$RESTORE_SHAPE" != "$SRC_SHAPE" ]; then
+    print_fail "Tree shape mismatch: source has $SRC_SHAPE blocks, restored 
has $RESTORE_SHAPE blocks — metadata blocks missing (regression detected)."
+fi
+print_pass "Metadata tree shape identical ($SRC_SHAPE blocks)."
+
+# --- Step 7b: Verify every source tree block physically present in restored 
---
+# The tree-shape check above proves the restored trees can be *walked*, but a
+# stably zeroed block in the middle of a tree could still escape it (btrfs
+# stops a walk at the first unreadable child). Compare every metadata block
+# byte-for-byte: enumerate all leaf/node logical addresses from the source
+# dump-tree, translate logical->physical through the chunk table (all mirror
+# stripes), and compare source vs restored content. Any block the bitmap
+# missed is left as zeros and shows up as a mismatch.
+
+print_info "Step 7b: Byte-comparing every source tree block against restored 
image..."
+
+$BTRFS_BIN inspect-internal dump-tree -t chunk "$RAW_SRC" 2>/dev/null | awk '
+/CHUNK_ITEM [0-9]+\)/ {
+    if (logical != "") print logical, len, p0, p1;
+    match($0, /CHUNK_ITEM [0-9]+\)/);
+    s = substr($0, RSTART, RLENGTH); gsub(/[^0-9]/, "", s);
+    logical = s; len = ""; p0 = "-"; p1 = "-"; next
+}
+/^[ \t]*length [0-9]+/ { len = $2; next }
+/stripe 0 devid 1 offset/ { p0 = $NF; next }
+/stripe 1 devid 1 offset/ { p1 = $NF; next }
+END { if (logical != "") print logical, len, p0, p1 }
+' > "$CHUNK_MAP"
+
+$BTRFS_BIN inspect-internal dump-tree "$RAW_SRC" 2>/dev/null \
+    | grep -oE '^(leaf|node) [0-9]+' | awk '{print $2}' | sort -un > 
"$TREE_BLOCKS"
+
+TOTAL_BLOCKS=$(wc -l < "$TREE_BLOCKS")
+LOST=0
+while read -r B; do
+    while read -r LSTART LLEN P0 P1; do
+        if [ "$B" -ge "$LSTART" ] && [ "$B" -lt $((LSTART + LLEN)) ]; then
+            OFF=$((B - LSTART))
+            for P in $P0 $P1; do
+                [ "$P" = "-" ] && continue
+                SKIP=$(( (P + OFF) / NODESIZE ))
+                A=$(dd if="$RAW_SRC" bs=$NODESIZE skip=$SKIP count=1 
2>/dev/null | md5sum | cut -d' ' -f1)
+                R=$(dd if="$RAW_RESTORE" bs=$NODESIZE skip=$SKIP count=1 
2>/dev/null | md5sum | cut -d' ' -f1)
+                if [ "$A" != "$R" ]; then
+                    LOST=$((LOST + 1))
+                    echo "  tree block LOST: logical=$B phys=$((P + OFF))"
+                fi
+            done
+            break
+        fi
+    done < "$CHUNK_MAP"
+done < "$TREE_BLOCKS"
+
+if [ "$LOST" -ne 0 ]; then
+    print_fail "$LOST of $TOTAL_BLOCKS tree blocks missing in restored image 
(regression detected)."
+fi
+print_pass "All $TOTAL_BLOCKS source tree blocks present in restored image."
+
+# --- Step 8: Verify all file contents via rootless extraction ---
+print_info "Step 8: Extracting all files from source and restored with 'btrfs 
restore' and diffing..."
+
+mkdir -p "$EXTRACT_SRC" "$EXTRACT_RESTORE"
+if ! $BTRFS_BIN restore "$RAW_SRC" "$EXTRACT_SRC" > /dev/null 2>&1; then
+    print_fail "btrfs restore failed on SOURCE filesystem (test setup 
problem)."
+fi
+if ! $BTRFS_BIN restore "$RAW_RESTORE" "$EXTRACT_RESTORE" > /dev/null 2>&1; 
then
+    print_fail "btrfs restore failed on restored filesystem — tree walk broken 
(regression detected)."
+fi
+
+SRC_COUNT=$(find "$EXTRACT_SRC" -type f | wc -l)
+RESTORE_COUNT=$(find "$EXTRACT_RESTORE" -type f | wc -l)
+if [ "$SRC_COUNT" -ne "$NFILES" ]; then
+    print_fail "btrfs restore on SOURCE gave $SRC_COUNT files, expected 
$NFILES (test setup problem)."
+fi
+if [ "$RESTORE_COUNT" -ne "$NFILES" ]; then
+    print_fail "Restored filesystem only contains $RESTORE_COUNT of $NFILES 
files — files missing (regression detected)."
+fi
+
+if ! diff -r "$EXTRACT_SRC" "$EXTRACT_RESTORE" > /dev/null 2>&1; then
+    print_fail "File content mismatch between source and restored filesystem 
(regression detected)."
+fi
+print_pass "All $NFILES files extracted intact with identical contents."
+
+# --- Final Success ---
+print_info "==================================================="
+print_pass " All Btrfs multi-level tree regression tests passed! "
+print_info "==================================================="
+
+exit 0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/partclone-0.3.49/tests/btrfs_uninitvalgrind.test 
new/partclone-0.3.50/tests/btrfs_uninitvalgrind.test
--- old/partclone-0.3.49/tests/btrfs_uninitvalgrind.test        1970-01-01 
01:00:00.000000000 +0100
+++ new/partclone-0.3.50/tests/btrfs_uninitvalgrind.test        2026-09-12 
08:27:49.000000000 +0200
@@ -0,0 +1,141 @@
+#!/bin/bash
+
+# 
==============================================================================
+# Partclone Btrfs Uninitialized-Read Detection Test (valgrind)
+#
+# The bitmap code in btrfsclone.c once marked interior tree nodes with an
+# *uninitialized* length (u64 size was only assigned on the leaf path of
+# dump_start_leaf()). Whether a clone then lost whole subtrees depended on
+# leftover stack garbage, so some builds/environments appeared fine while
+# others (e.g. Fedora 44 with multi-level trees) silently lost ~25% of the
+# data. Outcome-based tests can only catch this when the garbage happens to
+# be bad; valgrind catches the uninitialized *use* deterministically on the
+# very first interior tree node walked.
+#
+# This test is skipped (exit 77) when valgrind is unavailable.
+#
+# The test:
+#   1. Builds a btrfs image whose FS tree has interior nodes (root level >= 2)
+#   2. Runs partclone.btrfs under valgrind with error reporting enabled
+#   3. Fails if any "uninitialised value" report implicates the bitmap marking
+#      code (check_extent_bitmap / dump_start_leaf / set_bitmap)
+#
+# Requirements:
+#   - valgrind
+#   - btrfs-progs
+#   - Compiled partclone.btrfs in ../src/ (dynamic build; -g recommended)
+# 
==============================================================================
+
+set -e
+
+function print_info() {
+    echo -e "\e[34m[INFO]\e[0m $1"
+}
+
+function print_pass() {
+    echo -e "\e[32m[PASS]\e[0m $1"
+}
+
+function print_fail() {
+    echo -e "\e[31m[FAIL]\e[0m $1"
+    cleanup
+    exit 1
+}
+
+# --- Configuration ---
+SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
+PTLDIR="$SCRIPT_DIR/../src"
+PTLBTRFS="$PTLDIR/partclone.btrfs"
+
+IMG_SIZE=$((256 * 1024 * 1024))   # 256 MiB
+NFILES=2500                        # enough to force interior nodes at 4K 
nodesize
+RAW_SRC="$$_uvg_src.raw"
+CLONE_IMG="$$_uvg_clone.img"
+LOG_FILE="$$_uvg_test.log"
+VG_LOG="$$_uvg_valgrind.log"
+STAGING="$$_uvg_staging"
+
+function cleanup() {
+    print_info "Cleaning up generated files..."
+    rm -rf "$RAW_SRC" "$CLONE_IMG" "$LOG_FILE" "$VG_LOG" "$STAGING"
+}
+
+cd "$SCRIPT_DIR"
+trap cleanup EXIT
+
+# --- Pre-flight Checks ---
+
+VALGRIND=$(which valgrind 2>/dev/null || true)
+if [ -z "$VALGRIND" ]; then
+    print_info "valgrind not found. Skipping test."
+    exit 77
+fi
+
+if [ ! -x "$PTLBTRFS" ]; then
+    print_fail "$PTLBTRFS not found. Please compile partclone first."
+fi
+
+MKFS_BTRFS=$(which mkfs.btrfs 2>/dev/null || true)
+BTRFS_BIN=$(which btrfs 2>/dev/null || true)
+if [ -z "$MKFS_BTRFS" ] || [ -z "$BTRFS_BIN" ]; then
+    print_fail "btrfs-progs not found."
+fi
+
+print_info "==================================================="
+print_info "  Btrfs Uninitialized-Read Detection (valgrind)    "
+print_info "==================================================="
+
+# --- Step 1: Build a multi-level source filesystem ---
+print_info "Step 1: Creating $NFILES-file btrfs image (4K nodesize, FS tree 
level >= 2)..."
+
+mkdir -p "$STAGING"
+for i in $(seq 1 $NFILES); do
+    echo "valgrind uninit regression file $i" > "$STAGING/file_$i"
+done
+
+dd if=/dev/zero of="$RAW_SRC" bs=1M count=$((IMG_SIZE / 1024 / 1024)) 
status=none
+$MKFS_BTRFS -n 4096 -f --rootdir "$(cd "$STAGING" && pwd)" "$RAW_SRC" > 
/dev/null 2>&1
+
+FS_TREE_LEVEL=$($BTRFS_BIN inspect-internal dump-tree -t 5 "$RAW_SRC" 2>&1 \
+    | grep -E '^(node|leaf) [0-9]+ level' | head -1 | sed -n 's/^.* level 
\([0-9]\+\) .*/\1/p')
+if [ -z "$FS_TREE_LEVEL" ] || [ "$FS_TREE_LEVEL" -lt 2 ]; then
+    print_info "FS tree root level is '${FS_TREE_LEVEL:-unknown}', need >= 2 
for interior-node walk."
+    exit 77
+fi
+print_pass "Source filesystem ready (FS tree root level $FS_TREE_LEVEL)."
+
+# --- Step 2: Run clone under valgrind ---
+print_info "Step 2: Running partclone.btrfs under valgrind..."
+
+$VALGRIND --quiet --track-origins=no --error-exitcode=42 \
+    "$PTLBTRFS" -c -s "$RAW_SRC" -O "$CLONE_IMG" -F -L "$LOG_FILE" \
+    > /dev/null 2> "$VG_LOG" || VG_EXIT=$?
+VG_EXIT=${VG_EXIT:-0}
+
+if [ "$VG_EXIT" -eq 42 ]; then
+    print_info "valgrind reported errors; checking if they implicate the 
bitmap code..."
+fi
+
+# --- Step 3: Inspect valgrind reports ---
+# An uninitialized-read report looks like:
+#   ==XXXX== Conditional jump or move depends on uninitialised value(s)
+#   ==XXXX==    at 0x...: check_extent_bitmap (btrfsclone.c:...)
+# Match reports whose stack trace names the bitmap marking code.
+if grep -q "uninitialised value" "$VG_LOG"; then
+    if grep -B2 -A20 "uninitialised value" "$VG_LOG" \
+        | grep -qE 
"check_extent_bitmap|dump_start_leaf|set_bitmap|dump_file_extent_item"; then
+        grep -B2 -A20 "uninitialised value" "$VG_LOG" | head -30
+        print_fail "Uninitialized value used in bitmap marking code 
(regression detected)."
+    fi
+    print_info "valgrind found uninitialized values, but none in the bitmap 
marking code:"
+    grep -A5 "uninitialised value" "$VG_LOG" | head -12
+fi
+
+print_pass "No uninitialized reads in bitmap marking code."
+
+# --- Final Success ---
+print_info "==================================================="
+print_pass " Btrfs uninitialized-read detection test passed!  "
+print_info "==================================================="
+
+exit 0

Reply via email to