diff options
author | stephentoub <stoub@microsoft.com> | 2015-07-06 18:23:57 +0300 |
---|---|---|
committer | stephentoub <stoub@microsoft.com> | 2015-07-07 03:53:17 +0300 |
commit | c4bc3bbfae8a9bf50defcfb8d6e13905f1209240 (patch) | |
tree | 87c77669aa015c513efba0500d3e96f3fc876adb /src | |
parent | 3021cbb60162504a6307e3f80e3bc4cf1f7dc86d (diff) |
Implement System.IO.Path on Unix
Previously, System.IO.Path was implemented in mscorlib (with calls into Win32 on Windows / the Win32 PAL layer in libcoreclr on Unix) and type-forwarded from System.Runtime.Extensions. With #2234, we switched the Windows build to using System.IO.Path from System.Runtime.Extensions and Path.cs from corefx, but the Unix version was left still using the mscorlib implementation. One of the reasons for switching to the Path.cs in corefx, however, was to better enable a bunch of fixes to the behavior of the Unix implementation.
This change implements Path.cs in corefx for Unix and switches the Unix builds (Linux, OS X, and FreeBSD) over to using the implementation in corefx rather than type-forwarding to mscorlib.
Primary changes:
- Defined MaxPath / MaxComponent length in the Unix build in terms of pathconf
- Implemented the Unix build's NormalizePath to properly handle normalizing unnecessary "/", ".", and ".."s in the path.
- Implemented the Unix build's GetTempPath in terms of the TMPDIR environment variable (same logic that's in the Win32 PAL layer's implementation).
- Implemented the Unix build's GetTempFileName in terms of mkstemps.
- Implemented the Unix build's GetRandomFileName in terms of OpenSSL's RAND_pseudo_bytes, same as is done in System.Security.Cryptography.RandomNumberGenerator (which we can't reference due to layering).
- Centralized the wrapping of getcwd, which is now used from both System.Runtime.Extensions and System.IO.FileSystem. The previous usage of it always created a temporary array of size MaxPath, which can now be quite big, so the wrapper first tries to use stack space, and then graduates to retrying with larger and larger buffer sizes.
- Centralized the MaxPath and MaxName logic, which is now used by three libraries: System.Diagnostics.Process, System.IO.FileSystem, and now System.Runtime.Extensions.
- Centralized the throwing of PathTooLongException, as it now requires arguments for how long are the maximum path and component names.
- Cleaned up a few other minor things in the shared Path.cs (e.g. naming Boolean arguments) that were confusing me as I was working through the code.
- Added FreeBSD to the .sln and .csproj files, as otherwise the FreeBSD build for this library would be broken (previously it inherited the mscorlib implementation as did the other Unix OSes).
- Added a bunch of tests, some general across both Windows and Unix, but others specifically focused on Unix, which is more lenient than is Windows in the paths it allows.
Diffstat (limited to 'src')
22 files changed, 648 insertions, 313 deletions
diff --git a/src/Common/src/Interop/Unix/libc/Interop.getcwd.cs b/src/Common/src/Interop/Unix/libc/Interop.getcwd.cs index 6081cfd0a8..5489372a60 100644 --- a/src/Common/src/Interop/Unix/libc/Interop.getcwd.cs +++ b/src/Common/src/Interop/Unix/libc/Interop.getcwd.cs @@ -10,7 +10,66 @@ internal static partial class Interop { internal static partial class libc { + internal static unsafe string getcwd() + { + const int StackLimit = 256; + + // First try to get the path into a buffer on the stack + byte* stackBuf = stackalloc byte[StackLimit]; + string result = getcwd(stackBuf, StackLimit); + if (result != null) + { + return result; + } + + // If that was too small, try increasing large buffer sizes + // until we get one that works or until we hit MaxPath. + int maxPath = Interop.libc.MaxPath; + if (StackLimit < maxPath) + { + int bufferSize = StackLimit; + do + { + checked { bufferSize *= 2; } + var buf = new byte[Math.Min(bufferSize, maxPath)]; + fixed (byte* ptr = buf) + { + result = getcwd(ptr, buf.Length); + if (result != null) + { + return result; + } + } + } + while (bufferSize < maxPath); + } + + // If we couldn't get the cwd with a MaxPath-sized buffer, something's wrong. + throw Interop.GetExceptionForIoErrno(Interop.Errors.ENAMETOOLONG); + } + + private static unsafe string getcwd(byte* ptr, int bufferSize) + { + // Call the real getcwd + byte* result = getcwd(ptr, (size_t)bufferSize); + + // If it returned non-null, the null-terminated path is in the buffer + if (result != null) + { + return Marshal.PtrToStringAnsi((IntPtr)ptr); + } + + // Otherwise, if it failed due to the buffer being too small, return null; + // for anything else, throw. + int errno = Marshal.GetLastWin32Error(); + if (errno == Interop.Errors.ERANGE) + { + return null; + } + throw Interop.GetExceptionForIoErrno(errno); + } + [DllImport(Libraries.Libc, SetLastError = true)] - internal static extern unsafe byte* getcwd(byte* buf, size_t bufSize); + private static extern unsafe byte* getcwd(byte* buf, size_t bufSize); } } diff --git a/src/Common/src/Interop/Unix/libc/Interop.mkstemps.cs b/src/Common/src/Interop/Unix/libc/Interop.mkstemps.cs new file mode 100644 index 0000000000..628ee61fcf --- /dev/null +++ b/src/Common/src/Interop/Unix/libc/Interop.mkstemps.cs @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Runtime.InteropServices; + +internal static partial class Interop +{ + internal static partial class libc + { + [DllImport(Libraries.Libc, SetLastError = true)] + internal static extern int mkstemps(byte[] template, int suffixlen); + } +} diff --git a/src/Common/src/Interop/Unix/libc/Interop.pathconf.cs b/src/Common/src/Interop/Unix/libc/Interop.pathconf.cs index cd59569e5d..9587029193 100644 --- a/src/Common/src/Interop/Unix/libc/Interop.pathconf.cs +++ b/src/Common/src/Interop/Unix/libc/Interop.pathconf.cs @@ -8,6 +8,21 @@ internal static partial class Interop { internal static partial class libc { + /// <summary>The maximum path length for the system. -1 if it hasn't yet been initialized.</summary> + private static int s_maxPath = -1; + /// <summary>The maximum name length for the system. -1 if it hasn't yet been initialized.</summary> + private static int s_maxName = -1; + + internal static int MaxPath + { + get { return Interop.libc.GetPathConfValue(ref s_maxPath, Interop.libc.PathConfNames._PC_PATH_MAX, Interop.libc.DEFAULT_PC_PATH_MAX); } + } + + internal static int MaxName + { + get { return Interop.libc.GetPathConfValue(ref s_maxName, Interop.libc.PathConfNames._PC_NAME_MAX, Interop.libc.DEFAULT_PC_NAME_MAX); } + } + /// <summary> /// Gets a pathconf value by name. If the cached value is less than zero (meaning not yet initialized), /// pathconf is used to retrieve the value, which is then stored into the field. @@ -16,13 +31,14 @@ internal static partial class Interop /// <param name="cachedValue">The field used to cache the pathconf value.</param> /// <param name="pathConfName">The name of the pathconf value.</param> /// <param name="defaultValue">The default value to use in case pathconf fails.</param> - internal static void GetPathConfValue(ref int cachedValue, int pathConfName, int defaultValue) + private static int GetPathConfValue(ref int cachedValue, int pathConfName, int defaultValue) { if (cachedValue < 0) // benign race condition on cached value { int result = Interop.libc.pathconf("/", pathConfName); cachedValue = result >= 0 ? result : defaultValue; } + return cachedValue; } [DllImport(Libraries.Libc, SetLastError = true)] diff --git a/src/Common/src/Interop/Unix/libcrypto/Interop.Initialization.cs b/src/Common/src/Interop/Unix/libcrypto/Interop.Initialization.cs index d2f4666ce2..49b7ef654c 100644 --- a/src/Common/src/Interop/Unix/libcrypto/Interop.Initialization.cs +++ b/src/Common/src/Interop/Unix/libcrypto/Interop.Initialization.cs @@ -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; using System.Runtime.InteropServices; using System.Security.Cryptography; @@ -15,7 +16,13 @@ internal static partial class Interop { if (Interop.libcoreclr.EnsureOpenSslInitialized() != 0) { - throw new CryptographicException(); + // Ideally this would be a CryptographicException, but we use + // OpenSSL in libraries lower than System.Security.Cryptography. + // It's not a big deal, though: this will already be wrapped in a + // TypeLoadException, and this failing means something is very + // wrong with the system's configuration and any code using + // these libraries will be unable to operate correctly. + throw new InvalidOperationException(); } // Load the SHA-2 hash algorithms, and anything else not in the default diff --git a/src/System.Console/src/Resources/Strings.resx b/src/System.Console/src/Resources/Strings.resx index 8b0494deac..ed9dd38e4d 100644 --- a/src/System.Console/src/Resources/Strings.resx +++ b/src/System.Console/src/Resources/Strings.resx @@ -160,7 +160,7 @@ <value>Could not find a part of the path '{0}'.</value> </data> <data name="IO_PathTooLong" xml:space="preserve"> - <value>The specified path, file name, or both are too long. The fully qualified file name must be less than 260 characters, and the directory name must be less than 248 characters.</value> + <value>The specified file name or path is too long, or a component of the specified path is too long.</value> </data> <data name="UnauthorizedAccess_IODenied_NoPathName" xml:space="preserve"> <value>Access to the path is denied.</value> diff --git a/src/System.Diagnostics.Debug/src/Resources/Strings.resx b/src/System.Diagnostics.Debug/src/Resources/Strings.resx index f0b7557df8..f3640db3fc 100644 --- a/src/System.Diagnostics.Debug/src/Resources/Strings.resx +++ b/src/System.Diagnostics.Debug/src/Resources/Strings.resx @@ -166,6 +166,6 @@ <value>The file '{0}' already exists.</value> </data> <data name="IO_PathTooLong" xml:space="preserve"> - <value>The specified path, file name, or both are too long. The fully qualified file name must be less than 260 characters, and the directory name must be less than 248 characters.</value> + <value>The specified file name or path is too long, or a component of the specified path is too long.</value> </data> </root>
\ No newline at end of file diff --git a/src/System.Diagnostics.Process/src/System/Diagnostics/Process.Linux.cs b/src/System.Diagnostics.Process/src/System/Diagnostics/Process.Linux.cs index 36db971061..8cc94afbe5 100644 --- a/src/System.Diagnostics.Process/src/System/Diagnostics/Process.Linux.cs +++ b/src/System.Diagnostics.Process/src/System/Diagnostics/Process.Linux.cs @@ -146,8 +146,7 @@ namespace System.Diagnostics private static string GetExePath() { // Determine the maximum size of a path - int maxPath = -1; - Interop.libc.GetPathConfValue(ref maxPath, Interop.libc.PathConfNames._PC_PATH_MAX, Interop.libc.DEFAULT_PC_PATH_MAX); + int maxPath = Interop.libc.MaxPath; // Start small with a buffer allocation, and grow only up to the max path for (int pathLen = 256; pathLen < maxPath; pathLen *= 2) diff --git a/src/System.IO.FileSystem.DriveInfo/src/Resources/Strings.resx b/src/System.IO.FileSystem.DriveInfo/src/Resources/Strings.resx index 56342652fb..829efdb6ec 100644 --- a/src/System.IO.FileSystem.DriveInfo/src/Resources/Strings.resx +++ b/src/System.IO.FileSystem.DriveInfo/src/Resources/Strings.resx @@ -157,7 +157,7 @@ <value>Could not find a part of the path '{0}'.</value> </data> <data name="IO_PathTooLong" xml:space="preserve"> - <value>The specified path, file name, or both are too long. The fully qualified file name must be less than 260 characters, and the directory name must be less than 248 characters.</value> + <value>The specified file name or path is too long, or a component of the specified path is too long.</value> </data> <data name="IO_SharingViolation_File" xml:space="preserve"> <value>The process cannot access the file '{0}' because it is being used by another process.</value> diff --git a/src/System.IO.FileSystem.Watcher/src/Resources/Strings.resx b/src/System.IO.FileSystem.Watcher/src/Resources/Strings.resx index 2a114c578f..ecace8c61a 100644 --- a/src/System.IO.FileSystem.Watcher/src/Resources/Strings.resx +++ b/src/System.IO.FileSystem.Watcher/src/Resources/Strings.resx @@ -142,7 +142,7 @@ <value>Specified file length was too large for the file system.</value> </data> <data name="IO_PathTooLong" xml:space="preserve"> - <value>The specified path, file name, or both are too long. The fully qualified file name must be less than 260 characters, and the directory name must be less than 248 characters.</value> + <value>The specified file name or path is too long, or a component of the specified path is too long.</value> </data> <data name="IO_PathNotFound_NoPathName" xml:space="preserve"> <value>Could not find a part of the path.</value> diff --git a/src/System.IO.FileSystem/src/Resources/Strings.resx b/src/System.IO.FileSystem/src/Resources/Strings.resx index e3e0eddb63..6911411f5c 100644 --- a/src/System.IO.FileSystem/src/Resources/Strings.resx +++ b/src/System.IO.FileSystem/src/Resources/Strings.resx @@ -235,7 +235,7 @@ <value>Could not find a part of the path '{0}'.</value> </data> <data name="IO_PathTooLong" xml:space="preserve"> - <value>The specified path, file name, or both are too long. The fully qualified file name must be less than 260 characters, and the directory name must be less than 248 characters.</value> + <value>The specified file name or path is too long, or a component of the specified path is too long.</value> </data> <data name="IO_SeekAppendOverwrite" xml:space="preserve"> <value>Unable seek backward to overwrite data that previously existed in a file opened in Append mode.</value> diff --git a/src/System.IO.FileSystem/src/System/IO/UnixFileSystem.cs b/src/System.IO.FileSystem/src/System/IO/UnixFileSystem.cs index c5bff19aab..d091dab8e6 100644 --- a/src/System.IO.FileSystem/src/System/IO/UnixFileSystem.cs +++ b/src/System.IO.FileSystem/src/System/IO/UnixFileSystem.cs @@ -13,28 +13,9 @@ namespace System.IO /// <summary>Provides an implementation of FileSystem for Unix systems.</summary> internal sealed partial class UnixFileSystem : FileSystem { - /// <summary>The maximum path length for the system. -1 if it hasn't yet been initialized.</summary> - private static int _maxPath = -1; - /// <summary>The maximum name length for the system. -1 if it hasn't yet been initialized.</summary> - private static int _maxName = -1; + public override int MaxPath { get { return Interop.libc.MaxPath; } } - public override int MaxPath - { - get - { - Interop.libc.GetPathConfValue(ref _maxPath, Interop.libc.PathConfNames._PC_PATH_MAX, Interop.libc.DEFAULT_PC_PATH_MAX); - return _maxPath; - } - } - - public override int MaxDirectoryPath - { - get - { - Interop.libc.GetPathConfValue(ref _maxName, Interop.libc.PathConfNames._PC_NAME_MAX, Interop.libc.DEFAULT_PC_NAME_MAX); - return _maxName; - } - } + public override int MaxDirectoryPath { get { return Interop.libc.MaxName; } } public override FileStreamBase Open(string fullPath, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options, FileStream parent) { @@ -588,14 +569,9 @@ namespace System.IO return name == "." || name == ".."; } - public override unsafe string GetCurrentDirectory() + public override string GetCurrentDirectory() { - byte[] pathBuffer = new byte[MaxPath]; - fixed (byte* ptr = pathBuffer) - { - while (Interop.CheckIoPtr((IntPtr)Interop.libc.getcwd(ptr, (IntPtr)pathBuffer.Length))) ; - return Marshal.PtrToStringAnsi((IntPtr)ptr); - } + return Interop.libc.getcwd(); } public override void SetCurrentDirectory(string fullPath) diff --git a/src/System.IO.MemoryMappedFiles/src/Resources/Strings.resx b/src/System.IO.MemoryMappedFiles/src/Resources/Strings.resx index 5dda7b7cc8..23031fc8a5 100644 --- a/src/System.IO.MemoryMappedFiles/src/Resources/Strings.resx +++ b/src/System.IO.MemoryMappedFiles/src/Resources/Strings.resx @@ -151,7 +151,7 @@ <value>Could not find a part of the path.</value> </data> <data name="IO_PathTooLong" xml:space="preserve"> - <value>The specified path, file name, or both are too long. The fully qualified file name must be less than 260 characters, and the directory name must be less than 248 characters.</value> + <value>The specified file name or path is too long, or a component of the specified path is too long.</value> </data> <data name="UnauthorizedAccess_IODenied_Path" xml:space="preserve"> <value>Access to the path '{0}' is denied.</value> diff --git a/src/System.IO.Pipes/src/Resources/Strings.resx b/src/System.IO.Pipes/src/Resources/Strings.resx index 70fbdc8c6e..a91daeb030 100644 --- a/src/System.IO.Pipes/src/Resources/Strings.resx +++ b/src/System.IO.Pipes/src/Resources/Strings.resx @@ -244,7 +244,7 @@ <value>Could not find a part of the path.</value> </data> <data name="IO_PathTooLong" xml:space="preserve"> - <value>The specified path, file name, or both are too long. The fully qualified file name must be less than 260 characters, and the directory name must be less than 248 characters.</value> + <value>The specified file name or path is too long, or a component of the specified path is too long.</value> </data> <data name="NotSupported_UnreadableStream" xml:space="preserve"> <value>Stream does not support reading.</value> diff --git a/src/System.Private.Uri/src/Resources/Strings.resx b/src/System.Private.Uri/src/Resources/Strings.resx index 961b75c1cf..31d7dad3f2 100644 --- a/src/System.Private.Uri/src/Resources/Strings.resx +++ b/src/System.Private.Uri/src/Resources/Strings.resx @@ -259,6 +259,6 @@ <value>The file '{0}' already exists.</value> </data> <data name="IO_PathTooLong" xml:space="preserve"> - <value>The specified path, file name, or both are too long. The fully qualified file name must be less than 260 characters, and the directory name must be less than 248 characters.</value> + <value>The specified file name or path is too long, or a component of the specified path is too long.</value> </data> </root>
\ No newline at end of file diff --git a/src/System.Runtime.Extensions/System.Runtime.Extensions.sln b/src/System.Runtime.Extensions/System.Runtime.Extensions.sln index 241079d7e3..1aac9e5952 100644 --- a/src/System.Runtime.Extensions/System.Runtime.Extensions.sln +++ b/src/System.Runtime.Extensions/System.Runtime.Extensions.sln @@ -1,6 +1,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 14 -VisualStudioVersion = 14.0.22817.0 +VisualStudioVersion = 14.0.22823.1 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Runtime.Extensions.Tests", "tests\System.Runtime.Extensions.Tests.csproj", "{6C314C9B-3D28-4B05-9B4C-B57A00A9B3B9}" EndProject @@ -13,162 +13,48 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Runtime.Extensions.C EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Debug|ARM = Debug|ARM - Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 + FreeBSD_Debug|Any CPU = FreeBSD_Debug|Any CPU + FreeBSD_Release|Any CPU = FreeBSD_Release|Any CPU Linux_Debug|Any CPU = Linux_Debug|Any CPU - Linux_Debug|ARM = Linux_Debug|ARM - Linux_Debug|x64 = Linux_Debug|x64 - Linux_Debug|x86 = Linux_Debug|x86 Linux_Release|Any CPU = Linux_Release|Any CPU - Linux_Release|ARM = Linux_Release|ARM - Linux_Release|x64 = Linux_Release|x64 - Linux_Release|x86 = Linux_Release|x86 OSX_Debug|Any CPU = OSX_Debug|Any CPU - OSX_Debug|ARM = OSX_Debug|ARM - OSX_Debug|x64 = OSX_Debug|x64 - OSX_Debug|x86 = OSX_Debug|x86 OSX_Release|Any CPU = OSX_Release|Any CPU - OSX_Release|ARM = OSX_Release|ARM - OSX_Release|x64 = OSX_Release|x64 - OSX_Release|x86 = OSX_Release|x86 - Release|Any CPU = Release|Any CPU - Release|ARM = Release|ARM - Release|x64 = Release|x64 - Release|x86 = Release|x86 Windows_Debug|Any CPU = Windows_Debug|Any CPU - Windows_Debug|ARM = Windows_Debug|ARM - Windows_Debug|x64 = Windows_Debug|x64 - Windows_Debug|x86 = Windows_Debug|x86 Windows_Release|Any CPU = Windows_Release|Any CPU - Windows_Release|ARM = Windows_Release|ARM - Windows_Release|x64 = Windows_Release|x64 - Windows_Release|x86 = Windows_Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {6C314C9B-3D28-4B05-9B4C-B57A00A9B3B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6C314C9B-3D28-4B05-9B4C-B57A00A9B3B9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6C314C9B-3D28-4B05-9B4C-B57A00A9B3B9}.Debug|ARM.ActiveCfg = Debug|Any CPU - {6C314C9B-3D28-4B05-9B4C-B57A00A9B3B9}.Debug|x64.ActiveCfg = Debug|Any CPU - {6C314C9B-3D28-4B05-9B4C-B57A00A9B3B9}.Debug|x86.ActiveCfg = Debug|Any CPU + {6C314C9B-3D28-4B05-9B4C-B57A00A9B3B9}.FreeBSD_Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6C314C9B-3D28-4B05-9B4C-B57A00A9B3B9}.FreeBSD_Debug|Any CPU.Build.0 = Debug|Any CPU + {6C314C9B-3D28-4B05-9B4C-B57A00A9B3B9}.FreeBSD_Release|Any CPU.ActiveCfg = Release|Any CPU + {6C314C9B-3D28-4B05-9B4C-B57A00A9B3B9}.FreeBSD_Release|Any CPU.Build.0 = Release|Any CPU {6C314C9B-3D28-4B05-9B4C-B57A00A9B3B9}.Linux_Debug|Any CPU.ActiveCfg = Debug|Any CPU {6C314C9B-3D28-4B05-9B4C-B57A00A9B3B9}.Linux_Debug|Any CPU.Build.0 = Debug|Any CPU - {6C314C9B-3D28-4B05-9B4C-B57A00A9B3B9}.Linux_Debug|ARM.ActiveCfg = Debug|Any CPU - {6C314C9B-3D28-4B05-9B4C-B57A00A9B3B9}.Linux_Debug|ARM.Build.0 = Debug|Any CPU - {6C314C9B-3D28-4B05-9B4C-B57A00A9B3B9}.Linux_Debug|x64.ActiveCfg = Debug|Any CPU - {6C314C9B-3D28-4B05-9B4C-B57A00A9B3B9}.Linux_Debug|x64.Build.0 = Debug|Any CPU - {6C314C9B-3D28-4B05-9B4C-B57A00A9B3B9}.Linux_Debug|x86.ActiveCfg = Debug|Any CPU - {6C314C9B-3D28-4B05-9B4C-B57A00A9B3B9}.Linux_Debug|x86.Build.0 = Debug|Any CPU {6C314C9B-3D28-4B05-9B4C-B57A00A9B3B9}.Linux_Release|Any CPU.ActiveCfg = Release|Any CPU {6C314C9B-3D28-4B05-9B4C-B57A00A9B3B9}.Linux_Release|Any CPU.Build.0 = Release|Any CPU - {6C314C9B-3D28-4B05-9B4C-B57A00A9B3B9}.Linux_Release|ARM.ActiveCfg = Release|Any CPU - {6C314C9B-3D28-4B05-9B4C-B57A00A9B3B9}.Linux_Release|ARM.Build.0 = Release|Any CPU - {6C314C9B-3D28-4B05-9B4C-B57A00A9B3B9}.Linux_Release|x64.ActiveCfg = Release|Any CPU - {6C314C9B-3D28-4B05-9B4C-B57A00A9B3B9}.Linux_Release|x64.Build.0 = Release|Any CPU - {6C314C9B-3D28-4B05-9B4C-B57A00A9B3B9}.Linux_Release|x86.ActiveCfg = Release|Any CPU - {6C314C9B-3D28-4B05-9B4C-B57A00A9B3B9}.Linux_Release|x86.Build.0 = Release|Any CPU {6C314C9B-3D28-4B05-9B4C-B57A00A9B3B9}.OSX_Debug|Any CPU.ActiveCfg = Debug|Any CPU {6C314C9B-3D28-4B05-9B4C-B57A00A9B3B9}.OSX_Debug|Any CPU.Build.0 = Debug|Any CPU - {6C314C9B-3D28-4B05-9B4C-B57A00A9B3B9}.OSX_Debug|ARM.ActiveCfg = Debug|Any CPU - {6C314C9B-3D28-4B05-9B4C-B57A00A9B3B9}.OSX_Debug|ARM.Build.0 = Debug|Any CPU - {6C314C9B-3D28-4B05-9B4C-B57A00A9B3B9}.OSX_Debug|x64.ActiveCfg = Debug|Any CPU - {6C314C9B-3D28-4B05-9B4C-B57A00A9B3B9}.OSX_Debug|x64.Build.0 = Debug|Any CPU - {6C314C9B-3D28-4B05-9B4C-B57A00A9B3B9}.OSX_Debug|x86.ActiveCfg = Debug|Any CPU - {6C314C9B-3D28-4B05-9B4C-B57A00A9B3B9}.OSX_Debug|x86.Build.0 = Debug|Any CPU {6C314C9B-3D28-4B05-9B4C-B57A00A9B3B9}.OSX_Release|Any CPU.ActiveCfg = Release|Any CPU {6C314C9B-3D28-4B05-9B4C-B57A00A9B3B9}.OSX_Release|Any CPU.Build.0 = Release|Any CPU - {6C314C9B-3D28-4B05-9B4C-B57A00A9B3B9}.OSX_Release|ARM.ActiveCfg = Release|Any CPU - {6C314C9B-3D28-4B05-9B4C-B57A00A9B3B9}.OSX_Release|ARM.Build.0 = Release|Any CPU - {6C314C9B-3D28-4B05-9B4C-B57A00A9B3B9}.OSX_Release|x64.ActiveCfg = Release|Any CPU - {6C314C9B-3D28-4B05-9B4C-B57A00A9B3B9}.OSX_Release|x64.Build.0 = Release|Any CPU - {6C314C9B-3D28-4B05-9B4C-B57A00A9B3B9}.OSX_Release|x86.ActiveCfg = Release|Any CPU - {6C314C9B-3D28-4B05-9B4C-B57A00A9B3B9}.OSX_Release|x86.Build.0 = Release|Any CPU - {6C314C9B-3D28-4B05-9B4C-B57A00A9B3B9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6C314C9B-3D28-4B05-9B4C-B57A00A9B3B9}.Release|Any CPU.Build.0 = Release|Any CPU - {6C314C9B-3D28-4B05-9B4C-B57A00A9B3B9}.Release|ARM.ActiveCfg = Release|Any CPU - {6C314C9B-3D28-4B05-9B4C-B57A00A9B3B9}.Release|x64.ActiveCfg = Release|Any CPU - {6C314C9B-3D28-4B05-9B4C-B57A00A9B3B9}.Release|x86.ActiveCfg = Release|Any CPU {6C314C9B-3D28-4B05-9B4C-B57A00A9B3B9}.Windows_Debug|Any CPU.ActiveCfg = Debug|Any CPU {6C314C9B-3D28-4B05-9B4C-B57A00A9B3B9}.Windows_Debug|Any CPU.Build.0 = Debug|Any CPU - {6C314C9B-3D28-4B05-9B4C-B57A00A9B3B9}.Windows_Debug|ARM.ActiveCfg = Debug|Any CPU - {6C314C9B-3D28-4B05-9B4C-B57A00A9B3B9}.Windows_Debug|ARM.Build.0 = Debug|Any CPU - {6C314C9B-3D28-4B05-9B4C-B57A00A9B3B9}.Windows_Debug|x64.ActiveCfg = Debug|Any CPU - {6C314C9B-3D28-4B05-9B4C-B57A00A9B3B9}.Windows_Debug|x64.Build.0 = Debug|Any CPU - {6C314C9B-3D28-4B05-9B4C-B57A00A9B3B9}.Windows_Debug|x86.ActiveCfg = Debug|Any CPU - {6C314C9B-3D28-4B05-9B4C-B57A00A9B3B9}.Windows_Debug|x86.Build.0 = Debug|Any CPU {6C314C9B-3D28-4B05-9B4C-B57A00A9B3B9}.Windows_Release|Any CPU.ActiveCfg = Release|Any CPU {6C314C9B-3D28-4B05-9B4C-B57A00A9B3B9}.Windows_Release|Any CPU.Build.0 = Release|Any CPU - {6C314C9B-3D28-4B05-9B4C-B57A00A9B3B9}.Windows_Release|ARM.ActiveCfg = Release|Any CPU - {6C314C9B-3D28-4B05-9B4C-B57A00A9B3B9}.Windows_Release|ARM.Build.0 = Release|Any CPU - {6C314C9B-3D28-4B05-9B4C-B57A00A9B3B9}.Windows_Release|x64.ActiveCfg = Release|Any CPU - {6C314C9B-3D28-4B05-9B4C-B57A00A9B3B9}.Windows_Release|x64.Build.0 = Release|Any CPU - {6C314C9B-3D28-4B05-9B4C-B57A00A9B3B9}.Windows_Release|x86.ActiveCfg = Release|Any CPU - {6C314C9B-3D28-4B05-9B4C-B57A00A9B3B9}.Windows_Release|x86.Build.0 = Release|Any CPU - {845D2B72-D8A4-42E5-9BE9-17639EC4FC1A}.Debug|Any CPU.ActiveCfg = Windows_Debug|Any CPU - {845D2B72-D8A4-42E5-9BE9-17639EC4FC1A}.Debug|Any CPU.Build.0 = Windows_Debug|Any CPU - {845D2B72-D8A4-42E5-9BE9-17639EC4FC1A}.Debug|ARM.ActiveCfg = Windows_Debug|Any CPU - {845D2B72-D8A4-42E5-9BE9-17639EC4FC1A}.Debug|ARM.Build.0 = Windows_Debug|Any CPU - {845D2B72-D8A4-42E5-9BE9-17639EC4FC1A}.Debug|x64.ActiveCfg = Windows_Debug|Any CPU - {845D2B72-D8A4-42E5-9BE9-17639EC4FC1A}.Debug|x64.Build.0 = Windows_Debug|Any CPU - {845D2B72-D8A4-42E5-9BE9-17639EC4FC1A}.Debug|x86.ActiveCfg = Windows_Debug|Any CPU - {845D2B72-D8A4-42E5-9BE9-17639EC4FC1A}.Debug|x86.Build.0 = Windows_Debug|Any CPU + {845D2B72-D8A4-42E5-9BE9-17639EC4FC1A}.FreeBSD_Debug|Any CPU.ActiveCfg = FreeBSD_Debug|Any CPU + {845D2B72-D8A4-42E5-9BE9-17639EC4FC1A}.FreeBSD_Debug|Any CPU.Build.0 = FreeBSD_Debug|Any CPU + {845D2B72-D8A4-42E5-9BE9-17639EC4FC1A}.FreeBSD_Release|Any CPU.ActiveCfg = FreeBSD_Release|Any CPU + {845D2B72-D8A4-42E5-9BE9-17639EC4FC1A}.FreeBSD_Release|Any CPU.Build.0 = FreeBSD_Release|Any CPU {845D2B72-D8A4-42E5-9BE9-17639EC4FC1A}.Linux_Debug|Any CPU.ActiveCfg = Linux_Debug|Any CPU {845D2B72-D8A4-42E5-9BE9-17639EC4FC1A}.Linux_Debug|Any CPU.Build.0 = Linux_Debug|Any CPU - {845D2B72-D8A4-42E5-9BE9-17639EC4FC1A}.Linux_Debug|ARM.ActiveCfg = Linux_Debug|Any CPU - {845D2B72-D8A4-42E5-9BE9-17639EC4FC1A}.Linux_Debug|ARM.Build.0 = Linux_Debug|Any CPU - {845D2B72-D8A4-42E5-9BE9-17639EC4FC1A}.Linux_Debug|x64.ActiveCfg = Linux_Debug|Any CPU - {845D2B72-D8A4-42E5-9BE9-17639EC4FC1A}.Linux_Debug|x64.Build.0 = Linux_Debug|Any CPU - {845D2B72-D8A4-42E5-9BE9-17639EC4FC1A}.Linux_Debug|x86.ActiveCfg = Linux_Debug|Any CPU - {845D2B72-D8A4-42E5-9BE9-17639EC4FC1A}.Linux_Debug|x86.Build.0 = Linux_Debug|Any CPU {845D2B72-D8A4-42E5-9BE9-17639EC4FC1A}.Linux_Release|Any CPU.ActiveCfg = Linux_Release|Any CPU {845D2B72-D8A4-42E5-9BE9-17639EC4FC1A}.Linux_Release|Any CPU.Build.0 = Linux_Release|Any CPU - {845D2B72-D8A4-42E5-9BE9-17639EC4FC1A}.Linux_Release|ARM.ActiveCfg = Linux_Release|Any CPU - {845D2B72-D8A4-42E5-9BE9-17639EC4FC1A}.Linux_Release|ARM.Build.0 = Linux_Release|Any CPU - {845D2B72-D8A4-42E5-9BE9-17639EC4FC1A}.Linux_Release|x64.ActiveCfg = Linux_Release|Any CPU - {845D2B72-D8A4-42E5-9BE9-17639EC4FC1A}.Linux_Release|x64.Build.0 = Linux_Release|Any CPU - {845D2B72-D8A4-42E5-9BE9-17639EC4FC1A}.Linux_Release|x86.ActiveCfg = Linux_Release|Any CPU - {845D2B72-D8A4-42E5-9BE9-17639EC4FC1A}.Linux_Release|x86.Build.0 = Linux_Release|Any CPU {845D2B72-D8A4-42E5-9BE9-17639EC4FC1A}.OSX_Debug|Any CPU.ActiveCfg = OSX_Debug|Any CPU {845D2B72-D8A4-42E5-9BE9-17639EC4FC1A}.OSX_Debug|Any CPU.Build.0 = OSX_Debug|Any CPU - {845D2B72-D8A4-42E5-9BE9-17639EC4FC1A}.OSX_Debug|ARM.ActiveCfg = OSX_Debug|Any CPU - {845D2B72-D8A4-42E5-9BE9-17639EC4FC1A}.OSX_Debug|ARM.Build.0 = OSX_Debug|Any CPU - {845D2B72-D8A4-42E5-9BE9-17639EC4FC1A}.OSX_Debug|x64.ActiveCfg = OSX_Debug|Any CPU - {845D2B72-D8A4-42E5-9BE9-17639EC4FC1A}.OSX_Debug|x64.Build.0 = OSX_Debug|Any CPU - {845D2B72-D8A4-42E5-9BE9-17639EC4FC1A}.OSX_Debug|x86.ActiveCfg = OSX_Debug|Any CPU - {845D2B72-D8A4-42E5-9BE9-17639EC4FC1A}.OSX_Debug|x86.Build.0 = OSX_Debug|Any CPU {845D2B72-D8A4-42E5-9BE9-17639EC4FC1A}.OSX_Release|Any CPU.ActiveCfg = OSX_Release|Any CPU {845D2B72-D8A4-42E5-9BE9-17639EC4FC1A}.OSX_Release|Any CPU.Build.0 = OSX_Release|Any CPU - {845D2B72-D8A4-42E5-9BE9-17639EC4FC1A}.OSX_Release|ARM.ActiveCfg = OSX_Release|Any CPU - {845D2B72-D8A4-42E5-9BE9-17639EC4FC1A}.OSX_Release|ARM.Build.0 = OSX_Release|Any CPU - {845D2B72-D8A4-42E5-9BE9-17639EC4FC1A}.OSX_Release|x64.ActiveCfg = OSX_Release|Any CPU - {845D2B72-D8A4-42E5-9BE9-17639EC4FC1A}.OSX_Release|x64.Build.0 = OSX_Release|Any CPU - {845D2B72-D8A4-42E5-9BE9-17639EC4FC1A}.OSX_Release|x86.ActiveCfg = OSX_Release|Any CPU - {845D2B72-D8A4-42E5-9BE9-17639EC4FC1A}.OSX_Release|x86.Build.0 = OSX_Release|Any CPU - {845D2B72-D8A4-42E5-9BE9-17639EC4FC1A}.Release|Any CPU.ActiveCfg = Windows_Release|Any CPU - {845D2B72-D8A4-42E5-9BE9-17639EC4FC1A}.Release|Any CPU.Build.0 = Windows_Release|Any CPU - {845D2B72-D8A4-42E5-9BE9-17639EC4FC1A}.Release|ARM.ActiveCfg = Windows_Release|Any CPU - {845D2B72-D8A4-42E5-9BE9-17639EC4FC1A}.Release|ARM.Build.0 = Windows_Release|Any CPU - {845D2B72-D8A4-42E5-9BE9-17639EC4FC1A}.Release|x64.ActiveCfg = Windows_Release|Any CPU - {845D2B72-D8A4-42E5-9BE9-17639EC4FC1A}.Release|x64.Build.0 = Windows_Release|Any CPU - {845D2B72-D8A4-42E5-9BE9-17639EC4FC1A}.Release|x86.ActiveCfg = Windows_Release|Any CPU - {845D2B72-D8A4-42E5-9BE9-17639EC4FC1A}.Release|x86.Build.0 = Windows_Release|Any CPU {845D2B72-D8A4-42E5-9BE9-17639EC4FC1A}.Windows_Debug|Any CPU.ActiveCfg = Windows_Debug|Any CPU {845D2B72-D8A4-42E5-9BE9-17639EC4FC1A}.Windows_Debug|Any CPU.Build.0 = Windows_Debug|Any CPU - {845D2B72-D8A4-42E5-9BE9-17639EC4FC1A}.Windows_Debug|ARM.ActiveCfg = Windows_Debug|Any CPU - {845D2B72-D8A4-42E5-9BE9-17639EC4FC1A}.Windows_Debug|ARM.Build.0 = Windows_Debug|Any CPU - {845D2B72-D8A4-42E5-9BE9-17639EC4FC1A}.Windows_Debug|x64.ActiveCfg = Windows_Debug|Any CPU - {845D2B72-D8A4-42E5-9BE9-17639EC4FC1A}.Windows_Debug|x64.Build.0 = Windows_Debug|Any CPU - {845D2B72-D8A4-42E5-9BE9-17639EC4FC1A}.Windows_Debug|x86.ActiveCfg = Windows_Debug|Any CPU - {845D2B72-D8A4-42E5-9BE9-17639EC4FC1A}.Windows_Debug|x86.Build.0 = Windows_Debug|Any CPU {845D2B72-D8A4-42E5-9BE9-17639EC4FC1A}.Windows_Release|Any CPU.ActiveCfg = Windows_Release|Any CPU {845D2B72-D8A4-42E5-9BE9-17639EC4FC1A}.Windows_Release|Any CPU.Build.0 = Windows_Release|Any CPU - {845D2B72-D8A4-42E5-9BE9-17639EC4FC1A}.Windows_Release|ARM.ActiveCfg = Windows_Release|Any CPU - {845D2B72-D8A4-42E5-9BE9-17639EC4FC1A}.Windows_Release|ARM.Build.0 = Windows_Release|Any CPU - {845D2B72-D8A4-42E5-9BE9-17639EC4FC1A}.Windows_Release|x64.ActiveCfg = Windows_Release|Any CPU - {845D2B72-D8A4-42E5-9BE9-17639EC4FC1A}.Windows_Release|x64.Build.0 = Windows_Release|Any CPU - {845D2B72-D8A4-42E5-9BE9-17639EC4FC1A}.Windows_Release|x86.ActiveCfg = Windows_Release|Any CPU - {845D2B72-D8A4-42E5-9BE9-17639EC4FC1A}.Windows_Release|x86.Build.0 = Windows_Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/System.Runtime.Extensions/src/Resources/Strings.resx b/src/System.Runtime.Extensions/src/Resources/Strings.resx index f05a6adf29..b848e682bd 100644 --- a/src/System.Runtime.Extensions/src/Resources/Strings.resx +++ b/src/System.Runtime.Extensions/src/Resources/Strings.resx @@ -153,6 +153,9 @@ <data name="ArgumentOutOfRange_NeedNonNegNum" xml:space="preserve"> <value>Non-negative number required.</value> </data> + <data name="ArgumentOutOfRange_FileLengthTooBig" xml:space="preserve"> + <value>Specified file length was too large for the file system.</value> + </data> <data name="Arg_InvalidSearchPattern" xml:space="preserve"> <value>Search pattern cannot contain \"..\" to move up directories and can be contained only internally in file/directory names, as in \"a..b\".</value> </data> @@ -187,7 +190,7 @@ <value>'{0}' must be greater than zero.</value> </data> <data name="IO_PathTooLong" xml:space="preserve"> - <value>The specified path, file name, or both are too long. The fully qualified file name must be less than 260 characters, and the directory name must be less than 248 characters.</value> + <value>The specified file name or path is too long, or a component of the specified path is too long.</value> </data> <data name="IO_FileNotFound" xml:space="preserve"> <value>Unable to find the specified file.</value> @@ -219,6 +222,9 @@ <data name="IO_FileExists_Name" xml:space="preserve"> <value>The file '{0}' already exists.</value> </data> + <data name="InvalidOperation_Cryptography" xml:space="preserve"> + <value>Unable to use cryptographic functionality.</value> + </data> <data name="UnknownError_Num" xml:space="preserve"> <value>Unknown error '{0}'.</value> </data> diff --git a/src/System.Runtime.Extensions/src/System.Runtime.Extensions.CoreCLR.csproj b/src/System.Runtime.Extensions/src/System.Runtime.Extensions.CoreCLR.csproj index a00cc0ff97..16d7aa88e2 100644 --- a/src/System.Runtime.Extensions/src/System.Runtime.Extensions.CoreCLR.csproj +++ b/src/System.Runtime.Extensions/src/System.Runtime.Extensions.CoreCLR.csproj @@ -14,6 +14,8 @@ </PropertyGroup> <!-- Default configurations to help VS understand the configurations --> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'FreeBSD_Debug|AnyCPU' " /> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'FreeBSD_Release|AnyCPU' " /> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Linux_Debug|AnyCPU' " /> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Linux_Release|AnyCPU' " /> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'OSX_Debug|AnyCPU' " /> @@ -26,6 +28,7 @@ <Compile Include="System\BitConverter.cs" /> <Compile Include="System\IO\LowLevelTextWriter.cs" /> <Compile Include="System\IO\LowLevelStringWriter.cs" /> + <Compile Include="System\IO\Path.cs" /> <Compile Include="System\Net\WebUtility.cs" /> <Compile Include="System\Net\Configuration\UnicodeDecodingConformance.cs" /> <Compile Include="System\Net\Configuration\UnicodeEncodingConformance.cs" /> @@ -43,7 +46,6 @@ <ItemGroup Condition=" '$(TargetsWindows)' == 'true' "> <Compile Include="System\Diagnostics\Stopwatch.Windows.cs" /> - <Compile Include="System\IO\Path.cs" /> <!-- TODO: Move this to the common group once Path.Unix.cs is implemented --> <Compile Include="System\IO\Path.Windows.cs" /> <Compile Include="System\IO\Path.Win32.cs" /> <Compile Include="System\IO\PathHelper.Windows.cs" /> @@ -51,7 +53,7 @@ <Link>System\IO\Win32Marshal.cs</Link> </Compile> <Compile Include="$(CommonPath)\Interop\Windows\Interop.Libraries.cs"> - <Link>Interop\Windows\Interop.Libraries.cs</Link> + <Link>Common\Interop\Windows\Interop.Libraries.cs</Link> </Compile> <Compile Include="$(CommonPath)\Interop\Windows\BCrypt\Interop.BCryptGenRandom.cs"> <Link>Common\Interop\Windows\BCrypt\Interop.BCryptGenRandom.cs</Link> @@ -60,41 +62,105 @@ <Link>Common\Interop\Windows\BCrypt\Interop.NTSTATUS.cs</Link> </Compile> <Compile Include="$(CommonPath)\Interop\Windows\mincore\Interop.Errors.cs"> - <Link>Interop\Windows\mincore\Interop.Errors.cs</Link> + <Link>Common\Interop\Windows\mincore\Interop.Errors.cs</Link> </Compile> <Compile Include="$(CommonPath)\Interop\Windows\mincore\Interop.FormatMessage.cs"> - <Link>Interop\Windows\mincore\Interop.FormatMessage.cs</Link> + <Link>Common\Interop\Windows\mincore\Interop.FormatMessage.cs</Link> </Compile> <Compile Include="$(CommonPath)\Interop\Windows\mincore\Interop.GetFullPathNameW.cs"> - <Link>Interop\Windows\mincore\Interop.GetFullPathNameW.cs</Link> + <Link>Common\Interop\Windows\mincore\Interop.GetFullPathNameW.cs</Link> </Compile> <Compile Include="$(CommonPath)\Interop\Windows\mincore\Interop.GetLastError.cs"> - <Link>Interop\Windows\mincore\Interop.GetLastError.cs</Link> + <Link>Common\Interop\Windows\mincore\Interop.GetLastError.cs</Link> </Compile> <Compile Include="$(CommonPath)\Interop\Windows\mincore\Interop.GetLongPathNameW.cs"> - <Link>Interop\Windows\mincore\Interop.GetLongPathNameW.cs</Link> + <Link>Common\Interop\Windows\mincore\Interop.GetLongPathNameW.cs</Link> </Compile> <Compile Include="$(CommonPath)\Interop\Windows\mincore\Interop.GetTempFileNameW.cs"> - <Link>Interop\Windows\mincore\Interop.GetTempFileNameW.cs</Link> + <Link>Common\Interop\Windows\mincore\Interop.GetTempFileNameW.cs</Link> </Compile> <Compile Include="$(CommonPath)\Interop\Windows\mincore\Interop.GetTempPathW.cs"> - <Link>Interop\Windows\mincore\Interop.GetTempPathW.cs</Link> + <Link>Common\Interop\Windows\mincore\Interop.GetTempPathW.cs</Link> </Compile> <Compile Include="$(CommonPath)\Interop\Windows\mincore\Interop.QueryPerformanceCounter.cs"> - <Link>Interop\Windows\mincore\Interop.QueryPerformanceCounter.cs</Link> + <Link>Common\Interop\Windows\mincore\Interop.QueryPerformanceCounter.cs</Link> </Compile> <Compile Include="$(CommonPath)\Interop\Windows\mincore\Interop.QueryPerformanceFrequency.cs"> - <Link>Interop\Windows\mincore\Interop.QueryPerformanceFrequency.cs</Link> + <Link>Common\Interop\Windows\mincore\Interop.QueryPerformanceFrequency.cs</Link> </Compile> </ItemGroup> <ItemGroup Condition=" '$(TargetsUnix)' == 'true' "> <Compile Include="System\Diagnostics\Stopwatch.Unix.cs" /> + <Compile Include="System\IO\Path.Unix.cs" /> <Compile Include="$(CommonPath)\Interop\Unix\Interop.Libraries.cs"> - <Link>Interop\Unix\Interop.Libraries.cs</Link> + <Link>Common\Interop\Unix\Interop.Libraries.cs</Link> + </Compile> + <Compile Include="$(CommonPath)\Interop\Unix\Interop.Errors.cs"> + <Link>Common\Interop\Unix\Interop.Errors.cs</Link> + </Compile> + <Compile Include="$(CommonPath)\Interop\Unix\Interop.IOErrors.cs"> + <Link>Common\Interop\Unix\Interop.IOErrors.cs</Link> + </Compile> + <Compile Include="$(CommonPath)\Interop\Unix\libc\Interop.close.cs"> + <Link>Common\Interop\Unix\libc\Interop.close.cs</Link> + </Compile> + <Compile Include="$(CommonPath)\Interop\Unix\libc\Interop.getcwd.cs"> + <Link>Common\Interop\Unix\libc\Interop.getcwd.cs</Link> + </Compile> + <Compile Include="$(CommonPath)\Interop\Unix\libc\Interop.mkstemps.cs"> + <Link>Common\Interop\Unix\libc\Interop.mkstemps.cs</Link> + </Compile> + <Compile Include="$(CommonPath)\Interop\Unix\libc\Interop.pathconf.cs"> + <Link>Common\Interop\Unix\libc\Interop.mkstemps.cs</Link> + </Compile> + <Compile Include="$(CommonPath)\Interop\Unix\libc\Interop.gnu_get_libc_version.cs"> + <Link>Common\Interop\Unix\libc\Interop.gnu_get_libc_version.cs</Link> + </Compile> + <Compile Include="$(CommonPath)\Interop\Unix\libc\Interop.strerror.cs"> + <Link>Common\Interop\Unix\libc\Interop.strerror.cs</Link> + </Compile> + <Compile Include="$(CommonPath)\Interop\Unix\libcoreclr\Interop.EnsureOpenSslInitialized.cs"> + <Link>Common\Interop\Unix\libcoreclr\Interop.EnsureOpenSslInitialized.cs</Link> </Compile> <Compile Include="$(CommonPath)\Interop\Unix\libcoreclr\Interop.QueryPerformanceCounter.cs"> - <Link>Interop\Unix\libcoreclr\Interop.QueryPerformanceCounter.cs</Link> + <Link>Common\Interop\Unix\libcoreclr\Interop.QueryPerformanceCounter.cs</Link> + </Compile> + <Compile Include="$(CommonPath)\Interop\Unix\libcrypto\Interop.ERR.cs"> + <Link>Common\Interop\Unix\libcrypto\Interop.ERR.cs</Link> + </Compile> + <Compile Include="$(CommonPath)\Interop\Unix\libcrypto\Interop.Initialization.cs"> + <Link>Common\Interop\Unix\libcrypto\Interop.Initialization.cs</Link> + </Compile> + <Compile Include="$(CommonPath)\Interop\Unix\libcrypto\Interop.RAND.cs"> + <Link>Common\Interop\Unix\libcrypto\Interop.RAND.cs</Link> + </Compile> + </ItemGroup> + + <ItemGroup Condition=" '$(TargetsLinux)' == 'true' "> + <Compile Include="$(CommonPath)\Interop\Linux\Interop.Errors.cs"> + <Link>Common\Interop\Linux\Interop.Errors.cs</Link> + </Compile> + <Compile Include="$(CommonPath)\Interop\Linux\libc\Interop.PathConfNames.cs"> + <Link>Common\Interop\Linux\libc\Interop.PathConfNames.cs</Link> + </Compile> + </ItemGroup> + + <ItemGroup Condition=" '$(TargetsOSX)' == 'true' "> + <Compile Include="$(CommonPath)\Interop\OSX\Interop.Errors.cs"> + <Link>Common\Interop\OSX\Interop.Errors.cs</Link> + </Compile> + <Compile Include="$(CommonPath)\Interop\OSX\libc\Interop.PathConfNames.cs"> + <Link>Common\Interop\OSX\libc\Interop.PathConfNames.cs</Link> + </Compile> + </ItemGroup> + + <ItemGroup Condition=" '$(TargetsFreeBSD)' == 'true' "> + <Compile Include="$(CommonPath)\Interop\FreeBSD\Interop.Errors.cs"> + <Link>Common\Interop\FreeBSD\Interop.Errors.cs</Link> + </Compile> + <Compile Include="$(CommonPath)\Interop\FreeBSD\libc\Interop.PathConfNames.cs"> + <Link>Common\Interop\FreeBSD\libc\Interop.PathConfNames.cs</Link> </Compile> </ItemGroup> diff --git a/src/System.Runtime.Extensions/src/System/IO/Path.Unix.cs b/src/System.Runtime.Extensions/src/System/IO/Path.Unix.cs index 7e6cac3e9f..83c50e8fb7 100644 --- a/src/System.Runtime.Extensions/src/System/IO/Path.Unix.cs +++ b/src/System.Runtime.Extensions/src/System/IO/Path.Unix.cs @@ -1,11 +1,8 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -using System; using System.Diagnostics; - -// TODO: This partial implementation is currently stubbed out and should not be -// included in the .csproj until completed. +using System.Text; namespace System.IO { @@ -17,13 +14,12 @@ namespace System.IO private const string DirectorySeparatorCharAsString = "/"; - private static readonly char[] RealInvalidPathChars = { '\0' }; - private static readonly char[] InvalidPathCharsWithAdditionalChecks = { '\0', '*', '?' }; // Used by HasIllegalCharacters + private static readonly char[] InvalidPathChars = { '\0' }; private static readonly char[] InvalidFileNameChars = { '\0', '/' }; + private static readonly char[] InvalidPathCharsWithAdditionalChecks = InvalidFileNameChars; // no additional checks on Unix - // TODO: Implement these values correctly (dynamically) for Unix - private static readonly int MaxPath = 4096; - private static readonly int MaxComponentLength = 255; + private static readonly int MaxPath = Interop.libc.MaxPath; + private static readonly int MaxComponentLength = Interop.libc.MaxName; private static bool IsDirectorySeparator(char c) { @@ -43,23 +39,105 @@ namespace System.IO private static void EmulateFileIOPermissionChecks(string fullPath) { - CheckInvalidPathChars(fullPath, true); + // nop. On Windows this checks for additional characters (e.g. '?', '*') + // that are invalid in paths, but such characters are perfectly valid on Unix. } private static string NormalizePath( - string path, bool fullCheck, + string path, bool fullCheck, int maxPathLength, bool expandShortPaths) // ignored on Unix { Debug.Assert(path != null); - // If we're doing a full path check, look for illegal path characters. + if (path.Length == 0) + throw new ArgumentException(SR.Arg_PathIllegal); + if (fullCheck) { CheckInvalidPathChars(path); + + // Expand with current directory if necessary + if (!IsPathRooted(path)) + { + path = Combine(Interop.libc.getcwd(), path); + } + } + + // Remove "//", "/./", and "/../" from the path. We would ideally use realpath + // to do this, but it resolves symlinks, requires that the file actually exist, + // and turns it into a full path, which we only want if fullCheck is true. + // Instead, we do the normalization manually, copying each character to the output, + // except the ones we're removing, such that the builder contains the normalized path + // at the end. + var sb = StringBuilderCache.Acquire(path.Length); + int componentCharCount = 0; + for (int i = 0; i < path.Length; i++) + { + char c = path[i]; + + if (IsDirectorySeparator(c) && i + 1 < path.Length) + { + componentCharCount = 0; + + // Skip this character if it's a directory separator and if the next character is, too, + // e.g. "parent//child" => "parent/child" + if (IsDirectorySeparator(path[i + 1])) + { + continue; + } + + // Skip this character and the next if it's referring to the current directory, + // e.g. "parent/./child" =? "parent/child" + if ((i + 2 == path.Length || IsDirectorySeparator(path[i + 2])) && + path[i + 1] == '.') + { + i++; + continue; + } + + // Skip this character and the next two if it's referring to the parent directory, + // e.g. "parent/child/../grandchild" => "parent/grandchild" + if (i + 2 < path.Length && + (i + 3 == path.Length || IsDirectorySeparator(path[i + 3])) && + path[i + 1] == '.' && path[i + 2] == '.') + { + // Unwind back to the last slash (and if there isn't one, clear out everything). + int s; + for (s = sb.Length - 1; s >= 0; s--) + { + if (IsDirectorySeparator(sb[s])) + { + sb.Length = s; + break; + } + } + if (s < 0) + sb.Length = 0; + + i += 2; + continue; + } + } + + if (++componentCharCount > MaxComponentLength) + { + throw new PathTooLongException(SR.IO_PathTooLong); + } + sb.Append(c); + } + + Debug.Assert(sb.Length < path.Length || sb.ToString() == path, + "Either we've removed characters, or the string should be unmodified from the input path."); + + if (sb.Length > MaxPath) + { + throw new PathTooLongException(SR.IO_PathTooLong); } - // TODO: Implement this - throw new NotImplementedException(); + return + sb.Length == 0 ? (fullCheck ? DirectorySeparatorCharAsString : string.Empty) : + sb.Length == path.Length ? path : + sb.ToString(); } private static string RemoveLongPathPrefix(string path) @@ -69,15 +147,38 @@ namespace System.IO public static string GetTempPath() { - // TODO: Implement this - throw new NotImplementedException(); + const string TempEnvVar = "TMPDIR"; + const string DefaultTempPath = "/tmp/"; + + // Get the temp path from the TMPDIR environment variable. + // If it's not set, just return the default path. + // If it is, return it, ensuring it ends with a slash. + string path = Environment.GetEnvironmentVariable(TempEnvVar); + return + string.IsNullOrEmpty(path) ? DefaultTempPath : + IsDirectorySeparator(path[path.Length - 1]) ? path : + path + DirectorySeparatorChar; } private static string InternalGetTempFileName(bool checkHost) { - // TODO: Implement this - throw new NotImplementedException(); - }s + const string Suffix = ".tmp"; + const int SuffixByteLength = 4; + + // mkstemps takes a char* and overwrites the XXXXXX with six characters + // that'll result in a unique file name. + string template = GetTempPath() + "tmpXXXXXX" + Suffix + "\0"; + byte[] name = Encoding.UTF8.GetBytes(template); + + // Create, open, and close the temp file. + int fd; + Interop.CheckIo(fd = Interop.libc.mkstemps(name, SuffixByteLength)); + Interop.libc.close(fd); // ignore any errors from close; nothing to do if cleanup isn't possible + + // 'name' is now the name of the file + Debug.Assert(name[name.Length - 1] == '\0'); + return Encoding.UTF8.GetString(name, 0, name.Length - 1); // trim off the trailing '\0' + } public static bool IsPathRooted(string path) { @@ -94,11 +195,17 @@ namespace System.IO return path.Length > 0 && IsDirectorySeparator(path[0]) ? 1 : 0; } - private static byte[] CreateCryptoRandomByteArray(int byteLength) + private static unsafe byte[] CreateCryptoRandomByteArray(int byteLength) { - // TODO: Implement this - throw new NotImplementedException(); + var arr = new byte[byteLength]; + fixed (byte* buf = arr) + { + if (Interop.libcrypto.RAND_pseudo_bytes(buf, arr.Length) == -1) + { + throw new InvalidOperationException(SR.InvalidOperation_Cryptography); + } + } + return arr; } } - } diff --git a/src/System.Runtime.Extensions/src/System/IO/Path.Windows.cs b/src/System.Runtime.Extensions/src/System/IO/Path.Windows.cs index bea6e1f268..dbde84fe36 100644 --- a/src/System.Runtime.Extensions/src/System/IO/Path.Windows.cs +++ b/src/System.Runtime.Extensions/src/System/IO/Path.Windows.cs @@ -16,7 +16,7 @@ namespace System.IO private const string DirectorySeparatorCharAsString = "\\"; - private static readonly char[] RealInvalidPathChars = + private static readonly char[] InvalidPathChars = { '\"', '<', '>', '|', '\0', (char)1, (char)2, (char)3, (char)4, (char)5, (char)6, (char)7, (char)8, (char)9, (char)10, diff --git a/src/System.Runtime.Extensions/src/System/IO/Path.cs b/src/System.Runtime.Extensions/src/System/IO/Path.cs index 8a887ff62b..2a58e40294 100644 --- a/src/System.Runtime.Extensions/src/System/IO/Path.cs +++ b/src/System.Runtime.Extensions/src/System/IO/Path.cs @@ -1,12 +1,11 @@ -using System; -using System.Text; +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Diagnostics; +using System.Diagnostics.Contracts; using System.Runtime.InteropServices; using System.Security; -using System.Runtime.CompilerServices; -using System.Globalization; -using System.Runtime.Versioning; -using System.Diagnostics.Contracts; -using System.Diagnostics; +using System.Text; namespace System.IO { @@ -74,7 +73,7 @@ namespace System.IO { CheckInvalidPathChars(path); - string normalizedPath = NormalizePath(path, false); + string normalizedPath = NormalizePath(path, fullCheck: false); // If there are no permissions for PathDiscovery to this path, we should NOT expand the short paths // as this would leak information about paths to which the user would not have access to. @@ -104,7 +103,7 @@ namespace System.IO // Only re-normalize if the original path had a ~ in it. if (path.IndexOf("~", StringComparison.Ordinal) != -1) { - normalizedPath = NormalizePath(path, /*fullCheck*/ false, /*expandShortPaths*/ false); + normalizedPath = NormalizePath(path, fullCheck: false, expandShortPaths: false); } } catch (PathTooLongException) { } @@ -130,7 +129,7 @@ namespace System.IO public static char[] GetInvalidPathChars() { - return (char[])RealInvalidPathChars.Clone(); + return (char[])InvalidPathChars.Clone(); } public static char[] GetInvalidFileNameChars() @@ -191,7 +190,7 @@ namespace System.IO throw new ArgumentNullException("path"); Contract.EndContractBlock(); - return NormalizePath(path, true); + return NormalizePath(path, fullCheck: true); } [System.Security.SecuritySafeCritical] // auto-generated @@ -209,7 +208,7 @@ namespace System.IO [System.Security.SecuritySafeCritical] // auto-generated private static string NormalizePath(string path, bool fullCheck, int maxPathLength) { - return NormalizePath(path, fullCheck, maxPathLength, true); + return NormalizePath(path, fullCheck, maxPathLength, expandShortPaths: true); } // Returns the name and extension parts of the given path. The resulting @@ -258,7 +257,7 @@ namespace System.IO public static string GetPathRoot(string path) { if (path == null) return null; - path = NormalizePath(path, false); + path = NormalizePath(path, fullCheck: false); return path.Substring(0, GetRootLength(path)); } @@ -282,7 +281,7 @@ namespace System.IO [System.Security.SecuritySafeCritical] public static string GetTempFileName() { - return InternalGetTempFileName(true); + return InternalGetTempFileName(checkHost: true); } // Tests if a path includes a file extension. The result is @@ -485,7 +484,7 @@ namespace System.IO private static bool HasIllegalCharacters(string path, bool checkAdditional) { Contract.Requires(path != null); - return path.IndexOfAny(checkAdditional ? InvalidPathCharsWithAdditionalChecks : RealInvalidPathChars) >= 0; + return path.IndexOfAny(checkAdditional ? InvalidPathCharsWithAdditionalChecks : InvalidPathChars) >= 0; } private static void CheckInvalidPathChars(string path, bool checkAdditional = false) @@ -495,6 +494,5 @@ namespace System.IO if (HasIllegalCharacters(path, checkAdditional)) throw new ArgumentException(SR.Argument_InvalidPathChars, "path"); } - } } diff --git a/src/System.Runtime.Extensions/src/System/IO/PathHelper.Windows.cs b/src/System.Runtime.Extensions/src/System/IO/PathHelper.Windows.cs index d51c1a9e99..4187f702eb 100644 --- a/src/System.Runtime.Extensions/src/System/IO/PathHelper.Windows.cs +++ b/src/System.Runtime.Extensions/src/System/IO/PathHelper.Windows.cs @@ -332,7 +332,6 @@ namespace System.IO if (r >= m_maxPath) throw new PathTooLongException(SR.IO_PathTooLong); - sb = Path.RemoveLongPathPrefix(sb); Length = sb.Length; return true; diff --git a/src/System.Runtime.Extensions/tests/System/IO/Path.cs b/src/System.Runtime.Extensions/tests/System/IO/Path.cs index 351966e32c..5ccaa8a0da 100644 --- a/src/System.Runtime.Extensions/tests/System/IO/Path.cs +++ b/src/System.Runtime.Extensions/tests/System/IO/Path.cs @@ -37,9 +37,28 @@ public static class PathTests public static void GetDirectoryName() { Assert.Null(Path.GetDirectoryName(null)); + Assert.Throws<ArgumentException>(() => Path.GetDirectoryName(string.Empty)); + + Assert.Equal(string.Empty, Path.GetDirectoryName(".")); + Assert.Equal(string.Empty, Path.GetDirectoryName("..")); + + Assert.Equal(string.Empty, Path.GetDirectoryName("baz")); Assert.Equal("dir", Path.GetDirectoryName(Path.Combine("dir", "baz"))); - Assert.Equal(Path.GetDirectoryName("."), Path.GetDirectoryName("dir")); - Assert.Equal(null, Path.GetDirectoryName(Path.GetPathRoot(Directory.GetCurrentDirectory()))); + Assert.Equal(Path.Combine("dir", "baz"), Path.GetDirectoryName(Path.Combine("dir", "baz", "bar"))); + + string curDir = Directory.GetCurrentDirectory(); + Assert.Equal(curDir, Path.GetDirectoryName(Path.Combine(curDir, "baz"))); + Assert.Equal(null, Path.GetDirectoryName(Path.GetPathRoot(curDir))); + } + + [PlatformSpecific(PlatformID.AnyUnix)] + [Fact] + public static void GetDirectoryName_Unix() + { + Assert.Equal(new string('\t', 1), Path.GetDirectoryName(Path.Combine(new string('\t', 1), "file"))); + Assert.Equal(new string('\b', 2), Path.GetDirectoryName(Path.Combine(new string('\b', 2), "fi le"))); + Assert.Equal(new string('\v', 3), Path.GetDirectoryName(Path.Combine(new string('\v', 3), "fi\nle"))); + Assert.Equal(new string('\n', 4), Path.GetDirectoryName(Path.Combine(new string('\n', 4), "fi\rle"))); } [Theory] @@ -53,7 +72,22 @@ public static class PathTests public static void GetExtension(string expected, string path) { if (path != null) + { path = path.Replace('/', Path.DirectorySeparatorChar); + } + Assert.Equal(expected, Path.GetExtension(path)); + Assert.Equal(!string.IsNullOrEmpty(expected), Path.HasExtension(path)); + } + + [PlatformSpecific(PlatformID.AnyUnix)] + [Theory] + [InlineData(".e xe", "file.e xe")] + [InlineData(". ", "file. ")] + [InlineData(". ", " file. ")] + [InlineData(".extension", " file.extension")] + [InlineData(".exten\tsion", "file.exten\tsion")] + public static void GetExtension_Unix(string expected, string path) + { Assert.Equal(expected, Path.GetExtension(path)); Assert.Equal(!string.IsNullOrEmpty(expected), Path.HasExtension(path)); } @@ -61,22 +95,50 @@ public static class PathTests [Fact] public static void GetFileName() { + Assert.Equal(null, Path.GetFileName(null)); + Assert.Equal(string.Empty, Path.GetFileName(string.Empty)); + + Assert.Equal(".", Path.GetFileName(".")); + Assert.Equal("..", Path.GetFileName("..")); + Assert.Equal("file", Path.GetFileName("file")); + Assert.Equal("file.", Path.GetFileName("file.")); + Assert.Equal("file.exe", Path.GetFileName("file.exe")); + + Assert.Equal("file.exe", Path.GetFileName(Path.Combine("baz", "file.exe"))); Assert.Equal("file.exe", Path.GetFileName(Path.Combine("bar", "baz", "file.exe"))); + Assert.Equal(string.Empty, Path.GetFileName(Path.Combine("bar", "baz") + Path.DirectorySeparatorChar)); } + [PlatformSpecific(PlatformID.AnyUnix)] + [Fact] + public static void GetFileName_Unix() + { + Assert.Equal(" . ", Path.GetFileName(" . ")); + Assert.Equal(" .. ", Path.GetFileName(" .. ")); + Assert.Equal("fi le", Path.GetFileName("fi le")); + Assert.Equal("fi le", Path.GetFileName("fi le")); + Assert.Equal("fi le", Path.GetFileName(Path.Combine("b \r\n ar", "fi le"))); + } + [Fact] public static void GetFileNameWithoutExtension() { - Assert.Equal("file", Path.GetFileNameWithoutExtension(Path.Combine("bar","baz","file.exe"))); - Assert.Equal(string.Empty, Path.GetFileNameWithoutExtension(Path.Combine("bar","baz") + Path.DirectorySeparatorChar)); - Assert.Null(Path.GetFileNameWithoutExtension(null)); + Assert.Equal(null, Path.GetFileNameWithoutExtension(null)); + Assert.Equal(string.Empty, Path.GetFileNameWithoutExtension(string.Empty)); + + Assert.Equal("file", Path.GetFileNameWithoutExtension("file")); + Assert.Equal("file", Path.GetFileNameWithoutExtension("file.exe")); + Assert.Equal("file", Path.GetFileNameWithoutExtension(Path.Combine("bar", "baz", "file.exe"))); + + Assert.Equal(string.Empty, Path.GetFileNameWithoutExtension(Path.Combine("bar", "baz") + Path.DirectorySeparatorChar)); } [Fact] public static void GetPathRoot() { Assert.Null(Path.GetPathRoot(null)); + Assert.Throws<ArgumentException>(() => Path.GetPathRoot(string.Empty)); string cwd = Directory.GetCurrentDirectory(); Assert.Equal(cwd.Substring(0, cwd.IndexOf(Path.DirectorySeparatorChar) + 1), Path.GetPathRoot(cwd)); @@ -84,23 +146,39 @@ public static class PathTests Assert.Equal(string.Empty, Path.GetPathRoot(@"file.exe")); Assert.False(Path.IsPathRooted("file.exe")); + } - if (Interop.IsWindows) // UNC paths - { - Assert.Equal(@"\\test\unc", Path.GetPathRoot(@"\\test\unc\path\to\something")); - Assert.True(Path.IsPathRooted(@"\\test\unc\path\to\something")); - } + [PlatformSpecific(PlatformID.Windows)] + [Fact] + public static void GetPathRoot_Windows() + { + // UNC paths + string uncPath = @"\\test\unc\path\to\something"; + Assert.True(Path.IsPathRooted(uncPath)); + Assert.Equal(@"\\test\unc", Path.GetPathRoot(uncPath)); + } + + [PlatformSpecific(PlatformID.AnyUnix)] + [Fact] + public static void GetPathRoot_Unix() + { + // slashes are normal filename characters + string uncPath = @"\\test\unc\path\to\something"; + Assert.False(Path.IsPathRooted(uncPath)); + Assert.Equal(string.Empty, Path.GetPathRoot(uncPath)); } [Fact] public static void GetRandomFileName() { + char[] invalidChars = Path.GetInvalidFileNameChars(); var fileNames = new HashSet<string>(); for (int i = 0; i < 100; i++) { string s = Path.GetRandomFileName(); Assert.Equal(s.Length, 8 + 1 + 3); Assert.Equal(s[8], '.'); + Assert.Equal(-1, s.IndexOfAny(invalidChars)); Assert.True(fileNames.Add(s)); } } @@ -135,12 +213,29 @@ public static class PathTests { Assert.NotNull(Path.GetInvalidFileNameChars()); Assert.NotSame(Path.GetInvalidFileNameChars(), Path.GetInvalidFileNameChars()); - Assert.Equal((IEnumerable<char>)Path.GetInvalidFileNameChars(), (IEnumerable<char>)Path.GetInvalidFileNameChars()); + Assert.Equal((IEnumerable<char>)Path.GetInvalidFileNameChars(), Path.GetInvalidFileNameChars()); Assert.True(Path.GetInvalidFileNameChars().Length > 0); } [Fact] - public static void GetTempPath() + [OuterLoop] + public static void GetInvalidFileNameChars_OtherCharsValid() + { + string curDir = Directory.GetCurrentDirectory(); + var invalidChars = new HashSet<char>(Path.GetInvalidFileNameChars()); + for (int i = 0; i < char.MaxValue; i++) + { + char c = (char)i; + if (!invalidChars.Contains(c)) + { + string name = "file" + c + ".txt"; + Assert.Equal(Path.Combine(curDir, name), Path.GetFullPath(name)); + } + } + } + + [Fact] + public static void GetTempPath_Default() { string tmpPath = Path.GetTempPath(); Assert.False(string.IsNullOrEmpty(tmpPath)); @@ -173,6 +268,7 @@ public static class PathTests [InlineData(".tmp/", ".tmp")] [InlineData("./tmp/", "./tmp")] [InlineData("/home/someuser/sometempdir/", "/home/someuser/sometempdir/")] + [InlineData("/home/someuser/some tempdir/", "/home/someuser/some tempdir/")] public static void GetTempPath_SetEnvVar_Unix(string expected, string newTempPath) { GetTempPath_SetEnvVar("TMPDIR", expected, newTempPath); @@ -186,7 +282,7 @@ public static class PathTests { Environment.SetEnvironmentVariable(envVar, newTempPath); Assert.Equal( - Path.GetFullPath(expected), + Path.GetFullPath(expected), Path.GetFullPath(Path.GetTempPath())); } finally @@ -203,9 +299,12 @@ public static class PathTests try { Assert.True(File.Exists(tmpFile)); - Assert.Equal(".tmp", Path.GetExtension(tmpFile), ignoreCase: true); + Assert.Equal(".tmp", Path.GetExtension(tmpFile), ignoreCase: true, ignoreLineEndingDifferences: false, ignoreWhiteSpaceDifferences: false); + Assert.Equal(-1, tmpFile.IndexOfAny(Path.GetInvalidPathChars())); using (FileStream fs = File.OpenRead(tmpFile)) + { Assert.Equal(0, fs.Length); + } Assert.Equal(Path.Combine(Path.GetTempPath(), Path.GetFileName(tmpFile)), tmpFile); } finally @@ -215,104 +314,208 @@ public static class PathTests } [Fact] - public static void GetFullPath() + + public static void GetFullPath_InvalidArgs() { - // Basic invalid arg checks Assert.Throws<ArgumentNullException>(() => Path.GetFullPath(null)); - Assert.Throws<ArgumentException>(() => Path.GetFullPath("")); - Assert.Throws<ArgumentException>(() => Path.GetFullPath("http://www.microsoft.com")); - Assert.Throws<ArgumentException>(() => Path.GetFullPath("file://www.microsoft.com")); + Assert.Throws<ArgumentException>(() => Path.GetFullPath(string.Empty)); + } - // Basic expansions (e.g. self to self, period to self, normalization of lots of periods, etc.) + [Fact] + public static void GetFullPath_BasicExpansions() + { string curDir = Directory.GetCurrentDirectory(); + + // Current directory => current directory Assert.Equal(curDir, Path.GetFullPath(curDir)); + + // "." => current directory Assert.Equal(curDir, Path.GetFullPath(".")); + + // ".." => up a directory + Assert.Equal(Path.GetDirectoryName(curDir), Path.GetFullPath("..")); + + // "dir/./././." => "dir" Assert.Equal(curDir, Path.GetFullPath(Path.Combine(curDir, ".", ".", ".", ".", "."))); - Assert.Equal(curDir, Path.GetFullPath(curDir + Path.DirectorySeparatorChar + Path.DirectorySeparatorChar + Path.DirectorySeparatorChar + ".")); + + // "dir///." => "dir" + Assert.Equal(curDir, Path.GetFullPath(curDir + new string(Path.DirectorySeparatorChar, 3) + ".")); + + // "dir/../dir/./../dir" => "dir" Assert.Equal(curDir, Path.GetFullPath(Path.Combine(curDir, "..", Path.GetFileName(curDir), ".", "..", Path.GetFileName(curDir)))); + + // "C:\somedir\.." => "C:\" Assert.Equal(Path.GetPathRoot(curDir), Path.GetFullPath(Path.Combine(Path.GetPathRoot(curDir), "somedir", ".."))); + + // "C:\." => "C:\" Assert.Equal(Path.GetPathRoot(curDir), Path.GetFullPath(Path.Combine(Path.GetPathRoot(curDir), "."))); - // Try out a long path that normalizes down to less than MaxPath - var longPath = new StringBuilder(curDir); - for (int i = 0; i < 1000; i++) + // "C:\..\..\..\.." => "C:\" + Assert.Equal(Path.GetPathRoot(curDir), Path.GetFullPath(Path.Combine(Path.GetPathRoot(curDir), "..", "..", "..", ".."))); + + // "C:\\\" => "C:\" + Assert.Equal(Path.GetPathRoot(curDir), Path.GetFullPath(Path.GetPathRoot(curDir) + new string(Path.DirectorySeparatorChar, 3))); + + // Path longer than MaxPath that normalizes down to less than MaxPath + const int Iters = 10000; + var longPath = new StringBuilder(curDir, curDir.Length + (Iters * 2)); + for (int i = 0; i < 10000; i++) + { longPath.Append(Path.DirectorySeparatorChar).Append('.'); + } Assert.Equal(curDir, Path.GetFullPath(longPath.ToString())); + } + + [PlatformSpecific(PlatformID.AnyUnix)] + [Fact] + public static void GetFullPath_Unix_Whitespace() + { + string curDir = Directory.GetCurrentDirectory(); + Assert.Equal("/ / ", Path.GetFullPath("/ // ")); + Assert.Equal(Path.Combine(curDir, " "), Path.GetFullPath(" ")); + Assert.Equal(Path.Combine(curDir, "\r\n"), Path.GetFullPath("\r\n")); + } + + [PlatformSpecific(PlatformID.AnyUnix)] + [Theory] + [InlineData("http://www.microsoft.com")] + [InlineData("file://somefile")] + public static void GetFullPath_Unix_URIsAsFileNames(string uriAsFileName) + { + // URIs are valid filenames, though the multiple slashes will be consolidated in GetFullPath + Assert.Equal( + Path.Combine(Directory.GetCurrentDirectory(), uriAsFileName.Replace("//", "/")), + Path.GetFullPath(uriAsFileName)); + } - // Some Windows-only checks - if (Interop.IsWindows) + [PlatformSpecific(PlatformID.Windows)] + [Fact] + public static void GetFullPath_Windows_NormalizedLongPathTooLong() + { + // Try out a long path that normalizes down to more than MaxPath + string curDir = Directory.GetCurrentDirectory(); + const int Iters = 260; + var longPath = new StringBuilder(curDir, curDir.Length + (Iters * 4)); + for (int i = 0; i < Iters; i++) { - // Try out a long path that normalizes down to more than MaxPath - for (int i = 0; i < 500; i++) - longPath.Append(Path.DirectorySeparatorChar).Append('a').Append(Path.DirectorySeparatorChar).Append('.'); - Assert.Throws<PathTooLongException>(() => Path.GetFullPath(longPath.ToString())); - - // alternate data streams aren't supported - Assert.Throws<NotSupportedException>(() => Path.GetFullPath(@"C:\some\bad:path")); - Assert.Throws<NotSupportedException>(() => Path.GetFullPath(@"bad:path")); - - // Some Windows-specific bad paths - Assert.Throws<ArgumentException>(() => Path.GetFullPath(Path.DirectorySeparatorChar + ".. ." + Path.DirectorySeparatorChar)); - Assert.Throws<ArgumentException>(() => Path.GetFullPath(Path.DirectorySeparatorChar + ". ." + Path.DirectorySeparatorChar)); - Assert.Throws<ArgumentException>(() => Path.GetFullPath(Path.DirectorySeparatorChar + " ." + Path.DirectorySeparatorChar)); - Assert.Throws<ArgumentException>(() => Path.GetFullPath("C:...")); - Assert.Throws<ArgumentException>(() => Path.GetFullPath(@"C:...\somedir")); - Assert.Throws<ArgumentException>(() => Path.GetFullPath(@"C :")); - Assert.Throws<ArgumentException>(() => Path.GetFullPath(@"C :\somedir")); - Assert.Throws<ArgumentException>(() => Path.GetFullPath(@"bad::$DATA")); - Assert.Throws<PathTooLongException>(() => Path.GetFullPath(@"C:\" + new string('a', 255) + @"\")); - - // Some Windows-specific strange but legal paths - Assert.Equal( - Path.GetFullPath(curDir + Path.DirectorySeparatorChar), - Path.GetFullPath(curDir + Path.DirectorySeparatorChar + ". " + Path.DirectorySeparatorChar)); - Assert.Equal( - Path.GetFullPath(Path.GetDirectoryName(curDir) + Path.DirectorySeparatorChar), - Path.GetFullPath(curDir + Path.DirectorySeparatorChar + "..." + Path.DirectorySeparatorChar)); - Assert.Equal( - Path.GetFullPath(Path.GetDirectoryName(curDir) + Path.DirectorySeparatorChar), - Path.GetFullPath(curDir + Path.DirectorySeparatorChar + ".. " + Path.DirectorySeparatorChar)); - - // Windows-specific UNC paths - Assert.Equal(@"\\server\share", Path.GetFullPath(@"\\server\share")); - Assert.Equal(@"\\server\share", Path.GetFullPath(@" \\server\share")); - Assert.Equal(@"\\server\share\dir", Path.GetFullPath(@"\\server\share\dir")); - Assert.Equal(@"\\server\share", Path.GetFullPath(@"\\server\share\.")); - Assert.Equal(@"\\server\share", Path.GetFullPath(@"\\server\share\..")); - Assert.Equal(@"\\server\share\", Path.GetFullPath(@"\\server\share\ ")); - Assert.Equal(@"\\server\ share\", Path.GetFullPath(@"\\server\ share\")); - Assert.Throws<ArgumentException>(() => Path.GetFullPath(@"\\")); - Assert.Throws<ArgumentException>(() => Path.GetFullPath(@"\\server")); - Assert.Throws<ArgumentException>(() => Path.GetFullPath(@"\\server\")); - Assert.Throws<ArgumentException>(() => Path.GetFullPath(@"\\server\..")); - Assert.Throws<ArgumentException>(() => Path.GetFullPath(@"\\?\GLOBALROOT\")); - - // Windows short paths - string tempFilePath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N") + ".txt"); - File.Create(tempFilePath).Dispose(); - try + longPath.Append(Path.DirectorySeparatorChar).Append('a').Append(Path.DirectorySeparatorChar).Append('.'); + } + Assert.Throws<PathTooLongException>(() => Path.GetFullPath(longPath.ToString())); + } + + [PlatformSpecific(PlatformID.Windows)] + [Fact] + public static void GetFullPath_Windows_AlternateDataStreamsNotSupported() + { + Assert.Throws<NotSupportedException>(() => Path.GetFullPath(@"bad:path")); + Assert.Throws<NotSupportedException>(() => Path.GetFullPath(@"C:\some\bad:path")); + } + + [PlatformSpecific(PlatformID.Windows)] + [Fact] + public static void GetFullPath_Windows_URIFormatNotSupported() + { + Assert.Throws<ArgumentException>(() => Path.GetFullPath("http://www.microsoft.com")); + Assert.Throws<ArgumentException>(() => Path.GetFullPath("file://www.microsoft.com")); + } + + [PlatformSpecific(PlatformID.Windows)] + [Theory] + [InlineData(@"\.. .\")] + [InlineData(@"\. .\")] + [InlineData(@"\ .\")] + [InlineData(@"C:...")] + [InlineData(@"C:...\somedir")] + [InlineData(@"C :")] + [InlineData(@"C :\somedir")] + [InlineData(@"bad::$DATA")] + public static void GetFullPath_Windows_ArgumentExceptionPaths(string path) + { + Assert.Throws<ArgumentException>(() => Path.GetFullPath(path)); + } + + [PlatformSpecific(PlatformID.Windows)] + [Fact] + public static void GetFullPath_Windows_PathTooLong() + { + Assert.Throws<PathTooLongException>(() => Path.GetFullPath(@"C:\" + new string('a', 255) + @"\")); + } + + [PlatformSpecific(PlatformID.Windows)] + [Fact] + public static void GetFullPath_Windows_StrangeButLegalPaths() + { + string curDir = Directory.GetCurrentDirectory(); + Assert.Equal( + Path.GetFullPath(curDir + Path.DirectorySeparatorChar), + Path.GetFullPath(curDir + Path.DirectorySeparatorChar + ". " + Path.DirectorySeparatorChar)); + Assert.Equal( + Path.GetFullPath(Path.GetDirectoryName(curDir) + Path.DirectorySeparatorChar), + Path.GetFullPath(curDir + Path.DirectorySeparatorChar + "..." + Path.DirectorySeparatorChar)); + Assert.Equal( + Path.GetFullPath(Path.GetDirectoryName(curDir) + Path.DirectorySeparatorChar), + Path.GetFullPath(curDir + Path.DirectorySeparatorChar + ".. " + Path.DirectorySeparatorChar)); + } + + [PlatformSpecific(PlatformID.Windows)] + [Theory] + [InlineData(@"\\server\share", @"\\server\share")] + [InlineData(@"\\server\share", @" \\server\share")] + [InlineData(@"\\server\share\dir", @"\\server\share\dir")] + [InlineData(@"\\server\share", @"\\server\share\.")] + [InlineData(@"\\server\share", @"\\server\share\..")] + [InlineData(@"\\server\share\", @"\\server\share\ ")] + [InlineData(@"\\server\ share\", @"\\server\ share\")] + public static void GetFullPath_Windows_UNC_Valid(string expected, string input) + { + Assert.Equal(expected, Path.GetFullPath(input)); + } + + [PlatformSpecific(PlatformID.Windows)] + [Theory] + [InlineData(@"\\")] + [InlineData(@"\\server")] + [InlineData(@"\\server\")] + [InlineData(@"\\server\..")] + [InlineData(@"\\?\GLOBALROOT\")] + public static void GetFullPath_Windows_UNC_Invalid(string invalidPath) + { + Assert.Throws<ArgumentException>(() => Path.GetFullPath(invalidPath)); + } + + [PlatformSpecific(PlatformID.Windows)] + [Fact] + public static void GetFullPath_Windows_83Paths() + { + // Create a temporary file name with a name longer than 8.3 such that it'll need to be shortened. + string tempFilePath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N") + ".txt"); + File.Create(tempFilePath).Dispose(); + try + { + // Get its short name + var sb = new StringBuilder(260); + if (GetShortPathName(tempFilePath, sb, sb.Capacity) > 0) // only proceed if we could successfully create the short name { - // Validate a short name can be expanded - var sb = new StringBuilder(260); - if (GetShortPathName(tempFilePath, sb, sb.Capacity) > 0) // only proceed if we could successfully create the short name - { - Assert.Equal(tempFilePath, Path.GetFullPath(sb.ToString())); + // Make sure the shortened name expands back to the original one + Assert.Equal(tempFilePath, Path.GetFullPath(sb.ToString())); - // Validate case where short name doesn't expand to a real file - string invalidShortName = @"S:\DOESNT~1\USERNA~1.RED\LOCALS~1\Temp\bg3ylpzp"; - Assert.Equal(invalidShortName, Path.GetFullPath(invalidShortName)); + // Validate case where short name doesn't expand to a real file + string invalidShortName = @"S:\DOESNT~1\USERNA~1.RED\LOCALS~1\Temp\bg3ylpzp"; + Assert.Equal(invalidShortName, Path.GetFullPath(invalidShortName)); - // Same thing, but with a long path that normalizes down to a short enough one - var shortLongName = new StringBuilder(invalidShortName); - for (int i = 0; i < 1000; i++) - shortLongName.Append(Path.DirectorySeparatorChar).Append('.'); - Assert.Equal(invalidShortName, Path.GetFullPath(shortLongName.ToString())); + // Same thing, but with a long path that normalizes down to a short enough one + const int Iters = 1000; + var shortLongName = new StringBuilder(invalidShortName, invalidShortName.Length + (Iters * 2)); + for (int i = 0; i < Iters; i++) + { + shortLongName.Append(Path.DirectorySeparatorChar).Append('.'); } + Assert.Equal(invalidShortName, Path.GetFullPath(shortLongName.ToString())); } - finally - { - File.Delete(tempFilePath); - } + } + finally + { + File.Delete(tempFilePath); } } |