Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.
/ corefx Public archive

Commit

Permalink
Unblock paths > MAX_PATH without extended syntax.
Browse files Browse the repository at this point in the history
Also allow directories up to max component length (255) as opening does not
require extended syntax.
  • Loading branch information
JeremyKuhne committed Aug 28, 2015
1 parent b4f7547 commit 50a3b1a
Show file tree
Hide file tree
Showing 15 changed files with 188 additions and 58 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ internal partial class mincore

internal static bool CreateDirectory(string path, ref SECURITY_ATTRIBUTES lpSecurityAttributes)
{
path = PathInternal.AddExtendedPathPrefixForLongPaths(path);
// We always want to add for CreateDirectory to get around the legacy 248 character limitation
path = PathInternal.AddExtendedPathPrefix(path);
return CreateDirectoryPrivate(path, ref lpSecurityAttributes);
}
}
Expand Down
25 changes: 23 additions & 2 deletions src/Common/src/Interop/Windows/mincore/Interop.GetFullPathNameW.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,38 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;

partial class Interop
{
partial class mincore
{
/// <summary>
/// WARNING: This overload does not implicitly handle long paths.
/// </summary>
[DllImport(Libraries.CoreFile_L1, SetLastError = true, CharSet = CharSet.Unicode, BestFitMapping = false)]
internal unsafe static extern int GetFullPathNameW(char* path, int numBufferChars, char* buffer, IntPtr mustBeZero);

[DllImport(Libraries.CoreFile_L1, SetLastError = true, CharSet = CharSet.Unicode, BestFitMapping = false)]
internal static extern int GetFullPathNameW(string path, int numBufferChars, [Out]StringBuilder buffer, IntPtr mustBeZero);
[DllImport(Libraries.CoreFile_L1, EntryPoint = "GetFullPathNameW", SetLastError = true, CharSet = CharSet.Unicode, BestFitMapping = false)]
private static extern int GetFullPathNameWPrivate(string path, int numBufferChars, [Out]StringBuilder buffer, IntPtr mustBeZero);

internal static int GetFullPathNameW(string path, int numBufferChars, [Out]StringBuilder buffer, IntPtr mustBeZero)
{
bool wasExtended = PathInternal.IsExtended(path);
if (!wasExtended)
{
path = PathInternal.AddExtendedPathPrefixForLongPaths(path);
}
int result = GetFullPathNameWPrivate(path, buffer.Capacity, buffer, mustBeZero);

if (!wasExtended)
{
// We don't want to give back \\?\ if we possibly added it ourselves
PathInternal.RemoveExtendedPathPrefix(buffer);
}
return result;
}
}
}
25 changes: 23 additions & 2 deletions src/Common/src/Interop/Windows/mincore/Interop.GetLongPathNameW.cs
Original file line number Diff line number Diff line change
@@ -1,17 +1,38 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.IO;
using System.Text;
using System.Runtime.InteropServices;

