Repository navigation
Conversation
…rough one no-clobber ladder Moves, staged copy publication and Trash restores now go through one placement routine: renameat2(NOREPLACE), then a checked hard link for files or a rename over a fresh empty placeholder for folders, then a recreated link for symlinks, and a copy only across devices. EINVAL and EPERM on a user's item are resolved by a probe on Strata's own names, so an invalid name is reported as such instead of turning a same-volume move into a copy, and nothing is unlinked without identity proof. What each mount supports is cached per mount id and dropped on mount changes. Stages are created and discarded relative to their pinned parent off the main thread, symlink sources are staged as finished links and symlink copies are always exclusive. The removable flush reads the mount table off the main thread. Tests emulate filesystem quirks through scoped seams instead of the rename closures and the cross-volume flag. Refs #1530, #1590
… the main thread Extraction publishes staged roots through the shared placement ladder, so folders move whole over a placeholder where RENAME_NOREPLACE is missing, and the per-entry migration from #1586 is removed. A file on a mount with neither NOREPLACE nor hard links stays in staging with the new "can't guarantee" reason. Compression stages, encodes, chmods and publishes in one blocking step. Keep Both numbers names through the ladder. Replace picks Trash, EXCHANGE or set-aside before encoding and refuses up front when none is safe; an original removed without Trash is deleted only after the removable flush and records no undo entry. Refs #1530
…urce on same-filesystem moves Replace now plans in one blocking step: it pins both parents, stats both items, and picks a strategy before anything is copied. A replacing move within one filesystem renames the source onto the target through the ladder. Other replaces copy to a stage, then move the original out of the way by one of three routes: Trash, EXCHANGE (only on allowlisted filesystem types, checked fresh with fstatfs), or an identity-checked set-aside with put-back. When none of these is safe, the replace is refused with R1 before staging. Originals that were exchanged or set aside are deleted only after the removable flush. An item that replaced its original without Trash no longer reports a created location, so undoing the paste never deletes the only remaining copy. The other items in the paste still undo normally.
Where Trash is unavailable, a merge moves each original it overwrites into one hidden .strata-merge-* folder inside the merge target, through the no-clobber ladder, and deletes that folder by identity only after the merge succeeded and removable media were flushed. A failed aside or a cancel puts every original back first; a failed copy puts back what it can and names the folder that keeps the rest. Set-aside originals are left out of the merge undo, so undo never deletes the only copy. The merge takes fat_family from paste instead of reading the mount table on the main thread, and native Trash staging runs on a worker.
…nly renames F2 and undo rename of native items now run rename_in_place in one blocking step. A name that only differs in case on a case-insensitive mount is renamed through a hidden .strata-rename-* name, moved back on failure, and reported with the temporary name when that fails too. Where a probe on Strata's own names confirms the mount has neither RENAME_NOREPLACE nor hard links, a file is renamed over an exclusive empty file reserved at the new name and a folder after checking the name is free, so renames keep working there. Undo rename never copies: remote items use a GIO move without the copy fallback. The typed-rename prompt checks the name off the main thread and only treats it as taken when another item holds it.
GLib 2.86 to 2.88.4 can return FALSE without an error and leave the item in place, and gio-rs then reports success. Native items are now trashed in one blocking step through g_file_trash directly: FALSE without an error, or the same item still at its name afterwards, fails as NotSupported with a new reason, so deletion offers Delete permanently, Replace, Merge and compression fall back to their aside paths, and no undo is recorded for an item that never moved. Every trash site uses the shared helper, and the main-thread identity lstat before trashing is gone.
…nd bound retries Review fixes for the shared placement ladder: - place() with an expected identity now checks the entry after every rung, including a plain renameat2(NOREPLACE), and moves a substituted item back (Changed) instead of placing it. - A link refused for one item while the mount supports links (protected_hardlinks on NFS and similar) is Unsupported, not EPERM, so moves fall back to copying again. Nothing is learned from it. - An ambiguous mkdir error is Exists only when something is at the name, and the placeholder check reports I/O errors instead of Exists. Numbered extraction and archive names stop after 10,000 attempts. - The F2/undo last rung now requires no_replace=false and hard_links=false for folders too, as the commit that added it states. Known values come from the capability cache; only unknown ones are probed, once. - Probes skip the rename test once it is known and test links for files in the same pass; a folder move skips the ".." walk once NOREPLACE is known missing, and fd stats use AT_STATX_DONT_SYNC through the seam. - Staged publication tries rung 1 before any stat. - Compression records the original's identity before encoding and refuses to replace an item swapped in meanwhile; it shares the displaced-original removal and the EXCHANGE-rejected fallback with paste replace. - Move failures keep the item name in pasted and undo messages. - Hidden-name prefixes live in one place, classified by whether they may hold user data; duplicated blocking and test helpers are shared.
A parallel file copy that failed or was cancelled after creating its hidden stage dropped it without cleanup, leaving a partial .strata-replacement-* file in the destination. The stage now goes through the identity-checked discard on every failure. A new file stage takes its identity from the created fd instead of a second path stat. Tests cover the cancelled copy and the "staging copy could not be removed" report after the exclusive re-copy rung.
Restore and archive publication no longer require RENAME_NOREPLACE, and archive Replace no longer overwrites. The unsafe-code inventory lists the direct g_file_trash call that works around GLib #4060.
"Could not stage %{path}: %{error}" lost its only user when symlink copies
stopped staging; it is removed from all eleven catalogs.
Replace the eight-argument merge wrapper with a hooks builder and name the probe test's case tuple.
…ithout NOREPLACE On a mount without RENAME_NOREPLACE where hard links work but one file refuses them (EPERM from linkat under fs.protected_hardlinks, or EMLINK), F2 and undo rename now use the same last rung as mounts without links: reserve the new name with an exclusive empty file, then rename over it. Folders still need links confirmed missing, and moves keep their copy fallback. The item's refusal is never learned for the mount.
SMB with POSIX extensions fails open(O_CREAT | O_EXCL) over an existing folder with EISDIR instead of EEXIST. New-file numbering (GIO's IsDirectory), the hidden-name loops for probes, compression and replace stages, and the rename reservation now try the next name instead of failing. EISDIR from mkdir or any other call is still an error.
The runner still never inherits TMPDIR. An absolute STRATA_TEST_TMPDIR places the fixtures created with tempfile on another filesystem, which the real-filesystem lab uses; a relative value is refused before anything starts.
scripts/fslab/fslab.sh runs one privileged, network-less Docker container that mounts kernel NFS v3/v4.2, Samba over cifs (default, nocase, posix), vfat, exFAT, ntfs3, ntfs-3g, exfat-fuse and casefolded ext4 over loopback, then runs syscall probes or the scoped Rust tests with their fixtures on each mount. CI never runs it.
Record the no-clobber guarantees, the placement ladder, per-mount capabilities, Replace/Merge/Rename/Trash behaviour, hidden names, a filesystem capability table, and how to test with fault injection and the real-filesystem lab.
ext4, ntfs3, NFS and CIFS hand a freed inode number straight to the next item created, so a (device, inode) identity let a folder that replaced a file stage pass as the stage and be deleted recursively, and let an archive re-saved during encoding be replaced. LocalFileIdentity now also carries the file type and the statx birth time; birth times are compared only when both sides have one. The delete walk, restore cleanup, archive publication and placement proofs read identities through the fs_ops seam, so every check fails closed on a type change. File stages are no longer pre-created and copied over with G_FILE_COPY_OVERWRITE. The copy creates the stage exclusively at a fresh hidden name, retrying the next name when it is taken, and the stage's exact identity is recorded afterwards. A failed copy's partial stage is removed only when it is a regular file with one name. This drops the relaxation that accepted any regular file at a stage's name, and GIO's temporary file, rename and fsync per staged file. The fs_quirks seam gains reused inode numbers and missing birth times, which reproduce the fslab failures on tmpfs.
Renaming "report" to "Report" was refused as "already exists" on both. On SMB the client keeps the positive lookup of "Report" from the alias check for about a second, so the second renameat2(NOREPLACE) failed with EEXIST. The new spelling is now revalidated with AT_STATX_FORCE_SYNC before the second step, which costs one syscall on the case-only path. exfat-fuse (libfuse without use_ino) gives each spelling its own inode number, so the alias was never recognised. When the identities differ, the new spelling still counts as the item itself when the folder lists no such name, the source is the only name equal to it ignoring case, and type, size and modification time agree. The fs_quirks seam emulates both, and the case-only rename test runs each shape.
tar 0.4.46 treats a file that reports zero allocated blocks but a non-zero size as entirely sparse and never reads it. ntfs3 keeps small files inside the MFT record and reports st_blocks 0, so TAR and TAR.GZ archives made from them held zeros: the fslab run on ntfs3 failed every_compression_format_commits_a_readable_archive and three other archive tests, and strace showed the source opened and seeked but never read. ext4 inline data and FUSE filesystems can report the same. Sparse detection is now off for archives Strata writes, so members are always read; sparse files are stored in full.
publication_without_rename_flags_moves_unreadable_subtrees_whole left ro/sub at 0555, so its temporary directory and the empty extraction stage were never removed. It now records the mode it checks, then restores write access before asserting.
…bers The archive swap test told the recreated archive from the original by the temporary filesystem's real birth time, after a 20 ms sleep. It failed on filesystems without birth times, such as exfat-fuse in the lab. Names under `reused_inodes` now report a fake birth time that changes only when a test marks a new item there with `recreated`, so the test needs neither the real birth time nor the sleep, and still fails if identities ignore birth times.
copy_recursively_local read each source file's size with fstat and checked every folder of a staged copy with g_file_query_exists, both on the GTK main thread. A folder stage was copied with OVERWRITE only so that its root, made beforehand, would be skipped, which also probed every folder inside it. A recursive copy now says what it meets at the target: nothing, a fresh folder made beforehand, or entries a merge copies over. A fresh stage's root is used without a probe and its children are created without OVERWRITE. The source size comes from the fstat the worker already makes when it opens the file, and a merge checks for existing folders with an asynchronous query. Merge keeps its flags.
Once the cifs client turns server inode numbers off, each name of a hard-linked file reports its own inode number, so the capability probe could not match its link to the file it created and left .strata-probe-* files behind. The probe now also removes an empty probe file whose inode number changed when its device, type and birth time still match the file it created, since cifs keeps reporting the server's creation time. A file that neither its inode number nor its birth time proves is kept. The fs_quirks seam emulates per-name inode numbers for hard links, with the creation time all names of a file share.
GIO's make_directory_async reports a cancel even when its worker already made the folder, so a copy cancelled in that instant never recorded the folder as its own and left it behind instead of removing it. This made cancelling_staged_fallback_removes_its_reserved_directory fail now and then on slow mounts, where the test saw the folder before the result arrived. Copy folders are now made with the synchronous call on a worker, whose result stands. The test skips the placement placeholder and pauses between main loop passes, so the cancel lands in that window every time.
The container bind-mounted the live scripts/fslab at /lab, so a rebase that recreated the directory left it running "/lab//deleted" and `down` could no longer start its teardown. Every fslab.sh command now copies the scripts to /opt/fslab in the container first, and every docker exec runs from /, which no lab unmount can remove.
One busy mount stopped `down` before it detached any loop device, which left every idle lab device attached. `down` now unmounts and detaches everything that is free, with the same backing-file and exclusive-open checks, deletes the freed images, and keeps the container, the busy mount, its server and its loop device record for the next `down`.
After full runs the kernel kept a reference to the NFSv4.2 export once nfsd had stopped, pinning its loop device and memory on the host until reboot. Each export is now its own container-private tmpfs, so a leaked reference pins only that small tmpfs. NFSv4.2 still passes on tmpfs birth times.
… gone A created item removed before the undo must still be reported as removed, so a view that lists it drops it.
The test feeds monitor changes to a folder its source never watched. Since a restore now reloads open folders without a monitor, the source watches the folder, as a real monitor would.
Since operation changes stopped reloading every column, the finish of a foreground operation set file_operation_refreshed whenever any watched column rescanned. A cancel then reported no affected folders, so open folders without a monitor were never reloaded, and a completed deletion or restore skipped removing what left those folders. Both now behave as they do without a rescan; watched folders still reconcile through their rescan alone.
…arge removal A foreground deletion or restore of more than 64 items reloaded an open Recent or camera view at once, and that view reloaded again after the progress closed whenever its monitor had asked for a rescan, as those monitors do for every change. The bulk removal now leaves a rescanning view to the reload that follows the operation.
The reload after a cancel also reloaded every open folder without a monitor that is an ancestor of an affected path, and checked this by walking each affected path's ancestors for each open column, which took hundreds of milliseconds on the main thread after a large cancel. Every provider already reports the parents of what it changed, so only folders at or below an affected path reload now.
A background cancel, failure or transfer failure reloaded a remote destination before its outcome and again when the view or the failure handling reloaded it. A transfer failure also reloaded watched folders, twice when they had rescanned, and a foreground failure reloaded nothing although a paste can fail after placing items. Failures now reload the open folders without a monitor at or below what the operation touched, and a background cancel leaves its reload to the view, as a foreground one does.
… completes A completed transfer, rename, creation or compression reloaded only the remote folders it changed, so a local folder without a monitor kept listing an item moved out of it, and a foreground compression reloaded nothing. These endings now reload every open folder without a monitor that the operation changed and leave watched folders to their monitor. A destination that the transfer then reveals is reloaded once by the reveal instead, which now also reloads it when it already listed the items, so a replaced item no longer keeps its old details. The rename undo tests list a watched folder now, as their monitor updates assume.
…peration ends An extraction, or a compression whose result no longer had a pending destination, reloaded the active folder when it ended, even when that folder was watched and an open folder without a monitor that received the result was not the active one. Its result is now selected through the folder's monitor, and only the folder that received it reloads when it has no monitor. A cancelled or empty archive operation reloads nothing here; the cancel reload covers what it changed.
Applying a completed deletion or restore matched every removed location against every open column, rebuilding each location's parent on the way. A large removal now groups its locations by folder once and looks up each column's group; Recent and camera views still filter the whole set.
…flushes Every caller of flush_deferred_file_operation_changes passed false for prefer_refresh, and nothing has read its result since operation endings stopped tracking whether a column reloaded. The parameter, its reload-everything branch, the result and the test that only exercised that branch go.
Keep a dismissed asynchronous rename from submitting a reopened prompt with the same target and text. Refs #1590.
Keep originals intact and stages unpublished after delayed close errors or cancellation during finalization. Refs #1590.
Initialize capability invalidation independently of folder preference lookups without reading a mount snapshot on this path. Refs #1590.
|
Historical lab measurements moved out of maintained documentation during review. These are the PR author’s recorded measurements, not results from this review’s validation. Lab measurementsFlushes counted on the backing loop device for one call after writing 16 KiB, over three rounds.
On vfat, exFAT and both FUSE daemons Moving 1,000 files of 4 KiB with one flush per item cost 1,000 flushes and 16.6 s on vfat, against Copying a folder of 2,000 files of 1 KiB with eight workers, in seconds:
vfat waits 100 ms at each close of a written file, which bounds every copier there. The lab's SFTP |
Keep compatibility and safety rationale while removing field labels, control-flow summaries and test narration. Name independently copied files directly. Refs #1590.
Move historical measurements to the PR, state remaining UI-thread limitations and document checked descriptor close. Refs #1590.
Extract progress, replacement/merge, deletion and restore orchestration. Share native descriptors, names and publication primitives; preserve distinct safety and durability policies. Consolidate syscall injection and repeated test setup without removing distinct behavioral coverage. Refs #1590.
Stage selected symlinks and named pipes before publication. Keep sources and decline destructive undo when a fallback cannot prove ownership; never apply pipe metadata to a concurrent regular file. Refs #1590.
Keep worker-only filesystem assertions intact when GTK tests construct archive fixtures. Refs #1590.
Rescan ambiguous monitor batches instead of allowing asynchronous move metadata to remove a newly recreated entry. Refs #1590.
The existing rename-visibility fixture could lose its setup double-click after F5, reproduced on main. Use focused keyboard activation for setup; retain pointer activation coverage in test_click_modes and all existing rename assertions.
|
Review fixes are pushed through
Manual verification: on a private display/session bus, cancelling and reopening a typed rename leaves the original intact until explicit confirmation; confirmation preserves contents. Copying a folder containing a regular file and a socket copies the file and reports the skipped socket in one completion warning. Visual evidence remains pending upload. Current screenshots were captured and inspected, but the available GitHub CLI cannot upload attachments through the editor. They are retained locally for the owner to attach; no local links are being presented as uploaded evidence. Local validation and scopeAll builds and GUI checks used the verified pinned image, task-isolated rootless Podman, private Xvfb and private D-Bus.
This is not a merge-ready declaration: the PR is still draft, substantive CI is skipped by the draft conditions in |
|
Found 1 test failure on Blacksmith runners: Failure
|
Description
Strata's no-clobber file operations relied on
renameat2flags (RENAME_NOREPLACE,RENAME_EXCHANGE), and native copies went through GIO. Many mounts reject those flags withEINVAL, among them NFS, sshfs, ntfs-3g and other libfuse2 daemons, 9p, ceph and older ZFS.user.*xattrs on read-only items;This PR makes these operations work on such mounts. It never overwrites a destination that appeared meanwhile, and never deletes a source the copy doesn't hold. It is the single PR for umbrella #1590: each step lands here as its own group of commits, and it is merged once. The table further down compares main and this PR, filesystem by filesystem.
Step 1: placement, Replace/Merge, rename, Trash
One placement ladder for every site: move, staged copy publish, restore, archive extract/compress publish, symlink copy, undo and rename. It tries these in order:
renameat2(NOREPLACE).linkat. The link counts only once the new name reports the source's identity; the old name is then unlinked after an identity check.NOREPLACEnor usable hard links (sshfs, vboxsf, rclone, s3fs), an exclusive empty placeholder that is checked and then renamed over.EEXISTstays authoritative, and the ladder tells apart the three meanings ofEINVAL: flag unsupported, invalid name, and folder into itself.The reserved-name step has a narrow window.
docs/filesystem-compatibility.md.sshfs and other libfuse daemons without
use_inogive each hard link its own inode number. Strata learns this from a probe on its own names and never uses those links to prove a placement.Capabilities are learned per mount and cached by mount ID. The cache is dropped when the mount table changes.
Replace and Merge work without Trash or
EXCHANGE.EXCHANGEis used only on in-kernel local filesystems, never FUSE. exfat-fuse reports success for it but simply overwrites, which on main destroyed the original when Trash was unavailable.Rename (F2) and undo rename go through the ladder. Case-only renames (
foo→Foo) work on vfat, exFAT, SMB, casefold ext4 and exfat-fuse. GVfs locations keep GIO.Trash results are verified. Some FUSE mounts report success without moving anything (GLib #4060).
Identity checks compare file type and birth time as well as device and inode, because ext4, NFS, CIFS and ntfs3 reuse inode numbers immediately.
Archives:
Step 2: the copy engine
One descriptor-based engine replaces GIO wherever both ends are native paths:
It runs on worker threads, opens everything beneath pinned folders, and never replaces an entry. GVfs locations keep GIO.
Only complete copies appear.
O_TMPFILEand linked to its name when done..strata-copy-*stage, which is safe to delete, and published through the step 1 ladder.Data:
FICLONE, then chunkedcopy_file_range, then read/write. Networkcopy_file_rangeis gated on the kernel releases with the NFS, FUSE and SMB fixes, and NFS requests are capped at 2 MiB.Metadata:
user.*xattrs survive on read-only items.Names:
Makefileandmakefileon FAT, exFAT, SMB or casefold) are saved asmakefile (1)and reported. Undoing the move gives the name back.Hard links stay linked within one copied item where the destination keeps them.
Special files: named pipes are recreated, while sockets and devices are left out and reported.
Merges never overwrite.
Moves that copy delete a source entry only if it still matches what was copied:
Durability: nothing that only the copy holds is deleted before the destination is flushed. That covers move sources, Replace originals, merge set-asides, archive originals and restored Trash copies.
syncfs; vfat and exFAT also need a folderfsync, and SMB and other FUSE mounts sync each file.Trash restore copies through the engine where nothing can place the item.
.trashinfoand is reported.Undo of a merge or Replace keeps later edits.
.trashinfo, and the dialog says so.Completion report: one event carries renamed, left-out and left-behind items.
Responsiveness:
Docs and lab:
docs/filesystem-compatibility.mdgains a "Copying" section, the residual limits, and the lab's flush and claim measurements.scripts/fslabnow counts device cache flushes per mount.Speed on the lab (2,000 × 1 KiB, debug build): vfat with
flush, 26 s against GIO's 208 s; sshfs, 2–12 s against about 100 s; the rest on par. On exfat-fuse and ntfs-3g, a 300-file move went from about 300 device flushes to 2.Decisions taken with the owner during step 2:
Decided by the lead along the way:
cp --sparse=auto;Still to come on this PR:
The checklist in #1590 tracks them.
Known limits:
NOREPLACEis enforced only on the client.closetimeo, 1 s by default). Strata retries forclosetimeoplus 0.5 s, at most 10 s. A writer that keeps the file open still makes the copy fail with "Could not copy".EIO,ETIMEDOUT), an empty placeholder can stay behind, and the item is reported as unconfirmed rather than guessed. This happens on NFS and sshfs.posix-rename@openssh.com: nothing can be renamed over a placeholder..strata-copy-*stage left by a crash or an unplug stays until it is deleted (step 8).Edge cases by filesystem
Every filesystem edge case this PR handles, against
mainat the merge base (e7e43f2a). Rows under Any filesystem apply everywhere; the groups below list only what differs.Any filesystem
RENAME_NOREPLACEis rejected (#1530)EINVALfrom a renameNOREPLACEis missing), then the source deletedNOREPLACE) and merges wrote at real namesO_TMPFILElinked when done on local POSIX filesystems, otherwise a hidden.strata-copy-*stage.strata-replacement-*stage, or a half-written file under its final name.strata-copy-*stage, documented as safe to deleteNOREPLACE), even for a move within one filesystemRENAME_EXCHANGE, the original deleted at once; elsewhere failed after the full copyOVERWRITEinto the live folderrename(): an item created in between is replacedNOREPLACEis missing.trashinforemoveduser.*on 0444); named pipes refused; Trash copy deleted unflushed.trashinfo, reported. A cancel mid-copy ends as a cancel with nothing partial leftNOREPLACEor a hard link per file, refused elsewhere; compression published on the UI threaduser.*xattrs on read-only itemsMakefile,makefile)makefile (1)and reported; undoing a move gives the name back\,:)EIO,ETIMEDOUTafter the step happened)copy_file_range, splice, read/writecopy_file_range(gated per network filesystem and kernel), then read/writeNFS (v3 and v4.x)
CREATE,MKDIR,LINKrepliesCOPY: about 3 s for any file over 2 MiBcopy_file_rangein chunks of at most 2 MiB, from Linux 5.9syncfs(commits to the server)SMB/cifs
EXCHANGE); Merge refusedcopy_file_rangeon any kernelcopy_file_rangeonly on kernels with the client's 2023 fixes (6.7, 6.6.7, 6.1.73)fdatasyncof each written file (an SMB FLUSH)closetimeo); the copy failedclosetimeoplus 0.5 s, at most 10 snoserverino, or server inode numbers turned offsshfs and other libfuse daemons without `use_ino`
posix-renamefdatasyncof each written file, thenfsyncof the folderexfat-fuse
EXCHANGEsilently overwrote: the original destroyedNOREPLACE, no links)syncfs, which flushed nothingsyncfsplus one folderfsyncper batch; otherwisefdatasyncper file plus the folderntfs-3g
syncfs, which flushed nothingsyncfsplus one folderfsyncper batch; otherwise per filentfs3
EXCHANGE)$LX*attributes$LX*never copied; restore copies passsyncfs, which flushes the devicevfat
EXCHANGE, the original deleted at onceEXCHANGE; the original deleted only after the flushsyncfs, which flushed nothingsyncfsthen a folderfsync, which flushes the driveexFAT (kernel)
EXCHANGE)syncfs, which flushed nothingsyncfsthen a folderfsyncext4
fsyncper top-level fileO_TMPFILE, linked when complete: a crash leaves nothing; nofsyncfor plain copiesEXCHANGE, the original deleted at onceEXCHANGE; the original deleted only after the flushsyncfsCasefolded ext4
btrfs and XFS
syncfssubvol=mounts (no Trash)tmpfs
EXCHANGE, the original deleted at onceEXCHANGE; the original deleted after the flushZFS
overlayfs
redirect_dir)syncfsvboxsf, rclone, s3fs
fdatasyncper file plus the folder; vboxsf: per file plussyncfs9p, ceph
fdatasyncper file plussyncfsGVfs locations (`smb://`, `sftp://`, `mtp://`)
Visual evidence
Pending upload. Step 2 adds a completion report; step 1 changed no layout. The captures were taken on a private headless display and still need to be attached here:
Makefileandmakefileonto a case-insensitive folder fails with "An item with that name already exists".After: a notice says the destination can't tell these names apart and
makefilewas saved asmakefile (1).After: the pipe is recreated, and "Completed with errors" lists the two sockets as left out.
After: "3 items were left in their original folders". The reasons are a log written during the move, a socket, and a file in a read-only folder.
How to test
Step 1
scripts/fslab/fslab.sh upprovides both inside a container. On that mount, move a large file and a folder into another folder.Expected: the moves finish instantly without copying.
Expected: the item is swapped, with no "unexpected system error" and no second copy pass.
notes.txttoNotes.txtwith F2, then undo.Expected: both succeed.
Step 2
srcto another folder on the same disk, then to another disk.Expected:
rokeeps mode 555, its 2001 time anduser.note.aandbshare one inode wherever the destination keeps links.pipeis a FIFO.sparse.imgtakes almost no space on ext4, btrfs or tmpfs.sockas left out.Makefileandmakefileto a vfat or exFAT stick.Expected:
makefile (1)appears on the stick, with a notice. If you move the folder there instead and then undo, both names come back.while :; do date >> ~/t/live/log.txt; sleep 0.2; done, then move~/t/liveand a few other files to another disk.Expected: everything else moves.
log.txtstays, with a warning that it changed during the move.Expected: they copy in parallel, "Writing to device…" shows at the end, and every file arrives.
Expected: no partial or truncated item under a real name.
scripts/fslab/fslab.sh test local_operations::repeats the unit tests on every lab mount.Related issue
Closes #1530
Closes #1590