in src/NuGet.Core/NuGet.Packaging/PackageExtractor.cs [379:591]
public static async Task<bool> InstallFromSourceAsync(
string source,
PackageIdentity packageIdentity,
Func<Stream, Task> copyToAsync,
VersionFolderPathResolver versionFolderPathResolver,
PackageExtractionContext packageExtractionContext,
CancellationToken token,
Guid parentId = default(Guid))
{
if (copyToAsync == null)
{
throw new ArgumentNullException(nameof(copyToAsync));
}
if (packageExtractionContext == null)
{
throw new ArgumentNullException(nameof(packageExtractionContext));
}
var logger = packageExtractionContext.Logger;
var packageExtractionTelemetryEvent = new PackageExtractionTelemetryEvent(packageExtractionContext.PackageSaveMode, NuGetOperationStatus.Failed, ExtractionSource.DownloadResource, packageIdentity);
using (var telemetry = TelemetryActivity.Create(parentId, packageExtractionTelemetryEvent))
{
var targetPath = versionFolderPathResolver.GetInstallPath(packageIdentity.Id, packageIdentity.Version);
var targetNuspec = versionFolderPathResolver.GetManifestFilePath(packageIdentity.Id, packageIdentity.Version);
var targetNupkg = versionFolderPathResolver.GetPackageFilePath(packageIdentity.Id, packageIdentity.Version);
var hashPath = versionFolderPathResolver.GetHashPath(packageIdentity.Id, packageIdentity.Version);
var nupkgMetadataFilePath = versionFolderPathResolver.GetNupkgMetadataPath(packageIdentity.Id, packageIdentity.Version);
logger.LogVerbose(
$"Acquiring lock for the installation of {packageIdentity.Id} {packageIdentity.Version}");
// Acquire the lock on a nukpg before we extract it to prevent the race condition when multiple
// processes are extracting to the same destination simultaneously
return await ConcurrencyUtilities.ExecuteWithFileLockedAsync(targetNupkg,
action: async cancellationToken =>
{
// If this is the first process trying to install the target nupkg, go ahead
// After this process successfully installs the package, all other processes
// waiting on this lock don't need to install it again.
if (!File.Exists(nupkgMetadataFilePath))
{
logger.LogVerbose(
$"Acquired lock for the installation of {packageIdentity.Id} {packageIdentity.Version}");
cancellationToken.ThrowIfCancellationRequested();
// We do not stop the package extraction after this point
// based on CancellationToken, but things can still be stopped if the process is killed.
if (Directory.Exists(targetPath))
{
// If we had a broken restore, clean out the files first
var info = new DirectoryInfo(targetPath);
foreach (var file in info.GetFiles())
{
file.Delete();
}
foreach (var dir in info.GetDirectories())
{
dir.Delete(true);
}
}
else
{
Directory.CreateDirectory(targetPath);
}
var targetTempNupkg = Path.Combine(targetPath, Path.GetRandomFileName());
var tempHashPath = Path.Combine(targetPath, Path.GetRandomFileName());
var tempNupkgMetadataPath = Path.Combine(targetPath, Path.GetRandomFileName());
var packageSaveMode = packageExtractionContext.PackageSaveMode;
string contentHash;
try
{
// Extract the nupkg
using (var nupkgStream = new FileStream(
targetTempNupkg,
FileMode.Create,
FileAccess.ReadWrite,
FileShare.ReadWrite | FileShare.Delete,
bufferSize: 4096))
{
await copyToAsync(nupkgStream);
nupkgStream.Seek(0, SeekOrigin.Begin);
using (var packageReader = new PackageArchiveReader(nupkgStream))
{
if (packageSaveMode.HasFlag(PackageSaveMode.Nuspec) || packageSaveMode.HasFlag(PackageSaveMode.Files))
{
telemetry.StartIntervalMeasure();
await VerifyPackageSignatureAsync(
source,
telemetry.OperationId,
packageIdentity,
packageExtractionContext,
packageReader,
token);
telemetry.EndIntervalMeasure(PackagingConstants.PackageVerifyDurationName);
}
var nuspecFile = packageReader.GetNuspecFile();
if ((packageSaveMode & PackageSaveMode.Nuspec) == PackageSaveMode.Nuspec)
{
packageReader.ExtractFile(nuspecFile, targetNuspec, logger);
}
if ((packageSaveMode & PackageSaveMode.Files) == PackageSaveMode.Files)
{
var hashFileName = Path.GetFileName(hashPath);
var nupkgMetadataFileName = Path.GetFileName(nupkgMetadataFilePath);
var packageFiles = packageReader.GetFiles()
.Where(file => ShouldInclude(file, hashFileName, nupkgMetadataFileName));
var packageFileExtractor = new PackageFileExtractor(
packageFiles,
packageExtractionContext.XmlDocFileSaveMode);
packageReader.CopyFiles(
targetPath,
packageFiles,
packageFileExtractor.ExtractPackageFile,
logger,
token);
}
nupkgStream.Position = 0;
var packageHash = Convert.ToBase64String(new CryptoHashProvider("SHA512").CalculateHash(nupkgStream));
File.WriteAllText(tempHashPath, packageHash);
// get hash for the unsigned content of package
contentHash = packageReader.GetContentHash(cancellationToken, GetUnsignedPackageHash: () => packageHash);
// write the new hash file
var hashFile = new NupkgMetadataFile()
{
ContentHash = contentHash,
Source = source
};
NupkgMetadataFileFormat.Write(tempNupkgMetadataPath, hashFile);
}
}
}
catch (SignatureException)
{
try
{
DeleteTargetAndTempPaths(targetPath, targetTempNupkg);
}
catch (IOException ex)
{
logger.LogWarning(string.Format(
CultureInfo.CurrentCulture,
Strings.ErrorUnableToDeleteFile,
targetTempNupkg,
ex.Message));
}
throw;
}
// Now rename the tmp file
if ((packageExtractionContext.PackageSaveMode & PackageSaveMode.Nupkg) ==
PackageSaveMode.Nupkg)
{
File.Move(targetTempNupkg, targetNupkg);
}
else
{
try
{
File.Delete(targetTempNupkg);
}
catch (IOException ex)
{
logger.LogWarning(string.Format(
CultureInfo.CurrentCulture,
Strings.ErrorUnableToDeleteFile,
targetTempNupkg,
ex.Message));
}
}
// Note: PackageRepository relies on the hash file being written out as the
// final operation as part of a package install to assume a package was fully installed.
// Rename the tmp hash file
File.Move(tempHashPath, hashPath);
File.Move(tempNupkgMetadataPath, nupkgMetadataFilePath);
logger.LogInformation(StringFormatter.Log_InstalledPackage(packageIdentity.Id, packageIdentity.Version.OriginalVersion, source, contentHash, targetPath));
packageExtractionTelemetryEvent.SetResult(NuGetOperationStatus.Succeeded);
return true;
}
else
{
logger.LogVerbose("Lock not required - Package already installed "
+ $"{packageIdentity.Id} {packageIdentity.Version}");
packageExtractionTelemetryEvent.SetResult(NuGetOperationStatus.NoOp);
return false;
}
},
token: token);
}
}