partial class Interop
{
partial class mincore
{
/// <summary>
/// WARNING: This overload does not implicitly handle long paths.
/// </summary>
[DllImport(Libraries.CoreFile_L1, SetLastError = true, CharSet = CharSet.Unicode, BestFitMapping = false)]
internal unsafe static extern int GetLongPathNameW(char* path, char* longPathBuffer, int bufferLength);

[DllImport(Libraries.CoreFile_L1, SetLastError = true, CharSet = CharSet.Unicode, BestFitMapping = false)]
internal static extern int GetLongPathNameW(string path, [Out]StringBuilder longPathBuffer, int bufferLength);
[DllImport(Libraries.CoreFile_L1, EntryPoint = "GetLongPathNameW", SetLastError = true, CharSet = CharSet.Unicode, BestFitMapping = false)]
private static extern int GetLongPathNameWPrivate(string path, [Out]StringBuilder longPathBuffer, int bufferLength);

internal static int GetLongPathNameW(string path, [Out]StringBuilder longPathBuffer, int bufferLength)
{
bool wasExtended = PathInternal.IsExtended(path);
if (!wasExtended)
{
path = PathInternal.AddExtendedPathPrefixForLongPaths(path);
}
int result = GetLongPathNameWPrivate(path, longPathBuffer, longPathBuffer.Capacity);

if (!wasExtended)
{
// We don't want to give back \\?\ if we possibly added it ourselves
PathInternal.RemoveExtendedPathPrefix(longPathBuffer);
}
return result;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.IO;
using System.Text;
using System.Runtime.InteropServices;

Expand Down
44 changes: 24 additions & 20 deletions src/Common/src/System/IO/PathInternal.Windows.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ internal static partial class PathInternal
internal const string DevicePathPrefix = @"\\.\";
internal const int MaxShortPath = 260;
internal const int MaxShortDirectoryPath = 248;
internal const int MaxExtendedPath = short.MaxValue;
internal const int MaxLongPath = short.MaxValue;

internal static readonly char[] InvalidPathChars =
{
Expand All @@ -41,33 +41,37 @@ internal static partial class PathInternal
/// </summary>
internal static bool IsPathTooLong(string fullPath)
{
// We'll never know precisely what will fail as paths get changed internally in Windows and
// may grow to exceed MaxExtendedPath. We'll only try to catch ones we know will absolutely
// fail.

if (fullPath.Length < MaxLongPath - UncExtendedPathPrefix.Length)
{
// We won't push it over MaxLongPath
return false;
}

// We need to check if we have a prefix to account for one being implicitly added.
if (IsExtended(fullPath))
{
return fullPath.Length >= MaxExtendedPath;
// We won't prepend, just check
return fullPath.Length >= MaxLongPath;
}
else

if (fullPath.StartsWith(UncPathPrefix, StringComparison.Ordinal))
{
// Will need to be updated with #2581 to allow all paths to MaxExtendedPath
// minus legth of extended local or UNC prefix.
return fullPath.Length >= MaxShortPath;
return fullPath.Length + UncExtendedPrefixToInsert.Length >= MaxLongPath;
}

return fullPath.Length + ExtendedPathPrefix.Length >= MaxLongPath;
}

/// <summary>
/// Returns true if the directory is too long
/// </summary>
internal static bool IsDirectoryTooLong(string fullPath)
{
if (IsExtended(fullPath))
{
return fullPath.Length >= MaxExtendedPath;
}
else
{
// Will need to be updated with #2581 to allow all paths to MaxExtendedPath
// minus legth of extended local or UNC prefix.
return fullPath.Length >= MaxShortDirectoryPath;
}
return IsPathTooLong(fullPath);
}

/// <summary>
Expand Down Expand Up @@ -272,17 +276,17 @@ internal static bool IsPathRelative(string path)
return true;
}

if ((path[0] == '\\') || (path[0] == '/'))
if (IsDirectorySeparator(path[0]))
{
// There is no valid way to specify a relative path with two initial slashes
return !((path[1] == '\\') || (path[1] == '/'));
return !(IsDirectorySeparator(Path.DirectorySeparatorChar));
}

// The only way to specify a fixed path that doesn't begin with two slashes
// is the drive, colon, slash format- i.e. C:\
return !((path.Length >= 3)
&& (path[1] == ':')
&& ((path[2] == '\\') || (path[2] == '/')));
&& (path[1] == Path.VolumeSeparatorChar)
&& (IsDirectorySeparator(path[2])));
}

internal static bool IsDirectorySeparator(char c)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public void AddExtendedPathPrefixTest(string path, string expected)
InlineData(@"\\?\C:\Path", false),
InlineData(@"Path", true),
InlineData(@"X", true)]
[PlatformSpecific(PlatformID.Windows)]
public void IsPathRelative(string path, bool expected)
{
Assert.Equal(expected, PathInternal.IsPathRelative(path));
Expand Down
35 changes: 30 additions & 5 deletions src/System.IO.FileSystem/tests/Directory/CreateDirectory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -210,20 +210,44 @@ public void DirectoryWithComponentLongerThanMaxComponentAsPath_ThrowsPathTooLong

[Fact]
[PlatformSpecific(PlatformID.Windows)]
public void DirectoryLongerThanMaxPathAsPath_ThrowsPathTooLongException()
public void DirectoryLongerThanMaxPath_Succeeds()
{
var paths = IOInputs.GetPathsLongerThanMaxPath();
Assert.All(paths, (path) =>
{
DirectoryInfo result = Create(path);
Assert.True(Directory.Exists(result.FullName));
});
}

[Fact]
[PlatformSpecific(PlatformID.Windows)]
public void DirectoryLongerThanMaxLongPath_ThrowsPathTooLongException()
{
var paths = IOInputs.GetPathsLongerThanMaxLongPath();
Assert.All(paths, (path) =>
{
Assert.Throws<PathTooLongException>(() => Create(path));
});
}

[Fact]
[PlatformSpecific(PlatformID.Windows)]
public void DirectoryLongerThanMaxLongPathWithExtendedSyntax_ThrowsPathTooLongException()
{
var paths = IOInputs.GetPathsLongerThanMaxLongPath(useExtendedSyntax: true);
Assert.All(paths, (path) =>
{
Assert.Throws<PathTooLongException>(() => Create(path));
});
}


[Fact]
[PlatformSpecific(PlatformID.Windows)]
public void ExtendedDirectoryLongerThanLegacyMaxPathSucceeds()
public void ExtendedDirectoryLongerThanLegacyMaxPath_Succeeds()
{
var paths = IOInputs.GetPathsLongerThanMaxPath(useExtendedSyntax: true, includeExtendedMaxPath: false);
var paths = IOInputs.GetPathsLongerThanMaxPath(useExtendedSyntax: true);
Assert.All(paths, (path) =>
{
Assert.True(Create(path).Exists);
Expand All @@ -232,12 +256,13 @@ public void ExtendedDirectoryLongerThanLegacyMaxPathSucceeds()

[Fact]
[PlatformSpecific(PlatformID.Windows)]
public void DirectoryLongerThanMaxDirectoryAsPath_ThrowsPathTooLongException()
public void DirectoryLongerThanMaxDirectoryAsPath_Succeeds()
{
var paths = IOInputs.GetPathsLongerThanMaxDirectory();
Assert.All(paths, (path) =>
{
Assert.Throws<PathTooLongException>(() => Create(path));
var result = Create(path);
Assert.True(Directory.Exists(result.FullName));
});
}

Expand Down
4 changes: 2 additions & 2 deletions src/System.IO.FileSystem/tests/Directory/Exists.cs
Original file line number Diff line number Diff line change
Expand Up @@ -105,9 +105,9 @@ public void DotDotAsPath_ReturnsTrue()
}

[Fact]
public void DirectoryLongerThanMaxPathAsPath_DoesntThrow()
public void DirectoryLongerThanMaxLongPath_DoesntThrow()
{
Assert.All((IOInputs.GetPathsLongerThanMaxPath()), (path) =>
Assert.All((IOInputs.GetPathsLongerThanMaxLongPath()), (path) =>
{
Assert.False(Exists(path), path);
});
Expand Down
22 changes: 17 additions & 5 deletions src/System.IO.FileSystem/tests/Directory/Move.cs
Original file line number Diff line number Diff line change
Expand Up @@ -127,11 +127,11 @@ public void IncludeSubdirectories()
}

[Fact]
public void Path_Longer_Than_MaxPath_Throws_Exception()
public void Path_Longer_Than_MaxLongPath_Throws_Exception()
{
string testDir = GetTestFilePath();
Directory.CreateDirectory(testDir);
Assert.All((IOInputs.GetPathsLongerThanMaxPath()), (path) =>
Assert.All((IOInputs.GetPathsLongerThanMaxLongPath()), (path) =>
{
Assert.Throws<PathTooLongException>(() => Move(testDir, path));
Assert.Throws<PathTooLongException>(() => Move(path, testDir));
Expand All @@ -144,14 +144,26 @@ public void Path_Longer_Than_MaxPath_Throws_Exception()

[Fact]
[PlatformSpecific(PlatformID.Windows)]
public void Path_With_Longer_Than_MaxDirectory_Throws_Exception()
public void Path_With_Longer_Than_MaxDirectory_Succeeds()
{
string testDir = GetTestFilePath();
Directory.CreateDirectory(testDir);
Assert.True(Directory.Exists(testDir), "test directory should exist");
Assert.All((IOInputs.GetPathsLongerThanMaxDirectory()), (path) =>
{
Assert.Throws<PathTooLongException>(() => Move(testDir, path));
Assert.Throws<PathTooLongException>(() => Move(path, testDir));
string baseDestinationPath = Path.GetDirectoryName(path);
if (!Directory.Exists(baseDestinationPath))
{
Directory.CreateDirectory(baseDestinationPath);
}
Assert.True(Directory.Exists(baseDestinationPath), "base destination path should exist");

Move(testDir, path);
Assert.False(Directory.Exists(testDir), "source directory should exist");
Assert.True(Directory.Exists(path), "destination directory should exist");
Move(path, testDir);
Assert.False(Directory.Exists(path), "source directory should exist");
Assert.True(Directory.Exists(testDir), "destination directory should exist");
});
}

Expand Down
32 changes: 31 additions & 1 deletion src/System.IO.FileSystem/tests/File/Move.cs
Original file line number Diff line number Diff line change
Expand Up @@ -144,13 +144,43 @@ public void FileNameWithSignificantWhitespace()
}

[Fact]
[PlatformSpecific(PlatformID.Windows)]
public void MaxPath_Windows()
{
// Create a destination path longer than the traditional Windows limit of 256 characters,
// but under the long path limitation (32K).

string testFileSource = Path.Combine(TestDirectory, GetTestFileName());
File.Create(testFileSource).Dispose();
Assert.True(File.Exists(testFileSource), "test file should exist");

Assert.All(IOInputs.GetPathsLongerThanMaxPath(), (path) =>
{
string baseDestinationPath = Path.GetDirectoryName(path);
if (!Directory.Exists(baseDestinationPath))
{
Directory.CreateDirectory(baseDestinationPath);
}
Assert.True(Directory.Exists(baseDestinationPath), "base destination path should exist");

Move(testFileSource, path);
Assert.True(File.Exists(path), "moved test file should exist");
File.Delete(testFileSource);
Assert.False(File.Exists(testFileSource), "source test file should not exist");
Move(path, testFileSource);
Assert.True(File.Exists(testFileSource), "restored test file should exist");
});
}

[Fact]
[PlatformSpecific(PlatformID.AnyUnix)]
public void LongPath()
{
//Create a destination path longer than the traditional Windows limit of 256 characters
string testFileSource = Path.Combine(TestDirectory, GetTestFileName());
File.Create(testFileSource).Dispose();

Assert.All(IOInputs.GetPathsLongerThanMaxPath(), (path) =>
Assert.All(IOInputs.GetPathsLongerThanMaxLongPath(), (path) =>
{
Assert.Throws<PathTooLongException>(() => Move(testFileSource, path));
File.Delete(testFileSource);
Expand Down
18 changes: 12 additions & 6 deletions src/System.IO.FileSystem/tests/PortedCommon/IOInputs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,18 @@ internal static class IOInputs
// Max path length (minus trailing \0). Unix values vary system to system; just using really long values here likely to be more than on the average system.
public static readonly int MaxPath = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? 259 : 10000;

// Same as MaxPath on Unix
public static readonly int MaxLongPath = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? MaxExtendedPath : MaxPath;

// Windows specific, this is the maximum length that can be passed to APIs taking directory names, such as Directory.CreateDirectory & Directory.Move.
// Does not include the trailing \0.
// We now do the appropriate wrapping to allow creating longer directories. Like MaxPath, this is a legacy restriction.
public static readonly int MaxDirectory = 247;

// Windows specific, this is the maximum length that can be passed using extended syntax. Does not include the trailing \0.
public static readonly int MaxExtendedPath = short.MaxValue - 1;


public const int MaxComponent = 255;

public const string ExtendedPrefix = @"\\?\";
Expand Down Expand Up @@ -218,16 +223,17 @@ public static IEnumerable<string> GetPathsLongerThanMaxDirectory()
yield return GetLongPath(MaxDirectory + 3);
}

public static IEnumerable<string> GetPathsLongerThanMaxPath(bool useExtendedSyntax = false, bool includeExtendedMaxPath = true)
public static IEnumerable<string> GetPathsLongerThanMaxPath(bool useExtendedSyntax = false)
{
yield return GetLongPath(MaxPath + 1, useExtendedSyntax);
yield return GetLongPath(MaxPath + 2, useExtendedSyntax);
yield return GetLongPath(MaxPath + 3, useExtendedSyntax);
if (includeExtendedMaxPath)
{
yield return GetLongPath(MaxExtendedPath + 1, useExtendedSyntax);
yield return GetLongPath(MaxExtendedPath + 2, useExtendedSyntax);
}
}

public static IEnumerable<string> GetPathsLongerThanMaxLongPath(bool useExtendedSyntax = false)
{
yield return GetLongPath(MaxExtendedPath + 1 - (useExtendedSyntax ? 0 : ExtendedPrefix.Length), useExtendedSyntax);
yield return GetLongPath(MaxExtendedPath + 2 - (useExtendedSyntax ? 0 : ExtendedPrefix.Length), useExtendedSyntax);
}

private static string GetLongPath(int characterCount, bool extended = false)
Expand Down
Loading

0 comments on commit 50a3b1a

Please sign in to comment.