Michael Thvedt | 8d5a7bb | 2016-02-09 03:06:34 +0000 | [diff] [blame] | 1 | // Copyright 2016 The Bazel Authors. All rights reserved. |
| 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 | |
mjhalupka | 287c1e7 | 2018-08-10 11:57:28 -0700 | [diff] [blame] | 16 | import com.google.common.base.Function; |
Michael Thvedt | 8d5a7bb | 2016-02-09 03:06:34 +0000 | [diff] [blame] | 17 | import com.google.common.base.MoreObjects; |
| 18 | import com.google.common.collect.ImmutableMap; |
Michael Thvedt | e4a7b079 | 2016-02-09 12:15:53 +0000 | [diff] [blame] | 19 | import com.google.common.collect.ImmutableSet; |
Rumou Duan | a77f32c | 2016-04-13 21:59:21 +0000 | [diff] [blame] | 20 | import com.google.common.collect.Iterables; |
Michael Thvedt | 8d5a7bb | 2016-02-09 03:06:34 +0000 | [diff] [blame] | 21 | import com.google.common.collect.Maps; |
mjhalupka | 287c1e7 | 2018-08-10 11:57:28 -0700 | [diff] [blame] | 22 | import com.google.devtools.build.lib.actions.Artifact; |
Rumou Duan | a77f32c | 2016-04-13 21:59:21 +0000 | [diff] [blame] | 23 | import com.google.devtools.build.lib.actions.Artifact.TreeFileArtifact; |
shahan | 602cc85 | 2018-06-06 20:09:57 -0700 | [diff] [blame] | 24 | import com.google.devtools.build.lib.actions.FileArtifactValue; |
Shreya Bhattarai | 141b6c2 | 2016-08-22 22:00:24 +0000 | [diff] [blame] | 25 | import com.google.devtools.build.lib.actions.cache.DigestUtils; |
janakr | b4e8cd7 | 2018-02-27 15:39:08 -0800 | [diff] [blame] | 26 | import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec; |
mjhalupka | 61020a7 | 2018-12-20 10:57:43 -0800 | [diff] [blame] | 27 | import com.google.devtools.build.lib.util.BigIntegerFingerprint; |
Googler | feb2670 | 2018-11-06 10:23:45 -0800 | [diff] [blame] | 28 | import com.google.devtools.build.lib.vfs.Dirent; |
| 29 | import com.google.devtools.build.lib.vfs.Dirent.Type; |
Michael Thvedt | e4a7b079 | 2016-02-09 12:15:53 +0000 | [diff] [blame] | 30 | import com.google.devtools.build.lib.vfs.Path; |
Michael Thvedt | 8d5a7bb | 2016-02-09 03:06:34 +0000 | [diff] [blame] | 31 | import com.google.devtools.build.lib.vfs.PathFragment; |
Googler | feb2670 | 2018-11-06 10:23:45 -0800 | [diff] [blame] | 32 | import com.google.devtools.build.lib.vfs.Symlinks; |
Janak Ramakrishnan | ad77f97 | 2016-07-29 20:58:42 +0000 | [diff] [blame] | 33 | import com.google.devtools.build.skyframe.SkyValue; |
Michael Thvedt | e4a7b079 | 2016-02-09 12:15:53 +0000 | [diff] [blame] | 34 | import java.io.IOException; |
mjhalupka | 61020a7 | 2018-12-20 10:57:43 -0800 | [diff] [blame] | 35 | import java.math.BigInteger; |
Michael Thvedt | 8d5a7bb | 2016-02-09 03:06:34 +0000 | [diff] [blame] | 36 | import java.util.Arrays; |
Googler | feb2670 | 2018-11-06 10:23:45 -0800 | [diff] [blame] | 37 | import java.util.Collection; |
Michael Thvedt | 8d5a7bb | 2016-02-09 03:06:34 +0000 | [diff] [blame] | 38 | import java.util.Map; |
| 39 | import java.util.Set; |
Michael Thvedt | 8d5a7bb | 2016-02-09 03:06:34 +0000 | [diff] [blame] | 40 | import javax.annotation.Nullable; |
| 41 | |
| 42 | /** |
Janak Ramakrishnan | ad77f97 | 2016-07-29 20:58:42 +0000 | [diff] [blame] | 43 | * Value for TreeArtifacts, which contains a digest and the {@link FileArtifactValue}s of its child |
| 44 | * {@link TreeFileArtifact}s. |
Michael Thvedt | 8d5a7bb | 2016-02-09 03:06:34 +0000 | [diff] [blame] | 45 | */ |
janakr | b4e8cd7 | 2018-02-27 15:39:08 -0800 | [diff] [blame] | 46 | @AutoCodec |
lpino | 578ffcc | 2018-12-06 07:08:06 -0800 | [diff] [blame] | 47 | public class TreeArtifactValue implements SkyValue { |
mjhalupka | 287c1e7 | 2018-08-10 11:57:28 -0700 | [diff] [blame] | 48 | private static final Function<Artifact, PathFragment> PARENT_RELATIVE_PATHS = |
| 49 | new Function<Artifact, PathFragment>() { |
| 50 | @Override |
| 51 | public PathFragment apply(Artifact artifact) { |
| 52 | return artifact.getParentRelativePath(); |
| 53 | } |
| 54 | }; |
Michael Thvedt | 8d5a7bb | 2016-02-09 03:06:34 +0000 | [diff] [blame] | 55 | |
Rumou Duan | a77f32c | 2016-04-13 21:59:21 +0000 | [diff] [blame] | 56 | private final byte[] digest; |
mjhalupka | 287c1e7 | 2018-08-10 11:57:28 -0700 | [diff] [blame] | 57 | private final Map<TreeFileArtifact, FileArtifactValue> childData; |
mjhalupka | 61020a7 | 2018-12-20 10:57:43 -0800 | [diff] [blame] | 58 | private BigInteger valueFingerprint; |
Rumou Duan | a77f32c | 2016-04-13 21:59:21 +0000 | [diff] [blame] | 59 | |
janakr | b4e8cd7 | 2018-02-27 15:39:08 -0800 | [diff] [blame] | 60 | @AutoCodec.VisibleForSerialization |
buchgr | dcf9600 | 2019-03-13 11:32:30 -0700 | [diff] [blame^] | 61 | TreeArtifactValue(byte[] digest, Map<TreeFileArtifact, FileArtifactValue> childData) { |
Michael Thvedt | 8d5a7bb | 2016-02-09 03:06:34 +0000 | [diff] [blame] | 62 | this.digest = digest; |
mjhalupka | 287c1e7 | 2018-08-10 11:57:28 -0700 | [diff] [blame] | 63 | this.childData = ImmutableMap.copyOf(childData); |
Michael Thvedt | 8d5a7bb | 2016-02-09 03:06:34 +0000 | [diff] [blame] | 64 | } |
| 65 | |
| 66 | /** |
buchgr | dcf9600 | 2019-03-13 11:32:30 -0700 | [diff] [blame^] | 67 | * Returns a TreeArtifactValue out of the given Artifact-relative path fragments |
| 68 | * and their corresponding FileArtifactValues. |
Michael Thvedt | 8d5a7bb | 2016-02-09 03:06:34 +0000 | [diff] [blame] | 69 | */ |
Rumou Duan | baeed33 | 2016-10-18 15:30:25 +0000 | [diff] [blame] | 70 | static TreeArtifactValue create(Map<TreeFileArtifact, FileArtifactValue> childFileValues) { |
shahan | 602cc85 | 2018-06-06 20:09:57 -0700 | [diff] [blame] | 71 | Map<String, FileArtifactValue> digestBuilder = |
Michael Thvedt | 8d5a7bb | 2016-02-09 03:06:34 +0000 | [diff] [blame] | 72 | Maps.newHashMapWithExpectedSize(childFileValues.size()); |
Rumou Duan | a77f32c | 2016-04-13 21:59:21 +0000 | [diff] [blame] | 73 | for (Map.Entry<TreeFileArtifact, FileArtifactValue> e : childFileValues.entrySet()) { |
buchgr | dcf9600 | 2019-03-13 11:32:30 -0700 | [diff] [blame^] | 74 | digestBuilder.put(e.getKey().getParentRelativePath().getPathString(), e.getValue()); |
Michael Thvedt | 8d5a7bb | 2016-02-09 03:06:34 +0000 | [diff] [blame] | 75 | } |
buchgr | dcf9600 | 2019-03-13 11:32:30 -0700 | [diff] [blame^] | 76 | |
Michael Thvedt | 8d5a7bb | 2016-02-09 03:06:34 +0000 | [diff] [blame] | 77 | return new TreeArtifactValue( |
Shreya Bhattarai | 141b6c2 | 2016-08-22 22:00:24 +0000 | [diff] [blame] | 78 | DigestUtils.fromMetadata(digestBuilder).getDigestBytesUnsafe(), |
buchgr | dcf9600 | 2019-03-13 11:32:30 -0700 | [diff] [blame^] | 79 | ImmutableMap.copyOf(childFileValues)); |
Michael Thvedt | 8d5a7bb | 2016-02-09 03:06:34 +0000 | [diff] [blame] | 80 | } |
| 81 | |
Rumou Duan | baeed33 | 2016-10-18 15:30:25 +0000 | [diff] [blame] | 82 | FileArtifactValue getSelfData() { |
Michael Thvedt | 8d5a7bb | 2016-02-09 03:06:34 +0000 | [diff] [blame] | 83 | return FileArtifactValue.createProxy(digest); |
| 84 | } |
| 85 | |
shahan | 602cc85 | 2018-06-06 20:09:57 -0700 | [diff] [blame] | 86 | FileArtifactValue getMetadata() { |
ulfjack | 01776ee | 2017-06-26 10:33:05 +0200 | [diff] [blame] | 87 | return getSelfData(); |
Michael Thvedt | 8d5a7bb | 2016-02-09 03:06:34 +0000 | [diff] [blame] | 88 | } |
| 89 | |
Rumou Duan | baeed33 | 2016-10-18 15:30:25 +0000 | [diff] [blame] | 90 | Set<PathFragment> getChildPaths() { |
mjhalupka | 287c1e7 | 2018-08-10 11:57:28 -0700 | [diff] [blame] | 91 | return ImmutableSet.copyOf(Iterables.transform(childData.keySet(), PARENT_RELATIVE_PATHS)); |
Michael Thvedt | 8d5a7bb | 2016-02-09 03:06:34 +0000 | [diff] [blame] | 92 | } |
| 93 | |
| 94 | @Nullable |
Rumou Duan | baeed33 | 2016-10-18 15:30:25 +0000 | [diff] [blame] | 95 | byte[] getDigest() { |
Michael Thvedt | 8d5a7bb | 2016-02-09 03:06:34 +0000 | [diff] [blame] | 96 | return digest.clone(); |
| 97 | } |
| 98 | |
Rumou Duan | baeed33 | 2016-10-18 15:30:25 +0000 | [diff] [blame] | 99 | Iterable<TreeFileArtifact> getChildren() { |
Rumou Duan | a77f32c | 2016-04-13 21:59:21 +0000 | [diff] [blame] | 100 | return childData.keySet(); |
| 101 | } |
| 102 | |
Rumou Duan | baeed33 | 2016-10-18 15:30:25 +0000 | [diff] [blame] | 103 | Map<TreeFileArtifact, FileArtifactValue> getChildValues() { |
| 104 | return childData; |
Rumou Duan | a77f32c | 2016-04-13 21:59:21 +0000 | [diff] [blame] | 105 | } |
| 106 | |
Michael Thvedt | 8d5a7bb | 2016-02-09 03:06:34 +0000 | [diff] [blame] | 107 | @Override |
mjhalupka | 61020a7 | 2018-12-20 10:57:43 -0800 | [diff] [blame] | 108 | public BigInteger getValueFingerprint() { |
| 109 | if (valueFingerprint == null) { |
| 110 | BigIntegerFingerprint fp = new BigIntegerFingerprint(); |
janakr | 458f07a | 2019-02-05 13:51:54 -0800 | [diff] [blame] | 111 | fp.addDigestedBytes(digest); |
mjhalupka | 61020a7 | 2018-12-20 10:57:43 -0800 | [diff] [blame] | 112 | valueFingerprint = fp.getFingerprint(); |
| 113 | } |
| 114 | return valueFingerprint; |
| 115 | } |
| 116 | |
| 117 | @Override |
Michael Thvedt | 8d5a7bb | 2016-02-09 03:06:34 +0000 | [diff] [blame] | 118 | public int hashCode() { |
| 119 | return Arrays.hashCode(digest); |
| 120 | } |
| 121 | |
| 122 | @Override |
| 123 | public boolean equals(Object other) { |
| 124 | if (this == other) { |
| 125 | return true; |
| 126 | } |
| 127 | |
| 128 | if (!(other instanceof TreeArtifactValue)) { |
| 129 | return false; |
| 130 | } |
| 131 | |
| 132 | TreeArtifactValue that = (TreeArtifactValue) other; |
Shreya Bhattarai | 3b09680 | 2016-08-17 23:14:52 +0000 | [diff] [blame] | 133 | if (!Arrays.equals(digest, that.digest)) { |
Michael Thvedt | 8d5a7bb | 2016-02-09 03:06:34 +0000 | [diff] [blame] | 134 | return false; |
| 135 | } |
| 136 | |
| 137 | return childData.equals(that.childData); |
| 138 | } |
| 139 | |
| 140 | @Override |
| 141 | public String toString() { |
| 142 | return MoreObjects.toStringHelper(TreeArtifactValue.class) |
| 143 | .add("digest", digest) |
| 144 | .add("childData", childData) |
| 145 | .toString(); |
| 146 | } |
| 147 | |
| 148 | /** |
shahan | 602cc85 | 2018-06-06 20:09:57 -0700 | [diff] [blame] | 149 | * A TreeArtifactValue that represents a missing TreeArtifact. This is occasionally useful because |
| 150 | * Java's concurrent collections disallow null members. |
Michael Thvedt | 8d5a7bb | 2016-02-09 03:06:34 +0000 | [diff] [blame] | 151 | */ |
shahan | 602cc85 | 2018-06-06 20:09:57 -0700 | [diff] [blame] | 152 | static final TreeArtifactValue MISSING_TREE_ARTIFACT = |
buchgr | dcf9600 | 2019-03-13 11:32:30 -0700 | [diff] [blame^] | 153 | new TreeArtifactValue(null, ImmutableMap.<TreeFileArtifact, FileArtifactValue>of()) { |
shahan | 602cc85 | 2018-06-06 20:09:57 -0700 | [diff] [blame] | 154 | @Override |
| 155 | FileArtifactValue getSelfData() { |
| 156 | throw new UnsupportedOperationException(); |
| 157 | } |
Michael Thvedt | 8d5a7bb | 2016-02-09 03:06:34 +0000 | [diff] [blame] | 158 | |
shahan | 602cc85 | 2018-06-06 20:09:57 -0700 | [diff] [blame] | 159 | @Override |
| 160 | Iterable<TreeFileArtifact> getChildren() { |
| 161 | throw new UnsupportedOperationException(); |
| 162 | } |
Rumou Duan | a77f32c | 2016-04-13 21:59:21 +0000 | [diff] [blame] | 163 | |
shahan | 602cc85 | 2018-06-06 20:09:57 -0700 | [diff] [blame] | 164 | @Override |
| 165 | Map<TreeFileArtifact, FileArtifactValue> getChildValues() { |
| 166 | throw new UnsupportedOperationException(); |
| 167 | } |
Michael Thvedt | 8d5a7bb | 2016-02-09 03:06:34 +0000 | [diff] [blame] | 168 | |
shahan | 602cc85 | 2018-06-06 20:09:57 -0700 | [diff] [blame] | 169 | @Override |
| 170 | FileArtifactValue getMetadata() { |
| 171 | throw new UnsupportedOperationException(); |
| 172 | } |
Michael Thvedt | 8d5a7bb | 2016-02-09 03:06:34 +0000 | [diff] [blame] | 173 | |
shahan | 602cc85 | 2018-06-06 20:09:57 -0700 | [diff] [blame] | 174 | @Override |
| 175 | Set<PathFragment> getChildPaths() { |
| 176 | throw new UnsupportedOperationException(); |
| 177 | } |
Michael Thvedt | 8d5a7bb | 2016-02-09 03:06:34 +0000 | [diff] [blame] | 178 | |
shahan | 602cc85 | 2018-06-06 20:09:57 -0700 | [diff] [blame] | 179 | @Nullable |
| 180 | @Override |
| 181 | byte[] getDigest() { |
| 182 | throw new UnsupportedOperationException(); |
| 183 | } |
Michael Thvedt | 8d5a7bb | 2016-02-09 03:06:34 +0000 | [diff] [blame] | 184 | |
shahan | 602cc85 | 2018-06-06 20:09:57 -0700 | [diff] [blame] | 185 | @Override |
| 186 | public int hashCode() { |
| 187 | return 24; // my favorite number |
| 188 | } |
Michael Thvedt | 8d5a7bb | 2016-02-09 03:06:34 +0000 | [diff] [blame] | 189 | |
shahan | 602cc85 | 2018-06-06 20:09:57 -0700 | [diff] [blame] | 190 | @Override |
| 191 | public boolean equals(Object other) { |
| 192 | return this == other; |
| 193 | } |
Michael Thvedt | 8d5a7bb | 2016-02-09 03:06:34 +0000 | [diff] [blame] | 194 | |
shahan | 602cc85 | 2018-06-06 20:09:57 -0700 | [diff] [blame] | 195 | @Override |
| 196 | public String toString() { |
| 197 | return "MISSING_TREE_ARTIFACT"; |
| 198 | } |
| 199 | }; |
Michael Thvedt | e4a7b079 | 2016-02-09 12:15:53 +0000 | [diff] [blame] | 200 | |
felly | 09efb3f | 2018-07-26 07:46:15 -0700 | [diff] [blame] | 201 | private static void explodeDirectory(Path treeArtifactPath, |
Michael Thvedt | e4a7b079 | 2016-02-09 12:15:53 +0000 | [diff] [blame] | 202 | PathFragment pathToExplode, ImmutableSet.Builder<PathFragment> valuesBuilder) |
Rumou Duan | 9ad28cd | 2016-10-19 19:28:06 +0000 | [diff] [blame] | 203 | throws IOException { |
Googler | feb2670 | 2018-11-06 10:23:45 -0800 | [diff] [blame] | 204 | Path dir = treeArtifactPath.getRelative(pathToExplode); |
| 205 | Collection<Dirent> dirents = dir.readdir(Symlinks.NOFOLLOW); |
| 206 | for (Dirent dirent : dirents) { |
| 207 | PathFragment canonicalSubpathFragment = pathToExplode.getChild(dirent.getName()); |
| 208 | if (dirent.getType() == Type.DIRECTORY) { |
| 209 | explodeDirectory(treeArtifactPath, canonicalSubpathFragment, valuesBuilder); |
| 210 | } else if (dirent.getType() == Type.SYMLINK) { |
| 211 | Path subpath = dir.getRelative(dirent.getName()); |
Rumou Duan | 11730a0 | 2016-10-24 20:27:58 +0000 | [diff] [blame] | 212 | PathFragment linkTarget = subpath.readSymbolicLinkUnchecked(); |
Janak Ramakrishnan | 6b4a8b3 | 2017-02-22 19:51:28 +0000 | [diff] [blame] | 213 | valuesBuilder.add(canonicalSubpathFragment); |
Rumou Duan | 11730a0 | 2016-10-24 20:27:58 +0000 | [diff] [blame] | 214 | if (linkTarget.isAbsolute()) { |
Janak Ramakrishnan | 6b4a8b3 | 2017-02-22 19:51:28 +0000 | [diff] [blame] | 215 | // We tolerate absolute symlinks here. They will probably be dangling if any downstream |
| 216 | // consumer tries to read them, but let that be downstream's problem. |
| 217 | continue; |
Rumou Duan | 11730a0 | 2016-10-24 20:27:58 +0000 | [diff] [blame] | 218 | } |
Rumou Duan | 11730a0 | 2016-10-24 20:27:58 +0000 | [diff] [blame] | 219 | // We visit each path segment of the link target to catch any path traversal outside of the |
| 220 | // TreeArtifact root directory. For example, for TreeArtifact a/b/c, it is possible to have |
| 221 | // a symlink, a/b/c/sym_link that points to ../outside_dir/../c/link_target. Although this |
| 222 | // symlink points to a file under the TreeArtifact, the link target traverses outside of the |
| 223 | // TreeArtifact into a/b/outside_dir. |
| 224 | PathFragment intermediatePath = canonicalSubpathFragment.getParentDirectory(); |
| 225 | for (String pathSegment : linkTarget.getSegments()) { |
tomlu | a729b9b | 2018-02-08 15:32:00 -0800 | [diff] [blame] | 226 | intermediatePath = intermediatePath.getRelative(pathSegment); |
Rumou Duan | 11730a0 | 2016-10-24 20:27:58 +0000 | [diff] [blame] | 227 | if (intermediatePath.containsUplevelReferences()) { |
| 228 | String errorMessage = String.format( |
| 229 | "A TreeArtifact may not contain relative symlinks whose target paths traverse " |
| 230 | + "outside of the TreeArtifact, found %s pointing to %s.", |
| 231 | subpath, |
| 232 | linkTarget); |
| 233 | throw new IOException(errorMessage); |
| 234 | } |
| 235 | } |
Googler | feb2670 | 2018-11-06 10:23:45 -0800 | [diff] [blame] | 236 | } else if (dirent.getType() == Type.FILE) { |
Michael Thvedt | e4a7b079 | 2016-02-09 12:15:53 +0000 | [diff] [blame] | 237 | valuesBuilder.add(canonicalSubpathFragment); |
| 238 | } else { |
| 239 | // We shouldn't ever reach here. |
Googler | feb2670 | 2018-11-06 10:23:45 -0800 | [diff] [blame] | 240 | Path subpath = dir.getRelative(dirent.getName()); |
Michael Thvedt | e4a7b079 | 2016-02-09 12:15:53 +0000 | [diff] [blame] | 241 | throw new IllegalStateException("Could not determine type of file " + subpath); |
| 242 | } |
| 243 | } |
| 244 | } |
| 245 | |
| 246 | /** |
lpino | 578ffcc | 2018-12-06 07:08:06 -0800 | [diff] [blame] | 247 | * Recursively get all child files in a directory (excluding child directories themselves, but |
| 248 | * including all files in them). |
| 249 | * |
| 250 | * @throws IOException if there is any problem reading or validating outputs under the given tree |
| 251 | * artifact. |
Michael Thvedt | e4a7b079 | 2016-02-09 12:15:53 +0000 | [diff] [blame] | 252 | */ |
lpino | 578ffcc | 2018-12-06 07:08:06 -0800 | [diff] [blame] | 253 | public static Set<PathFragment> explodeDirectory(Path treeArtifactPath) throws IOException { |
Michael Thvedt | e4a7b079 | 2016-02-09 12:15:53 +0000 | [diff] [blame] | 254 | ImmutableSet.Builder<PathFragment> explodedDirectory = ImmutableSet.builder(); |
felly | 09efb3f | 2018-07-26 07:46:15 -0700 | [diff] [blame] | 255 | explodeDirectory(treeArtifactPath, PathFragment.EMPTY_FRAGMENT, explodedDirectory); |
Michael Thvedt | e4a7b079 | 2016-02-09 12:15:53 +0000 | [diff] [blame] | 256 | return explodedDirectory.build(); |
| 257 | } |
Michael Thvedt | 8d5a7bb | 2016-02-09 03:06:34 +0000 | [diff] [blame] | 258 | } |