diff --git a/src/libraries/System.Formats.Tar/ref/System.Formats.Tar.cs b/src/libraries/System.Formats.Tar/ref/System.Formats.Tar.cs index 66210cdcaeca4a..e2f283ba384104 100644 --- a/src/libraries/System.Formats.Tar/ref/System.Formats.Tar.cs +++ b/src/libraries/System.Formats.Tar/ref/System.Formats.Tar.cs @@ -84,9 +84,13 @@ public enum TarEntryType : byte public static partial class TarFile { public static void CreateFromDirectory(string sourceDirectoryName, System.IO.Stream destination, bool includeBaseDirectory) { } + public static void CreateFromDirectory(string sourceDirectoryName, System.IO.Stream destination, bool includeBaseDirectory, System.Formats.Tar.TarEntryFormat format) { } public static void CreateFromDirectory(string sourceDirectoryName, string destinationFileName, bool includeBaseDirectory) { } + public static void CreateFromDirectory(string sourceDirectoryName, string destinationFileName, bool includeBaseDirectory, System.Formats.Tar.TarEntryFormat format) { } public static System.Threading.Tasks.Task CreateFromDirectoryAsync(string sourceDirectoryName, System.IO.Stream destination, bool includeBaseDirectory, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } + public static System.Threading.Tasks.Task CreateFromDirectoryAsync(string sourceDirectoryName, System.IO.Stream destination, bool includeBaseDirectory, System.Formats.Tar.TarEntryFormat format, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } public static System.Threading.Tasks.Task CreateFromDirectoryAsync(string sourceDirectoryName, string destinationFileName, bool includeBaseDirectory, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } + public static System.Threading.Tasks.Task CreateFromDirectoryAsync(string sourceDirectoryName, string destinationFileName, bool includeBaseDirectory, System.Formats.Tar.TarEntryFormat format, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } public static void ExtractToDirectory(System.IO.Stream source, string destinationDirectoryName, bool overwriteFiles) { } public static void ExtractToDirectory(string sourceFileName, string destinationDirectoryName, bool overwriteFiles) { } public static System.Threading.Tasks.Task ExtractToDirectoryAsync(System.IO.Stream source, string destinationDirectoryName, bool overwriteFiles, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } diff --git a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarFile.cs b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarFile.cs index f1d6a059c57350..1853df91b563ec 100644 --- a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarFile.cs +++ b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarFile.cs @@ -16,22 +16,29 @@ namespace System.Formats.Tar /// public static class TarFile { + /// + public static void CreateFromDirectory(string sourceDirectoryName, Stream destination, bool includeBaseDirectory) + => CreateFromDirectory(sourceDirectoryName, destination, includeBaseDirectory, TarEntryFormat.Pax); + /// /// Creates a tar stream that contains all the filesystem entries from the specified directory. /// /// The path of the directory to archive. /// The destination stream the archive. /// to include the base directory name as the first segment in all the names of the archive entries. to exclude the base directory name from the archive entry names. + /// One of the enumeration values that specifies the tar entry format to use for the archive. /// or is . /// is empty. /// -or- /// does not support writing. /// The directory path was not found. + /// is either , or not one of the other enum values. /// An I/O exception occurred. - public static void CreateFromDirectory(string sourceDirectoryName, Stream destination, bool includeBaseDirectory) + public static void CreateFromDirectory(string sourceDirectoryName, Stream destination, bool includeBaseDirectory, TarEntryFormat format) { ArgumentException.ThrowIfNullOrEmpty(sourceDirectoryName); ArgumentNullException.ThrowIfNull(destination); + ValidateFormat(format); if (!destination.CanWrite) { @@ -46,15 +53,20 @@ public static void CreateFromDirectory(string sourceDirectoryName, Stream destin // Rely on Path.GetFullPath for validation of paths sourceDirectoryName = Path.GetFullPath(sourceDirectoryName); - CreateFromDirectoryInternal(sourceDirectoryName, destination, includeBaseDirectory, leaveOpen: true); + CreateFromDirectoryInternal(sourceDirectoryName, destination, includeBaseDirectory, leaveOpen: true, format); } + /// + public static Task CreateFromDirectoryAsync(string sourceDirectoryName, Stream destination, bool includeBaseDirectory, CancellationToken cancellationToken = default) + => CreateFromDirectoryAsync(sourceDirectoryName, destination, includeBaseDirectory, TarEntryFormat.Pax, cancellationToken); + /// /// Asynchronously creates a tar stream that contains all the filesystem entries from the specified directory. /// /// The path of the directory to archive. /// The destination stream of the archive. /// to include the base directory name as the first path segment in all the names of the archive entries. to exclude the base directory name from the entry name paths. + /// One of the enumeration values that specifies the tar entry format to use for the archive. /// The token to monitor for cancellation requests. The default value is . /// A task that represents the asynchronous creation operation. /// or is . @@ -62,8 +74,9 @@ public static void CreateFromDirectory(string sourceDirectoryName, Stream destin /// -or- /// does not support writing. /// The directory path was not found. + /// is either , or not one of the other enum values. /// An I/O exception occurred. - public static Task CreateFromDirectoryAsync(string sourceDirectoryName, Stream destination, bool includeBaseDirectory, CancellationToken cancellationToken = default) + public static Task CreateFromDirectoryAsync(string sourceDirectoryName, Stream destination, bool includeBaseDirectory, TarEntryFormat format, CancellationToken cancellationToken = default) { if (cancellationToken.IsCancellationRequested) { @@ -71,6 +84,7 @@ public static Task CreateFromDirectoryAsync(string sourceDirectoryName, Stream d } ArgumentException.ThrowIfNullOrEmpty(sourceDirectoryName); ArgumentNullException.ThrowIfNull(destination); + ValidateFormat(format); if (!destination.CanWrite) { @@ -85,23 +99,30 @@ public static Task CreateFromDirectoryAsync(string sourceDirectoryName, Stream d // Rely on Path.GetFullPath for validation of paths sourceDirectoryName = Path.GetFullPath(sourceDirectoryName); - return CreateFromDirectoryInternalAsync(sourceDirectoryName, destination, includeBaseDirectory, leaveOpen: true, cancellationToken); + return CreateFromDirectoryInternalAsync(sourceDirectoryName, destination, includeBaseDirectory, leaveOpen: true, format, cancellationToken); } + /// + public static void CreateFromDirectory(string sourceDirectoryName, string destinationFileName, bool includeBaseDirectory) + => CreateFromDirectory(sourceDirectoryName, destinationFileName, includeBaseDirectory, TarEntryFormat.Pax); + /// /// Creates a tar file that contains all the filesystem entries from the specified directory. /// /// The path of the directory to archive. /// The path of the destination archive file. /// to include the base directory name as the first path segment in all the names of the archive entries. to exclude the base directory name from the entry name paths. + /// One of the enumeration values that specifies the tar entry format to use for the archive. /// or is . /// or is empty. /// The directory path was not found. + /// is either , or not one of the other enum values. /// An I/O exception occurred. - public static void CreateFromDirectory(string sourceDirectoryName, string destinationFileName, bool includeBaseDirectory) + public static void CreateFromDirectory(string sourceDirectoryName, string destinationFileName, bool includeBaseDirectory, TarEntryFormat format) { ArgumentException.ThrowIfNullOrEmpty(sourceDirectoryName); ArgumentException.ThrowIfNullOrEmpty(destinationFileName); + ValidateFormat(format); // Rely on Path.GetFullPath for validation of paths sourceDirectoryName = Path.GetFullPath(sourceDirectoryName); @@ -115,22 +136,28 @@ public static void CreateFromDirectory(string sourceDirectoryName, string destin // Throws if the destination file exists using FileStream fs = new(destinationFileName, FileMode.CreateNew, FileAccess.Write); - CreateFromDirectoryInternal(sourceDirectoryName, fs, includeBaseDirectory, leaveOpen: false); + CreateFromDirectoryInternal(sourceDirectoryName, fs, includeBaseDirectory, leaveOpen: false, format); } + /// + public static Task CreateFromDirectoryAsync(string sourceDirectoryName, string destinationFileName, bool includeBaseDirectory, CancellationToken cancellationToken = default) + => CreateFromDirectoryAsync(sourceDirectoryName, destinationFileName, includeBaseDirectory, TarEntryFormat.Pax, cancellationToken); + /// /// Asynchronously creates a tar archive from the contents of the specified directory, and outputs them into the specified path. Can optionally include the base directory as the prefix for the entry names. /// /// The path of the directory to archive. /// The path of the destination archive file. /// to include the base directory name as the first path segment in all the names of the archive entries. to exclude the base directory name from the entry name paths. + /// One of the enumeration values that specifies the tar entry format to use for the archive. /// The token to monitor for cancellation requests. The default value is . /// A task that represents the asynchronous creation operation. /// or is . /// or is empty. /// The directory path was not found. + /// is either , or not one of the other enum values. /// An I/O exception occurred. - public static Task CreateFromDirectoryAsync(string sourceDirectoryName, string destinationFileName, bool includeBaseDirectory, CancellationToken cancellationToken = default) + public static Task CreateFromDirectoryAsync(string sourceDirectoryName, string destinationFileName, bool includeBaseDirectory, TarEntryFormat format, CancellationToken cancellationToken = default) { if (cancellationToken.IsCancellationRequested) { @@ -138,6 +165,7 @@ public static Task CreateFromDirectoryAsync(string sourceDirectoryName, string d } ArgumentException.ThrowIfNullOrEmpty(sourceDirectoryName); ArgumentException.ThrowIfNullOrEmpty(destinationFileName); + ValidateFormat(format); // Rely on Path.GetFullPath for validation of paths sourceDirectoryName = Path.GetFullPath(sourceDirectoryName); @@ -148,7 +176,7 @@ public static Task CreateFromDirectoryAsync(string sourceDirectoryName, string d return Task.FromException(new DirectoryNotFoundException(SR.Format(SR.IO_PathNotFound_Path, sourceDirectoryName))); } - return CreateFromDirectoryInternalAsync(sourceDirectoryName, destinationFileName, includeBaseDirectory, cancellationToken); + return CreateFromDirectoryInternalAsync(sourceDirectoryName, destinationFileName, includeBaseDirectory, format, cancellationToken); } /// @@ -323,11 +351,11 @@ public static Task ExtractToDirectoryAsync(string sourceFileName, string destina // Creates an archive from the contents of a directory. // It assumes the sourceDirectoryName is a fully qualified path, and allows choosing if the archive stream should be left open or not. - private static void CreateFromDirectoryInternal(string sourceDirectoryName, Stream destination, bool includeBaseDirectory, bool leaveOpen) + private static void CreateFromDirectoryInternal(string sourceDirectoryName, Stream destination, bool includeBaseDirectory, bool leaveOpen, TarEntryFormat format) { VerifyCreateFromDirectoryArguments(sourceDirectoryName, destination); - using (TarWriter writer = new TarWriter(destination, TarEntryFormat.Pax, leaveOpen)) + using (TarWriter writer = new TarWriter(destination, format, leaveOpen)) { DirectoryInfo di = new(sourceDirectoryName); @@ -353,7 +381,7 @@ private static void CreateFromDirectoryInternal(string sourceDirectoryName, Stre } // Asynchronously creates a tar archive from the contents of the specified directory, and outputs them into the specified path. - private static async Task CreateFromDirectoryInternalAsync(string sourceDirectoryName, string destinationFileName, bool includeBaseDirectory, CancellationToken cancellationToken) + private static async Task CreateFromDirectoryInternalAsync(string sourceDirectoryName, string destinationFileName, bool includeBaseDirectory, TarEntryFormat format, CancellationToken cancellationToken) { Debug.Assert(!string.IsNullOrEmpty(sourceDirectoryName)); Debug.Assert(!string.IsNullOrEmpty(destinationFileName)); @@ -370,18 +398,18 @@ private static async Task CreateFromDirectoryInternalAsync(string sourceDirector FileStream archive = new(destinationFileName, options); await using (archive.ConfigureAwait(false)) { - await CreateFromDirectoryInternalAsync(sourceDirectoryName, archive, includeBaseDirectory, leaveOpen: false, cancellationToken).ConfigureAwait(false); + await CreateFromDirectoryInternalAsync(sourceDirectoryName, archive, includeBaseDirectory, leaveOpen: false, format, cancellationToken).ConfigureAwait(false); } } // Asynchronously creates an archive from the contents of a directory. // It assumes the sourceDirectoryName is a fully qualified path, and allows choosing if the archive stream should be left open or not. - private static async Task CreateFromDirectoryInternalAsync(string sourceDirectoryName, Stream destination, bool includeBaseDirectory, bool leaveOpen, CancellationToken cancellationToken) + private static async Task CreateFromDirectoryInternalAsync(string sourceDirectoryName, Stream destination, bool includeBaseDirectory, bool leaveOpen, TarEntryFormat format, CancellationToken cancellationToken) { VerifyCreateFromDirectoryArguments(sourceDirectoryName, destination); cancellationToken.ThrowIfCancellationRequested(); - TarWriter writer = new TarWriter(destination, TarEntryFormat.Pax, leaveOpen); + TarWriter writer = new TarWriter(destination, format, leaveOpen); await using (writer.ConfigureAwait(false)) { DirectoryInfo di = new(sourceDirectoryName); @@ -534,5 +562,13 @@ private static void VerifyExtractToDirectoryArguments(Stream source, string dest Debug.Assert(Path.IsPathFullyQualified(destinationDirectoryPath)); Debug.Assert(source.CanRead); } + + private static void ValidateFormat(TarEntryFormat format) + { + if (format is < TarEntryFormat.V7 or > TarEntryFormat.Gnu) + { + throw new ArgumentOutOfRangeException(nameof(format)); + } + } } } diff --git a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.CreateFromDirectory.File.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.CreateFromDirectory.File.Tests.cs index 81fb9371011716..fd0eb8efb476ec 100644 --- a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.CreateFromDirectory.File.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.CreateFromDirectory.File.Tests.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; @@ -253,5 +253,41 @@ public void SkipRecursionIntoBaseDirectorySymlink() Assert.Null(reader.GetNextEntry()); } + + [Theory] + [MemberData(nameof(GetTarEntryFormats))] + public void CreateFromDirectory_WithFormat(TarEntryFormat format) + { + using TempDirectory source = new TempDirectory(); + using TempDirectory destination = new TempDirectory(); + + string fileName = "file.txt"; + File.Create(Path.Join(source.Path, fileName)).Dispose(); + + string destinationArchiveFileName = Path.Join(destination.Path, "output.tar"); + TarFile.CreateFromDirectory(source.Path, destinationArchiveFileName, includeBaseDirectory: false, format); + + using FileStream fileStream = File.OpenRead(destinationArchiveFileName); + using TarReader reader = new TarReader(fileStream); + + TarEntry entry = reader.GetNextEntry(); + Assert.NotNull(entry); + Assert.Equal(format, entry.Format); + Assert.Equal(fileName, entry.Name); + + Assert.Null(reader.GetNextEntry()); + } + + [Theory] + [MemberData(nameof(GetInvalidTarEntryFormats))] + public void CreateFromDirectory_InvalidFormat_Throws(TarEntryFormat format) + { + using TempDirectory source = new TempDirectory(); + using TempDirectory destination = new TempDirectory(); + string destinationArchiveFileName = Path.Join(destination.Path, "output.tar"); + + Assert.Throws("format", () => + TarFile.CreateFromDirectory(source.Path, destinationArchiveFileName, includeBaseDirectory: false, format)); + } } } diff --git a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.CreateFromDirectory.Stream.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.CreateFromDirectory.Stream.Tests.cs index a525e1ce7ea82e..4eb06da3c8eb61 100644 --- a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.CreateFromDirectory.Stream.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.CreateFromDirectory.Stream.Tests.cs @@ -40,5 +40,38 @@ public void NonExistentDirectory_Throws() using MemoryStream archive = new MemoryStream(); Assert.Throws(() => TarFile.CreateFromDirectory(sourceDirectoryName: dirPath, destination: archive, includeBaseDirectory: false)); } + + [Theory] + [MemberData(nameof(GetTarEntryFormats))] + public void CreateFromDirectory_WithFormat(TarEntryFormat format) + { + using TempDirectory source = new TempDirectory(); + string fileName = "file.txt"; + File.Create(Path.Join(source.Path, fileName)).Dispose(); + + using MemoryStream archive = new MemoryStream(); + TarFile.CreateFromDirectory(source.Path, archive, includeBaseDirectory: false, format); + + archive.Position = 0; + using TarReader reader = new TarReader(archive); + + TarEntry entry = reader.GetNextEntry(); + Assert.NotNull(entry); + Assert.Equal(format, entry.Format); + Assert.Equal(fileName, entry.Name); + + Assert.Null(reader.GetNextEntry()); + } + + [Theory] + [MemberData(nameof(GetInvalidTarEntryFormats))] + public void CreateFromDirectory_InvalidFormat_Throws(TarEntryFormat format) + { + using TempDirectory source = new TempDirectory(); + using MemoryStream archive = new MemoryStream(); + + Assert.Throws("format", () => + TarFile.CreateFromDirectory(source.Path, archive, includeBaseDirectory: false, format)); + } } } diff --git a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.CreateFromDirectoryAsync.File.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.CreateFromDirectoryAsync.File.Tests.cs index 0aa50a9ed71fb4..541976de84e507 100644 --- a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.CreateFromDirectoryAsync.File.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.CreateFromDirectoryAsync.File.Tests.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; @@ -297,5 +297,45 @@ public async Task SkipRecursionIntoBaseDirectorySymlinkAsync() Assert.Null(await reader.GetNextEntryAsync()); // subDirectory should not be found } + + [Theory] + [MemberData(nameof(GetTarEntryFormats))] + public async Task CreateFromDirectoryAsync_WithFormat(TarEntryFormat format) + { + using (TempDirectory source = new TempDirectory()) + using (TempDirectory destination = new TempDirectory()) + { + string fileName = "file.txt"; + File.Create(Path.Join(source.Path, fileName)).Dispose(); + + string destinationArchiveFileName = Path.Join(destination.Path, "output.tar"); + await TarFile.CreateFromDirectoryAsync(source.Path, destinationArchiveFileName, includeBaseDirectory: false, format); + + await using (FileStream fileStream = File.OpenRead(destinationArchiveFileName)) + await using (TarReader reader = new TarReader(fileStream)) + { + TarEntry entry = await reader.GetNextEntryAsync(); + Assert.NotNull(entry); + Assert.Equal(format, entry.Format); + Assert.Equal(fileName, entry.Name); + + Assert.Null(await reader.GetNextEntryAsync()); + } + } + } + + [Theory] + [MemberData(nameof(GetInvalidTarEntryFormats))] + public async Task CreateFromDirectoryAsync_InvalidFormat_Throws(TarEntryFormat format) + { + using (TempDirectory source = new TempDirectory()) + using (TempDirectory destination = new TempDirectory()) + { + string destinationArchiveFileName = Path.Join(destination.Path, "output.tar"); + + await Assert.ThrowsAsync("format", () => + TarFile.CreateFromDirectoryAsync(source.Path, destinationArchiveFileName, includeBaseDirectory: false, format)); + } + } } } diff --git a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.CreateFromDirectoryAsync.Stream.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.CreateFromDirectoryAsync.Stream.Tests.cs index 8dbde4461290a6..9ce4caece94a7e 100644 --- a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.CreateFromDirectoryAsync.Stream.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.CreateFromDirectoryAsync.Stream.Tests.cs @@ -66,5 +66,46 @@ public async Task NonExistentDirectory_Throws_Async() } } } + + [Theory] + [MemberData(nameof(GetTarEntryFormats))] + public async Task CreateFromDirectoryAsync_WithFormat(TarEntryFormat format) + { + using (TempDirectory source = new TempDirectory()) + { + string fileName = "file.txt"; + File.Create(Path.Join(source.Path, fileName)).Dispose(); + + await using (MemoryStream archive = new MemoryStream()) + { + await TarFile.CreateFromDirectoryAsync(source.Path, archive, includeBaseDirectory: false, format); + + archive.Position = 0; + await using (TarReader reader = new TarReader(archive)) + { + TarEntry entry = await reader.GetNextEntryAsync(); + Assert.NotNull(entry); + Assert.Equal(format, entry.Format); + Assert.Equal(fileName, entry.Name); + + Assert.Null(await reader.GetNextEntryAsync()); + } + } + } + } + + [Theory] + [MemberData(nameof(GetInvalidTarEntryFormats))] + public async Task CreateFromDirectoryAsync_InvalidFormat_Throws(TarEntryFormat format) + { + using (TempDirectory source = new TempDirectory()) + { + await using (MemoryStream archive = new MemoryStream()) + { + await Assert.ThrowsAsync("format", () => + TarFile.CreateFromDirectoryAsync(source.Path, archive, includeBaseDirectory: false, format)); + } + } + } } } diff --git a/src/libraries/System.Formats.Tar/tests/TarTestsBase.cs b/src/libraries/System.Formats.Tar/tests/TarTestsBase.cs index 606277b02b28b7..caa8c6f053a42b 100644 --- a/src/libraries/System.Formats.Tar/tests/TarTestsBase.cs +++ b/src/libraries/System.Formats.Tar/tests/TarTestsBase.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; @@ -560,6 +560,22 @@ public static IEnumerable GetTestTarFormats() } } + public static IEnumerable GetTarEntryFormats() + { + yield return new object[] { TarEntryFormat.V7 }; + yield return new object[] { TarEntryFormat.Ustar }; + yield return new object[] { TarEntryFormat.Pax }; + yield return new object[] { TarEntryFormat.Gnu }; + } + + public static IEnumerable GetInvalidTarEntryFormats() + { + yield return new object[] { TarEntryFormat.Unknown }; + yield return new object[] { (TarEntryFormat)67 }; + yield return new object[] { (TarEntryFormat)int.MaxValue }; + yield return new object[] { (TarEntryFormat)int.MinValue }; + } + public static IEnumerable GetFormatsAndLinks() { foreach (TarEntryFormat format in new[] { TarEntryFormat.V7, TarEntryFormat.Ustar, TarEntryFormat.Pax, TarEntryFormat.Gnu })