<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
	<channel>
		<title>Storage on tsvinc</title>
		<link>https://tsvinc.dev/tags/storage/</link>
		<description>Recent content in Storage on tsvinc</description>
		<generator>Hugo</generator>
		<language>en-us</language>
		
		
		
		
			<lastBuildDate>Sun, 25 Oct 2026 00:00:00 +0000</lastBuildDate>
		
			<atom:link href="https://tsvinc.dev/tags/storage/index.xml" rel="self" type="application/rss+xml" />
			<item>
				<title>12.4 GB of duplicates, 3.4 GB on disk</title>
				<link>https://tsvinc.dev/posts/btrfs-duplicates-that-werent/</link>
				<pubDate>Sun, 25 Oct 2026 00:00:00 +0000</pubDate>
				<guid>https://tsvinc.dev/posts/btrfs-duplicates-that-werent/</guid>
				<description>&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-console&#34; data-lang=&#34;console&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gp&#34;&gt;$&lt;/span&gt; compsize -x model.bin model-copy.bin&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;model.bin: SEARCH_V2: Operation not permitted&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That is compsize, the tool that reports what a file actually costs on btrfs, refusing to talk to a normal user. It reads each file&#39;s extent records through a btrfs ioctl, and the kernel lets only &lt;code&gt;CAP_SYS_ADMIN&lt;/code&gt; call it. I did the measuring on the workstation I was clearing out without sudo.&lt;/p&gt;&#xA;&lt;p&gt;What it did have was rootful Docker, through the docker group. So I ran the host&#39;s own compsize through a read-only chroot:&lt;/p&gt;</description>
			</item>
			<item>
				<title>Measure real btrfs disk use without sudo</title>
				<link>https://tsvinc.dev/guides/btrfs-compsize-without-root/</link>
				<pubDate>Sun, 25 Oct 2026 00:00:00 +0000</pubDate>
				<guid>https://tsvinc.dev/guides/btrfs-compsize-without-root/</guid>
				<description>&lt;p&gt;Run the host&#39;s own &lt;code&gt;compsize&lt;/code&gt; as root in a throwaway Docker container to get the real on-disk size of a path on btrfs, with compression and reflinked copies counted correctly, when you are in the &lt;code&gt;docker&lt;/code&gt; group but have no sudo.&lt;/p&gt;&#xA;&#xA;&lt;dl class=&#34;guide-scope&#34;&gt;&lt;div class=&#34;guide-scope-row&#34;&gt;&lt;dt&gt;Tested on&lt;/dt&gt;&lt;dd&gt;Fedora 44 workstation, kernel 7.2.10-200.fc44, btrfs &lt;code&gt;/home&lt;/code&gt; mounted with &lt;code&gt;compress=zstd:1&lt;/code&gt;, compsize 1.5^git20250123.d79eacf-15.fc44, coreutils 9.10, Docker CE 29.9.1 (rootful), &lt;code&gt;alpine&lt;/code&gt; image 3.24.2 (2026-10-25, on test files). Used on real data on the same workstation on 2026-10-23 or 2026-10-24 (tool versions not recorded).&lt;/dd&gt;&lt;/div&gt;&lt;div class=&#34;guide-scope-row is-gap&#34;&gt;&lt;dt&gt;Not verified&lt;/dt&gt;&lt;dd&gt;a Docker daemon with SELinux support enabled (&lt;code&gt;selinux&lt;/code&gt; in &lt;code&gt;docker info&lt;/code&gt; security options); rootless Docker or Podman themselves (only the user-namespace mechanism they rely on, Failure modes row 2); the &lt;code&gt;All empty or still-delalloced files.&lt;/code&gt; message compsize prints for data not yet written out.&lt;/dd&gt;&lt;/div&gt;&lt;div class=&#34;guide-scope-row&#34;&gt;&lt;dt&gt;Needs&lt;/dt&gt;&lt;dd&gt;a btrfs filesystem; &lt;code&gt;compsize&lt;/code&gt; installed on the host as &lt;code&gt;/usr/bin/compsize&lt;/code&gt;; access to a rootful Docker daemon, usually through the &lt;code&gt;docker&lt;/code&gt; group; the &lt;code&gt;alpine&lt;/code&gt; image, or network access to pull it. No sudo.&lt;/dd&gt;&lt;/div&gt;&lt;div class=&#34;guide-scope-row&#34;&gt;&lt;dt&gt;Time&lt;/dt&gt;&lt;dd&gt;5 min.&lt;/dd&gt;&lt;/div&gt;&lt;div class=&#34;guide-scope-row&#34;&gt;&lt;dt&gt;Risk&lt;/dt&gt;&lt;dd&gt;nothing on disk changes (except a root-owned &lt;code&gt;ro-test&lt;/code&gt; file if the read-only check in Before you start fails), but this is not a low-privilege method. The &lt;code&gt;docker&lt;/code&gt; group grants root-level privileges (Docker&#39;s own docs). The container runs as root with CAP_SYS_ADMIN, and &lt;code&gt;-v /:/host:ro&lt;/code&gt; guards against your own mistakes, not against hostile code in the image. Run it only with an image you trust. The image provides only &lt;code&gt;chroot&lt;/code&gt;; the compsize binary and its libraries come from the host. Nothing to undo afterwards.&lt;/dd&gt;&lt;/div&gt;&lt;/dl&gt;&#xA;&lt;h2 id=&#34;before-you-start&#34;&gt;Before you start&lt;a class=&#34;heading-anchor&#34; href=&#34;#before-you-start&#34; aria-label=&#34;Link to section: Before you start&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;&#xA;&lt;li&gt;Optional: a test directory with known content, to check the numbers before you trust them. Every transcript in this manual ran in one scratch directory on the same btrfs &lt;code&gt;/home&lt;/code&gt;; the measurements use these three 64 MiB files:&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-console&#34; data-lang=&#34;console&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gp&#34;&gt;$&lt;/span&gt; head -c 64M /dev/urandom &amp;gt; random.bin&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gp&#34;&gt;$&lt;/span&gt; cp random.bin random-copy.bin&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gp&#34;&gt;$&lt;/span&gt; yes &lt;span class=&#34;s1&#34;&gt;&amp;#39;compsize demo: the quick brown fox jumps over the lazy dog&amp;#39;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; head -c 64M &amp;gt; text.txt&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;&#xA;&lt;li&gt;compsize fails as your user, on any file on the btrfs filesystem (here &lt;code&gt;text.txt&lt;/code&gt; from the test directory). This is the error the manual works around:&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-console&#34; data-lang=&#34;console&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gp&#34;&gt;$&lt;/span&gt; compsize text.txt&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;text.txt: SEARCH_V2: Operation not permitted&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;&#xA;&lt;li&gt;The path is on btrfs, and compression is on:&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-console&#34; data-lang=&#34;console&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gp&#34;&gt;$&lt;/span&gt; findmnt -no FSTYPE,OPTIONS -T .&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;btrfs rw,relatime,seclabel,compress=zstd:1,ssd,discard=async,space_cache=v2,subvolid=257,subvol=/home&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;Expected: &lt;code&gt;btrfs&lt;/code&gt; and a &lt;code&gt;compress=&lt;/code&gt; option. Use &lt;code&gt;-T &amp;lt;path&amp;gt;&lt;/code&gt; for a path that is not the current directory.&lt;/li&gt;&#xA;&lt;li&gt;compsize is installed on the host, at the path the container will call:&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-console&#34; data-lang=&#34;console&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gp&#34;&gt;$&lt;/span&gt; rpm -q compsize&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;compsize-1.5^git20250123.d79eacf-15.fc44.x86_64&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gp&#34;&gt;$&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;command&lt;/span&gt; -v compsize&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;/usr/bin/compsize&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;Without it the &lt;code&gt;chroot&lt;/code&gt; has nothing to run, and installing it needs root.&lt;/li&gt;&#xA;&lt;li&gt;The Docker daemon is rootful:&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-console&#34; data-lang=&#34;console&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gp&#34;&gt;$&lt;/span&gt; docker info --format &lt;span class=&#34;s1&#34;&gt;&amp;#39;{{.SecurityOptions}}&amp;#39;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;[name=seccomp,profile=builtin name=cgroupns]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;Expected: no &lt;code&gt;rootless&lt;/code&gt; entry. A rootless daemon runs containers in a user namespace, where compsize fails the same way as for your user (Failure modes, row 2; rootless Docker itself not tested).&lt;/li&gt;&#xA;&lt;li&gt;The read-only mount reaches the filesystem that holds your path. &lt;code&gt;/home&lt;/code&gt; can be a separate mount under &lt;code&gt;/&lt;/code&gt;; on this box it is (&lt;code&gt;subvol=/home&lt;/code&gt; above). Docker makes the submounts of a read-only bind read-only too only as a best effort, on kernel 5.12 or newer:&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-console&#34; data-lang=&#34;console&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gp&#34;&gt;$&lt;/span&gt; docker run --rm -v /:/host:ro -w &lt;span class=&#34;s2&#34;&gt;&amp;#34;/host&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$PWD&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt; alpine touch ro-test&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;touch: ro-test: Read-only file system&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;Expected: &lt;code&gt;Read-only file system&lt;/code&gt;. If &lt;code&gt;touch&lt;/code&gt; succeeds instead, the guard does not cover your path, and a root-owned &lt;code&gt;ro-test&lt;/code&gt; file is left behind. Remove it through a container, for example &lt;code&gt;docker run --rm -v &amp;quot;$PWD&amp;quot;:/w alpine rm /w/ro-test&lt;/code&gt; &lt;code&gt;[UNVERIFIED: not exercised, the touch failed here]&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;li&gt;The &lt;code&gt;alpine&lt;/code&gt; image is present, or &lt;code&gt;docker run&lt;/code&gt; pulls it on first use:&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-console&#34; data-lang=&#34;console&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gp&#34;&gt;$&lt;/span&gt; docker run --rm alpine cat /etc/alpine-release&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;3.24.2&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;Expected: an Alpine version. The measurement uses only its &lt;code&gt;chroot&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;li&gt;Placeholders: &lt;code&gt;&amp;lt;path&amp;gt;&lt;/code&gt; is the file or directory to measure, as an &lt;strong&gt;absolute&lt;/strong&gt; path (&lt;code&gt;&amp;quot;$PWD/&amp;lt;name&amp;gt;&amp;quot;&lt;/code&gt; or &lt;code&gt;~/…&lt;/code&gt;; a relative path prints &lt;code&gt;No files.&lt;/code&gt;, Failure modes row 3). &lt;code&gt;&amp;lt;name&amp;gt;&lt;/code&gt; is a file or directory in the current directory. &lt;code&gt;&amp;lt;dir&amp;gt;&lt;/code&gt; is your test directory.&lt;/li&gt;&#xA;&lt;li&gt;Backups: none needed. Nothing is written.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;steps&#34;&gt;Steps&lt;a class=&#34;heading-anchor&#34; href=&#34;#steps&#34; aria-label=&#34;Link to section: Steps&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&lt;ol&gt;&#xA;&lt;li&gt;&lt;strong&gt;Flush pending writes.&lt;/strong&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sync&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;Expected: no output. compsize counts only data that already has extents on disk; its man page says recently written files can show as taking no space until they are synced or written out, typically within about 30 seconds.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Run the host&#39;s compsize through the container.&lt;/strong&gt; This is the command as used on real data:&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;docker run --rm --cap-add SYS_ADMIN -v /:/host:ro alpine chroot /host /usr/bin/compsize -x &amp;lt;path&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;On the test directory:&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-console&#34; data-lang=&#34;console&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gp&#34;&gt;$&lt;/span&gt; docker run --rm --cap-add SYS_ADMIN -v /:/host:ro alpine chroot /host /usr/bin/compsize -x &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$PWD&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;Processed 3 files, 513 regular extents (514 refs), 0 inline, 325 fragments.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;Type       Perc     Disk Usage   Uncompressed Referenced  &#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;TOTAL       51%       66M         128M         192M       &#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;none       100%       64M          64M         128M       &#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;zstd         3%      2.0M          64M          64M       &#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;Expected: a &lt;code&gt;Processed N files&lt;/code&gt; line, a &lt;code&gt;TOTAL&lt;/code&gt; row, and one row per compression type. &lt;code&gt;--cap-add SYS_ADMIN&lt;/code&gt; grants the capability that btrfs checks for the search ioctl compsize uses. &lt;code&gt;-v /:/host:ro&lt;/code&gt; shows the host&#39;s root read-only. &lt;code&gt;chroot /host&lt;/code&gt; makes &lt;code&gt;/usr/bin/compsize&lt;/code&gt; the host&#39;s binary. &lt;code&gt;-x&lt;/code&gt; stops at filesystem boundaries, and to compsize a nested btrfs subvolume is one (Failure modes, row 5).&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Read the &lt;code&gt;TOTAL&lt;/code&gt; row against &lt;code&gt;du&lt;/code&gt;.&lt;/strong&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-console&#34; data-lang=&#34;console&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gp&#34;&gt;$&lt;/span&gt; du -h random.bin random-copy.bin text.txt&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;64M&#x9;random.bin&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;64M&#x9;random-copy.bin&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;64M&#x9;text.txt&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gp&#34;&gt;$&lt;/span&gt; du -sh .&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;192M&#x9;.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;Expected: &lt;code&gt;du&lt;/code&gt; matches &lt;strong&gt;Referenced&lt;/strong&gt; (192M), not &lt;strong&gt;Disk Usage&lt;/strong&gt; (66M). From the compsize man page and README: Disk Usage is what the files cost on disk, and every referenced extent is counted once; Uncompressed is the same extents before compression; Referenced is the apparent size of every file, so each reflinked copy counts again. A &lt;code&gt;none&lt;/code&gt; row is data btrfs stored uncompressed.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Price duplicates as a set: all copies, then one.&lt;/strong&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-console&#34; data-lang=&#34;console&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gp&#34;&gt;$&lt;/span&gt; docker run --rm --cap-add SYS_ADMIN -v /:/host:ro alpine chroot /host /usr/bin/compsize -x &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$PWD&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;/random.bin&amp;#34;&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$PWD&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;/random-copy.bin&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;Processed 2 files, 1 regular extents (2 refs), 0 inline, 2 fragments.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;Type       Perc     Disk Usage   Uncompressed Referenced  &#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;TOTAL      100%       64M          64M         128M       &#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;none       100%       64M          64M         128M       &#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gp&#34;&gt;$&lt;/span&gt; docker run --rm --cap-add SYS_ADMIN -v /:/host:ro alpine chroot /host /usr/bin/compsize -x &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$PWD&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;/random.bin&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;Processed 1 file, 1 regular extents (1 refs), 0 inline, 1 fragments.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;Type       Perc     Disk Usage   Uncompressed Referenced  &#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;TOTAL      100%       64M          64M          64M       &#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;none       100%       64M          64M          64M       &#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;Expected: Disk Usage of all copies minus Disk Usage of one copy is what the extra copies really cost. Here it is 64M − 64M = 0, because &lt;code&gt;cp&lt;/code&gt; made a reflink: coreutils 9.0 and later default to &lt;code&gt;--reflink=auto&lt;/code&gt;, and this box has 9.10. A duplicate finder that adds up file sizes would report 64M wasted.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;h2 id=&#34;verify&#34;&gt;Verify&lt;a class=&#34;heading-anchor&#34; href=&#34;#verify&#34; aria-label=&#34;Link to section: Verify&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;&#xA;&lt;li&gt;A file with known content gives the expected cost. 64 MiB of repeated text takes 2.0M on disk:&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-console&#34; data-lang=&#34;console&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gp&#34;&gt;$&lt;/span&gt; docker run --rm --cap-add SYS_ADMIN -v /:/host:ro alpine chroot /host /usr/bin/compsize -x &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$PWD&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;/text.txt&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;Processed 1 file, 512 regular extents (512 refs), 0 inline, 323 fragments.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;Type       Perc     Disk Usage   Uncompressed Referenced  &#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;TOTAL        3%      2.0M          64M          64M       &#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;zstd         3%      2.0M          64M          64M       &#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;64 MiB of random data takes 64M, type &lt;code&gt;none&lt;/code&gt; (step 4, second command).&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;undo--rollback&#34;&gt;Undo / rollback&lt;a class=&#34;heading-anchor&#34; href=&#34;#undo--rollback&#34; aria-label=&#34;Link to section: Undo / rollback&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;&#xA;&lt;li&gt;Nothing on the filesystem changes: compsize only reads, and the host root is mounted read-only (checked in Before you start).&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;--rm&lt;/code&gt; removes the container when it exits. Checked with a named run:&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-console&#34; data-lang=&#34;console&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gp&#34;&gt;$&lt;/span&gt; docker run --rm --name compsize-manual-rmcheck-20261025 --cap-add SYS_ADMIN -v /:/host:ro alpine chroot /host /usr/bin/compsize -x &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$PWD&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;/text.txt&amp;#34;&lt;/span&gt; &amp;gt; /dev/null&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; docker ps -a --filter &lt;span class=&#34;nv&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;compsize-manual-rmcheck-20261025 --format &lt;span class=&#34;s1&#34;&gt;&amp;#39;{{.Names}}&amp;#39;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; wc -l&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;0&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;&#xA;&lt;li&gt;If &lt;code&gt;docker run&lt;/code&gt; pulled &lt;code&gt;alpine&lt;/code&gt; only for this, &lt;code&gt;docker image rm alpine&lt;/code&gt; removes it &lt;code&gt;[UNVERIFIED: not exercised, the image was already present]&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;li&gt;Remove the test directory with &lt;code&gt;rm -r &amp;lt;dir&amp;gt;&lt;/code&gt; if you made one &lt;code&gt;[UNVERIFIED: not exercised, the test files are kept as evidence]&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;failure-modes&#34;&gt;Failure modes&lt;a class=&#34;heading-anchor&#34; href=&#34;#failure-modes&#34; aria-label=&#34;Link to section: Failure modes&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&lt;div class=&#34;table-wrap&#34; aria-label=&#34;Table: Symptom (exact message), Cause, Fix&#34;&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;Symptom (exact message)&lt;/th&gt;&#xA;      &lt;th&gt;Cause&lt;/th&gt;&#xA;      &lt;th&gt;Fix&lt;/th&gt;&#xA;    &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;td&gt;&lt;code&gt;text.txt: SEARCH_V2: Operation not permitted&lt;/code&gt; (compsize run as your user; the file name comes first)&lt;/td&gt;&#xA;      &lt;td&gt;The btrfs &lt;code&gt;TREE_SEARCH_V2&lt;/code&gt; ioctl returns EPERM without CAP_SYS_ADMIN (kernel 7.2.10 &lt;code&gt;fs/btrfs/ioctl.c&lt;/code&gt;).&lt;/td&gt;&#xA;      &lt;td&gt;Step 2.&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;td&gt;The same message from root in a user namespace: &lt;code&gt;unshare -Ur&lt;/code&gt; shows &lt;code&gt;CapEff: 000001ffffffffff&lt;/code&gt; and compsize still fails&lt;/td&gt;&#xA;      &lt;td&gt;The kernel checks the capability in the initial user namespace. Rootless Docker runs the daemon and its containers inside a user namespace (Docker docs); tested here with &lt;code&gt;unshare -Ur&lt;/code&gt; only.&lt;/td&gt;&#xA;      &lt;td&gt;A rootful daemon, or an admin who runs compsize.&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;td&gt;&lt;code&gt;No files.&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;td&gt;A relative &lt;code&gt;&amp;lt;path&amp;gt;&lt;/code&gt;. After &lt;code&gt;chroot /host&lt;/code&gt; the working directory is &lt;code&gt;/&lt;/code&gt; (checked with &lt;code&gt;pwd&lt;/code&gt;), and compsize skips paths that do not exist without an error.&lt;/td&gt;&#xA;      &lt;td&gt;Pass an absolute path: &lt;code&gt;&amp;quot;$PWD/&amp;lt;name&amp;gt;&amp;quot;&lt;/code&gt; or &lt;code&gt;~/…&lt;/code&gt;. A relative path given together with absolute ones is skipped the same way, so the total is partial with no error.&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;td&gt;&lt;code&gt;du: cannot read directory &#39;./locked&#39;: Permission denied&lt;/code&gt; and a total that is too small&lt;/td&gt;&#xA;      &lt;td&gt;&lt;code&gt;du&lt;/code&gt; runs as your user and skips directories it cannot read, such as a container&#39;s database directory with mode 700.&lt;/td&gt;&#xA;      &lt;td&gt;Use the container total: root in the container reads them. On the test directory, a 16 MiB file in a &lt;code&gt;chmod 000&lt;/code&gt; directory was missing from &lt;code&gt;du -sh .&lt;/code&gt; (192M) and counted by compsize (Disk Usage 66M → 82M).&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;td&gt;No message, but files in a nested subvolume are missing: &lt;code&gt;Processed 4 files&lt;/code&gt; instead of 5&lt;/td&gt;&#xA;      &lt;td&gt;&lt;code&gt;-x&lt;/code&gt; skips anything whose device number differs from its parent&#39;s, and every btrfs subvolume has its own.&lt;/td&gt;&#xA;      &lt;td&gt;Drop &lt;code&gt;-x&lt;/code&gt; when the path holds subvolumes you want counted. On the test directory a 16 MiB file in a subvolume made 82M (with &lt;code&gt;-x&lt;/code&gt;) into 98M (without). Without &lt;code&gt;-x&lt;/code&gt; compsize also enters other mounts below the path, and per its source it stops at a non-btrfs one with &lt;code&gt;Not btrfs (or SEARCH_V2 unsupported).&lt;/code&gt; (not exercised).&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;/div&gt;&lt;h2 id=&#34;field-notes&#34;&gt;Field notes&lt;a class=&#34;heading-anchor&#34; href=&#34;#field-notes&#34; aria-label=&#34;Link to section: Field notes&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;2026-10-25, workstation: Fedora 44, kernel 7.2.10-200.fc44, btrfs compress=zstd:1 (&lt;code&gt;/home&lt;/code&gt;); compsize 1.5^git20250123.d79eacf-15.fc44; Docker CE 29.9.1 (rootful), alpine 3.24.2; coreutils 9.10, overlayfs storage, SELinux enforcing:&lt;/strong&gt; Every block in this manual was run on test files in a scratch directory on the same &lt;code&gt;/home&lt;/code&gt;, not on real data. As the normal user, compsize failed with the file name in front of the error: &lt;code&gt;text.txt: SEARCH_V2: Operation not permitted&lt;/code&gt;. The container run worked first time. &lt;code&gt;du&lt;/code&gt; said 192M for the directory, compsize said 66M on disk. The 64 MiB &lt;code&gt;cp&lt;/code&gt; copy cost nothing (one extent, two references). A compsize run started within a second of writing 64 MiB of text was expected to show the unflushed-data message, but all 512 extents were already counted, so the &lt;code&gt;sync&lt;/code&gt; in step 1 is a precaution from the man page, not an observed fix. A &lt;code&gt;touch&lt;/code&gt; through the read-only mount failed, so the read-only flag reached the &lt;code&gt;/home&lt;/code&gt; submount on this kernel. Root in a user namespace (&lt;code&gt;unshare -Ur&lt;/code&gt;, all capabilities) still got EPERM. A relative path printed &lt;code&gt;No files.&lt;/code&gt; A directory with mode 000 hid 16 MiB from &lt;code&gt;du&lt;/code&gt; but not from the container, and &lt;code&gt;-x&lt;/code&gt; silently left out a nested test subvolume. The daemon has no SELinux support enabled, so this run says nothing about one that has.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;2026-10-23 or 2026-10-24 (the day was not recorded), same workstation and &lt;code&gt;/home&lt;/code&gt; filesystem, tool versions not recorded:&lt;/strong&gt; On the real disk, compsize ran without sudo, through the rootful Docker daemon (&lt;code&gt;docker&lt;/code&gt; group), and each destructive step of the housekeeping waited for my approval. &lt;code&gt;du&lt;/code&gt; reported uncompressed sizes. compsize run as my normal user failed on SEARCH_V2 with &lt;code&gt;Operation not permitted&lt;/code&gt;, and the container command in step 2 worked. Result: 144 GB of data in 94 GB of disk. 78 GB of it was stored uncompressed: zips, ROMs, maps and model weights, data that btrfs&#39;s compression heuristic skips. A Postgres data directory owned by a container&#39;s uid (mode 700) made &lt;code&gt;du&lt;/code&gt; print &lt;code&gt;Permission denied&lt;/code&gt; and undercount by 15.8 GB. A duplicate finder listed 268 sets of identical files of at least 5 MB, 12.4 GB of apparent waste. compsize per set (all copies, then one) put the real figure at 3.4 GB, because &lt;code&gt;cp&lt;/code&gt; had already made reflinks: a 2.9 GB speech model and its copy took 2.8 GB together. Recompressing with &lt;code&gt;btrfs filesystem defragment -czstd -L 9&lt;/code&gt; worked without root but gained about 5%, so it was dropped. A live SQLite database had the &lt;code&gt;m&lt;/code&gt; (no-compress) attribute on purpose.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;notes&#34;&gt;Notes&lt;a class=&#34;heading-anchor&#34; href=&#34;#notes&#34; aria-label=&#34;Link to section: Notes&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;&#xA;&lt;li&gt;Without Docker: &lt;code&gt;btrfs filesystem du -s &amp;lt;path&amp;gt;&lt;/code&gt; runs as a normal user and separates &lt;code&gt;Exclusive&lt;/code&gt; from &lt;code&gt;Set shared&lt;/code&gt;, so it does see reflinked copies. It reports uncompressed sizes, though: on the test directory it gave &lt;code&gt;text.txt&lt;/code&gt; 64.00MiB, against compsize&#39;s 2.0M.&lt;/li&gt;&#xA;&lt;li&gt;Why &lt;code&gt;chroot&lt;/code&gt; into the host instead of a compsize image: no package install inside a root container, and the binary is the host&#39;s own. Docker keeps the &lt;code&gt;SYS_CHROOT&lt;/code&gt; capability by default.&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;~/…&lt;/code&gt; paths work for &lt;code&gt;&amp;lt;path&amp;gt;&lt;/code&gt; because the host shell expands them before &lt;code&gt;docker&lt;/code&gt; runs (checked); relative paths do not (Failure modes, row 3).&lt;/li&gt;&#xA;&lt;li&gt;Higher zstd level: about 5% on the original box, which was put down to btrfs compressing each 128 KiB chunk on its own (the chunk size is in the btrfs docs). &lt;code&gt;btrfs-filesystem(8)&lt;/code&gt; warns that defragmenting breaks up reflinks on kernels 3.14-rc2 and later, so do not defragment files whose copies share extents.&lt;/li&gt;&#xA;&lt;li&gt;The story behind the numbers in the field notes, and why &lt;code&gt;du&lt;/code&gt; misled: &lt;a href=&#34;https://tsvinc.dev/posts/btrfs-duplicates-that-werent/&#34;&gt;the btrfs duplicates that weren&#39;t&lt;/a&gt;.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;sources&#34;&gt;Sources&lt;a class=&#34;heading-anchor&#34; href=&#34;#sources&#34; aria-label=&#34;Link to section: Sources&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/kilobyte/compsize/blob/master/compsize.c&#34;&gt;compsize source, &lt;code&gt;compsize.c&lt;/code&gt;&lt;/a&gt;: the &lt;code&gt;%s: SEARCH_V2: %m&lt;/code&gt; error, &lt;code&gt;-x&lt;/code&gt; as a device-number comparison, missing paths skipped silently, &lt;code&gt;No files.&lt;/code&gt;. Fedora&#39;s package is built from commit &lt;code&gt;d79eacf&lt;/code&gt;, byte-identical to master when fetched.&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://raw.githubusercontent.com/kilobyte/compsize/master/compsize.8&#34;&gt;compsize man page, &lt;code&gt;compsize.8&lt;/code&gt;&lt;/a&gt;: meaning of Disk Usage, Uncompressed and Referenced; &lt;code&gt;-x&lt;/code&gt;; the caveat about unsynced files; &amp;quot;The ioctls used by this program require root.&amp;quot;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://raw.githubusercontent.com/kilobyte/compsize/master/README.md&#34;&gt;compsize README&lt;/a&gt;: &amp;quot;every referenced extent is counted whole, exactly once&amp;quot;; the ioctl requires root.&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://kernel.googlesource.com/pub/scm/linux/kernel/git/stable/linux/+/refs/tags/v7.2.10/fs/btrfs/ioctl.c&#34;&gt;Linux 7.2.10 &lt;code&gt;fs/btrfs/ioctl.c&lt;/code&gt;&lt;/a&gt;: &lt;code&gt;btrfs_ioctl_tree_search_v2()&lt;/code&gt; returns &lt;code&gt;-EPERM&lt;/code&gt; unless &lt;code&gt;capable(CAP_SYS_ADMIN)&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://raw.githubusercontent.com/coreutils/coreutils/master/NEWS&#34;&gt;GNU coreutils NEWS&lt;/a&gt;, release 9.0: &amp;quot;cp now uses --reflink=auto mode by default.&amp;quot;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://docs.docker.com/engine/install/linux-postinstall/&#34;&gt;Docker: Linux post-installation steps&lt;/a&gt;: &amp;quot;The docker group grants root-level privileges to the user.&amp;quot;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://docs.docker.com/engine/security/&#34;&gt;Docker: security&lt;/a&gt;: the daemon needs root unless in rootless mode; only trusted users should control it.&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://docs.docker.com/engine/storage/bind-mounts/&#34;&gt;Docker: bind mounts&lt;/a&gt;: read-only binds make submounts read-only as a best effort, on kernel 5.12 or newer.&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://docs.docker.com/engine/containers/run/&#34;&gt;Docker: running containers&lt;/a&gt;: default capabilities (&lt;code&gt;DAC_OVERRIDE&lt;/code&gt; and &lt;code&gt;SYS_CHROOT&lt;/code&gt; kept, &lt;code&gt;SYS_ADMIN&lt;/code&gt; only through &lt;code&gt;--cap-add&lt;/code&gt;).&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://docs.docker.com/engine/security/rootless/&#34;&gt;Docker: rootless mode&lt;/a&gt;: a rootless daemon lists &lt;code&gt;rootless&lt;/code&gt; under Security Options in &lt;code&gt;docker info&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://btrfs.readthedocs.io/en/latest/Compression.html&#34;&gt;btrfs documentation: compression&lt;/a&gt;: data is split into 128 KiB chunks before compression; if the first portion compressed does not shrink, compression is disabled for the whole file.&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;btrfs-filesystem(8)&lt;/code&gt; from btrfs-progs 7.1 (&lt;code&gt;man btrfs-filesystem&lt;/code&gt;): the defragment warning about breaking up reflinks.&lt;/li&gt;&#xA;&lt;/ul&gt;</description>
			</item>
			<item>
				<title>Prune a stale synced copy with an rsync checksum dry run</title>
				<link>https://tsvinc.dev/guides/rsync-checksum-compare-synced-copy/</link>
				<pubDate>Sun, 25 Oct 2026 00:00:00 +0000</pubDate>
				<guid>https://tsvinc.dev/guides/rsync-checksum-compare-synced-copy/</guid>
				<description>&lt;p&gt;Compare an old one-way copy of another machine&#39;s tree with that machine by checksum, without moving any file contents, delete locally only the top-level entries whose files all exist on the source with the same checksum (git&#39;s &lt;code&gt;.git/index&lt;/code&gt; and &lt;code&gt;FETCH_HEAD&lt;/code&gt; aside), and revoke the temporary SSH key.&lt;/p&gt;&#xA;&#xA;&lt;dl class=&#34;guide-scope&#34;&gt;&lt;div class=&#34;guide-scope-row&#34;&gt;&lt;dt&gt;Tested on&lt;/dt&gt;&lt;dd&gt;Fedora 44 (kernel 7.2.10), rsync 3.5.1 protocol 33, GNU findutils 4.10.0, GNU Awk 5.3.2, against a loopback remote shell on synthetic trees (2026-10-25). Original run: a Fedora workstation against an older laptop over SSH (2026-10-23 or 2026-10-24, the day was not recorded); the rsync versions and the laptop&#39;s OS were not recorded.&lt;/dd&gt;&lt;/div&gt;&lt;div class=&#34;guide-scope-row is-gap&#34;&gt;&lt;dt&gt;Not verified&lt;/dt&gt;&lt;dd&gt;steps 1, 10 and 11 against a real SSH server, and step 2 over ssh (it ran through the loopback shell). No second machine was reachable for this manual; the key-removal command ran only against a fake &lt;code&gt;authorized_keys&lt;/code&gt; through the loopback shell, and step 11&#39;s identity check only as &lt;code&gt;ssh -G&lt;/code&gt;.&lt;/dd&gt;&lt;/div&gt;&lt;div class=&#34;guide-scope-row&#34;&gt;&lt;dt&gt;Needs&lt;/dt&gt;&lt;dd&gt;bash, rsync on both machines, an SSH login on the source, and the source owner&#39;s permission to add a temporary key and remove it again. GNU find and xargs, awk and coreutils on this machine.&lt;/dd&gt;&lt;/div&gt;&lt;div class=&#34;guide-scope-row&#34;&gt;&lt;dt&gt;Time&lt;/dt&gt;&lt;dd&gt;about 30 min of hands-on steps (an estimate; the original run was not timed), plus one checksum pass over both trees.&lt;/dd&gt;&lt;/div&gt;&lt;div class=&#34;guide-scope-row&#34;&gt;&lt;dt&gt;Risk&lt;/dt&gt;&lt;dd&gt;step 8 deletes local entries for good. Every file in them matched the source by checksum at the step 7 re-check, except &lt;code&gt;.git/index&lt;/code&gt; and &lt;code&gt;.git/FETCH_HEAD&lt;/code&gt; in &lt;code&gt;git-noise&lt;/code&gt; entries: Undo brings back the source&#39;s versions of those two, so the copy&#39;s own index, and anything staged in it, cannot be restored. Undo needs access to the source again. On the source, step 1 adds one key and step 10 removes it; the dry runs write nothing there, but they make it read every file that has the same size as its local counterpart.&lt;/dd&gt;&lt;/div&gt;&lt;/dl&gt;&#xA;&lt;h2 id=&#34;before-you-start&#34;&gt;Before you start&lt;a class=&#34;heading-anchor&#34; href=&#34;#before-you-start&#34; aria-label=&#34;Link to section: Before you start&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;&#xA;&lt;li&gt;Placeholders used below:&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;&amp;lt;ABSOLUTE_PATH_OF_COPY&amp;gt;&lt;/code&gt;: root of the stale copy on this machine.&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;&amp;lt;USER&amp;gt;@&amp;lt;HOST&amp;gt;&lt;/code&gt;: the SSH login on the source machine.&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;&amp;lt;PATH_ON_SOURCE&amp;gt;&lt;/code&gt;: the tree the copy was made from, relative to that login&#39;s home or absolute.&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;&amp;lt;KEY&amp;gt;&lt;/code&gt;: file name for the temporary key, for example &lt;code&gt;tmp-compare&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;&amp;lt;ENTRY&amp;gt;&lt;/code&gt;: one top-level entry name (Undo only).&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;&amp;lt;ABSOLUTE_PATH_OF_SHIM&amp;gt;&lt;/code&gt;: where you saved the test shim (Notes only).&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;Set them once, in the bash shell you use for every step (the commands rely on bash splitting an unquoted &lt;code&gt;$RSYNC_RSH&lt;/code&gt; into words):&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;LOCAL&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&amp;lt;ABSOLUTE_PATH_OF_COPY&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;SRC&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&amp;lt;USER&amp;gt;@&amp;lt;HOST&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;REMOTE&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&amp;lt;PATH_ON_SOURCE&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;&#xA;&lt;li&gt;Work from a directory outside &lt;code&gt;$LOCAL&lt;/code&gt;, for example &lt;code&gt;mkdir -p ~/copy-compare &amp;amp;&amp;amp; cd ~/copy-compare&lt;/code&gt;. The steps write &lt;code&gt;dry.txt&lt;/code&gt;, &lt;code&gt;verdicts.tsv&lt;/code&gt; and similar files to the current directory; inside the copy they would turn up in the comparison.&lt;/li&gt;&#xA;&lt;li&gt;rsync, GNU find, awk, and a UTF-8 character set (in the C locale rsync escapes non-ASCII names; see Failure modes):&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-console&#34; data-lang=&#34;console&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gp&#34;&gt;$&lt;/span&gt; rsync --version &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; head -1&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; find --version &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; head -1&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; awk --version &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; head -1&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; locale charmap&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;rsync  version 3.5.1-g315819c5  protocol version 33&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;find (GNU findutils) 4.10.0&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;GNU Awk 5.3.2, API 4.0, PMA Avon 8-g1, (GNU MPFR 4.2.2, GNU MP 6.3.0)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;UTF-8&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;Expected: the last line is &lt;code&gt;UTF-8&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;li&gt;Nothing under &lt;code&gt;$LOCAL&lt;/code&gt; is unreadable to you:&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-console&#34; data-lang=&#34;console&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gp&#34;&gt;$&lt;/span&gt; find &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$LOCAL&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\(&lt;/span&gt; ! -type l ! -readable -o -type d ! -executable &lt;span class=&#34;se&#34;&gt;\)&lt;/span&gt; -prune -printf &lt;span class=&#34;s1&#34;&gt;&amp;#39;%U %m %P\n&amp;#39;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;Expected: no output. Each line it prints is an owner uid, a mode and a path that rsync cannot read or enter either. On a test tree with a mode-000 file and a mode-444 directory:&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-console&#34; data-lang=&#34;console&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gp&#34;&gt;$&lt;/span&gt; find &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$LOCAL&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\(&lt;/span&gt; ! -type l ! -readable -o -type d ! -executable &lt;span class=&#34;se&#34;&gt;\)&lt;/span&gt; -prune -printf &lt;span class=&#34;s1&#34;&gt;&amp;#39;%U %m %P\n&amp;#39;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;1000 0 both-locked/secret.bin&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;1000 444 noexec-dir&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;Both kinds can make an entry look identical. rsync prints no itemize line for the contents of a directory it cannot open, which shows up in step 3 as an error. A file it cannot read gets no error at all: when the source&#39;s copy of it is unreadable too and has the same size, rsync prints nothing for it (Failure modes, rows 2 and 3). Make the listed paths readable, or move them out of &lt;code&gt;$LOCAL&lt;/code&gt; and decide on them separately, before step 3. &lt;code&gt;! -readable&lt;/code&gt; alone misses the mode-444 directory, hence the &lt;code&gt;-executable&lt;/code&gt; test. &lt;code&gt;! -type l&lt;/code&gt; leaves symlinks out: rsync compares a symlink&#39;s target as text, so one that dangles on this machine is fine, and without that test the check listed a dangling symlink in the same test tree. The original copy held an unreadable directory, a Postgres data directory written by a container: owned by the container&#39;s uid, mode 700, and unreadable to &lt;code&gt;du&lt;/code&gt; as well.&lt;/li&gt;&#xA;&lt;li&gt;Backups: none. Nothing is deleted before step 8, and step 8 deletes only entries that step 7 has just re-checked against the source.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;steps&#34;&gt;Steps&lt;a class=&#34;heading-anchor&#34; href=&#34;#steps&#34; aria-label=&#34;Link to section: Steps&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&lt;ol&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;Create a temporary key and install it on the source.&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;ssh-keygen -t ed25519 -N &lt;span class=&#34;s1&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt; -C tmp-compare -f ~/.ssh/&amp;lt;KEY&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;ssh-copy-id -i ~/.ssh/&amp;lt;KEY&amp;gt;.pub &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$SRC&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;export&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;RSYNC_RSH&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;ssh -i &lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$HOME&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;/.ssh/&amp;lt;KEY&amp;gt; -o IdentitiesOnly=yes&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;ssh-copy-id&lt;/code&gt; appends the public key to &lt;code&gt;~/.ssh/authorized_keys&lt;/code&gt; on the source, creating the file if needed; it logs in with the account&#39;s password (or a key that already works) to do so. &lt;code&gt;RSYNC_RSH&lt;/code&gt; is the remote shell rsync uses when no &lt;code&gt;-e&lt;/code&gt; is given, so every rsync command below goes through this ssh command. rsync splits the value into separate arguments: a logging shim set as &lt;code&gt;RSYNC_RSH&lt;/code&gt; received &lt;code&gt;-i&lt;/code&gt;, the key path, &lt;code&gt;-o&lt;/code&gt; and &lt;code&gt;IdentitiesOnly=yes&lt;/code&gt; as four arguments, followed by the host and &lt;code&gt;rsync&lt;/code&gt;. &lt;code&gt;IdentitiesOnly=yes&lt;/code&gt; keeps keys held by ssh-agent out of the login; &lt;code&gt;IdentityFile&lt;/code&gt; lines from &lt;code&gt;~/.ssh/config&lt;/code&gt; still apply, which is why step 11 tests the key with &lt;code&gt;-F /dev/null&lt;/code&gt;.&#xA;Expected: &lt;code&gt;ssh-copy-id&lt;/code&gt; reports one key added. [UNVERIFIED: steps 1 and 2 not run against an SSH server for this manual. The original run used a temporary key; its setup commands were not recorded.]&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;Check the path and rsync on the source without listing anything.&lt;/strong&gt;&#xA;The loopback run (its &amp;quot;source&amp;quot; is a second directory on the same machine, so it reports the local rsync):&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-console&#34; data-lang=&#34;console&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gp&#34;&gt;$&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$RSYNC_RSH&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$SRC&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;test -d &lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$REMOTE&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt; &amp;amp;&amp;amp; echo dir-ok; command -v rsync &amp;amp;&amp;amp; rsync --version | head -1&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;rc=&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$?&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;dir-ok&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;/usr/bin/rsync&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;rsync  version 3.5.1-g315819c5  protocol version 33&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;rc=0&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Expected: three lines, &lt;code&gt;dir-ok&lt;/code&gt;, the path of rsync on the source and its version. No &lt;code&gt;dir-ok&lt;/code&gt; line: &lt;code&gt;$REMOTE&lt;/code&gt; is not a directory there (in the loopback run, a path that does not exist printed only the two rsync lines). No path and version: rsync is not installed there, or not in the remote shell&#39;s &lt;code&gt;PATH&lt;/code&gt;. The final &lt;code&gt;rc&lt;/code&gt; comes from the last command and says nothing about the directory.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;Run the checksum dry run, from the copy to the source.&lt;/strong&gt;&#xA;The command from the original run, with &lt;code&gt;LOCAL&lt;/code&gt;, &lt;code&gt;host&lt;/code&gt; and &lt;code&gt;REMOTE&lt;/code&gt; as its placeholders:&#xA;&lt;code&gt;rsync --dry-run -rlc --itemize-changes --out-format=&#39;%i %n&#39; LOCAL/ host:REMOTE/&lt;/code&gt;&#xA;The same command with the variables from Before you start, saving both output streams:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-console&#34; data-lang=&#34;console&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gp&#34;&gt;$&lt;/span&gt; rsync --dry-run -rlc --itemize-changes --out-format&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;%i %n&amp;#39;&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$LOCAL&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;/ &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$SRC&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;:&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$REMOTE&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;/ &amp;gt; dry.txt 2&amp;gt; dry.err&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;rc=&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$?&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;rc=0&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Expected: &lt;code&gt;rc=0&lt;/code&gt; and an empty &lt;code&gt;dry.err&lt;/code&gt; (step 4 shows its line count). Anything else: find the cause under Failure modes before going on; step 5 refuses to classify while &lt;code&gt;dry.err&lt;/code&gt; has a line.&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;--dry-run&lt;/code&gt;: rsync works out what it would send and reports it; no file contents are sent and nothing is written on the source. The copy&#39;s file list, with names, sizes and checksums, does go to the source (Notes). In the loopback runs, a listing of the source tree with sizes, mtimes and ctimes was the same before and after seven dry runs.&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;-c&lt;/code&gt;: decide by checksum, not by size and modification time. The local side checksums its files while it builds the file list; the source checksums each of its files that has the same size as the local one, and a file whose size differs is reported without one. The algorithm is negotiated between the two ends (&lt;code&gt;xxh128&lt;/code&gt; between the two rsync 3.5.1 ends of the loopback run), so &amp;quot;identical&amp;quot; below means the same checksum. Without &lt;code&gt;-c&lt;/code&gt;, the loopback run missed a same-size edit with an unchanged mtime and flagged a file whose bytes matched but whose mtime did not (Notes).&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;-r -l&lt;/code&gt;: recurse, and compare symlinks as links instead of following them. Not &lt;code&gt;-a&lt;/code&gt;: its &lt;code&gt;-t -p -o -g&lt;/code&gt; add attribute-only lines, such as &lt;code&gt;.f..t...... notes/touched.txt&lt;/code&gt; for a file with identical bytes, and those would turn an identical entry into &lt;code&gt;differs&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;--itemize-changes --out-format=&#39;%i %n&#39;&lt;/code&gt;: one line per item that would change, made of an 11-character change code, a space and the path relative to &lt;code&gt;$LOCAL&lt;/code&gt;. The explicit format leaves out the &lt;code&gt; -&amp;gt; target&lt;/code&gt; that &lt;code&gt;--itemize-changes&lt;/code&gt; alone appends to symlinks, so every line splits the same way.&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;&amp;quot;$LOCAL&amp;quot;/&lt;/code&gt; with the trailing slash: compare the contents of the copy with the contents of &lt;code&gt;$REMOTE&lt;/code&gt;. The form without the slash is Failure modes, row 4.&lt;/li&gt;&#xA;&lt;li&gt;No &lt;code&gt;--delete&lt;/code&gt;: it would itemize files that exist only on the source, and without &lt;code&gt;--dry-run&lt;/code&gt; it would delete them there.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;Read the itemized lines.&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-console&#34; data-lang=&#34;console&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gp&#34;&gt;$&lt;/span&gt; cat dry.txt&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; wc -l dry.txt dry.err&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;&amp;lt;fcsT...... edited/changed-size.txt&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;&amp;lt;fc.T...... edited/same-size.txt&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;cd+++++++++ gone-there/&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;&amp;lt;f+++++++++ gone-there/NOTES&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;cd+++++++++ gone-there/src/&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;&amp;lt;f+++++++++ gone-there/src/main.c&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;&amp;lt;f+++++++++ grown/new-here.txt&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;&amp;lt;fcsT...... repo-index/.git/FETCH_HEAD&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;&amp;lt;fc.T...... repo-index/.git/index&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;  9 dry.txt&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;  0 dry.err&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;  9 total&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In this direction the local side is the sender, so every file line starts with &lt;code&gt;&amp;lt;&lt;/code&gt;, sent to the remote host. On the loopback trees, &lt;code&gt;notes/&lt;/code&gt;, &lt;code&gt;repo-same/&lt;/code&gt;, &lt;code&gt;top-file.txt&lt;/code&gt; and &lt;code&gt;.dotfile&lt;/code&gt; print nothing: every file in them matched by checksum, including one whose mtime differs on the source, and the symlink in &lt;code&gt;notes/&lt;/code&gt; points at the same target on both sides.&lt;/p&gt;&#xA;&lt;div class=&#34;table-wrap&#34; aria-label=&#34;Table: Line, Meaning here&#34;&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;Line&lt;/th&gt;&#xA;      &lt;th&gt;Meaning here&lt;/th&gt;&#xA;    &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;td&gt;no line&lt;/td&gt;&#xA;      &lt;td&gt;the item exists on the source with the same checksum (a symlink: the same target)&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;td&gt;&lt;code&gt;&amp;lt;f+++++++++ path&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;td&gt;file missing on the source&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;td&gt;&lt;code&gt;cd+++++++++ dir/&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;td&gt;directory missing on the source; everything under it follows as new&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;td&gt;&lt;code&gt;cL+++++++++ link&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;td&gt;symlink missing on the source&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;td&gt;&lt;code&gt;&amp;lt;fcsT...... path&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;td&gt;content (&lt;code&gt;c&lt;/code&gt;) and size (&lt;code&gt;s&lt;/code&gt;) differ&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;td&gt;&lt;code&gt;&amp;lt;fc.T...... path&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;td&gt;same size, different content&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;/div&gt;&lt;p&gt;A leading &lt;code&gt;c&lt;/code&gt; means the receiver would create the item, a directory or symlink here. The &lt;code&gt;T&lt;/code&gt; only says the receiver would set the transfer time as mtime, because times are not preserved; it carries no information here. Line codes by kind:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-console&#34; data-lang=&#34;console&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gp&#34;&gt;$&lt;/span&gt; cut -c1-11 dry.txt &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; sort &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; uniq -c&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;      2 cd+++++++++&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;      3 &amp;lt;f+++++++++&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;      2 &amp;lt;fcsT......&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;      2 &amp;lt;fc.T......&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;div class=&#34;callout callout-gotcha&#34; role=&#34;note&#34;&gt;&#xA;&lt;p class=&#34;callout-label&#34;&gt;&lt;span class=&#34;callout-kind&#34;&gt;Gotcha&lt;/span&gt;&lt;span class=&#34;callout-title&#34;&gt;Count &lt;code&gt;&amp;lt;f&lt;/code&gt;, not &lt;code&gt;&amp;gt;f&lt;/code&gt;&lt;/span&gt;&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;&amp;gt;&lt;/code&gt; marks an item received by the local side, which happens in a pull. In the original run my first classifier looked for &lt;code&gt;&amp;gt;&lt;/code&gt; and counted zero.&lt;/p&gt;&#xA;&lt;/div&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-console&#34; data-lang=&#34;console&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gp&#34;&gt;$&lt;/span&gt; grep -c &lt;span class=&#34;s1&#34;&gt;&amp;#39;^&amp;gt;f&amp;#39;&lt;/span&gt; dry.txt&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;0&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gp&#34;&gt;$&lt;/span&gt; grep -c &lt;span class=&#34;s1&#34;&gt;&amp;#39;^&amp;lt;f&amp;#39;&lt;/span&gt; dry.txt&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;7&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;Give each top-level entry a verdict.&lt;/strong&gt;&#xA;Save this as &lt;code&gt;classify.awk&lt;/code&gt; in the working directory:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-awk&#34; data-lang=&#34;awk&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# classify.awk: one verdict per top-level entry of LOCAL, from a push-direction&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;#   rsync --dry-run -rlc --itemize-changes --out-format=&amp;#39;%i %n&amp;#39; LOCAL/ host:REMOTE/ &amp;gt; dry.txt 2&amp;gt; dry.err&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# usage: awk -f classify.awk entries.txt dry.txt dry.err &amp;gt; verdicts.tsv&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;#   identical   no line: every file under it exists on the source with the same checksum&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;#   git-noise   only .git/index or .git/FETCH_HEAD differ&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;#   only-local  the entry itself is missing on the source&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;#   differs     anything else (new or changed files inside)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Any line in dry.err, or any line of dry.txt it cannot map to an entry,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# aborts with exit 2 and no verdicts: an unreadable directory prints no line.&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;FILENAME&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;==&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;ARGV&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;order&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;++&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;n&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;$&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;v&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;$&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;identical&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;next&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;FILENAME&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;==&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;ARGV&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;3&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;print&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;rsync stderr: &amp;#34;&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;$&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;/dev/stderr&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;bad&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;next&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;code&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;substr&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;$&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;11&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;path&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;substr&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;$&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;13&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;sub&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;sr&#34;&gt;/\/$/&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;path&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;top&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;path&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;sub&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;sr&#34;&gt;/\/.*/&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;top&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;kr&#34;&gt;substr&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;$&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;12&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;!=&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34; &amp;#34;&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;||&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;!&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;top&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;in&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;v&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;kr&#34;&gt;print&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;unmapped line: &amp;#34;&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;$&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;/dev/stderr&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;bad&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;next&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;new&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;kr&#34;&gt;substr&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;code&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;3&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;==&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;+++++++++&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;path&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;==&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;top&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;new&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;v&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;top&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;only-local&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;else&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;v&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;top&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;==&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;only-local&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;next&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;else&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;!&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;new&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;path&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;~&lt;/span&gt; &lt;span class=&#34;sr&#34;&gt;/\/\.git\/(index|FETCH_HEAD)$/&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;v&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;top&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;==&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;identical&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;v&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;top&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;git-noise&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;else&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;v&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;top&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;differs&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;END&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;bad&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;exit&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;2&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;i&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;i&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;n&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;++&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;printf&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;%s\t%s\n&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;v&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;order&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]],&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;order&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;List the entries, then classify:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-console&#34; data-lang=&#34;console&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gp&#34;&gt;$&lt;/span&gt; find &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$LOCAL&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt; -mindepth &lt;span class=&#34;m&#34;&gt;1&lt;/span&gt; -maxdepth &lt;span class=&#34;m&#34;&gt;1&lt;/span&gt; -printf &lt;span class=&#34;s1&#34;&gt;&amp;#39;%f\n&amp;#39;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; sort &amp;gt; entries.txt&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; cat entries.txt&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;.dotfile&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;edited&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;gone-there&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;grown&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;notes&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;repo-index&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;repo-same&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;top-file.txt&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gp&#34;&gt;$&lt;/span&gt; awk -f classify.awk entries.txt dry.txt dry.err &amp;gt; verdicts.tsv&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;rc=&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$?&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; cat verdicts.tsv&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;rc=0&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;identical&#x9;.dotfile&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;differs&#x9;edited&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;only-local&#x9;gone-there&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;differs&#x9;grown&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;identical&#x9;notes&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;git-noise&#x9;repo-index&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;identical&#x9;repo-same&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;identical&#x9;top-file.txt&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Expected: &lt;code&gt;rc=0&lt;/code&gt; and one verdict per entry. &lt;code&gt;rc=2&lt;/code&gt; with &lt;code&gt;rsync stderr:&lt;/code&gt; or &lt;code&gt;unmapped line:&lt;/code&gt; messages and an empty &lt;code&gt;verdicts.tsv&lt;/code&gt;: see Failure modes.&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;identical&lt;/code&gt;: no line under the entry; every file in it exists on the source with the same checksum, and every symlink with the same target.&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;git-noise&lt;/code&gt;: the only differing files are &lt;code&gt;.git/index&lt;/code&gt; and &lt;code&gt;.git/FETCH_HEAD&lt;/code&gt;, and every object, ref and work-tree file matched by checksum. The index caches each tracked file&#39;s stat data, which differs between two machines even for identical files, and &lt;code&gt;git status&lt;/code&gt; rewrites it when it refreshes that data; &lt;code&gt;FETCH_HEAD&lt;/code&gt; records the last &lt;code&gt;git fetch&lt;/code&gt;. On the loopback tree, one &lt;code&gt;git status&lt;/code&gt; on the source side and a fetch there from a second URL produced exactly this. The index is also git&#39;s staging area, so whatever the copy had staged goes with it; the objects it names are on the source, because no file under &lt;code&gt;.git/objects&lt;/code&gt; differed. Any other differing file under &lt;code&gt;.git&lt;/code&gt; leaves the entry as &lt;code&gt;differs&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;only-local&lt;/code&gt;: the entry itself is missing on the source; the copy is the only one.&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;differs&lt;/code&gt;: new or changed files inside; either side may hold the newer version.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;List the deletion candidates and look at what stays.&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-console&#34; data-lang=&#34;console&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gp&#34;&gt;$&lt;/span&gt; awk -F&lt;span class=&#34;s1&#34;&gt;&amp;#39;\t&amp;#39;&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;$1 == &amp;#34;identical&amp;#34; || $1 == &amp;#34;git-noise&amp;#34; { print $2 }&amp;#39;&lt;/span&gt; verdicts.tsv &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; sort &amp;gt; planned.txt&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; cat planned.txt&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;.dotfile&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;notes&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;repo-index&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;repo-same&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;top-file.txt&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gp&#34;&gt;$&lt;/span&gt; awk -F&lt;span class=&#34;s1&#34;&gt;&amp;#39;\t&amp;#39;&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;$1 == &amp;#34;only-local&amp;#34; || $1 == &amp;#34;differs&amp;#34;&amp;#39;&lt;/span&gt; verdicts.tsv&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;differs&#x9;edited&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;only-local&#x9;gone-there&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;differs&#x9;grown&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Expected: &lt;code&gt;planned.txt&lt;/code&gt; holds only &lt;code&gt;identical&lt;/code&gt; and &lt;code&gt;git-noise&lt;/code&gt; entries. Remove from it anything you want to keep anyway. The other entries stay: an &lt;code&gt;only-local&lt;/code&gt; entry exists nowhere else, and a &lt;code&gt;differs&lt;/code&gt; entry may hold the newer copy of a file.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;Re-run the dry run right before deleting, and keep only entries that still qualify.&lt;/strong&gt;&#xA;The source can change between step 3 and the deletion:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;rsync --dry-run -rlc --itemize-changes --out-format&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;%i %n&amp;#39;&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$LOCAL&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;/ &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$SRC&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;:&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$REMOTE&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;/ &amp;gt; dry2.txt 2&amp;gt; dry2.err&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;rc=&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$?&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; cat dry2.err&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;awk -f classify.awk entries.txt dry2.txt dry2.err &amp;gt; verdicts2.tsv&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;rc=&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$?&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;awk -F&lt;span class=&#34;s1&#34;&gt;&amp;#39;\t&amp;#39;&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;$1 == &amp;#34;identical&amp;#34; || $1 == &amp;#34;git-noise&amp;#34; { print $2 }&amp;#39;&lt;/span&gt; verdicts2.tsv &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; sort &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; comm -12 planned.txt - &amp;gt; delete.txt&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;comm -23 planned.txt delete.txt &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; sed &lt;span class=&#34;s1&#34;&gt;&amp;#39;s/^/dropped: /&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; cat delete.txt&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In the loopback run, a line was appended to &lt;code&gt;notes/b.txt&lt;/code&gt; on the source between the two dry runs (a harness step, to show what a change does):&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-console&#34; data-lang=&#34;console&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gp&#34;&gt;$&lt;/span&gt; rsync --dry-run -rlc --itemize-changes --out-format&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;%i %n&amp;#39;&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$LOCAL&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;/ &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$SRC&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;:&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$REMOTE&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;/ &amp;gt; dry2.txt 2&amp;gt; dry2.err&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;rc=&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$?&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; cat dry2.err&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;rc=0&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gp&#34;&gt;$&lt;/span&gt; awk -f classify.awk entries.txt dry2.txt dry2.err &amp;gt; verdicts2.tsv&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;rc=&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$?&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;awk -F&amp;#39;\t&amp;#39; &amp;#39;$1 == &amp;#34;identical&amp;#34; || $1 == &amp;#34;git-noise&amp;#34; { print $2 }&amp;#39; verdicts2.tsv | sort | comm -12 planned.txt - &amp;gt; delete.txt&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;comm -23 planned.txt delete.txt | sed &amp;#39;s/^/dropped: /&amp;#39;; cat delete.txt&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;rc=0&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;dropped: notes&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;.dotfile&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;repo-index&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;repo-same&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;top-file.txt&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Expected: &lt;code&gt;rc=0&lt;/code&gt; twice, and &lt;code&gt;delete.txt&lt;/code&gt; lists the planned entries that are still &lt;code&gt;identical&lt;/code&gt; or &lt;code&gt;git-noise&lt;/code&gt;; &lt;code&gt;dropped:&lt;/code&gt; lines name those that are not. When the classifier refuses (exit 2), &lt;code&gt;verdicts2.tsv&lt;/code&gt; is left empty (0 bytes in the slashless test), so &lt;code&gt;delete.txt&lt;/code&gt; comes out empty.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;Delete the entries in &lt;code&gt;delete.txt&lt;/code&gt; from the copy.&lt;/strong&gt;&lt;/p&gt;&#xA;&#xA;&lt;div class=&#34;callout callout-warning&#34; role=&#34;note&#34;&gt;&#xA;&lt;p class=&#34;callout-label&#34;&gt;&lt;span class=&#34;callout-kind&#34;&gt;Warning&lt;/span&gt;&lt;span class=&#34;callout-title&#34;&gt;This step is irreversible on this machine&lt;/span&gt;&lt;/p&gt;&#xA;&lt;p&gt;The entries are gone from the copy. Their files matched the source by checksum at step 7, apart from &lt;code&gt;.git/index&lt;/code&gt; and &lt;code&gt;FETCH_HEAD&lt;/code&gt; in &lt;code&gt;git-noise&lt;/code&gt; entries, whose versions in the copy cannot be brought back. Undo pulls everything else back from the source while the source keeps it.&lt;/p&gt;&#xA;&lt;/div&gt;&#xA;&lt;p&gt;Size first:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-console&#34; data-lang=&#34;console&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gp&#34;&gt;$&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;cd&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$LOCAL&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; xargs -d &lt;span class=&#34;s1&#34;&gt;&amp;#39;\n&amp;#39;&lt;/span&gt; du -shc --&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; &amp;lt; delete.txt&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;4.0K&#x9;.dotfile&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;132K&#x9;repo-index&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;128K&#x9;repo-same&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;4.0K&#x9;top-file.txt&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;268K&#x9;total&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then delete:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;while&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;IFS&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;read&lt;/span&gt; -r e&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;do&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;[&lt;/span&gt; -z &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$e&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;||&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;[&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$e&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; . &lt;span class=&#34;o&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;||&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;[&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$e&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; .. &lt;span class=&#34;o&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;||&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;[&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;e&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;#*/&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt; !&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$e&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;then&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;skip: &lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$e&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt; &amp;gt;&lt;span class=&#34;p&#34;&gt;&amp;amp;&lt;/span&gt;2&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;continue&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;fi&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  rm -rf -- &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;LOCAL&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:?&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;/&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$e&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;printf&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;removed\t%s\n&amp;#39;&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$e&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;done&lt;/span&gt; &amp;lt; delete.txt &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; tee -a removed.log&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-console&#34; data-lang=&#34;console&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gp&#34;&gt;$&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;while&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;IFS&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;read&lt;/span&gt; -r e&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;do&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;  if [ -z &amp;#34;$e&amp;#34; ] || [ &amp;#34;$e&amp;#34; = . ] || [ &amp;#34;$e&amp;#34; = .. ] || [ &amp;#34;${e#*/}&amp;#34; != &amp;#34;$e&amp;#34; ]; then echo &amp;#34;skip: $e&amp;#34; &amp;gt;&amp;amp;2; continue; fi&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;  rm -rf -- &amp;#34;${LOCAL:?}/$e&amp;#34; &amp;amp;&amp;amp; printf &amp;#39;removed\t%s\n&amp;#39; &amp;#34;$e&amp;#34;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;done &amp;lt; delete.txt | tee -a removed.log&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;removed&#x9;.dotfile&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;removed&#x9;repo-index&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;removed&#x9;repo-same&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;removed&#x9;top-file.txt&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Expected: one &lt;code&gt;removed&lt;/code&gt; line per entry in &lt;code&gt;delete.txt&lt;/code&gt; and no &lt;code&gt;skip:&lt;/code&gt; line. The loop refuses empty names, &lt;code&gt;.&lt;/code&gt;, &lt;code&gt;..&lt;/code&gt; and anything containing &lt;code&gt;/&lt;/code&gt;, and &lt;code&gt;${LOCAL:?}&lt;/code&gt; makes the shell refuse the &lt;code&gt;rm&lt;/code&gt; when &lt;code&gt;$LOCAL&lt;/code&gt; is empty or unset instead of running it against &lt;code&gt;/&amp;lt;entry&amp;gt;&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;Re-check the copy against the source while you still have access.&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-console&#34; data-lang=&#34;console&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gp&#34;&gt;$&lt;/span&gt; find &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$LOCAL&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt; -mindepth &lt;span class=&#34;m&#34;&gt;1&lt;/span&gt; -maxdepth &lt;span class=&#34;m&#34;&gt;1&lt;/span&gt; -printf &lt;span class=&#34;s1&#34;&gt;&amp;#39;%f\n&amp;#39;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; sort &amp;gt; entries-after.txt&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;rsync --dry-run -rlc --itemize-changes --out-format=&amp;#39;%i %n&amp;#39; &amp;#34;$LOCAL&amp;#34;/ &amp;#34;$SRC&amp;#34;:&amp;#34;$REMOTE&amp;#34;/ &amp;gt; dry3.txt 2&amp;gt; dry3.err; echo &amp;#34;rc=$?&amp;#34;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;awk -f classify.awk entries-after.txt dry3.txt dry3.err&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;rc=0&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;differs&#x9;edited&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;only-local&#x9;gone-there&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;differs&#x9;grown&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;differs&#x9;notes&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Expected: &lt;code&gt;rc=0&lt;/code&gt;, and only &lt;code&gt;only-local&lt;/code&gt; and &lt;code&gt;differs&lt;/code&gt; verdicts, plus any entry you took out of &lt;code&gt;planned.txt&lt;/code&gt; on purpose. &lt;code&gt;notes&lt;/code&gt; is &lt;code&gt;differs&lt;/code&gt; here because of the source-side edit from step 7.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;Remove the temporary key from the source.&lt;/strong&gt;&#xA;This is the one change on the source after step 1; ask its owner first.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;BLOB&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;$(&lt;/span&gt;cut -d&lt;span class=&#34;s1&#34;&gt;&amp;#39; &amp;#39;&lt;/span&gt; -f2 ~/.ssh/&amp;lt;KEY&amp;gt;.pub&lt;span class=&#34;k&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;$RSYNC_RSH&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$SRC&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;cd ~/.ssh &amp;amp;&amp;amp; cp -p authorized_keys authorized_keys.tmpkey-bak &amp;amp;&amp;amp; { grep -vF &amp;#39;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$BLOB&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#39; authorized_keys.tmpkey-bak &amp;gt; authorized_keys; [ \$? -le 1 ]; } &amp;amp;&amp;amp; rm authorized_keys.tmpkey-bak; grep -cF &amp;#39;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$BLOB&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#39; authorized_keys&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Expected: &lt;code&gt;0&lt;/code&gt;, the number of lines still holding the key (the command exits 1 because that last grep matched nothing). This edits &lt;code&gt;authorized_keys&lt;/code&gt; only, the file &lt;code&gt;ssh-copy-id&lt;/code&gt; appended to; sshd also reads &lt;code&gt;authorized_keys2&lt;/code&gt; by default, which step 1 did not touch. &lt;code&gt;&amp;gt;&lt;/code&gt; rewrites the file in place, so it keeps its mode; the backup is deleted only after the filter succeeded. If grep reports an error instead, &lt;code&gt;authorized_keys&lt;/code&gt; may be cut short and the backup is still there: &lt;code&gt;cp -p ~/.ssh/authorized_keys.tmpkey-bak ~/.ssh/authorized_keys&lt;/code&gt; on the source puts it back (not run). The same command, run through the loopback shell against a fake home, left a file that had held two other keys plus this one with exactly the two others, and a file that had held only this key empty (0 bytes); both kept mode 600, and the backup was gone both times. It needs a POSIX-style login shell on the source and was tested with &lt;code&gt;/bin/sh&lt;/code&gt; only. [UNVERIFIED: not run against a real sshd.]&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;Close any shared connection and check that the key no longer logs in.&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;ssh -O &lt;span class=&#34;nb&#34;&gt;exit&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$SRC&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;ssh -G -F /dev/null -i ~/.ssh/&amp;lt;KEY&amp;gt; -o &lt;span class=&#34;nv&#34;&gt;IdentitiesOnly&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;yes -o &lt;span class=&#34;nv&#34;&gt;BatchMode&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;yes -o &lt;span class=&#34;nv&#34;&gt;ControlPath&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;none &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$SRC&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;true&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; grep -E &lt;span class=&#34;s1&#34;&gt;&amp;#39;^(identityfile|identitiesonly|batchmode|controlpath) &amp;#39;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;ssh -F /dev/null -i ~/.ssh/&amp;lt;KEY&amp;gt; -o &lt;span class=&#34;nv&#34;&gt;IdentitiesOnly&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;yes -o &lt;span class=&#34;nv&#34;&gt;BatchMode&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;yes -o &lt;span class=&#34;nv&#34;&gt;ControlPath&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;none &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$SRC&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt; true&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;rc=&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$?&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;ssh -O exit&lt;/code&gt; closes a master connection if one exists. Without one it fails harmlessly; with a control path that has no master behind it, a local test printed:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-console&#34; data-lang=&#34;console&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gp&#34;&gt;$&lt;/span&gt; ssh -F /dev/null -o &lt;span class=&#34;nv&#34;&gt;ControlPath&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;/nonexistent/master.sock -O &lt;span class=&#34;nb&#34;&gt;exit&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$SRC&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;rc=&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$?&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;Control socket connect(/nonexistent/master.sock): No such file or directory&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;rc=255&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;ssh -G&lt;/code&gt; prints the configuration the test login will use and exits without connecting. With &lt;code&gt;-F /dev/null&lt;/code&gt;, ssh reads no &lt;code&gt;~/.ssh/config&lt;/code&gt; and no system-wide config, so the temporary key is the only identity:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-console&#34; data-lang=&#34;console&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gp&#34;&gt;$&lt;/span&gt; ssh -G -F /dev/null -i keytest/tmp-key -o &lt;span class=&#34;nv&#34;&gt;IdentitiesOnly&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;yes -o &lt;span class=&#34;nv&#34;&gt;BatchMode&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;yes -o &lt;span class=&#34;nv&#34;&gt;ControlPath&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;none &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$SRC&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;true&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; grep -E &lt;span class=&#34;s1&#34;&gt;&amp;#39;^(identityfile|identitiesonly|batchmode|controlpath) &amp;#39;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;batchmode yes&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;identitiesonly yes&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;identityfile keytest/tmp-key&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Without &lt;code&gt;-F /dev/null&lt;/code&gt;, an &lt;code&gt;IdentityFile&lt;/code&gt; line in a config still joins the list, even with &lt;code&gt;IdentitiesOnly=yes&lt;/code&gt;:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-console&#34; data-lang=&#34;console&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gp&#34;&gt;$&lt;/span&gt; cat keytest/ssh_config&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; ssh -G -F keytest/ssh_config -i keytest/tmp-key -o &lt;span class=&#34;nv&#34;&gt;IdentitiesOnly&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;yes &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$SRC&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;true&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; grep &lt;span class=&#34;s1&#34;&gt;&amp;#39;^identityfile &amp;#39;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;Host *&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;    IdentityFile keytest/other-key&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;identityfile keytest/tmp-key&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;identityfile keytest/other-key&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Expected: the test login fails with a &lt;code&gt;Permission denied&lt;/code&gt; message and &lt;code&gt;rc=255&lt;/code&gt;. Any other error (&lt;code&gt;Could not resolve hostname&lt;/code&gt;, &lt;code&gt;Connection refused&lt;/code&gt;) means the test never reached authentication; if the source needs settings from &lt;code&gt;~/.ssh/config&lt;/code&gt;, such as &lt;code&gt;Port&lt;/code&gt; or &lt;code&gt;ProxyJump&lt;/code&gt;, add them as &lt;code&gt;-o&lt;/code&gt; options and test again. Only after a &lt;code&gt;Permission denied&lt;/code&gt;, delete the local key files: &lt;code&gt;rm ~/.ssh/&amp;lt;KEY&amp;gt; ~/.ssh/&amp;lt;KEY&amp;gt;.pub; unset RSYNC_RSH&lt;/code&gt;.&lt;/p&gt;&#xA;&#xA;&lt;div class=&#34;callout callout-gotcha&#34; role=&#34;note&#34;&gt;&#xA;&lt;p class=&#34;callout-label&#34;&gt;&lt;span class=&#34;callout-kind&#34;&gt;Gotcha&lt;/span&gt;&lt;span class=&#34;callout-title&#34;&gt;An open master connection logs in without the key&lt;/span&gt;&lt;/p&gt;&#xA;&lt;p&gt;With &lt;code&gt;ControlMaster&lt;/code&gt; and &lt;code&gt;ControlPersist&lt;/code&gt; in &lt;code&gt;~/.ssh/config&lt;/code&gt;, a master connection stays open in the background after the rsync runs, and later sessions reuse its network connection instead of opening a new one. A test login over it reuses a connection that was authenticated before the key was removed, so it says nothing about the key. &lt;code&gt;-O exit&lt;/code&gt; closes the master, &lt;code&gt;ControlPath=none&lt;/code&gt; keeps the test off any master, and &lt;code&gt;BatchMode=yes&lt;/code&gt; stops ssh from falling back to a password prompt.&lt;/p&gt;&#xA;&lt;/div&gt;&#xA;&lt;p&gt;[UNVERIFIED: the test login was not run against a real sshd. In the original run the multiplexed connection was closed and a fresh login was checked to fail.]&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;</description>
			</item>
			<item>
				<title>All four drives at once, and why that was the good news</title>
				<link>https://tsvinc.dev/posts/all-four-drives-at-once/</link>
				<pubDate>Wed, 08 Jul 2026 00:00:00 +0000</pubDate>
				<guid>https://tsvinc.dev/posts/all-four-drives-at-once/</guid>
				<description>&lt;div class=&#34;tldr&#34;&gt;&#xA;  &lt;span class=&#34;tldr-label&#34;&gt;TL;DR&lt;/span&gt;&#xA;  N drives failing &lt;em&gt;at the same instant&lt;/em&gt; is never N drive failures. It&#39;s a shared path: driver, cable, or power. Here it was two of them stacked — a partial kernel update that installed &lt;code&gt;kernel-core&lt;/code&gt; without &lt;code&gt;kernel-modules&lt;/code&gt;, stranding &lt;code&gt;mpt3sas&lt;/code&gt;, and underneath it a loose SFF-8087 cable with every PHY negotiating at unknown link rate. The boot SSD was on a different controller and never blinked, which was the clue. No data was lost.&#xA;&lt;/div&gt;&#xA;&#xA;&lt;p&gt;I asked in four words, flat, no punctuation to speak of, with the calm of someone who hasn&#39;t decided yet how bad it is:&lt;/p&gt;</description>
			</item>
			<item>
				<title>Reallocated sectors: is your drive dying or just scarred?</title>
				<link>https://tsvinc.dev/posts/reallocated-sectors-dying-or-scarred/</link>
				<pubDate>Wed, 08 Jul 2026 00:00:00 +0000</pubDate>
				<guid>https://tsvinc.dev/posts/reallocated-sectors-dying-or-scarred/</guid>
				<description>&lt;div class=&#34;tldr&#34;&gt;&#xA;  &lt;span class=&#34;tldr-label&#34;&gt;TL;DR&lt;/span&gt;&#xA;  A high &lt;code&gt;Reallocated_Sector_Ct&lt;/code&gt; is history, not prognosis. The drive with 344 reallocations was healthy — scarred, but its firmware was still successfully remapping. The drive with &lt;em&gt;zero&lt;/em&gt; reallocations was the dying one: &lt;code&gt;Current_Pending_Sector&lt;/code&gt; climbing while the reallocated count stayed pinned at 0 means the firmware has stopped remapping, which is the failure. Judge by whether the counters &lt;em&gt;move&lt;/em&gt; under a forced write pass, not by the raw number.&#xA;&lt;/div&gt;&#xA;&#xA;&lt;p&gt;Two drives on the same HBA, same evening, same test. Here are the SMART lines that matter, before I touched either of them:&lt;/p&gt;</description>
			</item>
			<item>
				<title>Salvaging a dying disk by partitioning around the wound</title>
				<link>https://tsvinc.dev/posts/partition-around-the-wound/</link>
				<pubDate>Wed, 08 Jul 2026 00:00:00 +0000</pubDate>
				<guid>https://tsvinc.dev/posts/partition-around-the-wound/</guid>
				<description>&lt;div class=&#34;tldr&#34;&gt;&#xA;  &lt;span class=&#34;tldr-label&#34;&gt;TL;DR&lt;/span&gt;&#xA;  &lt;code&gt;badblocks&lt;/code&gt; tells you &lt;em&gt;how many&lt;/em&gt; bad blocks; its &lt;code&gt;-o&lt;/code&gt; logfile tells you &lt;em&gt;where&lt;/em&gt;, and that changes the verdict. All 58 failures on this 8 TB drive sat in the first 5.23% of the platter. Partition past the wound, leave the damaged region unallocated, and the healthy 94.8% — about 6.7 TiB — goes back to work as disposable scratch space. Not for anything you&#39;d miss.&#xA;&lt;/div&gt;&#xA;&#xA;&lt;p&gt;Every &lt;code&gt;badblocks&lt;/code&gt; run leaves two artifacts, and the useful one is the file almost nobody opens. There&#39;s the summary line everyone reads:&lt;/p&gt;</description>
			</item>
			<item>
				<title>Stop chasing a sensor that doesn&#39;t exist</title>
				<link>https://tsvinc.dev/posts/stop-chasing-a-sensor-that-doesnt-exist/</link>
				<pubDate>Wed, 08 Jul 2026 00:00:00 +0000</pubDate>
				<guid>https://tsvinc.dev/posts/stop-chasing-a-sensor-that-doesnt-exist/</guid>
				<description>&lt;div class=&#34;tldr&#34;&gt;&#xA;  &lt;span class=&#34;tldr-label&#34;&gt;TL;DR&lt;/span&gt;&#xA;  The LSI SAS2008 has no temperature sensor. Not an unsupported one, not one behind a proprietary tool — the silicon was never fabricated with it. No driver, &lt;code&gt;lsiutil&lt;/code&gt; option, or sysfs path will summon it. Once you can tell &amp;quot;hard to monitor&amp;quot; from &amp;quot;impossible to monitor,&amp;quot; the answer stops being another tool and starts being airflow. The same night&#39;s fan experiment made the point twice: the null result &lt;em&gt;was&lt;/em&gt; the diagnosis.&#xA;&lt;/div&gt;&#xA;&#xA;&lt;p&gt;The storage box runs every HDD through one LSI SAS2008 HBA -- a 9211-8i-class card in IT mode, &lt;code&gt;mpt3sas&lt;/code&gt;, sitting at PCI &lt;code&gt;05:00.0&lt;/code&gt;. These things are fanless, run hot, and are famous for slow-cooking themselves in dead-air cases. So the instinct is obvious: monitor the chip temperature. Graph it, alert on it, normal ops hygiene.&lt;/p&gt;</description>
			</item>
	</channel>
</rss>
