/* Copyright (C) 2008-2016 Peter Palotas, Jeffrey Jangli, Alexandr Normuradov * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ using System; using System.Security; using System.Text; namespace Alphaleonis.Win32.Filesystem { public static partial class Path { #region .NET /// Returns the absolute path for the specified path string. /// The fully qualified location of path, such as "C:\MyFile.txt". /// /// GetFullPathName merges the name of the current drive and directory with a specified file name to determine the full path and file name of a specified file. /// It also calculates the address of the file name portion of the full path and file name. ///   /// This method does not verify that the resulting path and file name are valid, or that they see an existing file on the associated volume. /// The .NET Framework does not support direct access to physical disks through paths that are device names, such as "\\.\PHYSICALDRIVE0". ///   /// MSDN: Multithreaded applications and shared library code should not use the GetFullPathName function and /// should avoid using relative path names. The current directory state written by the SetCurrentDirectory function is stored as a global variable in each process, /// therefore multithreaded applications cannot reliably use this value without possible data corruption from other threads that may also be reading or setting this value. /// This limitation also applies to the SetCurrentDirectory and GetCurrentDirectory functions. The exception being when the application is guaranteed to be running in a single thread, /// for example parsing file names from the command line argument string in the main thread prior to creating any additional threads. /// Using relative path names in multithreaded applications or shared library code can yield unpredictable results and is not supported. /// /// /// /// /// The file or directory for which to obtain absolute path information. [SecurityCritical] public static string GetFullPath(string path) { return GetFullPathTackleCore(null, path, GetFullPathOptions.None); } #endregion // .NET #region AlphaFS /// Returns the absolute path for the specified path string. /// The fully qualified location of path, such as "C:\MyFile.txt". /// /// GetFullPathName merges the name of the current drive and directory with a specified file name to determine the full path and file name of a specified file. /// It also calculates the address of the file name portion of the full path and file name. ///   /// This method does not verify that the resulting path and file name are valid, or that they see an existing file on the associated volume. /// The .NET Framework does not support direct access to physical disks through paths that are device names, such as "\\.\PHYSICALDRIVE0". ///   /// MSDN: Multithreaded applications and shared library code should not use the GetFullPathName function and /// should avoid using relative path names. The current directory state written by the SetCurrentDirectory function is stored as a global variable in each process, /// therefore multithreaded applications cannot reliably use this value without possible data corruption from other threads that may also be reading or setting this value. /// This limitation also applies to the SetCurrentDirectory and GetCurrentDirectory functions. The exception being when the application is guaranteed to be running in a single thread, /// for example parsing file names from the command line argument string in the main thread prior to creating any additional threads. /// Using relative path names in multithreaded applications or shared library code can yield unpredictable results and is not supported. /// /// /// /// /// The file or directory for which to obtain absolute path information. /// Options for controlling the full path retrieval. [SecurityCritical] public static string GetFullPath(string path, GetFullPathOptions options) { return GetFullPathTackleCore(null, path, options); } /// [AlphaFS] Returns the absolute path for the specified path string. /// The fully qualified location of path, such as "C:\MyFile.txt". /// /// GetFullPathName merges the name of the current drive and directory with a specified file name to determine the full path and file name of a specified file. /// It also calculates the address of the file name portion of the full path and file name. ///   /// This method does not verify that the resulting path and file name are valid, or that they see an existing file on the associated volume. /// The .NET Framework does not support direct access to physical disks through paths that are device names, such as "\\.\PHYSICALDRIVE0". ///   /// MSDN: Multithreaded applications and shared library code should not use the GetFullPathName function and /// should avoid using relative path names. The current directory state written by the SetCurrentDirectory function is stored as a global variable in each process, /// therefore multithreaded applications cannot reliably use this value without possible data corruption from other threads that may also be reading or setting this value. /// This limitation also applies to the SetCurrentDirectory and GetCurrentDirectory functions. The exception being when the application is guaranteed to be running in a single thread, /// for example parsing file names from the command line argument string in the main thread prior to creating any additional threads. /// Using relative path names in multithreaded applications or shared library code can yield unpredictable results and is not supported. /// /// /// /// /// The transaction. /// The file or directory for which to obtain absolute path information. [SecurityCritical] public static string GetFullPathTransacted(KernelTransaction transaction, string path) { return GetFullPathTackleCore(transaction, path, GetFullPathOptions.None); } /// [AlphaFS] Returns the absolute path for the specified path string. /// The fully qualified location of path, such as "C:\MyFile.txt". /// /// GetFullPathName merges the name of the current drive and directory with a specified file name to determine the full path and file name of a specified file. /// It also calculates the address of the file name portion of the full path and file name. ///   /// This method does not verify that the resulting path and file name are valid, or that they see an existing file on the associated volume. /// The .NET Framework does not support direct access to physical disks through paths that are device names, such as "\\.\PHYSICALDRIVE0". ///   /// MSDN: Multithreaded applications and shared library code should not use the GetFullPathName function and /// should avoid using relative path names. The current directory state written by the SetCurrentDirectory function is stored as a global variable in each process, /// therefore multithreaded applications cannot reliably use this value without possible data corruption from other threads that may also be reading or setting this value. /// This limitation also applies to the SetCurrentDirectory and GetCurrentDirectory functions. The exception being when the application is guaranteed to be running in a single thread, /// for example parsing file names from the command line argument string in the main thread prior to creating any additional threads. /// Using relative path names in multithreaded applications or shared library code can yield unpredictable results and is not supported. /// /// /// /// /// The transaction. /// The file or directory for which to obtain absolute path information. /// Options for controlling the full path retrieval. [SecurityCritical] public static string GetFullPathTransacted(KernelTransaction transaction, string path, GetFullPathOptions options) { return GetFullPathTackleCore(transaction, path, options); } #endregion // AlphaFS #region Internal Methods /// Retrieves the absolute path for the specified string. /// The fully qualified location of , such as "C:\MyFile.txt". /// /// GetFullPathName merges the name of the current drive and directory with a specified file name to determine the full path and file name of a specified file. /// It also calculates the address of the file name portion of the full path and file name. ///   /// This method does not verify that the resulting path and file name are valid, or that they see an existing file on the associated volume. /// The .NET Framework does not support direct access to physical disks through paths that are device names, such as "\\.\PHYSICALDRIVE0". ///   /// MSDN: Multithreaded applications and shared library code should not use the GetFullPathName function and /// should avoid using relative path names. The current directory state written by the SetCurrentDirectory function is stored as a global variable in each process, /// therefore multithreaded applications cannot reliably use this value without possible data corruption from other threads that may also be reading or setting this value. /// This limitation also applies to the SetCurrentDirectory and GetCurrentDirectory functions. The exception being when the application is guaranteed to be running in a single thread, /// for example parsing file names from the command line argument string in the main thread prior to creating any additional threads. /// Using relative path names in multithreaded applications or shared library code can yield unpredictable results and is not supported. /// /// /// /// The transaction. /// The file or directory for which to obtain absolute path information. /// Options for controlling the full path retrieval. [SecurityCritical] internal static string GetFullPathCore(KernelTransaction transaction, string path, GetFullPathOptions options) { if (path != null) if (path.StartsWith(GlobalRootPrefix, StringComparison.OrdinalIgnoreCase) ||path.StartsWith(VolumePrefix, StringComparison.OrdinalIgnoreCase)) return path; if (options != GetFullPathOptions.None) { if ((options & GetFullPathOptions.CheckInvalidPathChars) != 0) { var checkAdditional = (options & GetFullPathOptions.CheckAdditional) != 0; CheckInvalidPathChars(path, checkAdditional, false); // Prevent duplicate checks. options &= ~GetFullPathOptions.CheckInvalidPathChars; if (checkAdditional) options &= ~GetFullPathOptions.CheckAdditional; } // Do not remove trailing directory separator when path points to a drive like: "C:\" // Doing so makes path point to the current directory. if (path == null || path.Length <= 3 || (!path.StartsWith(LongPathPrefix, StringComparison.OrdinalIgnoreCase) && path[1] != VolumeSeparatorChar)) options &= ~GetFullPathOptions.RemoveTrailingDirectorySeparator; } var pathLp = GetLongPathCore(path, options); uint bufferSize = NativeMethods.MaxPathUnicode; using (new NativeMethods.ChangeErrorMode(NativeMethods.ErrorMode.FailCriticalErrors)) { startGetFullPathName: var buffer = new StringBuilder((int)bufferSize); var returnLength = (transaction == null || !NativeMethods.IsAtLeastWindowsVista // GetFullPathName() / GetFullPathNameTransacted() // In the ANSI version of this function, the name is limited to MAX_PATH characters. // To extend this limit to 32,767 wide characters, call the Unicode version of the function and prepend "\\?\" to the path. // 2013-04-15: MSDN confirms LongPath usage. ? NativeMethods.GetFullPathName(pathLp, bufferSize, buffer, IntPtr.Zero) : NativeMethods.GetFullPathNameTransacted(pathLp, bufferSize, buffer, IntPtr.Zero, transaction.SafeHandle)); if (returnLength != Win32Errors.NO_ERROR) { if (returnLength > bufferSize) { bufferSize = returnLength; goto startGetFullPathName; } } else { if ((options & GetFullPathOptions.ContinueOnNonExist) != 0) return null; NativeError.ThrowException(pathLp); } var finalFullPath = (options & GetFullPathOptions.AsLongPath) != 0 ? GetLongPathCore(buffer.ToString(), GetFullPathOptions.None) : GetRegularPathCore(buffer.ToString(), GetFullPathOptions.None, false); finalFullPath = NormalizePath(finalFullPath, options); if ((options & GetFullPathOptions.KeepDotOrSpace) != 0) { if (pathLp.EndsWith(".", StringComparison.OrdinalIgnoreCase)) finalFullPath += "."; var lastChar = pathLp[pathLp.Length - 1]; if (char.IsWhiteSpace(lastChar)) finalFullPath += lastChar; } return finalFullPath; } } private static string GetFullPathTackleCore(KernelTransaction transaction, string path, GetFullPathOptions options) { if (path != null) { if (path.StartsWith(GlobalRootPrefix, StringComparison.OrdinalIgnoreCase) || path.StartsWith(VolumePrefix, StringComparison.OrdinalIgnoreCase)) return path; CheckInvalidUncPath(path); } CheckSupportedPathFormat(path, true, true); return GetFullPathCore(transaction, path, options); } /// Applies the to /// with applied . /// /// /// /// private static string ApplyFullPathOptions(string path, GetFullPathOptions options) { if ((options & GetFullPathOptions.TrimEnd) != 0) if ((options & GetFullPathOptions.KeepDotOrSpace) == 0) path = path.TrimEnd(); if ((options & GetFullPathOptions.AddTrailingDirectorySeparator) != 0) path = AddTrailingDirectorySeparator(path, false); if ((options & GetFullPathOptions.RemoveTrailingDirectorySeparator) != 0) path = RemoveTrailingDirectorySeparator(path, false); if ((options & GetFullPathOptions.CheckInvalidPathChars) != 0) CheckInvalidPathChars(path, (options & GetFullPathOptions.CheckAdditional) != 0, false); // Trim leading whitespace. if ((options & GetFullPathOptions.KeepDotOrSpace) == 0) path = path.TrimStart(); return path; } #endregion // Internal Methods } }