Damien Martin-Guillerez | f88f4d8 | 2015-09-25 13:56:55 +0000 | [diff] [blame] | 1 | // Copyright 2014 The Bazel Authors. All rights reserved. |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 2 | // |
| 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. |
| 14 | package com.google.devtools.build.lib.skyframe; |
| 15 | |
tomlu | a155b53 | 2017-11-08 20:12:47 +0100 | [diff] [blame] | 16 | import com.google.common.base.Preconditions; |
Janak Ramakrishnan | df0531f | 2015-09-23 17:30:04 +0000 | [diff] [blame] | 17 | import com.google.common.base.Predicate; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 18 | import com.google.common.collect.ImmutableList; |
Nathan Harmata | ad81050 | 2015-07-29 01:33:49 +0000 | [diff] [blame] | 19 | import com.google.common.collect.Iterables; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 20 | import com.google.common.collect.Sets; |
ulfjack | 4abd6c3 | 2017-12-21 10:52:16 -0800 | [diff] [blame] | 21 | import com.google.devtools.build.lib.actions.FileStateType; |
shahan | 602cc85 | 2018-06-06 20:09:57 -0700 | [diff] [blame] | 22 | import com.google.devtools.build.lib.actions.FileStateValue; |
| 23 | import com.google.devtools.build.lib.actions.FileValue; |
lberki | 612e0cf | 2022-05-09 01:54:44 -0700 | [diff] [blame] | 24 | import com.google.devtools.build.lib.analysis.BlazeDirectories; |
janakr | e2af68f | 2021-03-18 15:11:30 -0700 | [diff] [blame] | 25 | import com.google.devtools.build.lib.io.FileSymlinkCycleException; |
| 26 | import com.google.devtools.build.lib.io.FileSymlinkCycleUniquenessFunction; |
| 27 | import com.google.devtools.build.lib.io.FileSymlinkException; |
| 28 | import com.google.devtools.build.lib.io.FileSymlinkInfiniteExpansionException; |
| 29 | import com.google.devtools.build.lib.io.FileSymlinkInfiniteExpansionUniquenessFunction; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 30 | import com.google.devtools.build.lib.pkgcache.PathPackageLocator; |
| 31 | import com.google.devtools.build.lib.util.Pair; |
| 32 | import com.google.devtools.build.lib.vfs.Path; |
| 33 | import com.google.devtools.build.lib.vfs.PathFragment; |
lberki | 612e0cf | 2022-05-09 01:54:44 -0700 | [diff] [blame] | 34 | import com.google.devtools.build.lib.vfs.Root; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 35 | import com.google.devtools.build.lib.vfs.RootedPath; |
| 36 | import com.google.devtools.build.skyframe.SkyFunction; |
| 37 | import com.google.devtools.build.skyframe.SkyFunctionException; |
| 38 | import com.google.devtools.build.skyframe.SkyFunctionException.Transience; |
| 39 | import com.google.devtools.build.skyframe.SkyKey; |
Googler | 54e24df | 2016-03-28 19:11:39 +0000 | [diff] [blame] | 40 | import java.io.IOException; |
Nathan Harmata | ad81050 | 2015-07-29 01:33:49 +0000 | [diff] [blame] | 41 | import java.util.ArrayList; |
| 42 | import java.util.TreeSet; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 43 | import java.util.concurrent.atomic.AtomicReference; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 44 | import javax.annotation.Nullable; |
| 45 | |
| 46 | /** |
| 47 | * A {@link SkyFunction} for {@link FileValue}s. |
| 48 | * |
nharmata | 4ec695c | 2019-02-19 08:30:22 -0800 | [diff] [blame] | 49 | * <p>Most of the complexity in the implementation results from wanting incremental correctness in |
| 50 | * the presence of symlinks, esp. ancestor directory symlinks. |
Googler | 52e5551 | 2024-07-08 12:02:52 -0700 | [diff] [blame] | 51 | * |
| 52 | * <p>For an overview of the problem space and our approach, see the https://youtu.be/EoYdWmMcqDs |
| 53 | * talk from BazelCon 2019 (slides: |
Googler | 3a467b2 | 2024-07-09 11:59:57 -0700 | [diff] [blame] | 54 | * https://docs.google.com/presentation/d/e/2PACX-1vQWq1DUhl92dDs_okNxM7Qy9zX72tp7hMsGosGxmjhBLZ5e02IJf9dySK_6lEU2j6u_NOEaUCQGxEFh/pub). |
Googler | ff0bc4b | 2024-09-26 09:24:53 -0700 | [diff] [blame] | 55 | * [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 Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 60 | */ |
| 61 | public class FileFunction implements SkyFunction { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 62 | private final AtomicReference<PathPackageLocator> pkgLocator; |
lberki | 612e0cf | 2022-05-09 01:54:44 -0700 | [diff] [blame] | 63 | private final ImmutableList<Root> immutablePaths; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 64 | |
lberki | 612e0cf | 2022-05-09 01:54:44 -0700 | [diff] [blame] | 65 | public FileFunction( |
| 66 | AtomicReference<PathPackageLocator> pkgLocator, BlazeDirectories directories) { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 67 | this.pkgLocator = pkgLocator; |
lberki | 612e0cf | 2022-05-09 01:54:44 -0700 | [diff] [blame] | 68 | this.immutablePaths = |
| 69 | ImmutableList.of( |
| 70 | Root.fromPath(directories.getOutputBase()), |
| 71 | Root.fromPath(directories.getInstallBase())); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 72 | } |
| 73 | |
lberki | 7598bc6 | 2020-10-02 01:33:14 -0700 | [diff] [blame] | 74 | private static class SymlinkResolutionState { |
nharmata | 4ec695c | 2019-02-19 08:30:22 -0800 | [diff] [blame] | 75 | // 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]. |
Googler | c5377c1 | 2023-03-15 10:00:58 -0700 | [diff] [blame] | 90 | final ArrayList<RootedPath> logicalChain = new ArrayList<>(); |
| 91 | // Same contents as 'logicalChain', except stored as a sorted TreeSet for efficiency reasons. |
nharmata | 4ec695c | 2019-02-19 08:30:22 -0800 | [diff] [blame] | 92 | // See the usage in checkPathSeenDuringPartialResolutionInternal. |
Googler | c5377c1 | 2023-03-15 10:00:58 -0700 | [diff] [blame] | 93 | final TreeSet<Path> sortedLogicalChain = Sets.newTreeSet(); |
nharmata | 4ec695c | 2019-02-19 08:30:22 -0800 | [diff] [blame] | 94 | |
lberki | 7598bc6 | 2020-10-02 01:33:14 -0700 | [diff] [blame] | 95 | ImmutableList<RootedPath> pathToUnboundedAncestorSymlinkExpansionChain = null; |
| 96 | ImmutableList<RootedPath> unboundedAncestorSymlinkExpansionChain = null; |
| 97 | |
| 98 | private SymlinkResolutionState() {} |
| 99 | } |
| 100 | |
Googler | 5cc598c | 2022-07-06 03:29:45 -0700 | [diff] [blame] | 101 | @Nullable |
lberki | 7598bc6 | 2020-10-02 01:33:14 -0700 | [diff] [blame] | 102 | @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 | |
nharmata | 4ec695c | 2019-02-19 08:30:22 -0800 | [diff] [blame] | 108 | // 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 = |
lberki | 7598bc6 | 2020-10-02 01:33:14 -0700 | [diff] [blame] | 116 | resolveFromAncestors(rootedPath, symlinkResolutionState, env); |
nharmata | 4ec695c | 2019-02-19 08:30:22 -0800 | [diff] [blame] | 117 | if (resolveFromAncestorsResult == null) { |
| 118 | return null; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 119 | } |
nharmata | 4ec695c | 2019-02-19 08:30:22 -0800 | [diff] [blame] | 120 | RootedPath rootedPathFromAncestors = resolveFromAncestorsResult.rootedPath; |
| 121 | FileStateValue fileStateValueFromAncestors = resolveFromAncestorsResult.fileStateValue; |
| 122 | if (fileStateValueFromAncestors.getType() == FileStateType.NONEXISTENT) { |
| 123 | return FileValue.value( |
lberki | 7598bc6 | 2020-10-02 01:33:14 -0700 | [diff] [blame] | 124 | ImmutableList.copyOf(symlinkResolutionState.logicalChain), |
| 125 | symlinkResolutionState.pathToUnboundedAncestorSymlinkExpansionChain, |
| 126 | symlinkResolutionState.unboundedAncestorSymlinkExpansionChain, |
nharmata | 4ec695c | 2019-02-19 08:30:22 -0800 | [diff] [blame] | 127 | rootedPath, |
| 128 | FileStateValue.NONEXISTENT_FILE_STATE_NODE, |
| 129 | rootedPathFromAncestors, |
| 130 | fileStateValueFromAncestors); |
| 131 | } |
| 132 | |
| 133 | RootedPath realRootedPath = rootedPathFromAncestors; |
| 134 | FileStateValue realFileStateValue = fileStateValueFromAncestors; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 135 | |
ulfjack | 4abd6c3 | 2017-12-21 10:52:16 -0800 | [diff] [blame] | 136 | while (realFileStateValue.getType().isSymlink()) { |
nharmata | 4ec695c | 2019-02-19 08:30:22 -0800 | [diff] [blame] | 137 | PartialResolutionResult getSymlinkTargetRootedPathResult = |
| 138 | getSymlinkTargetRootedPath( |
lberki | 7598bc6 | 2020-10-02 01:33:14 -0700 | [diff] [blame] | 139 | realRootedPath, realFileStateValue.getSymlinkTarget(), symlinkResolutionState, env); |
nharmata | 4ec695c | 2019-02-19 08:30:22 -0800 | [diff] [blame] | 140 | if (getSymlinkTargetRootedPathResult == null) { |
Kristina Chodorow | f9fdc8d | 2015-12-08 12:49:31 +0000 | [diff] [blame] | 141 | return null; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 142 | } |
nharmata | 4ec695c | 2019-02-19 08:30:22 -0800 | [diff] [blame] | 143 | realRootedPath = getSymlinkTargetRootedPathResult.rootedPath; |
| 144 | realFileStateValue = getSymlinkTargetRootedPathResult.fileStateValue; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 145 | } |
nharmata | 4ec695c | 2019-02-19 08:30:22 -0800 | [diff] [blame] | 146 | |
| 147 | return FileValue.value( |
lberki | 7598bc6 | 2020-10-02 01:33:14 -0700 | [diff] [blame] | 148 | ImmutableList.copyOf(symlinkResolutionState.logicalChain), |
| 149 | symlinkResolutionState.pathToUnboundedAncestorSymlinkExpansionChain, |
| 150 | symlinkResolutionState.unboundedAncestorSymlinkExpansionChain, |
nharmata | 4ec695c | 2019-02-19 08:30:22 -0800 | [diff] [blame] | 151 | rootedPath, |
nharmata | b07cd06 | 2019-02-19 09:52:50 -0800 | [diff] [blame] | 152 | fileStateValueFromAncestors, |
nharmata | 4ec695c | 2019-02-19 08:30:22 -0800 | [diff] [blame] | 153 | realRootedPath, |
| 154 | realFileStateValue); |
| 155 | } |
| 156 | |
Googler | c5377c1 | 2023-03-15 10:00:58 -0700 | [diff] [blame] | 157 | 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 | } |
nharmata | 4ec695c | 2019-02-19 08:30:22 -0800 | [diff] [blame] | 162 | return RootedPath.toRootedPath( |
Googler | c5377c1 | 2023-03-15 10:00:58 -0700 | [diff] [blame] | 163 | parent.getRoot(), parent.getRootRelativePath().getChild(baseName)); |
nharmata | 4ec695c | 2019-02-19 08:30:22 -0800 | [diff] [blame] | 164 | } |
| 165 | |
| 166 | private RootedPath toRootedPath(Path path) { |
lberki | 612e0cf | 2022-05-09 01:54:44 -0700 | [diff] [blame] | 167 | // 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 Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 172 | } |
| 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 |
jhorvitz | 8514604 | 2021-04-29 10:12:10 -0700 | [diff] [blame] | 179 | private static PartialResolutionResult resolveFromAncestors( |
lberki | 7598bc6 | 2020-10-02 01:33:14 -0700 | [diff] [blame] | 180 | RootedPath rootedPath, SymlinkResolutionState symlinkResolutionState, Environment env) |
nharmata | 4ec695c | 2019-02-19 08:30:22 -0800 | [diff] [blame] | 181 | throws InterruptedException, FileFunctionException { |
ajurkowski | 7517289 | 2019-11-04 09:35:40 -0800 | [diff] [blame] | 182 | RootedPath parentRootedPath = rootedPath.getParentDirectory(); |
| 183 | return parentRootedPath != null |
lberki | 7598bc6 | 2020-10-02 01:33:14 -0700 | [diff] [blame] | 184 | ? resolveFromAncestorsWithParent(rootedPath, parentRootedPath, symlinkResolutionState, env) |
| 185 | : resolveFromAncestorsNoParent(rootedPath, symlinkResolutionState, env); |
nharmata | 4ec695c | 2019-02-19 08:30:22 -0800 | [diff] [blame] | 186 | } |
Googler | 54e24df | 2016-03-28 19:11:39 +0000 | [diff] [blame] | 187 | |
nharmata | 4ec695c | 2019-02-19 08:30:22 -0800 | [diff] [blame] | 188 | @Nullable |
jhorvitz | 8514604 | 2021-04-29 10:12:10 -0700 | [diff] [blame] | 189 | private static PartialResolutionResult resolveFromAncestorsWithParent( |
nharmata | 4ec695c | 2019-02-19 08:30:22 -0800 | [diff] [blame] | 190 | RootedPath rootedPath, |
ajurkowski | 7517289 | 2019-11-04 09:35:40 -0800 | [diff] [blame] | 191 | RootedPath parentRootedPath, |
lberki | 7598bc6 | 2020-10-02 01:33:14 -0700 | [diff] [blame] | 192 | SymlinkResolutionState symlinkResolutionState, |
nharmata | 4ec695c | 2019-02-19 08:30:22 -0800 | [diff] [blame] | 193 | Environment env) |
| 194 | throws InterruptedException, FileFunctionException { |
| 195 | PathFragment relativePath = rootedPath.getRootRelativePath(); |
nharmata | 4ec695c | 2019-02-19 08:30:22 -0800 | [diff] [blame] | 196 | String baseName = relativePath.getBaseName(); |
nharmata | 4ec695c | 2019-02-19 08:30:22 -0800 | [diff] [blame] | 197 | |
| 198 | FileValue parentFileValue = (FileValue) env.getValue(FileValue.key(parentRootedPath)); |
| 199 | if (parentFileValue == null) { |
| 200 | return null; |
| 201 | } |
Googler | c5377c1 | 2023-03-15 10:00:58 -0700 | [diff] [blame] | 202 | |
| 203 | RootedPath rootedPathFromAncestors = |
Googler | bf9a3d4 | 2024-07-24 18:52:40 -0700 | [diff] [blame] | 204 | getChild( |
| 205 | parentFileValue.realRootedPath(parentRootedPath), |
| 206 | baseName, |
| 207 | parentRootedPath, |
| 208 | rootedPath); |
nharmata | 4ec695c | 2019-02-19 08:30:22 -0800 | [diff] [blame] | 209 | |
| 210 | if (!parentFileValue.exists() || !parentFileValue.isDirectory()) { |
nharmata | 4ec695c | 2019-02-19 08:30:22 -0800 | [diff] [blame] | 211 | return new PartialResolutionResult( |
| 212 | rootedPathFromAncestors, FileStateValue.NONEXISTENT_FILE_STATE_NODE); |
| 213 | } |
| 214 | |
Googler | bf9a3d4 | 2024-07-24 18:52:40 -0700 | [diff] [blame] | 215 | for (RootedPath parentPartialRootedPath : |
| 216 | parentFileValue.logicalChainDuringResolution(parentRootedPath)) { |
nharmata | 4ec695c | 2019-02-19 08:30:22 -0800 | [diff] [blame] | 217 | checkAndNotePathSeenDuringPartialResolution( |
Googler | c5377c1 | 2023-03-15 10:00:58 -0700 | [diff] [blame] | 218 | getChild(parentPartialRootedPath, baseName, parentRootedPath, rootedPath), |
| 219 | symlinkResolutionState, |
| 220 | env); |
nharmata | 4ec695c | 2019-02-19 08:30:22 -0800 | [diff] [blame] | 221 | if (env.valuesMissing()) { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 222 | return null; |
| 223 | } |
nharmata | 4ec695c | 2019-02-19 08:30:22 -0800 | [diff] [blame] | 224 | } |
Googler | 54e24df | 2016-03-28 19:11:39 +0000 | [diff] [blame] | 225 | |
nharmata | 4ec695c | 2019-02-19 08:30:22 -0800 | [diff] [blame] | 226 | 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 |
jhorvitz | 8514604 | 2021-04-29 10:12:10 -0700 | [diff] [blame] | 236 | private static PartialResolutionResult resolveFromAncestorsNoParent( |
lberki | 7598bc6 | 2020-10-02 01:33:14 -0700 | [diff] [blame] | 237 | RootedPath rootedPath, SymlinkResolutionState symlinkResolutionState, Environment env) |
nharmata | 4ec695c | 2019-02-19 08:30:22 -0800 | [diff] [blame] | 238 | throws InterruptedException, FileFunctionException { |
lberki | 7598bc6 | 2020-10-02 01:33:14 -0700 | [diff] [blame] | 239 | checkAndNotePathSeenDuringPartialResolution(rootedPath, symlinkResolutionState, env); |
nharmata | 4ec695c | 2019-02-19 08:30:22 -0800 | [diff] [blame] | 240 | if (env.valuesMissing()) { |
| 241 | return null; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 242 | } |
| 243 | FileStateValue realFileStateValue = |
nharmata | 4ec695c | 2019-02-19 08:30:22 -0800 | [diff] [blame] | 244 | (FileStateValue) env.getValue(FileStateValue.key(rootedPath)); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 245 | if (realFileStateValue == null) { |
| 246 | return null; |
| 247 | } |
nharmata | 4ec695c | 2019-02-19 08:30:22 -0800 | [diff] [blame] | 248 | 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 Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 259 | } |
| 260 | |
| 261 | /** |
Janak Ramakrishnan | 3c0adb2 | 2016-08-15 21:54:55 +0000 | [diff] [blame] | 262 | * 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 Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 264 | */ |
| 265 | @Nullable |
nharmata | 4ec695c | 2019-02-19 08:30:22 -0800 | [diff] [blame] | 266 | private PartialResolutionResult getSymlinkTargetRootedPath( |
Janak Ramakrishnan | 3c0adb2 | 2016-08-15 21:54:55 +0000 | [diff] [blame] | 267 | RootedPath rootedPath, |
| 268 | PathFragment symlinkTarget, |
lberki | 7598bc6 | 2020-10-02 01:33:14 -0700 | [diff] [blame] | 269 | SymlinkResolutionState symlinkResolutionState, |
Janak Ramakrishnan | 3c0adb2 | 2016-08-15 21:54:55 +0000 | [diff] [blame] | 270 | Environment env) |
| 271 | throws FileFunctionException, InterruptedException { |
nharmata | 4ec695c | 2019-02-19 08:30:22 -0800 | [diff] [blame] | 272 | Path path = rootedPath.asPath(); |
| 273 | Path symlinkTargetPath; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 274 | if (symlinkTarget.isAbsolute()) { |
nharmata | 4ec695c | 2019-02-19 08:30:22 -0800 | [diff] [blame] | 275 | symlinkTargetPath = path.getRelative(symlinkTarget); |
Nathan Harmata | ad81050 | 2015-07-29 01:33:49 +0000 | [diff] [blame] | 276 | } else { |
nharmata | 4ec695c | 2019-02-19 08:30:22 -0800 | [diff] [blame] | 277 | Path parentPath = path.getParentDirectory(); |
| 278 | symlinkTargetPath = |
| 279 | parentPath != null |
| 280 | ? parentPath.getRelative(symlinkTarget) |
| 281 | : path.getRelative(symlinkTarget); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 282 | } |
nharmata | 4ec695c | 2019-02-19 08:30:22 -0800 | [diff] [blame] | 283 | RootedPath symlinkTargetRootedPath = toRootedPath(symlinkTargetPath); |
lberki | 7598bc6 | 2020-10-02 01:33:14 -0700 | [diff] [blame] | 284 | checkPathSeenDuringPartialResolution(symlinkTargetRootedPath, symlinkResolutionState, env); |
nharmata | 4ec695c | 2019-02-19 08:30:22 -0800 | [diff] [blame] | 285 | 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)! |
lberki | 7598bc6 | 2020-10-02 01:33:14 -0700 | [diff] [blame] | 290 | return resolveFromAncestors(symlinkTargetRootedPath, symlinkResolutionState, env); |
nharmata | 4ec695c | 2019-02-19 08:30:22 -0800 | [diff] [blame] | 291 | } |
| 292 | |
jhorvitz | 8514604 | 2021-04-29 10:12:10 -0700 | [diff] [blame] | 293 | private static void checkAndNotePathSeenDuringPartialResolution( |
lberki | 7598bc6 | 2020-10-02 01:33:14 -0700 | [diff] [blame] | 294 | RootedPath rootedPath, SymlinkResolutionState symlinkResolutionState, Environment env) |
nharmata | 4ec695c | 2019-02-19 08:30:22 -0800 | [diff] [blame] | 295 | throws FileFunctionException, InterruptedException { |
| 296 | Path path = rootedPath.asPath(); |
lberki | 7598bc6 | 2020-10-02 01:33:14 -0700 | [diff] [blame] | 297 | checkPathSeenDuringPartialResolutionInternal(rootedPath, path, symlinkResolutionState, env); |
| 298 | symlinkResolutionState.sortedLogicalChain.add(path); |
| 299 | symlinkResolutionState.logicalChain.add(rootedPath); |
nharmata | 4ec695c | 2019-02-19 08:30:22 -0800 | [diff] [blame] | 300 | } |
| 301 | |
jhorvitz | 8514604 | 2021-04-29 10:12:10 -0700 | [diff] [blame] | 302 | private static void checkPathSeenDuringPartialResolution( |
lberki | 7598bc6 | 2020-10-02 01:33:14 -0700 | [diff] [blame] | 303 | RootedPath rootedPath, SymlinkResolutionState symlinkResolutionState, Environment env) |
nharmata | 4ec695c | 2019-02-19 08:30:22 -0800 | [diff] [blame] | 304 | throws FileFunctionException, InterruptedException { |
| 305 | checkPathSeenDuringPartialResolutionInternal( |
lberki | 7598bc6 | 2020-10-02 01:33:14 -0700 | [diff] [blame] | 306 | rootedPath, rootedPath.asPath(), symlinkResolutionState, env); |
nharmata | 4ec695c | 2019-02-19 08:30:22 -0800 | [diff] [blame] | 307 | } |
| 308 | |
jhorvitz | 8514604 | 2021-04-29 10:12:10 -0700 | [diff] [blame] | 309 | private static void checkPathSeenDuringPartialResolutionInternal( |
nharmata | 4ec695c | 2019-02-19 08:30:22 -0800 | [diff] [blame] | 310 | RootedPath rootedPath, |
| 311 | Path path, |
lberki | 7598bc6 | 2020-10-02 01:33:14 -0700 | [diff] [blame] | 312 | SymlinkResolutionState symlinkResolutionState, |
nharmata | 4ec695c | 2019-02-19 08:30:22 -0800 | [diff] [blame] | 313 | 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 Bhattarai | f87a414 | 2015-10-09 19:48:11 +0000 | [diff] [blame] | 318 | // |
Googler | ff0bc4b | 2024-09-26 09:24:53 -0700 | [diff] [blame] | 319 | // There are three interesting cases to consider, all stemming from symlinks: |
nharmata | 4ec695c | 2019-02-19 08:30:22 -0800 | [diff] [blame] | 320 | // (i) Symlink cycle: |
| 321 | // p -> p1 -> p2 -> p1 |
Googler | ff0bc4b | 2024-09-26 09:24:53 -0700 | [diff] [blame] | 322 | // This means `p` has no real path, so we error out. |
nharmata | 4ec695c | 2019-02-19 08:30:22 -0800 | [diff] [blame] | 323 | // (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 |
Googler | ff0bc4b | 2024-09-26 09:24:53 -0700 | [diff] [blame] | 325 | // This means `p` has no real path, so we error out. |
nharmata | 4ec695c | 2019-02-19 08:30:22 -0800 | [diff] [blame] | 326 | // (iii) Unbounded expansion caused by a symlink to an ancestor of a member of the chain: |
| 327 | // p -> a/b -> c/d -> a |
Googler | ff0bc4b | 2024-09-26 09:24:53 -0700 | [diff] [blame] | 328 | // 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 Harmata | 808217c | 2015-10-12 22:07:19 +0000 | [diff] [blame] | 330 | // |
nharmata | 4ba404f | 2019-08-23 15:39:44 -0700 | [diff] [blame] | 331 | // We can detect all three of these symlink issues via inspection of the proposed new element. |
| 332 | // Here is our incremental algorithm: |
nharmata | 4ec695c | 2019-02-19 08:30:22 -0800 | [diff] [blame] | 333 | // 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 Harmata | 808217c | 2015-10-12 22:07:19 +0000 | [diff] [blame] | 338 | // We can check for these cases efficiently (read: sublinear time) by finding the extremal |
nharmata | 4ec695c | 2019-02-19 08:30:22 -0800 | [diff] [blame] | 339 | // candidate p for (ii) and (iii). |
Nathan Harmata | 808217c | 2015-10-12 22:07:19 +0000 | [diff] [blame] | 340 | SkyKey uniquenessKey = null; |
| 341 | FileSymlinkException fse = null; |
lberki | 7598bc6 | 2020-10-02 01:33:14 -0700 | [diff] [blame] | 342 | Path seenFloorPath = symlinkResolutionState.sortedLogicalChain.floor(path); |
| 343 | Path seenCeilingPath = symlinkResolutionState.sortedLogicalChain.ceiling(path); |
| 344 | if (symlinkResolutionState.sortedLogicalChain.contains(path)) { |
nharmata | 4ec695c | 2019-02-19 08:30:22 -0800 | [diff] [blame] | 345 | // 'rootedPath' is [transitively] a symlink to a previous element in the symlink chain (i). |
Nathan Harmata | 808217c | 2015-10-12 22:07:19 +0000 | [diff] [blame] | 346 | Pair<ImmutableList<RootedPath>, ImmutableList<RootedPath>> pathAndChain = |
lberki | 7598bc6 | 2020-10-02 01:33:14 -0700 | [diff] [blame] | 347 | CycleUtils.splitIntoPathAndChain( |
| 348 | isPathPredicate(path), symlinkResolutionState.logicalChain); |
Nathan Harmata | 808217c | 2015-10-12 22:07:19 +0000 | [diff] [blame] | 349 | FileSymlinkCycleException fsce = |
| 350 | new FileSymlinkCycleException(pathAndChain.getFirst(), pathAndChain.getSecond()); |
| 351 | uniquenessKey = FileSymlinkCycleUniquenessFunction.key(fsce.getCycle()); |
| 352 | fse = fsce; |
nharmata | 4ec695c | 2019-02-19 08:30:22 -0800 | [diff] [blame] | 353 | } 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 Harmata | 808217c | 2015-10-12 22:07:19 +0000 | [diff] [blame] | 356 | Pair<ImmutableList<RootedPath>, ImmutableList<RootedPath>> pathAndChain = |
| 357 | CycleUtils.splitIntoPathAndChain( |
| 358 | isPathPredicate(seenFloorPath), |
lberki | 7598bc6 | 2020-10-02 01:33:14 -0700 | [diff] [blame] | 359 | ImmutableList.copyOf( |
| 360 | Iterables.concat( |
| 361 | symlinkResolutionState.logicalChain, ImmutableList.of(rootedPath)))); |
Nathan Harmata | 808217c | 2015-10-12 22:07:19 +0000 | [diff] [blame] | 362 | uniquenessKey = FileSymlinkInfiniteExpansionUniquenessFunction.key(pathAndChain.getSecond()); |
jhorvitz | 8514604 | 2021-04-29 10:12:10 -0700 | [diff] [blame] | 363 | fse = |
| 364 | new FileSymlinkInfiniteExpansionException( |
| 365 | pathAndChain.getFirst(), pathAndChain.getSecond()); |
nharmata | 4ec695c | 2019-02-19 08:30:22 -0800 | [diff] [blame] | 366 | } 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). |
lberki | 7598bc6 | 2020-10-02 01:33:14 -0700 | [diff] [blame] | 369 | 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 Harmata | 808217c | 2015-10-12 22:07:19 +0000 | [diff] [blame] | 380 | } |
| 381 | if (uniquenessKey != null) { |
nharmata | 4ec695c | 2019-02-19 08:30:22 -0800 | [diff] [blame] | 382 | // 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 Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 387 | } |
ulfjack | e479453 | 2018-01-12 02:11:17 -0800 | [diff] [blame] | 388 | throw new FileFunctionException( |
| 389 | Preconditions.checkNotNull(fse, rootedPath), Transience.PERSISTENT); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 390 | } |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 391 | } |
| 392 | |
jhorvitz | 8514604 | 2021-04-29 10:12:10 -0700 | [diff] [blame] | 393 | private static Predicate<RootedPath> isPathPredicate(Path path) { |
| 394 | return rootedPath -> rootedPath.asPath().equals(path); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 395 | } |
| 396 | |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 397 | /** |
jhorvitz | 8514604 | 2021-04-29 10:12:10 -0700 | [diff] [blame] | 398 | * Used to declare all the exception types that can be wrapped in the exception thrown by {@link |
| 399 | * FileFunction#compute}. |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 400 | */ |
| 401 | private static final class FileFunctionException extends SkyFunctionException { |
jhorvitz | 8514604 | 2021-04-29 10:12:10 -0700 | [diff] [blame] | 402 | FileFunctionException(IOException e, Transience transience) { |
Googler | 54e24df | 2016-03-28 19:11:39 +0000 | [diff] [blame] | 403 | super(e, transience); |
| 404 | } |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 405 | } |
| 406 | } |