| // Copyright 2016 The Bazel Authors. All rights reserved. | 
 | // | 
 | // Licensed under the Apache License, Version 2.0 (the "License"); | 
 | // you may not use this file except in compliance with the License. | 
 | // You may obtain a copy of the License at | 
 | // | 
 | //    http://www.apache.org/licenses/LICENSE-2.0 | 
 | // | 
 | // Unless required by applicable law or agreed to in writing, software | 
 | // distributed under the License is distributed on an "AS IS" BASIS, | 
 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
 | // See the License for the specific language governing permissions and | 
 | // limitations under the License. | 
 | package com.google.devtools.build.lib.skyframe; | 
 |  | 
 | import com.google.common.base.Function; | 
 | import com.google.common.base.MoreObjects; | 
 | import com.google.common.collect.ImmutableMap; | 
 | import com.google.common.collect.ImmutableSet; | 
 | import com.google.common.collect.Iterables; | 
 | import com.google.common.collect.Maps; | 
 | import com.google.devtools.build.lib.actions.Artifact; | 
 | import com.google.devtools.build.lib.actions.Artifact.TreeFileArtifact; | 
 | import com.google.devtools.build.lib.actions.cache.DigestUtils; | 
 | import com.google.devtools.build.lib.actions.cache.Metadata; | 
 | import com.google.devtools.build.lib.vfs.Path; | 
 | import com.google.devtools.build.lib.vfs.PathFragment; | 
 | import com.google.devtools.build.skyframe.SkyValue; | 
 | import java.io.IOException; | 
 | import java.util.Arrays; | 
 | import java.util.Map; | 
 | import java.util.Set; | 
 | import javax.annotation.Nullable; | 
 |  | 
 | /** | 
 |  * Value for TreeArtifacts, which contains a digest and the {@link FileArtifactValue}s of its child | 
 |  * {@link TreeFileArtifact}s. | 
 |  */ | 
 | class TreeArtifactValue implements SkyValue { | 
 |   private static final Function<Artifact, PathFragment> PARENT_RELATIVE_PATHS = | 
 |       new Function<Artifact, PathFragment>() { | 
 |         @Override | 
 |         public PathFragment apply(Artifact artifact) { | 
 |             return artifact.getParentRelativePath(); | 
 |         } | 
 |       }; | 
 |  | 
 |   private final byte[] digest; | 
 |   private final Map<TreeFileArtifact, FileArtifactValue> childData; | 
 |  | 
 |   private TreeArtifactValue(byte[] digest, Map<TreeFileArtifact, FileArtifactValue> childData) { | 
 |     this.digest = digest; | 
 |     this.childData = ImmutableMap.copyOf(childData); | 
 |   } | 
 |  | 
 |   /** | 
 |    * Returns a TreeArtifactValue out of the given Artifact-relative path fragments | 
 |    * and their corresponding FileArtifactValues. | 
 |    */ | 
 |   static TreeArtifactValue create(Map<TreeFileArtifact, FileArtifactValue> childFileValues) { | 
 |     Map<String, Metadata> digestBuilder = | 
 |         Maps.newHashMapWithExpectedSize(childFileValues.size()); | 
 |     for (Map.Entry<TreeFileArtifact, FileArtifactValue> e : childFileValues.entrySet()) { | 
 |       digestBuilder.put(e.getKey().getParentRelativePath().getPathString(), e.getValue()); | 
 |     } | 
 |  | 
 |     return new TreeArtifactValue( | 
 |         DigestUtils.fromMetadata(digestBuilder).getDigestBytesUnsafe(), | 
 |         ImmutableMap.copyOf(childFileValues)); | 
 |   } | 
 |  | 
 |   FileArtifactValue getSelfData() { | 
 |     return FileArtifactValue.createProxy(digest); | 
 |   } | 
 |  | 
 |   Metadata getMetadata() { | 
 |     return getSelfData(); | 
 |   } | 
 |  | 
 |   Set<PathFragment> getChildPaths() { | 
 |     return ImmutableSet.copyOf(Iterables.transform(childData.keySet(), PARENT_RELATIVE_PATHS)); | 
 |   } | 
 |  | 
 |   @Nullable | 
 |   byte[] getDigest() { | 
 |     return digest.clone(); | 
 |   } | 
 |  | 
 |   Iterable<TreeFileArtifact> getChildren() { | 
 |     return childData.keySet(); | 
 |   } | 
 |  | 
 |   Map<TreeFileArtifact, FileArtifactValue> getChildValues() { | 
 |     return childData; | 
 |   } | 
 |  | 
 |   @Override | 
 |   public int hashCode() { | 
 |     return Arrays.hashCode(digest); | 
 |   } | 
 |  | 
 |   @Override | 
 |   public boolean equals(Object other) { | 
 |     if (this == other) { | 
 |       return true; | 
 |     } | 
 |  | 
 |     if (!(other instanceof TreeArtifactValue)) { | 
 |       return false; | 
 |     } | 
 |  | 
 |     TreeArtifactValue that = (TreeArtifactValue) other; | 
 |     if (!Arrays.equals(digest, that.digest)) { | 
 |       return false; | 
 |     } | 
 |  | 
 |     return childData.equals(that.childData); | 
 |   } | 
 |  | 
 |   @Override | 
 |   public String toString() { | 
 |     return MoreObjects.toStringHelper(TreeArtifactValue.class) | 
 |         .add("digest", digest) | 
 |         .add("childData", childData) | 
 |         .toString(); | 
 |   } | 
 |  | 
 |   /** | 
 |    * A TreeArtifactValue that represents a missing TreeArtifact. | 
 |    * This is occasionally useful because Java's concurrent collections disallow null members. | 
 |    */ | 
 |   static final TreeArtifactValue MISSING_TREE_ARTIFACT = new TreeArtifactValue(null, | 
 |       ImmutableMap.<TreeFileArtifact, FileArtifactValue>of()) { | 
 |     @Override | 
 |     FileArtifactValue getSelfData() { | 
 |       throw new UnsupportedOperationException(); | 
 |     } | 
 |  | 
 |     @Override | 
 |     Iterable<TreeFileArtifact> getChildren() { | 
 |       throw new UnsupportedOperationException(); | 
 |     } | 
 |  | 
 |     @Override | 
 |     Map<TreeFileArtifact, FileArtifactValue> getChildValues() { | 
 |       throw new UnsupportedOperationException(); | 
 |     } | 
 |  | 
 |     @Override | 
 |     Metadata getMetadata() { | 
 |       throw new UnsupportedOperationException(); | 
 |     } | 
 |  | 
 |     @Override | 
 |     Set<PathFragment> getChildPaths() { | 
 |       throw new UnsupportedOperationException(); | 
 |     } | 
 |  | 
 |     @Nullable | 
 |     @Override | 
 |     byte[] getDigest() { | 
 |       throw new UnsupportedOperationException(); | 
 |     } | 
 |  | 
 |     @Override | 
 |     public int hashCode() { | 
 |       return 24; // my favorite number | 
 |     } | 
 |  | 
 |     @Override | 
 |     public boolean equals(Object other) { | 
 |       return this == other; | 
 |     } | 
 |  | 
 |     @Override | 
 |     public String toString() { | 
 |       return "MISSING_TREE_ARTIFACT"; | 
 |     } | 
 |   }; | 
 |  | 
 |   private static void explodeDirectory(Artifact treeArtifact, | 
 |       PathFragment pathToExplode, ImmutableSet.Builder<PathFragment> valuesBuilder) | 
 |       throws IOException { | 
 |     for (Path subpath : treeArtifact.getPath().getRelative(pathToExplode).getDirectoryEntries()) { | 
 |       PathFragment canonicalSubpathFragment = | 
 |           pathToExplode.getChild(subpath.getBaseName()).normalize(); | 
 |       if (subpath.isDirectory()) { | 
 |         explodeDirectory(treeArtifact, | 
 |             pathToExplode.getChild(subpath.getBaseName()), valuesBuilder); | 
 |       } else if (subpath.isSymbolicLink()) { | 
 |         PathFragment linkTarget = subpath.readSymbolicLinkUnchecked(); | 
 |         valuesBuilder.add(canonicalSubpathFragment); | 
 |         if (linkTarget.isAbsolute()) { | 
 |           // We tolerate absolute symlinks here. They will probably be dangling if any downstream | 
 |           // consumer tries to read them, but let that be downstream's problem. | 
 |           continue; | 
 |         } | 
 |         // We visit each path segment of the link target to catch any path traversal outside of the | 
 |         // TreeArtifact root directory. For example, for TreeArtifact a/b/c, it is possible to have | 
 |         // a symlink, a/b/c/sym_link that points to ../outside_dir/../c/link_target. Although this | 
 |         // symlink points to a file under the TreeArtifact, the link target traverses outside of the | 
 |         // TreeArtifact into a/b/outside_dir. | 
 |         PathFragment intermediatePath = canonicalSubpathFragment.getParentDirectory(); | 
 |         for (String pathSegment : linkTarget.getSegments()) { | 
 |           intermediatePath = intermediatePath.getRelative(pathSegment).normalize(); | 
 |           if (intermediatePath.containsUplevelReferences()) { | 
 |             String errorMessage = String.format( | 
 |                 "A TreeArtifact may not contain relative symlinks whose target paths traverse " | 
 |                 + "outside of the TreeArtifact, found %s pointing to %s.", | 
 |                 subpath, | 
 |                 linkTarget); | 
 |             throw new IOException(errorMessage); | 
 |           } | 
 |         } | 
 |       } else if (subpath.isFile()) { | 
 |         valuesBuilder.add(canonicalSubpathFragment); | 
 |       } else { | 
 |         // We shouldn't ever reach here. | 
 |         throw new IllegalStateException("Could not determine type of file " + subpath); | 
 |       } | 
 |     } | 
 |   } | 
 |  | 
 |   /** | 
 |    * Recursively get all child files in a directory | 
 |    * (excluding child directories themselves, but including all files in them). | 
 |    * @throws IOException if there is any problem reading or validating outputs under the given | 
 |    *     tree artifact. | 
 |    */ | 
 |   static Set<PathFragment> explodeDirectory(Artifact treeArtifact) throws IOException { | 
 |     ImmutableSet.Builder<PathFragment> explodedDirectory = ImmutableSet.builder(); | 
 |     explodeDirectory(treeArtifact, PathFragment.EMPTY_FRAGMENT, explodedDirectory); | 
 |     return explodedDirectory.build(); | 
 |   } | 
 | } |