private unsafe Task <int> ReadAsyncInternal(Memory <byte> destination, CancellationToken cancellationToken = default) { if (!CanRead) { ThrowHelper.ThrowNotSupportedException_UnreadableStream(); } Debug.Assert(!_fileHandle.IsClosed, "!_handle.IsClosed"); // Create and store async stream class library specific data in the async result FileStreamCompletionSource completionSource = FileStreamCompletionSource.Create(this, _preallocatedOverlapped, 0, destination); NativeOverlapped * intOverlapped = completionSource.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 VerifyOSHandlePosition(); if (destination.Length > len - _filePosition) { if (_filePosition <= len) { destination = destination.Slice(0, (int)(len - _filePosition)); } else { destination = default; } } // 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)_filePosition); intOverlapped->OffsetHigh = (int)(_filePosition >> 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(_fileHandle, destination.Length, SeekOrigin.Current); } // queue an async ReadFile operation and pass in a packed overlapped int r = FileStreamHelpers.ReadFileNative(_fileHandle, destination.Span, intOverlapped, out int errorCode); // 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/ errorCode==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) { // For pipes, when they hit EOF, they will come here. if (errorCode == 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; completionSource.SetCompletedSynchronously(0); } else if (errorCode != ERROR_IO_PENDING) { if (!_fileHandle.IsClosed && CanSeek) // Update Position - It could be anywhere. { SeekCore(_fileHandle, 0, SeekOrigin.Current); } completionSource.ReleaseNativeResource(); if (errorCode == ERROR_HANDLE_EOF) { ThrowHelper.ThrowEndOfFileException(); } else { throw Win32Marshal.GetExceptionForWin32Error(errorCode, _path); } } else if (cancellationToken.CanBeCanceled) // ERROR_IO_PENDING { // Only once the IO is pending do we register for cancellation completionSource.RegisterForCancellation(cancellationToken); } } 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. } return(completionSource.Task); }
private unsafe Task WriteAsyncInternalCore(ReadOnlyMemory <byte> source, CancellationToken cancellationToken) { if (!CanWrite) { ThrowHelper.ThrowNotSupportedException_UnwritableStream(); } Debug.Assert(!_fileHandle.IsClosed, "!_handle.IsClosed"); // Create and store async stream class library specific data in the async result FileStreamCompletionSource completionSource = FileStreamCompletionSource.Create(this, _preallocatedOverlapped, 0, source); NativeOverlapped * intOverlapped = completionSource.Overlapped; if (CanSeek) { // Make sure we set the length of the file appropriately. long len = Length; // Make sure we are writing to the position that we think we are VerifyOSHandlePosition(); if (_filePosition + source.Length > len) { SetLengthCore(_filePosition + source.Length); } // 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)_filePosition; intOverlapped->OffsetHigh = (int)(_filePosition >> 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(_fileHandle, source.Length, SeekOrigin.Current); } // queue an async WriteFile operation and pass in a packed overlapped int r = FileStreamHelpers.WriteFileNative(_fileHandle, source.Span, intOverlapped, out int errorCode); // 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/ errorCode==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) { // For pipes, when they are closed on the other side, they will come here. if (errorCode == ERROR_NO_DATA) { // Not an error, but EOF. AsyncFSCallback will NOT be called. // Completing TCS and return cached task allowing the GC to collect TCS. completionSource.SetCompletedSynchronously(0); return(Task.CompletedTask); } else if (errorCode != ERROR_IO_PENDING) { if (!_fileHandle.IsClosed && CanSeek) // Update Position - It could be anywhere. { SeekCore(_fileHandle, 0, SeekOrigin.Current); } completionSource.ReleaseNativeResource(); if (errorCode == ERROR_HANDLE_EOF) { ThrowHelper.ThrowEndOfFileException(); } else { throw Win32Marshal.GetExceptionForWin32Error(errorCode, _path); } } else if (cancellationToken.CanBeCanceled) // ERROR_IO_PENDING { // Only once the IO is pending do we register for cancellation completionSource.RegisterForCancellation(cancellationToken); } } 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. } return(completionSource.Task); }
private static SafeMemoryMappedFileHandle CreateOrOpenCore( string mapName, HandleInheritability inheritability, MemoryMappedFileAccess access, MemoryMappedFileOptions options, long capacity) { /// Try to open the file if it exists -- this requires a bit more work. Loop until we can /// either create or open a memory mapped file up to a timeout. CreateFileMapping may fail /// if the file exists and we have non-null security attributes, in which case we need to /// use OpenFileMapping. But, there exists a race condition because the memory mapped file /// may have closed between the two calls -- hence the loop. /// /// The retry/timeout logic increases the wait time each pass through the loop and times /// out in approximately 1.4 minutes. If after retrying, a MMF handle still hasn't been opened, /// throw an InvalidOperationException. Debug.Assert(access != MemoryMappedFileAccess.Write, "Callers requesting write access shouldn't try to create a mmf"); SafeMemoryMappedFileHandle handle = null; Interop.mincore.SECURITY_ATTRIBUTES secAttrs = GetSecAttrs(inheritability); // split the long into two ints int capacityLow = unchecked ((int)(capacity & 0x00000000FFFFFFFFL)); int capacityHigh = unchecked ((int)(capacity >> 32)); int waitRetries = 14; //((2^13)-1)*10ms == approximately 1.4mins int waitSleep = 0; // keep looping until we've exhausted retries or break as soon we get valid handle while (waitRetries > 0) { // try to create handle = Interop.mincore.CreateFileMapping(INVALID_HANDLE_VALUE, ref secAttrs, GetPageAccess(access) | (int)options, capacityHigh, capacityLow, mapName); if (!handle.IsInvalid) { break; } else { handle.Dispose(); int createErrorCode = Marshal.GetLastWin32Error(); if (createErrorCode != Interop.mincore.Errors.ERROR_ACCESS_DENIED) { throw Win32Marshal.GetExceptionForWin32Error(createErrorCode); } } // try to open handle = Interop.mincore.OpenFileMapping(GetFileMapAccess(access), (inheritability & HandleInheritability.Inheritable) != 0, mapName); // valid handle if (!handle.IsInvalid) { break; } // didn't get valid handle; have to retry else { handle.Dispose(); int openErrorCode = Marshal.GetLastWin32Error(); if (openErrorCode != Interop.mincore.Errors.ERROR_FILE_NOT_FOUND) { throw Win32Marshal.GetExceptionForWin32Error(openErrorCode); } // increase wait time --waitRetries; if (waitSleep == 0) { waitSleep = 10; } else { ThreadSleep(waitSleep); waitSleep *= 2; } } } // finished retrying but couldn't create or open if (handle == null || handle.IsInvalid) { throw new InvalidOperationException(SR.InvalidOperation_CantCreateFileMapping); } return(handle); }
public static unsafe void SetWindowSize(int width, int height) { if (width <= 0) { throw new ArgumentOutOfRangeException("width", width, SR.ArgumentOutOfRange_NeedPosNum); } if (height <= 0) { throw new ArgumentOutOfRangeException("height", height, SR.ArgumentOutOfRange_NeedPosNum); } // Get the position of the current console window Interop.mincore.CONSOLE_SCREEN_BUFFER_INFO csbi = GetBufferInfo(); // If the buffer is smaller than this new window size, resize the // buffer to be large enough. Include window position. bool resizeBuffer = false; Interop.mincore.COORD size = new Interop.mincore.COORD(); size.X = csbi.dwSize.X; size.Y = csbi.dwSize.Y; if (csbi.dwSize.X < csbi.srWindow.Left + width) { if (csbi.srWindow.Left >= Int16.MaxValue - width) { throw new ArgumentOutOfRangeException("width", SR.ArgumentOutOfRange_ConsoleWindowBufferSize); } size.X = (short)(csbi.srWindow.Left + width); resizeBuffer = true; } if (csbi.dwSize.Y < csbi.srWindow.Top + height) { if (csbi.srWindow.Top >= Int16.MaxValue - height) { throw new ArgumentOutOfRangeException("height", SR.ArgumentOutOfRange_ConsoleWindowBufferSize); } size.Y = (short)(csbi.srWindow.Top + height); resizeBuffer = true; } if (resizeBuffer) { if (!Interop.mincore.SetConsoleScreenBufferSize(OutputHandle, size)) { throw Win32Marshal.GetExceptionForWin32Error(Marshal.GetLastWin32Error()); } } Interop.mincore.SMALL_RECT srWindow = csbi.srWindow; // Preserve the position, but change the size. srWindow.Bottom = (short)(srWindow.Top + height - 1); srWindow.Right = (short)(srWindow.Left + width - 1); if (!Interop.mincore.SetConsoleWindowInfo(OutputHandle, true, &srWindow)) { int errorCode = Marshal.GetLastWin32Error(); // If we resized the buffer, un-resize it. if (resizeBuffer) { Interop.mincore.SetConsoleScreenBufferSize(OutputHandle, csbi.dwSize); } // Try to give a better error message here Interop.mincore.COORD bounds = Interop.mincore.GetLargestConsoleWindowSize(OutputHandle); if (width > bounds.X) { throw new ArgumentOutOfRangeException("width", width, SR.Format(SR.ArgumentOutOfRange_ConsoleWindowSize_Size, bounds.X)); } if (height > bounds.Y) { throw new ArgumentOutOfRangeException("height", height, SR.Format(SR.ArgumentOutOfRange_ConsoleWindowSize_Size, bounds.Y)); } 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(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 contents hanging around. RemoveDirectoryInternal(fullPath, topLevel: topLevel, allowDirectoryNotEmpty: true); }
public static void CreateDirectory(string fullPath) { // 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 accessible 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 FileExists 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 && Path.EndsInDirectorySeparator(fullPath.AsSpan())) { length--; } int lengthRoot = PathInternal.GetRootLength(fullPath.AsSpan()); 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.Kernel32.SECURITY_ATTRIBUTES secAttrs = default; 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.Kernel32.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. FileExists // 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.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 (FileExists(name) || (!DirectoryExists(name, out currentError) && currentError == Interop.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.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); } }
// We can remove this link demand in a future version - we will // have scenarios for this in partial trust in the future, but // we're doing this just to restrict this in case the code below // is somehow incorrect. public MemoryFailPoint(int sizeInMegabytes) { if (sizeInMegabytes <= 0) { throw new ArgumentOutOfRangeException(nameof(sizeInMegabytes), SR.ArgumentOutOfRange_NeedNonNegNum); } ulong size = ((ulong)sizeInMegabytes) << 20; _reservedMemory = size; // Check to see that we both have enough memory on the system // and that we have enough room within the user section of the // process's address space. Also, we need to use the GC segment // size, not the amount of memory the user wants to allocate. // Consider correcting this to reflect free memory within the GC // heap, and to check both the normal & large object heaps. ulong segmentSize = (ulong)(Math.Ceiling((double)size / s_GCSegmentSize) * s_GCSegmentSize); if (segmentSize >= s_topOfMemory) { throw new InsufficientMemoryException(SR.InsufficientMemory_MemFailPoint_TooBig); } ulong requestedSizeRounded = (ulong)(Math.Ceiling((double)sizeInMegabytes / MemoryCheckGranularity) * MemoryCheckGranularity); //re-convert into bytes requestedSizeRounded <<= 20; ulong availPageFile = 0; // available VM (physical + page file) ulong totalAddressSpaceFree = 0; // non-contiguous free address space // Check for available memory, with 2 attempts at getting more // memory. // Stage 0: If we don't have enough, trigger a GC. // Stage 1: If we don't have enough, try growing the swap file. // Stage 2: Update memory state, then fail or leave loop. // // (In the future, we could consider adding another stage after // Stage 0 to run finalizers. However, before doing that make sure // that we could abort this constructor when we call // GC.WaitForPendingFinalizers, noting that this method uses a CER // so it can't be aborted, and we have a critical finalizer. It // would probably work, but do some thinking first.) for (int stage = 0; stage < 3; stage++) { CheckForAvailableMemory(out availPageFile, out totalAddressSpaceFree); // If we have enough room, then skip some stages. // Note that multiple threads can still lead to a race condition for our free chunk // of address space, which can't be easily solved. ulong reserved = MemoryFailPointReservedMemory; ulong segPlusReserved = segmentSize + reserved; bool overflow = segPlusReserved < segmentSize || segPlusReserved < reserved; bool needPageFile = availPageFile < (requestedSizeRounded + reserved + LowMemoryFudgeFactor) || overflow; bool needAddressSpace = totalAddressSpaceFree < segPlusReserved || overflow; // Ensure our cached amount of free address space is not stale. long now = Environment.TickCount; // Handle wraparound. if ((now > LastTimeCheckingAddressSpace + CheckThreshold || now < LastTimeCheckingAddressSpace) || LastKnownFreeAddressSpace < (long)segmentSize) { CheckForFreeAddressSpace(segmentSize, false); } bool needContiguousVASpace = (ulong)LastKnownFreeAddressSpace < segmentSize; if (!needPageFile && !needAddressSpace && !needContiguousVASpace) { break; } switch (stage) { case 0: // The GC will release empty segments to the OS. This will // relieve us from having to guess whether there's // enough memory in either GC heap, and whether // internal fragmentation will prevent those // allocations from succeeding. GC.Collect(); continue; case 1: // Do this step if and only if the page file is too small. if (!needPageFile) { continue; } // Attempt to grow the OS's page file. Note that we ignore // any allocation routines from the host intentionally. RuntimeHelpers.PrepareConstrainedRegions(); try { } finally { // This shouldn't overflow due to the if clauses above. UIntPtr numBytes = new UIntPtr(segmentSize); unsafe { #if ENABLE_WINRT void *pMemory = Interop.mincore.VirtualAllocFromApp(null, numBytes, Interop.Kernel32.MEM_COMMIT, Interop.Kernel32.PAGE_READWRITE); #else void *pMemory = Interop.Kernel32.VirtualAlloc(null, numBytes, Interop.Kernel32.MEM_COMMIT, Interop.Kernel32.PAGE_READWRITE); #endif if (pMemory != null) { bool r = Interop.Kernel32.VirtualFree(pMemory, UIntPtr.Zero, Interop.Kernel32.MEM_RELEASE); if (!r) { throw Win32Marshal.GetExceptionForWin32Error(Marshal.GetLastWin32Error()); } } } } continue; case 2: // The call to CheckForAvailableMemory above updated our // state. if (needPageFile || needAddressSpace) { InsufficientMemoryException e = new InsufficientMemoryException(SR.InsufficientMemory_MemFailPoint); #if DEBUG e.Data["MemFailPointState"] = new MemoryFailPointState(sizeInMegabytes, segmentSize, needPageFile, needAddressSpace, needContiguousVASpace, availPageFile >> 20, totalAddressSpaceFree >> 20, LastKnownFreeAddressSpace >> 20, reserved); #endif throw e; } if (needContiguousVASpace) { InsufficientMemoryException e = new InsufficientMemoryException(SR.InsufficientMemory_MemFailPoint_VAFrag); #if DEBUG e.Data["MemFailPointState"] = new MemoryFailPointState(sizeInMegabytes, segmentSize, needPageFile, needAddressSpace, needContiguousVASpace, availPageFile >> 20, totalAddressSpaceFree >> 20, LastKnownFreeAddressSpace >> 20, reserved); #endif throw e; } break; default: Debug.Fail("Fell through switch statement!"); break; } } // Success - we have enough room the last time we checked. // Now update our shared state in a somewhat atomic fashion // and handle a simple race condition with other MemoryFailPoint instances. AddToLastKnownFreeAddressSpace(-((long)size)); if (LastKnownFreeAddressSpace < 0) { CheckForFreeAddressSpace(segmentSize, true); } RuntimeHelpers.PrepareConstrainedRegions(); try { } finally { AddMemoryFailPointReservation((long)size); _mustSubtractReservation = true; } }
public static void MoveDirectory(string sourceFullPath, string destFullPath) { 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); } // 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); } }
// Flushes the changes such that they are in sync with the FileStream bits (ones obtained // with the win32 ReadFile and WriteFile functions). Need to call FileStream's Flush to // flush to the disk. // NOTE: This will flush all bytes before and after the view up until an offset that is a multiple // of SystemPageSize. public void Flush(UIntPtr capacity) { unsafe { byte *firstPagePtr = null; try { _viewHandle.AcquirePointer(ref firstPagePtr); if (Interop.Kernel32.FlushViewOfFile((IntPtr)firstPagePtr, capacity)) { return; } // It is a known issue within the NTFS transaction log system that // causes FlushViewOfFile to intermittently fail with ERROR_LOCK_VIOLATION // As a workaround, we catch this particular error and retry the flush operation // a few milliseconds later. If it does not work, we give it a few more tries with // increasing intervals. Eventually, however, we need to give up. In ad-hoc tests // this strategy successfully flushed the view after no more than 3 retries. int error = Marshal.GetLastWin32Error(); if (error != Interop.Errors.ERROR_LOCK_VIOLATION) { throw Win32Marshal.GetExceptionForWin32Error(error); } SpinWait spinWait = new SpinWait(); for (int w = 0; w < MaxFlushWaits; w++) { int pause = (1 << w); // MaxFlushRetries should never be over 30 Thread.Sleep(pause); for (int r = 0; r < MaxFlushRetriesPerWait; r++) { if (Interop.Kernel32.FlushViewOfFile((IntPtr)firstPagePtr, capacity)) { return; } error = Marshal.GetLastWin32Error(); if (error != Interop.Errors.ERROR_LOCK_VIOLATION) { throw Win32Marshal.GetExceptionForWin32Error(error); } spinWait.SpinOnce(); } } // We got to here, so there was no success: throw Win32Marshal.GetExceptionForWin32Error(error); } finally { if (firstPagePtr != null) { _viewHandle.ReleasePointer(); } } } }
public static unsafe MemoryMappedView CreateView(SafeMemoryMappedFileHandle memMappedFileHandle, MemoryMappedFileAccess access, long offset, long size) { // MapViewOfFile can only create views that start at a multiple of the system memory allocation // granularity. We decided to hide this restriction from the user by creating larger views than the // user requested and hiding the parts that the user did not request. extraMemNeeded is the amount of // extra memory we allocate before the start of the requested view. MapViewOfFile will also round the // capacity of the view to the nearest multiple of the system page size. Once again, we hide this // from the user by preventing them from writing to any memory that they did not request. ulong nativeSize; long extraMemNeeded, newOffset; ValidateSizeAndOffset( size, offset, GetSystemPageAllocationGranularity(), out nativeSize, out extraMemNeeded, out newOffset); // if request is >= than total virtual, then MapViewOfFile will fail with meaningless error message // "the parameter is incorrect"; this provides better error message in advance Interop.CheckForAvailableVirtualMemory(nativeSize); // create the view SafeMemoryMappedViewHandle viewHandle = Interop.MapViewOfFile(memMappedFileHandle, (int)MemoryMappedFile.GetFileMapAccess(access), newOffset, new UIntPtr(nativeSize)); if (viewHandle.IsInvalid) { viewHandle.Dispose(); throw Win32Marshal.GetExceptionForLastWin32Error(); } // Query the view for its size and allocation type Interop.Kernel32.MEMORY_BASIC_INFORMATION viewInfo = new Interop.Kernel32.MEMORY_BASIC_INFORMATION(); Interop.Kernel32.VirtualQuery(viewHandle, ref viewInfo, (UIntPtr)Marshal.SizeOf(viewInfo)); ulong viewSize = (ulong)viewInfo.RegionSize; // Allocate the pages if we were using the MemoryMappedFileOptions.DelayAllocatePages option // OR check if the allocated view size is smaller than the expected native size // If multiple overlapping views are created over the file mapping object, the pages in a given region // could have different attributes(MEM_RESERVE OR MEM_COMMIT) as MapViewOfFile preserves coherence between // views created on a mapping object backed by same file. // In which case, the viewSize will be smaller than nativeSize required and viewState could be MEM_COMMIT // but more pages may need to be committed in the region. // This is because, VirtualQuery function(that internally invokes VirtualQueryEx function) returns the attributes // and size of the region of pages with matching attributes starting from base address. // VirtualQueryEx: http://msdn.microsoft.com/en-us/library/windows/desktop/aa366907(v=vs.85).aspx if (((viewInfo.State & Interop.Kernel32.MemOptions.MEM_RESERVE) != 0) || ((ulong)viewSize < (ulong)nativeSize)) { IntPtr tempHandle = Interop.VirtualAlloc( viewHandle, (UIntPtr)(nativeSize != MemoryMappedFile.DefaultSize ? nativeSize : viewSize), Interop.Kernel32.MemOptions.MEM_COMMIT, MemoryMappedFile.GetPageAccess(access)); int lastError = Marshal.GetLastWin32Error(); if (viewHandle.IsInvalid) { viewHandle.Dispose(); throw Win32Marshal.GetExceptionForWin32Error(lastError); } // again query the view for its new size viewInfo = new Interop.Kernel32.MEMORY_BASIC_INFORMATION(); Interop.Kernel32.VirtualQuery(viewHandle, ref viewInfo, (UIntPtr)Marshal.SizeOf(viewInfo)); viewSize = (ulong)viewInfo.RegionSize; } // if the user specified DefaultSize as the size, we need to get the actual size if (size == MemoryMappedFile.DefaultSize) { size = (long)(viewSize - (ulong)extraMemNeeded); } else { Debug.Assert(viewSize >= (ulong)size, "viewSize < size"); } viewHandle.Initialize((ulong)size + (ulong)extraMemNeeded); return(new MemoryMappedView(viewHandle, extraMemNeeded, size, access)); }
internal static Exception GetIOError(int errorCode, string?path) => errorCode == Interop.Errors.ERROR_HANDLE_EOF ? ThrowHelper.CreateEndOfFileException() : Win32Marshal.GetExceptionForWin32Error(errorCode, path);
public void OperationCancelledErrors(string path, int errorCode) { var exception = Win32Marshal.GetExceptionForWin32Error(errorCode, path); Assert.IsType <OperationCanceledException>(exception); }
private static unsafe string?GetFinalLinkTarget(string linkPath, bool isDirectory) { Interop.Kernel32.WIN32_FIND_DATA data = default; GetFindData(linkPath, isDirectory, ref data); // The file or directory is not a reparse point. if ((data.dwFileAttributes & (uint)FileAttributes.ReparsePoint) == 0 || // Only symbolic links are supported at the moment. (data.dwReserved0 & Interop.Kernel32.IOReparseOptions.IO_REPARSE_TAG_SYMLINK) == 0) { return(null); } // We try to open the final file since they asked for the final target. using SafeFileHandle handle = OpenSafeFileHandle(linkPath, Interop.Kernel32.FileOperations.OPEN_EXISTING | Interop.Kernel32.FileOperations.FILE_FLAG_BACKUP_SEMANTICS); if (handle.IsInvalid) { // If the handle fails because it is unreachable, is because the link was broken. // We need to fallback to manually traverse the links and return the target of the last resolved link. int error = Marshal.GetLastWin32Error(); if (IsPathUnreachableError(error)) { return(GetFinalLinkTargetSlow(linkPath)); } throw Win32Marshal.GetExceptionForWin32Error(error, linkPath); } const int InitialBufferSize = 4096; char[] buffer = ArrayPool <char> .Shared.Rent(InitialBufferSize); try { uint result = GetFinalPathNameByHandle(handle, buffer); // If the function fails because lpszFilePath is too small to hold the string plus the terminating null character, // the return value is the required buffer size, in TCHARs. This value includes the size of the terminating null character. if (result > buffer.Length) { ArrayPool <char> .Shared.Return(buffer); buffer = ArrayPool <char> .Shared.Rent((int)result); result = GetFinalPathNameByHandle(handle, buffer); } // If the function fails for any other reason, the return value is zero. if (result == 0) { throw Win32Marshal.GetExceptionForLastWin32Error(linkPath); } Debug.Assert(PathInternal.IsExtended(new string(buffer, 0, (int)result).AsSpan())); // GetFinalPathNameByHandle always returns with extended DOS prefix even if the link target was created without one. // While this does not interfere with correct behavior, it might be unexpected. // Hence we trim it if the passed-in path to the link wasn't extended. int start = PathInternal.IsExtended(linkPath.AsSpan()) ? 0 : 4; return(new string(buffer, start, (int)result - start)); } finally { ArrayPool <char> .Shared.Return(buffer); } uint GetFinalPathNameByHandle(SafeFileHandle handle, char[] buffer) { fixed(char *bufPtr = buffer) { return(Interop.Kernel32.GetFinalPathNameByHandle(handle, bufPtr, (uint)buffer.Length, Interop.Kernel32.FILE_NAME_NORMALIZED)); } } string?GetFinalLinkTargetSlow(string linkPath) { // Since all these paths will be passed to CreateFile, which takes a string anyway, it is pointless to use span. // I am not sure if it's possible to change CreateFile's param to ROS<char> and avoid all these allocations. // We don't throw on error since we already did all the proper validations before. string?current = GetImmediateLinkTarget(linkPath, isDirectory, throwOnError: false, returnFullPath: true); string?prev = null; while (current != null) { prev = current; current = GetImmediateLinkTarget(current, isDirectory, throwOnError: false, returnFullPath: true); } return(prev); } }
/// <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.REPARSE_DATA_BUFFER rdb); Debug.Assert(success); // Only symbolic links are supported at the moment. if ((rdb.ReparseTag & Interop.Kernel32.IOReparseOptions.IO_REPARSE_TAG_SYMLINK) == 0) { return(null); } // We use PrintName instead of SubstitutneName given that we don't want to return a NT path when the link wasn't created with such NT path. // Unlike SubstituteName and GetFinalPathNameByHandle(), PrintName doesn't start with a prefix. // Another nuance is that SubstituteName does not contain redundant path segments while PrintName does. // PrintName can ONLY return a NT path if the link was created explicitly targeting a file/folder in such way. e.g: mklink /D linkName \??\C:\path\to\target. int printNameNameOffset = sizeof(Interop.Kernel32.REPARSE_DATA_BUFFER) + rdb.ReparseBufferSymbolicLink.PrintNameOffset; int printNameNameLength = rdb.ReparseBufferSymbolicLink.PrintNameLength; Span <char> targetPath = MemoryMarshal.Cast <byte, char>(bufferSpan.Slice(printNameNameOffset, printNameNameLength)); Debug.Assert((rdb.ReparseBufferSymbolicLink.Flags & Interop.Kernel32.SYMLINK_FLAG_RELATIVE) == 0 || !PathInternal.IsExtended(targetPath)); if (returnFullPath && (rdb.ReparseBufferSymbolicLink.Flags & Interop.Kernel32.SYMLINK_FLAG_RELATIVE) != 0) { // Target path is relative and is for ResolveLinkTarget(), we need to append the link directory. return(Path.Join(Path.GetDirectoryName(linkPath.AsSpan()), targetPath)); } return(targetPath.ToString()); } finally { ArrayPool <byte> .Shared.Return(buffer); } }
internal static void ThrowInvalidArgument(SafeFileHandle handle) => throw Win32Marshal.GetExceptionForWin32Error(Interop.Errors.ERROR_INVALID_PARAMETER, handle.Path);