/// <summary> /// Adds synthetic read accesses for all intermediate directory symlinks for the given access path /// </summary> /// <remarks> /// TODO: This function is only adding accesses for symlinks in the given path, so if those suymlinks point to other symlinks, /// those are not added. So the result is not completely sound, consider doing multi-hop resolution. /// </remarks> public void AddReadsForIntermediateSymlinks(FileAccessManifest manifest, ReportedFileAccess access, AbsolutePath accessPath, Dictionary <AbsolutePath, CompactSet <ReportedFileAccess> > accessesByPath) { Contract.Requires(accessPath.IsValid); AbsolutePath currentPath = accessPath.GetParent(m_context.PathTable); while (currentPath.IsValid) { // If we the current path is resolved and its resolved path is the same, then we know there are no more symlinks // in the path. Shorcut the search. if (m_resolvedPathCache.TryGetValue(currentPath, out var resolvedPath) && currentPath == resolvedPath) { return; } bool isDirSymlink; var result = m_symlinkCache.TryGet(currentPath); if (!result.IsFound) { isDirSymlink = FileUtilities.IsDirectorySymlinkOrJunction(currentPath.ToString(m_context.PathTable)); m_symlinkCache.TryAdd(currentPath, isDirSymlink); } else { isDirSymlink = result.Item.Value; } if (isDirSymlink) { accessesByPath.TryGetValue(currentPath, out CompactSet <ReportedFileAccess> existingAccessesToPath); accessesByPath[currentPath] = existingAccessesToPath.Add(GenerateReadAccessForPath(manifest, currentPath, access)); } currentPath = currentPath.GetParent(m_context.PathTable); } }
private bool IsDirectorySymlinkOrJunctionWithCache(ExpandedAbsolutePath path) { if (m_symlinkCache.TryGet(path.Path) is var cachedResult && cachedResult.IsFound) { return(cachedResult.Item.Value); } var result = FileUtilities.IsDirectorySymlinkOrJunction(path.ExpandedPath); m_symlinkCache.TryAdd(path.Path, result); return(result); }
private int EnsurePath(AbsolutePath path) { if (!path.IsValid) { return(0); } var getResult = m_pathToParentIndexMap.TryGet(path); if (getResult.IsFound) { return(getResult.Index); } var parentIndex = EnsurePath(path.GetParent(m_pathTable)); var addResult = m_pathToParentIndexMap.GetOrAdd(path, parentIndex); Contract.Assert(!addResult.IsFound); return(addResult.Index); }
/// <summary> /// Return a path where all intermediate reparse point directories are resolved to their final destinations. /// </summary> /// <remarks> /// The final segment of the path is never resolved /// </remarks> public AbsolutePath ResolveIntermediateDirectoryReparsePoints(AbsolutePath path) { Contract.Requires(path.IsValid); // This function only takes care of intermediate directories, so let's fully resolve the parent path var parentPath = path.GetParent(m_context.PathTable); // If no parent, there is nothing to resolve if (!parentPath.IsValid) { return(path); } PathAtom filename = path.GetName(m_context.PathTable); // Check the cache var cachedResult = m_resolvedPathCache.TryGet(parentPath); if (cachedResult.IsFound) { return(cachedResult.Item.Value.Combine(m_context.PathTable, filename)); } // The cache didn't have it, so let's resolve it if (!TryResolvePath(parentPath.ToString(m_context.PathTable), out ExpandedAbsolutePath resolvedExpandedPath)) { // If we cannot get the final path (e.g. file not found causes this), then we assume the path // is already canonicalized // Observe we cannot update the cache since the path was not resolved. return(path); } // Update the cache m_resolvedPathCache.TryAdd(parentPath, resolvedExpandedPath.Path); return(resolvedExpandedPath.Path.Combine(m_context.PathTable, filename)); }
private AbsolutePath ResolvePathWithCache(AbsolutePath accessPath, [CanBeNull] string accessPathAsString, [CanBeNull] out string resolvedPathAsString) { // BuildXL already handles the case of paths whose final fragment is a symlink. So we make sure to resolve everything else. var parentPath = accessPath.GetParent(m_context.PathTable); if (!parentPath.IsValid) { // The path doesn't have a root, so even if it is a symlink, BuildXL should be good with it resolvedPathAsString = accessPathAsString; return(accessPath); } var lastFragment = accessPath.GetName(m_context.PathTable); // In addition to buildxl already handling final fragment symlinks, by caching parents we enhance the chance of a hit, where // many files shared the same parent. Query the cache with it. var cachedResult = m_resolvedPathCache.TryGet(parentPath); if (cachedResult.IsFound) { // Observe we don't cache expanded paths since that might cause the majority of all the paths of a build to live // in memory for the whole execution time. So in case of a cache hit, we just return the absolute path. The expanded // path needs to be reconstructed as needed. resolvedPathAsString = null; var resolvedParentPath = cachedResult.Item.Value; // The cached resolved path could be invalid, meaning the original parent pointed to a drive's root return(resolvedParentPath.IsValid ? resolvedParentPath.Combine(m_context.PathTable, lastFragment) : AbsolutePath.Create(m_context.PathTable, lastFragment.ToString(m_context.StringTable))); } // The cache didn't have it, so let's resolve it accessPathAsString ??= accessPath.ToString(m_context.PathTable); if (!TryResolveSymlinkedPath(accessPathAsString, out ExpandedAbsolutePath resolvedExpandedPath)) { // If we cannot get the final path (e.g. file not found causes this), then we assume the path // is already canonicalized // Observe we cannot update the cache since the path was not resolved. // TODO: we could consider always trying to resolve the parent resolvedPathAsString = accessPathAsString; return(accessPath); } // Update the cache. Observe we may be storing an invalid path if the resolved path does not have a parent. var resolvedPathParent = resolvedExpandedPath.Path.GetParent(m_context.PathTable); m_resolvedPathCache.TryAdd(parentPath, resolvedPathParent); // If the resolved path is the same as the access path, then we know its last fragment is not a symlink. // So we might just update the symlink cache as well and hopefully speed up subsequent requests to generate read accesses // for intermediate symlink dirs if (parentPath == resolvedExpandedPath.Path) { m_symlinkCache.TryAdd(parentPath, false); } resolvedPathAsString = resolvedExpandedPath.ExpandedPath; return(resolvedExpandedPath.Path); }
/// <summary> /// Gets the symlink target or <see cref="AbsolutePath.Invalid"/> if the path is not a registered symlink /// </summary> public AbsolutePath TryGetSymlinkTarget(AbsolutePath symlink) => m_symlinkDefinitionMap.TryGet(symlink).Item.Value;
private AbsolutePath ResolvePathWithCache( AbsolutePath accessPath, [CanBeNull] string accessPathAsString, ReportedFileOperation operation, FlagsAndAttributes flagsAndAttributes, [CanBeNull] out string resolvedPathAsString, out bool isDirectoryReparsePoint) { accessPathAsString ??= accessPath.ToString(m_context.PathTable); // If the final segment is a directory reparse point and the operation acts on it, don't resolve it if (ShouldNotResolveLastSegment(ExpandedAbsolutePath.CreateUnsafe(accessPath, accessPathAsString), operation, flagsAndAttributes, out isDirectoryReparsePoint)) { AbsolutePath resolvedParentPath = ResolvePathWithCache(accessPath.GetParent(m_context.PathTable), accessPathAsString: null, operation, flagsAndAttributes, out string resolvedParentPathAsString, out _); PathAtom name = accessPath.GetName(m_context.PathTable); resolvedPathAsString = resolvedParentPathAsString != null?Path.Combine(resolvedParentPathAsString, name.ToString(m_context.StringTable)) : null; return(resolvedParentPath.Combine(m_context.PathTable, name)); } // Check the cache var cachedResult = m_resolvedPathCache.TryGet(accessPath); if (cachedResult.IsFound) { // Observe we don't cache expanded paths since that might cause the majority of all the paths of a build to live // in memory for the whole execution time. So in case of a cache hit, we just return the absolute path. The expanded // path needs to be reconstructed as needed. resolvedPathAsString = null; return(cachedResult.Item.Value); } // If not found and the path is not a reparse point, check the cache for its parent. // Many files may have the same parent, and since the path is not a reparse point we know that // resolve(parent\segment) == resolve(parent)\segment var parentPath = accessPath.GetParent(m_context.PathTable); if (parentPath.IsValid && !isDirectoryReparsePoint && m_resolvedPathCache.TryGet(parentPath) is var cachedParent && cachedParent.IsFound) { var lastFragment = accessPath.GetName(m_context.PathTable); resolvedPathAsString = null; AbsolutePath cachedParentPath = cachedParent.Item.Value; return(cachedParentPath.IsValid ? cachedParentPath.Combine(m_context.PathTable, lastFragment) : AbsolutePath.Create(m_context.PathTable, lastFragment.ToString(m_context.StringTable))); } // The cache didn't have it, so let's resolve it if (!TryResolveSymlinkedPath(accessPathAsString, out ExpandedAbsolutePath resolvedExpandedPath)) { // If we cannot get the final path (e.g. file not found causes this), then we assume the path // is already canonicalized // Observe we cannot update the cache since the path was not resolved. // TODO: we could consider always trying to resolve the parent resolvedPathAsString = accessPathAsString; return(accessPath); } // Update the cache m_resolvedPathCache.TryAdd(accessPath, resolvedExpandedPath.Path); // If the path is not a reparse point, update the parent as well if (!isDirectoryReparsePoint && parentPath.IsValid) { // Observe we may be storing an invalid path if the resolved path does not have a parent. m_resolvedPathCache.TryAdd(parentPath, resolvedExpandedPath.Path.GetParent(m_context.PathTable)); } // If the resolved path is the same as the access path, then we know its last fragment is not a reparse point. // So we might just update the symlink cache as well and hopefully speed up subsequent requests to generate read accesses // for intermediate symlink dirs if (accessPath == resolvedExpandedPath.Path) { while (parentPath.IsValid && m_symlinkCache.TryAdd(parentPath, false)) { parentPath = parentPath.GetParent(m_context.PathTable); } } resolvedPathAsString = resolvedExpandedPath.ExpandedPath; return(resolvedExpandedPath.Path); }