<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Rsync on Lessons learned</title><link>https://eghuro.cz/tags/rsync/</link><description>Recent content in Rsync on Lessons learned</description><generator>Hugo</generator><language>en-us</language><copyright>© Alexandr Mansurov · Licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/)</copyright><lastBuildDate>Thu, 22 Oct 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://eghuro.cz/tags/rsync/index.xml" rel="self" type="application/rss+xml"/><item><title>rsync code 23: atomic archives can still form an incomplete release</title><link>https://eghuro.cz/posts/rsync-atomic-archives-incomplete-release/</link><pubDate>Thu, 22 Oct 2026 00:00:00 +0000</pubDate><guid>https://eghuro.cz/posts/rsync-atomic-archives-incomplete-release/</guid><description>&lt;p&gt;A dataset transfer ended with:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;rsync error: some files/attrs were not transferred (see previous errors) (code 23) at main.c(1327) [sender=3.2.5]&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The packing job published each finished archive by rename, so final filenames never exposed an archive still being written. The transfer nevertheless began before the collection was complete. Repeating it after packing finished, then checking destination hashes, recovered the release.&lt;/p&gt;&#10;&lt;p&gt;This happened in the &lt;a&#10; href="https://eghuro.cz/posts/claude-code-ssh-openpbs-setup"&#10; &#10; data-rel="prefetch"&#10; &#10; &#10;&gt;Claude Code over SSH and OpenPBS workflow&lt;/a&gt;&#10;: a PBS job produced the archives, while separate remote commands and a human-operated transfer consumed them.&lt;/p&gt;&#10;&#10;&lt;h2 id="the-filename-was-safe-before-the-collection-was-ready"&gt;The filename was safe before the collection was ready &lt;/h2&gt;&#10;&lt;p&gt;The packer used this pattern, with paths generalized:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;tar -czf groups_00.tar.gz.part groups/part_00 &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; mv groups_00.tar.gz.part groups_00.tar.gz&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;A reader looking for &lt;code&gt;*.tar.gz&lt;/code&gt; would only see that archive after its successful creation and rename on the same filesystem. But the producer repeated the operation for several archives, then wrote sidecar files. Nothing prevented a consumer from enumerating the directory between those steps.&lt;/p&gt;&#10;&lt;p&gt;Transfer instructions went out while the packer was running. The user&amp;rsquo;s pasted output ended with &lt;code&gt;to-chk=0/16&lt;/code&gt;—the intended release contained seventeen archives plus a checksum manifest and a guide. A later source listing showed the complete set.&lt;/p&gt;&#10;&lt;p&gt;The count, transferred size, and packing timeline supported the explanation that the last archive and sidecars didn&amp;rsquo;t exist when the transfer enumerated its inputs. The earlier detailed rsync errors were not included, so this remained a supported diagnosis rather than a direct readback of each missing-path error.&lt;/p&gt;&#10;&lt;p&gt;Code 23 alone cannot identify that cause. The &lt;a&#10; href="https://download.samba.org/pub/rsync/rsync.1"&#10; &#10; target="_blank" rel="noopener"&#10; &#10; &#10;&gt;rsync manual&lt;/a&gt;&#10; defines it as a partial transfer due to error; inspect the preceding errors before retrying mindlessly.&lt;/p&gt;&#10;&#10;&lt;h2 id="repeat-the-transfer-after-checking-the-producer"&gt;Repeat the transfer after checking the producer &lt;/h2&gt;&#10;&lt;p&gt;The recovery checked that packing had finished, repeated the transfer, and verified the archives at the destination. With server names and filenames generalized, run from the producer&amp;rsquo;s archive directory:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;rsync -rtv --partial --progress &lt;span class="se"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; SHA256SUMS guide.txt groups_*.tar.gz metadata.tar.gz &lt;span class="se"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; researcher@192.168.0.3:incoming/release-01/&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then, on the destination:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ~/incoming/release-01 &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; sha256sum -c SHA256SUMS&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The user supplied seventeen &lt;code&gt;OK&lt;/code&gt; results. Those checks verified the listed archive bytes, including the archives transferred earlier. They did not verify recipient permissions or the guide&amp;rsquo;s contents. A separate archive-content check also lacked &lt;code&gt;bc&lt;/code&gt;, so its aggregate group-count calculation did not succeed; that check must not be presented as another passing validation.&lt;/p&gt;&#10;&lt;p&gt;&lt;code&gt;--partial&lt;/code&gt; retains partial transfers. It does not by itself promise efficient offset-based resumption under every transfer configuration.&lt;/p&gt;&#10;&#10;&lt;h2 id="add-a-release-complete-barrier"&gt;Add a release-complete barrier &lt;/h2&gt;&#10;&lt;p&gt;We did not implement a stronger publication protocol during this recovery. For a future run, use a fresh release directory that consumers cannot mistake for an earlier finished release. Publish a completion marker only after validating the expected archive set and sidecars. Consumers should require that marker before enumeration and check hashes after transfer.&lt;/p&gt;&#10;&lt;p&gt;A marker needs an immutable release behind it. Reusing a directory with an old marker can announce incomplete new contents as ready; writing the marker early merely moves the race.&lt;/p&gt;&#10;&lt;p&gt;Before sending a transfer command, check the producer&amp;rsquo;s completion evidence and the expected file set. Per-file renames protect readers of each file, but consumers still need a defined point at which the collection is ready.&lt;/p&gt;&#10;</description></item><item><title>APFS sparse bundle on SMB: rsync "Operation not supported (45)" and F_FULLFSYNC</title><link>https://eghuro.cz/posts/apfs-sparse-bundle-smb-fullfsync/</link><pubDate>Tue, 29 Sep 2026 12:55:00 +0200</pubDate><guid>https://eghuro.cz/posts/apfs-sparse-bundle-smb-fullfsync/</guid><description>&lt;p&gt;A backup job that rsyncs into encrypted disk images on a NAS started printing this for every file (paths elided):&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;rsync: [generator] failed to set times on …&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;rsync: [receiver] mkstemp …&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;rsync: [generator] delete_file: unlink … failed: Operation not supported (45)&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then &lt;code&gt;hdiutil attach&lt;/code&gt; for the next image hung for over ten minutes, and the two images whose rsyncs had finished stayed mounted.&lt;/p&gt;&#10;&lt;p&gt;If you keep APFS sparse bundles on an SMB share and see error 45 inside them, the files aren&amp;rsquo;t the problem. The share can&amp;rsquo;t do &lt;code&gt;F_FULLFSYNC&lt;/code&gt;, and APFS can&amp;rsquo;t commit a single transaction without it.&lt;/p&gt;&#10;&#10;&lt;h2 id="the-setup"&gt;The setup &lt;/h2&gt;&#10;&lt;p&gt;macOS 27.0.1 dropped AFP, so an old NAS that speaks only SMB 2.1 got mounted over SMB instead. Each backup source has its own AES-256 sparse bundle on the share, created like this:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;hdiutil create -encryption AES-256 -type SPARSEBUNDLE -size 50g &lt;span class="se"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -fs APFS -volname app -stdinpass /Volumes/backup/staging/app.sparsebundle&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The job attaches a bundle, rsyncs a server&amp;rsquo;s files into it, and detaches it.&lt;/p&gt;&#10;&#10;&lt;h2 id="the-kernel-log"&gt;The kernel log &lt;/h2&gt;&#10;&lt;p&gt;rsync names the files, so the obvious suspects are permissions or the files themselves. The kernel log points at the image instead (timestamps shortened, process column removed):&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;/usr/bin/log show --last 90m --style compact &lt;span class="se"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --predicate &lt;span class="s1"&gt;&amp;#39;process == &amp;#34;kernel&amp;#34; AND eventMessage CONTAINS &amp;#34;apfs&amp;#34;&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;12:43:53.531 disk7s1 mount-complete volume app, requested by: mount_apfs (pid 82535); parent: mount (pid 82534)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;12:43:53.532 disk7s1 Host is remote&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;...&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;12:47:45.393 (apfs) tx_flush_thread:1678: disk7 tx_flush(8311) failed, 45, # 297&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;12:47:45.931 (apfs) tx_flush_thread:1678: disk7 tx_flush(8311) failed, 45, # 298&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;APFS noticed the image lives on a remote host. From 24 seconds after mounting, it failed to flush a transaction about twice a second, with error 45 (&lt;code&gt;ENOTSUP&lt;/code&gt;). The second mounted image did the same. Nothing written inside them was being committed.&lt;/p&gt;&#10;&#10;&lt;h2 id="the-probe"&gt;The probe &lt;/h2&gt;&#10;&lt;p&gt;Error 45 from a flush suggested the sync call, not the write. This script tries plain &lt;code&gt;fsync&lt;/code&gt; and the two stronger syncs macOS offers. Run it from a directory on the share:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;fcntl&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;_synctest&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;fd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;O_RDWR&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;O_CREAT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mo"&gt;0o600&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;x&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;4096&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;op&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;fsync&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;F_FULLFSYNC&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;51&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;F_BARRIERFSYNC&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;85&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;op&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;fcntl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fcntl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;OK&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="ne"&gt;OSError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;-&amp;gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;unlink&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;fsync OK&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;F_FULLFSYNC -&amp;gt; [Errno 45] Operation not supported&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;F_BARRIERFSYNC -&amp;gt; [Errno 45] Operation not supported&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;APFS asks the disk image to flush; the disk image passes that on as &lt;code&gt;F_FULLFSYNC&lt;/code&gt; on its band files on the share, and the SMB client says no.&lt;/p&gt;&#10;&lt;p&gt;The NAS also had a share called &lt;code&gt;TimeMachineBackup&lt;/code&gt;, and Time Machine over SMB needs full sync, so it seemed worth a try. Same result. Its Time Machine backups dated from the AFP days.&lt;/p&gt;&#10;&#10;&lt;h2 id="what-works"&gt;What works &lt;/h2&gt;&#10;&lt;p&gt;Keep the image off the share. The bundles moved to a USB drive, and every healthy one attaches, writes, deletes, and detaches fine there. That&amp;rsquo;s where the job runs now.&lt;/p&gt;&#10;&lt;p&gt;If the image has to stay on the share, use HFS+ inside it. A test bundle on the same share, created with &lt;code&gt;-fs HFS+J&lt;/code&gt; instead of &lt;code&gt;-fs APFS&lt;/code&gt;, passed everything the job does: rsync with &lt;code&gt;--delete-during&lt;/code&gt;, rename, setting times, deletes, detach and reattach.&lt;/p&gt;&#10;&lt;p&gt;If the server runs Samba and you control it, &lt;code&gt;fruit:time machine = yes&lt;/code&gt; on the share makes vfs_fruit advertise the FULLSYNC volume capability to macOS clients. That should let APFS images commit, but it wasn&amp;rsquo;t tested here.&lt;/p&gt;&#10;&#10;&lt;h2 id="check-the-images-you-already-have"&gt;Check the images you already have &lt;/h2&gt;&#10;&lt;p&gt;The failed commits also left damage. When the bundles were copied to the USB drive, one of them wouldn&amp;rsquo;t mount:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;hdiutil: attach failed - no mountable file systems&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;A read-only check of its volume (&lt;code&gt;fsck_apfs -n&lt;/code&gt;) said why:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;warning: (oid 0x74e6) om: btn: invalid o_cksum (0xdfbd1ccee26c9006)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;** The volume /dev/rdisk8s1 with UUID … was found to be corrupt and cannot be repaired.&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Its band files had last been modified during the failed SMB run, and the original on the NAS was identical, so the copy wasn&amp;rsquo;t to blame. Since it was only a staging mirror, it got recreated empty, and the next run filled it again. After any such episode, attach each image and run &lt;code&gt;diskutil verifyVolume&lt;/code&gt; on it.&lt;/p&gt;&#10;&#10;&lt;h2 id="why-restic-didnt-mind"&gt;Why restic didn&amp;rsquo;t mind &lt;/h2&gt;&#10;&lt;p&gt;Restic repositories on the same share were unaffected. Restic is written in Go, and Go&amp;rsquo;s &lt;code&gt;Fsync&lt;/code&gt; on macOS tries &lt;code&gt;F_FULLFSYNC&lt;/code&gt; and falls back to plain &lt;code&gt;fsync&lt;/code&gt; on &lt;code&gt;ENOTSUP&lt;/code&gt;. The &lt;a&#10; href="https://github.com/golang/go/blob/master/src/internal/poll/fd_fsync_darwin.go"&#10; &#10; target="_blank" rel="noopener"&#10; &#10; &#10;&gt;comment in the Go source&lt;/a&gt;&#10; names this exact case: &amp;ldquo;There are scenarios such as SMB mounts where fcntl will fail with ENOTSUP.&amp;rdquo; APFS inside a disk image has no such fallback: when the full flush fails, it stops committing.&lt;/p&gt;&#10;&#10;&lt;h2 id="takeaway"&gt;Takeaway &lt;/h2&gt;&#10;&lt;p&gt;An APFS disk image on an SMB share works only if the share supports &lt;code&gt;F_FULLFSYNC&lt;/code&gt;. Without it, every write inside the image fails with error 45, and the failed commits can corrupt the image.&lt;/p&gt;&#10;&lt;p&gt;If you&amp;rsquo;re moving sparse bundles from AFP to SMB, run the probe above on the new share before you mount anything on it.&lt;/p&gt;&#10;</description></item></channel></rss>