diff --git a/src/System.IO.FileSystem/src/Microsoft/Win32/SafeHandles/SafeFindHandle.Windows.cs b/src/System.IO.FileSystem/src/Microsoft/Win32/SafeHandles/SafeFindHandle.Windows.cs
index c9c118c1b51d..dc02183f4306 100644
--- a/src/System.IO.FileSystem/src/Microsoft/Win32/SafeHandles/SafeFindHandle.Windows.cs
+++ b/src/System.IO.FileSystem/src/Microsoft/Win32/SafeHandles/SafeFindHandle.Windows.cs
@@ -3,10 +3,7 @@
// See the LICENSE file in the project root for more information.
using System;
-using System.Security;
using System.Runtime.InteropServices;
-using System.Runtime.CompilerServices;
-using Microsoft.Win32;
namespace Microsoft.Win32.SafeHandles
{
diff --git a/src/System.IO.FileSystem/src/System/IO/Directory.cs b/src/System.IO.FileSystem/src/System/IO/Directory.cs
index d5e09c82819c..93e2d57c00ad 100644
--- a/src/System.IO.FileSystem/src/System/IO/Directory.cs
+++ b/src/System.IO.FileSystem/src/System/IO/Directory.cs
@@ -5,7 +5,6 @@
using System.Collections.Generic;
using System.IO.Enumeration;
using System.Linq;
-using System.Security;
namespace System.IO
{
@@ -42,9 +41,6 @@ public static DirectoryInfo CreateDirectory(string path)
}
// Tests if the given path refers to an existing DirectoryInfo on disk.
- //
- // Your application must have Read permission to the directory's
- // contents.
public static bool Exists(string path)
{
try
@@ -59,8 +55,6 @@ public static bool Exists(string path)
return FileSystem.DirectoryExists(fullPath);
}
catch (ArgumentException) { }
- catch (NotSupportedException) { } // Security can throw this on ":"
- catch (SecurityException) { }
catch (IOException) { }
catch (UnauthorizedAccessException) { }
diff --git a/src/System.IO.FileSystem/src/System/IO/File.cs b/src/System.IO.FileSystem/src/System/IO/File.cs
index 9af91fb0a669..2253c2b68c25 100644
--- a/src/System.IO.FileSystem/src/System/IO/File.cs
+++ b/src/System.IO.FileSystem/src/System/IO/File.cs
@@ -5,7 +5,6 @@
using System.Buffers;
using System.Collections.Generic;
using System.Diagnostics;
-using System.Security;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
@@ -44,39 +43,18 @@ public static StreamWriter AppendText(string path)
return new StreamWriter(path, append: true);
}
-
- // Copies an existing file to a new file. An exception is raised if the
- // destination file already exists. Use the
- // Copy(string, string, boolean) method to allow
- // overwriting an existing file.
- //
- // The caller must have certain FileIOPermissions. The caller must have
- // Read permission to sourceFileName and Create
- // and Write permissions to destFileName.
- //
+ ///
+ /// Copies an existing file to a new file.
+ /// An exception is raised if the destination file already exists.
+ ///
public static void Copy(string sourceFileName, string destFileName)
- {
- if (sourceFileName == null)
- throw new ArgumentNullException(nameof(sourceFileName), SR.ArgumentNull_FileName);
- if (destFileName == null)
- throw new ArgumentNullException(nameof(destFileName), SR.ArgumentNull_FileName);
- if (sourceFileName.Length == 0)
- throw new ArgumentException(SR.Argument_EmptyFileName, nameof(sourceFileName));
- if (destFileName.Length == 0)
- throw new ArgumentException(SR.Argument_EmptyFileName, nameof(destFileName));
-
- InternalCopy(sourceFileName, destFileName, false);
- }
+ => Copy(sourceFileName, destFileName, overwrite: false);
- // Copies an existing file to a new file. If overwrite is
- // false, then an IOException is thrown if the destination file
- // already exists. If overwrite is true, the file is
- // overwritten.
- //
- // The caller must have certain FileIOPermissions. The caller must have
- // Read permission to sourceFileName
- // and Write permissions to destFileName.
- //
+ ///
+ /// Copies an existing file to a new file.
+ /// If is false, an exception will be
+ /// raised if the destination exists. Otherwise it will be overwritten.
+ ///
public static void Copy(string sourceFileName, string destFileName, bool overwrite)
{
if (sourceFileName == null)
@@ -88,36 +66,13 @@ public static void Copy(string sourceFileName, string destFileName, bool overwri
if (destFileName.Length == 0)
throw new ArgumentException(SR.Argument_EmptyFileName, nameof(destFileName));
- InternalCopy(sourceFileName, destFileName, overwrite);
+ FileSystem.CopyFile(Path.GetFullPath(sourceFileName), Path.GetFullPath(destFileName), overwrite);
}
- ///
- /// Note: This returns the fully qualified name of the destination file.
- ///
- internal static string InternalCopy(string sourceFileName, string destFileName, bool overwrite)
- {
- Debug.Assert(sourceFileName != null);
- Debug.Assert(destFileName != null);
- Debug.Assert(sourceFileName.Length > 0);
- Debug.Assert(destFileName.Length > 0);
-
- string fullSourceFileName = Path.GetFullPath(sourceFileName);
- string fullDestFileName = Path.GetFullPath(destFileName);
-
- FileSystem.CopyFile(fullSourceFileName, fullDestFileName, overwrite);
-
- return fullDestFileName;
- }
-
-
// Creates a file in a particular path. If the file exists, it is replaced.
// The file is opened with ReadWrite access and cannot be opened by another
// application until it has been closed. An IOException is thrown if the
// directory specified doesn't exist.
- //
- // Your application must have Create, Read, and Write permissions to
- // the file.
- //
public static FileStream Create(string path)
{
return Create(path, DefaultBufferSize);
@@ -127,48 +82,30 @@ public static FileStream Create(string path)
// The file is opened with ReadWrite access and cannot be opened by another
// application until it has been closed. An IOException is thrown if the
// directory specified doesn't exist.
- //
- // Your application must have Create, Read, and Write permissions to
- // the file.
- //
public static FileStream Create(string path, int bufferSize)
- {
- return new FileStream(path, FileMode.Create, FileAccess.ReadWrite, FileShare.None, bufferSize);
- }
+ => new FileStream(path, FileMode.Create, FileAccess.ReadWrite, FileShare.None, bufferSize);
public static FileStream Create(string path, int bufferSize, FileOptions options)
- {
- return new FileStream(path, FileMode.Create, FileAccess.ReadWrite,
- FileShare.None, bufferSize, options);
- }
+ => new FileStream(path, FileMode.Create, FileAccess.ReadWrite, FileShare.None, bufferSize, options);
// Deletes a file. The file specified by the designated path is deleted.
// If the file does not exist, Delete succeeds without throwing
// an exception.
//
- // On NT, Delete will fail for a file that is open for normal I/O
- // or a file that is memory mapped.
- //
- // Your application must have Delete permission to the target file.
- //
+ // On Windows, Delete will fail for a file that is open for normal I/O
+ // or a file that is memory mapped.
public static void Delete(string path)
{
if (path == null)
throw new ArgumentNullException(nameof(path));
- string fullPath = Path.GetFullPath(path);
-
- FileSystem.DeleteFile(fullPath);
+ FileSystem.DeleteFile(Path.GetFullPath(path));
}
-
- // Tests if a file exists. The result is true if the file
+ // Tests whether a file exists. The result is true if the file
// given by the specified path exists; otherwise, the result is
// false. Note that if path describes a directory,
// Exists will return true.
- //
- // Your application must have Read permission for the target directory.
- //
public static bool Exists(string path)
{
try
@@ -179,6 +116,7 @@ public static bool Exists(string path)
return false;
path = Path.GetFullPath(path);
+
// After normalizing, check whether path ends in directory separator.
// Otherwise, FillAttributeInfo removes it and we may return a false positive.
// GetFullPath should never return null
@@ -191,8 +129,6 @@ public static bool Exists(string path)
return FileSystem.FileExists(path);
}
catch (ArgumentException) { }
- catch (NotSupportedException) { } // Security can throw this on ":"
- catch (SecurityException) { }
catch (IOException) { }
catch (UnauthorizedAccessException) { }
diff --git a/src/System.IO.FileSystem/src/System/IO/FileInfo.cs b/src/System.IO.FileSystem/src/System/IO/FileInfo.cs
index 3811ed8a2274..c6b3589decca 100644
--- a/src/System.IO.FileSystem/src/System/IO/FileInfo.cs
+++ b/src/System.IO.FileSystem/src/System/IO/FileInfo.cs
@@ -71,23 +71,15 @@ public bool IsReadOnly
}
public StreamReader OpenText()
- => new StreamReader(FullPath, Encoding.UTF8, detectEncodingFromByteOrderMarks: true);
+ => new StreamReader(NormalizedPath, Encoding.UTF8, detectEncodingFromByteOrderMarks: true);
public StreamWriter CreateText()
- => new StreamWriter(FullPath, append: false);
+ => new StreamWriter(NormalizedPath, append: false);
public StreamWriter AppendText()
- => new StreamWriter(FullPath, append: true);
+ => new StreamWriter(NormalizedPath, append: true);
- public FileInfo CopyTo(string destFileName)
- {
- if (destFileName == null)
- throw new ArgumentNullException(nameof(destFileName), SR.ArgumentNull_FileName);
- if (destFileName.Length == 0)
- throw new ArgumentException(SR.Argument_EmptyFileName, nameof(destFileName));
-
- return new FileInfo(File.InternalCopy(FullPath, destFileName, false), isNormalized: true);
- }
+ public FileInfo CopyTo(string destFileName) => CopyTo(destFileName, overwrite: false);
public FileInfo CopyTo(string destFileName, bool overwrite)
{
@@ -96,10 +88,12 @@ public FileInfo CopyTo(string destFileName, bool overwrite)
if (destFileName.Length == 0)
throw new ArgumentException(SR.Argument_EmptyFileName, nameof(destFileName));
- return new FileInfo(File.InternalCopy(FullPath, destFileName, overwrite), isNormalized: true);
+ string destinationPath = Path.GetFullPath(destFileName);
+ FileSystem.CopyFile(FullPath, destinationPath, overwrite);
+ return new FileInfo(destinationPath, isNormalized: true);
}
- public FileStream Create() => File.Create(FullPath);
+ public FileStream Create() => File.Create(NormalizedPath);
public override void Delete() => FileSystem.DeleteFile(FullPath);
@@ -110,13 +104,13 @@ public FileStream Open(FileMode mode, FileAccess access)
=> Open(mode, access, FileShare.None);
public FileStream Open(FileMode mode, FileAccess access, FileShare share)
- => new FileStream(FullPath, mode, access, share);
+ => new FileStream(NormalizedPath, mode, access, share);
public FileStream OpenRead()
- => new FileStream(FullPath, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, false);
+ => new FileStream(NormalizedPath, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, false);
public FileStream OpenWrite()
- => new FileStream(FullPath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None);
+ => new FileStream(NormalizedPath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None);
// Moves a given file to a new location and potentially a new file name.
// This method does work across volumes.
@@ -153,7 +147,15 @@ public FileInfo Replace(string destinationFileName, string destinationBackupFile
public FileInfo Replace(string destinationFileName, string destinationBackupFileName, bool ignoreMetadataErrors)
{
- File.Replace(FullPath, destinationFileName, destinationBackupFileName, ignoreMetadataErrors);
+ if (destinationFileName == null)
+ throw new ArgumentNullException(nameof(destinationFileName));
+
+ FileSystem.ReplaceFile(
+ FullPath,
+ Path.GetFullPath(destinationFileName),
+ destinationBackupFileName != null ? Path.GetFullPath(destinationBackupFileName) : null,
+ ignoreMetadataErrors);
+
return new FileInfo(destinationFileName);
}
diff --git a/src/System.IO.FileSystem/src/System/IO/FileSystemInfo.Unix.cs b/src/System.IO.FileSystem/src/System/IO/FileSystemInfo.Unix.cs
index cbc0e9be0560..aac479e6e117 100644
--- a/src/System.IO.FileSystem/src/System/IO/FileSystemInfo.Unix.cs
+++ b/src/System.IO.FileSystem/src/System/IO/FileSystemInfo.Unix.cs
@@ -78,5 +78,8 @@ internal static void ThrowNotFound(string path)
bool directoryError = !Directory.Exists(Path.GetDirectoryName(PathInternal.TrimEndingDirectorySeparator(path)));
throw Interop.GetExceptionForIoErrno(new Interop.ErrorInfo(Interop.Error.ENOENT), path, directoryError);
}
+
+ // There is no special handling for Unix- see Windows code for the reason we do this
+ internal string NormalizedPath => FullPath;
}
}
diff --git a/src/System.IO.FileSystem/src/System/IO/FileSystemInfo.Windows.cs b/src/System.IO.FileSystem/src/System/IO/FileSystemInfo.Windows.cs
index bfec961847ee..3ea48437e85d 100644
--- a/src/System.IO.FileSystem/src/System/IO/FileSystemInfo.Windows.cs
+++ b/src/System.IO.FileSystem/src/System/IO/FileSystemInfo.Windows.cs
@@ -150,5 +150,9 @@ public void Refresh()
// when someone actually accesses a property
_dataInitialized = FileSystem.FillAttributeInfo(FullPath, ref _data, returnErrorOnNotFound: false);
}
+
+ // If we're opened around a enumerated path that ends in a period or space we need to be able to
+ // act on the path normally (open streams/writers/etc.)
+ internal string NormalizedPath => PathInternal.EnsureExtendedPrefixIfNeeded(FullPath);
}
}
diff --git a/src/System.IO.FileSystem/tests/Enumeration/TrimmedPaths.netcoreapp.cs b/src/System.IO.FileSystem/tests/Enumeration/TrimmedPaths.netcoreapp.cs
index 8621e8eb662a..7ae7943b3b03 100644
--- a/src/System.IO.FileSystem/tests/Enumeration/TrimmedPaths.netcoreapp.cs
+++ b/src/System.IO.FileSystem/tests/Enumeration/TrimmedPaths.netcoreapp.cs
@@ -17,11 +17,11 @@ public void TrimmedPathsAreFound_Windows()
// to access without using the \\?\ device syntax. We should, however, be able to find them
// and retain the filename in the info classes and string results.
- var directory = Directory.CreateDirectory(GetTestFilePath());
+ DirectoryInfo directory = Directory.CreateDirectory(GetTestFilePath());
File.Create(@"\\?\" + Path.Combine(directory.FullName, "Trailing space ")).Dispose();
File.Create(@"\\?\" + Path.Combine(directory.FullName, "Trailing period.")).Dispose();
- var files = directory.GetFiles();
+ FileInfo[] files = directory.GetFiles();
Assert.Equal(2, files.Count());
FSAssert.EqualWhenOrdered(new string[] { "Trailing space ", "Trailing period." }, files.Select(f => f.Name));
@@ -38,7 +38,7 @@ public void TrimmedPathsDeletion_Windows()
// to access without using the \\?\ device syntax. We should, however, be able to delete them
// from the info class.
- var directory = Directory.CreateDirectory(GetTestFilePath());
+ DirectoryInfo directory = Directory.CreateDirectory(GetTestFilePath());
File.Create(@"\\?\" + Path.Combine(directory.FullName, "Trailing space ")).Dispose();
File.Create(@"\\?\" + Path.Combine(directory.FullName, "Trailing period.")).Dispose();
@@ -47,18 +47,183 @@ public void TrimmedPathsDeletion_Windows()
var paths = Directory.GetFiles(directory.FullName);
Assert.All(paths, p => Assert.False(File.Exists(p)));
- var files = directory.GetFiles();
+ FileInfo[] files = directory.GetFiles();
Assert.Equal(2, files.Count());
Assert.All(files, f => Assert.True(f.Exists));
- foreach (var f in files)
+ foreach (FileInfo f in files)
f.Refresh();
Assert.All(files, f => Assert.True(f.Exists));
- foreach (var f in files)
+ foreach (FileInfo f in files)
{
f.Delete();
f.Refresh();
}
Assert.All(files, f => Assert.False(f.Exists));
+
+ foreach (FileInfo f in files)
+ {
+ f.Create().Dispose();
+ f.Refresh();
+ }
+ Assert.All(files, f => Assert.True(f.Exists));
+ }
+
+ [Fact]
+ [PlatformSpecific(TestPlatforms.Windows)]
+ public void TrimmedPathsOpen_Windows()
+ {
+ // Trailing spaces and periods are eaten when normalizing in Windows, making them impossible
+ // to access without using the \\?\ device syntax. We should, however, be able to open them
+ // from the info class when enumerating.
+
+ DirectoryInfo directory = Directory.CreateDirectory(GetTestFilePath());
+ string fileOne = Path.Join(directory.FullName, "Trailing space ");
+ string fileTwo = Path.Join(directory.FullName, "Trailing period.");
+ File.Create(@"\\?\" + fileOne).Dispose();
+ File.Create(@"\\?\" + fileTwo).Dispose();
+
+ FileInfo[] files = directory.GetFiles();
+ Assert.Equal(2, files.Length);
+ foreach (FileInfo fi in directory.GetFiles())
+ {
+ // Shouldn't throw hitting any of the Open overloads
+ using (FileStream stream = fi.Open(FileMode.Open))
+ { }
+ using (FileStream stream = fi.Open(FileMode.Open, FileAccess.Read))
+ { }
+ using (FileStream stream = fi.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
+ { }
+ using (FileStream stream = fi.OpenRead())
+ { }
+ using (FileStream stream = fi.OpenWrite())
+ { }
+ }
+ }
+
+ [Fact]
+ [PlatformSpecific(TestPlatforms.Windows)]
+ public void TrimmedPathsText_Windows()
+ {
+ // Trailing spaces and periods are eaten when normalizing in Windows, making them impossible
+ // to access without using the \\?\ device syntax. We should, however, be able to open readers
+ // and writers from the the info class when enumerating.
+
+ DirectoryInfo directory = Directory.CreateDirectory(GetTestFilePath());
+ string fileOne = Path.Join(directory.FullName, "Trailing space ");
+ string fileTwo = Path.Join(directory.FullName, "Trailing period.");
+ File.WriteAllText(@"\\?\" + fileOne, "space");
+ File.WriteAllText(@"\\?\" + fileTwo, "period");
+
+ FileInfo[] files = directory.GetFiles();
+ Assert.Equal(2, files.Length);
+ foreach (FileInfo fi in directory.GetFiles())
+ {
+ using (StreamReader reader = fi.OpenText())
+ {
+ string content = reader.ReadToEnd();
+ if (fi.FullName.EndsWith(fileOne))
+ {
+ Assert.Equal("space", content);
+ }
+ else if (fi.FullName.EndsWith(fileTwo))
+ {
+ Assert.Equal("period", content);
+ }
+ else
+ {
+ Assert.False(true, $"Unexpected name '{fi.FullName}'");
+ }
+ }
+
+ using (StreamWriter writer = fi.CreateText())
+ {
+ writer.Write("foo");
+ }
+
+ using (StreamReader reader = fi.OpenText())
+ {
+ Assert.Equal("foo", reader.ReadToEnd());
+ }
+
+ using (StreamWriter writer = fi.AppendText())
+ {
+ writer.Write("bar");
+ }
+
+ using (StreamReader reader = fi.OpenText())
+ {
+ Assert.Equal("foobar", reader.ReadToEnd());
+ }
+ }
+ }
+
+ [Fact]
+ [PlatformSpecific(TestPlatforms.Windows)]
+ public void TrimmedPathsCopyTo_Windows()
+ {
+ // Trailing spaces and periods are eaten when normalizing in Windows, making them impossible
+ // to access without using the \\?\ device syntax. We should, however, be able to copy them
+ // without the special syntax from the info class when enumerating.
+
+ DirectoryInfo directory = Directory.CreateDirectory(GetTestFilePath());
+ string fileOne = Path.Join(directory.FullName, "Trailing space ");
+ string fileTwo = Path.Join(directory.FullName, "Trailing period.");
+ File.Create(@"\\?\" + fileOne).Dispose();
+ File.Create(@"\\?\" + fileTwo).Dispose();
+
+ FileInfo[] files = directory.GetFiles();
+ Assert.Equal(2, files.Length);
+ foreach (FileInfo fi in directory.GetFiles())
+ {
+ FileInfo newInfo = fi.CopyTo(Path.Join(directory.FullName, GetTestFileName()));
+ Assert.True(newInfo.Exists);
+ FileInfo newerInfo = fi.CopyTo(Path.Join(directory.FullName, GetTestFileName()), overwrite: true);
+ Assert.True(newerInfo.Exists);
+ }
+
+ Assert.Equal(6, directory.GetFiles().Length);
+ }
+
+ [Fact]
+ [PlatformSpecific(TestPlatforms.Windows)]
+ public void TrimmedPathsReplace_Windows()
+ {
+ // Trailing spaces and periods are eaten when normalizing in Windows, making them impossible
+ // to access without using the \\?\ device syntax. We should, however, be able to replace them
+ // from the info class when enumerating.
+
+ DirectoryInfo directory = Directory.CreateDirectory(GetTestFilePath());
+ string fileOne = Path.Join(directory.FullName, "Trailing space ");
+ string fileTwo = Path.Join(directory.FullName, "Trailing period.");
+ File.WriteAllText(@"\\?\" + fileOne, "space");
+ File.WriteAllText(@"\\?\" + fileTwo, "period");
+
+ FileInfo[] files = directory.GetFiles();
+ Assert.Equal(2, files.Length);
+
+ FileInfo destination = new FileInfo(Path.Join(directory.FullName, GetTestFileName()));
+ destination.Create().Dispose();
+
+ foreach (FileInfo fi in files)
+ {
+ fi.Replace(destination.FullName, null);
+ using (StreamReader reader = destination.OpenText())
+ {
+ string content = reader.ReadToEnd();
+ if (fi.FullName.EndsWith(fileOne))
+ {
+ Assert.Equal("space", content);
+ }
+ else if (fi.FullName.EndsWith(fileTwo))
+ {
+ Assert.Equal("period", content);
+ }
+ else
+ {
+ Assert.False(true, $"Unexpected name '{fi.FullName}'");
+ }
+ }
+ }
}
}
}