private static void MoveDirectory(string sourceFullPath, string destFullPath, bool sameDirectoryDifferentCase) { if (!Interop.Kernel32.MoveFile(sourceFullPath, destFullPath, overwrite: false)) { int errorCode = Marshal.GetLastWin32Error(); if (errorCode == Interop.Errors.ERROR_FILE_NOT_FOUND) { throw Win32Marshal.GetExceptionForWin32Error(Interop.Errors.ERROR_PATH_NOT_FOUND, sourceFullPath); } if (errorCode == Interop.Errors.ERROR_ALREADY_EXISTS) { throw Win32Marshal.GetExceptionForWin32Error(Interop.Errors.ERROR_ALREADY_EXISTS, destFullPath); } // This check was originally put in for Win9x (unfortunately without special casing it to be for Win9x only). We can't change the NT codepath now for backcomp reasons. if (errorCode == Interop.Errors.ERROR_ACCESS_DENIED) // WinNT throws IOException. This check is for Win9x. We can't change it for backcomp. { throw new IOException(SR.Format(SR.UnauthorizedAccess_IODenied_Path, sourceFullPath), Win32Marshal.MakeHRFromErrorCode(errorCode)); } throw Win32Marshal.GetExceptionForWin32Error(errorCode); } }
[System.Security.SecurityCritical] // auto-generated private static void RemoveDirectoryHelper(string fullPath, bool recursive, bool throwOnTopLevelDirectoryNotFound) { bool r; int errorCode; Exception ex = null; // Do not recursively delete through reparse points. Perhaps in a // future version we will add a new flag to control this behavior, // but for now we're much safer if we err on the conservative side. // This applies to symbolic links and mount points. // Note the logic to check whether fullPath is a reparse point is // in Delete(String, String, bool), and will set "recursive" to false. // Note that Win32's DeleteFile and RemoveDirectory will just delete // the reparse point itself. if (recursive) { Interop.mincore.WIN32_FIND_DATA data = new Interop.mincore.WIN32_FIND_DATA(); // Open a Find handle using (SafeFindHandle hnd = Interop.mincore.FindFirstFile(Directory.EnsureTrailingDirectorySeparator(fullPath) + "*", ref data)) { if (hnd.IsInvalid) { throw Win32Marshal.GetExceptionForLastWin32Error(fullPath); } do { bool isDir = (0 != (data.dwFileAttributes & Interop.mincore.FileAttributes.FILE_ATTRIBUTE_DIRECTORY)); if (isDir) { // Skip ".", "..". if (data.cFileName.Equals(".") || data.cFileName.Equals("..")) { continue; } // Recurse for all directories, unless they are // reparse points. Do not follow mount points nor // symbolic links, but do delete the reparse point // itself. bool shouldRecurse = (0 == (data.dwFileAttributes & (int)FileAttributes.ReparsePoint)); if (shouldRecurse) { string newFullPath = Path.Combine(fullPath, data.cFileName); try { RemoveDirectoryHelper(newFullPath, recursive, false); } catch (Exception e) { if (ex == null) { ex = e; } } } else { // Check to see if this is a mount point, and // unmount it. if (data.dwReserved0 == Interop.mincore.IOReparseOptions.IO_REPARSE_TAG_MOUNT_POINT) { // Use full path plus a trailing '\' String mountPoint = Path.Combine(fullPath, data.cFileName + PathHelpers.DirectorySeparatorCharAsString); if (!Interop.mincore.DeleteVolumeMountPoint(mountPoint)) { errorCode = Marshal.GetLastWin32Error(); if (errorCode != Interop.mincore.Errors.ERROR_SUCCESS && errorCode != Interop.mincore.Errors.ERROR_PATH_NOT_FOUND) { try { throw Win32Marshal.GetExceptionForWin32Error(errorCode, data.cFileName); } catch (Exception e) { if (ex == null) { ex = e; } } } } } // RemoveDirectory on a symbolic link will // remove the link itself. String reparsePoint = Path.Combine(fullPath, data.cFileName); r = Interop.mincore.RemoveDirectory(reparsePoint); if (!r) { errorCode = Marshal.GetLastWin32Error(); if (errorCode != Interop.mincore.Errors.ERROR_PATH_NOT_FOUND) { try { throw Win32Marshal.GetExceptionForWin32Error(errorCode, data.cFileName); } catch (Exception e) { if (ex == null) { ex = e; } } } } } } else { String fileName = Path.Combine(fullPath, data.cFileName); r = Interop.mincore.DeleteFile(fileName); if (!r) { errorCode = Marshal.GetLastWin32Error(); if (errorCode != Interop.mincore.Errors.ERROR_FILE_NOT_FOUND) { try { throw Win32Marshal.GetExceptionForWin32Error(errorCode, data.cFileName); } catch (Exception e) { if (ex == null) { ex = e; } } } } } } while (Interop.mincore.FindNextFile(hnd, ref data)); // Make sure we quit with a sensible error. errorCode = Marshal.GetLastWin32Error(); } if (ex != null) { throw ex; } if (errorCode != 0 && errorCode != Interop.mincore.Errors.ERROR_NO_MORE_FILES) { throw Win32Marshal.GetExceptionForWin32Error(errorCode, fullPath); } } r = Interop.mincore.RemoveDirectory(fullPath); if (!r) { errorCode = Marshal.GetLastWin32Error(); if (errorCode == Interop.mincore.Errors.ERROR_FILE_NOT_FOUND) // A dubious error code. { errorCode = Interop.mincore.Errors.ERROR_PATH_NOT_FOUND; } // This check was originally put in for Win9x (unfortunately without special casing it to be for Win9x only). We can't change the NT codepath now for backcomp reasons. if (errorCode == Interop.mincore.Errors.ERROR_ACCESS_DENIED) { throw new IOException(SR.Format(SR.UnauthorizedAccess_IODenied_Path, fullPath)); } // don't throw the DirectoryNotFoundException since this is a subdir and // there could be a race condition between two Directory.Delete callers if (errorCode == Interop.mincore.Errors.ERROR_PATH_NOT_FOUND && !throwOnTopLevelDirectoryNotFound) { return; } throw Win32Marshal.GetExceptionForWin32Error(errorCode, fullPath); } }
private async Task <FileStreamBase> OpenAsync(string fullPath, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options, FileStream parent) { // Win32 CreateFile returns ERROR_PATH_NOT_FOUND when given a path that ends with '\' if (PathHelpers.EndsInDirectorySeparator(fullPath)) { throw Win32Marshal.GetExceptionForWin32Error(Interop.ERROR_PATH_NOT_FOUND, fullPath); } StorageFile file = null; // FileMode if (mode == FileMode.Open || mode == FileMode.Truncate) { file = await StorageFile.GetFileFromPathAsync(fullPath).TranslateWinRTTask(fullPath); } else { CreationCollisionOption collisionOptions; switch (mode) { case FileMode.Create: collisionOptions = CreationCollisionOption.ReplaceExisting; break; case FileMode.CreateNew: collisionOptions = CreationCollisionOption.FailIfExists; break; case FileMode.Append: case FileMode.OpenOrCreate: default: collisionOptions = CreationCollisionOption.OpenIfExists; break; } string directoryPath, fileName; PathHelpers.SplitDirectoryFile(fullPath, out directoryPath, out fileName); StorageFolder directory = await StorageFolder.GetFolderFromPathAsync(directoryPath).TranslateWinRTTask(directoryPath, isDirectory: true); file = await directory.CreateFileAsync(fileName, collisionOptions).TranslateWinRTTask(fullPath); } // FileAccess: WinRT doesn't support FileAccessMode.Write so we upgrade to ReadWrite FileAccessMode accessMode = ((access & FileAccess.Write) != 0) ? FileAccessMode.ReadWrite : FileAccessMode.Read; // FileShare: cannot translate StorageFile uses a different sharing model (oplocks) that is controlled via FileAccessMode // FileOptions: ignore most values of FileOptions as they are hints and are not supported by WinRT. // FileOptions.Encrypted is not a hint, and not supported by WinRT, but there is precedent for ignoring this (FAT). // FileOptions.DeleteOnClose should result in an UnauthorizedAccessException when // opening a file that can only be read, but we cannot safely reproduce that behavior // in WinRT without actually deleting the file. // Instead the failure will occur in the finalizer for WinRTFileStream and be ignored. // open our stream Stream stream = (await file.OpenAsync(accessMode).TranslateWinRTTask(fullPath)).AsStream(bufferSize); if (mode == FileMode.Append) { // seek to end. stream.Seek(0, SeekOrigin.End); } else if (mode == FileMode.Truncate) { // truncate stream to 0 stream.SetLength(0); } return(new WinRTFileStream(stream, file, access, options, parent)); }
[System.Security.SecurityCritical] // auto-generated internal static int FillAttributeInfo(String path, ref Interop.mincore.WIN32_FILE_ATTRIBUTE_DATA data, bool tryagain, bool returnErrorOnNotFound) { int errorCode = 0; if (tryagain) // someone has a handle to the file open, or other error { Interop.mincore.WIN32_FIND_DATA findData; findData = new Interop.mincore.WIN32_FIND_DATA(); // Remove trialing slash since this can cause grief to FindFirstFile. You will get an invalid argument error String tempPath = path.TrimEnd(PathHelpers.DirectorySeparatorChars); // For floppy drives, normally the OS will pop up a dialog saying // there is no disk in drive A:, please insert one. We don't want that. // SetErrorMode will let us disable this, but we should set the error // mode back, since this may have wide-ranging effects. uint oldMode = Interop.mincore.SetErrorMode(Interop.mincore.SEM_FAILCRITICALERRORS); try { bool error = false; SafeFindHandle handle = Interop.mincore.FindFirstFile(tempPath, ref findData); try { if (handle.IsInvalid) { error = true; errorCode = Marshal.GetLastWin32Error(); if (errorCode == Interop.mincore.Errors.ERROR_FILE_NOT_FOUND || errorCode == Interop.mincore.Errors.ERROR_PATH_NOT_FOUND || errorCode == Interop.mincore.Errors.ERROR_NOT_READY) // floppy device not ready { if (!returnErrorOnNotFound) { // Return default value for backward compatibility errorCode = 0; data.fileAttributes = -1; } } return(errorCode); } } finally { // Close the Win32 handle try { handle.Dispose(); } catch { // if we're already returning an error, don't throw another one. if (!error) { throw Win32Marshal.GetExceptionForLastWin32Error(); } } } } finally { Interop.mincore.SetErrorMode(oldMode); } // Copy the information to data data.PopulateFrom(ref findData); } else { // For floppy drives, normally the OS will pop up a dialog saying // there is no disk in drive A:, please insert one. We don't want that. // SetErrorMode will let us disable this, but we should set the error // mode back, since this may have wide-ranging effects. bool success = false; uint oldMode = Interop.mincore.SetErrorMode(Interop.mincore.SEM_FAILCRITICALERRORS); try { success = Interop.mincore.GetFileAttributesEx(path, Interop.mincore.GET_FILEEX_INFO_LEVELS.GetFileExInfoStandard, ref data); } finally { Interop.mincore.SetErrorMode(oldMode); } if (!success) { errorCode = Marshal.GetLastWin32Error(); if (errorCode != Interop.mincore.Errors.ERROR_FILE_NOT_FOUND && errorCode != Interop.mincore.Errors.ERROR_PATH_NOT_FOUND && errorCode != Interop.mincore.Errors.ERROR_NOT_READY) // floppy device not ready { // In case someone latched onto the file. Take the perf hit only for failure return(FillAttributeInfo(path, ref data, true, returnErrorOnNotFound)); } else { if (!returnErrorOnNotFound) { // Return default value for backward compatibility errorCode = 0; data.fileAttributes = -1; } } } } return(errorCode); }
public override void CreateDirectory(string fullPath) { if (PathInternal.IsDirectoryTooLong(fullPath)) { throw new PathTooLongException(SR.IO_PathTooLong); } // We can save a bunch of work if the directory we want to create already exists. This also // saves us in the case where sub paths are inaccessible (due to ERROR_ACCESS_DENIED) but the // final path is accessable and the directory already exists. For example, consider trying // to create c:\Foo\Bar\Baz, where everything already exists but ACLS prevent access to c:\Foo // and c:\Foo\Bar. In that case, this code will think it needs to create c:\Foo, and c:\Foo\Bar // and fail to due so, causing an exception to be thrown. This is not what we want. if (DirectoryExists(fullPath)) { return; } List <string> stackDir = new List <string>(); // Attempt to figure out which directories don't exist, and only // create the ones we need. Note that InternalExists may fail due // to Win32 ACL's preventing us from seeing a directory, and this // isn't threadsafe. bool somepathexists = false; 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 && PathHelpers.EndsInDirectorySeparator(fullPath)) { length--; } int lengthRoot = PathInternal.GetRootLength(fullPath); if (length > lengthRoot) { // Special case root (fullpath = X:\\) int i = length - 1; while (i >= lengthRoot && !somepathexists) { String dir = fullPath.Substring(0, i + 1); if (!DirectoryExists(dir)) // Create only the ones missing { stackDir.Add(dir); } else { somepathexists = true; } while (i > lengthRoot && !PathInternal.IsDirectorySeparator(fullPath[i])) { i--; } i--; } } int count = stackDir.Count; // If we were passed a DirectorySecurity, convert it to a security // descriptor and set it in he call to CreateDirectory. Interop.mincore.SECURITY_ATTRIBUTES secAttrs = default(Interop.mincore.SECURITY_ATTRIBUTES); bool r = true; int firstError = 0; String errorString = fullPath; // If all the security checks succeeded create all the directories while (stackDir.Count > 0) { String name = stackDir[stackDir.Count - 1]; stackDir.RemoveAt(stackDir.Count - 1); r = Interop.mincore.CreateDirectory(name, ref secAttrs); if (!r && (firstError == 0)) { int currentError = Marshal.GetLastWin32Error(); // While we tried to avoid creating directories that don't // exist above, there are at least two cases that will // cause us to see ERROR_ALREADY_EXISTS here. InternalExists // can fail because we didn't have permission to the // directory. Secondly, another thread or process could // create the directory between the time we check and the // time we try using the directory. Thirdly, it could // fail because the target does exist, but is a file. if (currentError != Interop.mincore.Errors.ERROR_ALREADY_EXISTS) { firstError = currentError; } else { // If there's a file in this directory's place, or if we have ERROR_ACCESS_DENIED when checking if the directory already exists throw. if (File.InternalExists(name) || (!DirectoryExists(name, out currentError) && currentError == Interop.mincore.Errors.ERROR_ACCESS_DENIED)) { firstError = currentError; errorString = name; } } } } // We need this check to mask OS differences // Handle CreateDirectory("X:\\") when X: doesn't exist. Similarly for n/w paths. if ((count == 0) && !somepathexists) { String root = Directory.InternalGetDirectoryRoot(fullPath); if (!DirectoryExists(root)) { throw Win32Marshal.GetExceptionForWin32Error(Interop.mincore.Errors.ERROR_PATH_NOT_FOUND, root); } return; } // Only throw an exception if creating the exact directory we // wanted failed to work correctly. if (!r && (firstError != 0)) { throw Win32Marshal.GetExceptionForWin32Error(firstError, errorString); } }
[System.Security.SecurityCritical] // auto-generated private unsafe static string NormalizePath(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. CheckInvalidPathChars(path); } 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 = null; if (path.Length + 1 <= MaxPath) { char *m_arrayPtr = stackalloc char[MaxPath]; newBuffer = new PathHelper(m_arrayPtr, MaxPath); } else { newBuffer = new PathHelper(path.Length + 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; 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 && IsDirectorySeparator(path[0])) { newBuffer.Append('\\'); index++; lastSigChar = 0; } // 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 (IsDirectorySeparator(currentChar)) { // 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(SR.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(SR.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(SR.Arg_PathIllegal); } } numDots = 2; } else { if (numDots > 1) { throw new ArgumentException(SR.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 && IsDirectorySeparator(path[index + 1])) { 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; // 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; } int thisPos = newBuffer.Length - 1; if (thisPos - lastDirectorySeparatorPos > MaxComponentLength) { throw new PathTooLongException(SR.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 (currentChar == '~' && expandShortPaths) { mightBeShortFileName = true; } fixupDirectorySeparator = false; // 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(SR.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 { 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 > MaxComponentLength) { throw new PathTooLongException(SR.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(SR.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(SR.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(SR.Arg_PathIllegal); } } numDots = 2; } else { if (numDots > 1) { throw new ArgumentException(SR.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(SR.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(SR.Argument_PathUriFormatNotSupported); } } // If the last part of the path (file or directory name) had a tilde, // expand that too. if (mightBeShortFileName) { newBuffer.TryExpandShortFileName(); } // 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 > MaxPath (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 MaxPath restriction. For ex, // "c:\SomeReallyLongDirName(thinkGreaterThan_MAXPATH)\..\foo.txt" which actually requires a // buffer well with in the MaxPath 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. // Either way we need to workaround it here... // 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 MaxPath buffer and grow it once if the // return value is > MaxPath. result = newBuffer.GetFullPathName(); // 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(SR.IO_PathTooLong); } int lenSavedName = newBuffer.Length - lastSlash - 1; Debug.Assert(lastSlash < newBuffer.Length, "path unexpectedly ended in a '\'"); newBuffer.Fixup(lenSavedName, lastSlash); } } } } 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(SR.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(SR.Arg_PathGlobalRoot); } } } // Check our result and form the managed string as necessary. if (newBuffer.Length >= maxPathLength) { throw new PathTooLongException(SR.IO_PathTooLong); } if (result == 0) { int errorCode = Marshal.GetLastWin32Error(); if (errorCode == 0) { errorCode = Interop.mincore.Errors.ERROR_BAD_PATHNAME; } throw Win32Marshal.GetExceptionForWin32Error(errorCode, path); } string returnVal = newBuffer.ToString(); if (string.Equals(returnVal, path, StringComparison.Ordinal)) { returnVal = path; } return(returnVal); }
private void HandleError(int hr, String path) { Dispose(); throw Win32Marshal.GetExceptionForWin32Error(hr, path); }
internal unsafe int GetFullPathName() { if (useStackAlloc) { char *finalBuffer = stackalloc char[Path.MaxPath + 1]; int result = Interop.mincore.GetFullPathNameUnsafe(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 = Interop.mincore.GetFullPathNameUnsafe(m_arrayPtr, result, finalBuffer, IntPtr.Zero); } // Full path is genuinely long if (result >= Path.MaxPath) { throw new PathTooLongException(SR.IO_PathTooLong); } Debug.Assert(result < Path.MaxPath, "did we accidently remove a PathTooLongException check?"); if (result == 0 && m_arrayPtr[0] != '\0') { throw Win32Marshal.GetExceptionForLastWin32Error(); } 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; 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 = Interop.mincore.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 = Interop.mincore.GetFullPathName(m_sb.ToString(), result, finalBuffer, IntPtr.Zero); } // Fullpath is genuinely long if (result >= m_maxPath) { throw new PathTooLongException(SR.IO_PathTooLong); } Debug.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(SR.IO_PathTooLong); } throw Win32Marshal.GetExceptionForLastWin32Error(); } // 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); } }
private static unsafe void WriteSyncUsingAsyncHandle(SafeFileHandle handle, ReadOnlySpan <byte> buffer, long fileOffset) { if (buffer.IsEmpty) { return; } handle.EnsureThreadPoolBindingInitialized(); CallbackResetEvent resetEvent = new CallbackResetEvent(handle.ThreadPoolBinding !); NativeOverlapped * overlapped = null; try { overlapped = GetNativeOverlappedForAsyncHandle(handle, fileOffset, resetEvent); fixed(byte *pinned = &MemoryMarshal.GetReference(buffer)) { Interop.Kernel32.WriteFile(handle, pinned, buffer.Length, IntPtr.Zero, overlapped); int errorCode = FileStreamHelpers.GetLastWin32ErrorAndDisposeHandleIfInvalid(handle); if (errorCode == Interop.Errors.ERROR_IO_PENDING) { resetEvent.WaitOne(); errorCode = Interop.Errors.ERROR_SUCCESS; } if (errorCode == Interop.Errors.ERROR_SUCCESS) { int result = 0; if (Interop.Kernel32.GetOverlappedResult(handle, overlapped, ref result, bWait: false)) { Debug.Assert(result == buffer.Length, $"GetOverlappedResult returned {result} for {buffer.Length} bytes request"); return; } errorCode = FileStreamHelpers.GetLastWin32ErrorAndDisposeHandleIfInvalid(handle); } switch (errorCode) { case Interop.Errors.ERROR_NO_DATA: // For pipes, ERROR_NO_DATA is not an error, but the pipe is closing. return; case Interop.Errors.ERROR_INVALID_PARAMETER: // ERROR_INVALID_PARAMETER may be returned for writes // where the position is too large or for synchronous writes // to a handle opened asynchronously. throw new IOException(SR.IO_FileTooLong); default: throw Win32Marshal.GetExceptionForWin32Error(errorCode, handle.Path); } } } finally { if (overlapped != null) { resetEvent.FreeNativeOverlapped(overlapped); } resetEvent.Dispose(); } }
private Exception HandleError(int errorCode, string path) { Dispose(); return(Win32Marshal.GetExceptionForWin32Error(errorCode, path)); }
private void HandleError(int errorCode, string path) { Dispose(); throw Win32Marshal.GetExceptionForWin32Error(errorCode, path); }
// Returns 0 on success, otherwise a Win32 error code. Note that // classes should use -1 as the uninitialized state for dataInitialized. internal static int FillAttributeInfo(String path, ref Win32Native.WIN32_FILE_ATTRIBUTE_DATA data, bool tryagain, bool returnErrorOnNotFound) { int dataInitialised = 0; if (tryagain) // someone has a handle to the file open, or other error { Win32Native.WIN32_FIND_DATA findData; findData = new Win32Native.WIN32_FIND_DATA(); // Remove trialing slash since this can cause grief to FindFirstFile. You will get an invalid argument error String tempPath = path.TrimEnd(new char[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }); #if !PLATFORM_UNIX // For floppy drives, normally the OS will pop up a dialog saying // there is no disk in drive A:, please insert one. We don't want that. // SetThreadErrorMode will let us disable this, but we should set the error // mode back, since this may have wide-ranging effects. uint oldMode; bool errorModeSuccess = Interop.Kernel32.SetThreadErrorMode(Interop.Kernel32.SEM_FAILCRITICALERRORS, out oldMode); try { #endif bool error = false; SafeFindHandle handle = Win32Native.FindFirstFile(tempPath, findData); try { if (handle.IsInvalid) { error = true; dataInitialised = Marshal.GetLastWin32Error(); if (dataInitialised == Win32Native.ERROR_FILE_NOT_FOUND || dataInitialised == Win32Native.ERROR_PATH_NOT_FOUND || dataInitialised == Win32Native.ERROR_NOT_READY) // floppy device not ready { if (!returnErrorOnNotFound) { // Return default value for backward compatibility dataInitialised = 0; data.fileAttributes = -1; } } return(dataInitialised); } } finally { // Close the Win32 handle try { handle.Close(); } catch { // if we're already returning an error, don't throw another one. if (!error) { Debug.Fail("File::FillAttributeInfo - FindClose failed!"); throw Win32Marshal.GetExceptionForLastWin32Error(); } } } #if !PLATFORM_UNIX } finally { if (errorModeSuccess) { Interop.Kernel32.SetThreadErrorMode(oldMode, out oldMode); } } #endif // Copy the information to data data.PopulateFrom(findData); } else { bool success = false; #if !PLATFORM_UNIX // For floppy drives, normally the OS will pop up a dialog saying // there is no disk in drive A:, please insert one. We don't want that. // SetThreadErrorMode will let us disable this, but we should set the error // mode back, since this may have wide-ranging effects. uint oldMode; bool errorModeSuccess = Interop.Kernel32.SetThreadErrorMode(Interop.Kernel32.SEM_FAILCRITICALERRORS, out oldMode); try { #endif success = Win32Native.GetFileAttributesEx(path, GetFileExInfoStandard, ref data); #if !PLATFORM_UNIX } finally { if (errorModeSuccess) { Interop.Kernel32.SetThreadErrorMode(oldMode, out oldMode); } } #endif if (!success) { dataInitialised = Marshal.GetLastWin32Error(); if (dataInitialised != Win32Native.ERROR_FILE_NOT_FOUND && dataInitialised != Win32Native.ERROR_PATH_NOT_FOUND && dataInitialised != Win32Native.ERROR_NOT_READY) // floppy device not ready { // In case someone latched onto the file. Take the perf hit only for failure return(FillAttributeInfo(path, ref data, true, returnErrorOnNotFound)); } else { if (!returnErrorOnNotFound) { // Return default value for backward compbatibility dataInitialised = 0; data.fileAttributes = -1; } } } } return(dataInitialised); }
public override void MoveDirectory(string sourceFullPath, string destFullPath) { if (!Interop.mincore.MoveFile(sourceFullPath, destFullPath)) { int errorCode = Marshal.GetLastWin32Error(); if (errorCode == Interop.ERROR_FILE_NOT_FOUND) { throw Win32Marshal.GetExceptionForWin32Error(Interop.ERROR_PATH_NOT_FOUND, sourceFullPath); } // This check was originally put in for Win9x (unfortunately without special casing it to be for Win9x only). We can't change the NT codepath now for backcomp reasons. if (errorCode == Interop.ERROR_ACCESS_DENIED) // WinNT throws IOException. This check is for Win9x. We can't change it for backcomp. { throw new IOException(SR.Format(SR.UnauthorizedAccess_IODenied_Path, sourceFullPath), Win32Marshal.MakeHRFromErrorCode(errorCode)); } throw Win32Marshal.GetExceptionForWin32Error(errorCode); } }
private static void RemoveDirectoryRecursive(string fullPath, ref Interop.Kernel32.WIN32_FIND_DATA findData, bool topLevel) { int errorCode; Exception exception = null; using (SafeFindHandle handle = Interop.Kernel32.FindFirstFile(Directory.EnsureTrailingDirectorySeparator(fullPath) + "*", ref findData)) { if (handle.IsInvalid) { throw Win32Marshal.GetExceptionForLastWin32Error(fullPath); } do { if ((findData.dwFileAttributes & Interop.Kernel32.FileAttributes.FILE_ATTRIBUTE_DIRECTORY) == 0) { // File string fileName = findData.cFileName.GetStringFromFixedBuffer(); if (!Interop.Kernel32.DeleteFile(Path.Combine(fullPath, fileName)) && exception == null) { errorCode = Marshal.GetLastWin32Error(); // We don't care if something else deleted the file first if (errorCode != Interop.Errors.ERROR_FILE_NOT_FOUND) { exception = Win32Marshal.GetExceptionForWin32Error(errorCode, fileName); } } } else { // Directory, skip ".", "..". if (findData.cFileName.FixedBufferEqualsString(".") || findData.cFileName.FixedBufferEqualsString("..")) { continue; } string fileName = findData.cFileName.GetStringFromFixedBuffer(); if ((findData.dwFileAttributes & (int)FileAttributes.ReparsePoint) == 0) { // Not a reparse point, recurse. try { RemoveDirectoryRecursive( Path.Combine(fullPath, fileName), findData: ref findData, topLevel: false); } catch (Exception e) { if (exception == null) { exception = e; } } } else { // Reparse point, don't recurse, just remove. (dwReserved0 is documented for this flag) if (findData.dwReserved0 == Interop.Kernel32.IOReparseOptions.IO_REPARSE_TAG_MOUNT_POINT) { // Mount point. Unmount using full path plus a trailing '\'. // (Note: This doesn't remove the underlying directory) string mountPoint = Path.Combine(fullPath, fileName + PathHelpers.DirectorySeparatorCharAsString); if (!Interop.Kernel32.DeleteVolumeMountPoint(mountPoint) && exception == null) { errorCode = Marshal.GetLastWin32Error(); if (errorCode != Interop.Errors.ERROR_SUCCESS && errorCode != Interop.Errors.ERROR_PATH_NOT_FOUND) { exception = Win32Marshal.GetExceptionForWin32Error(errorCode, fileName); } } } // Note that RemoveDirectory on a symbolic link will remove the link itself. if (!Interop.Kernel32.RemoveDirectory(Path.Combine(fullPath, fileName)) && exception == null) { errorCode = Marshal.GetLastWin32Error(); if (errorCode != Interop.Errors.ERROR_PATH_NOT_FOUND) { exception = Win32Marshal.GetExceptionForWin32Error(errorCode, fileName); } } } } } while (Interop.Kernel32.FindNextFile(handle, ref findData)); if (exception != null) { throw exception; } errorCode = Marshal.GetLastWin32Error(); if (errorCode != Interop.Errors.ERROR_SUCCESS && errorCode != Interop.Errors.ERROR_NO_MORE_FILES) { throw Win32Marshal.GetExceptionForWin32Error(errorCode, fullPath); } } RemoveDirectoryInternal(fullPath, topLevel: topLevel); }
private static void RemoveDirectoryRecursive(string fullPath, ref Interop.Kernel32.WIN32_FIND_DATA findData, bool topLevel) { int errorCode; Exception?exception = null; using (SafeFindHandle handle = Interop.Kernel32.FindFirstFile(Path.Join(fullPath, "*"), ref findData)) { if (handle.IsInvalid) { throw Win32Marshal.GetExceptionForLastWin32Error(fullPath); } do { if ((findData.dwFileAttributes & Interop.Kernel32.FileAttributes.FILE_ATTRIBUTE_DIRECTORY) == 0) { // File string fileName = findData.cFileName.GetStringFromFixedBuffer(); if (!Interop.Kernel32.DeleteFile(Path.Combine(fullPath, fileName)) && exception == null) { errorCode = Marshal.GetLastWin32Error(); // We don't care if something else deleted the file first if (errorCode != Interop.Errors.ERROR_FILE_NOT_FOUND) { exception = Win32Marshal.GetExceptionForWin32Error(errorCode, fileName); } } } else { // Directory, skip ".", "..". if (findData.cFileName.FixedBufferEqualsString(".") || findData.cFileName.FixedBufferEqualsString("..")) { continue; } string fileName = findData.cFileName.GetStringFromFixedBuffer(); if (!IsNameSurrogateReparsePoint(ref findData)) { // Not a reparse point, or the reparse point isn't a name surrogate, recurse. try { RemoveDirectoryRecursive( Path.Combine(fullPath, fileName), findData: ref findData, topLevel: false); } catch (Exception e) { if (exception == null) { exception = e; } } } else { // Name surrogate reparse point, don't recurse, simply remove the directory. // If a mount point, we have to delete the mount point first. if (findData.dwReserved0 == Interop.Kernel32.IOReparseOptions.IO_REPARSE_TAG_MOUNT_POINT) { // Mount point. Unmount using full path plus a trailing '\'. // (Note: This doesn't remove the underlying directory) string mountPoint = Path.Join(fullPath, fileName, PathInternal.DirectorySeparatorCharAsString); if (!Interop.Kernel32.DeleteVolumeMountPoint(mountPoint) && exception == null) { errorCode = Marshal.GetLastWin32Error(); if (errorCode != Interop.Errors.ERROR_SUCCESS && errorCode != Interop.Errors.ERROR_PATH_NOT_FOUND) { exception = Win32Marshal.GetExceptionForWin32Error(errorCode, fileName); } } } // Note that RemoveDirectory on a symbolic link will remove the link itself. if (!Interop.Kernel32.RemoveDirectory(Path.Combine(fullPath, fileName)) && exception == null) { errorCode = Marshal.GetLastWin32Error(); if (errorCode != Interop.Errors.ERROR_PATH_NOT_FOUND) { exception = Win32Marshal.GetExceptionForWin32Error(errorCode, fileName); } } } } } while (Interop.Kernel32.FindNextFile(handle, ref findData)); if (exception != null) { throw exception; } errorCode = Marshal.GetLastWin32Error(); if (errorCode != Interop.Errors.ERROR_SUCCESS && errorCode != Interop.Errors.ERROR_NO_MORE_FILES) { throw Win32Marshal.GetExceptionForWin32Error(errorCode, fullPath); } } // As we successfully removed all of the files we shouldn't care about the directory itself // not being empty. As file deletion is just a marker to remove the file when all handles // are closed we could still have undeleted contents. RemoveDirectoryInternal(fullPath, topLevel: topLevel, allowDirectoryNotEmpty: true); }
private static unsafe int ReadSyncUsingAsyncHandle(SafeFileHandle handle, Span <byte> buffer, long fileOffset) { handle.EnsureThreadPoolBindingInitialized(); CallbackResetEvent resetEvent = new CallbackResetEvent(handle.ThreadPoolBinding !); NativeOverlapped * overlapped = null; try { overlapped = GetNativeOverlappedForAsyncHandle(handle, fileOffset, resetEvent); fixed(byte *pinned = &MemoryMarshal.GetReference(buffer)) { Interop.Kernel32.ReadFile(handle, pinned, buffer.Length, IntPtr.Zero, overlapped); int errorCode = FileStreamHelpers.GetLastWin32ErrorAndDisposeHandleIfInvalid(handle); if (errorCode == Interop.Errors.ERROR_IO_PENDING) { resetEvent.WaitOne(); errorCode = Interop.Errors.ERROR_SUCCESS; } if (errorCode == Interop.Errors.ERROR_SUCCESS) { int result = 0; if (Interop.Kernel32.GetOverlappedResult(handle, overlapped, ref result, bWait: false)) { Debug.Assert(result >= 0 && result <= buffer.Length, $"GetOverlappedResult returned {result} for {buffer.Length} bytes request"); return(result); } errorCode = FileStreamHelpers.GetLastWin32ErrorAndDisposeHandleIfInvalid(handle); } switch (errorCode) { case Interop.Errors.ERROR_HANDLE_EOF: // logically success with 0 bytes read (read at end of file) case Interop.Errors.ERROR_BROKEN_PIPE: case Interop.Errors.ERROR_INVALID_PARAMETER when IsEndOfFileForNoBuffering(handle, fileOffset): // EOF on a pipe. Callback will not be called. // We clear the overlapped status bit for this special case (failure // to do so looks like we are freeing a pending overlapped later). overlapped->InternalLow = IntPtr.Zero; return(0); default: throw Win32Marshal.GetExceptionForWin32Error(errorCode, handle.Path); } } } finally { if (overlapped != null) { resetEvent.FreeNativeOverlapped(overlapped); } resetEvent.Dispose(); } }
/// <summary> /// Gets reparse point information associated to <paramref name="linkPath"/>. /// </summary> /// <returns>The immediate link target, absolute or relative or null if the file is not a supported link.</returns> internal static unsafe string?GetImmediateLinkTarget(string linkPath, bool isDirectory, bool throwOnError, bool returnFullPath) { using SafeFileHandle handle = OpenSafeFileHandle(linkPath, Interop.Kernel32.FileOperations.FILE_FLAG_BACKUP_SEMANTICS | Interop.Kernel32.FileOperations.FILE_FLAG_OPEN_REPARSE_POINT); if (handle.IsInvalid) { if (!throwOnError) { return(null); } int error = Marshal.GetLastWin32Error(); // File not found doesn't make much sense coming from a directory. if (isDirectory && error == Interop.Errors.ERROR_FILE_NOT_FOUND) { error = Interop.Errors.ERROR_PATH_NOT_FOUND; } throw Win32Marshal.GetExceptionForWin32Error(error, linkPath); } byte[] buffer = ArrayPool <byte> .Shared.Rent(Interop.Kernel32.MAXIMUM_REPARSE_DATA_BUFFER_SIZE); try { bool success = Interop.Kernel32.DeviceIoControl( handle, dwIoControlCode: Interop.Kernel32.FSCTL_GET_REPARSE_POINT, lpInBuffer: IntPtr.Zero, nInBufferSize: 0, lpOutBuffer: buffer, nOutBufferSize: Interop.Kernel32.MAXIMUM_REPARSE_DATA_BUFFER_SIZE, out _, IntPtr.Zero); if (!success) { if (!throwOnError) { return(null); } int error = Marshal.GetLastWin32Error(); // The file or directory is not a reparse point. if (error == Interop.Errors.ERROR_NOT_A_REPARSE_POINT) { return(null); } throw Win32Marshal.GetExceptionForWin32Error(error, linkPath); } Span <byte> bufferSpan = new(buffer); success = MemoryMarshal.TryRead(bufferSpan, out Interop.Kernel32.SymbolicLinkReparseBuffer rbSymlink); Debug.Assert(success); // We always use SubstituteName(Offset|Length) instead of PrintName(Offset|Length), // the latter is just the display name of the reparse point and it can show something completely unrelated to the target. if (rbSymlink.ReparseTag == Interop.Kernel32.IOReparseOptions.IO_REPARSE_TAG_SYMLINK) { int offset = sizeof(Interop.Kernel32.SymbolicLinkReparseBuffer) + rbSymlink.SubstituteNameOffset; int length = rbSymlink.SubstituteNameLength; Span <char> targetPath = MemoryMarshal.Cast <byte, char>(bufferSpan.Slice(offset, length)); bool isRelative = (rbSymlink.Flags & Interop.Kernel32.SYMLINK_FLAG_RELATIVE) != 0; if (!isRelative) { // Absolute target is in NT format and we need to clean it up before return it to the user. if (targetPath.StartsWith(PathInternal.UncNTPathPrefix.AsSpan())) { // We need to prepend the Win32 equivalent of UNC NT prefix. return(Path.Join(PathInternal.UncPathPrefix.AsSpan(), targetPath.Slice(PathInternal.UncNTPathPrefix.Length))); } return(GetTargetPathWithoutNTPrefix(targetPath)); } else if (returnFullPath) { return(Path.Join(Path.GetDirectoryName(linkPath.AsSpan()), targetPath)); } else { return(targetPath.ToString()); } } else if (rbSymlink.ReparseTag == Interop.Kernel32.IOReparseOptions.IO_REPARSE_TAG_MOUNT_POINT) { success = MemoryMarshal.TryRead(bufferSpan, out Interop.Kernel32.MountPointReparseBuffer rbMountPoint); Debug.Assert(success); int offset = sizeof(Interop.Kernel32.MountPointReparseBuffer) + rbMountPoint.SubstituteNameOffset; int length = rbMountPoint.SubstituteNameLength; Span <char> targetPath = MemoryMarshal.Cast <byte, char>(bufferSpan.Slice(offset, length)); // Unlike symbolic links, mount point paths cannot be relative. Debug.Assert(!PathInternal.IsPartiallyQualified(targetPath)); // Mount points cannot point to a remote location. Debug.Assert(!targetPath.StartsWith(PathInternal.UncNTPathPrefix.AsSpan())); return(GetTargetPathWithoutNTPrefix(targetPath)); } return(null); } finally { ArrayPool <byte> .Shared.Return(buffer); }
private static void MoveDirectory(string sourceFullPath, string destFullPath, bool isCaseSensitiveRename) { // Source and destination must have the same root. ReadOnlySpan <char> sourceRoot = Path.GetPathRoot(sourceFullPath); ReadOnlySpan <char> destinationRoot = Path.GetPathRoot(destFullPath); if (!sourceRoot.Equals(destinationRoot, StringComparison.OrdinalIgnoreCase)) { throw new IOException(SR.IO_SourceDestMustHaveSameRoot); } if (!Interop.Kernel32.MoveFile(sourceFullPath, destFullPath, overwrite: false)) { int errorCode = Marshal.GetLastWin32Error(); if (errorCode == Interop.Errors.ERROR_FILE_NOT_FOUND) { throw Win32Marshal.GetExceptionForWin32Error(Interop.Errors.ERROR_PATH_NOT_FOUND, sourceFullPath); } if (errorCode == Interop.Errors.ERROR_ALREADY_EXISTS) { throw Win32Marshal.GetExceptionForWin32Error(Interop.Errors.ERROR_ALREADY_EXISTS, destFullPath); } // This check was originally put in for Win9x (unfortunately without special casing it to be for Win9x only). We can't change the NT codepath now for backcomp reasons. if (errorCode == Interop.Errors.ERROR_ACCESS_DENIED) // WinNT throws IOException. This check is for Win9x. We can't change it for backcomp. { throw new IOException(SR.Format(SR.UnauthorizedAccess_IODenied_Path, sourceFullPath), Win32Marshal.MakeHRFromErrorCode(errorCode)); } throw Win32Marshal.GetExceptionForWin32Error(errorCode); } }