CA-375900: Prepend \\?\ to file paths when creating streams for archive generation (#3184)

* CA-375900: Prepend `//?/` to file paths when creating streams for archive generation
The string works to enable creation of files with paths larger than 260 characters.
* CA-375900: Add directory support and rename utility method
* Fix whitespace in `ArchiveWriterTest`
* CA-375900: Explicitly enumerate files and directories in `ArchiveWriter`
`Directory.GetFiles` and `Directory.GetDirectories` do not enumerate if paths are longer than 260, even when prepended with `//?/`.
* CA-375900: Add long path tests to `ArchiveWriter`
* CA-375900: Add long path tests to `ArchiveIterator`
* CA-375900: Ensure files are added to folders in archive
* Use a recursive method to add directories and files to archive in `ArchiveIterator`
Also improves progress reporting by basing it on directory count
* Fix typos
* Expand `ArchiveWriterTests` to cover all combinations of directory and path lengths
* Ensure that directories used in recursive `Directory.Delete` calls are using long path format
If files in the directory exceed the 260 character limit, the calls will fail
* Expand `ArchiveIteratorTests` to cover all combinations of directory and path lengths
* Ensure relative path name removes `rootPath`
* Fix typo
* Do not use long paths when importing OVFs
The import uses `DiscUtils` which cannot handle paths prepended with `//?/`
* Remove use of `ToLongWindowsPath` within appliance export
This partially reverts commit 819425855c56c14b937849714b359003465bd2f4.
* Refactoring and some corrections.

Signed-off-by: Danilo Del Busso <danilo.delbusso@cloud.com>
Signed-off-by: Konstantina Chremmou <Konstantina.Chremmou@cloud.com>
Co-authored-by: Konstantina Chremmou <Konstantina.Chremmou@cloud.com>
This commit is contained in:
Danilo Del Busso 2023-09-22 15:41:54 +01:00 committed by GitHub
parent 9470e20808
commit 86fa2f6abf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 313 additions and 113 deletions

View File

@ -32,6 +32,7 @@ using System;
using System.IO;
using System.Text;
using NUnit.Framework;
using XenCenterLib;
using XenCenterLib.Archive;
namespace XenAdminTests.ArchiveTests
@ -55,7 +56,7 @@ namespace XenAdminTests.ArchiveTests
disposed = false;
}
}
public string CurrentFileNameReturn { private get; set; }
public string CurrentFileNameReturn { get; set; }
public long CurrentFileSizeReturn { private get; set; }
public DateTime ModTimeReturn { private get; set; }
public bool IsDirectoryReturn { private get; set; }
@ -97,10 +98,10 @@ namespace XenAdminTests.ArchiveTests
public override string CurrentFileName()
{
if (String.IsNullOrEmpty(CurrentFileNameReturn))
if (string.IsNullOrEmpty(CurrentFileNameReturn))
return CurrentFileNameReturn;
return NumberOfCallsLeftReturn + CurrentFileNameReturn;
return CurrentFileNameReturn + NumberOfCallsLeftReturn;
}
public override long CurrentFileSize()
@ -121,19 +122,15 @@ namespace XenAdminTests.ArchiveTests
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if( !disposed )
{
if( disposing )
{
if(extractedFile != null)
extractedFile.Dispose();
}
}
if (!disposed && disposing)
extractedFile?.Dispose();
}
}
#endregion
private ArchiveIteratorFake fakeIterator;
private string tempPath;
[OneTimeSetUp]
public void Setup()
@ -150,66 +147,101 @@ namespace XenAdminTests.ArchiveTests
[SetUp]
public void TestSetup()
{
tempPath = null;
fakeIterator.Reset();
}
[TearDown]
public void TestTearDown()
{
if (Directory.Exists(tempPath))
Directory.Delete(tempPath, true);
}
[Test]
public void AnExceptionIsThrownForNullArgumentWhenCallingExtractAllContents()
public void TestExtractToNullDestinationPath()
{
Assert.Throws(typeof(ArgumentNullException), () => fakeIterator.ExtractAllContents(null));
}
[Test]
public void AnExceptionIsThrownForANullFileNameWhenCallingExtractAllContents()
public void TestExtractNullFile()
{
fakeIterator.CurrentFileNameReturn = null;
Assert.Throws(typeof(ArgumentNullException), () => fakeIterator.ExtractAllContents(Path.GetTempPath()));
Assert.Throws(typeof(NullReferenceException), () => fakeIterator.ExtractAllContents(Path.GetTempPath()));
}
[Test]
public void VerifyAFileIsWrittenWhenCallingExtractAllContents()
[TestCase(true, true)]
[TestCase(true, false)]
[TestCase(false, true)]
[TestCase(false, false)]
public void TestExtractFile(bool longDirectoryPath, bool longFilePath)
{
string tempPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
tempPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
var dirCharNumber = (longDirectoryPath ? 248 : 247) - tempPath.Length - 2;
//2 was removed for the combining slash between tempPath and dir, and the combining slash between dir and filename
var dir = new string('A', dirCharNumber);
var fileCharNumber = (longFilePath ? 260 : 259) - Path.Combine(tempPath, dir).Length - 2;
//2 was removed for the combining slash between the full dir path and filename, and the NumberOfCallsLeftReturn
var fileName = new string('B', fileCharNumber);
const int numberOfFiles = 3;
fakeIterator.NumberOfCallsLeftReturn = numberOfFiles;
fakeIterator.CurrentFileNameReturn = Path.Combine(dir, fileName);
fakeIterator.ExtractAllContents(tempPath);
//Test file has been created
string targetFile = Path.Combine(tempPath, fakeIterator.CurrentFileName());
Assert.IsTrue(File.Exists(targetFile), "File Exists");
for (var i = 0; i < 3; i++)
{
string targetFile = Path.Combine(tempPath, fakeIterator.CurrentFileNameReturn + i);
Assert.IsTrue(File.ReadAllBytes(targetFile).Length > 1, "File length > 1");
if (longDirectoryPath || longFilePath)
targetFile = StringUtility.ToLongWindowsPathUnchecked(targetFile);
Assert.IsTrue(File.Exists(targetFile), "File should exist");
Assert.IsNotEmpty(File.ReadAllBytes(targetFile), "File should not be empty");
Assert.IsFalse((File.GetAttributes(targetFile) & FileAttributes.Directory) == FileAttributes.Directory,
"It should not have directory attributes");
}
//Check recursively that there are only the correct number of files
Assert.IsTrue(Directory.GetFiles(tempPath, "*.*", SearchOption.AllDirectories).Length == numberOfFiles, "File number is correct");
var actualFileNumber = Directory.GetFiles(tempPath, "*.*", SearchOption.AllDirectories).Length;
Assert.AreEqual(numberOfFiles, actualFileNumber, $"There should be {numberOfFiles}");
Assert.IsFalse((File.GetAttributes(targetFile) & FileAttributes.Directory) == FileAttributes.Directory, "Is not a dir");
Directory.Delete(tempPath,true);
if (longDirectoryPath || longFilePath)
tempPath = StringUtility.ToLongWindowsPathUnchecked(tempPath);
}
[Test]
public void VerifyADirectoryIsWrittenWhenCallingExtractAllContents()
[TestCase(true)]
[TestCase(false)]
public void TestExtractDirectory(bool longDirectoryPath)
{
string tempPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
tempPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
var dirCharNumber = (longDirectoryPath ? 248 : 247) - tempPath.Length - 2;
//2 was removed for the combining slash between tempPath and dir, and the NumberOfCallsLeftReturn
var dir = new string('A', dirCharNumber);
fakeIterator.IsDirectoryReturn = true;
fakeIterator.CurrentFileNameReturn = "FakePath" + Path.DirectorySeparatorChar;
fakeIterator.CurrentFileNameReturn = dir;
fakeIterator.ExtractAllContents(tempPath);
//Test file has been created
string targetPath = Path.Combine(tempPath, fakeIterator.CurrentFileName());
Assert.IsFalse(File.Exists(targetPath), "No files exist");
Assert.IsTrue(Directory.Exists(targetPath), "Directories exist");
//No files - just a directory
Assert.IsTrue(Directory.GetFiles(tempPath).Length < 1, "No file in the directory" );
if (longDirectoryPath)
targetPath = StringUtility.ToLongWindowsPathUnchecked(targetPath);
//Check it's a directory
Assert.IsTrue((File.GetAttributes(targetPath) & FileAttributes.Directory) == FileAttributes.Directory, "Has directory attributes");
Assert.IsFalse(File.Exists(targetPath), "Files should not exist");
Assert.IsEmpty(Directory.GetFiles(tempPath), "Directory should not have files");
Directory.Delete(tempPath, true);
Assert.IsTrue(Directory.Exists(targetPath), "Directory should exist");
Assert.IsTrue((File.GetAttributes(targetPath) & FileAttributes.Directory) == FileAttributes.Directory,
"It should have directory attributes");
if (longDirectoryPath)
tempPath = StringUtility.ToLongWindowsPathUnchecked(tempPath);
}
}
}

