diff options
54 files changed, 3271 insertions, 5742 deletions
diff --git a/clr.coreclr.props b/clr.coreclr.props index 8ff0f0b263..516f4ea311 100644 --- a/clr.coreclr.props +++ b/clr.coreclr.props @@ -82,6 +82,8 @@ <FeatureCoreFxFileStream>true</FeatureCoreFxFileStream> <FeatureCoreFxShim>true</FeatureCoreFxShim> <FeatureCoreFxOverlapped>true</FeatureCoreFxOverlapped> + <FeatureCoreFxPath>true</FeatureCoreFxPath> + <FeatureCoreFxInteropServices>true</FeatureCoreFxInteropServices> <!-- We only want to use the CoreFx implementation of SecureString when we are not building the legacy surface area --> <FeatureCoreFxSecureString Condition="'$(FeatureLegacySurface)'==''" >true</FeatureCoreFxSecureString> diff --git a/src/mscorlib/corefx/Interop/Unix/System.Native/Interop.GetCwd.cs b/src/mscorlib/corefx/Interop/Unix/System.Native/Interop.GetCwd.cs new file mode 100644 index 0000000000..724e342342 --- /dev/null +++ b/src/mscorlib/corefx/Interop/Unix/System.Native/Interop.GetCwd.cs @@ -0,0 +1,74 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.InteropServices; + +internal static partial class Interop +{ + internal static partial class Sys + { + [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_GetCwd", SetLastError = true)] + private static unsafe extern byte* GetCwd(byte* buffer, int bufferLength); + + 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 = GetCwdHelper(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.Sys.MaxPath; + if (StackLimit < maxPath) + { + int bufferSize = StackLimit; + do + { + checked { bufferSize *= 2; } + var buf = new byte[Math.Min(bufferSize, maxPath)]; + fixed (byte* ptr = buf) + { + result = GetCwdHelper(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(new ErrorInfo(Interop.Error.ENAMETOOLONG)); + } + + private static unsafe string GetCwdHelper(byte* ptr, int bufferSize) + { + // Call the real getcwd + byte* result = GetCwd(ptr, 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. + ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo(); + if (errorInfo.Error == Interop.Error.ERANGE) + { + return null; + } + throw Interop.GetExceptionForIoErrno(errorInfo); + } + } +} diff --git a/src/mscorlib/corefx/Interop/Unix/System.Native/Interop.GetUnixName.cs b/src/mscorlib/corefx/Interop/Unix/System.Native/Interop.GetUnixName.cs new file mode 100644 index 0000000000..33664c4d39 --- /dev/null +++ b/src/mscorlib/corefx/Interop/Unix/System.Native/Interop.GetUnixName.cs @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.InteropServices; + +internal static partial class Interop +{ + internal static partial class Sys + { + [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_GetUnixName")] + private static extern IntPtr GetUnixNamePrivate(); + + internal static string GetUnixName() + { + IntPtr ptr = GetUnixNamePrivate(); + return Marshal.PtrToStringAnsi(ptr); + } + } +} diff --git a/src/mscorlib/corefx/Interop/Unix/System.Native/Interop.MksTemps.cs b/src/mscorlib/corefx/Interop/Unix/System.Native/Interop.MksTemps.cs new file mode 100644 index 0000000000..b8694d9007 --- /dev/null +++ b/src/mscorlib/corefx/Interop/Unix/System.Native/Interop.MksTemps.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.InteropServices; + +internal static partial class Interop +{ + internal static partial class Sys + { + [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_MksTemps", SetLastError = true)] + internal static extern IntPtr MksTemps( + byte[] template, + int suffixlen); + } +} diff --git a/src/mscorlib/corefx/Interop/Unix/System.Native/Interop.PathConf.cs b/src/mscorlib/corefx/Interop/Unix/System.Native/Interop.PathConf.cs new file mode 100644 index 0000000000..4a1fcf67d0 --- /dev/null +++ b/src/mscorlib/corefx/Interop/Unix/System.Native/Interop.PathConf.cs @@ -0,0 +1,73 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.InteropServices; + +internal static partial class Interop +{ + internal static partial class Sys + { + internal static int DEFAULT_PC_NAME_MAX = 255; + + internal enum PathConfName : int + { + PC_LINK_MAX = 1, + PC_MAX_CANON = 2, + PC_MAX_INPUT = 3, + PC_NAME_MAX = 4, + PC_PATH_MAX = 5, + PC_PIPE_BUF = 6, + PC_CHOWN_RESTRICTED = 7, + PC_NO_TRUNC = 8, + PC_VDISABLE = 9, + } + + /// <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 + { + // Benign race condition on cached value + if (s_maxPath < 0) + { + // GetMaximumPath returns a long from PathConf + // but our callers expect an int so we need to convert. + long temp = GetMaximumPath(); + if (temp > int.MaxValue) + s_maxPath = int.MaxValue; + else + s_maxPath = Convert.ToInt32(temp); + } + return s_maxPath; + } + } + + internal static int MaxName + { + get + { + // Benign race condition on cached value + if (s_maxName < 0) + { + int result = PathConf("/", PathConfName.PC_NAME_MAX); + s_maxName = result >= 0 ? result : DEFAULT_PC_NAME_MAX; + } + + return s_maxName; + } + } + + [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_PathConf", SetLastError = true)] + private static extern int PathConf(string path, PathConfName name); + + [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_GetMaximumPath")] + private static extern long GetMaximumPath(); + } +} diff --git a/src/mscorlib/corefx/Interop/Windows/BCrypt/Interop.BCryptGenRandom.cs b/src/mscorlib/corefx/Interop/Windows/BCrypt/Interop.BCryptGenRandom.cs new file mode 100644 index 0000000000..d2ce4131b0 --- /dev/null +++ b/src/mscorlib/corefx/Interop/Windows/BCrypt/Interop.BCryptGenRandom.cs @@ -0,0 +1,26 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; + +internal partial class Interop +{ + internal partial class BCrypt + { + internal static unsafe NTSTATUS BCryptGenRandom(byte* pbBuffer, int count) + { + Debug.Assert(pbBuffer != null); + Debug.Assert(count >= 0); + + return BCryptGenRandom(IntPtr.Zero, pbBuffer, count, BCRYPT_USE_SYSTEM_PREFERRED_RNG); + } + + private const int BCRYPT_USE_SYSTEM_PREFERRED_RNG = 0x00000002; + + [DllImport(Libraries.BCrypt, CharSet = CharSet.Unicode)] + private static unsafe extern NTSTATUS BCryptGenRandom(IntPtr hAlgorithm, byte* pbBuffer, int cbBuffer, int dwFlags); + } +} diff --git a/src/mscorlib/corefx/Interop/Windows/BCrypt/Interop.NTSTATUS.cs b/src/mscorlib/corefx/Interop/Windows/BCrypt/Interop.NTSTATUS.cs new file mode 100644 index 0000000000..49d674f399 --- /dev/null +++ b/src/mscorlib/corefx/Interop/Windows/BCrypt/Interop.NTSTATUS.cs @@ -0,0 +1,19 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; + +internal partial class Interop +{ + internal partial class BCrypt + { + internal enum NTSTATUS : uint + { + STATUS_SUCCESS = 0x0, + STATUS_NOT_FOUND = 0xc0000225, + STATUS_INVALID_PARAMETER = 0xc000000d, + STATUS_NO_MEMORY = 0xc0000017, + } + } +} diff --git a/src/mscorlib/corefx/Interop/Windows/mincore/Interop.GetFullPathNameW.cs b/src/mscorlib/corefx/Interop/Windows/mincore/Interop.GetFullPathNameW.cs new file mode 100644 index 0000000000..a34cc33db3 --- /dev/null +++ b/src/mscorlib/corefx/Interop/Windows/mincore/Interop.GetFullPathNameW.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.InteropServices; + +internal partial class Interop +{ + internal partial class mincore + { + /// <summary> + /// WARNING: This method does not implicitly handle long paths. Use GetFullPathName or PathHelper. + /// </summary> + [DllImport(Libraries.CoreFile_L1, SetLastError = true, CharSet = CharSet.Unicode, BestFitMapping = false, ExactSpelling = true)] + unsafe internal static extern uint GetFullPathNameW(char* path, uint numBufferChars, SafeHandle buffer, IntPtr mustBeZero); + } +} diff --git a/src/mscorlib/corefx/Interop/Windows/mincore/Interop.GetLongPathNameW.cs b/src/mscorlib/corefx/Interop/Windows/mincore/Interop.GetLongPathNameW.cs new file mode 100644 index 0000000000..d50db6650b --- /dev/null +++ b/src/mscorlib/corefx/Interop/Windows/mincore/Interop.GetLongPathNameW.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.InteropServices; + +partial class Interop +{ + partial class mincore + { + /// <summary> + /// WARNING: This method does not implicitly handle long paths. Use GetFullPath/PathHelper. + /// </summary> + [DllImport(Libraries.CoreFile_L1, SetLastError = true, CharSet = CharSet.Unicode, BestFitMapping = false, ExactSpelling = true)] + internal static extern uint GetLongPathNameW(SafeHandle lpszShortPath, SafeHandle lpszLongPath, uint cchBuffer); + } +} diff --git a/src/mscorlib/corefx/Interop/Windows/mincore/Interop.GetTempFileNameW.cs b/src/mscorlib/corefx/Interop/Windows/mincore/Interop.GetTempFileNameW.cs new file mode 100644 index 0000000000..f06d11be52 --- /dev/null +++ b/src/mscorlib/corefx/Interop/Windows/mincore/Interop.GetTempFileNameW.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Text; +using System.Runtime.InteropServices; + +partial class Interop +{ + partial class mincore + { + [DllImport(Libraries.CoreFile_L1, CharSet = CharSet.Unicode, SetLastError = true, BestFitMapping = false)] + internal static extern uint GetTempFileNameW(string tmpPath, string prefix, uint uniqueIdOrZero, [Out]StringBuilder tmpFileName); + } +} diff --git a/src/mscorlib/corefx/Interop/Windows/mincore/Interop.GetTempPathW.cs b/src/mscorlib/corefx/Interop/Windows/mincore/Interop.GetTempPathW.cs new file mode 100644 index 0000000000..0ccc27c9ec --- /dev/null +++ b/src/mscorlib/corefx/Interop/Windows/mincore/Interop.GetTempPathW.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.IO; +using System.Text; +using System.Runtime.InteropServices; + +partial class Interop +{ + partial class mincore + { + [DllImport(Libraries.CoreFile_L1_2, CharSet = CharSet.Unicode, BestFitMapping = false)] + internal static extern uint GetTempPathW(int bufferLen, [Out]StringBuilder buffer); + } +} diff --git a/src/mscorlib/corefx/SR.cs b/src/mscorlib/corefx/SR.cs index d234c9d5f3..d820613f7e 100644 --- a/src/mscorlib/corefx/SR.cs +++ b/src/mscorlib/corefx/SR.cs @@ -551,6 +551,36 @@ internal static class SR get { return Environment.GetResourceString("ArgumentException_BufferNotFromPool"); } } + public static string Argument_InvalidPathChars + { + get { return Environment.GetResourceString("Argument_InvalidPathChars"); } + } + + public static string Argument_PathFormatNotSupported + { + get { return Environment.GetResourceString("Argument_PathFormatNotSupported"); } + } + + public static string Arg_PathIllegal + { + get { return Environment.GetResourceString("Arg_PathIllegal"); } + } + + public static string Arg_PathIllegalUNC + { + get { return Environment.GetResourceString("Arg_PathIllegalUNC"); } + } + + public static string Arg_InvalidSearchPattern + { + get { return Environment.GetResourceString("Arg_InvalidSearchPattern"); } + } + + public static string InvalidOperation_Cryptography + { + get { return Environment.GetResourceString("InvalidOperation_Cryptography"); } + } + public static string Format(string formatString, params object[] args) { return string.Format(CultureInfo.CurrentCulture, formatString, args); diff --git a/src/mscorlib/corefx/System/IO/Path.Unix.cs b/src/mscorlib/corefx/System/IO/Path.Unix.cs new file mode 100644 index 0000000000..2dd1907007 --- /dev/null +++ b/src/mscorlib/corefx/System/IO/Path.Unix.cs @@ -0,0 +1,256 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Text; + +namespace System.IO +{ + public static partial class Path + { + public static readonly char DirectorySeparatorChar = '/'; + public static readonly char VolumeSeparatorChar = '/'; + public static readonly char PathSeparator = ':'; + + private const string DirectorySeparatorCharAsString = "/"; + + public static char[] GetInvalidFileNameChars() => new char[] { '\0', '/' }; + + internal static readonly int MaxPath = Interop.Sys.MaxPath; + private static readonly int MaxLongPath = MaxPath; + + private static readonly bool s_isMac = Interop.Sys.GetUnixName() == "OSX"; + + // Expands the given path to a fully qualified path. + public static string GetFullPath(string path) + { + if (path == null) + throw new ArgumentNullException(nameof(path)); + + if (path.Length == 0) + throw new ArgumentException(SR.Arg_PathIllegal); + + PathInternal.CheckInvalidPathChars(path); + + // Expand with current directory if necessary + if (!IsPathRooted(path)) + { + path = Combine(Interop.Sys.GetCwd(), 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. + string collapsedString = RemoveRelativeSegments(path); + + Debug.Assert(collapsedString.Length < path.Length || collapsedString.ToString() == path, + "Either we've removed characters, or the string should be unmodified from the input path."); + + if (collapsedString.Length > MaxPath) + { + throw new PathTooLongException(SR.IO_PathTooLong); + } + + string result = collapsedString.Length == 0 ? DirectorySeparatorCharAsString : collapsedString; + + return result; + } + + /// <summary> + /// Try to remove relative segments from the given path (without combining with a root). + /// </summary> + /// <param name="skip">Skip the specified number of characters before evaluating.</param> + private static string RemoveRelativeSegments(string path, int skip = 0) + { + bool flippedSeparator = false; + + // Remove "//", "/./", and "/../" from the path by 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); + if (skip > 0) + { + sb.Append(path, 0, skip); + } + + int componentCharCount = 0; + for (int i = skip; i < path.Length; i++) + { + char c = path[i]; + + if (PathInternal.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 (PathInternal.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 || PathInternal.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 || PathInternal.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 (PathInternal.IsDirectorySeparator(sb[s])) + { + sb.Length = s; + break; + } + } + if (s < 0) + { + sb.Length = 0; + } + + i += 2; + continue; + } + } + + if (++componentCharCount > PathInternal.MaxComponentLength) + { + throw new PathTooLongException(SR.IO_PathTooLong); + } + + // Normalize the directory separator if needed + if (c != Path.DirectorySeparatorChar && c == Path.AltDirectorySeparatorChar) + { + c = Path.DirectorySeparatorChar; + flippedSeparator = true; + } + + sb.Append(c); + } + + if (flippedSeparator || sb.Length != path.Length) + { + return StringBuilderCache.GetStringAndRelease(sb); + } + else + { + // We haven't changed the source path, return the original + StringBuilderCache.Release(sb); + return path; + } + } + + private static string RemoveLongPathPrefix(string path) + { + return path; // nop. There's nothing special about "long" paths on Unix. + } + + public static string GetTempPath() + { + 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 : + PathInternal.IsDirectorySeparator(path[path.Length - 1]) ? path : + path + DirectorySeparatorChar; + } + + public static string GetTempFileName() + { + 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. + IntPtr fd = Interop.CheckIo(Interop.Sys.MksTemps(name, SuffixByteLength)); + Interop.Sys.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) + { + if (path == null) + return false; + + PathInternal.CheckInvalidPathChars(path); + return path.Length > 0 && path[0] == DirectorySeparatorChar; + } + + public static string GetPathRoot(string path) + { + if (path == null) return null; + return IsPathRooted(path) ? DirectorySeparatorCharAsString : String.Empty; + } + + private static unsafe void GetCryptoRandomBytes(byte* bytes, int byteCount) + { +#if FEATURE_CORECLR + // We want to avoid dependencies on the Crypto library when compiling in CoreCLR. This + // will use the existing PAL implementation. + byte[] buffer = new byte[KeyLength]; + Microsoft.Win32.Win32Native.Random(bStrong: true, buffer: buffer, length: KeyLength); + Runtime.InteropServices.Marshal.Copy(buffer, 0, (IntPtr)bytes, KeyLength); +#else + if (s_isMac) + { + GetCryptoRandomBytesApple(bytes, byteCount); + } + else + { + GetCryptoRandomBytesOpenSsl(bytes, byteCount); + } +#endif + } + +#if !FEATURE_CORECLR + private static unsafe void GetCryptoRandomBytesApple(byte* bytes, int byteCount) + { + Debug.Assert(bytes != null); + Debug.Assert(byteCount >= 0); + + if (Interop.CommonCrypto.CCRandomGenerateBytes(bytes, byteCount) != 0) + { + throw new InvalidOperationException(SR.InvalidOperation_Cryptography); + } + } + + private static unsafe void GetCryptoRandomBytesOpenSsl(byte* bytes, int byteCount) + { + Debug.Assert(bytes != null); + Debug.Assert(byteCount >= 0); + + if (!Interop.Crypto.GetRandomBytes(bytes, byteCount)) + { + throw new InvalidOperationException(SR.InvalidOperation_Cryptography); + } + } +#endif + + /// <summary>Gets whether the system is case-sensitive.</summary> + internal static bool IsCaseSensitive { get { return !s_isMac; } } + } +} diff --git a/src/mscorlib/corefx/System/IO/Path.Win32.cs b/src/mscorlib/corefx/System/IO/Path.Win32.cs new file mode 100644 index 0000000000..8a9e62e6e5 --- /dev/null +++ b/src/mscorlib/corefx/System/IO/Path.Win32.cs @@ -0,0 +1,36 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; + +namespace System.IO +{ + public static partial class Path + { + private static unsafe void GetCryptoRandomBytes(byte* bytes, int byteCount) + { + // We need to fill a byte array with cryptographically-strong random bytes, but we can't reference + // System.Security.Cryptography.RandomNumberGenerator.dll due to layering. Instead, we just + // call to BCryptGenRandom directly, which is all that RandomNumberGenerator does. + + Debug.Assert(bytes != null); + Debug.Assert(byteCount >= 0); + + Interop.BCrypt.NTSTATUS status = Interop.BCrypt.BCryptGenRandom(bytes, byteCount); + if (status == Interop.BCrypt.NTSTATUS.STATUS_SUCCESS) + { + return; + } + else if (status == Interop.BCrypt.NTSTATUS.STATUS_NO_MEMORY) + { + throw new OutOfMemoryException(); + } + else + { + Debug.Fail("BCryptGenRandom should only fail due to OOM or invalid args / handle inputs."); + throw new InvalidOperationException(); + } + } + } +} diff --git a/src/mscorlib/corefx/System/IO/Path.Windows.cs b/src/mscorlib/corefx/System/IO/Path.Windows.cs new file mode 100644 index 0000000000..b597efc54e --- /dev/null +++ b/src/mscorlib/corefx/System/IO/Path.Windows.cs @@ -0,0 +1,153 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; +using System.Text; + +namespace System.IO +{ + public static partial class Path + { + public static readonly char DirectorySeparatorChar = '\\'; + public static readonly char VolumeSeparatorChar = ':'; + public static readonly char PathSeparator = ';'; + + private const string DirectorySeparatorCharAsString = "\\"; + + public static char[] GetInvalidFileNameChars() => new char[] + { + '\"', '<', '>', '|', '\0', + (char)1, (char)2, (char)3, (char)4, (char)5, (char)6, (char)7, (char)8, (char)9, (char)10, + (char)11, (char)12, (char)13, (char)14, (char)15, (char)16, (char)17, (char)18, (char)19, (char)20, + (char)21, (char)22, (char)23, (char)24, (char)25, (char)26, (char)27, (char)28, (char)29, (char)30, + (char)31, ':', '*', '?', '\\', '/' + }; + + // The max total path is 260, and the max individual component length is 255. + // For example, D:\<256 char file name> isn't legal, even though it's under 260 chars. + internal static readonly int MaxPath = 260; + internal static readonly int MaxLongPath = short.MaxValue; + + // Expands the given path to a fully qualified path. + public static string GetFullPath(string path) + { + if (path == null) + throw new ArgumentNullException(nameof(path)); + + // Embedded null characters are the only invalid character case we want to check up front. + // This is because the nulls will signal the end of the string to Win32 and therefore have + // unpredictable results. Other invalid characters we give a chance to be normalized out. + if (path.IndexOf('\0') != -1) + throw new ArgumentException(SR.Argument_InvalidPathChars, nameof(path)); + + if (PathInternal.IsExtended(path)) + { + // We can't really know what is valid for all cases of extended paths. + // + // - object names can include other characters as well (':', '/', etc.) + // - even file objects have different rules (pipe names can contain most characters) + // + // As such we will do no further analysis of extended paths to avoid blocking known and unknown + // scenarios as well as minimizing compat breaks should we block now and need to unblock later. + return path; + } + + bool isDevice = PathInternal.IsDevice(path); + if (!isDevice) + { + // Toss out paths with colons that aren't a valid drive specifier. + // Cannot start with a colon and can only be of the form "C:". + // (Note that we used to explicitly check "http:" and "file:"- these are caught by this check now.) + int startIndex = PathInternal.PathStartSkip(path); + + // Move past the colon + startIndex += 2; + + if ((path.Length > 0 && path[0] == VolumeSeparatorChar) + || (path.Length >= startIndex && path[startIndex - 1] == VolumeSeparatorChar && !PathInternal.IsValidDriveChar(path[startIndex - 2])) + || (path.Length > startIndex && path.IndexOf(VolumeSeparatorChar, startIndex) != -1)) + { + throw new NotSupportedException(SR.Argument_PathFormatNotSupported); + } + } + + // Technically this doesn't matter but we used to throw for this case + if (string.IsNullOrWhiteSpace(path)) + throw new ArgumentException(SR.Arg_PathIllegal); + + // We don't want to check invalid characters for device format- see comments for extended above + string fullPath = PathHelper.Normalize(path, checkInvalidCharacters: !isDevice, expandShortPaths: true); + + if (!isDevice) + { + // Emulate FileIOPermissions checks, retained for compatibility (normal invalid characters have already been checked) + if (PathInternal.HasWildCardCharacters(fullPath)) + throw new ArgumentException(SR.Argument_InvalidPathChars, nameof(path)); + } + + return fullPath; + } + + public static string GetTempPath() + { + StringBuilder sb = StringBuilderCache.Acquire(MaxPath); + uint r = Interop.mincore.GetTempPathW(MaxPath, sb); + if (r == 0) + throw Win32Marshal.GetExceptionForLastWin32Error(); + return GetFullPath(StringBuilderCache.GetStringAndRelease(sb)); + } + + // Returns a unique temporary file name, and creates a 0-byte file by that + // name on disk. + public static string GetTempFileName() + { + string path = GetTempPath(); + + StringBuilder sb = StringBuilderCache.Acquire(MaxPath); + uint r = Interop.mincore.GetTempFileNameW(path, "tmp", 0, sb); + if (r == 0) + throw Win32Marshal.GetExceptionForLastWin32Error(); + return StringBuilderCache.GetStringAndRelease(sb); + } + + // Tests if the given path contains a root. A path is considered rooted + // if it starts with a backslash ("\") or a drive letter and a colon (":"). + public static bool IsPathRooted(string path) + { + if (path != null) + { + PathInternal.CheckInvalidPathChars(path); + + int length = path.Length; + if ((length >= 1 && PathInternal.IsDirectorySeparator(path[0])) || + (length >= 2 && path[1] == VolumeSeparatorChar)) + return true; + } + return false; + } + + // Returns the root portion of the given path. The resulting string + // consists of those rightmost characters of the path that constitute the + // root of the path. Possible patterns for the resulting string are: An + // empty string (a relative path on the current drive), "\" (an absolute + // path on the current drive), "X:" (a relative path on a given drive, + // where X is the drive letter), "X:\" (an absolute path on a given drive), + // and "\\server\share" (a UNC path for a given server and share name). + // The resulting string is null if path is null. + public static string GetPathRoot(string path) + { + if (path == null) return null; + PathInternal.CheckInvalidPathChars(path); + + // Need to return the normalized directory separator + path = PathInternal.NormalizeDirectorySeparators(path); + + int pathRoot = PathInternal.GetRootLength(path); + return pathRoot <= 0 ? string.Empty : path.Substring(0, pathRoot); + } + + /// <summary>Gets whether the system is case-sensitive.</summary> + internal static bool IsCaseSensitive { get { return false; } } + } +} diff --git a/src/mscorlib/corefx/System/IO/Path.cs b/src/mscorlib/corefx/System/IO/Path.cs new file mode 100644 index 0000000000..3b1ba6b07d --- /dev/null +++ b/src/mscorlib/corefx/System/IO/Path.cs @@ -0,0 +1,578 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; +using System.Diagnostics.Contracts; +using System.Text; + +namespace System.IO +{ + // Provides methods for processing file system strings in a cross-platform manner. + // Most of the methods don't do a complete parsing (such as examining a UNC hostname), + // but they will handle most string operations. + public static partial class Path + { + // Platform specific alternate directory separator character. + // There is only one directory separator char on Unix, which is the same + // as the alternate separator on Windows, so same definition is used for both. + public static readonly char AltDirectorySeparatorChar = '/'; + + // For generating random file names + // 8 random bytes provides 12 chars in our encoding for the 8.3 name. + const int KeyLength = 8; + + [Obsolete("Please use GetInvalidPathChars or GetInvalidFileNameChars instead.")] + public static readonly char[] InvalidPathChars = GetInvalidPathChars(); + + // Changes the extension of a file path. The path parameter + // specifies a file path, and the extension parameter + // specifies a file extension (with a leading period, such as + // ".exe" or ".cs"). + // + // The function returns a file path with the same root, directory, and base + // name parts as path, but with the file extension changed to + // the specified extension. If path is null, the function + // returns null. If path does not contain a file extension, + // the new file extension is appended to the path. If extension + // is null, any existing extension is removed from path. + public static string ChangeExtension(string path, string extension) + { + if (path != null) + { + PathInternal.CheckInvalidPathChars(path); + + string s = path; + for (int i = path.Length - 1; i >= 0; i--) + { + char ch = path[i]; + if (ch == '.') + { + s = path.Substring(0, i); + break; + } + if (PathInternal.IsDirectoryOrVolumeSeparator(ch)) break; + } + + if (extension != null && path.Length != 0) + { + s = (extension.Length == 0 || extension[0] != '.') ? + s + "." + extension : + s + extension; + } + + return s; + } + return null; + } + + // Returns the directory path of a file path. This method effectively + // removes the last element of the given file path, i.e. it returns a + // string consisting of all characters up to but not including the last + // backslash ("\") in the file path. The returned value is null if the file + // path is null or if the file path denotes a root (such as "\", "C:", or + // "\\server\share"). + public static string GetDirectoryName(string path) + { + if (path != null) + { + PathInternal.CheckInvalidPathChars(path); + path = PathInternal.NormalizeDirectorySeparators(path); + int root = PathInternal.GetRootLength(path); + + int i = path.Length; + if (i > root) + { + while (i > root && !PathInternal.IsDirectorySeparator(path[--i])) ; + return path.Substring(0, i); + } + } + return null; + } + + public static char[] GetInvalidPathChars() + { + return PathInternal.GetInvalidPathChars(); + } + + // Returns the extension of the given path. The returned value includes the + // period (".") character of the extension except when you have a terminal period when you get string.Empty, such as ".exe" or + // ".cpp". The returned value is null if the given path is + // null or if the given path does not include an extension. + [Pure] + public static string GetExtension(string path) + { + if (path == null) + return null; + + PathInternal.CheckInvalidPathChars(path); + int length = path.Length; + for (int i = length - 1; i >= 0; i--) + { + char ch = path[i]; + if (ch == '.') + { + if (i != length - 1) + return path.Substring(i, length - i); + else + return string.Empty; + } + if (PathInternal.IsDirectoryOrVolumeSeparator(ch)) + break; + } + return string.Empty; + } + + // Returns the name and extension parts of the given path. The resulting + // string contains the characters of path that follow the last + // separator in path. The resulting string is null if path is null. + [Pure] + public static string GetFileName(string path) + { + if (path == null) + return null; + + int offset = PathInternal.FindFileNameIndex(path); + int count = path.Length - offset; + return path.Substring(offset, count); + } + + [Pure] + public static string GetFileNameWithoutExtension(string path) + { + if (path == null) + return null; + + int length = path.Length; + int offset = PathInternal.FindFileNameIndex(path); + + int end = path.LastIndexOf('.', length - 1, length - offset); + return end == -1 ? + path.Substring(offset) : // No extension was found + path.Substring(offset, end - offset); + } + + // Returns a cryptographically strong random 8.3 string that can be + // used as either a folder name or a file name. + public static unsafe string GetRandomFileName() + { + + byte* pKey = stackalloc byte[KeyLength]; + GetCryptoRandomBytes(pKey, KeyLength); + + const int RandomFileNameLength = 12; + char* pRandomFileName = stackalloc char[RandomFileNameLength]; + Populate83FileNameFromRandomBytes(pKey, KeyLength, pRandomFileName, RandomFileNameLength); + return new string(pRandomFileName, 0, RandomFileNameLength); + } + + // Tests if a path includes a file extension. The result is + // true if the characters that follow the last directory + // separator ('\\' or '/') or volume separator (':') in the path include + // a period (".") other than a terminal period. The result is false otherwise. + [Pure] + public static bool HasExtension(string path) + { + if (path != null) + { + PathInternal.CheckInvalidPathChars(path); + + for (int i = path.Length - 1; i >= 0; i--) + { + char ch = path[i]; + if (ch == '.') + { + return i != path.Length - 1; + } + if (PathInternal.IsDirectoryOrVolumeSeparator(ch)) break; + } + } + return false; + } + + public static string Combine(string path1, string path2) + { + if (path1 == null || path2 == null) + throw new ArgumentNullException((path1 == null) ? nameof(path1): nameof(path2)); + Contract.EndContractBlock(); + + PathInternal.CheckInvalidPathChars(path1); + PathInternal.CheckInvalidPathChars(path2); + + return CombineNoChecks(path1, path2); + } + + public static string Combine(string path1, string path2, string path3) + { + if (path1 == null || path2 == null || path3 == null) + throw new ArgumentNullException((path1 == null) ? nameof(path1): (path2 == null) ? nameof(path2): nameof(path3)); + Contract.EndContractBlock(); + + PathInternal.CheckInvalidPathChars(path1); + PathInternal.CheckInvalidPathChars(path2); + PathInternal.CheckInvalidPathChars(path3); + + return CombineNoChecks(path1, path2, path3); + } + + public static string Combine(string path1, string path2, string path3, string path4) + { + if (path1 == null || path2 == null || path3 == null || path4 == null) + throw new ArgumentNullException((path1 == null) ? nameof(path1): (path2 == null) ? nameof(path2): (path3 == null) ? nameof(path3): nameof(path4)); + Contract.EndContractBlock(); + + PathInternal.CheckInvalidPathChars(path1); + PathInternal.CheckInvalidPathChars(path2); + PathInternal.CheckInvalidPathChars(path3); + PathInternal.CheckInvalidPathChars(path4); + + return CombineNoChecks(path1, path2, path3, path4); + } + + public static string Combine(params string[] paths) + { + if (paths == null) + { + throw new ArgumentNullException(nameof(paths)); + } + Contract.EndContractBlock(); + + int finalSize = 0; + int firstComponent = 0; + + // We have two passes, the first calculates how large a buffer to allocate and does some precondition + // checks on the paths passed in. The second actually does the combination. + + for (int i = 0; i < paths.Length; i++) + { + if (paths[i] == null) + { + throw new ArgumentNullException(nameof(paths)); + } + + if (paths[i].Length == 0) + { + continue; + } + + PathInternal.CheckInvalidPathChars(paths[i]); + + if (IsPathRooted(paths[i])) + { + firstComponent = i; + finalSize = paths[i].Length; + } + else + { + finalSize += paths[i].Length; + } + + char ch = paths[i][paths[i].Length - 1]; + if (!PathInternal.IsDirectoryOrVolumeSeparator(ch)) + finalSize++; + } + + StringBuilder finalPath = StringBuilderCache.Acquire(finalSize); + + for (int i = firstComponent; i < paths.Length; i++) + { + if (paths[i].Length == 0) + { + continue; + } + + if (finalPath.Length == 0) + { + finalPath.Append(paths[i]); + } + else + { + char ch = finalPath[finalPath.Length - 1]; + if (!PathInternal.IsDirectoryOrVolumeSeparator(ch)) + { + finalPath.Append(DirectorySeparatorChar); + } + + finalPath.Append(paths[i]); + } + } + + return StringBuilderCache.GetStringAndRelease(finalPath); + } + + private static string CombineNoChecks(string path1, string path2) + { + if (path2.Length == 0) + return path1; + + if (path1.Length == 0) + return path2; + + if (IsPathRooted(path2)) + return path2; + + char ch = path1[path1.Length - 1]; + return PathInternal.IsDirectoryOrVolumeSeparator(ch) ? + path1 + path2 : + path1 + DirectorySeparatorCharAsString + path2; + } + + private static string CombineNoChecks(string path1, string path2, string path3) + { + if (path1.Length == 0) + return CombineNoChecks(path2, path3); + if (path2.Length == 0) + return CombineNoChecks(path1, path3); + if (path3.Length == 0) + return CombineNoChecks(path1, path2); + + if (IsPathRooted(path3)) + return path3; + if (IsPathRooted(path2)) + return CombineNoChecks(path2, path3); + + bool hasSep1 = PathInternal.IsDirectoryOrVolumeSeparator(path1[path1.Length - 1]); + bool hasSep2 = PathInternal.IsDirectoryOrVolumeSeparator(path2[path2.Length - 1]); + + if (hasSep1 && hasSep2) + { + return path1 + path2 + path3; + } + else if (hasSep1) + { + return path1 + path2 + DirectorySeparatorCharAsString + path3; + } + else if (hasSep2) + { + return path1 + DirectorySeparatorCharAsString + path2 + path3; + } + else + { + // string.Concat only has string-based overloads up to four arguments; after that requires allocating + // a params string[]. Instead, try to use a cached StringBuilder. + StringBuilder sb = StringBuilderCache.Acquire(path1.Length + path2.Length + path3.Length + 2); + sb.Append(path1) + .Append(DirectorySeparatorChar) + .Append(path2) + .Append(DirectorySeparatorChar) + .Append(path3); + return StringBuilderCache.GetStringAndRelease(sb); + } + } + + private static string CombineNoChecks(string path1, string path2, string path3, string path4) + { + if (path1.Length == 0) + return CombineNoChecks(path2, path3, path4); + if (path2.Length == 0) + return CombineNoChecks(path1, path3, path4); + if (path3.Length == 0) + return CombineNoChecks(path1, path2, path4); + if (path4.Length == 0) + return CombineNoChecks(path1, path2, path3); + + if (IsPathRooted(path4)) + return path4; + if (IsPathRooted(path3)) + return CombineNoChecks(path3, path4); + if (IsPathRooted(path2)) + return CombineNoChecks(path2, path3, path4); + + bool hasSep1 = PathInternal.IsDirectoryOrVolumeSeparator(path1[path1.Length - 1]); + bool hasSep2 = PathInternal.IsDirectoryOrVolumeSeparator(path2[path2.Length - 1]); + bool hasSep3 = PathInternal.IsDirectoryOrVolumeSeparator(path3[path3.Length - 1]); + + if (hasSep1 && hasSep2 && hasSep3) + { + // Use string.Concat overload that takes four strings + return path1 + path2 + path3 + path4; + } + else + { + // string.Concat only has string-based overloads up to four arguments; after that requires allocating + // a params string[]. Instead, try to use a cached StringBuilder. + StringBuilder sb = StringBuilderCache.Acquire(path1.Length + path2.Length + path3.Length + path4.Length + 3); + + sb.Append(path1); + if (!hasSep1) + { + sb.Append(DirectorySeparatorChar); + } + + sb.Append(path2); + if (!hasSep2) + { + sb.Append(DirectorySeparatorChar); + } + + sb.Append(path3); + if (!hasSep3) + { + sb.Append(DirectorySeparatorChar); + } + + sb.Append(path4); + + return StringBuilderCache.GetStringAndRelease(sb); + } + } + + private static readonly char[] s_base32Char = { + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', + 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', + 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', + 'y', 'z', '0', '1', '2', '3', '4', '5'}; + + private static unsafe void Populate83FileNameFromRandomBytes(byte* bytes, int byteCount, char* chars, int charCount) + { + Debug.Assert(bytes != null); + Debug.Assert(chars != null); + + // This method requires bytes of length 8 and chars of length 12. + Debug.Assert(byteCount == 8, $"Unexpected {nameof(byteCount)}"); + Debug.Assert(charCount == 12, $"Unexpected {nameof(charCount)}"); + + byte b0 = bytes[0]; + byte b1 = bytes[1]; + byte b2 = bytes[2]; + byte b3 = bytes[3]; + byte b4 = bytes[4]; + + // Consume the 5 Least significant bits of the first 5 bytes + chars[0] = s_base32Char[b0 & 0x1F]; + chars[1] = s_base32Char[b1 & 0x1F]; + chars[2] = s_base32Char[b2 & 0x1F]; + chars[3] = s_base32Char[b3 & 0x1F]; + chars[4] = s_base32Char[b4 & 0x1F]; + + // Consume 3 MSB of b0, b1, MSB bits 6, 7 of b3, b4 + chars[5] = s_base32Char[( + ((b0 & 0xE0) >> 5) | + ((b3 & 0x60) >> 2))]; + + chars[6] = s_base32Char[( + ((b1 & 0xE0) >> 5) | + ((b4 & 0x60) >> 2))]; + + // Consume 3 MSB bits of b2, 1 MSB bit of b3, b4 + b2 >>= 5; + + Debug.Assert(((b2 & 0xF8) == 0), "Unexpected set bits"); + + if ((b3 & 0x80) != 0) + b2 |= 0x08; + if ((b4 & 0x80) != 0) + b2 |= 0x10; + + chars[7] = s_base32Char[b2]; + + // Set the file extension separator + chars[8] = '.'; + + // Consume the 5 Least significant bits of the remaining 3 bytes + chars[9] = s_base32Char[(bytes[5] & 0x1F)]; + chars[10] = s_base32Char[(bytes[6] & 0x1F)]; + chars[11] = s_base32Char[(bytes[7] & 0x1F)]; + } + + /// <summary> + /// Create a relative path from one path to another. Paths will be resolved before calculating the difference. + /// Default path comparison for the active platform will be used (OrdinalIgnoreCase for Windows or Mac, Ordinal for Unix). + /// </summary> + /// <param name="relativeTo">The source path the output should be relative to. This path is always considered to be a directory.</param> + /// <param name="path">The destination path.</param> + /// <returns>The relative path or <paramref name="path"/> if the paths don't share the same root.</returns> + /// <exception cref="ArgumentNullException">Thrown if <paramref name="relativeTo"/> or <paramref name="path"/> is <c>null</c> or an empty string.</exception> + public static string GetRelativePath(string relativeTo, string path) + { + return GetRelativePath(relativeTo, path, StringComparison); + } + + private static string GetRelativePath(string relativeTo, string path, StringComparison comparisonType) + { + if (string.IsNullOrEmpty(relativeTo)) throw new ArgumentNullException(nameof(relativeTo)); + if (string.IsNullOrWhiteSpace(path)) throw new ArgumentNullException(nameof(path)); + Debug.Assert(comparisonType == StringComparison.Ordinal || comparisonType == StringComparison.OrdinalIgnoreCase); + + relativeTo = GetFullPath(relativeTo); + path = GetFullPath(path); + + // Need to check if the roots are different- if they are we need to return the "to" path. + if (!PathInternal.AreRootsEqual(relativeTo, path, comparisonType)) + return path; + + int commonLength = PathInternal.GetCommonPathLength(relativeTo, path, ignoreCase: comparisonType == StringComparison.OrdinalIgnoreCase); + + // If there is nothing in common they can't share the same root, return the "to" path as is. + if (commonLength == 0) + return path; + + // Trailing separators aren't significant for comparison + int relativeToLength = relativeTo.Length; + if (PathInternal.EndsInDirectorySeparator(relativeTo)) + relativeToLength--; + + bool pathEndsInSeparator = PathInternal.EndsInDirectorySeparator(path); + int pathLength = path.Length; + if (pathEndsInSeparator) + pathLength--; + + // If we have effectively the same path, return "." + if (relativeToLength == pathLength && commonLength >= relativeToLength) return "."; + + // We have the same root, we need to calculate the difference now using the + // common Length and Segment count past the length. + // + // Some examples: + // + // C:\Foo C:\Bar L3, S1 -> ..\Bar + // C:\Foo C:\Foo\Bar L6, S0 -> Bar + // C:\Foo\Bar C:\Bar\Bar L3, S2 -> ..\..\Bar\Bar + // C:\Foo\Foo C:\Foo\Bar L7, S1 -> ..\Bar + + StringBuilder sb = StringBuilderCache.Acquire(Math.Max(relativeTo.Length, path.Length)); + + // Add parent segments for segments past the common on the "from" path + if (commonLength < relativeToLength) + { + sb.Append(PathInternal.ParentDirectoryPrefix); + + for (int i = commonLength; i < relativeToLength; i++) + { + if (PathInternal.IsDirectorySeparator(relativeTo[i])) + { + sb.Append(PathInternal.ParentDirectoryPrefix); + } + } + } + else if (PathInternal.IsDirectorySeparator(path[commonLength])) + { + // No parent segments and we need to eat the initial separator + // (C:\Foo C:\Foo\Bar case) + commonLength++; + } + + // Now add the rest of the "to" path, adding back the trailing separator + int count = pathLength - commonLength; + if (pathEndsInSeparator) + count++; + + sb.Append(path, commonLength, count); + return StringBuilderCache.GetStringAndRelease(sb); + } + + // StringComparison and IsCaseSensitive are also available in PathInternal.CaseSensitivity but we are + // too low in System.Runtime.Extensions to use it (no FileStream, etc.) + + /// <summary>Returns a comparison that can be used to compare file and directory names for equality.</summary> + internal static StringComparison StringComparison + { + get + { + return IsCaseSensitive ? + StringComparison.Ordinal : + StringComparison.OrdinalIgnoreCase; + } + } + } +} diff --git a/src/mscorlib/src/System/IO/LongPathHelper.cs b/src/mscorlib/corefx/System/IO/PathHelper.Windows.cs index 9746fdc0aa..4c2cdff45e 100644 --- a/src/mscorlib/src/System/IO/LongPathHelper.cs +++ b/src/mscorlib/corefx/System/IO/PathHelper.Windows.cs @@ -2,17 +2,16 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System.Diagnostics.Contracts; +using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using Microsoft.Win32; namespace System.IO { /// <summary> /// Wrapper to help with path normalization. /// </summary> - internal class LongPathHelper + unsafe internal class PathHelper { // Can't be over 8.3 and be a short name private const int MaxShortName = 12; @@ -42,8 +41,7 @@ namespace System.IO /// <exception cref="UnauthorizedAccessException">Thrown if Windows returns ERROR_ACCESS_DENIED. (See Win32Marshal.GetExceptionForWin32Error)</exception> /// <exception cref="IOException">Thrown if Windows returns an error that doesn't map to the above. (See Win32Marshal.GetExceptionForWin32Error)</exception> /// <returns>Normalized path</returns> - [System.Security.SecurityCritical] - unsafe internal static string Normalize(string path, uint maxPathLength, bool checkInvalidCharacters, bool expandShortPaths) + internal static string Normalize(string path, bool checkInvalidCharacters, bool expandShortPaths) { // Get the full path StringBuffer fullPath = t_fullPathBuffer ?? (t_fullPathBuffer = new StringBuffer(PathInternal.MaxShortPath)); @@ -52,12 +50,12 @@ namespace System.IO GetFullPathName(path, fullPath); // Trim whitespace off the end of the string. Win32 normalization trims only U+0020. - fullPath.TrimEnd(Path.TrimEndChars); + fullPath.TrimEnd(PathInternal.s_trimEndChars); - if (fullPath.Length >= maxPathLength) + if (fullPath.Length >= PathInternal.MaxLongPath) { // Fullpath is genuinely too long - throw new PathTooLongException(); + throw new PathTooLongException(SR.IO_PathTooLong); } // Checking path validity used to happen before getting the full path name. To avoid additional input allocation @@ -73,7 +71,7 @@ namespace System.IO // - Invalid UNC paths like \\, \\server, \\server\. // - Segments that are too long (over MaxComponentLength) - // As the path could be > 60K, we'll combine the validity scan. None of these checks are performed by the Win32 + // As the path could be > 30K, we'll combine the validity scan. None of these checks are performed by the Win32 // GetFullPathName() API. bool possibleShortPath = false; @@ -105,8 +103,7 @@ namespace System.IO case '>': case '<': case '\"': - if (checkInvalidCharacters) throw new ArgumentException(Environment.GetResourceString("Argument_InvalidPathChars")); - // No point in expanding a bad path + if (checkInvalidCharacters) throw new ArgumentException(SR.Argument_InvalidPathChars); foundTilde = false; break; case '~': @@ -115,7 +112,7 @@ namespace System.IO case '\\': segmentLength = index - lastSeparator - 1; if (segmentLength > (uint)PathInternal.MaxComponentLength) - throw new PathTooLongException(); + throw new PathTooLongException(SR.IO_PathTooLong + fullPath.ToString()); lastSeparator = index; if (foundTilde) @@ -134,7 +131,7 @@ namespace System.IO // If we're at the end of the path and this is the first separator, we're missing the share. // Otherwise we're good, so ignore UNC tracking from here. if (index == fullPath.Length - 1) - throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegalUNC")); + throw new ArgumentException(SR.Arg_PathIllegalUNC); else possibleBadUnc = false; } @@ -142,7 +139,7 @@ namespace System.IO break; default: - if (checkInvalidCharacters && current < ' ') throw new ArgumentException(Environment.GetResourceString("Argument_InvalidPathChars")); + if (checkInvalidCharacters && current < ' ') throw new ArgumentException(SR.Argument_InvalidPathChars, nameof(path)); break; } } @@ -151,11 +148,11 @@ namespace System.IO } if (possibleBadUnc) - throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegalUNC")); + throw new ArgumentException(SR.Arg_PathIllegalUNC); segmentLength = fullPath.Length - lastSeparator - 1; if (segmentLength > (uint)PathInternal.MaxComponentLength) - throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong")); + throw new PathTooLongException(SR.IO_PathTooLong); if (foundTilde && segmentLength <= MaxShortName) possibleShortPath = true; @@ -186,12 +183,17 @@ namespace System.IO } } - [System.Security.SecurityCritical] - unsafe private static void GetFullPathName(string path, StringBuffer fullPath) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsDosUnc(StringBuffer buffer) + { + return !PathInternal.IsDevice(buffer) && buffer.Length > 1 && buffer[0] == '\\' && buffer[1] == '\\'; + } + + private static void GetFullPathName(string path, StringBuffer fullPath) { // If the string starts with an extended prefix we would need to remove it from the path before we call GetFullPathName as // it doesn't root extended paths correctly. We don't currently resolve extended paths, so we'll just assert here. - Contract.Assert(PathInternal.IsPartiallyQualified(path) || !PathInternal.IsExtended(path)); + Debug.Assert(PathInternal.IsPartiallyQualified(path) || !PathInternal.IsExtended(path)); // Historically we would skip leading spaces *only* if the path started with a drive " C:" or a UNC " \\" int startIndex = PathInternal.PathStartSkip(path); @@ -199,7 +201,7 @@ namespace System.IO fixed (char* pathStart = path) { uint result = 0; - while ((result = Win32Native.GetFullPathNameW(pathStart + startIndex, fullPath.CharCapacity, fullPath.GetHandle(), IntPtr.Zero)) > fullPath.CharCapacity) + while ((result = Interop.mincore.GetFullPathNameW(pathStart + startIndex, fullPath.CharCapacity, fullPath.GetHandle(), IntPtr.Zero)) > fullPath.CharCapacity) { // Reported size (which does not include the null) is greater than the buffer size. Increase the capacity. fullPath.EnsureCharCapacity(result); @@ -210,150 +212,14 @@ namespace System.IO // Failure, get the error and throw int errorCode = Marshal.GetLastWin32Error(); if (errorCode == 0) - errorCode = Win32Native.ERROR_BAD_PATHNAME; - __Error.WinIOError(errorCode, path); + errorCode = Interop.mincore.Errors.ERROR_BAD_PATHNAME; + throw Win32Marshal.GetExceptionForWin32Error(errorCode, path); } fullPath.Length = result; } } - [System.Security.SecurityCritical] - unsafe internal static string GetLongPathName(StringBuffer path) - { - using (StringBuffer outputBuffer = new StringBuffer(path.Length)) - { - uint result = 0; - while ((result = Win32Native.GetLongPathNameW(path.GetHandle(), outputBuffer.GetHandle(), outputBuffer.CharCapacity)) > outputBuffer.CharCapacity) - { - // Reported size (which does not include the null) is greater than the buffer size. Increase the capacity. - outputBuffer.EnsureCharCapacity(result); - } - - if (result == 0) - { - // Failure, get the error and throw - GetErrorAndThrow(path.ToString()); - } - - outputBuffer.Length = result; - return outputBuffer.ToString(); - } - } - - [System.Security.SecurityCritical] - unsafe internal static string GetLongPathName(string path) - { - using (StringBuffer outputBuffer = new StringBuffer((uint)path.Length)) - { - uint result = 0; - while ((result = Win32Native.GetLongPathNameW(path, outputBuffer.GetHandle(), outputBuffer.CharCapacity)) > outputBuffer.CharCapacity) - { - // Reported size (which does not include the null) is greater than the buffer size. Increase the capacity. - outputBuffer.EnsureCharCapacity(result); - } - - if (result == 0) - { - // Failure, get the error and throw - GetErrorAndThrow(path); - } - - outputBuffer.Length = result; - return outputBuffer.ToString(); - } - } - - [System.Security.SecurityCritical] - private static void GetErrorAndThrow(string path) - { - int errorCode = Marshal.GetLastWin32Error(); - if (errorCode == 0) - errorCode = Win32Native.ERROR_BAD_PATHNAME; - __Error.WinIOError(errorCode, path); - } - - // It is significantly more complicated to get the long path with minimal allocations if we're injecting the extended dos path prefix. The implicit version - // should match up with what is in CoreFx System.Runtime.Extensions. -#if !FEATURE_IMPLICIT_LONGPATH - [System.Security.SecuritySafeCritical] - private unsafe static string TryExpandShortFileName(StringBuffer outputBuffer, string originalPath) - { - // We guarantee we'll expand short names for paths that only partially exist. As such, we need to find the part of the path that actually does exist. To - // avoid allocating like crazy we'll create only one input array and modify the contents with embedded nulls. - - Contract.Assert(!PathInternal.IsPartiallyQualified(outputBuffer), "should have resolved by now"); - - using (StringBuffer inputBuffer = new StringBuffer(outputBuffer)) - { - bool success = false; - uint lastIndex = outputBuffer.Length - 1; - uint foundIndex = lastIndex; - uint rootLength = PathInternal.GetRootLength(outputBuffer); - - while (!success) - { - uint result = Win32Native.GetLongPathNameW(inputBuffer.GetHandle(), outputBuffer.GetHandle(), outputBuffer.CharCapacity); - - // Replace any temporary null we added - if (inputBuffer[foundIndex] == '\0') inputBuffer[foundIndex] = '\\'; - - if (result == 0) - { - // Look to see if we couldn't find the file - int error = Marshal.GetLastWin32Error(); - if (error != Win32Native.ERROR_FILE_NOT_FOUND && error != Win32Native.ERROR_PATH_NOT_FOUND) - { - // Some other failure, give up - break; - } - - // We couldn't find the path at the given index, start looking further back in the string. - foundIndex--; - - for (; foundIndex > rootLength && inputBuffer[foundIndex] != '\\'; foundIndex--) ; - if (foundIndex == rootLength) - { - // Can't trim the path back any further - break; - } - else - { - // Temporarily set a null in the string to get Windows to look further up the path - inputBuffer[foundIndex] = '\0'; - } - } - else if (result > outputBuffer.CharCapacity) - { - // Not enough space. The result count for this API does not include the null terminator. - outputBuffer.EnsureCharCapacity(result); - } - else - { - // Found the path - success = true; - outputBuffer.Length = result; - if (foundIndex < lastIndex) - { - // It was a partial find, put the non-existant part of the path back - outputBuffer.Append(inputBuffer, foundIndex, inputBuffer.Length - foundIndex); - } - } - } - - StringBuffer bufferToUse = success ? outputBuffer : inputBuffer; - - if (bufferToUse.SubstringEquals(originalPath)) - { - // Use the original path to avoid allocating - return originalPath; - } - - return bufferToUse.ToString(); - } - } -#else // !FEATURE_IMPLICIT_LONGPATH - private static uint GetInputBuffer(StringBuffer content, bool isDosUnc, out StringBuffer buffer) { uint length = content.Length; @@ -388,9 +254,13 @@ namespace System.IO } } - [System.Security.SecuritySafeCritical] private static string TryExpandShortFileName(StringBuffer outputBuffer, string originalPath) { + // We guarantee we'll expand short names for paths that only partially exist. As such, we need to find the part of the path that actually does exist. To + // avoid allocating like crazy we'll create only one input array and modify the contents with embedded nulls. + + Debug.Assert(!PathInternal.IsPartiallyQualified(outputBuffer), "should have resolved by now"); + // We'll have one of a few cases by now (the normalized path will have already: // // 1. Dos path (C:\) @@ -398,6 +268,8 @@ namespace System.IO // 3. Dos device path (\\.\C:\, \\?\C:\) // // We want to put the extended syntax on the front if it doesn't already have it, which may mean switching from \\.\. + // + // Note that we will never get \??\ here as GetFullPathName() does not recognize \??\ and will return it as C:\??\ (or whatever the current drive is). uint rootLength = PathInternal.GetRootLength(outputBuffer); bool isDevice = PathInternal.IsDevice(outputBuffer); @@ -411,7 +283,6 @@ namespace System.IO if (isDevice) { // We have one of the following (\\?\ or \\.\) - // We will never get \??\ here as GetFullPathName() does not recognize \??\ and will return it as C:\??\ (or whatever the current drive is). inputBuffer = new StringBuffer(); inputBuffer.Append(outputBuffer); @@ -423,9 +294,7 @@ namespace System.IO } else { - // \\Server\Share, but not \\.\ or \\?\. - // We need to know this to be able to push \\?\UNC\ on if required - isDosUnc = outputBuffer.Length > 1 && outputBuffer[0] == '\\' && outputBuffer[1] == '\\' && !PathInternal.IsDevice(outputBuffer); + isDosUnc = IsDosUnc(outputBuffer); rootDifference = GetInputBuffer(outputBuffer, isDosUnc, out inputBuffer); } @@ -437,7 +306,7 @@ namespace System.IO while (!success) { - uint result = Win32Native.GetLongPathNameW(inputBuffer.GetHandle(), outputBuffer.GetHandle(), outputBuffer.CharCapacity); + uint result = Interop.mincore.GetLongPathNameW(inputBuffer.GetHandle(), outputBuffer.GetHandle(), outputBuffer.CharCapacity); // Replace any temporary null we added if (inputBuffer[foundIndex] == '\0') inputBuffer[foundIndex] = '\\'; @@ -446,7 +315,7 @@ namespace System.IO { // Look to see if we couldn't find the file int error = Marshal.GetLastWin32Error(); - if (error != Win32Native.ERROR_FILE_NOT_FOUND && error != Win32Native.ERROR_PATH_NOT_FOUND) + if (error != Interop.mincore.Errors.ERROR_FILE_NOT_FOUND && error != Interop.mincore.Errors.ERROR_PATH_NOT_FOUND) { // Some other failure, give up break; @@ -471,7 +340,7 @@ namespace System.IO { // Not enough space. The result count for this API does not include the null terminator. outputBuffer.EnsureCharCapacity(result); - result = Win32Native.GetLongPathNameW(inputBuffer.GetHandle(), outputBuffer.GetHandle(), outputBuffer.CharCapacity); + result = Interop.mincore.GetLongPathNameW(inputBuffer.GetHandle(), outputBuffer.GetHandle(), outputBuffer.CharCapacity); } else { @@ -516,6 +385,5 @@ namespace System.IO inputBuffer.Dispose(); return returnValue; } -#endif // FEATURE_IMPLICIT_LONGPATH } -}
\ No newline at end of file +} diff --git a/src/mscorlib/corefx/System/IO/PathInternal.CaseSensitivity.cs b/src/mscorlib/corefx/System/IO/PathInternal.CaseSensitivity.cs new file mode 100644 index 0000000000..bea2df93b9 --- /dev/null +++ b/src/mscorlib/corefx/System/IO/PathInternal.CaseSensitivity.cs @@ -0,0 +1,75 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; + +namespace System.IO +{ + /// <summary>Contains internal path helpers that are shared between many projects.</summary> + internal static partial class PathInternal + { + private enum Tristate : byte + { + NotInitialized, + True, + False, + } + + private static Tristate s_isCaseSensitive = Tristate.NotInitialized; + + /// <summary>Returns a comparison that can be used to compare file and directory names for equality.</summary> + internal static StringComparison StringComparison + { + get + { + return IsCaseSensitive ? + StringComparison.Ordinal : + StringComparison.OrdinalIgnoreCase; + } + } + + /// <summary>Gets whether the system is case-sensitive.</summary> + internal static bool IsCaseSensitive + { + get + { + // This must be lazily initialized as there are dependencies on PathInternal's static constructor + // being fully initialized. (GetIsCaseSensitive() calls GetFullPath() which needs to use PathInternal) + if (s_isCaseSensitive == Tristate.NotInitialized) + s_isCaseSensitive = GetIsCaseSensitive() ? Tristate.True : Tristate.False; + + return s_isCaseSensitive == Tristate.True; + } + } + + /// <summary> + /// Determines whether the file system is case sensitive. + /// </summary> + /// <remarks> + /// Ideally we'd use something like pathconf with _PC_CASE_SENSITIVE, but that is non-portable, + /// not supported on Windows or Linux, etc. For now, this function creates a tmp file with capital letters + /// and then tests for its existence with lower-case letters. This could return invalid results in corner + /// cases where, for example, different file systems are mounted with differing sensitivities. + /// </remarks> + private static bool GetIsCaseSensitive() + { + try + { + string pathWithUpperCase = Path.Combine(Path.GetTempPath(), "CASESENSITIVETEST" + Guid.NewGuid().ToString("N")); + using (new FileStream(pathWithUpperCase, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.None, 0x1000, FileOptions.DeleteOnClose)) + { + string lowerCased = pathWithUpperCase.ToLowerInvariant(); + return !File.Exists(lowerCased); + } + } + catch (Exception exc) + { + // In case something goes terribly wrong, we don't want to fail just because + // of a casing test, so we assume case-insensitive-but-preserving. + Debug.Fail("Casing test failed: " + exc); + return false; + } + } + } +} diff --git a/src/mscorlib/corefx/System/IO/PathInternal.Unix.cs b/src/mscorlib/corefx/System/IO/PathInternal.Unix.cs new file mode 100644 index 0000000000..6c39f99556 --- /dev/null +++ b/src/mscorlib/corefx/System/IO/PathInternal.Unix.cs @@ -0,0 +1,122 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; +using System.Text; + +namespace System.IO +{ + /// <summary>Contains internal path helpers that are shared between many projects.</summary> + internal static partial class PathInternal + { + // There is only one invalid path character in Unix + private const char InvalidPathChar = '\0'; + internal static char[] GetInvalidPathChars() => new char[] { InvalidPathChar }; + + internal static readonly int MaxComponentLength = Interop.Sys.MaxName; + + internal const string ParentDirectoryPrefix = @"../"; + + /// <summary>Returns a value indicating if the given path contains invalid characters.</summary> + internal static bool HasIllegalCharacters(string path) + { + Debug.Assert(path != null); + return path.IndexOf(InvalidPathChar) >= 0; + } + + internal static int GetRootLength(string path) + { + return path.Length > 0 && IsDirectorySeparator(path[0]) ? 1 : 0; + } + + internal static bool IsDirectorySeparator(char c) + { + // The alternate directory separator char is the same as the directory separator, + // so we only need to check one. + Debug.Assert(Path.DirectorySeparatorChar == Path.AltDirectorySeparatorChar); + return c == Path.DirectorySeparatorChar; + } + + /// <summary> + /// Returns true if the path is too long + /// </summary> + internal static bool IsPathTooLong(string fullPath) + { + return fullPath.Length >= Interop.Sys.MaxPath; + } + + /// <summary> + /// Returns true if the directory is too long + /// </summary> + internal static bool IsDirectoryTooLong(string fullPath) + { + return fullPath.Length >= Interop.Sys.MaxPath; + } + + /// <summary> + /// Normalize separators in the given path. Compresses forward slash runs. + /// </summary> + internal static string NormalizeDirectorySeparators(string path) + { + if (string.IsNullOrEmpty(path)) return path; + + // Make a pass to see if we need to normalize so we can potentially skip allocating + bool normalized = true; + + for (int i = 0; i < path.Length; i++) + { + if (IsDirectorySeparator(path[i]) + && (i + 1 < path.Length && IsDirectorySeparator(path[i + 1]))) + { + normalized = false; + break; + } + } + + if (normalized) return path; + + StringBuilder builder = new StringBuilder(path.Length); + + for (int i = 0; i < path.Length; i++) + { + char current = path[i]; + + // Skip if we have another separator following + if (IsDirectorySeparator(current) + && (i + 1 < path.Length && IsDirectorySeparator(path[i + 1]))) + continue; + + builder.Append(current); + } + + return builder.ToString(); + } + + /// <summary> + /// Returns true if the character is a directory or volume separator. + /// </summary> + /// <param name="ch">The character to test.</param> + internal static bool IsDirectoryOrVolumeSeparator(char ch) + { + // The directory separator, volume separator, and the alternate directory + // separator should be the same on Unix, so we only need to check one. + Debug.Assert(Path.DirectorySeparatorChar == Path.AltDirectorySeparatorChar); + Debug.Assert(Path.DirectorySeparatorChar == Path.VolumeSeparatorChar); + return ch == Path.DirectorySeparatorChar; + } + + internal static bool HasInvalidVolumeSeparator(string path) + { + // This is only ever true for Windows + return false; + } + + internal static bool IsPartiallyQualified(string path) + { + // This is much simpler than Windows where paths can be rooted, but not fully qualified (such as Drive Relative) + // As long as the path is rooted in Unix it doesn't use the current directory and therefore is fully qualified. + return !Path.IsPathRooted(path); + } + } +} diff --git a/src/mscorlib/corefx/System/IO/PathInternal.Windows.StringBuffer.cs b/src/mscorlib/corefx/System/IO/PathInternal.Windows.StringBuffer.cs new file mode 100644 index 0000000000..fec2218844 --- /dev/null +++ b/src/mscorlib/corefx/System/IO/PathInternal.Windows.StringBuffer.cs @@ -0,0 +1,89 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace System.IO +{ + /// <summary>Contains internal path helpers that are shared between many projects.</summary> + internal static partial class PathInternal + { + /// <summary> + /// Returns true if the path uses the extended syntax (\\?\) + /// </summary> + internal static bool IsExtended(StringBuffer path) + { + // While paths like "//?/C:/" will work, they're treated the same as "\\.\" paths. + // Skipping of normalization will *only* occur if back slashes ('\') are used. + return path.Length >= DevicePrefixLength + && path[0] == '\\' + && (path[1] == '\\' || path[1] == '?') + && path[2] == '?' + && path[3] == '\\'; + } + + /// <summary> + /// Gets the length of the root of the path (drive, share, etc.). + /// </summary> + internal unsafe static uint GetRootLength(StringBuffer path) + { + if (path.Length == 0) return 0; + return GetRootLength(path.CharPointer, path.Length); + } + + /// <summary> + /// Returns true if the path uses any of the DOS device path syntaxes. ("\\.\", "\\?\", or "\??\") + /// </summary> + internal static bool IsDevice(StringBuffer path) + { + // If the path begins with any two separators is will be recognized and normalized and prepped with + // "\??\" for internal usage correctly. "\??\" is recognized and handled, "/??/" is not. + return IsExtended(path) + || + ( + path.Length >= DevicePrefixLength + && IsDirectorySeparator(path[0]) + && IsDirectorySeparator(path[1]) + && (path[2] == '.' || path[2] == '?') + && IsDirectorySeparator(path[3]) + ); + } + + /// <summary> + /// Returns true if the path specified is relative to the current drive or working directory. + /// Returns false if the path is fixed to a specific drive or UNC path. This method does no + /// validation of the path (URIs will be returned as relative as a result). + /// </summary> + /// <remarks> + /// Handles paths that use the alternate directory separator. It is a frequent mistake to + /// assume that rooted paths (Path.IsPathRooted) are not relative. This isn't the case. + /// "C:a" is drive relative- meaning that it will be resolved against the current directory + /// for C: (rooted, but relative). "C:\a" is rooted and not relative (the current directory + /// will not be used to modify the path). + /// </remarks> + internal static bool IsPartiallyQualified(StringBuffer path) + { + if (path.Length < 2) + { + // It isn't fixed, it must be relative. There is no way to specify a fixed + // path with one character (or less). + return true; + } + + if (IsDirectorySeparator(path[0])) + { + // There is no valid way to specify a relative path with two initial slashes or + // \? as ? isn't valid for drive relative paths and \??\ is equivalent to \\?\ + return !(path[1] == '?' || IsDirectorySeparator(path[1])); + } + + // 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.VolumeSeparatorChar) + && IsDirectorySeparator(path[2])); + } + } +} diff --git a/src/mscorlib/corefx/System/IO/PathInternal.Windows.cs b/src/mscorlib/corefx/System/IO/PathInternal.Windows.cs new file mode 100644 index 0000000000..bd7f1eae41 --- /dev/null +++ b/src/mscorlib/corefx/System/IO/PathInternal.Windows.cs @@ -0,0 +1,482 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Text; + +namespace System.IO +{ + /// <summary>Contains internal path helpers that are shared between many projects.</summary> + internal static partial class PathInternal + { + // All paths in Win32 ultimately end up becoming a path to a File object in the Windows object manager. Passed in paths get mapped through + // DosDevice symbolic links in the object tree to actual File objects under \Devices. To illustrate, this is what happens with a typical + // path "Foo" passed as a filename to any Win32 API: + // + // 1. "Foo" is recognized as a relative path and is appended to the current directory (say, "C:\" in our example) + // 2. "C:\Foo" is prepended with the DosDevice namespace "\??\" + // 3. CreateFile tries to create an object handle to the requested file "\??\C:\Foo" + // 4. The Object Manager recognizes the DosDevices prefix and looks + // a. First in the current session DosDevices ("\Sessions\1\DosDevices\" for example, mapped network drives go here) + // b. If not found in the session, it looks in the Global DosDevices ("\GLOBAL??\") + // 5. "C:" is found in DosDevices (in our case "\GLOBAL??\C:", which is a symbolic link to "\Device\HarddiskVolume6") + // 6. The full path is now "\Device\HarddiskVolume6\Foo", "\Device\HarddiskVolume6" is a File object and parsing is handed off + // to the registered parsing method for Files + // 7. The registered open method for File objects is invoked to create the file handle which is then returned + // + // There are multiple ways to directly specify a DosDevices path. The final format of "\??\" is one way. It can also be specified + // as "\\.\" (the most commonly documented way) and "\\?\". If the question mark syntax is used the path will skip normalization + // (essentially GetFullPathName()) and path length checks. + + // Windows Kernel-Mode Object Manager + // https://msdn.microsoft.com/en-us/library/windows/hardware/ff565763.aspx + // https://channel9.msdn.com/Shows/Going+Deep/Windows-NT-Object-Manager + // + // Introduction to MS-DOS Device Names + // https://msdn.microsoft.com/en-us/library/windows/hardware/ff548088.aspx + // + // Local and Global MS-DOS Device Names + // https://msdn.microsoft.com/en-us/library/windows/hardware/ff554302.aspx + + internal const string ExtendedPathPrefix = @"\\?\"; + internal const string UncPathPrefix = @"\\"; + internal const string UncExtendedPrefixToInsert = @"?\UNC\"; + internal const string UncExtendedPathPrefix = @"\\?\UNC\"; + internal const string DevicePathPrefix = @"\\.\"; + internal const string ParentDirectoryPrefix = @"..\"; + + internal const int MaxShortPath = 260; + internal const int MaxShortDirectoryPath = 248; + internal const int MaxLongPath = short.MaxValue; + // \\?\, \\.\, \??\ + internal const int DevicePrefixLength = 4; + // \\ + internal const int UncPrefixLength = 2; + // \\?\UNC\, \\.\UNC\ + internal const int UncExtendedPrefixLength = 8; + internal const int MaxComponentLength = 255; + + internal static char[] GetInvalidPathChars() => new char[] + { + '|', '\0', + (char)1, (char)2, (char)3, (char)4, (char)5, (char)6, (char)7, (char)8, (char)9, (char)10, + (char)11, (char)12, (char)13, (char)14, (char)15, (char)16, (char)17, (char)18, (char)19, (char)20, + (char)21, (char)22, (char)23, (char)24, (char)25, (char)26, (char)27, (char)28, (char)29, (char)30, + (char)31 + }; + + // [MS - FSA] 2.1.4.4 Algorithm for Determining if a FileName Is in an Expression + // https://msdn.microsoft.com/en-us/library/ff469270.aspx + private static readonly char[] s_wildcardChars = + { + '\"', '<', '>', '*', '?' + }; + + /// <summary> + /// Returns true if the given character is a valid drive letter + /// </summary> + internal static bool IsValidDriveChar(char value) + { + return ((value >= 'A' && value <= 'Z') || (value >= 'a' && value <= 'z')); + } + + /// <summary> + /// Returns true if the path is too long + /// </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 MaxLongPath. + return fullPath.Length >= MaxLongPath; + } + + /// <summary> + /// Returns true if the directory is too long + /// </summary> + internal static bool IsDirectoryTooLong(string fullPath) + { + return IsPathTooLong(fullPath); + } + + /// <summary> + /// Adds the extended path prefix (\\?\) if not already a device path, IF the path is not relative, + /// AND the path is more than 259 characters. (> MAX_PATH + null) + /// </summary> + internal static string EnsureExtendedPrefixOverMaxPath(string path) + { + if (path != null && path.Length >= MaxShortPath) + { + return EnsureExtendedPrefix(path); + } + else + { + return path; + } + } + + /// <summary> + /// Adds the extended path prefix (\\?\) if not relative or already a device path. + /// </summary> + internal static string EnsureExtendedPrefix(string path) + { + // Putting the extended prefix on the path changes the processing of the path. It won't get normalized, which + // means adding to relative paths will prevent them from getting the appropriate current directory inserted. + + // If it already has some variant of a device path (\??\, \\?\, \\.\, //./, etc.) we don't need to change it + // as it is either correct or we will be changing the behavior. When/if Windows supports long paths implicitly + // in the future we wouldn't want normalization to come back and break existing code. + + // In any case, all internal usages should be hitting normalize path (Path.GetFullPath) before they hit this + // shimming method. (Or making a change that doesn't impact normalization, such as adding a filename to a + // normalized base path.) + if (IsPartiallyQualified(path) || IsDevice(path)) + return path; + + // Given \\server\share in longpath becomes \\?\UNC\server\share + if (path.StartsWith(UncPathPrefix, StringComparison.OrdinalIgnoreCase)) + return path.Insert(2, UncExtendedPrefixToInsert); + + return ExtendedPathPrefix + path; + } + + /// <summary> + /// Returns true if the path uses any of the DOS device path syntaxes. ("\\.\", "\\?\", or "\??\") + /// </summary> + internal static bool IsDevice(string path) + { + // If the path begins with any two separators is will be recognized and normalized and prepped with + // "\??\" for internal usage correctly. "\??\" is recognized and handled, "/??/" is not. + return IsExtended(path) + || + ( + path.Length >= DevicePrefixLength + && IsDirectorySeparator(path[0]) + && IsDirectorySeparator(path[1]) + && (path[2] == '.' || path[2] == '?') + && IsDirectorySeparator(path[3]) + ); + } + + /// <summary> + /// Returns true if the path uses the canonical form of extended syntax ("\\?\" or "\??\"). If the + /// path matches exactly (cannot use alternate directory separators) Windows will skip normalization + /// and path length checks. + /// </summary> + internal static bool IsExtended(string path) + { + // While paths like "//?/C:/" will work, they're treated the same as "\\.\" paths. + // Skipping of normalization will *only* occur if back slashes ('\') are used. + return path.Length >= DevicePrefixLength + && path[0] == '\\' + && (path[1] == '\\' || path[1] == '?') + && path[2] == '?' + && path[3] == '\\'; + } + + /// <summary> + /// Returns a value indicating if the given path contains invalid characters (", <, >, | + /// NUL, or any ASCII char whose integer representation is in the range of 1 through 31). + /// Does not check for wild card characters ? and *. + /// </summary> + internal static bool HasIllegalCharacters(string path) + { + // This is equivalent to IndexOfAny(InvalidPathChars) >= 0, + // except faster since IndexOfAny grows slower as the input + // array grows larger. + // Since we know that some of the characters we're looking + // for are contiguous in the alphabet-- the path cannot contain + // characters 0-31-- we can optimize this for our specific use + // case and use simple comparison operations. + + for (int i = 0; i < path.Length; i++) + { + char c = path[i]; + + if (c <= '\u001f' || c == '|') + { + return true; + } + } + + return false; + } + + /// <summary> + /// Check for known wildcard characters. '*' and '?' are the most common ones. + /// </summary> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal unsafe static bool HasWildCardCharacters(string path) + { + // Question mark is part of dos device syntax so we have to skip if we are + int startIndex = IsDevice(path) ? ExtendedPathPrefix.Length : 0; + + return path.IndexOfAny(s_wildcardChars, startIndex) >= 0; + } + + /// <summary> + /// Gets the length of the root of the path (drive, share, etc.). + /// </summary> + internal unsafe static int GetRootLength(string path) + { + fixed(char* value = path) + { + return (int)GetRootLength(value, (uint)path.Length); + } + } + + private unsafe static uint GetRootLength(char* path, uint pathLength) + { + uint i = 0; + uint volumeSeparatorLength = 2; // Length to the colon "C:" + uint uncRootLength = 2; // Length to the start of the server name "\\" + + bool extendedSyntax = StartsWithOrdinal(path, pathLength, ExtendedPathPrefix); + bool extendedUncSyntax = StartsWithOrdinal(path, pathLength, UncExtendedPathPrefix); + if (extendedSyntax) + { + // Shift the position we look for the root from to account for the extended prefix + if (extendedUncSyntax) + { + // "\\" -> "\\?\UNC\" + uncRootLength = (uint)UncExtendedPathPrefix.Length; + } + else + { + // "C:" -> "\\?\C:" + volumeSeparatorLength += (uint)ExtendedPathPrefix.Length; + } + } + + if ((!extendedSyntax || extendedUncSyntax) && pathLength > 0 && IsDirectorySeparator(path[0])) + { + // UNC or simple rooted path (e.g. "\foo", NOT "\\?\C:\foo") + + i = 1; // Drive rooted (\foo) is one character + if (extendedUncSyntax || (pathLength > 1 && IsDirectorySeparator(path[1]))) + { + // UNC (\\?\UNC\ or \\), scan past the next two directory separators at most + // (e.g. to \\?\UNC\Server\Share or \\Server\Share\) + i = uncRootLength; + int n = 2; // Maximum separators to skip + while (i < pathLength && (!IsDirectorySeparator(path[i]) || --n > 0)) i++; + } + } + else if (pathLength >= volumeSeparatorLength && path[volumeSeparatorLength - 1] == Path.VolumeSeparatorChar) + { + // Path is at least longer than where we expect a colon, and has a colon (\\?\A:, A:) + // If the colon is followed by a directory separator, move past it + i = volumeSeparatorLength; + if (pathLength >= volumeSeparatorLength + 1 && IsDirectorySeparator(path[volumeSeparatorLength])) i++; + } + return i; + } + + private unsafe static bool StartsWithOrdinal(char* source, uint sourceLength, string value) + { + if (sourceLength < (uint)value.Length) return false; + for (int i = 0; i < value.Length; i++) + { + if (value[i] != source[i]) return false; + } + return true; + } + + /// <summary> + /// Returns true if the path specified is relative to the current drive or working directory. + /// Returns false if the path is fixed to a specific drive or UNC path. This method does no + /// validation of the path (URIs will be returned as relative as a result). + /// </summary> + /// <remarks> + /// Handles paths that use the alternate directory separator. It is a frequent mistake to + /// assume that rooted paths (Path.IsPathRooted) are not relative. This isn't the case. + /// "C:a" is drive relative- meaning that it will be resolved against the current directory + /// for C: (rooted, but relative). "C:\a" is rooted and not relative (the current directory + /// will not be used to modify the path). + /// </remarks> + internal static bool IsPartiallyQualified(string path) + { + if (path.Length < 2) + { + // It isn't fixed, it must be relative. There is no way to specify a fixed + // path with one character (or less). + return true; + } + + if (IsDirectorySeparator(path[0])) + { + // There is no valid way to specify a relative path with two initial slashes or + // \? as ? isn't valid for drive relative paths and \??\ is equivalent to \\?\ + return !(path[1] == '?' || IsDirectorySeparator(path[1])); + } + + // 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.VolumeSeparatorChar) + && IsDirectorySeparator(path[2]) + // To match old behavior we'll check the drive character for validity as the path is technically + // not qualified if you don't have a valid drive. "=:\" is the "=" file's default data stream. + && IsValidDriveChar(path[0])); + } + + /// <summary> + /// Returns the characters to skip at the start of the path if it starts with space(s) and a drive or directory separator. + /// (examples are " C:", " \") + /// This is a legacy behavior of Path.GetFullPath(). + /// </summary> + /// <remarks> + /// Note that this conflicts with IsPathRooted() which doesn't (and never did) such a skip. + /// </remarks> + internal static int PathStartSkip(string path) + { + int startIndex = 0; + while (startIndex < path.Length && path[startIndex] == ' ') startIndex++; + + if (startIndex > 0 && (startIndex < path.Length && IsDirectorySeparator(path[startIndex])) + || (startIndex + 1 < path.Length && path[startIndex + 1] == ':' && IsValidDriveChar(path[startIndex]))) + { + // Go ahead and skip spaces as we're either " C:" or " \" + return startIndex; + } + + return 0; + } + + /// <summary> + /// True if the given character is a directory separator. + /// </summary> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool IsDirectorySeparator(char c) + { + return c == Path.DirectorySeparatorChar || c == Path.AltDirectorySeparatorChar; + } + + /// <summary> + /// Normalize separators in the given path. Converts forward slashes into back slashes and compresses slash runs, keeping initial 2 if present. + /// Also trims initial whitespace in front of "rooted" paths (see PathStartSkip). + /// + /// This effectively replicates the behavior of the legacy NormalizePath when it was called with fullCheck=false and expandShortpaths=false. + /// The current NormalizePath gets directory separator normalization from Win32's GetFullPathName(), which will resolve relative paths and as + /// such can't be used here (and is overkill for our uses). + /// + /// Like the current NormalizePath this will not try and analyze periods/spaces within directory segments. + /// </summary> + /// <remarks> + /// The only callers that used to use Path.Normalize(fullCheck=false) were Path.GetDirectoryName() and Path.GetPathRoot(). Both usages do + /// not need trimming of trailing whitespace here. + /// + /// GetPathRoot() could technically skip normalizing separators after the second segment- consider as a future optimization. + /// + /// For legacy desktop behavior with ExpandShortPaths: + /// - It has no impact on GetPathRoot() so doesn't need consideration. + /// - It could impact GetDirectoryName(), but only if the path isn't relative (C:\ or \\Server\Share). + /// + /// In the case of GetDirectoryName() the ExpandShortPaths behavior was undocumented and provided inconsistent results if the path was + /// fixed/relative. For example: "C:\PROGRA~1\A.TXT" would return "C:\Program Files" while ".\PROGRA~1\A.TXT" would return ".\PROGRA~1". If you + /// ultimately call GetFullPath() this doesn't matter, but if you don't or have any intermediate string handling could easily be tripped up by + /// this undocumented behavior. + /// + /// We won't match this old behavior because: + /// + /// 1. It was undocumented + /// 2. It was costly (extremely so if it actually contained '~') + /// 3. Doesn't play nice with string logic + /// 4. Isn't a cross-plat friendly concept/behavior + /// </remarks> + internal static string NormalizeDirectorySeparators(string path) + { + if (string.IsNullOrEmpty(path)) return path; + + char current; + int start = PathStartSkip(path); + + if (start == 0) + { + // Make a pass to see if we need to normalize so we can potentially skip allocating + bool normalized = true; + + for (int i = 0; i < path.Length; i++) + { + current = path[i]; + if (IsDirectorySeparator(current) + && (current != Path.DirectorySeparatorChar + // Check for sequential separators past the first position (we need to keep initial two for UNC/extended) + || (i > 0 && i + 1 < path.Length && IsDirectorySeparator(path[i + 1])))) + { + normalized = false; + break; + } + } + + if (normalized) return path; + } + + StringBuilder builder = new StringBuilder(path.Length); + + if (IsDirectorySeparator(path[start])) + { + start++; + builder.Append(Path.DirectorySeparatorChar); + } + + for (int i = start; i < path.Length; i++) + { + current = path[i]; + + // If we have a separator + if (IsDirectorySeparator(current)) + { + // If the next is a separator, skip adding this + if (i + 1 < path.Length && IsDirectorySeparator(path[i + 1])) + { + continue; + } + + // Ensure it is the primary separator + current = Path.DirectorySeparatorChar; + } + + builder.Append(current); + } + + return builder.ToString(); + } + + /// <summary> + /// Returns true if the character is a directory or volume separator. + /// </summary> + /// <param name="ch">The character to test.</param> + internal static bool IsDirectoryOrVolumeSeparator(char ch) + { + return IsDirectorySeparator(ch) || Path.VolumeSeparatorChar == ch; + } + + /// <summary> + /// Validates volume separator only occurs as C: or \\?\C:. This logic is meant to filter out Alternate Data Streams. + /// </summary> + /// <returns>True if the path has an invalid volume separator.</returns> + internal static bool HasInvalidVolumeSeparator(string path) + { + // Toss out paths with colons that aren't a valid drive specifier. + // Cannot start with a colon and can only be of the form "C:" or "\\?\C:". + // (Note that we used to explicitly check "http:" and "file:"- these are caught by this check now.) + + // We don't care about skipping starting space for extended paths. Assume no knowledge of extended paths if we're forcing old path behavior. + int startIndex = IsExtended(path) ? ExtendedPathPrefix.Length : PathStartSkip(path); + + // If we start with a colon + if ((path.Length > startIndex && path[startIndex] == Path.VolumeSeparatorChar) + // Or have an invalid drive letter and colon + || (path.Length >= startIndex + 2 && path[startIndex + 1] == Path.VolumeSeparatorChar && !IsValidDriveChar(path[startIndex])) + // Or have any colons beyond the drive colon + || (path.Length > startIndex + 2 && path.IndexOf(Path.VolumeSeparatorChar, startIndex + 2) != -1)) + { + return true; + } + + return false; + } + } +} diff --git a/src/mscorlib/corefx/System/IO/PathInternal.cs b/src/mscorlib/corefx/System/IO/PathInternal.cs new file mode 100644 index 0000000000..ee67680df5 --- /dev/null +++ b/src/mscorlib/corefx/System/IO/PathInternal.cs @@ -0,0 +1,230 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; +using System.Text; + +namespace System.IO +{ + /// <summary>Contains internal path helpers that are shared between many projects.</summary> + internal static partial class PathInternal + { + // Trim trailing white spaces, tabs etc but don't be aggressive in removing everything that has UnicodeCategory of trailing space. + // string.WhitespaceChars will trim more aggressively than what the underlying FS does (for ex, NTFS, FAT). + // + // (This is for compatibility with old behavior.) + internal static readonly char[] s_trimEndChars = + { + (char)0x9, // Horizontal tab + (char)0xA, // Line feed + (char)0xB, // Vertical tab + (char)0xC, // Form feed + (char)0xD, // Carriage return + (char)0x20, // Space + (char)0x85, // Next line + (char)0xA0 // Non breaking space + }; + + /// <summary> + /// Checks for invalid path characters in the given path. + /// </summary> + /// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception> + /// <exception cref="System.ArgumentException">Thrown if the path has invalid characters.</exception> + /// <param name="path">The path to check for invalid characters.</param> + internal static void CheckInvalidPathChars(string path) + { + if (path == null) + throw new ArgumentNullException(nameof(path)); + + if (HasIllegalCharacters(path)) + throw new ArgumentException(SR.Argument_InvalidPathChars, nameof(path)); + } + + + /// <summary> + /// Returns true if the given StringBuilder starts with the given value. + /// </summary> + /// <param name="value">The string to compare against the start of the StringBuilder.</param> + internal static bool StartsWithOrdinal(this StringBuilder builder, string value) + { + if (value == null || builder.Length < value.Length) + return false; + + for (int i = 0; i < value.Length; i++) + { + if (builder[i] != value[i]) return false; + } + return true; + } + + /// <summary> + /// Returns true if the given string starts with the given value. + /// </summary> + /// <param name="value">The string to compare against the start of the source string.</param> + internal static bool StartsWithOrdinal(this string source, string value) + { + if (value == null || source.Length < value.Length) + return false; + + return source.StartsWith(value, StringComparison.Ordinal); + } + + /// <summary> + /// Trims the specified characters from the end of the StringBuilder. + /// </summary> + internal static StringBuilder TrimEnd(this StringBuilder builder, params char[] trimChars) + { + if (trimChars == null || trimChars.Length == 0) + return builder; + + int end = builder.Length - 1; + + for (; end >= 0; end--) + { + int i = 0; + char ch = builder[end]; + for (; i < trimChars.Length; i++) + { + if (trimChars[i] == ch) break; + } + if (i == trimChars.Length) + { + // Not a trim char + break; + } + } + + builder.Length = end + 1; + return builder; + } + + /// <summary> + /// Returns the start index of the filename + /// in the given path, or 0 if no directory + /// or volume separator is found. + /// </summary> + /// <param name="path">The path in which to find the index of the filename.</param> + /// <remarks> + /// This method returns path.Length for + /// inputs like "/usr/foo/" on Unix. As such, + /// it is not safe for being used to index + /// the string without additional verification. + /// </remarks> + internal static int FindFileNameIndex(string path) + { + Debug.Assert(path != null); + CheckInvalidPathChars(path); + + for (int i = path.Length - 1; i >= 0; i--) + { + char ch = path[i]; + if (IsDirectoryOrVolumeSeparator(ch)) + return i + 1; + } + + return 0; // the whole path is the filename + } + + /// <summary> + /// Returns true if the path ends in a directory separator. + /// </summary> + internal static bool EndsInDirectorySeparator(string path) => + !string.IsNullOrEmpty(path) && IsDirectorySeparator(path[path.Length - 1]); + + /// <summary> + /// Get the common path length from the start of the string. + /// </summary> + internal static int GetCommonPathLength(string first, string second, bool ignoreCase) + { + int commonChars = EqualStartingCharacterCount(first, second, ignoreCase: ignoreCase); + + // If nothing matches + if (commonChars == 0) + return commonChars; + + // Or we're a full string and equal length or match to a separator + if (commonChars == first.Length + && (commonChars == second.Length || IsDirectorySeparator(second[commonChars]))) + return commonChars; + + if (commonChars == second.Length && IsDirectorySeparator(first[commonChars])) + return commonChars; + + // It's possible we matched somewhere in the middle of a segment e.g. C:\Foodie and C:\Foobar. + while (commonChars > 0 && !IsDirectorySeparator(first[commonChars - 1])) + commonChars--; + + return commonChars; + } + + /// <summary> + /// Gets the count of common characters from the left optionally ignoring case + /// </summary> + unsafe internal static int EqualStartingCharacterCount(string first, string second, bool ignoreCase) + { + if (string.IsNullOrEmpty(first) || string.IsNullOrEmpty(second)) return 0; + + int commonChars = 0; + + fixed (char* f = first) + fixed (char* s = second) + { + char* l = f; + char* r = s; + char* leftEnd = l + first.Length; + char* rightEnd = r + second.Length; + + while (l != leftEnd && r != rightEnd + && (*l == *r || (ignoreCase && char.ToUpperInvariant((*l)) == char.ToUpperInvariant((*r))))) + { + commonChars++; + l++; + r++; + } + } + + return commonChars; + } + + /// <summary> + /// Returns true if the two paths have the same root + /// </summary> + internal static bool AreRootsEqual(string first, string second, StringComparison comparisonType) + { + int firstRootLength = GetRootLength(first); + int secondRootLength = GetRootLength(second); + + return firstRootLength == secondRootLength + && string.Compare( + strA: first, + indexA: 0, + strB: second, + indexB: 0, + length: firstRootLength, + comparisonType: comparisonType) == 0; + } + + /// <summary> + /// Returns false for ".." unless it is specified as a part of a valid File/Directory name. + /// (Used to avoid moving up directories.) + /// + /// Valid: a..b abc..d + /// Invalid: ..ab ab.. .. abc..d\abc.. + /// </summary> + internal static void CheckSearchPattern(string searchPattern) + { + int index; + while ((index = searchPattern.IndexOf("..", StringComparison.Ordinal)) != -1) + { + // Terminal ".." . Files names cannot end in ".." + if (index + 2 == searchPattern.Length + || IsDirectorySeparator(searchPattern[index + 2])) + throw new ArgumentException(SR.Arg_InvalidSearchPattern); + + searchPattern = searchPattern.Substring(index + 2); + } + + } + } +} diff --git a/src/mscorlib/corefx/System/Runtime/InteropServices/NativeBuffer.cs b/src/mscorlib/corefx/System/Runtime/InteropServices/NativeBuffer.cs new file mode 100644 index 0000000000..875009aee2 --- /dev/null +++ b/src/mscorlib/corefx/System/Runtime/InteropServices/NativeBuffer.cs @@ -0,0 +1,157 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Runtime.CompilerServices; + +namespace System.Runtime.InteropServices +{ + /// <summary> + /// Wrapper for access to the native heap. Dispose to free the memory. Try to use with using statements. + /// Does not allocate zero size buffers, and will free the existing native buffer if capacity is dropped to zero. + /// + /// NativeBuffer utilizes a cache of heap buffers. + /// </summary> + /// <remarks> + /// Suggested use through P/Invoke: define DllImport arguments that take a byte buffer as SafeHandle. + /// + /// Using SafeHandle will ensure that the buffer will not get collected during a P/Invoke. + /// (Notably AddRef and ReleaseRef will be called by the interop layer.) + /// + /// This class is not threadsafe, changing the capacity or disposing on multiple threads risks duplicate heap + /// handles or worse. + /// </remarks> + internal class NativeBuffer : IDisposable + { + private readonly static SafeHeapHandleCache s_handleCache = new SafeHeapHandleCache(); + private readonly static SafeHandle s_emptyHandle = new EmptySafeHandle(); + private SafeHeapHandle _handle; + private ulong _capacity; + + /// <summary> + /// Create a buffer with at least the specified initial capacity in bytes. + /// </summary> + public NativeBuffer(ulong initialMinCapacity = 0) + { + EnsureByteCapacity(initialMinCapacity); + } + + protected unsafe void* VoidPointer + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + return _handle == null ? null : _handle.DangerousGetHandle().ToPointer(); + } + } + + protected unsafe byte* BytePointer + { + get + { + return (byte*)VoidPointer; + } + } + + /// <summary> + /// Get the handle for the buffer. + /// </summary> + public SafeHandle GetHandle() + { + // Marshalling code will throw on null for SafeHandle + return _handle ?? s_emptyHandle; + } + + /// <summary> + /// The capacity of the buffer in bytes. + /// </summary> + public ulong ByteCapacity + { + get { return _capacity; } + } + + /// <summary> + /// Ensure capacity in bytes is at least the given minimum. + /// </summary> + /// <exception cref="OutOfMemoryException">Thrown if unable to allocate memory when setting.</exception> + /// <exception cref="ArgumentOutOfRangeException">Thrown if attempting to set <paramref name="nameof(minCapacity)"/> to a value that is larger than the maximum addressable memory.</exception> + public void EnsureByteCapacity(ulong minCapacity) + { + if (_capacity < minCapacity) + { + Resize(minCapacity); + _capacity = minCapacity; + } + } + + public unsafe byte this[ulong index] + { + get + { + if (index >= _capacity) throw new ArgumentOutOfRangeException(); + return BytePointer[index]; + } + set + { + if (index >= _capacity) throw new ArgumentOutOfRangeException(); + BytePointer[index] = value; + } + } + + private unsafe void Resize(ulong byteLength) + { + if (byteLength == 0) + { + ReleaseHandle(); + return; + } + + if (_handle == null) + { + _handle = s_handleCache.Acquire(byteLength); + } + else + { + _handle.Resize(byteLength); + } + } + + private void ReleaseHandle() + { + if (_handle != null) + { + s_handleCache.Release(_handle); + _capacity = 0; + _handle = null; + } + } + + /// <summary> + /// Release the backing buffer + /// </summary> + public virtual void Free() + { + ReleaseHandle(); + } + + public void Dispose() + { + Free(); + } + + private sealed class EmptySafeHandle : SafeHandle + { + public EmptySafeHandle() : base(IntPtr.Zero, true) { } + + public override bool IsInvalid + { + get { return true; } + } + + protected override bool ReleaseHandle() + { + return true; + } + } + } +} diff --git a/src/mscorlib/corefx/System/Runtime/InteropServices/SafeHeapHandle.cs b/src/mscorlib/corefx/System/Runtime/InteropServices/SafeHeapHandle.cs new file mode 100644 index 0000000000..92b3d980db --- /dev/null +++ b/src/mscorlib/corefx/System/Runtime/InteropServices/SafeHeapHandle.cs @@ -0,0 +1,109 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Runtime.InteropServices +{ + /// <summary> + /// Handle for heap memory that allows tracking of capacity and reallocating. + /// </summary> + internal sealed class SafeHeapHandle : SafeBuffer + { + /// <summary> + /// Allocate a buffer of the given size if requested. + /// </summary> + /// <param name="byteLength">Required size in bytes. Must be less than UInt32.MaxValue for 32 bit or UInt64.MaxValue for 64 bit.</param> + /// <exception cref="OutOfMemoryException">Thrown if the requested memory size cannot be allocated.</exception> + /// <exception cref="ArgumentOutOfRangeException">Thrown if size is greater than the maximum memory size.</exception> + public SafeHeapHandle(ulong byteLength) : base(ownsHandle: true) + { + Resize(byteLength); + } + + public override bool IsInvalid + { + get { return handle == IntPtr.Zero; } + } + + /// <summary> + /// Resize the buffer to the given size if requested. + /// </summary> + /// <param name="byteLength">Required size in bytes. Must be less than UInt32.MaxValue for 32 bit or UInt64.MaxValue for 64 bit.</param> + /// <exception cref="OutOfMemoryException">Thrown if the requested memory size cannot be allocated.</exception> + /// <exception cref="ArgumentOutOfRangeException">Thrown if size is greater than the maximum memory size.</exception> + public void Resize(ulong byteLength) + { + if (IsClosed) throw new ObjectDisposedException(nameof(SafeHeapHandle)); + + ulong originalLength = 0; + if (handle == IntPtr.Zero) + { + handle = Marshal.AllocHGlobal((IntPtr)byteLength); + } + else + { + originalLength = ByteLength; + + // This may or may not be the same handle, may realloc in place. If the + // handle changes Windows will deal with the old handle, trying to free it will + // cause an error. + handle = Marshal.ReAllocHGlobal(pv: handle, cb: (IntPtr)byteLength); + } + + if (handle == IntPtr.Zero) + { + // Only real plausible answer + throw new OutOfMemoryException(); + } + + if (byteLength > originalLength) + { + // Add pressure + ulong addedBytes = byteLength - originalLength; + if (addedBytes > long.MaxValue) + { + GC.AddMemoryPressure(long.MaxValue); + GC.AddMemoryPressure((long)(addedBytes - long.MaxValue)); + } + else + { + GC.AddMemoryPressure((long)addedBytes); + } + } + else + { + // Shrank or did nothing, release pressure if needed + RemoveMemoryPressure(originalLength - byteLength); + } + + Initialize(byteLength); + } + + private void RemoveMemoryPressure(ulong removedBytes) + { + if (removedBytes == 0) return; + + if (removedBytes > long.MaxValue) + { + GC.RemoveMemoryPressure(long.MaxValue); + GC.RemoveMemoryPressure((long)(removedBytes - long.MaxValue)); + } + else + { + GC.RemoveMemoryPressure((long)removedBytes); + } + } + + protected override bool ReleaseHandle() + { + if (handle != IntPtr.Zero) + { + RemoveMemoryPressure(ByteLength); + Marshal.FreeHGlobal(handle); + } + + handle = IntPtr.Zero; + return true; + } + } +} diff --git a/src/mscorlib/corefx/System/Runtime/InteropServices/SafeHeapHandleCache.cs b/src/mscorlib/corefx/System/Runtime/InteropServices/SafeHeapHandleCache.cs new file mode 100644 index 0000000000..725076ed66 --- /dev/null +++ b/src/mscorlib/corefx/System/Runtime/InteropServices/SafeHeapHandleCache.cs @@ -0,0 +1,97 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Threading; + +namespace System.Runtime.InteropServices +{ + /// <summary> + /// Allows limited thread safe reuse of heap buffers to limit memory pressure. + /// + /// This cache does not ensure that multiple copies of handles are not released back into the cache. + /// </summary> + internal sealed class SafeHeapHandleCache : IDisposable + { + private readonly ulong _minSize; + private readonly ulong _maxSize; + + // internal for testing + internal readonly SafeHeapHandle[] _handleCache; + + /// <param name="minSize">Smallest buffer size to allocate in bytes.</param> + /// <param name="maxSize">The largest buffer size to cache in bytes.</param> + /// <param name="maxHandles">The maximum number of handles to cache.</param> + public SafeHeapHandleCache(ulong minSize = 64, ulong maxSize = 1024 * 2, int maxHandles = 0) + { + _minSize = minSize; + _maxSize = maxSize; + _handleCache = new SafeHeapHandle[maxHandles > 0 ? maxHandles : Environment.ProcessorCount * 4]; + } + + /// <summary> + /// Get a HeapHandle + /// </summary> + public SafeHeapHandle Acquire(ulong minSize = 0) + { + if (minSize < _minSize) minSize = _minSize; + + SafeHeapHandle handle = null; + + for (int i = 0; i < _handleCache.Length; i++) + { + handle = Interlocked.Exchange(ref _handleCache[i], null); + if (handle != null) break; + } + + if (handle != null) + { + // One possible future consideration is to attempt cycling through to + // find one that might already have sufficient capacity + if (handle.ByteLength < minSize) + handle.Resize(minSize); + } + else + { + handle = new SafeHeapHandle(minSize); + } + + return handle; + } + + /// <summary> + /// Give a HeapHandle back for potential reuse + /// </summary> + public void Release(SafeHeapHandle handle) + { + if (handle.ByteLength <= _maxSize) + { + for (int i = 0; i < _handleCache.Length; i++) + { + // Push the handles down, walking the last one off the end to keep + // the top of the "stack" fresh + handle = Interlocked.Exchange(ref _handleCache[i], handle); + if (handle == null) return; + } + } + + handle.Dispose(); + } + + public void Dispose() + { + Dispose(disposing: true); + } + + private void Dispose(bool disposing) + { + if (disposing && _handleCache != null) + { + foreach (SafeHeapHandle handle in _handleCache) + { + if (handle != null) handle.Dispose(); + } + } + } + } +} diff --git a/src/mscorlib/corefx/System/Runtime/InteropServices/StringBuffer.cs b/src/mscorlib/corefx/System/Runtime/InteropServices/StringBuffer.cs new file mode 100644 index 0000000000..8dce4f0350 --- /dev/null +++ b/src/mscorlib/corefx/System/Runtime/InteropServices/StringBuffer.cs @@ -0,0 +1,355 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Runtime.InteropServices +{ + /// <summary> + /// Native buffer that deals in char size increments. Dispose to free memory. Allows buffers larger + /// than a maximum size string to enable working with very large string arrays. Always makes ordinal + /// comparisons. + /// + /// A more performant replacement for StringBuilder when performing native interop. + /// </summary> + /// <remarks> + /// Suggested use through P/Invoke: define DllImport arguments that take a character buffer as SafeHandle and pass StringBuffer.GetHandle(). + /// </remarks> + internal class StringBuffer : NativeBuffer + { + private uint _length; + + /// <summary> + /// Instantiate the buffer with capacity for at least the specified number of characters. Capacity + /// includes the trailing null character. + /// </summary> + public StringBuffer(uint initialCapacity = 0) + : base(initialCapacity * (ulong)sizeof(char)) + { + } + + /// <summary> + /// Get/set the character at the given index. + /// </summary> + /// <exception cref="ArgumentOutOfRangeException">Thrown if attempting to index outside of the buffer length.</exception> + public unsafe char this[uint index] + { + get + { + if (index >= _length) throw new ArgumentOutOfRangeException(nameof(index)); + return CharPointer[index]; + } + set + { + if (index >= _length) throw new ArgumentOutOfRangeException(nameof(index)); + CharPointer[index] = value; + } + } + + /// <summary> + /// Character capacity of the buffer. Includes the count for the trailing null character. + /// </summary> + public uint CharCapacity + { + get + { + ulong byteCapacity = ByteCapacity; + ulong charCapacity = byteCapacity == 0 ? 0 : byteCapacity / sizeof(char); + return charCapacity > uint.MaxValue ? uint.MaxValue : (uint)charCapacity; + } + } + + /// <summary> + /// Ensure capacity in characters is at least the given minimum. + /// </summary> + /// <exception cref="OutOfMemoryException">Thrown if unable to allocate memory when setting.</exception> + public void EnsureCharCapacity(uint minCapacity) + { + EnsureByteCapacity(minCapacity * (ulong)sizeof(char)); + } + + /// <summary> + /// The logical length of the buffer in characters. (Does not include the final null.) Will automatically attempt to increase capacity. + /// This is where the usable data ends. + /// </summary> + /// <exception cref="OutOfMemoryException">Thrown if unable to allocate memory when setting.</exception> + /// <exception cref="ArgumentOutOfRangeException">Thrown if the set size in bytes is uint.MaxValue (as space is implicitly reserved for the trailing null).</exception> + public unsafe uint Length + { + get { return _length; } + set + { + if (value == uint.MaxValue) throw new ArgumentOutOfRangeException(nameof(Length)); + + // Null terminate + EnsureCharCapacity(value + 1); + CharPointer[value] = '\0'; + + _length = value; + } + } + + /// <summary> + /// For use when the native api null terminates but doesn't return a length. + /// If no null is found, the length will not be changed. + /// </summary> + public unsafe void SetLengthToFirstNull() + { + char* buffer = CharPointer; + uint capacity = CharCapacity; + for (uint i = 0; i < capacity; i++) + { + if (buffer[i] == '\0') + { + _length = i; + break; + } + } + } + + internal unsafe char* CharPointer + { + get + { + return (char*)VoidPointer; + } + } + + /// <summary> + /// True if the buffer contains the given character. + /// </summary> + [System.Security.SecurityCritical] + public unsafe bool Contains(char value) + { + char* start = CharPointer; + uint length = _length; + + for (uint i = 0; i < length; i++) + { + if (*start++ == value) return true; + } + + return false; + } + + /// <summary> + /// Returns true if the buffer starts with the given string. + /// </summary> + public bool StartsWith(string value) + { + if (value == null) throw new ArgumentNullException(nameof(value)); + if (_length < (uint)value.Length) return false; + return SubstringEquals(value, startIndex: 0, count: value.Length); + } + + /// <summary> + /// Returns true if the specified StringBuffer substring equals the given value. + /// </summary> + /// <param name="value">The value to compare against the specified substring.</param> + /// <param name="startIndex">Start index of the sub string.</param> + /// <param name="count">Length of the substring, or -1 to check all remaining.</param> + /// <exception cref="ArgumentOutOfRangeException"> + /// Thrown if <paramref name="startIndex"/> or <paramref name="count"/> are outside the range + /// of the buffer's length. + /// </exception> + public unsafe bool SubstringEquals(string value, uint startIndex = 0, int count = -1) + { + if (value == null) return false; + if (count < -1) throw new ArgumentOutOfRangeException(nameof(count)); + if (startIndex > _length) throw new ArgumentOutOfRangeException(nameof(startIndex)); + + uint realCount = count == -1 ? _length - startIndex : (uint)count; + if (checked(startIndex + realCount) > _length) throw new ArgumentOutOfRangeException(nameof(count)); + + int length = value.Length; + + // Check the substring length against the input length + if (realCount != (uint)length) return false; + + fixed (char* valueStart = value) + { + char* bufferStart = CharPointer + startIndex; + for (int i = 0; i < length; i++) + { + // Note that indexing in this case generates faster code than trying to copy the pointer and increment it + if (*bufferStart++ != valueStart[i]) return false; + } + } + + return true; + } + + /// <summary> + /// Append the given string. + /// </summary> + /// <param name="value">The string to append.</param> + /// <param name="startIndex">The index in the input string to start appending from.</param> + /// <param name="count">The count of characters to copy from the input string, or -1 for all remaining.</param> + /// <exception cref="ArgumentNullException">Thrown if <paramref name="value"/> is null.</exception> + /// <exception cref="ArgumentOutOfRangeException"> + /// Thrown if <paramref name="startIndex"/> or <paramref name="count"/> are outside the range + /// of <paramref name="value"/> characters. + /// </exception> + public void Append(string value, int startIndex = 0, int count = -1) + { + CopyFrom( + bufferIndex: _length, + source: value, + sourceIndex: startIndex, + count: count); + } + + /// <summary> + /// Append the given buffer. + /// </summary> + /// <param name="value">The buffer to append.</param> + /// <param name="startIndex">The index in the input buffer to start appending from.</param> + /// <param name="count">The count of characters to copy from the buffer string.</param> + /// <exception cref="ArgumentNullException">Thrown if <paramref name="value"/> is null.</exception> + /// <exception cref="ArgumentOutOfRangeException"> + /// Thrown if <paramref name="startIndex"/> or <paramref name="count"/> are outside the range + /// of <paramref name="value"/> characters. + /// </exception> + public void Append(StringBuffer value, uint startIndex = 0) + { + if (value == null) throw new ArgumentNullException(nameof(value)); + if (value.Length == 0) return; + + value.CopyTo( + bufferIndex: startIndex, + destination: this, + destinationIndex: _length, + count: value.Length); + } + + /// <summary> + /// Append the given buffer. + /// </summary> + /// <param name="value">The buffer to append.</param> + /// <param name="startIndex">The index in the input buffer to start appending from.</param> + /// <param name="count">The count of characters to copy from the buffer string.</param> + /// <exception cref="ArgumentNullException">Thrown if <paramref name="value"/> is null.</exception> + /// <exception cref="ArgumentOutOfRangeException"> + /// Thrown if <paramref name="startIndex"/> or <paramref name="count"/> are outside the range + /// of <paramref name="value"/> characters. + /// </exception> + public void Append(StringBuffer value, uint startIndex, uint count) + { + if (value == null) throw new ArgumentNullException(nameof(value)); + if (count == 0) return; + + value.CopyTo( + bufferIndex: startIndex, + destination: this, + destinationIndex: _length, + count: count); + } + + /// <summary> + /// Copy contents to the specified buffer. Destination index must be within current destination length. + /// Will grow the destination buffer if needed. + /// </summary> + /// <exception cref="ArgumentOutOfRangeException"> + /// Thrown if <paramref name="bufferIndex"/> or <paramref name="destinationIndex"/> or <paramref name="count"/> are outside the range + /// of <paramref name="value"/> characters. + /// </exception> + /// <exception cref="ArgumentNullException">Thrown if <paramref name="destination"/> is null.</exception> + public unsafe void CopyTo(uint bufferIndex, StringBuffer destination, uint destinationIndex, uint count) + { + if (destination == null) throw new ArgumentNullException(nameof(destination)); + if (destinationIndex > destination._length) throw new ArgumentOutOfRangeException(nameof(destinationIndex)); + if (bufferIndex >= _length) throw new ArgumentOutOfRangeException(nameof(bufferIndex)); + if (_length < checked(bufferIndex + count)) throw new ArgumentOutOfRangeException(nameof(count)); + + if (count == 0) return; + uint lastIndex = checked(destinationIndex + count); + if (destination._length < lastIndex) destination.Length = lastIndex; + + Buffer.MemoryCopy( + source: CharPointer + bufferIndex, + destination: destination.CharPointer + destinationIndex, + destinationSizeInBytes: checked((long)(destination.ByteCapacity - (destinationIndex * sizeof(char)))), + sourceBytesToCopy: checked((long)count * sizeof(char))); + } + + /// <summary> + /// Copy contents from the specified string into the buffer at the given index. Start index must be within the current length of + /// the buffer, will grow as necessary. + /// </summary> + public unsafe void CopyFrom(uint bufferIndex, string source, int sourceIndex = 0, int count = -1) + { + if (source == null) throw new ArgumentNullException(nameof(source)); + if (bufferIndex > _length) throw new ArgumentOutOfRangeException(nameof(bufferIndex)); + if (sourceIndex < 0 || sourceIndex > source.Length) throw new ArgumentOutOfRangeException(nameof(sourceIndex)); + if (count == -1) count = source.Length - sourceIndex; + if (count < 0 || source.Length - count < sourceIndex) throw new ArgumentOutOfRangeException(nameof(count)); + + if (count == 0) return; + uint lastIndex = bufferIndex + (uint)count; + if (_length < lastIndex) Length = lastIndex; + + fixed (char* content = source) + { + Buffer.MemoryCopy( + source: content + sourceIndex, + destination: CharPointer + bufferIndex, + destinationSizeInBytes: checked((long)(ByteCapacity - (bufferIndex * sizeof(char)))), + sourceBytesToCopy: (long)count * sizeof(char)); + } + } + + /// <summary> + /// Trim the specified values from the end of the buffer. If nothing is specified, nothing is trimmed. + /// </summary> + public unsafe void TrimEnd(char[] values) + { + if (values == null || values.Length == 0 || _length == 0) return; + + char* end = CharPointer + _length - 1; + + while (_length > 0 && Array.IndexOf(values, *end) >= 0) + { + Length = _length - 1; + end--; + } + } + + /// <summary> + /// String representation of the entire buffer. If the buffer is larger than the maximum size string (int.MaxValue) this will throw. + /// </summary> + /// <exception cref="InvalidOperationException">Thrown if the buffer is too big to fit into a string.</exception> + public unsafe override string ToString() + { + if (_length == 0) return string.Empty; + if (_length > int.MaxValue) throw new InvalidOperationException(); + return new string(CharPointer, startIndex: 0, length: (int)_length); + } + + /// <summary> + /// Get the given substring in the buffer. + /// </summary> + /// <param name="count">Count of characters to take, or remaining characters from <paramref name="startIndex"/> if -1.</param> + /// <exception cref="ArgumentOutOfRangeException"> + /// Thrown if <paramref name="startIndex"/> or <paramref name="count"/> are outside the range of the buffer's length + /// or count is greater than the maximum string size (int.MaxValue). + /// </exception> + public unsafe string Substring(uint startIndex, int count = -1) + { + if (startIndex > (_length == 0 ? 0 : _length - 1)) throw new ArgumentOutOfRangeException(nameof(startIndex)); + if (count < -1) throw new ArgumentOutOfRangeException(nameof(count)); + + uint realCount = count == -1 ? _length - startIndex : (uint)count; + if (realCount > int.MaxValue || checked(startIndex + realCount) > _length) throw new ArgumentOutOfRangeException(nameof(count)); + if (realCount == 0) return string.Empty; + + // The buffer could be bigger than will fit into a string, but the substring might fit. As the starting + // index might be bigger than int we need to index ourselves. + return new string(value: CharPointer + startIndex, startIndex: 0, length: (int)realCount); + } + + public override void Free() + { + base.Free(); + _length = 0; + } + } +} diff --git a/src/mscorlib/mscorlib.shared.sources.props b/src/mscorlib/mscorlib.shared.sources.props index 82539946f4..2fdfa01cb5 100644 --- a/src/mscorlib/mscorlib.shared.sources.props +++ b/src/mscorlib/mscorlib.shared.sources.props @@ -106,7 +106,6 @@ <InteropSources Include="$(BclSourcesRoot)\System\Runtime\InteropServices\SEHException.cs" /> <InteropSources Include="$(BclSourcesRoot)\System\Runtime\InteropServices\SafeBuffer.cs" /> <InteropSources Include="$(BclSourcesRoot)\System\Runtime\InteropServices\SafeHandle.cs" /> - <InteropSources Include="$(BclSourcesRoot)\System\Runtime\InteropServices\SafeHeapHandle.cs" /> <InteropSources Include="$(BclSourcesRoot)\System\Runtime\InteropServices\BStrWrapper.cs" /> <InteropSources Include="$(BclSourcesRoot)\System\Runtime\InteropServices\CurrencyWrapper.cs" /> <InteropSources Include="$(BclSourcesRoot)\System\Runtime\InteropServices\ErrorWrapper.cs" /> @@ -118,8 +117,6 @@ <InteropSources Include="$(BclSourcesRoot)\System\Runtime\InteropServices\InvalidComObjectException.cs" /> <InteropSources Include="$(BclSourcesRoot)\System\Runtime\InteropServices\SafeArrayRankMismatchException.cs" /> <InteropSources Include="$(BclSourcesRoot)\System\Runtime\InteropServices\SafeArrayTypeMismatchException.cs" /> - <InteropSources Include="$(BclSourcesRoot)\System\Runtime\InteropServices\NativeBuffer.cs" /> - <InteropSources Include="$(BclSourcesRoot)\System\Runtime\InteropServices\StringBuffer.cs" /> <InteropSources Condition="'$(FeatureCoreClr)'=='true'" Include="$(BclSourcesRoot)\System\Runtime\InteropServices\NativeCallableAttribute.cs" /> <InteropSources Condition="'$(FeatureCominterop)' != 'true'" Include="$(BclSourcesRoot)\System\Runtime\InteropServices\NonPortable.cs" /> <InteropSources Condition="'$(FeatureCominterop)' == 'true'" Include="$(BclSourcesRoot)\System\Runtime\InteropServices\DispatchWrapper.cs" /> @@ -127,6 +124,17 @@ <InteropSources Include="$(BclSourcesRoot)\System\Runtime\InteropServices\ICustomFactory.cs" /> <InteropSources Condition="'$(FeatureCominterop)' == 'true'" Include="$(BclSourcesRoot)\System\Runtime\InteropServices\ObjectCreationDelegate.cs" /> </ItemGroup> + <ItemGroup Condition="'$(FeatureCoreFxInteropServices)' != 'true'"> + <InteropSources Include="$(BclSourcesRoot)\System\Runtime\InteropServices\NativeBuffer.cs" /> + <InteropSources Include="$(BclSourcesRoot)\System\Runtime\InteropServices\StringBuffer.cs" /> + <InteropSources Include="$(BclSourcesRoot)\System\Runtime\InteropServices\SafeHeapHandle.cs" /> + </ItemGroup> + <ItemGroup Condition="'$(FeatureCoreFxInteropServices)' == 'true'"> + <InteropSources Include="$(CoreFxSourcesRoot)\System\Runtime\InteropServices\NativeBuffer.cs" /> + <InteropSources Include="$(CoreFxSourcesRoot)\System\Runtime\InteropServices\StringBuffer.cs" /> + <InteropSources Include="$(CoreFxSourcesRoot)\System\Runtime\InteropServices\SafeHeapHandle.cs" /> + <InteropSources Include="$(CoreFxSourcesRoot)\System\Runtime\InteropServices\SafeHeapHandleCache.cs" /> + </ItemGroup> <ItemGroup Condition="'$(FeatureClassicCominterop)' == 'true'"> <InteropSources Include="$(BclSourcesRoot)\System\Runtime\InteropServices\IRegistrationServices.cs" /> <InteropSources Include="$(BclSourcesRoot)\System\Runtime\InteropServices\ITypeLibConverter.cs" /> @@ -760,10 +768,6 @@ <ThreadingSources Include="$(BclSourcesRoot)\System\Threading\Tasks\TaskToApm.cs" /> <ThreadingSources Condition="'$(FeatureCominterop)' == 'true'" Include="$(BclSourcesRoot)\System\Threading\Tasks\IAsyncCausalityTracerStatics.cs" /> </ItemGroup> - <ItemGroup Condition="'$(FeatureCoreFxFileStream)' != 'true'"> - <FileStreamSources Include="$(BclSourcesRoot)\System\IO\FileStream.cs" /> - <SafehandleSources Include="$(BclSourcesRoot)\Microsoft\Win32\SafeHandles\SafeFileHandle.cs" /> - </ItemGroup> <ItemGroup Condition="'$(FeatureCoreFxFileStream)' == 'true'"> <FileStreamSources Include="$(CoreFxSourcesRoot)\System\IO\FileStream.cs" /> <FileStreamSources Include="$(CoreFxSourcesRoot)\System\IO\FileStream.NetStandard17.cs" /> @@ -779,6 +783,22 @@ <FileStreamSources Include="$(CoreFxSourcesRoot)\System\IO\FileStreamCompletionSource.Win32.cs" /> <FileStreamSources Include="$(CoreFxSourcesRoot)\System\IO\Win32Marshal.cs" /> </ItemGroup> + <ItemGroup Condition="'$(FeatureCoreFxPath)' == 'true'"> + <IoSources Include="$(CoreFxSourcesRoot)\System\IO\Path.cs" /> + <IoSources Include="$(CoreFxSourcesRoot)\System\IO\PathInternal.cs" /> + <IoSources Include="$(CoreFxSourcesRoot)\System\IO\PathInternal.CaseSensitivity.cs" /> + </ItemGroup> + <ItemGroup Condition="'$(FeatureCoreFxPath)' == 'true' and '$(TargetsUnix)' == 'true'"> + <IoSources Include="$(CoreFxSourcesRoot)\System\IO\Path.Unix.cs" /> + <IoSources Include="$(CoreFxSourcesRoot)\System\IO\PathInternal.Unix.cs" /> + </ItemGroup> + <ItemGroup Condition="'$(FeatureCoreFxPath)' == 'true' and '$(TargetsUnix)' != 'true'"> + <IoSources Include="$(CoreFxSourcesRoot)\System\IO\Path.Win32.cs" /> + <IoSources Include="$(CoreFxSourcesRoot)\System\IO\Path.Windows.cs" /> + <IoSources Include="$(CoreFxSourcesRoot)\System\IO\PathHelper.Windows.cs" /> + <IoSources Include="$(CoreFxSourcesRoot)\System\IO\PathInternal.Windows.cs" /> + <IoSources Include="$(CoreFxSourcesRoot)\System\IO\PathInternal.Windows.StringBuffer.cs" /> + </ItemGroup> <ItemGroup Condition="'$(FeatureCoreFxOverlapped)' == 'true'" > <ThreadingSources Include="$(CoreFxSourcesRoot)\System\Threading\DeferredDisposableLifetime.cs" /> <ThreadingSources Include="$(CoreFxSourcesRoot)\System\Threading\ClrThreadPoolBoundHandle.cs" /> @@ -811,10 +831,6 @@ <IoSources Include="$(BclSourcesRoot)\System\IO\FileAttributes.cs" /> <IoSources Include="$(BclSourcesRoot)\System\IO\IOException.cs" /> <IoSources Include="$(BclSourcesRoot)\System\IO\MemoryStream.cs" /> - <IoSources Include="$(BclSourcesRoot)\System\IO\Path.cs" /> - <IoSources Include="$(BclSourcesRoot)\System\IO\PathHelper.cs" /> - <IoSources Condition="'$(TargetsUnix)' != 'true'" Include="$(BclSourcesRoot)\System\IO\LongPathHelper.cs" /> - <IoSources Include="$(BclSourcesRoot)\System\IO\PathInternal.cs" /> <IoSources Include="$(BclSourcesRoot)\System\IO\PathTooLongException.cs" /> <IoSources Include="$(BclSourcesRoot)\System\IO\PinnedBufferMemoryStream.cs" /> <IoSources Include="$(BclSourcesRoot)\System\IO\ReadLinesIterator.cs" /> @@ -1176,6 +1192,8 @@ <WindowsInteropSources Include="$(CoreFxSourcesRoot)\System\HResults.cs" /> <WindowsInteropSources Include="$(CoreFxSourcesRoot)\Interop\Windows\Interop.BOOL.cs" /> <WindowsInteropSources Include="$(CoreFxSourcesRoot)\Interop\Windows\Interop.Libraries.cs" /> + <WindowsInteropSources Include="$(CoreFxSourcesRoot)\Interop\Windows\BCrypt\Interop.BCryptGenRandom.cs" /> + <WindowsInteropSources Include="$(CoreFxSourcesRoot)\Interop\Windows\BCrypt\Interop.NTSTATUS.cs" /> <WindowsInteropSources Include="$(CoreFxSourcesRoot)\Interop\Windows\mincore\Interop.CancelIoEx.cs" /> <WindowsInteropSources Include="$(CoreFxSourcesRoot)\Interop\Windows\mincore\Interop.CloseHandle.cs" /> <WindowsInteropSources Include="$(CoreFxSourcesRoot)\Interop\Windows\mincore\Interop.CreateFile.cs" /> @@ -1186,6 +1204,10 @@ <WindowsInteropSources Include="$(CoreFxSourcesRoot)\Interop\Windows\mincore\Interop.FormatMessage.cs" /> <WindowsInteropSources Include="$(CoreFxSourcesRoot)\Interop\Windows\mincore\Interop.GetFileInformationByHandleEx.cs" /> <WindowsInteropSources Include="$(CoreFxSourcesRoot)\Interop\Windows\mincore\Interop.GetFileType_SafeHandle.cs" /> + <WindowsInteropSources Include="$(CoreFxSourcesRoot)\Interop\Windows\mincore\Interop.GetFullPathNameW.cs" /> + <WindowsInteropSources Include="$(CoreFxSourcesRoot)\Interop\Windows\mincore\Interop.GetLongPathNameW.cs" /> + <WindowsInteropSources Include="$(CoreFxSourcesRoot)\Interop\Windows\mincore\Interop.GetTempFileNameW.cs" /> + <WindowsInteropSources Include="$(CoreFxSourcesRoot)\Interop\Windows\mincore\Interop.GetTempPathW.cs" /> <WindowsInteropSources Include="$(CoreFxSourcesRoot)\Interop\Windows\mincore\Interop.LockFile.cs" /> <WindowsInteropSources Include="$(CoreFxSourcesRoot)\Interop\Windows\mincore\Interop.ReadFile_SafeHandle_IntPtr.cs" /> <WindowsInteropSources Include="$(CoreFxSourcesRoot)\Interop\Windows\mincore\Interop.ReadFile_SafeHandle_NativeOverlapped.cs" /> @@ -1226,12 +1248,16 @@ <UnixInteropSources Include="$(CoreFxSourcesRoot)\Interop\Unix\Interop.IOErrors.cs" /> <UnixInteropSources Include="$(CoreFxSourcesRoot)\Interop\Unix\Interop.Libraries.cs" /> <UnixInteropSources Include="$(CoreFxSourcesRoot)\Interop\Unix\System.Native\Interop.Close.cs" /> + <UnixInteropSources Include="$(CoreFxSourcesRoot)\Interop\Unix\System.Native\Interop.GetCwd.cs" /> + <UnixInteropSources Include="$(CoreFxSourcesRoot)\Interop\Unix\System.Native\Interop.GetUnixName.cs" /> <UnixInteropSources Include="$(CoreFxSourcesRoot)\Interop\Unix\System.Native\Interop.FLock.cs" /> <UnixInteropSources Include="$(CoreFxSourcesRoot)\Interop\Unix\System.Native\Interop.FSync.cs" /> <UnixInteropSources Include="$(CoreFxSourcesRoot)\Interop\Unix\System.Native\Interop.FTruncate.cs" /> <UnixInteropSources Include="$(CoreFxSourcesRoot)\Interop\Unix\System.Native\Interop.LSeek.cs" /> + <UnixInteropSources Include="$(CoreFxSourcesRoot)\Interop\Unix\System.Native\Interop.MksTemps.cs" /> <UnixInteropSources Include="$(CoreFxSourcesRoot)\Interop\Unix\System.Native\Interop.Open.cs" /> <UnixInteropSources Include="$(CoreFxSourcesRoot)\Interop\Unix\System.Native\Interop.OpenFlags.cs" /> + <UnixInteropSources Include="$(CoreFxSourcesRoot)\Interop\Unix\System.Native\Interop.PathConf.cs" /> <UnixInteropSources Include="$(CoreFxSourcesRoot)\Interop\Unix\System.Native\Interop.Permissions.cs" /> <UnixInteropSources Include="$(CoreFxSourcesRoot)\Interop\Unix\System.Native\Interop.PosixFAdvise.cs" /> <UnixInteropSources Include="$(CoreFxSourcesRoot)\Interop\Unix\System.Native\Interop.Read.cs" /> diff --git a/src/mscorlib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.cs b/src/mscorlib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.cs deleted file mode 100644 index ab06347ee4..0000000000 --- a/src/mscorlib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.cs +++ /dev/null @@ -1,43 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -/*============================================================ -** -** -** -** A wrapper for file handles -** -** -===========================================================*/ - -using System; -using System.Security; -using System.Security.Permissions; -using System.Runtime.InteropServices; -using System.Runtime.CompilerServices; -using System.Runtime.ConstrainedExecution; -using System.Runtime.Versioning; -using Microsoft.Win32; - -namespace Microsoft.Win32.SafeHandles { - - [System.Security.SecurityCritical] // auto-generated_required - public sealed class SafeFileHandle: SafeHandleZeroOrMinusOneIsInvalid { - - private SafeFileHandle() : base(true) - { - } - - public SafeFileHandle(IntPtr preexistingHandle, bool ownsHandle) : base(ownsHandle) { - SetHandle(preexistingHandle); - } - - [System.Security.SecurityCritical] - override protected bool ReleaseHandle() - { - return Win32Native.CloseHandle(handle); - } - } -} - diff --git a/src/mscorlib/src/System/AppDomain.cs b/src/mscorlib/src/System/AppDomain.cs index 475fdd76f4..03c061f038 100644 --- a/src/mscorlib/src/System/AppDomain.cs +++ b/src/mscorlib/src/System/AppDomain.cs @@ -3597,7 +3597,7 @@ namespace System { AppDomainInitializerInfo initializerInfo = (AppDomainInitializerInfo)args[5]; string sandboxName = (string)args[6]; string[] propertyNames = (string[])args[7]; // can contain null elements - string[] propertyValues = (string[])args[8]; // can contain null elements + string[] propertyValues = (string[])args[8]; // can contain null elements // extract evidence Evidence providedSecurityInfo = null; Evidence creatorsSecurityInfo = null; @@ -3617,8 +3617,8 @@ namespace System { { if(propertyValues[i]==null) throw new ArgumentNullException("APPBASE"); - - if (Path.IsRelative(propertyValues[i])) + + if (PathInternal.IsPartiallyQualified(propertyValues[i])) throw new ArgumentException( Environment.GetResourceString( "Argument_AbsolutePathRequired" ) ); newSetup.ApplicationBase = NormalizePath(propertyValues[i], fullCheck: true); @@ -3700,7 +3700,7 @@ namespace System { if( path.Length==0 ) // skip empty dirs continue; - if (Path.IsRelative(path)) + if (PathInternal.IsPartiallyQualified(path)) throw new ArgumentException( Environment.GetResourceString( "Argument_AbsolutePathRequired" ) ); string appPath = NormalizePath(path, fullCheck: true); @@ -3817,10 +3817,7 @@ namespace System { maxPathLength: PathInternal.MaxShortPath, expandShortPaths: true); #else - return Path.NormalizePath( - path: path, - fullCheck: fullCheck, - expandShortPaths: true); + return Path.GetFullPath(path); #endif } diff --git a/src/mscorlib/src/System/AppDomainSetup.cs b/src/mscorlib/src/System/AppDomainSetup.cs index 43c85838a9..710df60d2b 100644 --- a/src/mscorlib/src/System/AppDomainSetup.cs +++ b/src/mscorlib/src/System/AppDomainSetup.cs @@ -896,7 +896,7 @@ namespace System { else fDelimiter = true; - newPath.Append(Path.GetFullPathInternal(directories[i])); + newPath.Append(Path.GetFullPath(directories[i])); } } diff --git a/src/mscorlib/src/System/CfgParser.cs b/src/mscorlib/src/System/CfgParser.cs index 1996f8d444..aba416c820 100644 --- a/src/mscorlib/src/System/CfgParser.cs +++ b/src/mscorlib/src/System/CfgParser.cs @@ -264,7 +264,7 @@ namespace System } if (!skipSecurityStuff) { - (new FileIOPermission( FileIOPermissionAccess.Read, System.IO.Path.GetFullPathInternal( fileName ) )).Demand(); + (new FileIOPermission(FileIOPermissionAccess.Read, Path.GetFullPath(fileName))).Demand(); } #pragma warning disable 618 (new SecurityPermission(SecurityPermissionFlag.UnmanagedCode)).Assert(); diff --git a/src/mscorlib/src/System/IO/Directory.cs b/src/mscorlib/src/System/IO/Directory.cs index 03f9a1e6e5..b13226268d 100644 --- a/src/mscorlib/src/System/IO/Directory.cs +++ b/src/mscorlib/src/System/IO/Directory.cs @@ -45,9 +45,9 @@ namespace System.IO { throw new ArgumentException(Environment.GetResourceString("Argument_PathEmpty"), nameof(path)); Contract.EndContractBlock(); - String fullPath = Path.GetFullPathInternal(path); - - String s = Path.GetDirectoryName(fullPath); + string fullPath = Path.GetFullPath(path); + + string s = Path.GetDirectoryName(fullPath); if (s==null) return null; return new DirectoryInfo(s); @@ -82,7 +82,7 @@ namespace System.IO { Contract.Requires(path != null); Contract.Requires(path.Length != 0); - String fullPath = Path.GetFullPathInternal(path); + String fullPath = Path.GetFullPath(path); // You need read access to the directory to be returned back and write access to all the directories // that you need to create. If we fail any security checks we will not create any directories at all. @@ -142,12 +142,12 @@ namespace System.IO { || fullPath.EndsWith( Path.AltDirectorySeparatorChar ) ) demandPath = fullPath + "."; else - demandPath = fullPath + Path.DirectorySeparatorCharAsString + "."; + demandPath = fullPath + Path.DirectorySeparatorChar + "."; } else { if (!(fullPath.EndsWith( Path.DirectorySeparatorChar ) || fullPath.EndsWith( Path.AltDirectorySeparatorChar )) ) - demandPath = fullPath + Path.DirectorySeparatorCharAsString; + demandPath = fullPath + Path.DirectorySeparatorChar; else demandPath = fullPath; } @@ -170,13 +170,13 @@ namespace System.IO { int length = fullPath.Length; // We need to trim the trailing slash or the code will try to create 2 directories of the same name. - if (length >= 2 && Path.IsDirectorySeparator(fullPath[length - 1])) + if (length >= 2 && PathInternal.IsDirectorySeparator(fullPath[length - 1])) length--; - int lengthRoot = Path.GetRootLength(fullPath); + int lengthRoot = PathInternal.GetRootLength(fullPath); // For UNC paths that are only // or /// - if (length == 2 && Path.IsDirectorySeparator(fullPath[1])) + if (length == 2 && PathInternal.IsDirectorySeparator(fullPath[1])) throw new IOException(Environment.GetResourceString("IO.IO_CannotCreateDirectory", path)); // We can save a bunch of work if the directory we want to create already exists. This also @@ -358,7 +358,7 @@ namespace System.IO { // Get fully qualified file name ending in \* for security check - String fullPath = Path.GetFullPathInternal(path); + String fullPath = Path.GetFullPath(path); String demandPath = GetDemandDir(fullPath, true); #if FEATURE_CORECLR @@ -925,10 +925,10 @@ namespace System.IO { if (path==null) throw new ArgumentNullException(nameof(path)); Contract.EndContractBlock(); - - String fullPath = Path.GetFullPathInternal(path); - String root = fullPath.Substring(0, Path.GetRootLength(fullPath)); - String demandPath = GetDemandDir(root, true); + + string fullPath = Path.GetFullPath(path); + string root = fullPath.Substring(0, PathInternal.GetRootLength(fullPath)); + string demandPath = GetDemandDir(root, true); #if FEATURE_CORECLR FileSecurityState state = new FileSecurityState(FileSecurityStateAccess.PathDiscovery, path, demandPath); @@ -942,7 +942,7 @@ namespace System.IO { internal static String InternalGetDirectoryRoot(String path) { if (path == null) return null; - return path.Substring(0, Path.GetRootLength(path)); + return path.Substring(0, PathInternal.GetRootLength(path)); } /*===============================CurrentDirectory=============================== @@ -1022,7 +1022,8 @@ namespace System.IO { [System.Security.SecurityCritical] private static string NewGetCurrentDirectory() { - using (StringBuffer buffer = new StringBuffer(PathInternal.MaxShortPath)) + // Start with a buffer the size of MAX_PATH + using (StringBuffer buffer = new StringBuffer(260)) { uint result = 0; while ((result = Win32Native.GetCurrentDirectoryW(buffer.CharCapacity, buffer.GetHandle())) > buffer.CharCapacity) @@ -1039,7 +1040,7 @@ namespace System.IO { #if !PLATFORM_UNIX if (buffer.Contains('~')) - return LongPathHelper.GetLongPathName(buffer); + return Path.GetFullPath(buffer.ToString()); #endif return buffer.ToString(); @@ -1067,7 +1068,7 @@ namespace System.IO { new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Demand(); #pragma warning restore 618 - String fulldestDirName = Path.GetFullPathInternal(path); + String fulldestDirName = Path.GetFullPath(path); if (!Win32Native.SetCurrentDirectory(fulldestDirName)) { // If path doesn't exist, this sets last error to 2 (File @@ -1103,13 +1104,13 @@ namespace System.IO { throw new ArgumentException(Environment.GetResourceString("Argument_EmptyFileName"), nameof(destDirName)); Contract.EndContractBlock(); - String fullsourceDirName = Path.GetFullPathInternal(sourceDirName); + String fullsourceDirName = Path.GetFullPath(sourceDirName); String sourcePath = GetDemandDir(fullsourceDirName, false); if (PathInternal.IsDirectoryTooLong(sourcePath)) throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong")); - String fulldestDirName = Path.GetFullPathInternal(destDirName); + String fulldestDirName = Path.GetFullPath(destDirName); String destPath = GetDemandDir(fulldestDirName, false); if (PathInternal.IsDirectoryTooLong(destPath)) @@ -1153,21 +1154,21 @@ namespace System.IO { [System.Security.SecuritySafeCritical] public static void Delete(String path) { - String fullPath = Path.GetFullPathInternal(path); + String fullPath = Path.GetFullPath(path); Delete(fullPath, path, false, true); } [System.Security.SecuritySafeCritical] public static void Delete(String path, bool recursive) { - String fullPath = Path.GetFullPathInternal(path); + String fullPath = Path.GetFullPath(path); Delete(fullPath, path, recursive, true); } [System.Security.SecurityCritical] internal static void UnsafeDelete(String path, bool recursive) { - String fullPath = Path.GetFullPathInternal(path); + String fullPath = Path.GetFullPath(path); Delete(fullPath, path, recursive, false); } @@ -1235,12 +1236,12 @@ namespace System.IO { Win32Native.WIN32_FIND_DATA data = new Win32Native.WIN32_FIND_DATA(); // Open a Find handle - using (SafeFindHandle hnd = Win32Native.FindFirstFile(fullPath+Path.DirectorySeparatorCharAsString+"*", data)) { + using (SafeFindHandle hnd = Win32Native.FindFirstFile(fullPath + Path.DirectorySeparatorChar + "*", data)) { if (hnd.IsInvalid) { hr = Marshal.GetLastWin32Error(); __Error.WinIOError(hr, fullPath); } - + do { bool isDir = (0!=(data.dwFileAttributes & Win32Native.FILE_ATTRIBUTE_DIRECTORY)); if (isDir) { @@ -1254,8 +1255,8 @@ namespace System.IO { // itself. bool shouldRecurse = (0 == (data.dwFileAttributes & (int) FileAttributes.ReparsePoint)); if (shouldRecurse) { - String newFullPath = Path.InternalCombine(fullPath, data.cFileName); - String newUserPath = Path.InternalCombine(userPath, data.cFileName); + String newFullPath = Path.Combine(fullPath, data.cFileName); + String newUserPath = Path.Combine(userPath, data.cFileName); try { DeleteHelper(newFullPath, newUserPath, recursive, false); } @@ -1270,7 +1271,7 @@ namespace System.IO { // unmount it. if (data.dwReserved0 == Win32Native.IO_REPARSE_TAG_MOUNT_POINT) { // Use full path plus a trailing '\' - String mountPoint = Path.InternalCombine(fullPath, data.cFileName + Path.DirectorySeparatorChar); + String mountPoint = Path.Combine(fullPath, data.cFileName + Path.DirectorySeparatorChar); r = Win32Native.DeleteVolumeMountPoint(mountPoint); if (!r) { hr = Marshal.GetLastWin32Error(); @@ -1289,7 +1290,7 @@ namespace System.IO { // RemoveDirectory on a symbolic link will // remove the link itself. - String reparsePoint = Path.InternalCombine(fullPath, data.cFileName); + String reparsePoint = Path.Combine(fullPath, data.cFileName); r = Win32Native.RemoveDirectory(reparsePoint); if (!r) { hr = Marshal.GetLastWin32Error(); @@ -1307,7 +1308,7 @@ namespace System.IO { } } else { - String fileName = Path.InternalCombine(fullPath, data.cFileName); + String fileName = Path.Combine(fullPath, data.cFileName); r = Win32Native.DeleteFile(fileName); if (!r) { hr = Marshal.GetLastWin32Error(); diff --git a/src/mscorlib/src/System/IO/DirectoryInfo.cs b/src/mscorlib/src/System/IO/DirectoryInfo.cs index ec58ae9bff..d29ab5791c 100644 --- a/src/mscorlib/src/System/IO/DirectoryInfo.cs +++ b/src/mscorlib/src/System/IO/DirectoryInfo.cs @@ -84,7 +84,7 @@ namespace System.IO { } // Must fully qualify the path for the security check - String fullPath = Path.GetFullPathInternal(path); + String fullPath = Path.GetFullPath(path); demandDir = new String[] {Directory.GetDemandDir(fullPath, true)}; #if FEATURE_CORECLR @@ -106,7 +106,7 @@ namespace System.IO { #endif //FEATURE_CORESYSTEM internal DirectoryInfo(String fullPath, bool junk) { - Contract.Assert(Path.GetRootLength(fullPath) > 0, "fullPath must be fully qualified!"); + Contract.Assert(PathInternal.GetRootLength(fullPath) > 0, "fullPath must be fully qualified!"); // Fast path when we know a DirectoryInfo exists. OriginalPath = Path.GetFileName(fullPath); @@ -198,8 +198,8 @@ namespace System.IO { { Contract.Requires(path != null); - String newDirs = Path.InternalCombine(FullPath, path); - String fullPath = Path.GetFullPathInternal(newDirs); + String newDirs = Path.Combine(FullPath, path); + String fullPath = Path.GetFullPath(newDirs); if (0!=String.Compare(FullPath,0,fullPath,0, FullPath.Length,StringComparison.OrdinalIgnoreCase)) { String displayPath = __Error.GetDisplayablePath(DisplayPath, false); @@ -519,7 +519,7 @@ namespace System.IO { get { String demandPath; - int rootLength = Path.GetRootLength(FullPath); + int rootLength = PathInternal.GetRootLength(FullPath); String rootPath = FullPath.Substring(0, rootLength); demandPath = Directory.GetDemandDir(rootPath, true); @@ -547,7 +547,7 @@ namespace System.IO { #else new FileIOPermission(FileIOPermissionAccess.Write | FileIOPermissionAccess.Read, demandDir, false, false).Demand(); #endif - String fullDestDirName = Path.GetFullPathInternal(destDirName); + String fullDestDirName = Path.GetFullPath(destDirName); String demandPath; if (!fullDestDirName.EndsWith(Path.DirectorySeparatorChar)) fullDestDirName = fullDestDirName + Path.DirectorySeparatorChar; diff --git a/src/mscorlib/src/System/IO/DriveInfo.cs b/src/mscorlib/src/System/IO/DriveInfo.cs index 62eac429af..efb322bf8a 100644 --- a/src/mscorlib/src/System/IO/DriveInfo.cs +++ b/src/mscorlib/src/System/IO/DriveInfo.cs @@ -63,7 +63,7 @@ namespace System.IO _name = driveName + ":\\"; else { // GetPathRoot does not check all invalid characters - Path.CheckInvalidPathChars(driveName); + PathInternal.CheckInvalidPathChars(driveName); _name = Path.GetPathRoot(driveName); // Disallow null or empty drive letters and UNC paths if (_name == null || _name.Length == 0 || _name.StartsWith("\\\\", StringComparison.Ordinal)) diff --git a/src/mscorlib/src/System/IO/File.cs b/src/mscorlib/src/System/IO/File.cs index 76cd0b2280..ce7022096c 100644 --- a/src/mscorlib/src/System/IO/File.cs +++ b/src/mscorlib/src/System/IO/File.cs @@ -139,8 +139,8 @@ namespace System.IO { Contract.Requires(sourceFileName.Length > 0); Contract.Requires(destFileName.Length > 0); - String fullSourceFileName = Path.GetFullPathInternal(sourceFileName); - String fullDestFileName = Path.GetFullPathInternal(destFileName); + String fullSourceFileName = Path.GetFullPath(sourceFileName); + String fullDestFileName = Path.GetFullPath(destFileName); #if FEATURE_CORECLR if (checkHost) { @@ -251,7 +251,7 @@ namespace System.IO { [System.Security.SecurityCritical] internal static void InternalDelete(String path, bool checkHost) { - String fullPath = Path.GetFullPathInternal(path); + String fullPath = Path.GetFullPath(path); #if FEATURE_CORECLR if (checkHost) @@ -282,7 +282,7 @@ namespace System.IO { throw new ArgumentNullException(nameof(path)); Contract.EndContractBlock(); - String fullPath = Path.GetFullPathInternal(path); + String fullPath = Path.GetFullPath(path); FileIOPermission.QuickDemand(FileIOPermissionAccess.Read | FileIOPermissionAccess.Write, fullPath, false, false); bool r = Win32Native.DecryptFile(fullPath, 0); @@ -306,7 +306,7 @@ namespace System.IO { throw new ArgumentNullException(nameof(path)); Contract.EndContractBlock(); - String fullPath = Path.GetFullPathInternal(path); + String fullPath = Path.GetFullPath(path); FileIOPermission.QuickDemand(FileIOPermissionAccess.Read | FileIOPermissionAccess.Write, fullPath, false, false); bool r = Win32Native.EncryptFile(fullPath); @@ -352,12 +352,12 @@ namespace System.IO { if (path.Length == 0) return false; - path = Path.GetFullPathInternal(path); + path = Path.GetFullPath(path); // After normalizing, check whether path ends in directory separator. // Otherwise, FillAttributeInfo removes it and we may return a false positive. - // GetFullPathInternal should never return null - Contract.Assert(path != null, "File.Exists: GetFullPathInternal returned null"); - if (path.Length > 0 && Path.IsDirectorySeparator(path[path.Length - 1])) + // GetFullPath should never return null + Contract.Assert(path != null, "File.Exists: GetFullPath returned null"); + if (path.Length > 0 && PathInternal.IsDirectorySeparator(path[path.Length - 1])) { return false; } @@ -441,7 +441,7 @@ namespace System.IO { [System.Security.SecurityCritical] private static DateTime InternalGetCreationTimeUtc(String path, bool checkHost) { - String fullPath = Path.GetFullPathInternal(path); + String fullPath = Path.GetFullPath(path); #if FEATURE_CORECLR if (checkHost) { @@ -498,7 +498,7 @@ namespace System.IO { [System.Security.SecurityCritical] private static DateTime InternalGetLastAccessTimeUtc(String path, bool checkHost) { - String fullPath = Path.GetFullPathInternal(path); + String fullPath = Path.GetFullPath(path); #if FEATURE_CORECLR if (checkHost) { @@ -555,7 +555,7 @@ namespace System.IO { [System.Security.SecurityCritical] private static DateTime InternalGetLastWriteTimeUtc(String path, bool checkHost) { - String fullPath = Path.GetFullPathInternal(path); + String fullPath = Path.GetFullPath(path); #if FEATURE_CORECLR if (checkHost) { @@ -578,7 +578,7 @@ namespace System.IO { [System.Security.SecuritySafeCritical] public static FileAttributes GetAttributes(String path) { - String fullPath = Path.GetFullPathInternal(path); + String fullPath = Path.GetFullPath(path); #if FEATURE_CORECLR FileSecurityState state = new FileSecurityState(FileSecurityStateAccess.Read, path, fullPath); state.EnsureState(); @@ -601,7 +601,7 @@ namespace System.IO { #endif public static void SetAttributes(String path, FileAttributes fileAttributes) { - String fullPath = Path.GetFullPathInternal(path); + String fullPath = Path.GetFullPath(path); #if !FEATURE_CORECLR FileIOPermission.QuickDemand(FileIOPermissionAccess.Write, fullPath, false, false); #endif @@ -633,7 +633,7 @@ namespace System.IO { throw new ArgumentNullException(nameof(fileSecurity)); Contract.EndContractBlock(); - String fullPath = Path.GetFullPathInternal(path); + String fullPath = Path.GetFullPath(path); // Appropriate security check should be done for us by FileSecurity. fileSecurity.Persist(fullPath); } @@ -1052,8 +1052,8 @@ namespace System.IO { throw new ArgumentException(Environment.GetResourceString("Argument_EmptyFileName"), nameof(destFileName)); Contract.EndContractBlock(); - String fullSourceFileName = Path.GetFullPathInternal(sourceFileName); - String fullDestFileName = Path.GetFullPathInternal(destFileName); + String fullSourceFileName = Path.GetFullPath(sourceFileName); + String fullDestFileName = Path.GetFullPath(destFileName); #if FEATURE_CORECLR if (checkHost) { @@ -1107,11 +1107,11 @@ namespace System.IO { // Write permission to all three files, read permission to source // and dest. - String fullSrcPath = Path.GetFullPathInternal(sourceFileName); - String fullDestPath = Path.GetFullPathInternal(destinationFileName); + String fullSrcPath = Path.GetFullPath(sourceFileName); + String fullDestPath = Path.GetFullPath(destinationFileName); String fullBackupPath = null; if (destinationBackupFileName != null) - fullBackupPath = Path.GetFullPathInternal(destinationBackupFileName); + fullBackupPath = Path.GetFullPath(destinationBackupFileName); #if FEATURE_CORECLR FileSecurityState sourceState = new FileSecurityState(FileSecurityStateAccess.Read | FileSecurityStateAccess.Write, sourceFileName, fullSrcPath); @@ -1251,7 +1251,7 @@ namespace System.IO { // we usually get ERROR_PATH_NOT_FOUND from the OS. We should // probably be consistent w/ every other directory. int hr = Marshal.GetLastWin32Error(); - String FullPath = Path.GetFullPathInternal(path); + String FullPath = Path.GetFullPath(path); if (hr==__Error.ERROR_PATH_NOT_FOUND && FullPath.Equals(Directory.GetDirectoryRoot(FullPath))) hr = __Error.ERROR_ACCESS_DENIED; diff --git a/src/mscorlib/src/System/IO/FileInfo.cs b/src/mscorlib/src/System/IO/FileInfo.cs index d9f57532cb..60a7b35d97 100644 --- a/src/mscorlib/src/System/IO/FileInfo.cs +++ b/src/mscorlib/src/System/IO/FileInfo.cs @@ -74,7 +74,7 @@ namespace System.IO { { OriginalPath = fileName; // Must fully qualify the path for the security check - String fullPath = Path.GetFullPathInternal(fileName); + String fullPath = Path.GetFullPath(fileName); #if FEATURE_CORECLR if (checkHost) { @@ -115,7 +115,7 @@ namespace System.IO { #endif //FEATURE_CORESYSTEM internal FileInfo(String fullPath, bool ignoreThis) { - Contract.Assert(Path.GetRootLength(fullPath) > 0, "fullPath must be fully qualified!"); + Contract.Assert(PathInternal.GetRootLength(fullPath) > 0, "fullPath must be fully qualified!"); _name = Path.GetFileName(fullPath); OriginalPath = _name; FullPath = fullPath; @@ -388,7 +388,7 @@ namespace System.IO { throw new ArgumentException(Environment.GetResourceString("Argument_EmptyFileName"), nameof(destFileName)); Contract.EndContractBlock(); - String fullDestFileName = Path.GetFullPathInternal(destFileName); + string fullDestFileName = Path.GetFullPath(destFileName); #if FEATURE_CORECLR FileSecurityState sourceState = new FileSecurityState(FileSecurityStateAccess.Write | FileSecurityStateAccess.Read, DisplayPath, FullPath); FileSecurityState destState = new FileSecurityState(FileSecurityStateAccess.Write, destFileName, fullDestFileName); diff --git a/src/mscorlib/src/System/IO/FileSecurityState.cs b/src/mscorlib/src/System/IO/FileSecurityState.cs index a3fa1fb460..f5a3f63c84 100644 --- a/src/mscorlib/src/System/IO/FileSecurityState.cs +++ b/src/mscorlib/src/System/IO/FileSecurityState.cs @@ -56,7 +56,7 @@ namespace System.IO else { VerifyPath(path); - m_canonicalizedPath = System.IO.Path.GetFullPathInternal(path); + m_canonicalizedPath = IO.Path.GetFullPath(path); } } @@ -115,7 +115,7 @@ namespace System.IO throw new ArgumentOutOfRangeException(nameof(access), Environment.GetResourceString("Arg_EnumIllegalVal")); } - private static void VerifyPath(String path) + private static void VerifyPath(string path) { if (path != null) { @@ -126,7 +126,7 @@ namespace System.IO throw new ArgumentException(Environment.GetResourceString("Argument_PathFormatNotSupported")); #endif - System.IO.Path.CheckInvalidPathChars(path, checkAdditional: true); + PathInternal.CheckInvalidPathChars(path); } } } diff --git a/src/mscorlib/src/System/IO/FileStream.cs b/src/mscorlib/src/System/IO/FileStream.cs deleted file mode 100644 index 2eb8744862..0000000000 --- a/src/mscorlib/src/System/IO/FileStream.cs +++ /dev/null @@ -1,2695 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -/*============================================================ -** -** -** -** -** -** Purpose: Exposes a Stream around a file, with full -** synchronous and asychronous support, and buffering. -** -** -===========================================================*/ -using System; -using Microsoft.Win32; -using Microsoft.Win32.SafeHandles; -using System.Security; -#if FEATURE_MACL -using System.Security.AccessControl; -#endif -using System.Security.Permissions; -using System.Threading; -using System.Threading.Tasks; -using System.Runtime.InteropServices; -#if FEATURE_REMOTING -using System.Runtime.Remoting.Messaging; -#endif -using System.Runtime.CompilerServices; -using System.Globalization; -using System.Runtime.Versioning; -using System.Diagnostics.Contracts; -using System.Diagnostics.Tracing; - -/* - * FileStream supports different modes of accessing the disk - async mode - * and sync mode. They are two completely different codepaths in the - * sync & async methods (ie, Read/Write vs. BeginRead/BeginWrite). File - * handles in NT can be opened in only sync or overlapped (async) mode, - * and we have to deal with this pain. Stream has implementations of - * the sync methods in terms of the async ones, so we'll - * call through to our base class to get those methods when necessary. - * - * Also buffering is added into FileStream as well. Folded in the - * code from BufferedStream, so all the comments about it being mostly - * aggressive (and the possible perf improvement) apply to FileStream as - * well. Also added some buffering to the async code paths. - * - * Class Invariants: - * The class has one buffer, shared for reading & writing. It can only be - * used for one or the other at any point in time - not both. The following - * should be true: - * 0 <= _readPos <= _readLen < _bufferSize - * 0 <= _writePos < _bufferSize - * _readPos == _readLen && _readPos > 0 implies the read buffer is valid, - * but we're at the end of the buffer. - * _readPos == _readLen == 0 means the read buffer contains garbage. - * Either _writePos can be greater than 0, or _readLen & _readPos can be - * greater than zero, but neither can be greater than zero at the same time. - * - */ - -namespace System.IO { - - // This is an internal object implementing IAsyncResult with fields - // for all of the relevant data necessary to complete the IO operation. - // This is used by AsyncFSCallback and all of the async methods. - // We should probably make this a nested type of FileStream. But - // I don't know how to define a nested class in mscorlib.h - - unsafe internal sealed class FileStreamAsyncResult : IAsyncResult - { - // README: - // If you modify the order of these fields, make sure to update - // the native VM definition of this class as well!!! - // User code callback - private AsyncCallback _userCallback; - private Object _userStateObject; - private ManualResetEvent _waitHandle; - [System.Security.SecurityCritical] - private SafeFileHandle _handle; // For cancellation support. - - [SecurityCritical] - private NativeOverlapped* _overlapped; - internal NativeOverlapped* OverLapped { [SecurityCritical]get { return _overlapped; } } - internal bool IsAsync { [SecuritySafeCritical]get { return _overlapped != null; } } - - - internal int _EndXxxCalled; // Whether we've called EndXxx already. - private int _numBytes; // number of bytes read OR written - internal int NumBytes { get { return _numBytes; } } - - private int _errorCode; - internal int ErrorCode { get { return _errorCode; } } - - private int _numBufferedBytes; - internal int NumBufferedBytes { get { return _numBufferedBytes; } } - - internal int NumBytesRead { get { return _numBytes + _numBufferedBytes; } } - - private bool _isWrite; // Whether this is a read or a write - internal bool IsWrite { get { return _isWrite; } } - - private bool _isComplete; // Value for IsCompleted property - private bool _completedSynchronously; // Which thread called callback - - // The NativeOverlapped struct keeps a GCHandle to this IAsyncResult object. - // So if the user doesn't call EndRead/EndWrite, a finalizer won't help because - // it'll never get called. - - // Overlapped class will take care of the async IO operations in progress - // when an appdomain unload occurs. - - [System.Security.SecurityCritical] // auto-generated - private unsafe static IOCompletionCallback s_IOCallback; - - [SecuritySafeCritical] - internal FileStreamAsyncResult( - int numBufferedBytes, - byte[] bytes, - SafeFileHandle handle, - AsyncCallback userCallback, - Object userStateObject, - bool isWrite) - { - _userCallback = userCallback; - _userStateObject = userStateObject; - _isWrite = isWrite; - _numBufferedBytes = numBufferedBytes; - _handle = handle; - - // For Synchronous IO, I could go with either a callback and using - // the managed Monitor class, or I could create a handle and wait on it. - ManualResetEvent waitHandle = new ManualResetEvent(false); - _waitHandle = waitHandle; - - // Create a managed overlapped class - // We will set the file offsets later - Overlapped overlapped = new Overlapped(0, 0, IntPtr.Zero, this); - - // Pack the Overlapped class, and store it in the async result - if (userCallback != null) - { - var ioCallback = s_IOCallback; // cached static delegate; delay initialized due to it being SecurityCritical - if (ioCallback == null) s_IOCallback = ioCallback = new IOCompletionCallback(AsyncFSCallback); - _overlapped = overlapped.Pack(ioCallback, bytes); - } - else - { - _overlapped = overlapped.UnsafePack(null, bytes); - } - - Contract.Assert(_overlapped != null, "Did Overlapped.Pack or Overlapped.UnsafePack just return a null?"); - } - - internal static FileStreamAsyncResult CreateBufferedReadResult(int numBufferedBytes, AsyncCallback userCallback, Object userStateObject, bool isWrite) - { - FileStreamAsyncResult asyncResult = new FileStreamAsyncResult(numBufferedBytes, userCallback, userStateObject, isWrite); - asyncResult.CallUserCallback(); - return asyncResult; - } - - // This creates a synchronous Async Result. We should consider making this a separate class and maybe merge it with - // System.IO.Stream.SynchronousAsyncResult - private FileStreamAsyncResult(int numBufferedBytes, AsyncCallback userCallback, Object userStateObject, bool isWrite) - { - _userCallback = userCallback; - _userStateObject = userStateObject; - _isWrite = isWrite; - _numBufferedBytes = numBufferedBytes; - } - - public Object AsyncState - { - get { return _userStateObject; } - } - - public bool IsCompleted - { - get { return _isComplete; } - } - - public WaitHandle AsyncWaitHandle - { - [System.Security.SecuritySafeCritical] // auto-generated - get { - // Consider uncommenting this someday soon - the EventHandle - // in the Overlapped struct is really useless half of the - // time today since the OS doesn't signal it. If users call - // EndXxx after the OS call happened to complete, there's no - // reason to create a synchronization primitive here. Fixing - // this will save us some perf, assuming we can correctly - // initialize the ManualResetEvent. - if (_waitHandle == null) { - ManualResetEvent mre = new ManualResetEvent(false); - if (_overlapped != null && _overlapped->EventHandle != IntPtr.Zero) { - mre.SafeWaitHandle = new SafeWaitHandle(_overlapped->EventHandle, true); - } - - // make sure only one thread sets _waitHandle - if (Interlocked.CompareExchange<ManualResetEvent>(ref _waitHandle, mre, null) == null) { - if (_isComplete) - _waitHandle.Set(); - } - else { - // There's a slight but acceptable race condition if we weren't - // the thread that set _waitHandle and this code path - // returns before the code in the if statement - // executes (on the other thread). However, the - // caller is waiting for the wait handle to be set, - // which will still happen. - mre.Close(); - } - } - return _waitHandle; - } - } - - // Returns true iff the user callback was called by the thread that - // called BeginRead or BeginWrite. If we use an async delegate or - // threadpool thread internally, this will be false. This is used - // by code to determine whether a successive call to BeginRead needs - // to be done on their main thread or in their callback to avoid a - // stack overflow on many reads or writes. - public bool CompletedSynchronously - { - get { return _completedSynchronously; } - } - - private void CallUserCallbackWorker() - { - _isComplete = true; - - // ensure _isComplete is set before reading _waitHandle - Thread.MemoryBarrier(); - if (_waitHandle != null) - _waitHandle.Set(); - - _userCallback(this); - } - - internal void CallUserCallback() - { - // Convenience method for me, since I have to do this in a number - // of places in the buffering code for fake IAsyncResults. - // AsyncFSCallback intentionally does not use this method. - - if (_userCallback != null) { - // Call user's callback on a threadpool thread. - // Set completedSynchronously to false, since it's on another - // thread, not the main thread. - _completedSynchronously = false; - ThreadPool.QueueUserWorkItem(state => ((FileStreamAsyncResult)state).CallUserCallbackWorker(), this); - } - else { - _isComplete = true; - - // ensure _isComplete is set before reading _waitHandle - Thread.MemoryBarrier(); - if (_waitHandle != null) - _waitHandle.Set(); - } - } - - [SecurityCritical] - internal void ReleaseNativeResource() - { - // Free memory & GC handles. - if (this._overlapped != null) - Overlapped.Free(_overlapped); - } - - internal void Wait() - { - if (_waitHandle != null) - { - // We must block to ensure that AsyncFSCallback has completed, - // and we should close the WaitHandle in here. AsyncFSCallback - // and the hand-ported imitation version in COMThreadPool.cpp - // are the only places that set this event. - try - { - _waitHandle.WaitOne(); - Contract.Assert(_isComplete == true, "FileStreamAsyncResult::Wait - AsyncFSCallback didn't set _isComplete to true!"); - } - finally - { - _waitHandle.Close(); - } - } - } - - // When doing IO asynchronously (ie, _isAsync==true), this callback is - // called by a free thread in the threadpool when the IO operation - // completes. - [System.Security.SecurityCritical] // auto-generated - unsafe private static void AsyncFSCallback(uint errorCode, uint numBytes, NativeOverlapped* pOverlapped) - { - BCLDebug.Log(String.Format("AsyncFSCallback called. errorCode: " + errorCode + " numBytes: " + numBytes)); - - // Unpack overlapped - Overlapped overlapped = Overlapped.Unpack(pOverlapped); - // Free the overlapped struct in EndRead/EndWrite. - - // Extract async result from overlapped - FileStreamAsyncResult asyncResult = - (FileStreamAsyncResult)overlapped.AsyncResult; - asyncResult._numBytes = (int)numBytes; - - if (FrameworkEventSource.IsInitialized && FrameworkEventSource.Log.IsEnabled(EventLevel.Informational, FrameworkEventSource.Keywords.ThreadTransfer)) - FrameworkEventSource.Log.ThreadTransferReceive((long)(asyncResult.OverLapped), 2, string.Empty); - - // Handle reading from & writing to closed pipes. While I'm not sure - // this is entirely necessary anymore, maybe it's possible for - // an async read on a pipe to be issued and then the pipe is closed, - // returning this error. This may very well be necessary. - if (errorCode == FileStream.ERROR_BROKEN_PIPE || errorCode == FileStream.ERROR_NO_DATA) - errorCode = 0; - - asyncResult._errorCode = (int)errorCode; - - // Call the user-provided callback. It can and often should - // call EndRead or EndWrite. There's no reason to use an async - // delegate here - we're already on a threadpool thread. - // IAsyncResult's completedSynchronously property must return - // false here, saying the user callback was called on another thread. - asyncResult._completedSynchronously = false; - asyncResult._isComplete = true; - - // ensure _isComplete is set before reading _waitHandle - Thread.MemoryBarrier(); - - // The OS does not signal this event. We must do it ourselves. - ManualResetEvent wh = asyncResult._waitHandle; - if (wh != null) - { - Contract.Assert(!wh.SafeWaitHandle.IsClosed, "ManualResetEvent already closed!"); - bool r = wh.Set(); - Contract.Assert(r, "ManualResetEvent::Set failed!"); - if (!r) __Error.WinIOError(); - } - - AsyncCallback userCallback = asyncResult._userCallback; - if (userCallback != null) - userCallback(asyncResult); - } - - [SecuritySafeCritical] - [HostProtection(ExternalThreading = true)] - internal void Cancel() - { - Contract.Assert(_handle != null, "_handle should not be null."); - Contract.Assert(_overlapped != null, "Cancel should only be called on true asynchronous FileStreamAsyncResult, i.e. _overlapped is not null"); - - if (IsCompleted) - return; - - if (_handle.IsInvalid) - return; - - bool r = Win32Native.CancelIoEx(_handle, _overlapped); - if (!r) - { - int errorCode = Marshal.GetLastWin32Error(); - - // ERROR_NOT_FOUND is returned if CancelIoEx cannot find the request to cancel. - // This probably means that the IO operation has completed. - if (errorCode != Win32Native.ERROR_NOT_FOUND) - __Error.WinIOError(errorCode, String.Empty); - } - } - } - - [ComVisible(true)] - public class FileStream : Stream - { - internal const int DefaultBufferSize = 4096; - - private byte[] _buffer; // Shared read/write buffer. Alloc on first use. - private String _fileName; // Fully qualified file name. - private bool _isAsync; // Whether we opened the handle for overlapped IO - private bool _canRead; - private bool _canWrite; - private bool _canSeek; - private bool _exposedHandle; // Could other code be using this handle? - private bool _isPipe; // Whether to disable async buffering code. - private int _readPos; // Read pointer within shared buffer. - private int _readLen; // Number of bytes read in buffer from file. - private int _writePos; // Write pointer within shared buffer. - private int _bufferSize; // Length of internal buffer, if it's allocated. - [System.Security.SecurityCritical] // auto-generated - private SafeFileHandle _handle; - private long _pos; // Cache current location in the file. - private long _appendStart;// When appending, prevent overwriting file. - private static AsyncCallback s_endReadTask; - private static AsyncCallback s_endWriteTask; - private static Action<object> s_cancelReadHandler; - private static Action<object> s_cancelWriteHandler; - - //This exists only to support IsolatedStorageFileStream. - //Any changes to FileStream must include the corresponding changes in IsolatedStorage. - internal FileStream() { - } -#if FEATURE_CORECLR - [System.Security.SecuritySafeCritical] - public FileStream(String path, FileMode mode) - : this(path, mode, (mode == FileMode.Append ? FileAccess.Write : FileAccess.ReadWrite), FileShare.Read, DefaultBufferSize, FileOptions.None, Path.GetFileName(path), false, false, true) { - } - - [System.Security.SecuritySafeCritical] - public FileStream(String path, FileMode mode, FileAccess access) - : this(path, mode, access, FileShare.Read, DefaultBufferSize, FileOptions.None, Path.GetFileName(path), false, false, true) { - } - - [System.Security.SecuritySafeCritical] - public FileStream(String path, FileMode mode, FileAccess access, FileShare share) - : this(path, mode, access, share, DefaultBufferSize, FileOptions.None, Path.GetFileName(path), false, false, true) { - } - - [System.Security.SecuritySafeCritical] - public FileStream(String path, FileMode mode, FileAccess access, FileShare share, int bufferSize) - : this(path, mode, access, share, bufferSize, FileOptions.None, Path.GetFileName(path), false, false, true) - { - } - -#else // FEATURE_CORECLR - [System.Security.SecuritySafeCritical] // auto-generated - public FileStream(String path, FileMode mode) - : this(path, mode, (mode == FileMode.Append ? FileAccess.Write : FileAccess.ReadWrite), FileShare.Read, DefaultBufferSize, FileOptions.None, Path.GetFileName(path), false) { - } - - [System.Security.SecuritySafeCritical] // auto-generated - public FileStream(String path, FileMode mode, FileAccess access) - : this(path, mode, access, FileShare.Read, DefaultBufferSize, FileOptions.None, Path.GetFileName(path), false) { - } - - [System.Security.SecuritySafeCritical] // auto-generated - public FileStream(String path, FileMode mode, FileAccess access, FileShare share) - : this(path, mode, access, share, DefaultBufferSize, FileOptions.None, Path.GetFileName(path), false) { - } - - [System.Security.SecuritySafeCritical] // auto-generated - public FileStream(String path, FileMode mode, FileAccess access, FileShare share, int bufferSize) - : this(path, mode, access, share, bufferSize, FileOptions.None, Path.GetFileName(path), false) - { - } -#endif // FEATURE_CORECLR - - [System.Security.SecuritySafeCritical] // auto-generated - public FileStream(String path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options) - : this(path, mode, access, share, bufferSize, options, Path.GetFileName(path), false) - { - } - - #if FEATURE_CORECLR - [System.Security.SecurityCritical] // auto-generated - #else - [System.Security.SecuritySafeCritical] - #endif - public FileStream(String path, FileMode mode, FileAccess access, FileShare share, int bufferSize, bool useAsync) - : this(path, mode, access, share, bufferSize, (useAsync ? FileOptions.Asynchronous : FileOptions.None), Path.GetFileName(path), false) - { - } - -#if FEATURE_MACL - // This constructor is done differently to avoid loading a few more - // classes, and more importantly, to build correctly on Rotor. - [System.Security.SecuritySafeCritical] // auto-generated - public FileStream(String path, FileMode mode, FileSystemRights rights, FileShare share, int bufferSize, FileOptions options, FileSecurity fileSecurity) - { - Object pinningHandle; - Win32Native.SECURITY_ATTRIBUTES secAttrs = GetSecAttrs(share, fileSecurity, out pinningHandle); - try { - Init(path, mode, (FileAccess)0, (int)rights, true, share, bufferSize, options, secAttrs, Path.GetFileName(path), false, false, false); - } - finally { - if (pinningHandle != null) { - GCHandle pinHandle = (GCHandle) pinningHandle; - pinHandle.Free(); - } - } - } - - [System.Security.SecuritySafeCritical] // auto-generated - public FileStream(String path, FileMode mode, FileSystemRights rights, FileShare share, int bufferSize, FileOptions options) - { - Win32Native.SECURITY_ATTRIBUTES secAttrs = GetSecAttrs(share); - Init(path, mode, (FileAccess)0, (int)rights, true, share, bufferSize, options, secAttrs, Path.GetFileName(path), false, false, false); - } -#endif - - [System.Security.SecurityCritical] // auto-generated - internal FileStream(String path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options, String msgPath, bool bFromProxy) - { - Win32Native.SECURITY_ATTRIBUTES secAttrs = GetSecAttrs(share); - Init(path, mode, access, 0, false, share, bufferSize, options, secAttrs, msgPath, bFromProxy, false, false); - } - - [System.Security.SecurityCritical] - internal FileStream(String path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options, String msgPath, bool bFromProxy, bool useLongPath) - { - Win32Native.SECURITY_ATTRIBUTES secAttrs = GetSecAttrs(share); - Init(path, mode, access, 0, false, share, bufferSize, options, secAttrs, msgPath, bFromProxy, useLongPath, false); - } - - [System.Security.SecurityCritical] - internal FileStream(String path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options, String msgPath, bool bFromProxy, bool useLongPath, bool checkHost) - { - Win32Native.SECURITY_ATTRIBUTES secAttrs = GetSecAttrs(share); - Init(path, mode, access, 0, false, share, bufferSize, options, secAttrs, msgPath, bFromProxy, useLongPath, checkHost); - } - - // AccessControl namespace is not defined in Rotor - [System.Security.SecuritySafeCritical] - private void Init(String path, FileMode mode, FileAccess access, int rights, bool useRights, FileShare share, int bufferSize, FileOptions options, Win32Native.SECURITY_ATTRIBUTES secAttrs, String msgPath, bool bFromProxy, bool useLongPath, bool checkHost) - { - if (path == null) - throw new ArgumentNullException(nameof(path), Environment.GetResourceString("ArgumentNull_Path")); - if (path.Length == 0) - throw new ArgumentException(Environment.GetResourceString("Argument_EmptyPath")); - Contract.EndContractBlock(); - -#if FEATURE_MACL - FileSystemRights fileSystemRights = (FileSystemRights)rights; -#endif - // msgPath must be safe to hand back to untrusted code. - - _fileName = msgPath; // To handle odd cases of finalizing partially constructed objects. - _exposedHandle = false; - - // don't include inheritable in our bounds check for share - FileShare tempshare = share & ~FileShare.Inheritable; - String badArg = null; - - if (mode < FileMode.CreateNew || mode > FileMode.Append) - badArg = nameof(mode); - else if (!useRights && (access < FileAccess.Read || access > FileAccess.ReadWrite)) - badArg = nameof(access); -#if FEATURE_MACL - else if (useRights && (fileSystemRights < FileSystemRights.ReadData || fileSystemRights > FileSystemRights.FullControl)) - badArg = "rights"; -#endif - else if (tempshare < FileShare.None || tempshare > (FileShare.ReadWrite | FileShare.Delete)) - badArg = nameof(share); - - if (badArg != null) - throw new ArgumentOutOfRangeException(badArg, Environment.GetResourceString("ArgumentOutOfRange_Enum")); - - // NOTE: any change to FileOptions enum needs to be matched here in the error validation - if (options != FileOptions.None && (options & ~(FileOptions.WriteThrough | FileOptions.Asynchronous | FileOptions.RandomAccess | FileOptions.DeleteOnClose | FileOptions.SequentialScan | FileOptions.Encrypted | (FileOptions)0x20000000 /* NoBuffering */)) != 0) - throw new ArgumentOutOfRangeException(nameof(options), Environment.GetResourceString("ArgumentOutOfRange_Enum")); - - if (bufferSize <= 0) - throw new ArgumentOutOfRangeException(nameof(bufferSize), Environment.GetResourceString("ArgumentOutOfRange_NeedPosNum")); - - // Write access validation -#if FEATURE_MACL - if ((!useRights && (access & FileAccess.Write) == 0) - || (useRights && (fileSystemRights & FileSystemRights.Write) == 0)) -#else - if (!useRights && (access & FileAccess.Write) == 0) -#endif //FEATURE_MACL - { - if (mode==FileMode.Truncate || mode==FileMode.CreateNew || mode==FileMode.Create || mode==FileMode.Append) { - // No write access - if (!useRights) - throw new ArgumentException(Environment.GetResourceString("Argument_InvalidFileMode&AccessCombo", mode, access)); -#if FEATURE_MACL - else - throw new ArgumentException(Environment.GetResourceString("Argument_InvalidFileMode&RightsCombo", mode, fileSystemRights)); -#endif //FEATURE_MACL - } - } - -#if FEATURE_MACL - // FileMode.Truncate only works with GENERIC_WRITE (FileAccess.Write), source:MSDN - // For backcomp use FileAccess.Write when FileSystemRights.Write is specified - if (useRights && (mode == FileMode.Truncate)) { - if (fileSystemRights == FileSystemRights.Write) { - useRights = false; - access = FileAccess.Write; - } - else { - throw new ArgumentException(Environment.GetResourceString("Argument_InvalidFileModeTruncate&RightsCombo", mode, fileSystemRights)); - } - } -#endif - - int fAccess; - if (!useRights) { - fAccess = access == FileAccess.Read? GENERIC_READ: - access == FileAccess.Write? GENERIC_WRITE: - GENERIC_READ | GENERIC_WRITE; - } - else { - fAccess = rights; - } - - // Get absolute path - Security needs this to prevent something - // like trying to create a file in c:\tmp with the name - // "..\WinNT\System32\ntoskrnl.exe". Store it for user convenience. - int maxPath = useLongPath ? Path.MaxLongPath : Path.MaxPath; - String filePath = Path.NormalizePath(path, true, maxPath); - - _fileName = filePath; - - // Prevent access to your disk drives as raw block devices. - if (filePath.StartsWith("\\\\.\\", StringComparison.Ordinal)) - throw new ArgumentException(Environment.GetResourceString("Arg_DevicesNotSupported")); - - // In 4.0, we always construct a FileIOPermission object below. - // If filePath contained a ':', we would throw a NotSupportedException in - // System.Security.Util.StringExpressionSet.CanonicalizePath. - // If filePath contained other illegal characters, we would throw an ArgumentException in - // FileIOPermission.CheckIllegalCharacters. - // In 4.5 we on longer construct the FileIOPermission object in full trust. - // To preserve the 4.0 behavior we do an explicit check for ':' here and also call Path.CheckInvalidPathChars. - // Note that we need to call CheckInvalidPathChars before checking for ':' because that is what FileIOPermission does. - - Path.CheckInvalidPathChars(filePath, true); - -#if !PLATFORM_UNIX - if (filePath.IndexOf( ':', 2 ) != -1) - throw new NotSupportedException( Environment.GetResourceString( "Argument_PathFormatNotSupported" ) ); -#endif // !PLATFORM_UNIX - - bool read = false; - -#if FEATURE_MACL - if ((!useRights && (access & FileAccess.Read) != 0) || (useRights && (fileSystemRights & FileSystemRights.ReadAndExecute) != 0)) -#else - if (!useRights && (access & FileAccess.Read) != 0) -#endif //FEATURE_MACL - { - if (mode == FileMode.Append) - throw new ArgumentException(Environment.GetResourceString("Argument_InvalidAppendMode")); - else - read = true; - } - - // All demands in full trust domains are no-ops, so skip -#if FEATURE_CAS_POLICY - if (!CodeAccessSecurityEngine.QuickCheckForAllDemands()) -#endif // FEATURE_CAS_POLICY - { - // Build up security permissions required, as well as validate we - // have a sensible set of parameters. IE, creating a brand new file - // for reading doesn't make much sense. - FileIOPermissionAccess secAccess = FileIOPermissionAccess.NoAccess; - - if (read) - { - Contract.Assert(mode != FileMode.Append); - secAccess = secAccess | FileIOPermissionAccess.Read; - } - - // I can't think of any combos of FileMode we should disallow if we - // don't have read access. Writing would pretty much always be valid - // in those cases. - - // For any FileSystemRights other than ReadAndExecute, demand Write permission - // This is probably bit overkill for TakeOwnership etc but we don't have any - // matching FileIOPermissionAccess to demand. It is better that we ask for Write permission. - -#if FEATURE_MACL - // FileMode.OpenOrCreate & FileSystemRights.Synchronize can create 0-byte file; demand write - if ((!useRights && (access & FileAccess.Write) != 0) - || (useRights && (fileSystemRights & (FileSystemRights.Write | FileSystemRights.Delete - | FileSystemRights.DeleteSubdirectoriesAndFiles - | FileSystemRights.ChangePermissions - | FileSystemRights.TakeOwnership)) != 0) - || (useRights && ((fileSystemRights & FileSystemRights.Synchronize) != 0) - && mode==FileMode.OpenOrCreate) - ) -#else - if (!useRights && (access & FileAccess.Write) != 0) -#endif //FEATURE_MACL - { - if (mode==FileMode.Append) - secAccess = secAccess | FileIOPermissionAccess.Append; - else - secAccess = secAccess | FileIOPermissionAccess.Write; - } - -#if FEATURE_MACL - bool specifiedAcl; - unsafe { - specifiedAcl = secAttrs != null && secAttrs.pSecurityDescriptor != null; - } - - AccessControlActions control = specifiedAcl ? AccessControlActions.Change : AccessControlActions.None; - new FileIOPermission(secAccess, control, new String[] { filePath }, false, false).Demand(); -#else -#if FEATURE_CORECLR - if (checkHost) { - FileSecurityState state = new FileSecurityState(FileSecurityState.ToFileSecurityState(secAccess), path, filePath); - state.EnsureState(); - } -#else - new FileIOPermission(secAccess, new String[] { filePath }, false, false).Demand(); -#endif // FEATURE_CORECLR -#endif - } - - // Our Inheritable bit was stolen from Windows, but should be set in - // the security attributes class. Don't leave this bit set. - share &= ~FileShare.Inheritable; - - bool seekToEnd = (mode==FileMode.Append); - // Must use a valid Win32 constant here... - if (mode == FileMode.Append) - mode = FileMode.OpenOrCreate; - - // WRT async IO, do the right thing for whatever platform we're on. - // This way, someone can easily write code that opens a file - // asynchronously no matter what their platform is. - if ((options & FileOptions.Asynchronous) != 0) - _isAsync = true; - else - options &= ~FileOptions.Asynchronous; - - int flagsAndAttributes = (int) options; - -#if !PLATFORM_UNIX - // For mitigating local elevation of privilege attack through named pipes - // make sure we always call CreateFile with SECURITY_ANONYMOUS so that the - // named pipe server can't impersonate a high privileged client security context - flagsAndAttributes |= (Win32Native.SECURITY_SQOS_PRESENT | Win32Native.SECURITY_ANONYMOUS); -#endif - - // Don't pop up a dialog for reading from an emtpy floppy drive - int oldMode = Win32Native.SetErrorMode(Win32Native.SEM_FAILCRITICALERRORS); - try { - String tempPath = filePath; - if (useLongPath) - tempPath = Path.AddLongPathPrefix(tempPath); - _handle = Win32Native.SafeCreateFile(tempPath, fAccess, share, secAttrs, mode, flagsAndAttributes, IntPtr.Zero); - - if (_handle.IsInvalid) { - // Return a meaningful exception, using the RELATIVE path to - // the file to avoid returning extra information to the caller - // unless they have path discovery permission, in which case - // the full path is fine & useful. - - // NT5 oddity - when trying to open "C:\" as a FileStream, - // we usually get ERROR_PATH_NOT_FOUND from the OS. We should - // probably be consistent w/ every other directory. - int errorCode = Marshal.GetLastWin32Error(); - if (errorCode==__Error.ERROR_PATH_NOT_FOUND && filePath.Equals(Directory.InternalGetDirectoryRoot(filePath))) - errorCode = __Error.ERROR_ACCESS_DENIED; - - // We need to give an exception, and preferably it would include - // the fully qualified path name. Do security check here. If - // we fail, give back the msgPath, which should not reveal much. - // While this logic is largely duplicated in - // __Error.WinIOError, we need this for - // IsolatedStorageFileStream. - bool canGiveFullPath = false; - - if (!bFromProxy) - { - try { -#if !FEATURE_CORECLR - new FileIOPermission(FileIOPermissionAccess.PathDiscovery, new String[] { _fileName }, false, false ).Demand(); -#endif - canGiveFullPath = true; - } - catch(SecurityException) {} - } - - if (canGiveFullPath) - __Error.WinIOError(errorCode, _fileName); - else - __Error.WinIOError(errorCode, msgPath); - } - } - finally { - Win32Native.SetErrorMode(oldMode); - } - - // Disallow access to all non-file devices from the FileStream - // constructors that take a String. Everyone else can call - // CreateFile themselves then use the constructor that takes an - // IntPtr. Disallows "con:", "com1:", "lpt1:", etc. - int fileType = Win32Native.GetFileType(_handle); - if (fileType != Win32Native.FILE_TYPE_DISK) { - _handle.Close(); - throw new NotSupportedException(Environment.GetResourceString("NotSupported_FileStreamOnNonFiles")); - } - - // This is necessary for async IO using IO Completion ports via our - // managed Threadpool API's. This (theoretically) calls the OS's - // BindIoCompletionCallback method, and passes in a stub for the - // LPOVERLAPPED_COMPLETION_ROUTINE. This stub looks at the Overlapped - // struct for this request and gets a delegate to a managed callback - // from there, which it then calls on a threadpool thread. (We allocate - // our native OVERLAPPED structs 2 pointers too large and store EE state - // & GC handles there, one to an IAsyncResult, the other to a delegate.) - if (_isAsync) { - bool b = false; - // BindHandle requires UnmanagedCode permission -#pragma warning disable 618 - new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Assert(); -#pragma warning restore 618 - try { - b = ThreadPool.BindHandle(_handle); - } - finally { - CodeAccessPermission.RevertAssert(); - if (!b) { - // We should close the handle so that the handle is not open until SafeFileHandle GC - Contract.Assert(!_exposedHandle, "Are we closing handle that we exposed/not own, how?"); - _handle.Close(); - } - } - if (!b) - throw new IOException(Environment.GetResourceString("IO.IO_BindHandleFailed")); - } - - if (!useRights) { - _canRead = (access & FileAccess.Read) != 0; - _canWrite = (access & FileAccess.Write) != 0; - } -#if FEATURE_MACL - else { - _canRead = (fileSystemRights & FileSystemRights.ReadData) != 0; - _canWrite = ((fileSystemRights & FileSystemRights.WriteData) != 0) - || ((fileSystemRights & FileSystemRights.AppendData) != 0); - } -#endif //FEATURE_MACL - - _canSeek = true; - _isPipe = false; - _pos = 0; - _bufferSize = bufferSize; - _readPos = 0; - _readLen = 0; - _writePos = 0; - - // For Append mode... - if (seekToEnd) { - _appendStart = SeekCore(0, SeekOrigin.End); - } - else { - _appendStart = -1; - } - } - - [Obsolete("This constructor has been deprecated. Please use new FileStream(SafeFileHandle handle, FileAccess access) instead. http://go.microsoft.com/fwlink/?linkid=14202")] - public FileStream(IntPtr handle, FileAccess access) - : this(handle, access, true, DefaultBufferSize, false) { - } - - [Obsolete("This constructor has been deprecated. Please use new FileStream(SafeFileHandle handle, FileAccess access) instead, and optionally make a new SafeFileHandle with ownsHandle=false if needed. http://go.microsoft.com/fwlink/?linkid=14202")] - public FileStream(IntPtr handle, FileAccess access, bool ownsHandle) - : this(handle, access, ownsHandle, DefaultBufferSize, false) { - } - - [Obsolete("This constructor has been deprecated. Please use new FileStream(SafeFileHandle handle, FileAccess access, int bufferSize) instead, and optionally make a new SafeFileHandle with ownsHandle=false if needed. http://go.microsoft.com/fwlink/?linkid=14202")] - public FileStream(IntPtr handle, FileAccess access, bool ownsHandle, int bufferSize) - : this(handle, access, ownsHandle, bufferSize, false) { - } - - // We explicitly do a Demand, not a LinkDemand here. - [System.Security.SecuritySafeCritical] // auto-generated - [Obsolete("This constructor has been deprecated. Please use new FileStream(SafeFileHandle handle, FileAccess access, int bufferSize, bool isAsync) instead, and optionally make a new SafeFileHandle with ownsHandle=false if needed. http://go.microsoft.com/fwlink/?linkid=14202")] -#pragma warning disable 618 - [SecurityPermissionAttribute(SecurityAction.Demand, Flags=SecurityPermissionFlag.UnmanagedCode)] -#pragma warning restore 618 - public FileStream(IntPtr handle, FileAccess access, bool ownsHandle, int bufferSize, bool isAsync) - : this(new SafeFileHandle(handle, ownsHandle), access, bufferSize, isAsync) { - } - - [System.Security.SecuritySafeCritical] // auto-generated - public FileStream(SafeFileHandle handle, FileAccess access) - : this(handle, access, DefaultBufferSize, false) { - } - - [System.Security.SecuritySafeCritical] // auto-generated - public FileStream(SafeFileHandle handle, FileAccess access, int bufferSize) - : this(handle, access, bufferSize, false) { - } - - [System.Security.SecuritySafeCritical] // auto-generated -#pragma warning disable 618 - [SecurityPermissionAttribute(SecurityAction.Demand, Flags=SecurityPermissionFlag.UnmanagedCode)] -#pragma warning restore 618 - public FileStream(SafeFileHandle handle, FileAccess access, int bufferSize, bool isAsync) { - // To ensure we don't leak a handle, put it in a SafeFileHandle first - if (handle.IsInvalid) - throw new ArgumentException(Environment.GetResourceString("Arg_InvalidHandle"), nameof(handle)); - Contract.EndContractBlock(); - - _handle = handle; - _exposedHandle = true; - - // Now validate arguments. - if (access < FileAccess.Read || access > FileAccess.ReadWrite) - throw new ArgumentOutOfRangeException(nameof(access), Environment.GetResourceString("ArgumentOutOfRange_Enum")); - if (bufferSize <= 0) - throw new ArgumentOutOfRangeException(nameof(bufferSize), Environment.GetResourceString("ArgumentOutOfRange_NeedPosNum")); - - int handleType = Win32Native.GetFileType(_handle); - Contract.Assert(handleType == Win32Native.FILE_TYPE_DISK || handleType == Win32Native.FILE_TYPE_PIPE || handleType == Win32Native.FILE_TYPE_CHAR, "FileStream was passed an unknown file type!"); - _isAsync = isAsync; - _canRead = 0 != (access & FileAccess.Read); - _canWrite = 0 != (access & FileAccess.Write); - _canSeek = handleType == Win32Native.FILE_TYPE_DISK; - _bufferSize = bufferSize; - _readPos = 0; - _readLen = 0; - _writePos = 0; - _fileName = null; - _isPipe = handleType == Win32Native.FILE_TYPE_PIPE; - - // This is necessary for async IO using IO Completion ports via our - // managed Threadpool API's. This calls the OS's - // BindIoCompletionCallback method, and passes in a stub for the - // LPOVERLAPPED_COMPLETION_ROUTINE. This stub looks at the Overlapped - // struct for this request and gets a delegate to a managed callback - // from there, which it then calls on a threadpool thread. (We allocate - // our native OVERLAPPED structs 2 pointers too large and store EE - // state & a handle to a delegate there.) -#if !FEATURE_CORECLR - if (_isAsync) { - bool b = false; - try { - b = ThreadPool.BindHandle(_handle); - } - catch (ApplicationException) { - // If you passed in a synchronous handle and told us to use - // it asynchronously, throw here. - throw new ArgumentException(Environment.GetResourceString("Arg_HandleNotAsync")); - } - if (!b) { - throw new IOException(Environment.GetResourceString("IO.IO_BindHandleFailed")); - } - } - else { -#endif // FEATURE_CORECLR - if (handleType != Win32Native.FILE_TYPE_PIPE) - VerifyHandleIsSync(); -#if !FEATURE_CORECLR - } -#endif // FEATURE_CORECLR - - if (_canSeek) - SeekCore(0, SeekOrigin.Current); - else - _pos = 0; - } - - [System.Security.SecuritySafeCritical] // auto-generated - private static Win32Native.SECURITY_ATTRIBUTES GetSecAttrs(FileShare share) - { - Win32Native.SECURITY_ATTRIBUTES secAttrs = null; - if ((share & FileShare.Inheritable) != 0) { - secAttrs = new Win32Native.SECURITY_ATTRIBUTES(); - secAttrs.nLength = (int)Marshal.SizeOf(secAttrs); - - secAttrs.bInheritHandle = 1; - } - return secAttrs; - } - -#if FEATURE_MACL - // If pinningHandle is not null, caller must free it AFTER the call to - // CreateFile has returned. - [System.Security.SecuritySafeCritical] // auto-generated - private unsafe static Win32Native.SECURITY_ATTRIBUTES GetSecAttrs(FileShare share, FileSecurity fileSecurity, out Object pinningHandle) - { - pinningHandle = null; - Win32Native.SECURITY_ATTRIBUTES secAttrs = null; - if ((share & FileShare.Inheritable) != 0 || fileSecurity != null) { - secAttrs = new Win32Native.SECURITY_ATTRIBUTES(); - secAttrs.nLength = (int)Marshal.SizeOf(secAttrs); - - if ((share & FileShare.Inheritable) != 0) { - secAttrs.bInheritHandle = 1; - } - - // For ACL's, get the security descriptor from the FileSecurity. - if (fileSecurity != null) { - byte[] sd = fileSecurity.GetSecurityDescriptorBinaryForm(); - pinningHandle = GCHandle.Alloc(sd, GCHandleType.Pinned); - fixed(byte* pSecDescriptor = sd) - secAttrs.pSecurityDescriptor = pSecDescriptor; - } - } - return secAttrs; - } -#endif - - // Verifies that this handle supports synchronous IO operations (unless you - // didn't open it for either reading or writing). - [System.Security.SecuritySafeCritical] // auto-generated - private unsafe void VerifyHandleIsSync() - { - // Do NOT use this method on pipes. Reading or writing to a pipe may - // cause an app to block incorrectly, introducing a deadlock (depending - // on whether a write will wake up an already-blocked thread or this - // FileStream's thread). - - // Do NOT change this to use a byte[] of length 0, or test test won't - // work. Our ReadFile & WriteFile methods are special cased to return - // for arrays of length 0, since we'd get an IndexOutOfRangeException - // while using C#'s fixed syntax. - byte[] bytes = new byte[1]; - int hr = 0; - int r = 0; - - // If the handle is a pipe, ReadFile will block until there - // has been a write on the other end. We'll just have to deal with it, - // For the read end of a pipe, you can mess up and - // accidentally read synchronously from an async pipe. - if (CanRead) { - r = ReadFileNative(_handle, bytes, 0, 0, null, out hr); - } - else if (CanWrite) { - r = WriteFileNative(_handle, bytes, 0, 0, null, out hr); - } - - if (hr==ERROR_INVALID_PARAMETER) - throw new ArgumentException(Environment.GetResourceString("Arg_HandleNotSync")); - if (hr == Win32Native.ERROR_INVALID_HANDLE) - __Error.WinIOError(hr, "<OS handle>"); - } - - - public override bool CanRead { - [Pure] - get { return _canRead; } - } - - public override bool CanWrite { - [Pure] - get { return _canWrite; } - } - - public override bool CanSeek { - [Pure] - get { return _canSeek; } - } - - public virtual bool IsAsync { - get { return _isAsync; } - } - - public override long Length { - [System.Security.SecuritySafeCritical] // auto-generated - get { - if (_handle.IsClosed) __Error.FileNotOpen(); - if (!CanSeek) __Error.SeekNotSupported(); - int hi = 0, lo = 0; - - lo = Win32Native.GetFileSize(_handle, out hi); - - if (lo==-1) { // Check for either an error or a 4GB - 1 byte file. - int hr = Marshal.GetLastWin32Error(); - if (hr != 0) - __Error.WinIOError(hr, String.Empty); - } - long len = (((long)hi) << 32) | ((uint) lo); - // If we're writing near the end of the file, we must include our - // internal buffer in our Length calculation. Don't flush because - // we use the length of the file in our async write method. - if (_writePos > 0 && _pos + _writePos > len) - len = _writePos + _pos; - return len; - } - } - - public String Name { - [System.Security.SecuritySafeCritical] - get { - if (_fileName == null) - return Environment.GetResourceString("IO_UnknownFileName"); -#if FEATURE_CORECLR - FileSecurityState sourceState = new FileSecurityState(FileSecurityStateAccess.PathDiscovery, String.Empty, _fileName); - sourceState.EnsureState(); -#else - new FileIOPermission(FileIOPermissionAccess.PathDiscovery, new String[] { _fileName }, false, false).Demand(); -#endif - return _fileName; - } - } - - internal String NameInternal { - get { - if (_fileName == null) - return "<UnknownFileName>"; - return _fileName; - } - } - - public override long Position { - [System.Security.SecuritySafeCritical] // auto-generated - get { - if (_handle.IsClosed) __Error.FileNotOpen(); - if (!CanSeek) __Error.SeekNotSupported(); - - Contract.Assert((_readPos == 0 && _readLen == 0 && _writePos >= 0) || (_writePos == 0 && _readPos <= _readLen), "We're either reading or writing, but not both."); - - // Verify that internal position is in sync with the handle - if (_exposedHandle) - VerifyOSHandlePosition(); - - // Compensate for buffer that we read from the handle (_readLen) Vs what the user - // read so far from the internel buffer (_readPos). Of course add any unwrittern - // buffered data - return _pos + (_readPos - _readLen + _writePos); - } - set { - if (value < 0) throw new ArgumentOutOfRangeException(nameof(value), Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); - Contract.EndContractBlock(); - if (_writePos > 0) FlushWrite(false); - _readPos = 0; - _readLen = 0; - Seek(value, SeekOrigin.Begin); - } - } - -#if FEATURE_MACL - [System.Security.SecuritySafeCritical] // auto-generated - public FileSecurity GetAccessControl() - { - if (_handle.IsClosed) __Error.FileNotOpen(); - return new FileSecurity(_handle, _fileName, AccessControlSections.Access | AccessControlSections.Owner | AccessControlSections.Group); - } - - [System.Security.SecuritySafeCritical] // auto-generated - public void SetAccessControl(FileSecurity fileSecurity) - { - if (fileSecurity == null) - throw new ArgumentNullException(nameof(fileSecurity)); - Contract.EndContractBlock(); - - if (_handle.IsClosed) __Error.FileNotOpen(); - - fileSecurity.Persist(_handle, _fileName); - } -#endif - - [System.Security.SecuritySafeCritical] // auto-generated - protected override void Dispose(bool disposing) - { - // Nothing will be done differently based on whether we are - // disposing vs. finalizing. This is taking advantage of the - // weak ordering between normal finalizable objects & critical - // finalizable objects, which I included in the SafeHandle - // design for FileStream, which would often "just work" when - // finalized. - try { - if (_handle != null && !_handle.IsClosed) { - // Flush data to disk iff we were writing. After - // thinking about this, we also don't need to flush - // our read position, regardless of whether the handle - // was exposed to the user. They probably would NOT - // want us to do this. - if (_writePos > 0) { - FlushWrite(!disposing); - } - } - } - finally { - if (_handle != null && !_handle.IsClosed) - _handle.Dispose(); - - _canRead = false; - _canWrite = false; - _canSeek = false; - // Don't set the buffer to null, to avoid a NullReferenceException - // when users have a race condition in their code (ie, they call - // Close when calling another method on Stream like Read). - //_buffer = null; - base.Dispose(disposing); - } - } - - [System.Security.SecuritySafeCritical] // auto-generated - ~FileStream() - { - if (_handle != null) { - BCLDebug.Correctness(_handle.IsClosed, "You didn't close a FileStream & it got finalized. Name: \""+_fileName+"\""); - Dispose(false); - } - } - - public override void Flush() - { - Flush(false); - } - - [System.Security.SecuritySafeCritical] - public virtual void Flush(Boolean flushToDisk) - { - // This code is duplicated in Dispose - if (_handle.IsClosed) __Error.FileNotOpen(); - - FlushInternalBuffer(); - - if (flushToDisk && CanWrite) - { - FlushOSBuffer(); - } - } - - private void FlushInternalBuffer() - { - if (_writePos > 0) - { - FlushWrite(false); - } - else if (_readPos < _readLen && CanSeek) - { - FlushRead(); - } - } - - [System.Security.SecuritySafeCritical] - private void FlushOSBuffer() - { - if (!Win32Native.FlushFileBuffers(_handle)) - { - __Error.WinIOError(); - } - } - - // Reading is done by blocks from the file, but someone could read - // 1 byte from the buffer then write. At that point, the OS's file - // pointer is out of sync with the stream's position. All write - // functions should call this function to preserve the position in the file. - private void FlushRead() { - Contract.Assert(_writePos == 0, "FileStream: Write buffer must be empty in FlushRead!"); - if (_readPos - _readLen != 0) { - Contract.Assert(CanSeek, "FileStream will lose buffered read data now."); - SeekCore(_readPos - _readLen, SeekOrigin.Current); - } - _readPos = 0; - _readLen = 0; - } - - // Writes are buffered. Anytime the buffer fills up - // (_writePos + delta > _bufferSize) or the buffer switches to reading - // and there is left over data (_writePos > 0), this function must be called. - private void FlushWrite(bool calledFromFinalizer) { - Contract.Assert(_readPos == 0 && _readLen == 0, "FileStream: Read buffer must be empty in FlushWrite!"); - - if (_isAsync) { - IAsyncResult asyncResult = BeginWriteCore(_buffer, 0, _writePos, null, null); - // With our Whidbey async IO & overlapped support for AD unloads, - // we don't strictly need to block here to release resources - // since that support takes care of the pinning & freeing the - // overlapped struct. We need to do this when called from - // Close so that the handle is closed when Close returns, but - // we do't need to call EndWrite from the finalizer. - // Additionally, if we do call EndWrite, we block forever - // because AD unloads prevent us from running the managed - // callback from the IO completion port. Blocking here when - // called from the finalizer during AD unload is clearly wrong, - // but we can't use any sort of test for whether the AD is - // unloading because if we weren't unloading, an AD unload - // could happen on a separate thread before we call EndWrite. - if (!calledFromFinalizer) - EndWrite(asyncResult); - } - else - WriteCore(_buffer, 0, _writePos); - - _writePos = 0; - } - - - [Obsolete("This property has been deprecated. Please use FileStream's SafeFileHandle property instead. http://go.microsoft.com/fwlink/?linkid=14202")] - public virtual IntPtr Handle { - [System.Security.SecurityCritical] // auto-generated_required -#if !FEATURE_CORECLR - [SecurityPermissionAttribute(SecurityAction.InheritanceDemand, Flags=SecurityPermissionFlag.UnmanagedCode)] -#endif - get { - Flush(); - // Explicitly dump any buffered data, since the user could move our - // position or write to the file. - _readPos = 0; - _readLen = 0; - _writePos = 0; - _exposedHandle = true; - - return _handle.DangerousGetHandle(); - } - } - - public virtual SafeFileHandle SafeFileHandle { - [System.Security.SecurityCritical] // auto-generated_required -#if !FEATURE_CORECLR - [SecurityPermissionAttribute(SecurityAction.InheritanceDemand, Flags=SecurityPermissionFlag.UnmanagedCode)] -#endif - get { - Flush(); - // Explicitly dump any buffered data, since the user could move our - // position or write to the file. - _readPos = 0; - _readLen = 0; - _writePos = 0; - _exposedHandle = true; - - return _handle; - } - } - - [System.Security.SecuritySafeCritical] // auto-generated - public override void SetLength(long value) - { - if (value < 0) - throw new ArgumentOutOfRangeException(nameof(value), Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); - Contract.EndContractBlock(); - - if (_handle.IsClosed) __Error.FileNotOpen(); - if (!CanSeek) __Error.SeekNotSupported(); - if (!CanWrite) __Error.WriteNotSupported(); - - // Handle buffering updates. - if (_writePos > 0) { - FlushWrite(false); - } - else if (_readPos < _readLen) { - FlushRead(); - } - _readPos = 0; - _readLen = 0; - - if (_appendStart != -1 && value < _appendStart) - throw new IOException(Environment.GetResourceString("IO.IO_SetLengthAppendTruncate")); - SetLengthCore(value); - } - - // We absolutely need this method broken out so that BeginWriteCore can call - // a method without having to go through buffering code that might call - // FlushWrite. - [System.Security.SecuritySafeCritical] // auto-generated - private void SetLengthCore(long value) - { - Contract.Assert(value >= 0, "value >= 0"); - long origPos = _pos; - - if (_exposedHandle) - VerifyOSHandlePosition(); - if (_pos != value) - SeekCore(value, SeekOrigin.Begin); - if (!Win32Native.SetEndOfFile(_handle)) { - int hr = Marshal.GetLastWin32Error(); - if (hr==__Error.ERROR_INVALID_PARAMETER) - throw new ArgumentOutOfRangeException(nameof(value), Environment.GetResourceString("ArgumentOutOfRange_FileLengthTooBig")); - __Error.WinIOError(hr, String.Empty); - } - // Return file pointer to where it was before setting length - if (origPos != value) { - if (origPos < value) - SeekCore(origPos, SeekOrigin.Begin); - else - SeekCore(0, SeekOrigin.End); - } - } - - [System.Security.SecuritySafeCritical] // auto-generated - public override int Read([In, Out] byte[] array, int offset, int count) { - if (array==null) - throw new ArgumentNullException(nameof(array), Environment.GetResourceString("ArgumentNull_Buffer")); - if (offset < 0) - throw new ArgumentOutOfRangeException(nameof(offset), Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); - if (count < 0) - throw new ArgumentOutOfRangeException(nameof(count), Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); - if (array.Length - offset < count) - throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); - Contract.EndContractBlock(); - - if (_handle.IsClosed) __Error.FileNotOpen(); - - Contract.Assert((_readPos==0 && _readLen==0 && _writePos >= 0) || (_writePos==0 && _readPos <= _readLen), "We're either reading or writing, but not both."); - - bool isBlocked = false; - int n = _readLen - _readPos; - // if the read buffer is empty, read into either user's array or our - // buffer, depending on number of bytes user asked for and buffer size. - if (n == 0) { - if (!CanRead) __Error.ReadNotSupported(); - if (_writePos > 0) FlushWrite(false); - if (!CanSeek || (count >= _bufferSize)) { - n = ReadCore(array, offset, count); - // Throw away read buffer. - _readPos = 0; - _readLen = 0; - return n; - } - if (_buffer == null) _buffer = new byte[_bufferSize]; - n = ReadCore(_buffer, 0, _bufferSize); - if (n == 0) return 0; - isBlocked = n < _bufferSize; - _readPos = 0; - _readLen = n; - } - // Now copy min of count or numBytesAvailable (ie, near EOF) to array. - if (n > count) n = count; - Buffer.InternalBlockCopy(_buffer, _readPos, array, offset, n); - _readPos += n; - - // We may have read less than the number of bytes the user asked - // for, but that is part of the Stream contract. Reading again for - // more data may cause us to block if we're using a device with - // no clear end of file, such as a serial port or pipe. If we - // blocked here & this code was used with redirected pipes for a - // process's standard output, this can lead to deadlocks involving - // two processes. But leave this here for files to avoid what would - // probably be a breaking change. -- - - // If we are reading from a device with no clear EOF like a - // serial port or a pipe, this will cause us to block incorrectly. - if (!_isPipe) { - // If we hit the end of the buffer and didn't have enough bytes, we must - // read some more from the underlying stream. However, if we got - // fewer bytes from the underlying stream than we asked for (ie, we're - // probably blocked), don't ask for more bytes. - if (n < count && !isBlocked) { - Contract.Assert(_readPos == _readLen, "Read buffer should be empty!"); - int moreBytesRead = ReadCore(array, offset + n, count - n); - n += moreBytesRead; - // We've just made our buffer inconsistent with our position - // pointer. We must throw away the read buffer. - _readPos = 0; - _readLen = 0; - } - } - - return n; - } - - [System.Security.SecuritySafeCritical] // auto-generated - private unsafe int ReadCore(byte[] buffer, int offset, int count) { - Contract.Assert(!_handle.IsClosed, "!_handle.IsClosed"); - Contract.Assert(CanRead, "CanRead"); - - Contract.Assert(buffer != null, "buffer != null"); - Contract.Assert(_writePos == 0, "_writePos == 0"); - Contract.Assert(offset >= 0, "offset is negative"); - Contract.Assert(count >= 0, "count is negative"); - - if (_isAsync) { - IAsyncResult result = BeginReadCore(buffer, offset, count, null, null, 0); - return EndRead(result); - } - - // Make sure we are reading from the right spot - if (_exposedHandle) - VerifyOSHandlePosition(); - - int hr = 0; - int r = ReadFileNative(_handle, buffer, offset, count, null, out hr); - if (r == -1) { - // For pipes, ERROR_BROKEN_PIPE is the normal end of the pipe. - if (hr == ERROR_BROKEN_PIPE) { - r = 0; - } - else { - if (hr == ERROR_INVALID_PARAMETER) - throw new ArgumentException(Environment.GetResourceString("Arg_HandleNotSync")); - - __Error.WinIOError(hr, String.Empty); - } - } - Contract.Assert(r >= 0, "FileStream's ReadCore is likely broken."); - _pos += r; - - return r; - } - - [System.Security.SecuritySafeCritical] // auto-generated - public override long Seek(long offset, SeekOrigin origin) { - if (origin<SeekOrigin.Begin || origin>SeekOrigin.End) - throw new ArgumentException(Environment.GetResourceString("Argument_InvalidSeekOrigin")); - Contract.EndContractBlock(); - if (_handle.IsClosed) __Error.FileNotOpen(); - if (!CanSeek) __Error.SeekNotSupported(); - - Contract.Assert((_readPos==0 && _readLen==0 && _writePos >= 0) || (_writePos==0 && _readPos <= _readLen), "We're either reading or writing, but not both."); - - // If we've got bytes in our buffer to write, write them out. - // If we've read in and consumed some bytes, we'll have to adjust - // our seek positions ONLY IF we're seeking relative to the current - // position in the stream. This simulates doing a seek to the new - // position, then a read for the number of bytes we have in our buffer. - if (_writePos > 0) { - FlushWrite(false); - } - else if (origin == SeekOrigin.Current) { - // Don't call FlushRead here, which would have caused an infinite - // loop. Simply adjust the seek origin. This isn't necessary - // if we're seeking relative to the beginning or end of the stream. - offset -= (_readLen - _readPos); - } - - // Verify that internal position is in sync with the handle - if (_exposedHandle) - VerifyOSHandlePosition(); - - long oldPos = _pos + (_readPos - _readLen); - long pos = SeekCore(offset, origin); - - // Prevent users from overwriting data in a file that was opened in - // append mode. - if (_appendStart != -1 && pos < _appendStart) { - SeekCore(oldPos, SeekOrigin.Begin); - throw new IOException(Environment.GetResourceString("IO.IO_SeekAppendOverwrite")); - } - - // We now must update the read buffer. We can in some cases simply - // update _readPos within the buffer, copy around the buffer so our - // Position property is still correct, and avoid having to do more - // reads from the disk. Otherwise, discard the buffer's contents. - if (_readLen > 0) { - // We can optimize the following condition: - // oldPos - _readPos <= pos < oldPos + _readLen - _readPos - if (oldPos == pos) { - if (_readPos > 0) { - //Console.WriteLine("Seek: seeked for 0, adjusting buffer back by: "+_readPos+" _readLen: "+_readLen); - Buffer.InternalBlockCopy(_buffer, _readPos, _buffer, 0, _readLen - _readPos); - _readLen -= _readPos; - _readPos = 0; - } - // If we still have buffered data, we must update the stream's - // position so our Position property is correct. - if (_readLen > 0) - SeekCore(_readLen, SeekOrigin.Current); - } - else if (oldPos - _readPos < pos && pos < oldPos + _readLen - _readPos) { - int diff = (int)(pos - oldPos); - //Console.WriteLine("Seek: diff was "+diff+", readpos was "+_readPos+" adjusting buffer - shrinking by "+ (_readPos + diff)); - Buffer.InternalBlockCopy(_buffer, _readPos+diff, _buffer, 0, _readLen - (_readPos + diff)); - _readLen -= (_readPos + diff); - _readPos = 0; - if (_readLen > 0) - SeekCore(_readLen, SeekOrigin.Current); - } - else { - // Lose the read buffer. - _readPos = 0; - _readLen = 0; - } - Contract.Assert(_readLen >= 0 && _readPos <= _readLen, "_readLen should be nonnegative, and _readPos should be less than or equal _readLen"); - Contract.Assert(pos == Position, "Seek optimization: pos != Position! Buffer math was mangled."); - } - return pos; - } - - // This doesn't do argument checking. Necessary for SetLength, which must - // set the file pointer beyond the end of the file. This will update the - // internal position - [System.Security.SecuritySafeCritical] // auto-generated - private long SeekCore(long offset, SeekOrigin origin) { - Contract.Assert(!_handle.IsClosed && CanSeek, "!_handle.IsClosed && CanSeek"); - Contract.Assert(origin>=SeekOrigin.Begin && origin<=SeekOrigin.End, "origin>=SeekOrigin.Begin && origin<=SeekOrigin.End"); - int hr = 0; - long ret = 0; - - ret = Win32Native.SetFilePointer(_handle, offset, origin, out hr); - if (ret == -1) { - // #errorInvalidHandle - // If ERROR_INVALID_HANDLE is returned, it doesn't suffice to set - // the handle as invalid; the handle must also be closed. - // - // Marking the handle as invalid but not closing the handle - // resulted in exceptions during finalization and locked column - // values (due to invalid but unclosed handle) in SQL FileStream - // scenarios. - // - // A more mainstream scenario involves accessing a file on a - // network share. ERROR_INVALID_HANDLE may occur because the network - // connection was dropped and the server closed the handle. However, - // the client side handle is still open and even valid for certain - // operations. - // - // Note that Dispose doesn't throw so we don't need to special case. - // SetHandleAsInvalid only sets _closed field to true (without - // actually closing handle) so we don't need to call that as well. - if (hr == Win32Native.ERROR_INVALID_HANDLE) - _handle.Dispose(); - __Error.WinIOError(hr, String.Empty); - } - - _pos = ret; - return ret; - } - - // Checks the position of the OS's handle equals what we expect it to. - // This will fail if someone else moved the FileStream's handle or if - // we've hit a bug in FileStream's position updating code. - private void VerifyOSHandlePosition() - { - if (!CanSeek) - return; - - // SeekCore will override the current _pos, so save it now - long oldPos = _pos; - long curPos = SeekCore(0, SeekOrigin.Current); - - if (curPos != oldPos) { - // For reads, this is non-fatal but we still could have returned corrupted - // data in some cases. So discard the internal buffer. Potential MDA - _readPos = 0; - _readLen = 0; - if(_writePos > 0) { - // Discard the buffer and let the user know! - _writePos = 0; - throw new IOException(Environment.GetResourceString("IO.IO_FileStreamHandlePosition")); - } - } - } - - [System.Security.SecuritySafeCritical] // auto-generated - public override void Write(byte[] array, int offset, int count) { - if (array==null) - throw new ArgumentNullException(nameof(array), Environment.GetResourceString("ArgumentNull_Buffer")); - if (offset < 0) - throw new ArgumentOutOfRangeException(nameof(offset), Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); - if (count < 0) - throw new ArgumentOutOfRangeException(nameof(count), Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); - if (array.Length - offset < count) - throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); - Contract.EndContractBlock(); - - if (_handle.IsClosed) __Error.FileNotOpen(); - - if (_writePos == 0) - { - // Ensure we can write to the stream, and ready buffer for writing. - if (!CanWrite) __Error.WriteNotSupported(); - if (_readPos < _readLen) FlushRead(); - _readPos = 0; - _readLen = 0; - } - - // If our buffer has data in it, copy data from the user's array into - // the buffer, and if we can fit it all there, return. Otherwise, write - // the buffer to disk and copy any remaining data into our buffer. - // The assumption here is memcpy is cheaper than disk (or net) IO. - // (10 milliseconds to disk vs. ~20-30 microseconds for a 4K memcpy) - // So the extra copying will reduce the total number of writes, in - // non-pathological cases (ie, write 1 byte, then write for the buffer - // size repeatedly) - if (_writePos > 0) { - int numBytes = _bufferSize - _writePos; // space left in buffer - if (numBytes > 0) { - if (numBytes > count) - numBytes = count; - Buffer.InternalBlockCopy(array, offset, _buffer, _writePos, numBytes); - _writePos += numBytes; - if (count==numBytes) return; - offset += numBytes; - count -= numBytes; - } - // Reset our buffer. We essentially want to call FlushWrite - // without calling Flush on the underlying Stream. - - if (_isAsync) { - IAsyncResult result = BeginWriteCore(_buffer, 0, _writePos, null, null); - EndWrite(result); - } - else - { - WriteCore(_buffer, 0, _writePos); - } - - _writePos = 0; - } - // If the buffer would slow writes down, avoid buffer completely. - if (count >= _bufferSize) { - Contract.Assert(_writePos == 0, "FileStream cannot have buffered data to write here! Your stream will be corrupted."); - WriteCore(array, offset, count); - return; - } - else if (count == 0) - return; // Don't allocate a buffer then call memcpy for 0 bytes. - if (_buffer==null) _buffer = new byte[_bufferSize]; - // Copy remaining bytes into buffer, to write at a later date. - Buffer.InternalBlockCopy(array, offset, _buffer, _writePos, count); - _writePos = count; - return; - } - - [System.Security.SecuritySafeCritical] // auto-generated - private unsafe void WriteCore(byte[] buffer, int offset, int count) { - Contract.Assert(!_handle.IsClosed, "!_handle.IsClosed"); - Contract.Assert(CanWrite, "CanWrite"); - - Contract.Assert(buffer != null, "buffer != null"); - Contract.Assert(_readPos == _readLen, "_readPos == _readLen"); - Contract.Assert(offset >= 0, "offset is negative"); - Contract.Assert(count >= 0, "count is negative"); - - if (_isAsync) { - IAsyncResult result = BeginWriteCore(buffer, offset, count, null, null); - EndWrite(result); - return; - } - - // Make sure we are writing to the position that we think we are - if (_exposedHandle) - VerifyOSHandlePosition(); - - int hr = 0; - int r = WriteFileNative(_handle, buffer, offset, count, null, out hr); - if (r == -1) { - // For pipes, ERROR_NO_DATA is not an error, but the pipe is closing. - if (hr == ERROR_NO_DATA) { - r = 0; - } - else { - // ERROR_INVALID_PARAMETER may be returned for writes - // where the position is too large (ie, writing at Int64.MaxValue - // on Win9x) OR for synchronous writes to a handle opened - // asynchronously. - if (hr == ERROR_INVALID_PARAMETER) - throw new IOException(Environment.GetResourceString("IO.IO_FileTooLongOrHandleNotSync")); - __Error.WinIOError(hr, String.Empty); - } - } - Contract.Assert(r >= 0, "FileStream's WriteCore is likely broken."); - _pos += r; - return; - } - - - [System.Security.SecuritySafeCritical] // auto-generated - [HostProtection(ExternalThreading = true)] - public override IAsyncResult BeginRead(byte[] array, int offset, int numBytes, AsyncCallback userCallback, Object stateObject) - { - if (array==null) - throw new ArgumentNullException(nameof(array)); - if (offset < 0) - throw new ArgumentOutOfRangeException(nameof(offset), Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); - if (numBytes < 0) - throw new ArgumentOutOfRangeException(nameof(numBytes), Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); - if (array.Length - offset < numBytes) - throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); - Contract.EndContractBlock(); - - if (_handle.IsClosed) __Error.FileNotOpen(); - - if (!_isAsync) - return base.BeginRead(array, offset, numBytes, userCallback, stateObject); - else - return BeginReadAsync(array, offset, numBytes, userCallback, stateObject); - } - - [System.Security.SecuritySafeCritical] // auto-generated - [HostProtection(ExternalThreading = true)] - private FileStreamAsyncResult BeginReadAsync(byte[] array, int offset, int numBytes, AsyncCallback userCallback, Object stateObject) - { - Contract.Assert(_isAsync); - - if (!CanRead) __Error.ReadNotSupported(); - - Contract.Assert((_readPos == 0 && _readLen == 0 && _writePos >= 0) || (_writePos == 0 && _readPos <= _readLen), "We're either reading or writing, but not both."); - - if (_isPipe) - { - // When redirecting stdout & stderr with the Process class, it's easy to deadlock your - // parent & child processes when doing writes 4K at a time. The - // OS appears to use a 4K buffer internally. If you write to a - // pipe that is full, you will block until someone read from - // that pipe. If you try reading from an empty pipe and - // FileStream's BeginRead blocks waiting for data to fill it's - // internal buffer, you will be blocked. In a case where a child - // process writes to stdout & stderr while a parent process tries - // reading from both, you can easily get into a deadlock here. - // To avoid this deadlock, don't buffer when doing async IO on - // pipes. But don't completely ignore buffered data either. - if (_readPos < _readLen) - { - int n = _readLen - _readPos; - if (n > numBytes) n = numBytes; - Buffer.InternalBlockCopy(_buffer, _readPos, array, offset, n); - _readPos += n; - - // Return a synchronous FileStreamAsyncResult - return FileStreamAsyncResult.CreateBufferedReadResult(n, userCallback, stateObject, false); - } - else - { - Contract.Assert(_writePos == 0, "FileStream must not have buffered write data here! Pipes should be unidirectional."); - return BeginReadCore(array, offset, numBytes, userCallback, stateObject, 0); - } - } - - Contract.Assert(!_isPipe, "Should not be a pipe."); - - // Handle buffering. - if (_writePos > 0) FlushWrite(false); - if (_readPos == _readLen) - { - // I can't see how to handle buffering of async requests when - // filling the buffer asynchronously, without a lot of complexity. - // The problems I see are issuing an async read, we do an async - // read to fill the buffer, then someone issues another read - // (either synchronously or asynchronously) before the first one - // returns. This would involve some sort of complex buffer locking - // that we probably don't want to get into, at least not in V1. - // If we did a sync read to fill the buffer, we could avoid the - // problem, and any async read less than 64K gets turned into a - // synchronous read by NT anyways... -- - - if (numBytes < _bufferSize) - { - if (_buffer == null) _buffer = new byte[_bufferSize]; - IAsyncResult bufferRead = BeginReadCore(_buffer, 0, _bufferSize, null, null, 0); - _readLen = EndRead(bufferRead); - int n = _readLen; - if (n > numBytes) n = numBytes; - Buffer.InternalBlockCopy(_buffer, 0, array, offset, n); - _readPos = n; - - // Return a synchronous FileStreamAsyncResult - return FileStreamAsyncResult.CreateBufferedReadResult(n, userCallback, stateObject, false); - } - else - { - // Here we're making our position pointer inconsistent - // with our read buffer. Throw away the read buffer's contents. - _readPos = 0; - _readLen = 0; - return BeginReadCore(array, offset, numBytes, userCallback, stateObject, 0); - } - } - else - { - int n = _readLen - _readPos; - if (n > numBytes) n = numBytes; - Buffer.InternalBlockCopy(_buffer, _readPos, array, offset, n); - _readPos += n; - - if (n >= numBytes) - { - // Return a synchronous FileStreamAsyncResult - return FileStreamAsyncResult.CreateBufferedReadResult(n, userCallback, stateObject, false); - } - else - { - // For streams with no clear EOF like serial ports or pipes - // we cannot read more data without causing an app to block - // incorrectly. Pipes don't go down this path - // though. This code needs to be fixed. - // Throw away read buffer. - _readPos = 0; - _readLen = 0; - return BeginReadCore(array, offset + n, numBytes - n, userCallback, stateObject, n); - } - // WARNING: all state on asyncResult objects must be set before - // we call ReadFile in BeginReadCore, since the OS can run our - // callback & the user's callback before ReadFile returns. - } - } - - [System.Security.SecuritySafeCritical] // auto-generated - unsafe private FileStreamAsyncResult BeginReadCore(byte[] bytes, int offset, int numBytes, AsyncCallback userCallback, Object stateObject, int numBufferedBytesRead) - { - Contract.Assert(!_handle.IsClosed, "!_handle.IsClosed"); - Contract.Assert(CanRead, "CanRead"); - Contract.Assert(bytes != null, "bytes != null"); - Contract.Assert(_writePos == 0, "_writePos == 0"); - Contract.Assert(_isAsync, "BeginReadCore doesn't work on synchronous file streams!"); - Contract.Assert(offset >= 0, "offset is negative"); - Contract.Assert(numBytes >= 0, "numBytes is negative"); - - // Create and store async stream class library specific data in the async result - - // Must pass in _numBufferedBytes here to ensure all the state on the IAsyncResult - // object is set before we call ReadFile, which gives the OS an - // opportunity to run our callback (including the user callback & - // the call to EndRead) before ReadFile has returned. - FileStreamAsyncResult asyncResult = new FileStreamAsyncResult(numBufferedBytesRead, bytes, _handle, userCallback, stateObject, false); - NativeOverlapped* intOverlapped = asyncResult.OverLapped; - - // Calculate position in the file we should be at after the read is done - if (CanSeek) { - long len = Length; - - // Make sure we are reading from the position that we think we are - if (_exposedHandle) - VerifyOSHandlePosition(); - - if (_pos + numBytes > len) { - if (_pos <= len) - numBytes = (int) (len - _pos); - else - numBytes = 0; - } - - // Now set the position to read from in the NativeOverlapped struct - // For pipes, we should leave the offset fields set to 0. - intOverlapped->OffsetLow = unchecked((int)_pos); - intOverlapped->OffsetHigh = (int)(_pos>>32); - - // When using overlapped IO, the OS is not supposed to - // touch the file pointer location at all. We will adjust it - // ourselves. This isn't threadsafe. - - // WriteFile should not update the file pointer when writing - // in overlapped mode, according to MSDN. But it does update - // the file pointer when writing to a UNC path! - // So changed the code below to seek to an absolute - // location, not a relative one. ReadFile seems consistent though. - SeekCore(numBytes, SeekOrigin.Current); - } - - if (FrameworkEventSource.IsInitialized && FrameworkEventSource.Log.IsEnabled(EventLevel.Informational, FrameworkEventSource.Keywords.ThreadTransfer)) - FrameworkEventSource.Log.ThreadTransferSend((long)(asyncResult.OverLapped), 2, string.Empty, false); - - // queue an async ReadFile operation and pass in a packed overlapped - int hr = 0; - int r = ReadFileNative(_handle, bytes, offset, numBytes, intOverlapped, out hr); - // ReadFile, the OS version, will return 0 on failure. But - // my ReadFileNative wrapper returns -1. My wrapper will return - // the following: - // On error, r==-1. - // On async requests that are still pending, r==-1 w/ hr==ERROR_IO_PENDING - // on async requests that completed sequentially, r==0 - // You will NEVER RELIABLY be able to get the number of bytes - // read back from this call when using overlapped structures! You must - // not pass in a non-null lpNumBytesRead to ReadFile when using - // overlapped structures! This is by design NT behavior. - if (r==-1 && numBytes!=-1) { - - // For pipes, when they hit EOF, they will come here. - if (hr == ERROR_BROKEN_PIPE) { - // Not an error, but EOF. AsyncFSCallback will NOT be - // called. Call the user callback here. - - // We clear the overlapped status bit for this special case. - // Failure to do so looks like we are freeing a pending overlapped later. - intOverlapped->InternalLow = IntPtr.Zero; - asyncResult.CallUserCallback(); - // EndRead will free the Overlapped struct correctly. - } - else if (hr != ERROR_IO_PENDING) { - if (!_handle.IsClosed && CanSeek) // Update Position - It could be anywhere. - SeekCore(0, SeekOrigin.Current); - - if (hr == ERROR_HANDLE_EOF) - __Error.EndOfFile(); - else - __Error.WinIOError(hr, String.Empty); - } - } - else { - // Due to a workaround for a race condition in NT's ReadFile & - // WriteFile routines, we will always be returning 0 from ReadFileNative - // when we do async IO instead of the number of bytes read, - // irregardless of whether the operation completed - // synchronously or asynchronously. We absolutely must not - // set asyncResult._numBytes here, since will never have correct - // results. - //Console.WriteLine("ReadFile returned: "+r+" (0x"+Int32.Format(r, "x")+") The IO completed synchronously, but the user callback was called on a separate thread"); - } - - return asyncResult; - } - - [System.Security.SecuritySafeCritical] // Although the unsafe code is only required in PAL, the block is wide scoped. Leave it here for desktop to ensure it's reviewed. - public unsafe override int EndRead(IAsyncResult asyncResult) - { - // There are 3 significantly different IAsyncResults we'll accept - // here. One is from Stream::BeginRead. The other two are variations - // on our FileStreamAsyncResult. One is from BeginReadCore, - // while the other is from the BeginRead buffering wrapper. - if (asyncResult==null) - throw new ArgumentNullException(nameof(asyncResult)); - Contract.EndContractBlock(); - - if (!_isAsync) - return base.EndRead(asyncResult); - - FileStreamAsyncResult afsar = asyncResult as FileStreamAsyncResult; - if (afsar==null || afsar.IsWrite) - __Error.WrongAsyncResult(); - - // Ensure we don't have any race conditions by doing an interlocked - // CompareExchange here. Avoids corrupting memory via freeing the - // NativeOverlapped class or GCHandle twice. -- - if (1 == Interlocked.CompareExchange(ref afsar._EndXxxCalled, 1, 0)) - __Error.EndReadCalledTwice(); - - // Obtain the WaitHandle, but don't use public property in case we - // delay initialize the manual reset event in the future. - afsar.Wait(); - - // Free memory & GC handles. - afsar.ReleaseNativeResource(); - - // Now check for any error during the read. - if (afsar.ErrorCode != 0) - __Error.WinIOError(afsar.ErrorCode, String.Empty); - - return afsar.NumBytesRead; - } - - // Reads a byte from the file stream. Returns the byte cast to an int - // or -1 if reading from the end of the stream. - [System.Security.SecuritySafeCritical] // auto-generated - public override int ReadByte() { - if (_handle.IsClosed) __Error.FileNotOpen(); - if (_readLen==0 && !CanRead) __Error.ReadNotSupported(); - Contract.Assert((_readPos==0 && _readLen==0 && _writePos >= 0) || (_writePos==0 && _readPos <= _readLen), "We're either reading or writing, but not both."); - if (_readPos == _readLen) { - if (_writePos > 0) FlushWrite(false); - Contract.Assert(_bufferSize > 0, "_bufferSize > 0"); - if (_buffer == null) _buffer = new byte[_bufferSize]; - _readLen = ReadCore(_buffer, 0, _bufferSize); - _readPos = 0; - } - if (_readPos == _readLen) - return -1; - - int result = _buffer[_readPos]; - _readPos++; - return result; - } - - - [System.Security.SecuritySafeCritical] // auto-generated - [HostProtection(ExternalThreading=true)] - public override IAsyncResult BeginWrite(byte[] array, int offset, int numBytes, AsyncCallback userCallback, Object stateObject) - { - if (array==null) - throw new ArgumentNullException(nameof(array)); - if (offset < 0) - throw new ArgumentOutOfRangeException(nameof(offset), Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); - if (numBytes < 0) - throw new ArgumentOutOfRangeException(nameof(numBytes), Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); - if (array.Length - offset < numBytes) - throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); - Contract.EndContractBlock(); - - if (_handle.IsClosed) __Error.FileNotOpen(); - - if (!_isAsync) - return base.BeginWrite(array, offset, numBytes, userCallback, stateObject); - else - return BeginWriteAsync(array, offset, numBytes, userCallback, stateObject); - } - - [System.Security.SecuritySafeCritical] // auto-generated - [HostProtection(ExternalThreading = true)] - private FileStreamAsyncResult BeginWriteAsync(byte[] array, int offset, int numBytes, AsyncCallback userCallback, Object stateObject) - { - Contract.Assert(_isAsync); - - if (!CanWrite) __Error.WriteNotSupported(); - - Contract.Assert((_readPos == 0 && _readLen == 0 && _writePos >= 0) || (_writePos == 0 && _readPos <= _readLen), "We're either reading or writing, but not both."); - - if (_isPipe) - { - // When redirecting stdout & stderr with the Process class, it's easy to deadlock your - // parent & child processes when doing writes 4K at a time. The - // OS appears to use a 4K buffer internally. If you write to a - // pipe that is full, you will block until someone read from - // that pipe. If you try reading from an empty pipe and - // FileStream's BeginRead blocks waiting for data to fill it's - // internal buffer, you will be blocked. In a case where a child - // process writes to stdout & stderr while a parent process tries - // reading from both, you can easily get into a deadlock here. - // To avoid this deadlock, don't buffer when doing async IO on - // pipes. - Contract.Assert(_readPos == 0 && _readLen == 0, "FileStream must not have buffered data here! Pipes should be unidirectional."); - - if (_writePos > 0) - FlushWrite(false); - - return BeginWriteCore(array, offset, numBytes, userCallback, stateObject); - } - - // Handle buffering. - if (_writePos == 0) - { - if (_readPos < _readLen) FlushRead(); - _readPos = 0; - _readLen = 0; - } - - int n = _bufferSize - _writePos; - if (numBytes <= n) - { - if (_writePos == 0) _buffer = new byte[_bufferSize]; - Buffer.InternalBlockCopy(array, offset, _buffer, _writePos, numBytes); - _writePos += numBytes; - - // Return a synchronous FileStreamAsyncResult - return FileStreamAsyncResult.CreateBufferedReadResult(numBytes, userCallback, stateObject, true); - } - - if (_writePos > 0) - FlushWrite(false); - - return BeginWriteCore(array, offset, numBytes, userCallback, stateObject); - } - - [System.Security.SecuritySafeCritical] // auto-generated - unsafe private FileStreamAsyncResult BeginWriteCore(byte[] bytes, int offset, int numBytes, AsyncCallback userCallback, Object stateObject) - { - Contract.Assert(!_handle.IsClosed, "!_handle.IsClosed"); - Contract.Assert(CanWrite, "CanWrite"); - Contract.Assert(bytes != null, "bytes != null"); - Contract.Assert(_readPos == _readLen, "_readPos == _readLen"); - Contract.Assert(_isAsync, "BeginWriteCore doesn't work on synchronous file streams!"); - Contract.Assert(offset >= 0, "offset is negative"); - Contract.Assert(numBytes >= 0, "numBytes is negative"); - - // Create and store async stream class library specific data in the async result - FileStreamAsyncResult asyncResult = new FileStreamAsyncResult(0, bytes, _handle, userCallback, stateObject, true); - NativeOverlapped* intOverlapped = asyncResult.OverLapped; - - if (CanSeek) { - // Make sure we set the length of the file appropriately. - long len = Length; - //Console.WriteLine("BeginWrite - Calculating end pos. pos: "+pos+" len: "+len+" numBytes: "+numBytes); - - // Make sure we are writing to the position that we think we are - if (_exposedHandle) - VerifyOSHandlePosition(); - - if (_pos + numBytes > len) { - //Console.WriteLine("BeginWrite - Setting length to: "+(pos + numBytes)); - SetLengthCore(_pos + numBytes); - } - - // Now set the position to read from in the NativeOverlapped struct - // For pipes, we should leave the offset fields set to 0. - intOverlapped->OffsetLow = (int)_pos; - intOverlapped->OffsetHigh = (int)(_pos>>32); - - // When using overlapped IO, the OS is not supposed to - // touch the file pointer location at all. We will adjust it - // ourselves. This isn't threadsafe. - - SeekCore(numBytes, SeekOrigin.Current); - } - - //Console.WriteLine("BeginWrite finishing. pos: "+pos+" numBytes: "+numBytes+" _pos: "+_pos+" Position: "+Position); - - if (FrameworkEventSource.IsInitialized && FrameworkEventSource.Log.IsEnabled(EventLevel.Informational, FrameworkEventSource.Keywords.ThreadTransfer)) - FrameworkEventSource.Log.ThreadTransferSend((long)(asyncResult.OverLapped), 2, string.Empty, false); - - int hr = 0; - // queue an async WriteFile operation and pass in a packed overlapped - int r = WriteFileNative(_handle, bytes, offset, numBytes, intOverlapped, out hr); - - // WriteFile, the OS version, will return 0 on failure. But - // my WriteFileNative wrapper returns -1. My wrapper will return - // the following: - // On error, r==-1. - // On async requests that are still pending, r==-1 w/ hr==ERROR_IO_PENDING - // On async requests that completed sequentially, r==0 - // You will NEVER RELIABLY be able to get the number of bytes - // written back from this call when using overlapped IO! You must - // not pass in a non-null lpNumBytesWritten to WriteFile when using - // overlapped structures! This is ByDesign NT behavior. - if (r==-1 && numBytes!=-1) { - //Console.WriteLine("WriteFile returned 0; Write will complete asynchronously (if hr==3e5) hr: 0x{0:x}", hr); - - // For pipes, when they are closed on the other side, they will come here. - if (hr == ERROR_NO_DATA) { - // Not an error, but EOF. AsyncFSCallback will NOT be - // called. Call the user callback here. - asyncResult.CallUserCallback(); - // EndWrite will free the Overlapped struct correctly. - } - else if (hr != ERROR_IO_PENDING) { - if (!_handle.IsClosed && CanSeek) // Update Position - It could be anywhere. - SeekCore(0, SeekOrigin.Current); - - if (hr == ERROR_HANDLE_EOF) - __Error.EndOfFile(); - else - __Error.WinIOError(hr, String.Empty); - } - } - else { - // Due to a workaround for a race condition in NT's ReadFile & - // WriteFile routines, we will always be returning 0 from WriteFileNative - // when we do async IO instead of the number of bytes written, - // irregardless of whether the operation completed - // synchronously or asynchronously. We absolutely must not - // set asyncResult._numBytes here, since will never have correct - // results. - //Console.WriteLine("WriteFile returned: "+r+" (0x"+Int32.Format(r, "x")+") The IO completed synchronously, but the user callback was called on another thread."); - } - - return asyncResult; - } - - [System.Security.SecuritySafeCritical] // Although the unsafe code is only required in PAL, the block is wide scoped. Leave it here for desktop to ensure it's reviewed. - public unsafe override void EndWrite(IAsyncResult asyncResult) - { - if (asyncResult==null) - throw new ArgumentNullException(nameof(asyncResult)); - Contract.EndContractBlock(); - - if (!_isAsync) { - base.EndWrite(asyncResult); - return; - } - - FileStreamAsyncResult afsar = asyncResult as FileStreamAsyncResult; - if (afsar==null || !afsar.IsWrite) - __Error.WrongAsyncResult(); - - // Ensure we can't have any race conditions by doing an interlocked - // CompareExchange here. Avoids corrupting memory via freeing the - // NativeOverlapped class or GCHandle twice. -- - if (1 == Interlocked.CompareExchange(ref afsar._EndXxxCalled, 1, 0)) - __Error.EndWriteCalledTwice(); - - // Obtain the WaitHandle, but don't use public property in case we - // delay initialize the manual reset event in the future. - afsar.Wait(); - - // Free memory & GC handles. - afsar.ReleaseNativeResource(); - - // Now check for any error during the write. - if (afsar.ErrorCode != 0) - __Error.WinIOError(afsar.ErrorCode, String.Empty); - - // Number of bytes written is afsar._numBytes + afsar._numBufferedBytes. - return; - } - - [System.Security.SecuritySafeCritical] // auto-generated - public override void WriteByte(byte value) - { - if (_handle.IsClosed) __Error.FileNotOpen(); - if (_writePos==0) { - if (!CanWrite) __Error.WriteNotSupported(); - if (_readPos < _readLen) FlushRead(); - _readPos = 0; - _readLen = 0; - Contract.Assert(_bufferSize > 0, "_bufferSize > 0"); - if (_buffer==null) _buffer = new byte[_bufferSize]; - } - if (_writePos == _bufferSize) - FlushWrite(false); - - _buffer[_writePos] = value; - _writePos++; - } - - [System.Security.SecuritySafeCritical] // auto-generated - public virtual void Lock(long position, long length) { - if (position < 0 || length < 0) - throw new ArgumentOutOfRangeException((position < 0 ? nameof(position) : nameof(length)), Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); - Contract.EndContractBlock(); - if (_handle.IsClosed) __Error.FileNotOpen(); - - int positionLow = unchecked((int)(position )); - int positionHigh = unchecked((int)(position >> 32)); - int lengthLow = unchecked((int)(length )); - int lengthHigh = unchecked((int)(length >> 32)); - - if (!Win32Native.LockFile(_handle, positionLow, positionHigh, lengthLow, lengthHigh)) - __Error.WinIOError(); - } - - [System.Security.SecuritySafeCritical] // auto-generated - public virtual void Unlock(long position, long length) { - if (position < 0 || length < 0) - throw new ArgumentOutOfRangeException((position < 0 ? nameof(position) : nameof(length)), Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); - Contract.EndContractBlock(); - if (_handle.IsClosed) __Error.FileNotOpen(); - - int positionLow = unchecked((int)(position )); - int positionHigh = unchecked((int)(position >> 32)); - int lengthLow = unchecked((int)(length )); - int lengthHigh = unchecked((int)(length >> 32)); - - if (!Win32Native.UnlockFile(_handle, positionLow, positionHigh, lengthLow, lengthHigh)) - __Error.WinIOError(); - } - - // Windows API definitions, from winbase.h and others - - private const int FILE_ATTRIBUTE_NORMAL = 0x00000080; - private const int FILE_ATTRIBUTE_ENCRYPTED = 0x00004000; - private const int FILE_FLAG_OVERLAPPED = 0x40000000; - internal const int GENERIC_READ = unchecked((int)0x80000000); - private const int GENERIC_WRITE = 0x40000000; - - private const int FILE_BEGIN = 0; - private const int FILE_CURRENT = 1; - private const int FILE_END = 2; - - // Error codes (not HRESULTS), from winerror.h - internal const int ERROR_BROKEN_PIPE = 109; - internal const int ERROR_NO_DATA = 232; - private const int ERROR_HANDLE_EOF = 38; - private const int ERROR_INVALID_PARAMETER = 87; - private const int ERROR_IO_PENDING = 997; - - - // __ConsoleStream also uses this code. - [System.Security.SecurityCritical] // auto-generated - private unsafe int ReadFileNative(SafeFileHandle handle, byte[] bytes, int offset, int count, NativeOverlapped* overlapped, out int hr) - { - Contract.Requires(handle != null, "handle != null"); - Contract.Requires(offset >= 0, "offset >= 0"); - Contract.Requires(count >= 0, "count >= 0"); - Contract.Requires(bytes != null, "bytes != null"); - // Don't corrupt memory when multiple threads are erroneously writing - // to this stream simultaneously. - if (bytes.Length - offset < count) - throw new IndexOutOfRangeException(Environment.GetResourceString("IndexOutOfRange_IORaceCondition")); - Contract.EndContractBlock(); - - Contract.Assert((_isAsync && overlapped != null) || (!_isAsync && overlapped == null), "Async IO parameter mismatch in call to ReadFileNative."); - - // You can't use the fixed statement on an array of length 0. - if (bytes.Length==0) { - hr = 0; - return 0; - } - - int r = 0; - int numBytesRead = 0; - - fixed(byte* p = bytes) { - if (_isAsync) - r = Win32Native.ReadFile(handle, p + offset, count, IntPtr.Zero, overlapped); - else - r = Win32Native.ReadFile(handle, p + offset, count, out numBytesRead, IntPtr.Zero); - } - - if (r==0) { - hr = Marshal.GetLastWin32Error(); - // We should never silently drop an error here without some - // extra work. We must make sure that BeginReadCore won't return an - // IAsyncResult that will cause EndRead to block, since the OS won't - // call AsyncFSCallback for us. - if (hr == ERROR_BROKEN_PIPE || hr == Win32Native.ERROR_PIPE_NOT_CONNECTED) { - // This handle was a pipe, and it's done. Not an error, but EOF. - // However, the OS will not call AsyncFSCallback! - // Let the caller handle this, since BeginReadCore & ReadCore - // need to do different things. - return -1; - } - - // See code:#errorInvalidHandle in "private long SeekCore(long offset, SeekOrigin origin)". - if (hr == Win32Native.ERROR_INVALID_HANDLE) - _handle.Dispose(); - - return -1; - } - else - hr = 0; - return numBytesRead; - } - - [System.Security.SecurityCritical] // auto-generated - private unsafe int WriteFileNative(SafeFileHandle handle, byte[] bytes, int offset, int count, NativeOverlapped* overlapped, out int hr) { - Contract.Requires(handle != null, "handle != null"); - Contract.Requires(offset >= 0, "offset >= 0"); - Contract.Requires(count >= 0, "count >= 0"); - Contract.Requires(bytes != null, "bytes != null"); - // Don't corrupt memory when multiple threads are erroneously writing - // to this stream simultaneously. (the OS is reading from - // the array we pass to WriteFile, but if we read beyond the end and - // that memory isn't allocated, we could get an AV.) - if (bytes.Length - offset < count) - throw new IndexOutOfRangeException(Environment.GetResourceString("IndexOutOfRange_IORaceCondition")); - Contract.EndContractBlock(); - - Contract.Assert((_isAsync && overlapped != null) || (!_isAsync && overlapped == null), "Async IO parameter missmatch in call to WriteFileNative."); - - // You can't use the fixed statement on an array of length 0. - if (bytes.Length==0) { - hr = 0; - return 0; - } - - int numBytesWritten = 0; - int r = 0; - - fixed(byte* p = bytes) { - if (_isAsync) - r = Win32Native.WriteFile(handle, p + offset, count, IntPtr.Zero, overlapped); - else - r = Win32Native.WriteFile(handle, p + offset, count, out numBytesWritten, IntPtr.Zero); - } - - if (r==0) { - hr = Marshal.GetLastWin32Error(); - // We should never silently drop an error here without some - // extra work. We must make sure that BeginWriteCore won't return an - // IAsyncResult that will cause EndWrite to block, since the OS won't - // call AsyncFSCallback for us. - - if (hr==ERROR_NO_DATA) { - // This handle was a pipe, and the pipe is being closed on the - // other side. Let the caller handle this, since BeginWriteCore - // & WriteCore need to do different things. - return -1; - } - - // See code:#errorInvalidHandle in "private long SeekCore(long offset, SeekOrigin origin)". - if (hr == Win32Native.ERROR_INVALID_HANDLE) - _handle.Dispose(); - - return -1; - } - else - hr = 0; - return numBytesWritten; - } - - - [HostProtection(ExternalThreading = true)] - [ComVisible(false)] - [SecuritySafeCritical] - public override Task<int> ReadAsync(Byte[] buffer, int offset, int count, CancellationToken cancellationToken) - { - if (buffer == null) - throw new ArgumentNullException(nameof(buffer)); - if (offset < 0) - throw new ArgumentOutOfRangeException(nameof(offset), Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); - if (count < 0) - throw new ArgumentOutOfRangeException(nameof(count), Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); - if (buffer.Length - offset < count) - throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); - Contract.EndContractBlock(); - - // If we have been inherited into a subclass, the following implementation could be incorrect - // since it does not call through to Read() or BeginRead() which a subclass might have overriden. - // To be safe we will only use this implementation in cases where we know it is safe to do so, - // and delegate to our base class (which will call into Read/BeginRead) when we are not sure. - if (this.GetType() != typeof(FileStream)) - return base.ReadAsync(buffer, offset, count, cancellationToken); - - if (cancellationToken.IsCancellationRequested) - return Task.FromCanceled<int>(cancellationToken); - - if (_handle.IsClosed) - __Error.FileNotOpen(); - - // If async IO is not supported on this platform or - // if this FileStream was not opened with FileOptions.Asynchronous. - if (!_isAsync) - return base.ReadAsync(buffer, offset, count, cancellationToken); - - var readTask = new FileStreamReadWriteTask<int>(cancellationToken); - var endReadTask = s_endReadTask; - if (endReadTask == null) s_endReadTask = endReadTask = EndReadTask; // benign initialization race condition - readTask._asyncResult = BeginReadAsync(buffer, offset, count, endReadTask, readTask); - - if (readTask._asyncResult.IsAsync && cancellationToken.CanBeCanceled) - { - var cancelReadHandler = s_cancelReadHandler; - if (cancelReadHandler == null) s_cancelReadHandler = cancelReadHandler = CancelTask<int>; // benign initialization race condition - readTask._registration = cancellationToken.Register(cancelReadHandler, readTask); - - // In case the task is completed right before we register the cancellation callback. - if (readTask._asyncResult.IsCompleted) - readTask._registration.Dispose(); - } - - return readTask; - } - - [HostProtection(ExternalThreading = true)] - [ComVisible(false)] - [SecuritySafeCritical] - public override Task WriteAsync(Byte[] buffer, int offset, int count, CancellationToken cancellationToken) - { - if (buffer == null) - throw new ArgumentNullException(nameof(buffer)); - if (offset < 0) - throw new ArgumentOutOfRangeException(nameof(offset), Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); - if (count < 0) - throw new ArgumentOutOfRangeException(nameof(count), Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); - if (buffer.Length - offset < count) - throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); - Contract.EndContractBlock(); - - // If we have been inherited into a subclass, the following implementation could be incorrect - // since it does not call through to Write() or BeginWrite() which a subclass might have overriden. - // To be safe we will only use this implementation in cases where we know it is safe to do so, - // and delegate to our base class (which will call into Write/BeginWrite) when we are not sure. - if (this.GetType() != typeof(FileStream)) - return base.WriteAsync(buffer, offset, count, cancellationToken); - - if (cancellationToken.IsCancellationRequested) - return Task.FromCanceled(cancellationToken); - - if (_handle.IsClosed) - __Error.FileNotOpen(); - - // If async IO is not supported on this platform or - // if this FileStream was not opened with FileOptions.Asynchronous. - if (!_isAsync) - return base.WriteAsync(buffer, offset, count, cancellationToken); - - var writeTask = new FileStreamReadWriteTask<VoidTaskResult>(cancellationToken); - var endWriteTask = s_endWriteTask; - if (endWriteTask == null) s_endWriteTask = endWriteTask = EndWriteTask; // benign initialization race condition - writeTask._asyncResult = BeginWriteAsync(buffer, offset, count, endWriteTask, writeTask); - - if (writeTask._asyncResult.IsAsync && cancellationToken.CanBeCanceled) - { - var cancelWriteHandler = s_cancelWriteHandler; - if (cancelWriteHandler == null) s_cancelWriteHandler = cancelWriteHandler = CancelTask<VoidTaskResult>; // benign initialization race condition - writeTask._registration = cancellationToken.Register(cancelWriteHandler, writeTask); - - // In case the task is completed right before we register the cancellation callback. - if (writeTask._asyncResult.IsCompleted) - writeTask._registration.Dispose(); - } - - return writeTask; - } - - // The task instance returned from ReadAsync and WriteAsync. - // Also stores all of the state necessary for those calls to avoid closures and extraneous delegate allocations. - private sealed class FileStreamReadWriteTask<T> : Task<T> - { - internal CancellationToken _cancellationToken; - internal CancellationTokenRegistration _registration; - internal FileStreamAsyncResult _asyncResult; // initialized after Begin call completes - - internal FileStreamReadWriteTask(CancellationToken cancellationToken) : base() - { - _cancellationToken = cancellationToken; - } - } - - // Cancellation callback for both ReadAsync and WriteAsync. - [SecuritySafeCritical] - private static void CancelTask<T>(object state) - { - var task = state as FileStreamReadWriteTask<T>; - Contract.Assert(task != null); - FileStreamAsyncResult asyncResult = task._asyncResult; - - // This method is used as both the completion callback and the cancellation callback. - // We should try to cancel the operation if this is running as the completion callback - // or if cancellation is not applicable: - // 1. asyncResult is not a FileStreamAsyncResult - // 2. asyncResult.IsAsync is false: asyncResult is a "synchronous" FileStreamAsyncResult. - // 3. The asyncResult is completed: this should never happen. - Contract.Assert((!asyncResult.IsWrite && typeof(T) == typeof(int)) || - (asyncResult.IsWrite && typeof(T) == typeof(VoidTaskResult))); - Contract.Assert(asyncResult != null); - Contract.Assert(asyncResult.IsAsync); - - try - { - // Cancel the overlapped read and set the task to cancelled state. - if (!asyncResult.IsCompleted) - asyncResult.Cancel(); - } - catch (Exception ex) - { - task.TrySetException(ex); - } - } - - // Completion callback for ReadAsync - [SecuritySafeCritical] - private static void EndReadTask(IAsyncResult iar) - { - FileStreamAsyncResult asyncResult = iar as FileStreamAsyncResult; - Contract.Assert(asyncResult != null); - Contract.Assert(asyncResult.IsCompleted, "How can we end up in the completion callback if the IAsyncResult is not completed?"); - - var readTask = asyncResult.AsyncState as FileStreamReadWriteTask<int>; - Contract.Assert(readTask != null); - - try - { - if (asyncResult.IsAsync) - { - asyncResult.ReleaseNativeResource(); - - // release the resource held by CancellationTokenRegistration - readTask._registration.Dispose(); - } - - if (asyncResult.ErrorCode == Win32Native.ERROR_OPERATION_ABORTED) - { - var cancellationToken = readTask._cancellationToken; - Contract.Assert(cancellationToken.IsCancellationRequested, "How can the IO operation be aborted if cancellation was not requested?"); - readTask.TrySetCanceled(cancellationToken); - } - else - readTask.TrySetResult(asyncResult.NumBytesRead); - } - catch (Exception ex) - { - readTask.TrySetException(ex); - } - } - - // Completion callback for WriteAsync - [SecuritySafeCritical] - private static void EndWriteTask(IAsyncResult iar) - { - var asyncResult = iar as FileStreamAsyncResult; - Contract.Assert(asyncResult != null); - Contract.Assert(asyncResult.IsCompleted, "How can we end up in the completion callback if the IAsyncResult is not completed?"); - - var writeTask = iar.AsyncState as FileStreamReadWriteTask<VoidTaskResult>; - Contract.Assert(writeTask != null); - - try - { - if (asyncResult.IsAsync) - { - asyncResult.ReleaseNativeResource(); - - // release the resource held by CancellationTokenRegistration - writeTask._registration.Dispose(); - } - - if (asyncResult.ErrorCode == Win32Native.ERROR_OPERATION_ABORTED) - { - var cancellationToken = writeTask._cancellationToken; - Contract.Assert(cancellationToken.IsCancellationRequested, "How can the IO operation be aborted if cancellation was not requested?"); - writeTask.TrySetCanceled(cancellationToken); - } - else - writeTask.TrySetResult(default(VoidTaskResult)); - } - catch (Exception ex) - { - writeTask.TrySetException(ex); - } - } - - // Unlike Flush(), FlushAsync() always flushes to disk. This is intentional. - // Legend is that we chose not to flush the OS file buffers in Flush() in fear of - // perf problems with frequent, long running FlushFileBuffers() calls. But we don't - // have that problem with FlushAsync() because we will call FlushFileBuffers() in the background. - [HostProtection(ExternalThreading = true)] - [ComVisible(false)] - [System.Security.SecuritySafeCritical] - public override Task FlushAsync(CancellationToken cancellationToken) - { - // If we have been inherited into a subclass, the following implementation could be incorrect - // since it does not call through to Flush() which a subclass might have overriden. To be safe - // we will only use this implementation in cases where we know it is safe to do so, - // and delegate to our base class (which will call into Flush) when we are not sure. - if (this.GetType() != typeof(FileStream)) - return base.FlushAsync(cancellationToken); - - if (cancellationToken.IsCancellationRequested) - return Task.FromCanceled(cancellationToken); - - if (_handle.IsClosed) - __Error.FileNotOpen(); - - // The always synchronous data transfer between the OS and the internal buffer is intentional - // because this is needed to allow concurrent async IO requests. Concurrent data transfer - // between the OS and the internal buffer will result in race conditions. Since FlushWrite and - // FlushRead modify internal state of the stream and transfer data between the OS and the - // internal buffer, they cannot be truly async. We will, however, flush the OS file buffers - // asynchronously because it doesn't modify any internal state of the stream and is potentially - // a long running process. - try - { - FlushInternalBuffer(); - } - catch (Exception e) - { - return Task.FromException(e); - } - - if (CanWrite) - return Task.Factory.StartNew( - state => ((FileStream)state).FlushOSBuffer(), - this, - cancellationToken, - TaskCreationOptions.DenyChildAttach, - TaskScheduler.Default); - else - return Task.CompletedTask; - } - - } -} diff --git a/src/mscorlib/src/System/IO/FileSystemEnumerable.cs b/src/mscorlib/src/System/IO/FileSystemEnumerable.cs index c2e603c06a..21e355d9fa 100644 --- a/src/mscorlib/src/System/IO/FileSystemEnumerable.cs +++ b/src/mscorlib/src/System/IO/FileSystemEnumerable.cs @@ -211,7 +211,7 @@ namespace System.IO _resultHandler = resultHandler; this.searchOption = searchOption; - fullPath = Path.GetFullPathInternal(path); + fullPath = Path.GetFullPath(path); String fullSearchString = GetFullSearchString(fullPath, normalizedSearchPattern); normalizedSearchPath = Path.GetDirectoryName(fullSearchString); @@ -260,7 +260,7 @@ namespace System.IO Contract.Assert(searchCriteria != null && searchData != null, "searchCriteria and searchData should be initialized"); // Execute searchCriteria against the current directory - String searchPath = Path.InternalCombine(searchData.fullPath, searchCriteria); + String searchPath = Path.Combine(searchData.fullPath, searchCriteria); Win32Native.WIN32_FIND_DATA data = new Win32Native.WIN32_FIND_DATA(); @@ -416,7 +416,7 @@ namespace System.IO AddSearchableDirsToStack(searchData); // Execute searchCriteria against the current directory - String searchPath = Path.InternalCombine(searchData.fullPath, searchCriteria); + String searchPath = Path.Combine(searchData.fullPath, searchCriteria); // Open a Find handle _hnd = Win32Native.FindFirstFile(searchPath, data); @@ -509,8 +509,8 @@ namespace System.IO [System.Security.SecurityCritical] private SearchResult CreateSearchResult(Directory.SearchData localSearchData, Win32Native.WIN32_FIND_DATA findData) { - String userPathFinal = Path.InternalCombine(localSearchData.userPath, findData.cFileName); - String fullPathFinal = Path.InternalCombine(localSearchData.fullPath, findData.cFileName); + String userPathFinal = Path.Combine(localSearchData.userPath, findData.cFileName); + String fullPathFinal = Path.Combine(localSearchData.fullPath, findData.cFileName); return new SearchResult(fullPathFinal, userPathFinal, findData); } @@ -526,7 +526,7 @@ namespace System.IO { Contract.Requires(localSearchData != null); - String searchPath = Path.InternalCombine(localSearchData.fullPath, "*"); + String searchPath = Path.Combine(localSearchData.fullPath, "*"); SafeFindHandle hnd = null; Win32Native.WIN32_FIND_DATA data = new Win32Native.WIN32_FIND_DATA(); try @@ -553,8 +553,8 @@ namespace System.IO { if (FileSystemEnumerableHelpers.IsDir(data)) { - String tempFullPath = Path.InternalCombine(localSearchData.fullPath, data.cFileName); - String tempUserPath = Path.InternalCombine(localSearchData.userPath, data.cFileName); + String tempFullPath = Path.Combine(localSearchData.fullPath, data.cFileName); + String tempUserPath = Path.Combine(localSearchData.userPath, data.cFileName); SearchOption option = localSearchData.searchOption; @@ -598,8 +598,8 @@ namespace System.IO { Contract.Requires(searchPattern != null); - // Win32 normalization trims only U+0020. - String tempSearchPattern = searchPattern.TrimEnd(Path.TrimEndChars); + // Win32 normalization trims only U+0020. + String tempSearchPattern = searchPattern.TrimEnd(PathInternal.s_trimEndChars); // Make this corner case more useful, like dir if (tempSearchPattern.Equals(".")) @@ -607,7 +607,7 @@ namespace System.IO tempSearchPattern = "*"; } - Path.CheckSearchPattern(tempSearchPattern); + PathInternal.CheckSearchPattern(tempSearchPattern); return tempSearchPattern; } @@ -619,7 +619,7 @@ namespace System.IO String searchCriteria = null; char lastChar = fullPathMod[fullPathMod.Length - 1]; - if (Path.IsDirectorySeparator(lastChar)) + if (PathInternal.IsDirectorySeparator(lastChar)) { // Can happen if the path is C:\temp, in which case GetDirectoryName would return C:\ searchCriteria = fullSearchString.Substring(fullPathMod.Length); @@ -637,11 +637,11 @@ namespace System.IO Contract.Requires(fullPath != null); Contract.Requires(searchPattern != null); - String tempStr = Path.InternalCombine(fullPath, searchPattern); + String tempStr = Path.Combine(fullPath, searchPattern); // If path ends in a trailing slash (\), append a * or we'll get a "Cannot find the file specified" exception char lastChar = tempStr[tempStr.Length - 1]; - if (Path.IsDirectorySeparator(lastChar) || lastChar == Path.VolumeSeparatorChar) + if (PathInternal.IsDirectorySeparator(lastChar) || lastChar == Path.VolumeSeparatorChar) { tempStr = tempStr + '*'; } diff --git a/src/mscorlib/src/System/IO/FileSystemInfo.cs b/src/mscorlib/src/System/IO/FileSystemInfo.cs index eaf559ba74..fe39b2d32d 100644 --- a/src/mscorlib/src/System/IO/FileSystemInfo.cs +++ b/src/mscorlib/src/System/IO/FileSystemInfo.cs @@ -63,10 +63,10 @@ namespace System.IO { if (info == null) throw new ArgumentNullException(nameof(info)); Contract.EndContractBlock(); - + // Must use V1 field names here, since V1 didn't implement // ISerializable. - FullPath = Path.GetFullPathInternal(info.GetString("FullPath")); + FullPath = Path.GetFullPath(info.GetString("FullPath")); OriginalPath = info.GetString("OriginalPath"); // Lazily initialize the file attributes. diff --git a/src/mscorlib/src/System/IO/Path.cs b/src/mscorlib/src/System/IO/Path.cs deleted file mode 100644 index 6670ad738b..0000000000 --- a/src/mscorlib/src/System/IO/Path.cs +++ /dev/null @@ -1,1433 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -/*============================================================ -** -** -** -** -** -** Purpose: A collection of path manipulation methods. -** -** -===========================================================*/ - -using System; -using System.Security.Permissions; -using Win32Native = Microsoft.Win32.Win32Native; -using System.Text; -using System.Runtime.InteropServices; -using System.Security; -#if FEATURE_LEGACYSURFACE -using System.Security.Cryptography; -#endif -using System.Runtime.CompilerServices; -using System.Globalization; -using System.Runtime.Versioning; -using System.Diagnostics.Contracts; - -namespace System.IO { - // Provides methods for processing directory strings in an ideally - // cross-platform manner. Most of the methods don't do a complete - // full parsing (such as examining a UNC hostname), but they will - // handle most string operations. - [ComVisible(true)] - public static class Path - { - // Platform specific directory separator character. This is backslash - // ('\') on Windows and slash ('/') on Unix. - // -#if !PLATFORM_UNIX - public static readonly char DirectorySeparatorChar = '\\'; - internal const string DirectorySeparatorCharAsString = "\\"; -#else - public static readonly char DirectorySeparatorChar = '/'; - internal const string DirectorySeparatorCharAsString = "/"; -#endif // !PLATFORM_UNIX - - // Platform specific alternate directory separator character. - // There is only one directory separator char on Unix, - // so the same definition is used for both Unix and Windows. - public static readonly char AltDirectorySeparatorChar = '/'; - - // Platform specific volume separator character. This is colon (':') - // on Windows and MacOS, and slash ('/') on Unix. This is mostly - // useful for parsing paths like "c:\windows" or "MacVolume:System Folder". - // -#if !PLATFORM_UNIX - public static readonly char VolumeSeparatorChar = ':'; -#else - public static readonly char VolumeSeparatorChar = '/'; -#endif // !PLATFORM_UNIX - - // Platform specific invalid list of characters in a path. - // See the "Naming a File" MSDN conceptual docs for more details on - // what is valid in a file name (which is slightly different from what - // is legal in a path name). - // Note: This list is duplicated in CheckInvalidPathChars - [Obsolete("Please use GetInvalidPathChars or GetInvalidFileNameChars instead.")] -#if !PLATFORM_UNIX - public 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, (Char)11, (Char)12, (Char)13, (Char)14, (Char)15, (Char)16, (Char)17, (Char)18, (Char)19, (Char)20, (Char)21, (Char)22, (Char)23, (Char)24, (Char)25, (Char)26, (Char)27, (Char)28, (Char)29, (Char)30, (Char)31 }; -#else - public static readonly char[] InvalidPathChars = { '\0' }; -#endif // !PLATFORM_UNIX - - // Trim trailing white spaces, tabs etc but don't be aggressive in removing everything that has UnicodeCategory of trailing space. - // String.WhitespaceChars will trim aggressively than what the underlying FS does (for ex, NTFS, FAT). - internal static readonly char[] TrimEndChars = - { - (char)0x09, // Horizontal tab - (char)0x0A, // Line feed - (char)0x0B, // Vertical tab - (char)0x0C, // Form feed - (char)0x0D, // Carriage return - (char)0x20, // Space - (char)0x85, // Next line - (char)0xA0 // Non breaking space - }; - -#if !PLATFORM_UNIX - private static readonly char[] RealInvalidPathChars = PathInternal.InvalidPathChars; - - private static readonly char[] InvalidFileNameChars = { '\"', '<', '>', '|', '\0', (Char)1, (Char)2, (Char)3, (Char)4, (Char)5, (Char)6, (Char)7, (Char)8, (Char)9, (Char)10, (Char)11, (Char)12, (Char)13, (Char)14, (Char)15, (Char)16, (Char)17, (Char)18, (Char)19, (Char)20, (Char)21, (Char)22, (Char)23, (Char)24, (Char)25, (Char)26, (Char)27, (Char)28, (Char)29, (Char)30, (Char)31, ':', '*', '?', '\\', '/' }; -#else - private static readonly char[] RealInvalidPathChars = { '\0' }; - - private static readonly char[] InvalidFileNameChars = { '\0', '/' }; -#endif // !PLATFORM_UNIX - -#if !PLATFORM_UNIX - public static readonly char PathSeparator = ';'; -#else - public static readonly char PathSeparator = ':'; -#endif // !PLATFORM_UNIX - - - // The max total path is 260, and the max individual component length is 255. - // For example, D:\<256 char file name> isn't legal, even though it's under 260 chars. - internal static readonly int MaxPath = PathInternal.MaxShortPath; - - internal static readonly int MaxPathComponentLength = PathInternal.MaxComponentLength; - - // Windows API definitions - internal const int MAX_PATH = 260; // From WinDef.h - internal const int MAX_DIRECTORY_PATH = 248; // cannot create directories greater than 248 characters - - // Changes the extension of a file path. The path parameter - // specifies a file path, and the extension parameter - // specifies a file extension (with a leading period, such as - // ".exe" or ".cs"). - // - // The function returns a file path with the same root, directory, and base - // name parts as path, but with the file extension changed to - // the specified extension. If path is null, the function - // returns null. If path does not contain a file extension, - // the new file extension is appended to the path. If extension - // is null, any exsiting extension is removed from path. - // - public static String ChangeExtension(String path, String extension) { - if (path != null) { - CheckInvalidPathChars(path); - - String s = path; - for (int i = path.Length; --i >= 0;) { - char ch = path[i]; - if (ch == '.') { - s = path.Substring(0, i); - break; - } - if (ch == DirectorySeparatorChar || ch == AltDirectorySeparatorChar || ch == VolumeSeparatorChar) break; - } - if (extension != null && path.Length != 0) { - if (extension.Length == 0 || extension[0] != '.') { - s = s + "."; - } - s = s + extension; - } - return s; - } - return null; - } - - // Returns the directory path of a file path. This method effectively - // removes the last element of the given file path, i.e. it returns a - // string consisting of all characters up to but not including the last - // backslash ("\") in the file path. The returned value is null if the file - // path is null or if the file path denotes a root (such as "\", "C:", or - // "\\server\share"). - public static String GetDirectoryName(String path) - { - return GetDirectoryNameInternal(path); - } - - [System.Security.SecuritySafeCritical] - private static string GetDirectoryNameInternal(string path) - { - if (path != null) - { - CheckInvalidPathChars(path); - - // Expanding short paths is dangerous in this case as the results will change with the current directory. - // - // Suppose you have a path called "PICTUR~1\Foo". Now suppose you have two folders on disk "C:\Mine\Pictures Of Me" - // and "C:\Yours\Pictures of You". If the current directory is neither you'll get back "PICTUR~1". If it is "C:\Mine" - // get back "Pictures Of Me". "C:\Yours" would give back "Pictures of You". - // - // Because of this and as it isn't documented that short paths are expanded we will not expand short names unless - // we're in legacy mode. - string normalizedPath = NormalizePath(path, fullCheck: false, expandShortPaths: -#if FEATURE_PATHCOMPAT - AppContextSwitches.UseLegacyPathHandling -#else - false -#endif - ); - - // 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. - if (path.Length > 0 -#if FEATURE_CAS_POLICY - // Only do the extra logic if we're not in full trust - && !CodeAccessSecurityEngine.QuickCheckForAllDemands() -#endif - ) - { - try - { - // If we were passed in a path with \\?\ we need to remove it as FileIOPermission does not like it. - string tempPath = RemoveLongPathPrefix(path); - - // FileIOPermission cannot handle paths that contain ? or * - // So we only pass to FileIOPermission the text up to them. - int pos = 0; - while (pos < tempPath.Length && (tempPath[pos] != '?' && tempPath[pos] != '*')) - pos++; - - // GetFullPath will Demand that we have the PathDiscovery FileIOPermission and thus throw - // SecurityException if we don't. - // While we don't use the result of this call we are using it as a consistent way of - // doing the security checks. - if (pos > 0) - GetFullPath(tempPath.Substring(0, pos)); - } - catch (SecurityException) - { - // If the user did not have permissions to the path, make sure that we don't leak expanded short paths - // Only re-normalize if the original path had a ~ in it. - if (path.IndexOf("~", StringComparison.Ordinal) != -1) - { - normalizedPath = NormalizePath(path, fullCheck: false, expandShortPaths: false); - } - } - catch (PathTooLongException) { } - catch (NotSupportedException) { } // Security can throw this on "c:\foo:" - catch (IOException) { } - catch (ArgumentException) { } // The normalizePath with fullCheck will throw this for file: and http: - } - - path = normalizedPath; - - int root = GetRootLength(path); - int i = path.Length; - if (i > root) - { - i = path.Length; - if (i == root) return null; - while (i > root && path[--i] != DirectorySeparatorChar && path[i] != AltDirectorySeparatorChar); - return path.Substring(0, i); - } - } - return null; - } - - // Gets the length of the root DirectoryInfo or whatever DirectoryInfo markers - // are specified for the first part of the DirectoryInfo name. - // - internal static int GetRootLength(string path) - { - CheckInvalidPathChars(path); - -#if !PLATFORM_UNIX && FEATURE_PATHCOMPAT - if (AppContextSwitches.UseLegacyPathHandling) - { - int i = 0; - int length = path.Length; - - if (length >= 1 && (IsDirectorySeparator(path[0]))) - { - // handles UNC names and directories off current drive's root. - i = 1; - if (length >= 2 && (IsDirectorySeparator(path[1]))) - { - i = 2; - int n = 2; - while (i < length && ((path[i] != DirectorySeparatorChar && path[i] != AltDirectorySeparatorChar) || --n > 0)) i++; - } - } - else if (length >= 2 && path[1] == VolumeSeparatorChar) - { - // handles A:\foo. - i = 2; - if (length >= 3 && (IsDirectorySeparator(path[2]))) i++; - } - return i; - } - else -#endif // !PLATFORM_UNIX && FEATURE_PATHCOMPAT - { - return PathInternal.GetRootLength(path); - } - } - - internal static bool IsDirectorySeparator(char c) { - return (c==DirectorySeparatorChar || c == AltDirectorySeparatorChar); - } - - public static char[] GetInvalidPathChars() - { - return (char[]) RealInvalidPathChars.Clone(); - } - - public static char[] GetInvalidFileNameChars() - { - return (char[]) InvalidFileNameChars.Clone(); - } - - // Returns the extension of the given path. The returned value includes the - // period (".") character of the extension except when you have a terminal period when you get String.Empty, such as ".exe" or - // ".cpp". The returned value is null if the given path is - // null or if the given path does not include an extension. - // - [Pure] - public static String GetExtension(String path) { - if (path==null) - return null; - - CheckInvalidPathChars(path); - int length = path.Length; - for (int i = length; --i >= 0;) { - char ch = path[i]; - if (ch == '.') - { - if (i != length - 1) - return path.Substring(i, length - i); - else - return String.Empty; - } - if (ch == DirectorySeparatorChar || ch == AltDirectorySeparatorChar || ch == VolumeSeparatorChar) - break; - } - return String.Empty; - } - - // Expands the given path to a fully qualified path. The resulting string - // consists of a drive letter, a colon, and a root relative path. This - // function does not verify that the resulting path - // refers to an existing file or directory on the associated volume. - [Pure] - [System.Security.SecuritySafeCritical] - public static String GetFullPath(String path) { - String fullPath = GetFullPathInternal(path); -#if FEATURE_CORECLR - FileSecurityState state = new FileSecurityState(FileSecurityStateAccess.PathDiscovery, path, fullPath); - state.EnsureState(); -#else - FileIOPermission.QuickDemand(FileIOPermissionAccess.PathDiscovery, fullPath, false, false); -#endif - return fullPath; - } - - [System.Security.SecurityCritical] - internal static String UnsafeGetFullPath(String path) - { - String fullPath = GetFullPathInternal(path); -#if !FEATURE_CORECLR - FileIOPermission.QuickDemand(FileIOPermissionAccess.PathDiscovery, fullPath, false, false); -#endif - return fullPath; - } - - // This method is package access to let us quickly get a string name - // while avoiding a security check. This also serves a slightly - // different purpose - when we open a file, we need to resolve the - // path into a fully qualified, non-relative path name. This - // method does that, finding the current drive &; directory. But - // as long as we don't return this info to the user, we're good. However, - // the public GetFullPath does need to do a security check. - internal static string GetFullPathInternal(string path) - { - if (path == null) - throw new ArgumentNullException(nameof(path)); - Contract.EndContractBlock(); - - string newPath = NormalizePath(path, fullCheck: true); - return newPath; - } - - [System.Security.SecuritySafeCritical] // auto-generated - internal unsafe static string NormalizePath(string path, bool fullCheck) - { - return NormalizePath(path, fullCheck, -#if FEATURE_PATHCOMPAT - AppContextSwitches.BlockLongPaths ? PathInternal.MaxShortPath : -#endif - PathInternal.MaxLongPath); - } - - [System.Security.SecuritySafeCritical] // auto-generated - internal unsafe static string NormalizePath(string path, bool fullCheck, bool expandShortPaths) - { - return NormalizePath(path, fullCheck, -#if FEATURE_PATHCOMPAT - AppContextSwitches.BlockLongPaths ? PathInternal.MaxShortPath : -#endif - PathInternal.MaxLongPath, - expandShortPaths); - } - - [System.Security.SecuritySafeCritical] // auto-generated - internal static string NormalizePath(string path, bool fullCheck, int maxPathLength) - { - return NormalizePath(path, fullCheck, maxPathLength, expandShortPaths: true); - } - - [System.Security.SecuritySafeCritical] - internal static string NormalizePath(string path, bool fullCheck, int maxPathLength, bool expandShortPaths) - { -#if FEATURE_PATHCOMPAT - if (AppContextSwitches.UseLegacyPathHandling) - { - return LegacyNormalizePath(path, fullCheck, maxPathLength, expandShortPaths); - } - else -#endif // FEATURE_APPCOMPAT - { - if (PathInternal.IsExtended(path)) - { - // We can't really know what is valid for all cases of extended paths. - // - // - object names can include other characters as well (':', '/', etc.) - // - even file objects have different rules (pipe names can contain most characters) - // - // As such we will do no further analysis of extended paths to avoid blocking known and unknown - // scenarios as well as minimizing compat breaks should we block now and need to unblock later. - return path; - } - - string normalizedPath = null; - - if (fullCheck == false) - { - // Disabled fullCheck is only called by GetDirectoryName and GetPathRoot. - // Avoid adding addtional callers and try going direct to lighter weight NormalizeDirectorySeparators. - normalizedPath = NewNormalizePathLimitedChecks(path, maxPathLength, expandShortPaths); - } - else - { - normalizedPath = NewNormalizePath(path, maxPathLength, expandShortPaths: true); - } - - if (string.IsNullOrWhiteSpace(normalizedPath)) - throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal")); - return normalizedPath; - } - } - - [System.Security.SecuritySafeCritical] - private static string NewNormalizePathLimitedChecks(string path, int maxPathLength, bool expandShortPaths) - { - string normalized = PathInternal.NormalizeDirectorySeparators(path); - - if (PathInternal.IsPathTooLong(normalized) || PathInternal.AreSegmentsTooLong(normalized)) - throw new PathTooLongException(); - -#if !PLATFORM_UNIX - if (!PathInternal.IsDevice(normalized) && PathInternal.HasInvalidVolumeSeparator(path)) - throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal")); - - if (expandShortPaths && normalized.IndexOf('~') != -1) - { - try - { - return LongPathHelper.GetLongPathName(normalized); - } - catch - { - // Don't care if we can't get the long path- might not exist, etc. - } - } -#endif - - return normalized; - } - - /// <summary> - /// Normalize the path and check for bad characters or other invalid syntax. - /// </summary> - [System.Security.SecuritySafeCritical] - private static string NewNormalizePath(string path, int maxPathLength, bool expandShortPaths) - { - Contract.Requires(path != null, "path can't be null"); - - // Embedded null characters are the only invalid character case we want to check up front. - // This is because the nulls will signal the end of the string to Win32 and therefore have - // unpredictable results. Other invalid characters we give a chance to be normalized out. - if (path.IndexOf('\0') != -1) - throw new ArgumentException(Environment.GetResourceString("Argument_InvalidPathChars")); - -#if !PLATFORM_UNIX - // Note that colon and wildcard checks happen in FileIOPermissions - - // Technically this doesn't matter but we used to throw for this case - if (string.IsNullOrWhiteSpace(path)) - throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal")); - - // We don't want to check invalid characters for device format- see comments for extended above - return LongPathHelper.Normalize(path, (uint)maxPathLength, checkInvalidCharacters: !PathInternal.IsDevice(path), expandShortPaths: expandShortPaths); -#else - if (path.Length == 0) - throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal")); - - // Expand with current directory if necessary - if (!IsPathRooted(path)) - path = Combine(Directory.GetCurrentDirectory(), 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. - string collapsedString = PathInternal.RemoveRelativeSegments(path); - - if (collapsedString.Length > maxPathLength) - throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong")); - - return collapsedString.Length == 0 ? "/" : collapsedString; -#endif // PLATFORM_UNIX - } - -#if FEATURE_PATHCOMPAT - [System.Security.SecurityCritical] // auto-generated - internal unsafe static String LegacyNormalizePath(String path, bool fullCheck, int maxPathLength, bool expandShortPaths) { - - Contract.Requires(path != null, "path can't be null"); - // If we're doing a full path check, trim whitespace and look for - // illegal path characters. - if (fullCheck) { - // Trim whitespace off the end of the string. - // Win32 normalization trims only U+0020. - path = path.TrimEnd(TrimEndChars); - - // Look for illegal path characters. - if (PathInternal.AnyPathHasIllegalCharacters(path)) - throw new ArgumentException(Environment.GetResourceString("Argument_InvalidPathChars")); - } - - int index = 0; - // We prefer to allocate on the stack for workingset/perf gain. If the - // starting path is less than MaxPath then we can stackalloc; otherwise we'll - // use a StringBuilder (PathHelper does this under the hood). The latter may - // happen in 2 cases: - // 1. Starting path is greater than MaxPath but it normalizes down to MaxPath. - // This is relevant for paths containing escape sequences. In this case, we - // attempt to normalize down to MaxPath, but the caller pays a perf penalty - // since StringBuilder is used. - // 2. IsolatedStorage, which supports paths longer than MaxPath (value given - // by maxPathLength. - PathHelper newBuffer; - if (path.Length + 1 <= MaxPath) { - char* m_arrayPtr = stackalloc char[MaxPath]; - newBuffer = new PathHelper(m_arrayPtr, MaxPath); - } else { - newBuffer = new PathHelper(path.Length + Path.MaxPath, maxPathLength); - } - - uint numSpaces = 0; - uint numDots = 0; - bool fixupDirectorySeparator = false; - // Number of significant chars other than potentially suppressible - // dots and spaces since the last directory or volume separator char - uint numSigChars = 0; - int lastSigChar = -1; // Index of last significant character. - // Whether this segment of the path (not the complete path) started - // with a volume separator char. Reject "c:...". - bool startedWithVolumeSeparator = false; - bool firstSegment = true; - int lastDirectorySeparatorPos = 0; - -#if !PLATFORM_UNIX - bool mightBeShortFileName = false; - - // LEGACY: This code is here for backwards compatibility reasons. It - // ensures that \\foo.cs\bar.cs stays \\foo.cs\bar.cs instead of being - // turned into \foo.cs\bar.cs. - if (path.Length > 0 && (path[0] == DirectorySeparatorChar || path[0] == AltDirectorySeparatorChar)) { - newBuffer.Append('\\'); - index++; - lastSigChar = 0; - } -#endif - - // Normalize the string, stripping out redundant dots, spaces, and - // slashes. - while (index < path.Length) { - char currentChar = path[index]; - - // We handle both directory separators and dots specially. For - // directory separators, we consume consecutive appearances. - // For dots, we consume all dots beyond the second in - // succession. All other characters are added as is. In - // addition we consume all spaces after the last other char - // in a directory name up until the directory separator. - - if (currentChar == DirectorySeparatorChar || currentChar == AltDirectorySeparatorChar) { - // If we have a path like "123.../foo", remove the trailing dots. - // However, if we found "c:\temp\..\bar" or "c:\temp\...\bar", don't. - // Also remove trailing spaces from both files & directory names. - // This was agreed on with the OS team to fix undeletable directory - // names ending in spaces. - - // If we saw a '\' as the previous last significant character and - // are simply going to write out dots, suppress them. - // If we only contain dots and slashes though, only allow - // a string like [dot]+ [space]*. Ignore everything else. - // Legal: "\.. \", "\...\", "\. \" - // Illegal: "\.. .\", "\. .\", "\ .\" - if (numSigChars == 0) { - // Dot and space handling - if (numDots > 0) { - // Look for ".[space]*" or "..[space]*" - int start = lastSigChar + 1; - if (path[start] != '.') - throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal")); - - // Only allow "[dot]+[space]*", and normalize the - // legal ones to "." or ".." - if (numDots >= 2) { - // Reject "C:..." - if (startedWithVolumeSeparator && numDots > 2) - - throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal")); - - if (path[start + 1] == '.') { - // Search for a space in the middle of the - // dots and throw - for(int i=start + 2; i < start + numDots; i++) { - if (path[i] != '.') - throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal")); - } - - numDots = 2; - } - else { - if (numDots > 1) - throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal")); - numDots = 1; - } - } - - if (numDots == 2) { - newBuffer.Append('.'); - } - - newBuffer.Append('.'); - fixupDirectorySeparator = false; - - // Continue in this case, potentially writing out '\'. - } - - if (numSpaces > 0 && firstSegment) { - // Handle strings like " \\server\share". - if (index + 1 < path.Length && - (path[index + 1] == DirectorySeparatorChar || path[index + 1] == AltDirectorySeparatorChar)) - { - newBuffer.Append(DirectorySeparatorChar); - } - } - } - numDots = 0; - numSpaces = 0; // Suppress trailing spaces - - if (!fixupDirectorySeparator) { - fixupDirectorySeparator = true; - newBuffer.Append(DirectorySeparatorChar); - } - numSigChars = 0; - lastSigChar = index; - startedWithVolumeSeparator = false; - firstSegment = false; - -#if !PLATFORM_UNIX - // For short file names, we must try to expand each of them as - // soon as possible. We need to allow people to specify a file - // name that doesn't exist using a path with short file names - // in it, such as this for a temp file we're trying to create: - // C:\DOCUME~1\USERNA~1.RED\LOCALS~1\Temp\bg3ylpzp - // We could try doing this afterwards piece by piece, but it's - // probably a lot simpler to do it here. - if (mightBeShortFileName) { - newBuffer.TryExpandShortFileName(); - mightBeShortFileName = false; - } -#endif - int thisPos = newBuffer.Length - 1; - if (thisPos - lastDirectorySeparatorPos > MaxPathComponentLength) - { - throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong")); - } - lastDirectorySeparatorPos = thisPos; - } // if (Found directory separator) - else if (currentChar == '.') { - // Reduce only multiple .'s only after slash to 2 dots. For - // instance a...b is a valid file name. - numDots++; - // Don't flush out non-terminal spaces here, because they may in - // the end not be significant. Turn "c:\ . .\foo" -> "c:\foo" - // which is the conclusion of removing trailing dots & spaces, - // as well as folding multiple '\' characters. - } - else if (currentChar == ' ') { - numSpaces++; - } - else { // Normal character logic -#if !PLATFORM_UNIX - if (currentChar == '~' && expandShortPaths) - mightBeShortFileName = true; -#endif - - fixupDirectorySeparator = false; - -#if !PLATFORM_UNIX - // To reject strings like "C:...\foo" and "C :\foo" - if (firstSegment && currentChar == VolumeSeparatorChar) { - // Only accept "C:", not "c :" or ":" - // Get a drive letter or ' ' if index is 0. - char driveLetter = (index > 0) ? path[index-1] : ' '; - bool validPath = ((numDots == 0) && (numSigChars >= 1) && (driveLetter != ' ')); - if (!validPath) - throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal")); - - startedWithVolumeSeparator = true; - // We need special logic to make " c:" work, we should not fix paths like " foo::$DATA" - if (numSigChars > 1) { // Common case, simply do nothing - int spaceCount = 0; // How many spaces did we write out, numSpaces has already been reset. - while((spaceCount < newBuffer.Length) && newBuffer[spaceCount] == ' ') - spaceCount++; - if (numSigChars - spaceCount == 1) { - //Safe to update stack ptr directly - newBuffer.Length = 0; - newBuffer.Append(driveLetter); // Overwrite spaces, we need a special case to not break " foo" as a relative path. - } - } - numSigChars = 0; - } - else -#endif // !PLATFORM_UNIX - { - numSigChars += 1 + numDots + numSpaces; - } - - // Copy any spaces & dots since the last significant character - // to here. Note we only counted the number of dots & spaces, - // and don't know what order they're in. Hence the copy. - if (numDots > 0 || numSpaces > 0) { - int numCharsToCopy = (lastSigChar >= 0) ? index - lastSigChar - 1 : index; - if (numCharsToCopy > 0) { - for (int i=0; i<numCharsToCopy; i++) { - newBuffer.Append(path[lastSigChar + 1 + i]); - } - } - numDots = 0; - numSpaces = 0; - } - - newBuffer.Append(currentChar); - lastSigChar = index; - } - - index++; - } // end while - - if (newBuffer.Length - 1 - lastDirectorySeparatorPos > MaxPathComponentLength) - { - throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong")); - } - - // Drop any trailing dots and spaces from file & directory names, EXCEPT - // we MUST make sure that "C:\foo\.." is correctly handled. - // Also handle "C:\foo\." -> "C:\foo", while "C:\." -> "C:\" - if (numSigChars == 0) { - if (numDots > 0) { - // Look for ".[space]*" or "..[space]*" - int start = lastSigChar + 1; - if (path[start] != '.') - throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal")); - - // Only allow "[dot]+[space]*", and normalize the - // legal ones to "." or ".." - if (numDots >= 2) { - // Reject "C:..." - if (startedWithVolumeSeparator && numDots > 2) - throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal")); - - if (path[start + 1] == '.') { - // Search for a space in the middle of the - // dots and throw - for(int i=start + 2; i < start + numDots; i++) { - if (path[i] != '.') - throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal")); - } - - numDots = 2; - } - else { - if (numDots > 1) - throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal")); - numDots = 1; - } - } - - if (numDots == 2) { - newBuffer.Append('.'); - } - - newBuffer.Append('.'); - } - } // if (numSigChars == 0) - - // If we ended up eating all the characters, bail out. - if (newBuffer.Length == 0) - throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal")); - - // Disallow URL's here. Some of our other Win32 API calls will reject - // them later, so we might be better off rejecting them here. - // Note we've probably turned them into "file:\D:\foo.tmp" by now. - // But for compatibility, ensure that callers that aren't doing a - // full check aren't rejected here. - if (fullCheck) { - if ( newBuffer.OrdinalStartsWith("http:", false) || - newBuffer.OrdinalStartsWith("file:", false)) - { - throw new ArgumentException(Environment.GetResourceString("Argument_PathUriFormatNotSupported")); - } - } - -#if !PLATFORM_UNIX - // If the last part of the path (file or directory name) had a tilde, - // expand that too. - if (mightBeShortFileName) { - newBuffer.TryExpandShortFileName(); - } -#endif - - // Call the Win32 API to do the final canonicalization step. - int result = 1; - - if (fullCheck) { - // NOTE: Win32 GetFullPathName requires the input buffer to be big enough to fit the initial - // path which is a concat of CWD and the relative path, this can be of an arbitrary - // size and could be > MAX_PATH (which becomes an artificial limit at this point), - // even though the final normalized path after fixing up the relative path syntax - // might be well within the MAX_PATH restriction. For ex, - // "c:\SomeReallyLongDirName(thinkGreaterThan_MAXPATH)\..\foo.txt" which actually requires a - // buffer well with in the MAX_PATH as the normalized path is just "c:\foo.txt" - // This buffer requirement seems wrong, it could be a bug or a perf optimization - // like returning required buffer length quickly or avoid stratch buffer etc. - // Ideally we would get the required buffer length first by calling GetFullPathName - // once without the buffer and use that in the later call but this doesn't always work - // due to Win32 GetFullPathName bug. For instance, in Win2k, when the path we are trying to - // fully qualify is a single letter name (such as "a", "1", ",") GetFullPathName - // fails to return the right buffer size (i.e, resulting in insufficient buffer). - // To workaround this bug we will start with MAX_PATH buffer and grow it once if the - // return value is > MAX_PATH. - - result = newBuffer.GetFullPathName(); - -#if !PLATFORM_UNIX - // If we called GetFullPathName with something like "foo" and our - // command window was in short file name mode (ie, by running edlin or - // DOS versions of grep, etc), we might have gotten back a short file - // name. So, check to see if we need to expand it. - mightBeShortFileName = false; - for(int i=0; i < newBuffer.Length && !mightBeShortFileName; i++) { - if (newBuffer[i] == '~' && expandShortPaths) - mightBeShortFileName = true; - } - - if (mightBeShortFileName) { - bool r = newBuffer.TryExpandShortFileName(); - // Consider how the path "Doesn'tExist" would expand. If - // we add in the current directory, it too will need to be - // fully expanded, which doesn't happen if we use a file - // name that doesn't exist. - if (!r) { - int lastSlash = -1; - - for (int i = newBuffer.Length - 1; i >= 0; i--) { - if (newBuffer[i] == DirectorySeparatorChar) { - lastSlash = i; - break; - } - } - - if (lastSlash >= 0) { - - // This bounds check is for safe memcpy but we should never get this far - if (newBuffer.Length >= maxPathLength) - throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong")); - - int lenSavedName = newBuffer.Length - lastSlash - 1; - Contract.Assert(lastSlash < newBuffer.Length, "path unexpectedly ended in a '\'"); - - newBuffer.Fixup(lenSavedName, lastSlash); - } - } - } -#endif // PLATFORM_UNIX - } - - if (result != 0) { - /* Throw an ArgumentException for paths like \\, \\server, \\server\ - This check can only be properly done after normalizing, so - \\foo\.. will be properly rejected. Also, reject \\?\GLOBALROOT\ - (an internal kernel path) because it provides aliases for drives. */ - if (newBuffer.Length > 1 && newBuffer[0] == '\\' && newBuffer[1] == '\\') { - int startIndex = 2; - while (startIndex < result) { - if (newBuffer[startIndex] == '\\') { - startIndex++; - break; - } - else { - startIndex++; - } - } - if (startIndex == result) - throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegalUNC")); - - // Check for \\?\Globalroot, an internal mechanism to the kernel - // that provides aliases for drives and other undocumented stuff. - // The kernel team won't even describe the full set of what - // is available here - we don't want managed apps mucking - // with this for security reasons. - if ( newBuffer.OrdinalStartsWith("\\\\?\\globalroot", true)) - throw new ArgumentException(Environment.GetResourceString("Arg_PathGlobalRoot")); - } - } - - // Check our result and form the managed string as necessary. - if (newBuffer.Length >= maxPathLength) - throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong")); - - if (result == 0) { - int errorCode = Marshal.GetLastWin32Error(); - if (errorCode == 0) - errorCode = Win32Native.ERROR_BAD_PATHNAME; - __Error.WinIOError(errorCode, path); - return null; // Unreachable - silence a compiler error. - } - - return newBuffer.ToStringOrExisting(path); - } -#endif // FEATURE_PATHCOMPAT - - internal const int MaxLongPath = PathInternal.MaxLongPath; - - private const string LongPathPrefix = PathInternal.ExtendedPathPrefix; - private const string UNCPathPrefix = PathInternal.UncPathPrefix; - private const string UNCLongPathPrefixToInsert = PathInternal.UncExtendedPrefixToInsert; - private const string UNCLongPathPrefix = PathInternal.UncExtendedPathPrefix; - - internal static bool HasLongPathPrefix(string path) - { -#if FEATURE_PATHCOMPAT - if (AppContextSwitches.UseLegacyPathHandling) - return path.StartsWith(LongPathPrefix, StringComparison.Ordinal); - else -#endif - return PathInternal.IsExtended(path); - } - - internal static string AddLongPathPrefix(string path) - { -#if FEATURE_PATHCOMPAT - if (AppContextSwitches.UseLegacyPathHandling) - { - if (path.StartsWith(LongPathPrefix, StringComparison.Ordinal)) - return path; - - if (path.StartsWith(UNCPathPrefix, StringComparison.Ordinal)) - return path.Insert(2, UNCLongPathPrefixToInsert); // Given \\server\share in longpath becomes \\?\UNC\server\share => UNCLongPathPrefix + path.SubString(2); => The actual command simply reduces the operation cost. - - return LongPathPrefix + path; - } - else -#endif - { - return PathInternal.EnsureExtendedPrefix(path); - } - } - - internal static string RemoveLongPathPrefix(string path) - { -#if FEATURE_PATHCOMPAT - if (AppContextSwitches.UseLegacyPathHandling) - { - if (!path.StartsWith(LongPathPrefix, StringComparison.Ordinal)) - return path; - - if (path.StartsWith(UNCLongPathPrefix, StringComparison.OrdinalIgnoreCase)) - return path.Remove(2, 6); // Given \\?\UNC\server\share we return \\server\share => @'\\' + path.SubString(UNCLongPathPrefix.Length) => The actual command simply reduces the operation cost. - - return path.Substring(4); - } - else -#endif - { - return PathInternal.RemoveExtendedPrefix(path); - } - } - - internal static StringBuilder RemoveLongPathPrefix(StringBuilder pathSB) - { -#if FEATURE_PATHCOMPAT - if (AppContextSwitches.UseLegacyPathHandling) - { - if (!PathInternal.StartsWithOrdinal(pathSB, LongPathPrefix)) - return pathSB; - - // Given \\?\UNC\server\share we return \\server\share => @'\\' + path.SubString(UNCLongPathPrefix.Length) => The actual command simply reduces the operation cost. - if (PathInternal.StartsWithOrdinal(pathSB, UNCLongPathPrefix, ignoreCase: true)) - return pathSB.Remove(2, 6); - - return pathSB.Remove(0, 4); - } - else -#endif - { - return PathInternal.RemoveExtendedPrefix(pathSB); - } - } - - - // Returns the name and extension parts of the given path. The resulting - // string contains the characters of path that follow the last - // backslash ("\"), slash ("/"), or colon (":") character in - // path. The resulting string is the entire path if path - // contains no backslash after removing trailing slashes, slash, or colon characters. The resulting - // string is null if path is null. - // - [Pure] - public static String GetFileName(String path) { - if (path != null) { - CheckInvalidPathChars(path); - - int length = path.Length; - for (int i = length; --i >= 0;) { - char ch = path[i]; - if (ch == DirectorySeparatorChar || ch == AltDirectorySeparatorChar || ch == VolumeSeparatorChar) - return path.Substring(i + 1, length - i - 1); - - } - } - return path; - } - - [Pure] - public static String GetFileNameWithoutExtension(String path) { - path = GetFileName(path); - if (path != null) - { - int i; - if ((i=path.LastIndexOf('.')) == -1) - return path; // No path extension found - else - return path.Substring(0,i); - } - return null; - } - - - - // Returns the root portion of the given path. The resulting string - // consists of those rightmost characters of the path that constitute the - // root of the path. Possible patterns for the resulting string are: An - // empty string (a relative path on the current drive), "\" (an absolute - // path on the current drive), "X:" (a relative path on a given drive, - // where X is the drive letter), "X:\" (an absolute path on a given drive), - // and "\\server\share" (a UNC path for a given server and share name). - // The resulting string is null if path is null. - // - [Pure] - public static String GetPathRoot(String path) { - if (path == null) return null; - - // Expanding short paths has no impact on the path root- there is no such thing as an - // 8.3 volume or server/share name. - path = NormalizePath(path, fullCheck: false, expandShortPaths: false); - return path.Substring(0, GetRootLength(path)); - } - - [System.Security.SecuritySafeCritical] - public static String GetTempPath() - { -#if !FEATURE_CORECLR - new EnvironmentPermission(PermissionState.Unrestricted).Demand(); -#endif - StringBuilder sb = new StringBuilder(PathInternal.MaxShortPath); - uint r = Win32Native.GetTempPath(PathInternal.MaxShortPath, sb); - String path = sb.ToString(); - if (r==0) __Error.WinIOError(); - path = GetFullPathInternal(path); -#if FEATURE_CORECLR - FileSecurityState state = new FileSecurityState(FileSecurityStateAccess.Write, String.Empty, path); - state.EnsureState(); -#endif - return path; - } - - internal static bool IsRelative(string path) - { - Contract.Assert(path != null, "path can't be null"); - return PathInternal.IsPartiallyQualified(path); - } - - // Returns a cryptographically strong random 8.3 string that can be - // used as either a folder name or a file name. -#if FEATURE_CORECLR - [System.Security.SecuritySafeCritical] -#endif - public static String GetRandomFileName() - { - // 5 bytes == 40 bits == 40/5 == 8 chars in our encoding - // This gives us exactly 8 chars. We want to avoid the 8.3 short name issue - byte[] key = new byte[10]; - -#if FEATURE_CORECLR - Win32Native.Random(true, key, key.Length); -#else - // RNGCryptoServiceProvider is disposable in post-Orcas desktop mscorlibs, but not in CoreCLR's - // mscorlib, so we need to do a manual using block for it. - RNGCryptoServiceProvider rng = null; - try - { - rng = new RNGCryptoServiceProvider(); - - rng.GetBytes(key); - } - finally - { - if (rng != null) - { - rng.Dispose(); - } - } -#endif - - // rndCharArray is expected to be 16 chars - char[] rndCharArray = Path.ToBase32StringSuitableForDirName(key).ToCharArray(); - rndCharArray[8] = '.'; - return new String(rndCharArray, 0, 12); - } - - // Returns a unique temporary file name, and creates a 0-byte file by that - // name on disk. - [System.Security.SecuritySafeCritical] - public static String GetTempFileName() - { - return InternalGetTempFileName(true); - } - - [System.Security.SecurityCritical] - internal static String UnsafeGetTempFileName() - { - return InternalGetTempFileName(false); - } - - [System.Security.SecurityCritical] - private static String InternalGetTempFileName(bool checkHost) - { - String path = GetTempPath(); - - // Since this can write to the temp directory and theoretically - // cause a denial of service attack, demand FileIOPermission to - // that directory. - -#if FEATURE_CORECLR - if (checkHost) - { - FileSecurityState state = new FileSecurityState(FileSecurityStateAccess.Write, String.Empty, path); - state.EnsureState(); - } -#else - FileIOPermission.QuickDemand(FileIOPermissionAccess.Write, path); -#endif - StringBuilder sb = new StringBuilder(MaxPath); - uint r = Win32Native.GetTempFileName(path, "tmp", 0, sb); - if (r==0) __Error.WinIOError(); - return sb.ToString(); - } - - // Tests if a path includes a file extension. The result is - // true if the characters that follow the last directory - // separator ('\\' or '/') or volume separator (':') in the path include - // a period (".") other than a terminal period. The result is false otherwise. - // - [Pure] - public static bool HasExtension(String path) { - if (path != null) { - CheckInvalidPathChars(path); - - for (int i = path.Length; --i >= 0;) { - char ch = path[i]; - if (ch == '.') { - if ( i != path.Length - 1) - return true; - else - return false; - } - if (ch == DirectorySeparatorChar || ch == AltDirectorySeparatorChar || ch == VolumeSeparatorChar) break; - } - } - return false; - } - - // Tests if the given path contains a root. A path is considered rooted - // if it starts with a backslash ("\") or a drive letter and a colon (":"). - // - [Pure] - public static bool IsPathRooted(String path) { - if (path != null) { - CheckInvalidPathChars(path); - - int length = path.Length; -#if !PLATFORM_UNIX - if ((length >= 1 && (path[0] == DirectorySeparatorChar || path[0] == AltDirectorySeparatorChar)) || (length >= 2 && path[1] == VolumeSeparatorChar)) - return true; -#else - if (length >= 1 && (path[0] == DirectorySeparatorChar || path[0] == AltDirectorySeparatorChar)) - return true; -#endif - } - return false; - } - - public static String Combine(String path1, String path2) { - if (path1==null || path2==null) - throw new ArgumentNullException((path1==null) ? nameof(path1) : nameof(path2)); - Contract.EndContractBlock(); - CheckInvalidPathChars(path1); - CheckInvalidPathChars(path2); - - return CombineNoChecks(path1, path2); - } - - public static String Combine(String path1, String path2, String path3) { - if (path1 == null || path2 == null || path3 == null) - throw new ArgumentNullException((path1 == null) ? nameof(path1) : (path2 == null) ? nameof(path2) : nameof(path3)); - Contract.EndContractBlock(); - CheckInvalidPathChars(path1); - CheckInvalidPathChars(path2); - CheckInvalidPathChars(path3); - - return CombineNoChecks(CombineNoChecks(path1, path2), path3); - } - - public static String Combine(String path1, String path2, String path3, String path4) { - if (path1 == null || path2 == null || path3 == null || path4 == null) - throw new ArgumentNullException((path1 == null) ? nameof(path1) : (path2 == null) ? nameof(path2) : (path3 == null) ? nameof(path3) : nameof(path4)); - Contract.EndContractBlock(); - CheckInvalidPathChars(path1); - CheckInvalidPathChars(path2); - CheckInvalidPathChars(path3); - CheckInvalidPathChars(path4); - - return CombineNoChecks(CombineNoChecks(CombineNoChecks(path1, path2), path3), path4); - } - - public static String Combine(params String[] paths) { - if (paths == null) { - throw new ArgumentNullException(nameof(paths)); - } - Contract.EndContractBlock(); - - int finalSize = 0; - int firstComponent = 0; - - // We have two passes, the first calcuates how large a buffer to allocate and does some precondition - // checks on the paths passed in. The second actually does the combination. - - for (int i = 0; i < paths.Length; i++) { - if (paths[i] == null) { - throw new ArgumentNullException(nameof(paths)); - } - - if (paths[i].Length == 0) { - continue; - } - - CheckInvalidPathChars(paths[i]); - - if (Path.IsPathRooted(paths[i])) { - firstComponent = i; - finalSize = paths[i].Length; - } else { - finalSize += paths[i].Length; - } - - char ch = paths[i][paths[i].Length - 1]; - if (ch != DirectorySeparatorChar && ch != AltDirectorySeparatorChar && ch != VolumeSeparatorChar) - finalSize++; - } - - StringBuilder finalPath = StringBuilderCache.Acquire(finalSize); - - for (int i = firstComponent; i < paths.Length; i++) { - if (paths[i].Length == 0) { - continue; - } - - if (finalPath.Length == 0) { - finalPath.Append(paths[i]); - } else { - char ch = finalPath[finalPath.Length - 1]; - if (ch != DirectorySeparatorChar && ch != AltDirectorySeparatorChar && ch != VolumeSeparatorChar) { - finalPath.Append(DirectorySeparatorChar); - } - - finalPath.Append(paths[i]); - } - } - - return StringBuilderCache.GetStringAndRelease(finalPath); - } - - private static String CombineNoChecks(String path1, String path2) { - if (path2.Length == 0) - return path1; - - if (path1.Length == 0) - return path2; - - if (IsPathRooted(path2)) - return path2; - - char ch = path1[path1.Length - 1]; - if (ch != DirectorySeparatorChar && ch != AltDirectorySeparatorChar && ch != VolumeSeparatorChar) - return path1 + DirectorySeparatorCharAsString + path2; - return path1 + path2; - } - - private static readonly Char[] s_Base32Char = { - 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', - 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', - 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', - 'y', 'z', '0', '1', '2', '3', '4', '5'}; - - internal static String ToBase32StringSuitableForDirName(byte[] buff) - { - // This routine is optimised to be used with buffs of length 20 - Contract.Assert(((buff.Length % 5) == 0), "Unexpected hash length"); - - StringBuilder sb = StringBuilderCache.Acquire(); - byte b0, b1, b2, b3, b4; - int l, i; - - l = buff.Length; - i = 0; - - // Create l chars using the last 5 bits of each byte. - // Consume 3 MSB bits 5 bytes at a time. - - do - { - b0 = (i < l) ? buff[i++] : (byte)0; - b1 = (i < l) ? buff[i++] : (byte)0; - b2 = (i < l) ? buff[i++] : (byte)0; - b3 = (i < l) ? buff[i++] : (byte)0; - b4 = (i < l) ? buff[i++] : (byte)0; - - // Consume the 5 Least significant bits of each byte - sb.Append(s_Base32Char[b0 & 0x1F]); - sb.Append(s_Base32Char[b1 & 0x1F]); - sb.Append(s_Base32Char[b2 & 0x1F]); - sb.Append(s_Base32Char[b3 & 0x1F]); - sb.Append(s_Base32Char[b4 & 0x1F]); - - // Consume 3 MSB of b0, b1, MSB bits 6, 7 of b3, b4 - sb.Append(s_Base32Char[( - ((b0 & 0xE0) >> 5) | - ((b3 & 0x60) >> 2))]); - - sb.Append(s_Base32Char[( - ((b1 & 0xE0) >> 5) | - ((b4 & 0x60) >> 2))]); - - // Consume 3 MSB bits of b2, 1 MSB bit of b3, b4 - - b2 >>= 5; - - Contract.Assert(((b2 & 0xF8) == 0), "Unexpected set bits"); - - if ((b3 & 0x80) != 0) - b2 |= 0x08; - if ((b4 & 0x80) != 0) - b2 |= 0x10; - - sb.Append(s_Base32Char[b2]); - - } while (i < l); - - return StringBuilderCache.GetStringAndRelease(sb); - } - - // ".." can only be used if it is specified as a part of a valid File/Directory name. We disallow - // the user being able to use it to move up directories. Here are some examples eg - // Valid: a..b abc..d - // Invalid: ..ab ab.. .. abc..d\abc.. - // - internal static void CheckSearchPattern(String searchPattern) - { - int index; - while ((index = searchPattern.IndexOf("..", StringComparison.Ordinal)) != -1) { - - if (index + 2 == searchPattern.Length) // Terminal ".." . Files names cannot end in ".." - throw new ArgumentException(Environment.GetResourceString("Arg_InvalidSearchPattern")); - - if ((searchPattern[index+2] == DirectorySeparatorChar) - || (searchPattern[index+2] == AltDirectorySeparatorChar)) - throw new ArgumentException(Environment.GetResourceString("Arg_InvalidSearchPattern")); - - searchPattern = searchPattern.Substring(index + 2); - } - - } - - internal static void CheckInvalidPathChars(String path, bool checkAdditional = false) - { - if (path == null) - throw new ArgumentNullException(nameof(path)); - - if (PathInternal.HasIllegalCharacters(path, checkAdditional)) - throw new ArgumentException(Environment.GetResourceString("Argument_InvalidPathChars")); - } - - internal static String InternalCombine(String path1, String path2) { - if (path1==null || path2==null) - throw new ArgumentNullException((path1==null) ? nameof(path1) : nameof(path2)); - Contract.EndContractBlock(); - CheckInvalidPathChars(path1); - CheckInvalidPathChars(path2); - - if (path2.Length == 0) - throw new ArgumentException(Environment.GetResourceString("Argument_PathEmpty"), nameof(path2)); - if (IsPathRooted(path2)) - throw new ArgumentException(Environment.GetResourceString("Arg_Path2IsRooted"), nameof(path2)); - int i = path1.Length; - if (i == 0) return path2; - char ch = path1[i - 1]; - if (ch != DirectorySeparatorChar && ch != AltDirectorySeparatorChar && ch != VolumeSeparatorChar) - return path1 + DirectorySeparatorCharAsString + path2; - return path1 + path2; - } - - } -} diff --git a/src/mscorlib/src/System/IO/PathHelper.cs b/src/mscorlib/src/System/IO/PathHelper.cs deleted file mode 100644 index 8e39b3c537..0000000000 --- a/src/mscorlib/src/System/IO/PathHelper.cs +++ /dev/null @@ -1,448 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -#if FEATURE_PATHCOMPAT -using System; -using System.Collections; -using System.Text; -using Microsoft.Win32; -using System.Runtime.InteropServices; -using System.Runtime.CompilerServices; -using System.Globalization; -using System.Runtime.Versioning; -using System.Security; -using System.Security.Permissions; -using System.Diagnostics.Contracts; - -namespace System.IO { - - // ABOUT: - // Helps with path normalization; support allocating on the stack or heap - // - // PathHelper can't stackalloc the array for obvious reasons; you must pass - // in an array of chars allocated on the stack. - // - // USAGE: - // Suppose you need to represent a char array of length len. Then this is the - // suggested way to instantiate PathHelper: - // *************************************************************************** - // PathHelper pathHelper; - // if (charArrayLength less than stack alloc threshold == Path.MaxPath) - // char* arrayPtr = stackalloc char[Path.MaxPath]; - // pathHelper = new PathHelper(arrayPtr); - // else - // pathHelper = new PathHelper(capacity, maxPath); - // *************************************************************************** - // - // note in the StringBuilder ctor: - // - maxPath may be greater than Path.MaxPath (for isolated storage) - // - capacity may be greater than maxPath. This is even used for non-isolated - // storage scenarios where we want to temporarily allow strings greater - // than Path.MaxPath if they can be normalized down to Path.MaxPath. This - // can happen if the path contains escape characters "..". - // - unsafe internal struct PathHelper { // should not be serialized - - // maximum size, max be greater than max path if contains escape sequence - private int m_capacity; - // current length (next character position) - private int m_length; - // max path, may be less than capacity - private int m_maxPath; - - // ptr to stack alloc'd array of chars - [SecurityCritical] - private char* m_arrayPtr; - - // StringBuilder - private StringBuilder m_sb; - - // whether to operate on stack alloc'd or heap alloc'd array - private bool useStackAlloc; - - // Whether to skip calls to Win32Native.GetLongPathName becasue we tried before and failed: - private bool doNotTryExpandShortFileName; - - // Instantiates a PathHelper with a stack alloc'd array of chars - [System.Security.SecurityCritical] - internal PathHelper(char* charArrayPtr, int length) { - Contract.Requires(charArrayPtr != null); - // force callers to be aware of this - Contract.Requires(length == Path.MaxPath); - this.m_length = 0; - this.m_sb = null; - - this.m_arrayPtr = charArrayPtr; - this.m_capacity = length; - this.m_maxPath = Path.MaxPath; - useStackAlloc = true; - doNotTryExpandShortFileName = false; - } - - // Instantiates a PathHelper with a heap alloc'd array of ints. Will create a StringBuilder - [System.Security.SecurityCritical] - internal PathHelper(int capacity, int maxPath) - { - this.m_length = 0; - this.m_arrayPtr = null; - this.useStackAlloc = false; - - this.m_sb = new StringBuilder(capacity); - this.m_capacity = capacity; - this.m_maxPath = maxPath; - doNotTryExpandShortFileName = false; - } - - internal int Length { - get { - if (useStackAlloc) { - return m_length; - } - else { - return m_sb.Length; - } - } - set { - if (useStackAlloc) { - m_length = value; - } - else { - m_sb.Length = value; - } - } - } - - internal int Capacity { - get { - return m_capacity; - } - } - - internal char this[int index] { - [System.Security.SecurityCritical] - get { - Contract.Requires(index >= 0 && index < Length); - if (useStackAlloc) { - return m_arrayPtr[index]; - } - else { - return m_sb[index]; - } - } - [System.Security.SecurityCritical] - set { - Contract.Requires(index >= 0 && index < Length); - if (useStackAlloc) { - m_arrayPtr[index] = value; - } - else { - m_sb[index] = value; - } - } - } - - [System.Security.SecurityCritical] - internal unsafe void Append(char value) { - if (Length + 1 >= m_capacity) - throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong")); - - if (useStackAlloc) { - m_arrayPtr[Length] = value; - m_length++; - } - else { - m_sb.Append(value); - } - } - - [System.Security.SecurityCritical] - internal unsafe int GetFullPathName() { - if (useStackAlloc) { - char* finalBuffer = stackalloc char[Path.MaxPath + 1]; - int result = Win32Native.GetFullPathName(m_arrayPtr, Path.MaxPath + 1, finalBuffer, IntPtr.Zero); - - // If success, the return buffer length does not account for the terminating null character. - // If in-sufficient buffer, the return buffer length does account for the path + the terminating null character. - // If failure, the return buffer length is zero - if (result > Path.MaxPath) { - char* tempBuffer = stackalloc char[result]; - finalBuffer = tempBuffer; - result = Win32Native.GetFullPathName(m_arrayPtr, result, finalBuffer, IntPtr.Zero); - } - - // Full path is genuinely long - if (result >= Path.MaxPath) - throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong")); - - Contract.Assert(result < Path.MaxPath, "did we accidently remove a PathTooLongException check?"); - if (result == 0 && m_arrayPtr[0] != '\0') { - __Error.WinIOError(); - } - - else if (result < Path.MaxPath) { - // Null terminate explicitly (may be only needed for some cases such as empty strings) - // GetFullPathName return length doesn't account for null terminating char... - finalBuffer[result] = '\0'; // Safe to write directly as result is < Path.MaxPath - } - - // We have expanded the paths and GetLongPathName may or may not behave differently from before. - // We need to call it again to see: - doNotTryExpandShortFileName = false; - - String.wstrcpy(m_arrayPtr, finalBuffer, result); - // Doesn't account for null terminating char. Think of this as the last - // valid index into the buffer but not the length of the buffer - Length = result; - return result; - } - else { - StringBuilder finalBuffer = new StringBuilder(m_capacity + 1); - int result = Win32Native.GetFullPathName(m_sb.ToString(), m_capacity + 1, finalBuffer, IntPtr.Zero); - - // If success, the return buffer length does not account for the terminating null character. - // If in-sufficient buffer, the return buffer length does account for the path + the terminating null character. - // If failure, the return buffer length is zero - if (result > m_maxPath) { - finalBuffer.Length = result; - result = Win32Native.GetFullPathName(m_sb.ToString(), result, finalBuffer, IntPtr.Zero); - } - - // Fullpath is genuinely long - if (result >= m_maxPath) - throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong")); - - Contract.Assert(result < m_maxPath, "did we accidentally remove a PathTooLongException check?"); - if (result == 0 && m_sb[0] != '\0') { - if (Length >= m_maxPath) { - throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong")); - } - __Error.WinIOError(); - } - - // We have expanded the paths and GetLongPathName may or may not behave differently from before. - // We need to call it again to see: - doNotTryExpandShortFileName = false; - - m_sb = finalBuffer; - return result; - } - } - - [System.Security.SecurityCritical] - internal unsafe bool TryExpandShortFileName() { - - if (doNotTryExpandShortFileName) - return false; - - if (useStackAlloc) { - NullTerminate(); - char* buffer = UnsafeGetArrayPtr(); - char* shortFileNameBuffer = stackalloc char[Path.MaxPath + 1]; - - int r = Win32Native.GetLongPathName(buffer, shortFileNameBuffer, Path.MaxPath); - - // If success, the return buffer length does not account for the terminating null character. - // If in-sufficient buffer, the return buffer length does account for the path + the terminating null character. - // If failure, the return buffer length is zero - if (r >= Path.MaxPath) - throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong")); - - if (r == 0) { - // Note: GetLongPathName will return ERROR_INVALID_FUNCTION on a - // path like \\.\PHYSICALDEVICE0 - some device driver doesn't - // support GetLongPathName on that string. This behavior is - // by design, according to the Core File Services team. - // We also get ERROR_NOT_ENOUGH_QUOTA in SQL_CLR_STRESS runs - // intermittently on paths like D:\DOCUME~1\user\LOCALS~1\Temp\ - - // We do not need to call GetLongPathName if we know it will fail becasue the path does not exist: - int lastErr = Marshal.GetLastWin32Error(); - if (lastErr == Win32Native.ERROR_FILE_NOT_FOUND || lastErr == Win32Native.ERROR_PATH_NOT_FOUND) - doNotTryExpandShortFileName = true; - - return false; - } - - // Safe to copy as we have already done Path.MaxPath bound checking - String.wstrcpy(buffer, shortFileNameBuffer, r); - Length = r; - // We should explicitly null terminate as in some cases the long version of the path - // might actually be shorter than what we started with because of Win32's normalization - // Safe to write directly as bufferLength is guaranteed to be < Path.MaxPath - NullTerminate(); - return true; - } - else { - StringBuilder sb = GetStringBuilder(); - - String origName = sb.ToString(); - String tempName = origName; - bool addedPrefix = false; - if (tempName.Length > Path.MaxPath) { - tempName = Path.AddLongPathPrefix(tempName); - addedPrefix = true; - } - sb.Capacity = m_capacity; - sb.Length = 0; - int r = Win32Native.GetLongPathName(tempName, sb, m_capacity); - - if (r == 0) { - // Note: GetLongPathName will return ERROR_INVALID_FUNCTION on a - // path like \\.\PHYSICALDEVICE0 - some device driver doesn't - // support GetLongPathName on that string. This behavior is - // by design, according to the Core File Services team. - // We also get ERROR_NOT_ENOUGH_QUOTA in SQL_CLR_STRESS runs - // intermittently on paths like D:\DOCUME~1\user\LOCALS~1\Temp\ - - // We do not need to call GetLongPathName if we know it will fail becasue the path does not exist: - int lastErr = Marshal.GetLastWin32Error(); - if (Win32Native.ERROR_FILE_NOT_FOUND == lastErr || Win32Native.ERROR_PATH_NOT_FOUND == lastErr) - doNotTryExpandShortFileName = true; - - sb.Length = 0; - sb.Append(origName); - return false; - } - - if (addedPrefix) - r -= 4; - - // If success, the return buffer length does not account for the terminating null character. - // If in-sufficient buffer, the return buffer length does account for the path + the terminating null character. - // If failure, the return buffer length is zero - if (r >= m_maxPath) - throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong")); - - - sb = Path.RemoveLongPathPrefix(sb); - Length = sb.Length; - return true; - - } - } - - [System.Security.SecurityCritical] - internal unsafe void Fixup(int lenSavedName, int lastSlash) { - if (useStackAlloc) { - char* savedName = stackalloc char[lenSavedName]; - String.wstrcpy(savedName, m_arrayPtr + lastSlash + 1, lenSavedName); - Length = lastSlash; - NullTerminate(); - doNotTryExpandShortFileName = false; - bool r = TryExpandShortFileName(); - // Clean up changes made to the newBuffer. - Append(Path.DirectorySeparatorChar); - if (Length + lenSavedName >= Path.MaxPath) - throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong")); - String.wstrcpy(m_arrayPtr + Length, savedName, lenSavedName); - Length = Length + lenSavedName; - - } - else { - String savedName = m_sb.ToString(lastSlash + 1, lenSavedName); - Length = lastSlash; - doNotTryExpandShortFileName = false; - bool r = TryExpandShortFileName(); - // Clean up changes made to the newBuffer. - Append(Path.DirectorySeparatorChar); - if (Length + lenSavedName >= m_maxPath) - throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong")); - m_sb.Append(savedName); - } - } - - [System.Security.SecurityCritical] - internal unsafe bool OrdinalStartsWith(String compareTo, bool ignoreCase) { - if (Length < compareTo.Length) - return false; - - if (useStackAlloc) { - NullTerminate(); - if (ignoreCase) { - String s = new String(m_arrayPtr, 0, compareTo.Length); - return compareTo.Equals(s, StringComparison.OrdinalIgnoreCase); - } - else { - for (int i = 0; i < compareTo.Length; i++) { - if (m_arrayPtr[i] != compareTo[i]) { - return false; - } - } - return true; - } - } - else { - if (ignoreCase) { - return m_sb.ToString().StartsWith(compareTo, StringComparison.OrdinalIgnoreCase); - } - else { - return m_sb.ToString().StartsWith(compareTo, StringComparison.Ordinal); - } - } - } - - [System.Security.SecurityCritical] - private unsafe bool OrdinalEqualsStackAlloc(String compareTo) - { - Contract.Requires(useStackAlloc, "Currently no efficient implementation for StringBuilder.OrdinalEquals(String)"); - - if (Length != compareTo.Length) { - return false; - } - - for (int i = 0; i < compareTo.Length; i++) { - if (m_arrayPtr[i] != compareTo[i]) { - return false; - } - } - - return true; - } - - [System.Security.SecuritySafeCritical] - public override String ToString() { - if (useStackAlloc) { - return new String(m_arrayPtr, 0, Length); - } - else { - return m_sb.ToString(); - } - } - - [System.Security.SecuritySafeCritical] - internal String ToStringOrExisting(String existingString) - { - if (useStackAlloc) { - return OrdinalEqualsStackAlloc(existingString) ? - existingString : - new String(m_arrayPtr, 0, Length); - } - else { - string newString = m_sb.ToString(); // currently no good StringBuilder.OrdinalEquals(string) - return String.Equals(newString, existingString, StringComparison.Ordinal) ? - existingString : - newString; - } - } - - [System.Security.SecurityCritical] - private unsafe char* UnsafeGetArrayPtr() { - Contract.Requires(useStackAlloc, "This should never be called for PathHelpers wrapping a StringBuilder"); - return m_arrayPtr; - } - - private StringBuilder GetStringBuilder() { - Contract.Requires(!useStackAlloc, "This should never be called for PathHelpers that wrap a stackalloc'd buffer"); - return m_sb; - } - - [System.Security.SecurityCritical] - private unsafe void NullTerminate() { - Contract.Requires(useStackAlloc, "This should never be called for PathHelpers wrapping a StringBuilder"); - m_arrayPtr[m_length] = '\0'; - } - - } -} -#endif // FEATURE_PATHCOMPAT
\ No newline at end of file diff --git a/src/mscorlib/src/System/IO/PathInternal.cs b/src/mscorlib/src/System/IO/PathInternal.cs deleted file mode 100644 index 9545905327..0000000000 --- a/src/mscorlib/src/System/IO/PathInternal.cs +++ /dev/null @@ -1,822 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using Microsoft.Win32; -using System; -using System.Diagnostics.Contracts; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Security; -using System.Text; - -namespace System.IO -{ - /// <summary>Contains internal path helpers that are shared between many projects.</summary> - internal static class PathInternal - { - internal const string ExtendedPathPrefix = @"\\?\"; - internal const string UncPathPrefix = @"\\"; - internal const string UncExtendedPrefixToInsert = @"?\UNC\"; - internal const string UncExtendedPathPrefix = @"\\?\UNC\"; - internal const string DevicePathPrefix = @"\\.\"; - // \\?\, \\.\, \??\ - internal const int DevicePrefixLength = 4; - // \\ - internal const int UncPrefixLength = 2; - // \\?\UNC\, \\.\UNC\ - internal const int UncExtendedPrefixLength = 8; -#if !PLATFORM_UNIX - internal const int MaxShortPath = 260; - internal const int MaxShortDirectoryPath = 248; -#else - internal const int MaxShortPath = 1024; - internal const int MaxShortDirectoryPath = MaxShortPath; -#endif - - // Windows is limited in long paths by the max size of its internal representation of a unicode string. - // UNICODE_STRING has a max length of USHORT in _bytes_ without a trailing null. - // https://msdn.microsoft.com/en-us/library/windows/hardware/ff564879.aspx - internal const int MaxLongPath = short.MaxValue; - internal static readonly int MaxComponentLength = 255; - -#if !PLATFORM_UNIX - internal 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, - (char)11, (char)12, (char)13, (char)14, (char)15, (char)16, (char)17, (char)18, (char)19, (char)20, - (char)21, (char)22, (char)23, (char)24, (char)25, (char)26, (char)27, (char)28, (char)29, (char)30, - (char)31 - }; -#else - internal static readonly char[] InvalidPathChars = { '\0' }; -#endif - - - /// <summary> - /// Validates volume separator only occurs as C: or \\?\C:. This logic is meant to filter out Alternate Data Streams. - /// </summary> - /// <returns>True if the path has an invalid volume separator.</returns> - internal static bool HasInvalidVolumeSeparator(string path) - { - // Toss out paths with colons that aren't a valid drive specifier. - // Cannot start with a colon and can only be of the form "C:" or "\\?\C:". - // (Note that we used to explicitly check "http:" and "file:"- these are caught by this check now.) - - // We don't care about skipping starting space for extended paths. Assume no knowledge of extended paths if we're forcing old path behavior. - bool isExtended = -#if FEATURE_PATHCOMPAT - !AppContextSwitches.UseLegacyPathHandling && -#endif - IsExtended(path); - int startIndex = isExtended ? ExtendedPathPrefix.Length : PathStartSkip(path); - - // If we start with a colon - if ((path.Length > startIndex && path[startIndex] == Path.VolumeSeparatorChar) - // Or have an invalid drive letter and colon - || (path.Length >= startIndex + 2 && path[startIndex + 1] == Path.VolumeSeparatorChar && !IsValidDriveChar(path[startIndex])) - // Or have any colons beyond the drive colon - || (path.Length > startIndex + 2 && path.IndexOf(Path.VolumeSeparatorChar, startIndex + 2) != -1)) - { - return true; - } - - return false; - } - - /// <summary> - /// Returns true if the given StringBuilder starts with the given value. - /// </summary> - /// <param name="value">The string to compare against the start of the StringBuilder.</param> - internal static bool StartsWithOrdinal(StringBuilder builder, string value, bool ignoreCase = false) - { - if (value == null || builder.Length < value.Length) - return false; - - if (ignoreCase) - { - for (int i = 0; i < value.Length; i++) - if (char.ToUpperInvariant(builder[i]) != char.ToUpperInvariant(value[i])) return false; - } - else - { - for (int i = 0; i < value.Length; i++) - if (builder[i] != value[i]) return false; - } - - return true; - } - - /// <summary> - /// Returns true if the given character is a valid drive letter - /// </summary> - internal static bool IsValidDriveChar(char value) - { - return ((value >= 'A' && value <= 'Z') || (value >= 'a' && value <= 'z')); - } - - /// <summary> - /// Returns true if the path is too long - /// </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 beyond / shrink below exceed MaxLongPath. -#if FEATURE_PATHCOMPAT - if (AppContextSwitches.BlockLongPaths) - { - // We allow paths of any length if extended (and not in compat mode) - if (AppContextSwitches.UseLegacyPathHandling || !IsExtended(fullPath)) - return fullPath.Length >= MaxShortPath; - } -#endif - - return fullPath.Length >= MaxLongPath; - } - - /// <summary> - /// Return true if any path segments are too long - /// </summary> - internal static bool AreSegmentsTooLong(string fullPath) - { - int length = fullPath.Length; - int lastSeparator = 0; - - for (int i = 0; i < length; i++) - { - if (IsDirectorySeparator(fullPath[i])) - { - if (i - lastSeparator > MaxComponentLength) - return true; - lastSeparator = i; - } - } - - if (length - 1 - lastSeparator > MaxComponentLength) - return true; - - return false; - } - - /// <summary> - /// Returns true if the directory is too long - /// </summary> - internal static bool IsDirectoryTooLong(string fullPath) - { -#if FEATURE_PATHCOMPAT - if (AppContextSwitches.BlockLongPaths) - { - // We allow paths of any length if extended (and not in compat mode) - if (AppContextSwitches.UseLegacyPathHandling || !IsExtended(fullPath)) - return (fullPath.Length >= MaxShortDirectoryPath); - } -#endif - - return IsPathTooLong(fullPath); - } - - /// <summary> - /// Adds the extended path prefix (\\?\) if not relative or already a device path. - /// </summary> - internal static string EnsureExtendedPrefix(string path) - { - // Putting the extended prefix on the path changes the processing of the path. It won't get normalized, which - // means adding to relative paths will prevent them from getting the appropriate current directory inserted. - - // If it already has some variant of a device path (\??\, \\?\, \\.\, //./, etc.) we don't need to change it - // as it is either correct or we will be changing the behavior. When/if Windows supports long paths implicitly - // in the future we wouldn't want normalization to come back and break existing code. - - // In any case, all internal usages should be hitting normalize path (Path.GetFullPath) before they hit this - // shimming method. (Or making a change that doesn't impact normalization, such as adding a filename to a - // normalized base path.) - if (IsPartiallyQualified(path) || IsDevice(path)) - return path; - - // Given \\server\share in longpath becomes \\?\UNC\server\share - if (path.StartsWith(UncPathPrefix, StringComparison.OrdinalIgnoreCase)) - return path.Insert(2, UncExtendedPrefixToInsert); - - return ExtendedPathPrefix + path; - } - - /// <summary> - /// Adds the extended path prefix (\\?\) if not already a device path, IF the path is not relative, - /// AND the path is more than 259 characters. (> MAX_PATH + null) - /// </summary> - internal static string EnsureExtendedPrefixOverMaxPath(string path) - { - if (path != null && path.Length >= MaxShortPath) - { - return EnsureExtendedPrefix(path); - } - else - { - return path; - } - } - - /// <summary> - /// Removes the extended path prefix (\\?\) if present. - /// </summary> - internal static string RemoveExtendedPrefix(string path) - { - if (!IsExtended(path)) - return path; - - // Given \\?\UNC\server\share we return \\server\share - if (IsExtendedUnc(path)) - return path.Remove(2, 6); - - return path.Substring(DevicePrefixLength); - } - - /// <summary> - /// Removes the extended path prefix (\\?\) if present. - /// </summary> - internal static StringBuilder RemoveExtendedPrefix(StringBuilder path) - { - if (!IsExtended(path)) - return path; - - // Given \\?\UNC\server\share we return \\server\share - if (IsExtendedUnc(path)) - return path.Remove(2, 6); - - return path.Remove(0, DevicePrefixLength); - } - - /// <summary> - /// Returns true if the path uses any of the DOS device path syntaxes. ("\\.\", "\\?\", or "\??\") - /// </summary> - internal static bool IsDevice(string path) - { - // If the path begins with any two separators it will be recognized and normalized and prepped with - // "\??\" for internal usage correctly. "\??\" is recognized and handled, "/??/" is not. - return IsExtended(path) - || - ( - path.Length >= DevicePrefixLength - && IsDirectorySeparator(path[0]) - && IsDirectorySeparator(path[1]) - && (path[2] == '.' || path[2] == '?') - && IsDirectorySeparator(path[3]) - ); - } - - /// <summary> - /// Returns true if the path uses any of the DOS device path syntaxes. ("\\.\", "\\?\", or "\??\") - /// </summary> - internal static bool IsDevice(StringBuffer path) - { - // If the path begins with any two separators it will be recognized and normalized and prepped with - // "\??\" for internal usage correctly. "\??\" is recognized and handled, "/??/" is not. - return IsExtended(path) - || - ( - path.Length >= DevicePrefixLength - && IsDirectorySeparator(path[0]) - && IsDirectorySeparator(path[1]) - && (path[2] == '.' || path[2] == '?') - && IsDirectorySeparator(path[3]) - ); - } - - /// <summary> - /// Returns true if the path uses the canonical form of extended syntax ("\\?\" or "\??\"). If the - /// path matches exactly (cannot use alternate directory separators) Windows will skip normalization - /// and path length checks. - /// </summary> - internal static bool IsExtended(string path) - { - // While paths like "//?/C:/" will work, they're treated the same as "\\.\" paths. - // Skipping of normalization will *only* occur if back slashes ('\') are used. - return path.Length >= DevicePrefixLength - && path[0] == '\\' - && (path[1] == '\\' || path[1] == '?') - && path[2] == '?' - && path[3] == '\\'; - } - - /// <summary> - /// Returns true if the path uses the canonical form of extended syntax ("\\?\" or "\??\"). If the - /// path matches exactly (cannot use alternate directory separators) Windows will skip normalization - /// and path length checks. - /// </summary> - internal static bool IsExtended(StringBuilder path) - { - // While paths like "//?/C:/" will work, they're treated the same as "\\.\" paths. - // Skipping of normalization will *only* occur if back slashes ('\') are used. - return path.Length >= DevicePrefixLength - && path[0] == '\\' - && (path[1] == '\\' || path[1] == '?') - && path[2] == '?' - && path[3] == '\\'; - } - - /// <summary> - /// Returns true if the path uses the canonical form of extended syntax ("\\?\" or "\??\"). If the - /// path matches exactly (cannot use alternate directory separators) Windows will skip normalization - /// and path length checks. - /// </summary> - internal static bool IsExtended(StringBuffer path) - { - // While paths like "//?/C:/" will work, they're treated the same as "\\.\" paths. - // Skipping of normalization will *only* occur if back slashes ('\') are used. - return path.Length >= DevicePrefixLength - && path[0] == '\\' - && (path[1] == '\\' || path[1] == '?') - && path[2] == '?' - && path[3] == '\\'; - } - - /// <summary> - /// Returns true if the path uses the extended UNC syntax (\\?\UNC\ or \??\UNC\) - /// </summary> - internal static bool IsExtendedUnc(string path) - { - return path.Length >= UncExtendedPathPrefix.Length - && IsExtended(path) - && char.ToUpper(path[4]) == 'U' - && char.ToUpper(path[5]) == 'N' - && char.ToUpper(path[6]) == 'C' - && path[7] == '\\'; - } - - /// <summary> - /// Returns true if the path uses the extended UNC syntax (\\?\UNC\ or \??\UNC\) - /// </summary> - internal static bool IsExtendedUnc(StringBuilder path) - { - return path.Length >= UncExtendedPathPrefix.Length - && IsExtended(path) - && char.ToUpper(path[4]) == 'U' - && char.ToUpper(path[5]) == 'N' - && char.ToUpper(path[6]) == 'C' - && path[7] == '\\'; - } - - /// <summary> - /// Returns a value indicating if the given path contains invalid characters (", <, >, | - /// NUL, or any ASCII char whose integer representation is in the range of 1 through 31). - /// Does not check for wild card characters ? and *. - /// - /// Will not check if the path is a device path and not in Legacy mode as many of these - /// characters are valid for devices (pipes for example). - /// </summary> - internal static bool HasIllegalCharacters(string path, bool checkAdditional = false) - { - if ( -#if FEATURE_PATHCOMPAT - !AppContextSwitches.UseLegacyPathHandling && -#endif - IsDevice(path)) - { - return false; - } - - return AnyPathHasIllegalCharacters(path, checkAdditional: checkAdditional); - } - - /// <summary> - /// Version of HasIllegalCharacters that checks no AppContextSwitches. Only use if you know you need to skip switches and don't care - /// about proper device path handling. - /// </summary> - internal static bool AnyPathHasIllegalCharacters(string path, bool checkAdditional = false) - { - return path.IndexOfAny(InvalidPathChars) >= 0 -#if !PLATFORM_UNIX - || (checkAdditional && AnyPathHasWildCardCharacters(path)) -#endif - ; - } - - /// <summary> - /// Check for ? and *. - /// </summary> - internal static bool HasWildCardCharacters(string path) - { - // Question mark is part of some device paths - int startIndex = -#if FEATURE_PATHCOMPAT - AppContextSwitches.UseLegacyPathHandling ? 0 : -#endif - IsDevice(path) ? ExtendedPathPrefix.Length : 0; - return AnyPathHasWildCardCharacters(path, startIndex: startIndex); - } - - /// <summary> - /// Version of HasWildCardCharacters that checks no AppContextSwitches. Only use if you know you need to skip switches and don't care - /// about proper device path handling. - /// </summary> - internal static bool AnyPathHasWildCardCharacters(string path, int startIndex = 0) - { - char currentChar; - for (int i = startIndex; i < path.Length; i++) - { - currentChar = path[i]; - if (currentChar == '*' || currentChar == '?') return true; - } - return false; - } - - /// <summary> - /// Gets the length of the root of the path (drive, share, etc.). - /// </summary> - [System.Security.SecuritySafeCritical] - internal unsafe static int GetRootLength(string path) - { - fixed (char* value = path) - { - return (int)GetRootLength(value, (ulong)path.Length); - } - } - - /// <summary> - /// Gets the length of the root of the path (drive, share, etc.). - /// </summary> - [System.Security.SecuritySafeCritical] - internal unsafe static uint GetRootLength(StringBuffer path) - { - if (path.Length == 0) return 0; - return GetRootLength(path.CharPointer, path.Length); - } - - [System.Security.SecurityCritical] - private unsafe static uint GetRootLength(char* path, ulong pathLength) - { - uint i = 0; - -#if PLATFORM_UNIX - if (pathLength >= 1 && (IsDirectorySeparator(path[0]))) - i = 1; -#else - uint volumeSeparatorLength = 2; // Length to the colon "C:" - uint uncRootLength = 2; // Length to the start of the server name "\\" - - bool extendedSyntax = StartsWithOrdinal(path, pathLength, ExtendedPathPrefix); - bool extendedUncSyntax = StartsWithOrdinal(path, pathLength, UncExtendedPathPrefix); - if (extendedSyntax) - { - // Shift the position we look for the root from to account for the extended prefix - if (extendedUncSyntax) - { - // "\\" -> "\\?\UNC\" - uncRootLength = (uint)UncExtendedPathPrefix.Length; - } - else - { - // "C:" -> "\\?\C:" - volumeSeparatorLength += (uint)ExtendedPathPrefix.Length; - } - } - - if ((!extendedSyntax || extendedUncSyntax) && pathLength > 0 && IsDirectorySeparator(path[0])) - { - // UNC or simple rooted path (e.g. "\foo", NOT "\\?\C:\foo") - - i = 1; // Drive rooted (\foo) is one character - if (extendedUncSyntax || (pathLength > 1 && IsDirectorySeparator(path[1]))) - { - // UNC (\\?\UNC\ or \\), scan past the next two directory separators at most - // (e.g. to \\?\UNC\Server\Share or \\Server\Share\) - i = uncRootLength; - int n = 2; // Maximum separators to skip - while (i < pathLength && (!IsDirectorySeparator(path[i]) || --n > 0)) i++; - } - } - else if (pathLength >= volumeSeparatorLength && path[volumeSeparatorLength - 1] == Path.VolumeSeparatorChar) - { - // Path is at least longer than where we expect a colon, and has a colon (\\?\A:, A:) - // If the colon is followed by a directory separator, move past it - i = volumeSeparatorLength; - if (pathLength >= volumeSeparatorLength + 1 && IsDirectorySeparator(path[volumeSeparatorLength])) i++; - } -#endif // !PLATFORM_UNIX - return i; - } - - [System.Security.SecurityCritical] - private unsafe static bool StartsWithOrdinal(char* source, ulong sourceLength, string value) - { - if (sourceLength < (ulong)value.Length) return false; - for (int i = 0; i < value.Length; i++) - { - if (value[i] != source[i]) return false; - } - return true; - } - - /// <summary> - /// Returns true if the path specified is relative to the current drive or working directory. - /// Returns false if the path is fixed to a specific drive or UNC path. This method does no - /// validation of the path (URIs will be returned as relative as a result). - /// </summary> - /// <remarks> - /// Handles paths that use the alternate directory separator. It is a frequent mistake to - /// assume that rooted paths (Path.IsPathRooted) are not relative. This isn't the case. - /// "C:a" is drive relative- meaning that it will be resolved against the current directory - /// for C: (rooted, but relative). "C:\a" is rooted and not relative (the current directory - /// will not be used to modify the path). - /// </remarks> - internal static bool IsPartiallyQualified(string path) - { -#if PLATFORM_UNIX - return !(path.Length >= 1 && path[0] == Path.DirectorySeparatorChar); -#else - if (path.Length < 2) - { - // It isn't fixed, it must be relative. There is no way to specify a fixed - // path with one character (or less). - return true; - } - - if (IsDirectorySeparator(path[0])) - { - // There is no valid way to specify a relative path with two initial slashes or - // \? as ? isn't valid for drive relative paths and \??\ is equivalent to \\?\ - return !(path[1] == '?' || IsDirectorySeparator(path[1])); - } - - // 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.VolumeSeparatorChar) - && IsDirectorySeparator(path[2]) - // To match old behavior we'll check the drive character for validity as the path is technically - // not qualified if you don't have a valid drive. "=:\" is the "=" file's default data stream. - && IsValidDriveChar(path[0])); -#endif // !PLATFORM_UNIX - } - - /// <summary> - /// Returns true if the path specified is relative to the current drive or working directory. - /// Returns false if the path is fixed to a specific drive or UNC path. This method does no - /// validation of the path (URIs will be returned as relative as a result). - /// </summary> - /// <remarks> - /// Handles paths that use the alternate directory separator. It is a frequent mistake to - /// assume that rooted paths (Path.IsPathRooted) are not relative. This isn't the case. - /// "C:a" is drive relative- meaning that it will be resolved against the current directory - /// for C: (rooted, but relative). "C:\a" is rooted and not relative (the current directory - /// will not be used to modify the path). - /// </remarks> - internal static bool IsPartiallyQualified(StringBuffer path) - { -#if PLATFORM_UNIX - return !(path.Length >= 1 && path[0] == Path.DirectorySeparatorChar); -#else - if (path.Length < 2) - { - // It isn't fixed, it must be relative. There is no way to specify a fixed - // path with one character (or less). - return true; - } - - if (IsDirectorySeparator(path[0])) - { - // There is no valid way to specify a relative path with two initial slashes or - // \? as ? isn't valid for drive relative paths and \??\ is equivalent to \\?\ - return !(path[1] == '?' || IsDirectorySeparator(path[1])); - } - - // 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.VolumeSeparatorChar) - && IsDirectorySeparator(path[2]) - // To match old behavior we'll check the drive character for validity as the path is technically - // not qualified if you don't have a valid drive. "=:\" is the "=" file's default data stream. - && IsValidDriveChar(path[0])); -#endif // !PLATFORM_UNIX - } - - /// <summary> - /// On Windows, returns the characters to skip at the start of the path if it starts with space(s) and a drive or directory separator. - /// (examples are " C:", " \") - /// This is a legacy behavior of Path.GetFullPath(). - /// </summary> - /// <remarks> - /// Note that this conflicts with IsPathRooted() which doesn't (and never did) such a skip. - /// </remarks> - internal static int PathStartSkip(string path) - { -#if !PLATFORM_UNIX - int startIndex = 0; - while (startIndex < path.Length && path[startIndex] == ' ') startIndex++; - - if (startIndex > 0 && (startIndex < path.Length && IsDirectorySeparator(path[startIndex])) - || (startIndex + 1 < path.Length && path[startIndex + 1] == Path.VolumeSeparatorChar && IsValidDriveChar(path[startIndex]))) - { - // Go ahead and skip spaces as we're either " C:" or " \" - return startIndex; - } -#endif - - return 0; - } - - /// <summary> - /// True if the given character is a directory separator. - /// </summary> - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static bool IsDirectorySeparator(char c) - { - return c == Path.DirectorySeparatorChar -#if !PLATFORM_UNIX - || c == Path.AltDirectorySeparatorChar -#endif - ; - } - - /// <summary> - /// Normalize separators in the given path. Converts forward slashes into back slashes and compresses slash runs, keeping initial 2 if present. - /// Also trims initial whitespace in front of "rooted" paths (see PathStartSkip). - /// - /// This effectively replicates the behavior of the legacy NormalizePath when it was called with fullCheck=false and expandShortpaths=false. - /// The current NormalizePath gets directory separator normalization from Win32's GetFullPathName(), which will resolve relative paths and as - /// such can't be used here (and is overkill for our uses). - /// - /// Like the current NormalizePath this will not try and analyze periods/spaces within directory segments. - /// </summary> - /// <remarks> - /// The only callers that used to use Path.Normalize(fullCheck=false) were Path.GetDirectoryName() and Path.GetPathRoot(). Both usages do - /// not need trimming of trailing whitespace here. - /// - /// GetPathRoot() could technically skip normalizing separators after the second segment- consider as a future optimization. - /// - /// For legacy desktop behavior with ExpandShortPaths: - /// - It has no impact on GetPathRoot() so doesn't need consideration. - /// - It could impact GetDirectoryName(), but only if the path isn't relative (C:\ or \\Server\Share). - /// - /// In the case of GetDirectoryName() the ExpandShortPaths behavior was undocumented and provided inconsistent results if the path was - /// fixed/relative. For example: "C:\PROGRA~1\A.TXT" would return "C:\Program Files" while ".\PROGRA~1\A.TXT" would return ".\PROGRA~1". If you - /// ultimately call GetFullPath() this doesn't matter, but if you don't or have any intermediate string handling could easily be tripped up by - /// this undocumented behavior. - /// </remarks> - internal static string NormalizeDirectorySeparators(string path) - { - if (string.IsNullOrEmpty(path)) return path; - - char current; - int start = PathStartSkip(path); - - if (start == 0) - { - // Make a pass to see if we need to normalize so we can potentially skip allocating - bool normalized = true; - - for (int i = 0; i < path.Length; i++) - { - current = path[i]; - if (IsDirectorySeparator(current) - && (current != Path.DirectorySeparatorChar -#if !PLATFORM_UNIX - // Check for sequential separators past the first position (we need to keep initial two for UNC/extended) - || (i > 0 && i + 1 < path.Length && IsDirectorySeparator(path[i + 1])) -#endif - )) - { - normalized = false; - break; - } - } - - if (normalized) return path; - } - - StringBuilder builder = StringBuilderCache.Acquire(path.Length); - -#if !PLATFORM_UNIX - // On Windows we always keep the first separator, even if the next is a separator (we need to keep initial two for UNC/extended) - if (IsDirectorySeparator(path[start])) - { - start++; - builder.Append(Path.DirectorySeparatorChar); - } -#endif - - for (int i = start; i < path.Length; i++) - { - current = path[i]; - - // If we have a separator - if (IsDirectorySeparator(current)) - { - // If the next is a separator, skip adding this - if (i + 1 < path.Length && IsDirectorySeparator(path[i + 1])) - { - continue; - } - - // Ensure it is the primary separator - current = Path.DirectorySeparatorChar; - } - - builder.Append(current); - } - - return StringBuilderCache.GetStringAndRelease(builder); - } - -#if PLATFORM_UNIX - // We rely on Windows to remove relative segments on Windows. This would need to be updated to - // handle the proper rooting on Windows if we for some reason need it. - - /// <summary> - /// Try to remove relative segments from the given path (without combining with a root). - /// </summary> - /// <param name="skip">Skip the specified number of characters before evaluating.</param> - internal static string RemoveRelativeSegments(string path, int skip = 0) - { - bool flippedSeparator = false; - - // Remove "//", "/./", and "/../" from the path by 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); - if (skip > 0) - { - sb.Append(path, 0, skip); - } - - int componentCharCount = 0; - for (int i = skip; i < path.Length; i++) - { - char c = path[i]; - - if (PathInternal.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 (PathInternal.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 || PathInternal.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 || PathInternal.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 (PathInternal.IsDirectorySeparator(sb[s])) - { - sb.Length = s; - break; - } - } - if (s < 0) - { - sb.Length = 0; - } - - i += 2; - continue; - } - } - - if (++componentCharCount > PathInternal.MaxComponentLength) - { - throw new PathTooLongException(); - } - - // Normalize the directory separator if needed - if (c != Path.DirectorySeparatorChar && c == Path.AltDirectorySeparatorChar) - { - c = Path.DirectorySeparatorChar; - flippedSeparator = true; - } - - sb.Append(c); - } - - if (flippedSeparator || sb.Length != path.Length) - { - return StringBuilderCache.GetStringAndRelease(sb); - } - else - { - // We haven't changed the source path, return the original - StringBuilderCache.Release(sb); - return path; - } - } -#endif // PLATFORM_UNIX - } -}
\ No newline at end of file diff --git a/src/mscorlib/src/System/IO/__Error.cs b/src/mscorlib/src/System/IO/__Error.cs index a31d9657e8..c67bc964f0 100644 --- a/src/mscorlib/src/System/IO/__Error.cs +++ b/src/mscorlib/src/System/IO/__Error.cs @@ -85,7 +85,7 @@ namespace System.IO { bool isFullyQualified = false; if (path.Length < 2) return path; - if (Path.IsDirectorySeparator(path[0]) && Path.IsDirectorySeparator(path[1])) + if (PathInternal.IsDirectorySeparator(path[0]) && PathInternal.IsDirectorySeparator(path[1])) isFullyQualified = true; else if (path[1] == Path.VolumeSeparatorChar) { isFullyQualified = true; @@ -116,7 +116,7 @@ namespace System.IO { } if (!safeToReturn) { - if (Path.IsDirectorySeparator(path[path.Length - 1])) + if (PathInternal.IsDirectorySeparator(path[path.Length - 1])) path = Environment.GetResourceString("IO.IO_NoPermissionToDirectoryName"); else path = Path.GetFileName(path); diff --git a/src/mscorlib/src/System/Reflection/Assembly.cs b/src/mscorlib/src/System/Reflection/Assembly.cs index 04f7faef97..dfa74c341c 100644 --- a/src/mscorlib/src/System/Reflection/Assembly.cs +++ b/src/mscorlib/src/System/Reflection/Assembly.cs @@ -114,7 +114,7 @@ namespace System.Reflection { if(assemblyFile == null) throw new ArgumentNullException(nameof(assemblyFile)); - string fullPath = Path.GetFullPathInternal(assemblyFile); + string fullPath = Path.GetFullPath(assemblyFile); return AssemblyLoadContext.Default.LoadFromAssemblyPath(fullPath); } #else @@ -584,15 +584,15 @@ namespace System.Reflection if(path == null) throw new ArgumentNullException(nameof(path)); - if (Path.IsRelative(path)) + if (PathInternal.IsPartiallyQualified(path)) { throw new ArgumentException(Environment.GetResourceString("Argument_AbsolutePathRequired"), nameof(path)); } - string normalizedPath = Path.GetFullPathInternal(path); + string normalizedPath = Path.GetFullPath(path); lock(s_loadfile) - { + { if(s_loadfile.TryGetValue(normalizedPath, out result)) return result; AssemblyLoadContext alc = new IndividualAssemblyLoadContext(); @@ -2396,10 +2396,10 @@ namespace System.Reflection else if ((len > 2) && (codebase[0] == '\\') && (codebase[1] == '\\')) return "file://" + codebase; else - return "file:///" + Path.GetFullPathInternal( codebase ); + return "file:///" + Path.GetFullPath(codebase); #else else - return "file://" + Path.GetFullPathInternal( codebase ); + return "file://" + Path.GetFullPath(codebase); #endif // !PLATFORM_UNIX } diff --git a/src/mscorlib/src/System/Reflection/AssemblyName.cs b/src/mscorlib/src/System/Reflection/AssemblyName.cs index c45a4619ea..abcbd8939d 100644 --- a/src/mscorlib/src/System/Reflection/AssemblyName.cs +++ b/src/mscorlib/src/System/Reflection/AssemblyName.cs @@ -199,7 +199,7 @@ namespace System.Reflection { // Assembly.GetNameInternal() will not demand path discovery // permission, so do that first. - String fullPath = Path.GetFullPathInternal(assemblyFile); + string fullPath = Path.GetFullPath(assemblyFile); new FileIOPermission( FileIOPermissionAccess.PathDiscovery, fullPath ).Demand(); return nGetFileInformation(fullPath); } diff --git a/src/mscorlib/src/System/Reflection/Emit/ModuleBuilder.cs b/src/mscorlib/src/System/Reflection/Emit/ModuleBuilder.cs index d2a95b0626..c474b703f4 100644 --- a/src/mscorlib/src/System/Reflection/Emit/ModuleBuilder.cs +++ b/src/mscorlib/src/System/Reflection/Emit/ModuleBuilder.cs @@ -589,7 +589,7 @@ namespace System.Reflection.Emit } #endregion - + #region Module Overrides // m_internalModuleBuilder is null iff this is a "internal" ModuleBuilder @@ -962,7 +962,7 @@ namespace System.Reflection.Emit if (ContainingAssemblyBuilder.m_assemblyData.m_strDir != null) { fullyQualifiedName = Path.Combine(ContainingAssemblyBuilder.m_assemblyData.m_strDir, fullyQualifiedName); - fullyQualifiedName = Path.UnsafeGetFullPath(fullyQualifiedName); + fullyQualifiedName = Path.GetFullPath(fullyQualifiedName); } if (ContainingAssemblyBuilder.m_assemblyData.m_strDir != null && fullyQualifiedName != null) diff --git a/src/mscorlib/src/System/Reflection/Module.cs b/src/mscorlib/src/System/Reflection/Module.cs index 6d492d8e74..e19a569cbd 100644 --- a/src/mscorlib/src/System/Reflection/Module.cs +++ b/src/mscorlib/src/System/Reflection/Module.cs @@ -1082,7 +1082,7 @@ namespace System.Reflection if (fullyQualifiedName != null) { bool checkPermission = true; try { - Path.GetFullPathInternal(fullyQualifiedName); + Path.GetFullPath(fullyQualifiedName); } catch(ArgumentException) { checkPermission = false; diff --git a/src/mscorlib/src/System/Runtime/Loader/AssemblyLoadContext.cs b/src/mscorlib/src/System/Runtime/Loader/AssemblyLoadContext.cs index fd896a6b42..37ac3096ab 100644 --- a/src/mscorlib/src/System/Runtime/Loader/AssemblyLoadContext.cs +++ b/src/mscorlib/src/System/Runtime/Loader/AssemblyLoadContext.cs @@ -104,7 +104,7 @@ namespace System.Runtime.Loader throw new ArgumentNullException(nameof(assemblyPath)); } - if (Path.IsRelative(assemblyPath)) + if (PathInternal.IsPartiallyQualified(assemblyPath)) { throw new ArgumentException( Environment.GetResourceString("Argument_AbsolutePathRequired"), nameof(assemblyPath)); } @@ -121,12 +121,12 @@ namespace System.Runtime.Loader throw new ArgumentNullException(nameof(nativeImagePath)); } - if (Path.IsRelative(nativeImagePath)) + if (PathInternal.IsPartiallyQualified(nativeImagePath)) { throw new ArgumentException( Environment.GetResourceString("Argument_AbsolutePathRequired"), nameof(nativeImagePath)); } - if (assemblyPath != null && Path.IsRelative(assemblyPath)) + if (assemblyPath != null && PathInternal.IsPartiallyQualified(assemblyPath)) { throw new ArgumentException(Environment.GetResourceString("Argument_AbsolutePathRequired"), nameof(assemblyPath)); } @@ -308,7 +308,7 @@ namespace System.Runtime.Loader { throw new ArgumentException(Environment.GetResourceString("Argument_EmptyPath"), nameof(unmanagedDllPath)); } - if (Path.IsRelative(unmanagedDllPath)) + if (PathInternal.IsPartiallyQualified(unmanagedDllPath)) { throw new ArgumentException(Environment.GetResourceString("Argument_AbsolutePathRequired"), nameof(unmanagedDllPath)); } @@ -391,8 +391,8 @@ namespace System.Runtime.Loader { throw new ArgumentNullException(nameof(assemblyPath)); } - - String fullPath = Path.GetFullPathInternal(assemblyPath); + + string fullPath = Path.GetFullPath(assemblyPath); return nGetFileInformation(fullPath); } diff --git a/src/mscorlib/src/System/Security/Permissions/FileIOPermission.cs b/src/mscorlib/src/System/Security/Permissions/FileIOPermission.cs index b4d4141f82..3ae193b96f 100644 --- a/src/mscorlib/src/System/Security/Permissions/FileIOPermission.cs +++ b/src/mscorlib/src/System/Security/Permissions/FileIOPermission.cs @@ -558,7 +558,7 @@ namespace System.Security.Permissions { throw new ArgumentException(Environment.GetResourceString("Argument_InvalidPathChars")); if (!onlyCheckExtras) - Path.CheckInvalidPathChars(str[i]); + PathInternal.CheckInvalidPathChars(str[i]); } #else // There are no "extras" on Unix @@ -567,7 +567,7 @@ namespace System.Security.Permissions { for (int i = 0; i < str.Length; ++i) { - Path.CheckInvalidPathChars(str[i]); + PathInternal.CheckInvalidPathChars(str[i]); } #endif } @@ -1090,15 +1090,18 @@ namespace System.Security.Permissions { // These checks are done via CheckIllegalCharacters() and StringExpressionSet in AddPathList() above. // // We have to check the beginning as some paths may be passed in as path + @"\.", which will be normalized away. +#if !FEATURE_CORECLR BCLDebug.Assert( fullPath.StartsWith(Path.NormalizePath(fullPath, fullCheck: false), StringComparison.OrdinalIgnoreCase), string.Format("path isn't normalized: {0}", fullPath)); +#endif +#if !PLATFORM_UNIX // Checking for colon / invalid characters on device paths blocks legitimate access to objects such as named pipes. if ( -#if FEATURE_PATHCOMPAT + #if FEATURE_PATHCOMPAT AppContextSwitches.UseLegacyPathHandling || -#endif + #endif !PathInternal.IsDevice(fullPath)) { // GetFullPath already checks normal invalid path characters. We need to just check additional (wildcard) characters here. @@ -1113,6 +1116,7 @@ namespace System.Security.Permissions { throw new NotSupportedException(Environment.GetResourceString("Argument_PathFormatNotSupported")); } } +#endif // !PLATFORM_UNIX } } diff --git a/src/mscorlib/src/System/Security/Util/StringExpressionSet.cs b/src/mscorlib/src/System/Security/Util/StringExpressionSet.cs index 03d6998c6d..c12ca2ff6f 100644 --- a/src/mscorlib/src/System/Security/Util/StringExpressionSet.cs +++ b/src/mscorlib/src/System/Security/Util/StringExpressionSet.cs @@ -165,7 +165,7 @@ namespace System.Security.Util { { if (m_throwOnRelative) { - if (Path.IsRelative(temp)) + if (PathInternal.IsPartiallyQualified(temp)) { throw new ArgumentException( Environment.GetResourceString( "Argument_AbsolutePathRequired" ) ); } @@ -325,7 +325,7 @@ namespace System.Security.Util { { if (m_throwOnRelative) { - if (Path.IsRelative(temp)) + if (PathInternal.IsPartiallyQualified(temp)) { throw new ArgumentException( Environment.GetResourceString( "Argument_AbsolutePathRequired" ) ); } @@ -742,7 +742,7 @@ namespace System.Security.Util { { if (needFullPath) { - string newPath = Path.GetFullPathInternal(path); + string newPath = Path.GetFullPath(path); if (path.EndsWith(m_directorySeparator + ".", StringComparison.Ordinal)) { if (newPath.EndsWith(m_directorySeparator)) diff --git a/src/mscorlib/src/System/Security/Util/URLString.cs b/src/mscorlib/src/System/Security/Util/URLString.cs index 997cb2cf12..bc05104e51 100644 --- a/src/mscorlib/src/System/Security/Util/URLString.cs +++ b/src/mscorlib/src/System/Security/Util/URLString.cs @@ -484,10 +484,14 @@ namespace System.Security.Util { private static void CheckPathTooLong(StringBuilder path) { if (path.Length >= ( -#if FEATURE_PATHCOMPAT +#if PLATFORM_UNIX + Interop.Sys.MaxPath)) +#else + #if FEATURE_PATHCOMPAT AppContextSwitches.BlockLongPaths ? PathInternal.MaxShortPath : -#endif + #endif PathInternal.MaxLongPath)) +#endif { throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong")); } @@ -513,7 +517,7 @@ namespace System.Security.Util { // file:/home/johndoe/here // file:../johndoe/here // file:~/johndoe/here - String temp = url; + String temp = url; int nbSlashes = 0; while(nbSlashes<temp.Length && '/'==temp[nbSlashes]) nbSlashes++; @@ -533,7 +537,7 @@ namespace System.Security.Util { { String temp = url; -#if !PLATFORM_UNIX +#if !PLATFORM_UNIX int index = temp.IndexOf( '/'); if (index != -1 && @@ -651,7 +655,7 @@ namespace System.Security.Util { } else { -#if !PLATFORM_UNIX +#if !PLATFORM_UNIX String site = temp.Substring( 0, index ); m_localSite = null; m_siteString = new SiteString( site ); diff --git a/src/mscorlib/src/System/Threading/Mutex.cs b/src/mscorlib/src/System/Threading/Mutex.cs index 434ab37b6d..98789d9301 100644 --- a/src/mscorlib/src/System/Threading/Mutex.cs +++ b/src/mscorlib/src/System/Threading/Mutex.cs @@ -182,7 +182,7 @@ namespace System.Threading #if PLATFORM_UNIX case Win32Native.ERROR_FILENAME_EXCED_RANGE: // On Unix, length validation is done by CoreCLR's PAL after converting to utf-8 - throw new ArgumentException(Environment.GetResourceString("Argument_WaitHandleNameTooLong", Path.MaxPathComponentLength), "name"); + throw new ArgumentException(Environment.GetResourceString("Argument_WaitHandleNameTooLong", PathInternal.MaxComponentLength), "name"); #endif case Win32Native.ERROR_INVALID_HANDLE: @@ -357,7 +357,7 @@ namespace System.Threading if (name != null && errorCode == Win32Native.ERROR_FILENAME_EXCED_RANGE) { // On Unix, length validation is done by CoreCLR's PAL after converting to utf-8 - throw new ArgumentException(Environment.GetResourceString("Argument_WaitHandleNameTooLong", Path.MaxPathComponentLength), nameof(name)); + throw new ArgumentException(Environment.GetResourceString("Argument_WaitHandleNameTooLong", PathInternal.MaxComponentLength), nameof(name)); } #endif diff --git a/src/mscorlib/src/mscorlib.txt b/src/mscorlib/src/mscorlib.txt index 7574417107..e3904efb49 100644 --- a/src/mscorlib/src/mscorlib.txt +++ b/src/mscorlib/src/mscorlib.txt @@ -1277,6 +1277,7 @@ InvalidOperation_ClaimCannotBeRemoved = The Claim '{0}' was not able to be remov InvalidOperationException_ActorGraphCircular = Actor cannot be set so that circular directed graph will exist chaining the subjects together. InvalidOperation_AsyncIOInProgress = The stream is currently in use by a previous operation on the stream. InvalidOperation_APIInvalidForCurrentContext = The API '{0}' cannot be used on the current platform. See http://go.microsoft.com/fwlink/?LinkId=248273 for more information. +InvalidOperation_Cryptography = Unable to use cryptographic functionality. ; InvalidProgramException InvalidProgram_Default = Common Language Runtime detected an invalid program. |