blob: aa0df45c9d6f5ca53c728ec26141d10e1f1357b0 [file] [log] [blame]
Damien Martin-Guillerezf88f4d82015-09-25 13:56:55 +00001// Copyright 2014 The Bazel Authors. All rights reserved.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01002//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14package com.google.devtools.build.lib.skyframe;
15
tomlua155b532017-11-08 20:12:47 +010016import com.google.common.base.Preconditions;
Janak Ramakrishnandf0531f2015-09-23 17:30:04 +000017import com.google.common.base.Predicate;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010018import com.google.common.collect.ImmutableList;
Nathan Harmataad810502015-07-29 01:33:49 +000019import com.google.common.collect.Iterables;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010020import com.google.common.collect.Sets;
ulfjack4abd6c32017-12-21 10:52:16 -080021import com.google.devtools.build.lib.actions.FileStateType;
shahan602cc852018-06-06 20:09:57 -070022import com.google.devtools.build.lib.actions.FileStateValue;
23import com.google.devtools.build.lib.actions.FileValue;
lberki612e0cf2022-05-09 01:54:44 -070024import com.google.devtools.build.lib.analysis.BlazeDirectories;
janakre2af68f2021-03-18 15:11:30 -070025import com.google.devtools.build.lib.io.FileSymlinkCycleException;
26import com.google.devtools.build.lib.io.FileSymlinkCycleUniquenessFunction;
27import com.google.devtools.build.lib.io.FileSymlinkException;
28import com.google.devtools.build.lib.io.FileSymlinkInfiniteExpansionException;
29import com.google.devtools.build.lib.io.FileSymlinkInfiniteExpansionUniquenessFunction;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010030import com.google.devtools.build.lib.pkgcache.PathPackageLocator;
31import com.google.devtools.build.lib.util.Pair;
32import com.google.devtools.build.lib.vfs.Path;
33import com.google.devtools.build.lib.vfs.PathFragment;
lberki612e0cf2022-05-09 01:54:44 -070034import com.google.devtools.build.lib.vfs.Root;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010035import com.google.devtools.build.lib.vfs.RootedPath;
36import com.google.devtools.build.skyframe.SkyFunction;
37import com.google.devtools.build.skyframe.SkyFunctionException;
38import com.google.devtools.build.skyframe.SkyFunctionException.Transience;
39import com.google.devtools.build.skyframe.SkyKey;
Googler54e24df2016-03-28 19:11:39 +000040import java.io.IOException;
Nathan Harmataad810502015-07-29 01:33:49 +000041import java.util.ArrayList;
42import java.util.TreeSet;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010043import java.util.concurrent.atomic.AtomicReference;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010044import javax.annotation.Nullable;
45
46/**
47 * A {@link SkyFunction} for {@link FileValue}s.
48 *
nharmata4ec695c2019-02-19 08:30:22 -080049 * <p>Most of the complexity in the implementation results from wanting incremental correctness in
50 * the presence of symlinks, esp. ancestor directory symlinks.
Googler52e55512024-07-08 12:02:52 -070051 *
52 * <p>For an overview of the problem space and our approach, see the https://youtu.be/EoYdWmMcqDs
53 * talk from BazelCon 2019 (slides:
Googler3a467b22024-07-09 11:59:57 -070054 * https://docs.google.com/presentation/d/e/2PACX-1vQWq1DUhl92dDs_okNxM7Qy9zX72tp7hMsGosGxmjhBLZ5e02IJf9dySK_6lEU2j6u_NOEaUCQGxEFh/pub).
Googlerff0bc4b2024-09-26 09:24:53 -070055 * [2024] N.B. The general idea of that talk is still right, but as of cl/334982640 aka commit
56 * 7598bc6 on GitHub (Oct 2020), we no longer unconditionally error out when encountering an
57 * unbounded ancestor expansion and instead leave it to consumers to decide what to do. A consumer
58 * that wants to do a recursive directory traversal starting from the path will probably want to
59 * error out, while a consumer that just wants metadata from the path probably doesn't care.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010060 */
61public class FileFunction implements SkyFunction {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010062 private final AtomicReference<PathPackageLocator> pkgLocator;
lberki612e0cf2022-05-09 01:54:44 -070063 private final ImmutableList<Root> immutablePaths;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010064
lberki612e0cf2022-05-09 01:54:44 -070065 public FileFunction(
66 AtomicReference<PathPackageLocator> pkgLocator, BlazeDirectories directories) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010067 this.pkgLocator = pkgLocator;
lberki612e0cf2022-05-09 01:54:44 -070068 this.immutablePaths =
69 ImmutableList.of(
70 Root.fromPath(directories.getOutputBase()),
71 Root.fromPath(directories.getInstallBase()));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010072 }
73
lberki7598bc62020-10-02 01:33:14 -070074 private static class SymlinkResolutionState {
nharmata4ec695c2019-02-19 08:30:22 -080075 // Suppose we have a path p. One of the goals of FileFunction is to resolve the "real path", if
76 // any, of p. The basic algorithm is to use the fully resolved path of p's parent directory to
77 // determine the fully resolved path of p. This is complicated when symlinks are involved, and
78 // is especially complicated when ancestor directory symlinks are involved.
79 //
80 // Since FileStateValues are the roots of invalidation, care has to be taken to ensuring we
81 // declare the proper FileStateValue deps. As a concrete example, let p = a/b and imagine (i) a
82 // is a direct symlink to c and also (ii) c/b is an existing file. Among other direct deps, we
83 // want to have a direct dep on FileStateValue(c/b), since that's the node that will be changed
84 // if the actual contents of a/b (aka c/b) changes. To rephrase: a dep on FileStateValue(a/b)
85 // won't do anything productive since that path will never be in the Skyframe diff.
86 //
87 // In the course of resolving the real path of p, there will be a logical chain of paths we
88 // consider. Going with the example from above, the full chain of paths we consider is
89 // [a/b, c/b].
Googlerc5377c12023-03-15 10:00:58 -070090 final ArrayList<RootedPath> logicalChain = new ArrayList<>();
91 // Same contents as 'logicalChain', except stored as a sorted TreeSet for efficiency reasons.
nharmata4ec695c2019-02-19 08:30:22 -080092 // See the usage in checkPathSeenDuringPartialResolutionInternal.
Googlerc5377c12023-03-15 10:00:58 -070093 final TreeSet<Path> sortedLogicalChain = Sets.newTreeSet();
nharmata4ec695c2019-02-19 08:30:22 -080094
lberki7598bc62020-10-02 01:33:14 -070095 ImmutableList<RootedPath> pathToUnboundedAncestorSymlinkExpansionChain = null;
96 ImmutableList<RootedPath> unboundedAncestorSymlinkExpansionChain = null;
97
98 private SymlinkResolutionState() {}
99 }
100
Googler5cc598c2022-07-06 03:29:45 -0700101 @Nullable
lberki7598bc62020-10-02 01:33:14 -0700102 @Override
103 public FileValue compute(SkyKey skyKey, Environment env)
104 throws FileFunctionException, InterruptedException {
105 RootedPath rootedPath = (RootedPath) skyKey.argument();
106 SymlinkResolutionState symlinkResolutionState = new SymlinkResolutionState();
107
nharmata4ec695c2019-02-19 08:30:22 -0800108 // Fully resolve the path of the parent directory, but only if the current file is not the
109 // filesystem root (has no parent) or a package path root (treated opaquely and handled by
110 // skyframe's DiffAwareness interface).
111 //
112 // This entails resolving ancestor symlinks fully. Note that this is the first thing we do - if
113 // an ancestor is part of a symlink cycle, we want to detect that quickly as it gives a more
114 // informative error message than we'd get doing bogus filesystem operations.
115 PartialResolutionResult resolveFromAncestorsResult =
lberki7598bc62020-10-02 01:33:14 -0700116 resolveFromAncestors(rootedPath, symlinkResolutionState, env);
nharmata4ec695c2019-02-19 08:30:22 -0800117 if (resolveFromAncestorsResult == null) {
118 return null;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100119 }
nharmata4ec695c2019-02-19 08:30:22 -0800120 RootedPath rootedPathFromAncestors = resolveFromAncestorsResult.rootedPath;
121 FileStateValue fileStateValueFromAncestors = resolveFromAncestorsResult.fileStateValue;
122 if (fileStateValueFromAncestors.getType() == FileStateType.NONEXISTENT) {
123 return FileValue.value(
lberki7598bc62020-10-02 01:33:14 -0700124 ImmutableList.copyOf(symlinkResolutionState.logicalChain),
125 symlinkResolutionState.pathToUnboundedAncestorSymlinkExpansionChain,
126 symlinkResolutionState.unboundedAncestorSymlinkExpansionChain,
nharmata4ec695c2019-02-19 08:30:22 -0800127 rootedPath,
128 FileStateValue.NONEXISTENT_FILE_STATE_NODE,
129 rootedPathFromAncestors,
130 fileStateValueFromAncestors);
131 }
132
133 RootedPath realRootedPath = rootedPathFromAncestors;
134 FileStateValue realFileStateValue = fileStateValueFromAncestors;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100135
ulfjack4abd6c32017-12-21 10:52:16 -0800136 while (realFileStateValue.getType().isSymlink()) {
nharmata4ec695c2019-02-19 08:30:22 -0800137 PartialResolutionResult getSymlinkTargetRootedPathResult =
138 getSymlinkTargetRootedPath(
lberki7598bc62020-10-02 01:33:14 -0700139 realRootedPath, realFileStateValue.getSymlinkTarget(), symlinkResolutionState, env);
nharmata4ec695c2019-02-19 08:30:22 -0800140 if (getSymlinkTargetRootedPathResult == null) {
Kristina Chodorowf9fdc8d2015-12-08 12:49:31 +0000141 return null;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100142 }
nharmata4ec695c2019-02-19 08:30:22 -0800143 realRootedPath = getSymlinkTargetRootedPathResult.rootedPath;
144 realFileStateValue = getSymlinkTargetRootedPathResult.fileStateValue;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100145 }
nharmata4ec695c2019-02-19 08:30:22 -0800146
147 return FileValue.value(
lberki7598bc62020-10-02 01:33:14 -0700148 ImmutableList.copyOf(symlinkResolutionState.logicalChain),
149 symlinkResolutionState.pathToUnboundedAncestorSymlinkExpansionChain,
150 symlinkResolutionState.unboundedAncestorSymlinkExpansionChain,
nharmata4ec695c2019-02-19 08:30:22 -0800151 rootedPath,
nharmatab07cd062019-02-19 09:52:50 -0800152 fileStateValueFromAncestors,
nharmata4ec695c2019-02-19 08:30:22 -0800153 realRootedPath,
154 realFileStateValue);
155 }
156
Googlerc5377c12023-03-15 10:00:58 -0700157 private static RootedPath getChild(
158 RootedPath parent, String baseName, RootedPath originalParent, RootedPath originalChild) {
159 if (parent.equals(originalParent)) {
160 return originalChild; // Avoid constructing a new instance if we already have the child.
161 }
nharmata4ec695c2019-02-19 08:30:22 -0800162 return RootedPath.toRootedPath(
Googlerc5377c12023-03-15 10:00:58 -0700163 parent.getRoot(), parent.getRootRelativePath().getChild(baseName));
nharmata4ec695c2019-02-19 08:30:22 -0800164 }
165
166 private RootedPath toRootedPath(Path path) {
lberki612e0cf2022-05-09 01:54:44 -0700167 // We check whether the path to be transformed is under the output base or the install base.
168 // These directories are under the control of Bazel and it therefore does not make much sense
169 // to check for changes in them or in their ancestors in the usual Skyframe way.
170 return RootedPath.toRootedPathMaybeUnderRoot(
171 path, Iterables.concat(pkgLocator.get().getPathEntries(), immutablePaths));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100172 }
173
174 /**
175 * Returns the path and file state of {@code rootedPath}, accounting for ancestor symlinks, or
176 * {@code null} if there was a missing dep.
177 */
178 @Nullable
jhorvitz85146042021-04-29 10:12:10 -0700179 private static PartialResolutionResult resolveFromAncestors(
lberki7598bc62020-10-02 01:33:14 -0700180 RootedPath rootedPath, SymlinkResolutionState symlinkResolutionState, Environment env)
nharmata4ec695c2019-02-19 08:30:22 -0800181 throws InterruptedException, FileFunctionException {
ajurkowski75172892019-11-04 09:35:40 -0800182 RootedPath parentRootedPath = rootedPath.getParentDirectory();
183 return parentRootedPath != null
lberki7598bc62020-10-02 01:33:14 -0700184 ? resolveFromAncestorsWithParent(rootedPath, parentRootedPath, symlinkResolutionState, env)
185 : resolveFromAncestorsNoParent(rootedPath, symlinkResolutionState, env);
nharmata4ec695c2019-02-19 08:30:22 -0800186 }
Googler54e24df2016-03-28 19:11:39 +0000187
nharmata4ec695c2019-02-19 08:30:22 -0800188 @Nullable
jhorvitz85146042021-04-29 10:12:10 -0700189 private static PartialResolutionResult resolveFromAncestorsWithParent(
nharmata4ec695c2019-02-19 08:30:22 -0800190 RootedPath rootedPath,
ajurkowski75172892019-11-04 09:35:40 -0800191 RootedPath parentRootedPath,
lberki7598bc62020-10-02 01:33:14 -0700192 SymlinkResolutionState symlinkResolutionState,
nharmata4ec695c2019-02-19 08:30:22 -0800193 Environment env)
194 throws InterruptedException, FileFunctionException {
195 PathFragment relativePath = rootedPath.getRootRelativePath();
nharmata4ec695c2019-02-19 08:30:22 -0800196 String baseName = relativePath.getBaseName();
nharmata4ec695c2019-02-19 08:30:22 -0800197
198 FileValue parentFileValue = (FileValue) env.getValue(FileValue.key(parentRootedPath));
199 if (parentFileValue == null) {
200 return null;
201 }
Googlerc5377c12023-03-15 10:00:58 -0700202
203 RootedPath rootedPathFromAncestors =
Googlerbf9a3d42024-07-24 18:52:40 -0700204 getChild(
205 parentFileValue.realRootedPath(parentRootedPath),
206 baseName,
207 parentRootedPath,
208 rootedPath);
nharmata4ec695c2019-02-19 08:30:22 -0800209
210 if (!parentFileValue.exists() || !parentFileValue.isDirectory()) {
nharmata4ec695c2019-02-19 08:30:22 -0800211 return new PartialResolutionResult(
212 rootedPathFromAncestors, FileStateValue.NONEXISTENT_FILE_STATE_NODE);
213 }
214
Googlerbf9a3d42024-07-24 18:52:40 -0700215 for (RootedPath parentPartialRootedPath :
216 parentFileValue.logicalChainDuringResolution(parentRootedPath)) {
nharmata4ec695c2019-02-19 08:30:22 -0800217 checkAndNotePathSeenDuringPartialResolution(
Googlerc5377c12023-03-15 10:00:58 -0700218 getChild(parentPartialRootedPath, baseName, parentRootedPath, rootedPath),
219 symlinkResolutionState,
220 env);
nharmata4ec695c2019-02-19 08:30:22 -0800221 if (env.valuesMissing()) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100222 return null;
223 }
nharmata4ec695c2019-02-19 08:30:22 -0800224 }
Googler54e24df2016-03-28 19:11:39 +0000225
nharmata4ec695c2019-02-19 08:30:22 -0800226 FileStateValue fileStateValueFromAncestors =
227 (FileStateValue) env.getValue(FileStateValue.key(rootedPathFromAncestors));
228 if (fileStateValueFromAncestors == null) {
229 return null;
230 }
231
232 return new PartialResolutionResult(rootedPathFromAncestors, fileStateValueFromAncestors);
233 }
234
235 @Nullable
jhorvitz85146042021-04-29 10:12:10 -0700236 private static PartialResolutionResult resolveFromAncestorsNoParent(
lberki7598bc62020-10-02 01:33:14 -0700237 RootedPath rootedPath, SymlinkResolutionState symlinkResolutionState, Environment env)
nharmata4ec695c2019-02-19 08:30:22 -0800238 throws InterruptedException, FileFunctionException {
lberki7598bc62020-10-02 01:33:14 -0700239 checkAndNotePathSeenDuringPartialResolution(rootedPath, symlinkResolutionState, env);
nharmata4ec695c2019-02-19 08:30:22 -0800240 if (env.valuesMissing()) {
241 return null;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100242 }
243 FileStateValue realFileStateValue =
nharmata4ec695c2019-02-19 08:30:22 -0800244 (FileStateValue) env.getValue(FileStateValue.key(rootedPath));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100245 if (realFileStateValue == null) {
246 return null;
247 }
nharmata4ec695c2019-02-19 08:30:22 -0800248 return new PartialResolutionResult(rootedPath, realFileStateValue);
249 }
250
251 private static final class PartialResolutionResult {
252 private final RootedPath rootedPath;
253 private final FileStateValue fileStateValue;
254
255 private PartialResolutionResult(RootedPath rootedPath, FileStateValue fileStateValue) {
256 this.rootedPath = rootedPath;
257 this.fileStateValue = fileStateValue;
258 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100259 }
260
261 /**
Janak Ramakrishnan3c0adb22016-08-15 21:54:55 +0000262 * Returns the symlink target and file state of {@code rootedPath}'s symlink to {@code
263 * symlinkTarget}, accounting for ancestor symlinks, or {@code null} if there was a missing dep.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100264 */
265 @Nullable
nharmata4ec695c2019-02-19 08:30:22 -0800266 private PartialResolutionResult getSymlinkTargetRootedPath(
Janak Ramakrishnan3c0adb22016-08-15 21:54:55 +0000267 RootedPath rootedPath,
268 PathFragment symlinkTarget,
lberki7598bc62020-10-02 01:33:14 -0700269 SymlinkResolutionState symlinkResolutionState,
Janak Ramakrishnan3c0adb22016-08-15 21:54:55 +0000270 Environment env)
271 throws FileFunctionException, InterruptedException {
nharmata4ec695c2019-02-19 08:30:22 -0800272 Path path = rootedPath.asPath();
273 Path symlinkTargetPath;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100274 if (symlinkTarget.isAbsolute()) {
nharmata4ec695c2019-02-19 08:30:22 -0800275 symlinkTargetPath = path.getRelative(symlinkTarget);
Nathan Harmataad810502015-07-29 01:33:49 +0000276 } else {
nharmata4ec695c2019-02-19 08:30:22 -0800277 Path parentPath = path.getParentDirectory();
278 symlinkTargetPath =
279 parentPath != null
280 ? parentPath.getRelative(symlinkTarget)
281 : path.getRelative(symlinkTarget);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100282 }
nharmata4ec695c2019-02-19 08:30:22 -0800283 RootedPath symlinkTargetRootedPath = toRootedPath(symlinkTargetPath);
lberki7598bc62020-10-02 01:33:14 -0700284 checkPathSeenDuringPartialResolution(symlinkTargetRootedPath, symlinkResolutionState, env);
nharmata4ec695c2019-02-19 08:30:22 -0800285 if (env.valuesMissing()) {
286 return null;
287 }
288 // The symlink target could have a different parent directory, which itself could be a directory
289 // symlink (or have an ancestor directory symlink)!
lberki7598bc62020-10-02 01:33:14 -0700290 return resolveFromAncestors(symlinkTargetRootedPath, symlinkResolutionState, env);
nharmata4ec695c2019-02-19 08:30:22 -0800291 }
292
jhorvitz85146042021-04-29 10:12:10 -0700293 private static void checkAndNotePathSeenDuringPartialResolution(
lberki7598bc62020-10-02 01:33:14 -0700294 RootedPath rootedPath, SymlinkResolutionState symlinkResolutionState, Environment env)
nharmata4ec695c2019-02-19 08:30:22 -0800295 throws FileFunctionException, InterruptedException {
296 Path path = rootedPath.asPath();
lberki7598bc62020-10-02 01:33:14 -0700297 checkPathSeenDuringPartialResolutionInternal(rootedPath, path, symlinkResolutionState, env);
298 symlinkResolutionState.sortedLogicalChain.add(path);
299 symlinkResolutionState.logicalChain.add(rootedPath);
nharmata4ec695c2019-02-19 08:30:22 -0800300 }
301
jhorvitz85146042021-04-29 10:12:10 -0700302 private static void checkPathSeenDuringPartialResolution(
lberki7598bc62020-10-02 01:33:14 -0700303 RootedPath rootedPath, SymlinkResolutionState symlinkResolutionState, Environment env)
nharmata4ec695c2019-02-19 08:30:22 -0800304 throws FileFunctionException, InterruptedException {
305 checkPathSeenDuringPartialResolutionInternal(
lberki7598bc62020-10-02 01:33:14 -0700306 rootedPath, rootedPath.asPath(), symlinkResolutionState, env);
nharmata4ec695c2019-02-19 08:30:22 -0800307 }
308
jhorvitz85146042021-04-29 10:12:10 -0700309 private static void checkPathSeenDuringPartialResolutionInternal(
nharmata4ec695c2019-02-19 08:30:22 -0800310 RootedPath rootedPath,
311 Path path,
lberki7598bc62020-10-02 01:33:14 -0700312 SymlinkResolutionState symlinkResolutionState,
nharmata4ec695c2019-02-19 08:30:22 -0800313 Environment env)
314 throws FileFunctionException, InterruptedException {
315 // We are about to perform another step of partial real path resolution. 'logicalChain' is the
316 // chain of paths we've considered so far, and 'rootedPath' / 'path' is the proposed next path
317 // we consider.
Shreya Bhattaraif87a4142015-10-09 19:48:11 +0000318 //
Googlerff0bc4b2024-09-26 09:24:53 -0700319 // There are three interesting cases to consider, all stemming from symlinks:
nharmata4ec695c2019-02-19 08:30:22 -0800320 // (i) Symlink cycle:
321 // p -> p1 -> p2 -> p1
Googlerff0bc4b2024-09-26 09:24:53 -0700322 // This means `p` has no real path, so we error out.
nharmata4ec695c2019-02-19 08:30:22 -0800323 // (ii) Unbounded expansion caused by a symlink to a descendant of a member of the chain:
324 // p -> a/b -> c/d -> a/b/e
Googlerff0bc4b2024-09-26 09:24:53 -0700325 // This means `p` has no real path, so we error out.
nharmata4ec695c2019-02-19 08:30:22 -0800326 // (iii) Unbounded expansion caused by a symlink to an ancestor of a member of the chain:
327 // p -> a/b -> c/d -> a
Googlerff0bc4b2024-09-26 09:24:53 -0700328 // This is not necessarily a problem (the real path of `p` in this example is simply `a`),
329 // so we just note the unbounded ancestor expansion and let consumers decide what to do.
Nathan Harmata808217c2015-10-12 22:07:19 +0000330 //
nharmata4ba404f2019-08-23 15:39:44 -0700331 // We can detect all three of these symlink issues via inspection of the proposed new element.
332 // Here is our incremental algorithm:
nharmata4ec695c2019-02-19 08:30:22 -0800333 // If 'path' is in 'sortedLogicalChain' then we have a found a cycle (i).
334 // If 'path' is a descendant of any path p in 'sortedLogicalChain' then we have unbounded
335 // expansion (ii).
336 // If 'path' is an ancestor of any path p in 'sortedLogicalChain' then we have unbounded
337 // expansion (iii).
Nathan Harmata808217c2015-10-12 22:07:19 +0000338 // We can check for these cases efficiently (read: sublinear time) by finding the extremal
nharmata4ec695c2019-02-19 08:30:22 -0800339 // candidate p for (ii) and (iii).
Nathan Harmata808217c2015-10-12 22:07:19 +0000340 SkyKey uniquenessKey = null;
341 FileSymlinkException fse = null;
lberki7598bc62020-10-02 01:33:14 -0700342 Path seenFloorPath = symlinkResolutionState.sortedLogicalChain.floor(path);
343 Path seenCeilingPath = symlinkResolutionState.sortedLogicalChain.ceiling(path);
344 if (symlinkResolutionState.sortedLogicalChain.contains(path)) {
nharmata4ec695c2019-02-19 08:30:22 -0800345 // 'rootedPath' is [transitively] a symlink to a previous element in the symlink chain (i).
Nathan Harmata808217c2015-10-12 22:07:19 +0000346 Pair<ImmutableList<RootedPath>, ImmutableList<RootedPath>> pathAndChain =
lberki7598bc62020-10-02 01:33:14 -0700347 CycleUtils.splitIntoPathAndChain(
348 isPathPredicate(path), symlinkResolutionState.logicalChain);
Nathan Harmata808217c2015-10-12 22:07:19 +0000349 FileSymlinkCycleException fsce =
350 new FileSymlinkCycleException(pathAndChain.getFirst(), pathAndChain.getSecond());
351 uniquenessKey = FileSymlinkCycleUniquenessFunction.key(fsce.getCycle());
352 fse = fsce;
nharmata4ec695c2019-02-19 08:30:22 -0800353 } else if (seenFloorPath != null && path.startsWith(seenFloorPath)) {
354 // 'rootedPath' is [transitively] a symlink to a descendant of a previous element in the
355 // symlink chain (ii).
Nathan Harmata808217c2015-10-12 22:07:19 +0000356 Pair<ImmutableList<RootedPath>, ImmutableList<RootedPath>> pathAndChain =
357 CycleUtils.splitIntoPathAndChain(
358 isPathPredicate(seenFloorPath),
lberki7598bc62020-10-02 01:33:14 -0700359 ImmutableList.copyOf(
360 Iterables.concat(
361 symlinkResolutionState.logicalChain, ImmutableList.of(rootedPath))));
Nathan Harmata808217c2015-10-12 22:07:19 +0000362 uniquenessKey = FileSymlinkInfiniteExpansionUniquenessFunction.key(pathAndChain.getSecond());
jhorvitz85146042021-04-29 10:12:10 -0700363 fse =
364 new FileSymlinkInfiniteExpansionException(
365 pathAndChain.getFirst(), pathAndChain.getSecond());
nharmata4ec695c2019-02-19 08:30:22 -0800366 } else if (seenCeilingPath != null && seenCeilingPath.startsWith(path)) {
367 // 'rootedPath' is [transitively] a symlink to an ancestor of a previous element in the
368 // symlink chain (iii).
lberki7598bc62020-10-02 01:33:14 -0700369 if (symlinkResolutionState.unboundedAncestorSymlinkExpansionChain == null) {
370 Pair<ImmutableList<RootedPath>, ImmutableList<RootedPath>> pathAndChain =
371 CycleUtils.splitIntoPathAndChain(
372 isPathPredicate(seenCeilingPath),
373 ImmutableList.copyOf(
374 Iterables.concat(
375 symlinkResolutionState.logicalChain, ImmutableList.of(rootedPath))));
376 symlinkResolutionState.pathToUnboundedAncestorSymlinkExpansionChain =
377 pathAndChain.getFirst();
378 symlinkResolutionState.unboundedAncestorSymlinkExpansionChain = pathAndChain.getSecond();
379 }
Nathan Harmata808217c2015-10-12 22:07:19 +0000380 }
381 if (uniquenessKey != null) {
nharmata4ec695c2019-02-19 08:30:22 -0800382 // Note that this dependency is merely to ensure that each unique symlink error gets
383 // reported exactly once.
384 env.getValue(uniquenessKey);
385 if (env.valuesMissing()) {
386 return;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100387 }
ulfjacke4794532018-01-12 02:11:17 -0800388 throw new FileFunctionException(
389 Preconditions.checkNotNull(fse, rootedPath), Transience.PERSISTENT);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100390 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100391 }
392
jhorvitz85146042021-04-29 10:12:10 -0700393 private static Predicate<RootedPath> isPathPredicate(Path path) {
394 return rootedPath -> rootedPath.asPath().equals(path);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100395 }
396
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100397 /**
jhorvitz85146042021-04-29 10:12:10 -0700398 * Used to declare all the exception types that can be wrapped in the exception thrown by {@link
399 * FileFunction#compute}.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100400 */
401 private static final class FileFunctionException extends SkyFunctionException {
jhorvitz85146042021-04-29 10:12:10 -0700402 FileFunctionException(IOException e, Transience transience) {
Googler54e24df2016-03-28 19:11:39 +0000403 super(e, transience);
404 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100405 }
406}