You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Found while implementing #373 (PR #375), and independent of it: the fix there is sound, and this
gap is older than it.
The gap
clean and distclean decide whether a recorded output is on disk with pup::platform::exists,
which is stat-based and follows symlinks (src/platform/file_io-posix.cpp, the exists
implementation). A dangling symlink — a generated symlink whose target no longer exists —
therefore reads as absent.
Two consequences, in the same direction:
The removal pass skips it (src/cli/cmd_clean.cpp, the if (!exists(abs_path_sv)) continue;
guard in remove_indexed_outputs), so the symlink stays on disk after a clean.
The two passes are at least consistent with each other — they use the same predicate, so the scan
never disagrees with the removal about what is present. That is why this does not undermine #375's
invariant in general: the record is still discarded only when the scan believes nothing survives.
The defect is that the belief is wrong for dangling symlinks.
Open question, and it should be answered first
Can a build actually record a symlink output? That was not determined. If no rule can produce
one, this is theoretical and the issue should be closed as such — the cost of the fix is not worth
paying for an unreachable state. If a rule can (a ln -s recipe, or a tool that emits symlinks),
the state is reachable by removing the target afterwards, which is ordinary enough.
If it is reachable
The fix is a presence predicate that does not follow symlinks — lstat-shaped rather than stat-shaped — used by both the removal pass and the survivor scan, so the two stay consistent.
That is a change to a platform primitive's contract (or a second primitive beside it), so it wants
a design-gate pass rather than a local patch: exists following symlinks is the right default for
most callers, and only the ownership paths want the other answer.
A dangling symlink is also the one case where "remove it" is unambiguously safe — there is no
target to lose.
Not urgent
No user has reported this. It is filed because it was found with evidence in hand, and because the
first question above is cheap to answer and decides whether anything else is worth doing.
Found while implementing #373 (PR #375), and independent of it: the fix there is sound, and this
gap is older than it.
The gap
cleananddistcleandecide whether a recorded output is on disk withpup::platform::exists,which is
stat-based and follows symlinks (src/platform/file_io-posix.cpp, theexistsimplementation). A dangling symlink — a generated symlink whose target no longer exists —
therefore reads as absent.
Two consequences, in the same direction:
src/cli/cmd_clean.cpp, theif (!exists(abs_path_sv)) continue;guard in
remove_indexed_outputs), so the symlink stays on disk after aclean.distcleancounts zerosurvivors and discards the record — leaving exactly the unattributable state Keep the build record when a file it owns survives the reset #375 exists to
prevent, for this one class of path.
The two passes are at least consistent with each other — they use the same predicate, so the scan
never disagrees with the removal about what is present. That is why this does not undermine #375's
invariant in general: the record is still discarded only when the scan believes nothing survives.
The defect is that the belief is wrong for dangling symlinks.
Open question, and it should be answered first
Can a build actually record a symlink output? That was not determined. If no rule can produce
one, this is theoretical and the issue should be closed as such — the cost of the fix is not worth
paying for an unreachable state. If a rule can (a
ln -srecipe, or a tool that emits symlinks),the state is reachable by removing the target afterwards, which is ordinary enough.
If it is reachable
The fix is a presence predicate that does not follow symlinks —
lstat-shaped rather thanstat-shaped — used by both the removal pass and the survivor scan, so the two stay consistent.That is a change to a platform primitive's contract (or a second primitive beside it), so it wants
a design-gate pass rather than a local patch:
existsfollowing symlinks is the right default formost callers, and only the ownership paths want the other answer.
A dangling symlink is also the one case where "remove it" is unambiguously safe — there is no
target to lose.
Not urgent
No user has reported this. It is filed because it was found with evidence in hand, and because the
first question above is cheap to answer and decides whether anything else is worth doing.