src/Microsoft.Diagnostics.Runtime/Implementation/FileSymbolCache.cs (133 lines of code) (raw):
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Microsoft.Diagnostics.Runtime.Utilities;
namespace Microsoft.Diagnostics.Runtime.Implementation
{
internal sealed class FileSymbolCache : FileLocatorBase
{
private readonly Dictionary<string, Task> _writingTo = new(GetEqualityComparer());
public static bool IsCaseInsensitiveFileSystem => RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
private static StringComparer GetEqualityComparer()
{
if (IsCaseInsensitiveFileSystem)
return StringComparer.OrdinalIgnoreCase;
return StringComparer.Ordinal;
}
public string Location { get; }
public FileSymbolCache(string cacheLocation)
{
if (string.IsNullOrWhiteSpace(cacheLocation))
throw new ArgumentNullException(nameof(cacheLocation));
if (!Directory.Exists(cacheLocation))
throw new DirectoryNotFoundException($"No symbol cache directory found at '{cacheLocation}'.");
Location = cacheLocation;
}
public override string? FindElfImage(string fileName, SymbolProperties archivedUnder, ImmutableArray<byte> buildId, bool checkProperties)
{
if (Path.GetFileName(fileName) != fileName && File.Exists(fileName))
{
if (!checkProperties || buildId.IsDefaultOrEmpty)
return fileName;
using ElfFile elf = new(fileName);
if (elf.BuildId.SequenceEqual(buildId))
return fileName;
}
string? key = base.FindElfImage(fileName, archivedUnder, buildId, checkProperties);
return FindImage(key);
}
public override string? FindMachOImage(string fileName, SymbolProperties archivedUnder, ImmutableArray<byte> uuid, bool checkProperties)
{
if (Path.GetFileName(fileName) != fileName && File.Exists(fileName))
{
if (!checkProperties || uuid.IsDefaultOrEmpty)
return fileName;
// TODO: We don't have a mach-o file reader to grab the uuid to verify.
return fileName;
}
string? key = base.FindMachOImage(fileName, archivedUnder, uuid, checkProperties);
return FindImage(key);
}
public override string? FindPEImage(string fileName, int buildTimeStamp, int imageSize, bool checkProperties)
{
if (Path.GetFileName(fileName) != fileName && File.Exists(fileName))
{
if (checkProperties)
{
using PEImage peImage = new(File.OpenRead(fileName), leaveOpen: false);
if (peImage.IndexFileSize == imageSize && peImage.IndexTimeStamp == buildTimeStamp)
return fileName;
}
}
string? key = base.FindPEImage(fileName, buildTimeStamp, imageSize, checkProperties);
string? image = FindImage(key);
if (checkProperties && image != null)
{
using PEImage peImage = new(File.OpenRead(image), leaveOpen: false);
if (peImage.IndexFileSize != imageSize || peImage.IndexTimeStamp != buildTimeStamp)
image = null;
}
return image;
}
public override string? FindPEImage(string fileName, SymbolProperties archivedUnder, ImmutableArray<byte> buildIdOrUUID, OSPlatform platform, bool checkProperties)
{
string? key = base.FindPEImage(fileName, archivedUnder, buildIdOrUUID, platform, checkProperties);
return FindImage(key);
}
private string? FindImage(string? key)
{
if (key is null)
return null;
string fullPath = Path.Combine(Location, key);
if (File.Exists(fullPath))
{
return fullPath;
}
else
{
string fileName = Path.GetFileName(fullPath);
fullPath = Path.Combine(Location, fileName);
return File.Exists(fullPath) ? fullPath : null;
}
}
internal string Store(Stream stream, string key)
{
if (string.IsNullOrWhiteSpace(key))
throw new InvalidOperationException($"Cannot store to an empty {nameof(key)}.");
string fullPath = Path.Combine(Location, key);
TaskCompletionSource<int> source = new();
Task? currentWrite = null;
lock (_writingTo)
{
if (!_writingTo.TryGetValue(fullPath, out currentWrite))
_writingTo.Add(fullPath, source.Task);
}
if (currentWrite != null)
{
// just in case
source.SetResult(0);
currentWrite.Wait();
return fullPath;
}
try
{
string directory = Path.GetDirectoryName(fullPath)!;
Directory.CreateDirectory(directory);
using FileStream fs = File.Create(fullPath);
stream.CopyTo(fs);
return fullPath;
}
catch (Exception ex)
{
source.SetException(ex);
throw;
}
finally
{
source.SetResult(0);
// We don't remove the entry from _writingTo. If we removed it: We could race between checking if a file
// exists and Store, meaning two threads see the file is missing, both attempt to store it, one thread
// completes this method and the other will overwrite that file again. A third thread might see the file
// exists and tries to open the half written file. Better to just 'leak' the path.
}
}
}
}