toolchains: don't make an unpreservable file mode a build failure - #1580
Open
akafael wants to merge 1 commit into
Open
toolchains: don't make an unpreservable file mode a build failure#1580akafael wants to merge 1 commit into
akafael wants to merge 1 commit into
Conversation
`replace_symlink` emits `cp -a` for every declared binary and library
output, and `symlink_to_dir` uses `cp -pR`. Both `-a` and `-p` set
coreutils' `require_preserve`, which promotes a failed attribute copy
from a warning to exit 1. When the destination filesystem refuses the
`chmod` that `--preserve=mode` performs, `cp` writes the file contents
correctly and then fails:
cp: preserving permissions for 'bazel-out/.../lib/libFoo.so': Operation not permitted
The generated wrapper runs under `set -euo pipefail`, so the action dies
with a correct artifact already on disk. This is reachable from any
CMake project that sets SOVERSION, since the versioned symlinks are
installed straight into the declared output directory.
Copy the data and restore the timestamp separately, which is what
`copy_dir_contents_to_dir` already does since bazel-contrib#583 ("Use touch not cp -p
to preserve timestamps"); these two call sites were missed. `cp -R`
still covers the symlink-to-directory case that `-a`'s implied `-d`
handled, and the source at the `replace_symlink` call site is already
resolved by `readlink -f`/`realpath`, so no-dereference is not relied
on.
No `touch -r` in `symlink_to_dir`: that branch is guarded by
`[[ -L "$source" && ! -d "$source" ]]` and is reachable only for
dangling symlinks and symlinks to non-regular files, where `touch -r`
would itself fail.
This is a strict narrowing of what is preserved, so it cannot break a
build that previously succeeded. The only behaviour lost is
mode/ownership/ACL propagation into `bazel-out`, which Bazel does not
guarantee.
Refs: bazel-contrib#1579
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #1579
What
replace_symlinkemitscp -afor every declared binary and library output, andsymlink_to_diruses
cp -pR. Both-aand-pset coreutils'require_preserve, which promotes a failedattribute copy from a warning to exit 1. When the destination filesystem refuses the
chmodthat--preserve=modeperforms,cpwrites the contents correctly and then fails:The generated wrapper runs under
set -euo pipefail, so the action dies with a correct artifactalready on disk.
This replaces both copies with
cp -Rplus a separatetouch -r, which is whatcopy_dir_contents_to_dirhas done since #583 ("Use touch not cp -p to preserve timestamps") —these two call sites were missed. Applied to all four toolchain command files (
linux,macos,freebsd,windows);test/expected/inner_fun_text{,_macos,_freebsd}.txtare updated to match.Two details worth calling out for review:
cp -R --preserve=timestamps. It fixes the reported error, but--preserve=stillsets
require_preserve, so it stays fatal in the adjacent case where the destination pre-existsowned by another uid (
cp: preserving times for '…': Operation not permitted), and BSDcponmacOS/FreeBSD has no
--preserve=at all.cp -R+touch -ris uniform across all fourtoolchains and matches the existing idiom.
touch -rinsymlink_to_dir. That branch is guarded by[[ -L "$source" && ! -d "$source" ]], reachable only for dangling symlinks and symlinks tonon-regular files, since a symlink to a real file already matched
-fabove.cp -Rrecreatesthe link exactly as
cp -pRdid, andtouch -rwould fail on a dangling link.-aalso implies-d; at thereplace_symlinkcall site the symlink has just been resolved withreadlink -f(realpathon macOS) and removed, so the source is a real file or directory,-Rcovers the directory case, and the no-dereference behaviour is not relied on.
This is a strict narrowing of what is preserved, so it cannot break a build that previously
succeeded. The only behaviour lost is mode/ownership/ACL propagation into
bazel-out, which Bazeldoes not guarantee in the first place.
Validating the mechanism (10 seconds, no cluster needed)
That
-p/-aare what make this fatal reproduces on any GNU coreutils. Copying onto adestination whose
chmodfails:Note a non-root uid alone is not sufficient — on ext4/overlayfs,
cp -afrom a root-ownedsource to a fresh destination as an unprivileged uid exits 0, because the failed
chownistolerated by
chown_failure_ok()and thechmodsucceeds on the filecpjust created. Thenecessary condition is a destination filesystem that rejects
chmod.Validating end to end with a local Buildbarn
bb-deployments gives a full cluster from Docker
Compose. Its FUSE worker materialises the action directory — including
bazel-out— onbb_worker's virtual filesystem rather than a plain local one, which is the interesting case.1. Start the cluster
2. Have the worker report what the action directory actually is
cp -a exit=1withcp -R exit=0is the failure this PR fixes.--remote_instance_name=hardlinkingselects the worker that uses a plain local directory instead,which is a useful contrast.
3. Reproduce through the rules
replace_symlinkfires whenever a declared output is a symlink, which is the normal result of aCMake project that sets
SOVERSION— the versioned links are installed straight into the declaredoutput directory:
Build it with the same remote flags, against this branch and against
main. Onmainthe actionfails at the
cp -ablock at the end ofbazel-bin/.../build_script.sh, afterCMake.logshowsthe install completing. The generated block can also be run standalone under
set -euo pipefailto isolate it from the rest of the build.
Testing
bazel test //test/...on Linux: 94/94 pass, including//test:shell_script_inner_fun_test,which diffs the generated script against the regenerated
test/expected/goldens.bb-deploymentscluster config(
worker-fuse-ubuntu22-04.jsonnet,runner-ubuntu22-04.jsonnet); I have not run the full cmakereproduction through it end to end, so treat step 3 as instructions rather than a recorded
result.
I could not find a way to assert this failure in CI without a second uid or a special filesystem,
so the change is covered by the existing golden and shellcheck tests rather than a new regression
test. Happy to add one if you have a preferred mechanism.