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