View File

@ -34,6 +34,7 @@ using System.IO;
using System.Linq;
using System.Text;
using NUnit.Framework;
using XenCenterLib;
using XenCenterLib.Archive;
namespace XenAdminTests.ArchiveTests
@ -68,18 +69,18 @@ namespace XenAdminTests.ArchiveTests
{
if (AddedStreamData != null)
{
foreach (Stream stream in AddedStreamData)
foreach (Stream stream in AddedStreamData)
{
if( stream != null )
if (stream != null)
stream.Dispose();
}
}
}
}
public override void Add(Stream filetoAdd, string fileName, DateTime modificationTime, Action cancellingDelegate)
public override void Add(Stream fileToAdd, string fileName, DateTime modificationTime, Action cancellingDelegate)
{
disposed = false;
AddedStreamData.Add(filetoAdd);
AddedStreamData.Add(fileToAdd);
AddedFileNameData.Add(fileName);
AddedDates.Add(modificationTime);
}
@ -93,9 +94,9 @@ namespace XenAdminTests.ArchiveTests
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if(disposing)
if (disposing)
{
if( !disposed )
if (!disposed)
{
DisposeStreamList();
}
@ -136,20 +137,10 @@ namespace XenAdminTests.ArchiveTests
Assert.AreEqual(1, fakeWriter.AddedDates.Count);
Assert.AreEqual(fileName, fakeWriter.AddedFileNameData[0], "File name");
Assert.IsTrue(fakeWriter.AddedStreamData[0].Length == 14, "Stream has data");
AssertCurrentDateIsPlausible(fakeWriter.AddedDates[0]);
Assert.That(fakeWriter.AddedDates[0], Is.EqualTo(DateTime.Now).Within(TimeSpan.FromSeconds(5)));
}
}
private void AssertCurrentDateIsPlausible(DateTime currentDate)
{
//If this is failing check that the number of seconds is enough
const double seconds = 5.0;
DateTime maxDate = DateTime.Now.AddSeconds(seconds);
DateTime minDate = DateTime.Now.AddSeconds(-1.0 * seconds);
Assert.IsTrue(currentDate > minDate, "Date is > minimum");
Assert.IsTrue(currentDate < maxDate, "Date is < maximum");
}
[Test]
public void DatelessAddDirectoryCallsImplementation()
{
@ -164,7 +155,7 @@ namespace XenAdminTests.ArchiveTests
Assert.AreEqual(0, fakeWriter.AddedStreamData.Count);
Assert.AreEqual(totalAdded, fakeWriter.AddedDates.Count);
Assert.AreEqual(dirName, fakeWriter.AddedFileNameData[0], "File name");
AssertCurrentDateIsPlausible(fakeWriter.AddedDates[0]);
Assert.That(fakeWriter.AddedDates[0], Is.EqualTo(DateTime.Now).Within(TimeSpan.FromSeconds(5)));
}
[Test]
@ -173,6 +164,96 @@ namespace XenAdminTests.ArchiveTests
Assert.Throws(typeof(FileNotFoundException), () => fakeWriter.CreateArchive("Yellow brick road - not a path!"));
}
[TestCase(true, true)]
[TestCase(false, true)]
[TestCase(true, false)]
[TestCase(false, false)]
public void CreateArchiveWithLongPath(bool longDirectoryPath, bool longFilePath)
{
//set up the path to zip
var zipPath = PopulateLongPathArchive(true, longDirectoryPath, longFilePath, out var addedData);
fakeWriter.CreateArchive(zipPath);
foreach (var datum in addedData)
Assert.Contains(datum, fakeWriter.AddedFileNameData);
// 2 folders and one file
Assert.AreEqual(addedData.Count, fakeWriter.AddedFileNameData.Count);
//clean up: we need to ensure we're deleting the folder
if (longDirectoryPath || longFilePath)
zipPath = StringUtility.ToLongWindowsPathUnchecked(zipPath);
Directory.Delete(zipPath, true);
}
[Test]
public void CreateArchiveWithLongPath_PathTooLong()
{
//! N.B.: If this test fails it might be because the project has moved to a version of .NET Core
//! that does not require calls to `StringUtils.ToLongWindowsPath`. Please review its uses
//! and remove it from the codebase if possible.
// this test ensures PopulateLongPathArchive's correctness
// since CreateArchiveWithLongPath depends on it
Assert.DoesNotThrow(() => PopulateLongPathArchive(false, false, false, out _));
Assert.Throws<DirectoryNotFoundException>(() => PopulateLongPathArchive(false, false, true, out _));
Assert.Throws<PathTooLongException>(() => PopulateLongPathArchive(false, true, true, out _));
Assert.Throws<PathTooLongException>(() => PopulateLongPathArchive(false, true, false, out _));
}
/// <summary>
/// Set up method creating a directory containing 2 subdirectories one of which has a file
/// </summary>
/// <param name="createValidPaths">set to true to ensure folders and files are prepended with \\?\</param>
/// <returns>the path to the folder</returns>
private string PopulateLongPathArchive(bool createValidPaths, bool longDirectoryPath, bool longFilePath, out List<string> addedData)
{
var zipPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
Directory.CreateDirectory(zipPath);
var dirCharNumber1 = (longDirectoryPath ? 248 : 247) - zipPath.Length - 2;
//2 was removed for the combining slash between tempPath and dir, and the first character
var relativeDirectoryPath1 = 0 + new string('A', dirCharNumber1);
var dirCharNumber2 = (longDirectoryPath ? 248 : 247) - zipPath.Length - 3;
//3 was removed for the combining slash between zipPath and dir, the first character,
//and the combining slash between dir and filename
var relativeDirectoryPath2 = 1 + new string('A', dirCharNumber2);
var fileCharNumber = (longFilePath ? 260 : 259) - Path.Combine(zipPath, relativeDirectoryPath2).Length - 1;
//1 was removed for the combining slash between the full dir path and filename
var fileName = new string('B', fileCharNumber);
var relativeFilePath = Path.Combine(relativeDirectoryPath2, fileName);
addedData = new List<string>
{
relativeDirectoryPath1.Replace(@"\", "/"),
relativeDirectoryPath2.Replace(@"\", "/"),
relativeFilePath.Replace(@"\", "/")
};
var directoryPath1 = Path.Combine(zipPath, relativeDirectoryPath1);
var directoryPath2 = Path.Combine(zipPath, relativeDirectoryPath2);
var filePath = Path.Combine(directoryPath2, fileName);
if (createValidPaths)
{
directoryPath1 = StringUtility.ToLongWindowsPathUnchecked(directoryPath1);
directoryPath2 = StringUtility.ToLongWindowsPathUnchecked(directoryPath2);
filePath = StringUtility.ToLongWindowsPathUnchecked(filePath);
}
Directory.CreateDirectory(directoryPath1);
Directory.CreateDirectory(directoryPath2);
File.WriteAllText(filePath, "Hello, World!");
return zipPath;
}
[Test]
public void CreateArchiveWorksWithValidDirectoryStructure()
{
@ -184,17 +265,17 @@ namespace XenAdminTests.ArchiveTests
{
string subfolder = Path.Combine(tempPath, Path.GetRandomFileName());
Directory.CreateDirectory(subfolder);
CreateFiles( subfolder, i);
CreateFiles(subfolder, i);
}
fakeWriter.CreateArchive(tempPath);
Assert.AreEqual(12, fakeWriter.AddedDates.Count );
Assert.AreEqual(12, fakeWriter.AddedDates.Count);
Assert.AreEqual(12, fakeWriter.AddedFileNameData.Count);
Assert.AreEqual(8, fakeWriter.AddedStreamData.Count);
foreach( DateTime date in fakeWriter.AddedDates )
AssertCurrentDateIsPlausible(date);
foreach (DateTime date in fakeWriter.AddedDates)
Assert.That(date, Is.EqualTo(DateTime.Now).Within(TimeSpan.FromSeconds(5)));
foreach (string name in fakeWriter.AddedFileNameData)
Assert.AreEqual(-1, name.IndexOfAny(@":\".ToArray()), "Unwanted chars found in path");
@ -206,7 +287,7 @@ namespace XenAdminTests.ArchiveTests
{
for (int i = 0; i < numberOfFiles; i++)
{
using( FileStream fs = File.OpenWrite(Path.Combine(tempPath, Path.GetRandomFileName())))
using (FileStream fs = File.OpenWrite(Path.Combine(tempPath, Path.GetRandomFileName())))
{
fs.Write(Encoding.ASCII.GetBytes("This is a test"), 0, 14);
fs.Flush();

View File

@ -47,21 +47,30 @@ namespace XenCenterLib.Archive
/// <exception cref="NullReferenceException">If while combining path and current file name a null arises</exception>
public void ExtractAllContents(string pathToExtractTo, Action cancellingDelegate = null)
{
if (String.IsNullOrEmpty(pathToExtractTo))
if (string.IsNullOrEmpty(pathToExtractTo))
throw new ArgumentNullException();
while (HasNext())
{
//Make the file path from the details in the archive making the path windows friendly
string conflatedPath = Path.Combine(pathToExtractTo, CurrentFileName()).Replace('/', Path.DirectorySeparatorChar);
//make the path Windows friendly
var fileName = CurrentFileName();
var isDirectory = IsDirectory();
//Create directories - empty ones will be made too
Directory.CreateDirectory(Path.GetDirectoryName(conflatedPath));
var sanitizedName = fileName.Replace('/', Path.DirectorySeparatorChar);
var conflatedPath = Path.Combine(pathToExtractTo, sanitizedName);
var dir = isDirectory ? conflatedPath : Path.GetDirectoryName(conflatedPath);
dir = StringUtility.ToLongWindowsPath(dir, true);
//Create directory - empty one will be made too
Directory.CreateDirectory(dir);
//If we have a file extract the contents
if (!IsDirectory())
if (!isDirectory)
{
using (FileStream fs = File.Create(conflatedPath))
conflatedPath = StringUtility.ToLongWindowsPath(conflatedPath, false);
using (var fs = File.Create(conflatedPath))
ExtractCurrentFile(fs, cancellingDelegate);
}
}

View File

@ -35,47 +35,18 @@ namespace XenCenterLib.Archive
{
public abstract class ArchiveWriter : IDisposable
{
public abstract void Add(Stream filetoAdd, string fileName, DateTime modificationTime, Action cancellingDelegate);
private int _progressTracker;
public abstract void Add(Stream fileToAdd, string fileName, DateTime modificationTime, Action cancellingDelegate);
public abstract void AddDirectory(string directoryName, DateTime modificationTime);
public virtual void SetBaseStream(Stream outputStream)
{
throw new NotImplementedException();
}
public abstract void AddDirectory(string directoryName, DateTime modificationTime);
/// <summary>
/// Disposal hook
/// </summary>
/// <param name="disposing"></param>
protected virtual void Dispose(bool disposing){ }
public void CreateArchive(string pathToArchive, Action cancellingDelegate = null, Action<int> progressDelegate = null)
protected virtual void Dispose(bool disposing)
{
if (!Directory.Exists(pathToArchive))
throw new FileNotFoundException("The path " + pathToArchive + " does not exist");
var files = Directory.GetFiles(pathToArchive, "*.*", SearchOption.AllDirectories);
for (var i = 0; i < files.Length; i++)
{
string filePath = files[i];
cancellingDelegate?.Invoke();
using (FileStream fs = File.OpenRead(filePath))
{
Add(fs, CleanRelativePathName(pathToArchive, filePath), File.GetCreationTime(filePath), cancellingDelegate);
progressDelegate?.Invoke((int)50.0 * i / files.Length);
}
}
var directories = Directory.GetDirectories(pathToArchive, "*.*", SearchOption.AllDirectories);
for (var j = 0; j < directories.Length; j++)
{
string dirPath = directories[j];
cancellingDelegate?.Invoke();
AddDirectory(CleanRelativePathName(pathToArchive, dirPath), Directory.GetCreationTime(dirPath));
progressDelegate?.Invoke(50 + (int)50.0 * j / directories.Length);
}
}
public void Dispose()
@ -84,10 +55,80 @@ namespace XenCenterLib.Archive
GC.SuppressFinalize(this);
}
private string CleanRelativePathName(string rootPath, string pathName)
public void CreateArchive(string pathToArchive, Action cancellingDelegate = null, Action<int> progressDelegate = null)
{
return pathName.Replace(rootPath, "").Replace('\\', '/').TrimStart('/');
// We look recursively and do not use Directory.GetDirectories and Directory.GetFiles with
// AllDirectories options because in .NET 4.8 they do not enumerate all elements if they
// have paths longer than 260 characters (248 for directories).
// If moving to .NET Core, please consider reverting this.
_progressTracker = 0;
PopulateArchive(pathToArchive, pathToArchive, cancellingDelegate, progressDelegate);
}
/// <summary>
/// Populate the archive by recursively calling the overridden <see cref="Add"/> and <see cref="AddDirectory"/>.
/// </summary>
/// <param name="pathToArchive">The path to the root of the folder we're archiving</param>
/// <param name="pathToCurrentDirectory">Keeps track of the current directory we're archiving. In the first recursive call it should be the same as <see cref="pathToArchive"/></param>
/// <param name="cancellingDelegate">Action cal led for cancelling</param>
/// <param name="progressDelegate">Action for reporting progress. Method will populate its parameter with the current progress of the recursive operation</param>
/// <param name="totalProgressIncrease">Total progress that needs to be added for archiving this directory. In the first recursive call it should be 100. If the folder we're adding should add 18 percentage points to the total progress, set this value to 18.</param>
/// <param name="progressOffset">Offset to the progress. This is added to <see cref="totalProgressIncrease"/> when setting the progress for this directory. If this folder should add 18 percentage points to the total progress, but it's for a folder past the 50% mark of the total progress (i.e.: completing this folder should set the total to 68), set this value to 50.</param>
/// <exception cref="FileNotFoundException">A directory could not be found.</exception>
private void PopulateArchive(string pathToArchive, string pathToCurrentDirectory, Action cancellingDelegate = null, Action<int> progressDelegate = null, float totalProgressIncrease = 100, float progressOffset = 0)
{
cancellingDelegate?.Invoke();
pathToArchive = StringUtility.ToLongWindowsPath(pathToArchive, true);
if (!Directory.Exists(pathToArchive))
throw new FileNotFoundException($"The path {pathToArchive} does not exist");
pathToCurrentDirectory = StringUtility.ToLongWindowsPath(pathToCurrentDirectory, true);
//add the current directory except when it's the root directory
if (pathToArchive != pathToCurrentDirectory)
AddDirectory(CleanRelativePathName(pathToArchive, pathToCurrentDirectory), Directory.GetCreationTime(pathToCurrentDirectory));
var files = Directory.GetFiles(pathToCurrentDirectory);
foreach (var file in files)
{
cancellingDelegate?.Invoke();
var filePath = StringUtility.ToLongWindowsPath(file, false);
using (var fs = File.OpenRead(filePath))
Add(fs, CleanRelativePathName(pathToArchive, filePath), File.GetCreationTime(filePath), cancellingDelegate);
}
if (_progressTracker != (int)progressOffset)
{
_progressTracker = (int)progressOffset;
progressDelegate?.Invoke(_progressTracker);
}
var directories = Directory.GetDirectories(pathToCurrentDirectory);
if (directories.Length == 0)
return;
float increment = totalProgressIncrease / directories.Length;
for (var i = 0; i < directories.Length; i++)
{
PopulateArchive(pathToArchive, directories[i], cancellingDelegate, progressDelegate,
increment, i * increment + progressOffset);
}
}
private string CleanRelativePathName(string rootPath, string pathName)
{
var cleanedRootPath = rootPath.Replace(@"\\?\", string.Empty);
return pathName
.Replace(@"\\?\", string.Empty)
.Replace(cleanedRootPath, string.Empty)
.Replace('\\', '/')
.TrimStart('/');
}
}
}

View File

@ -77,10 +77,10 @@ namespace XenCenterLib.Archive
tar.CloseEntry();
}
public override void Add(Stream filetoAdd, string fileName, DateTime modificationTime, Action cancellingDelegate)
public override void Add(Stream fileToAdd, string fileName, DateTime modificationTime, Action cancellingDelegate)
{
TarEntry entry = TarEntry.CreateTarEntry(fileName);
entry.Size = filetoAdd.Length;
entry.Size = fileToAdd.Length;
entry.ModTime = modificationTime;
tar.PutNextEntry(entry);
@ -89,9 +89,9 @@ namespace XenCenterLib.Archive
//You have to do this because if using a memory stream the pointer will be at the end it
//it's just been read and this will cause nothing to be written out
filetoAdd.Position = 0;
fileToAdd.Position = 0;
while ((n = filetoAdd.Read(buffer, 0, buffer.Length)) > 0)
while ((n = fileToAdd.Read(buffer, 0, buffer.Length)) > 0)
{
cancellingDelegate?.Invoke();
tar.Write(buffer, 0, n);

View File

@ -33,6 +33,8 @@ using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Linq;
using System.Collections;
using System.IO;
namespace XenCenterLib
{
@ -218,5 +220,39 @@ namespace XenCenterLib
return true;
}
/// <summary>
/// To be used to format file and directory paths for File streams. <br />
/// Prepends \\?\ to path if it is longer than (MAX_PATH - 12) in Windows. This is the legacy directory length limit.<br /><br />
/// See <see href='https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea'>CreateFileA</see> and <see href='https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=registry'>Maximum Path Length Limitation</see> for more info.
/// </summary>
/// <param name="inputPath">The input path</param>
/// <param name="isDirectory">Whether the input path is a directory</param>
public static string ToLongWindowsPath(string inputPath, bool isDirectory)
{
if (string.IsNullOrEmpty(inputPath) || inputPath.StartsWith(@"\\?\"))
return inputPath;
if (isDirectory)
return inputPath.Length >= 248 ? ToLongWindowsPathUnchecked(inputPath) : inputPath;
var dir = Path.GetDirectoryName(inputPath);
if (string.IsNullOrEmpty(dir))
return inputPath;
return dir.Length >= 248 || inputPath.Length >= 260 ? ToLongWindowsPathUnchecked(inputPath) : inputPath;
}
/// <summary>
/// To be used to format file and directory paths for File streams. <br />
/// Prepends \\?\ to path if it is longer than (MAX_PATH - 12) in Windows. This is the legacy directory length limit.<br /><br />
/// See <see href='https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea'>CreateFileA</see> and
/// <see href='https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=registry'>Maximum Path Length Limitation</see> for more info.
/// </summary>
/// <param name="inputPath">The input path</param>
public static string ToLongWindowsPathUnchecked(string inputPath)
{
return $@"\\?\{inputPath}";
}
}
}

View File

@ -30,6 +30,7 @@
using System;
using System.IO;
using XenCenterLib;
using XenCenterLib.Archive;
@ -65,7 +66,7 @@ namespace XenAdmin.Actions
public ZipStatusReportAction(string tempFolder, string destFile, string timeString = null, bool suppressHistory = true)
: base(null, Messages.BUGTOOL_SAVING, destFile, timeString, suppressHistory)
{
_inputTempFolder = tempFolder;
_inputTempFolder = StringUtility.ToLongWindowsPathUnchecked(tempFolder);
_destFile = destFile;
}
@ -74,7 +75,7 @@ namespace XenAdmin.Actions
Status = ReportStatus.inProgress;
do
{
_extractTempDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
_extractTempDir = StringUtility.ToLongWindowsPathUnchecked(Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()));
} while (Directory.Exists(_extractTempDir));
Directory.CreateDirectory(_extractTempDir);