diff --git a/src/libraries/System.IO.MemoryMappedFiles/src/System.IO.MemoryMappedFiles.csproj b/src/libraries/System.IO.MemoryMappedFiles/src/System.IO.MemoryMappedFiles.csproj
index 058554ec058b8d..e04c6f837ac55b 100644
--- a/src/libraries/System.IO.MemoryMappedFiles/src/System.IO.MemoryMappedFiles.csproj
+++ b/src/libraries/System.IO.MemoryMappedFiles/src/System.IO.MemoryMappedFiles.csproj
@@ -96,6 +96,8 @@
Link="Common\Interop\Unix\Interop.OpenFlags.cs" />
+
@@ -119,4 +121,4 @@
-
\ No newline at end of file
+
diff --git a/src/libraries/System.IO.MemoryMappedFiles/src/System/IO/MemoryMappedFiles/MemoryMappedFile.Unix.cs b/src/libraries/System.IO.MemoryMappedFiles/src/System/IO/MemoryMappedFiles/MemoryMappedFile.Unix.cs
index da7fbb0795dd85..16bb696a017985 100644
--- a/src/libraries/System.IO.MemoryMappedFiles/src/System/IO/MemoryMappedFiles/MemoryMappedFile.Unix.cs
+++ b/src/libraries/System.IO.MemoryMappedFiles/src/System/IO/MemoryMappedFiles/MemoryMappedFile.Unix.cs
@@ -8,6 +8,46 @@ namespace System.IO.MemoryMappedFiles
{
public partial class MemoryMappedFile
{
+ // This will verify file access and return file size. fileSize will return -1 for special devices.
+ private static void VerifyMemoryMappedFileAccess(MemoryMappedFileAccess access, long capacity, FileStream? fileStream, out long fileSize)
+ {
+ fileSize = -1;
+
+ if (fileStream != null)
+ {
+ Interop.Sys.FileStatus status;
+
+ int result = Interop.Sys.FStat(fileStream.SafeFileHandle, out status);
+ if (result != 0)
+ {
+ Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo();
+ throw Interop.GetExceptionForIoErrno(errorInfo);
+ }
+
+ bool isRegularFile = (status.Mode & Interop.Sys.FileTypes.S_IFCHR) == 0;
+
+ if (isRegularFile)
+ {
+ fileSize = status.Size;
+ if (access == MemoryMappedFileAccess.Read && capacity > status.Size)
+ {
+ throw new ArgumentException(SR.Argument_ReadAccessWithLargeCapacity);
+ }
+
+ // one can always create a small view if they do not want to map an entire file
+ if (fileStream.Length > capacity)
+ {
+ throw new ArgumentOutOfRangeException(nameof(capacity), SR.ArgumentOutOfRange_CapacityGEFileSizeRequired);
+ }
+
+ if (access == MemoryMappedFileAccess.Write)
+ {
+ throw new ArgumentException(SR.Argument_NewMMFWriteAccessNotAllowed, nameof(access));
+ }
+ }
+ }
+ }
+
///
/// Used by the 2 Create factory method groups. A null fileHandle specifies that the
/// memory mapped file should not be associated with an existing file on disk (i.e. start
@@ -18,6 +58,8 @@ private static unsafe SafeMemoryMappedFileHandle CreateCore(
HandleInheritability inheritability, MemoryMappedFileAccess access,
MemoryMappedFileOptions options, long capacity)
{
+ VerifyMemoryMappedFileAccess(access, capacity, fileStream, out long fileSize);
+
if (mapName != null)
{
// Named maps are not supported in our Unix implementation. We could support named maps on Linux using
@@ -35,10 +77,10 @@ private static unsafe SafeMemoryMappedFileHandle CreateCore(
bool ownsFileStream = false;
if (fileStream != null)
{
- // This map is backed by a file. Make sure the file's size is increased to be
- // at least as big as the requested capacity of the map.
- if (fileStream.Length < capacity)
+ if (fileSize >= 0 && capacity > fileSize)
{
+ // This map is backed by a file. Make sure the file's size is increased to be
+ // at least as big as the requested capacity of the map for Write* access.
try
{
fileStream.SetLength(capacity);
diff --git a/src/libraries/System.IO.MemoryMappedFiles/src/System/IO/MemoryMappedFiles/MemoryMappedFile.Windows.cs b/src/libraries/System.IO.MemoryMappedFiles/src/System/IO/MemoryMappedFiles/MemoryMappedFile.Windows.cs
index c2399181c5a562..11ffb4ecb75c86 100644
--- a/src/libraries/System.IO.MemoryMappedFiles/src/System/IO/MemoryMappedFiles/MemoryMappedFile.Windows.cs
+++ b/src/libraries/System.IO.MemoryMappedFiles/src/System/IO/MemoryMappedFiles/MemoryMappedFile.Windows.cs
@@ -11,12 +11,26 @@ namespace System.IO.MemoryMappedFiles
{
public partial class MemoryMappedFile
{
+ // This will verify file access.
+ private static void VerifyMemoryMappedFileAccess(MemoryMappedFileAccess access, long capacity, FileStream fileStream)
+ {
+ if (access == MemoryMappedFileAccess.Read && capacity > fileStream.Length)
+ {
+ throw new ArgumentException(SR.Argument_ReadAccessWithLargeCapacity);
+ }
+
+ // one can always create a small view if they do not want to map an entire file
+ if (fileStream.Length > capacity)
+ {
+ throw new ArgumentOutOfRangeException(nameof(capacity), SR.ArgumentOutOfRange_CapacityGEFileSizeRequired);
+ }
+ }
+
///
/// Used by the 2 Create factory method groups. A null fileHandle specifies that the
/// memory mapped file should not be associated with an existing file on disk (i.e. start
/// out empty).
///
-
private static SafeMemoryMappedFileHandle CreateCore(
FileStream? fileStream, string? mapName, HandleInheritability inheritability,
MemoryMappedFileAccess access, MemoryMappedFileOptions options, long capacity)
@@ -25,6 +39,11 @@ private static SafeMemoryMappedFileHandle CreateCore(
Interop.Kernel32.SECURITY_ATTRIBUTES secAttrs = GetSecAttrs(inheritability);
+ if (fileStream != null)
+ {
+ VerifyMemoryMappedFileAccess(access, capacity, fileStream);
+ }
+
SafeMemoryMappedFileHandle handle = fileHandle != null ?
Interop.CreateFileMapping(fileHandle, ref secAttrs, GetPageAccess(access) | (int)options, capacity, mapName) :
Interop.CreateFileMapping(new IntPtr(-1), ref secAttrs, GetPageAccess(access) | (int)options, capacity, mapName);
diff --git a/src/libraries/System.IO.MemoryMappedFiles/src/System/IO/MemoryMappedFiles/MemoryMappedFile.cs b/src/libraries/System.IO.MemoryMappedFiles/src/System/IO/MemoryMappedFiles/MemoryMappedFile.cs
index f743f1c49d6e0d..0f6552df57bd03 100644
--- a/src/libraries/System.IO.MemoryMappedFiles/src/System/IO/MemoryMappedFiles/MemoryMappedFile.cs
+++ b/src/libraries/System.IO.MemoryMappedFiles/src/System/IO/MemoryMappedFiles/MemoryMappedFile.cs
@@ -154,24 +154,11 @@ public static MemoryMappedFile CreateFromFile(string path, FileMode mode, string
throw new ArgumentException(SR.Argument_EmptyFile);
}
- if (access == MemoryMappedFileAccess.Read && capacity > fileStream.Length)
- {
- CleanupFile(fileStream, existed, path);
- throw new ArgumentException(SR.Argument_ReadAccessWithLargeCapacity);
- }
-
if (capacity == DefaultSize)
{
capacity = fileStream.Length;
}
- // one can always create a small view if they do not want to map an entire file
- if (fileStream.Length > capacity)
- {
- CleanupFile(fileStream, existed, path);
- throw new ArgumentOutOfRangeException(nameof(capacity), SR.ArgumentOutOfRange_CapacityGEFileSizeRequired);
- }
-
SafeMemoryMappedFileHandle? handle = null;
try
{
@@ -224,11 +211,6 @@ public static MemoryMappedFile CreateFromFile(FileStream fileStream, string? map
throw new ArgumentException(SR.Argument_NewMMFWriteAccessNotAllowed, nameof(access));
}
- if (access == MemoryMappedFileAccess.Read && capacity > fileStream.Length)
- {
- throw new ArgumentException(SR.Argument_ReadAccessWithLargeCapacity);
- }
-
if (inheritability < HandleInheritability.None || inheritability > HandleInheritability.Inheritable)
{
throw new ArgumentOutOfRangeException(nameof(inheritability));
@@ -242,12 +224,6 @@ public static MemoryMappedFile CreateFromFile(FileStream fileStream, string? map
capacity = fileStream.Length;
}
- // one can always create a small view if they do not want to map an entire file
- if (fileStream.Length > capacity)
- {
- throw new ArgumentOutOfRangeException(nameof(capacity), SR.ArgumentOutOfRange_CapacityGEFileSizeRequired);
- }
-
SafeMemoryMappedFileHandle handle = CreateCore(fileStream, mapName, inheritability,
access, MemoryMappedFileOptions.None, capacity);
diff --git a/src/libraries/System.IO.MemoryMappedFiles/tests/MemoryMappedFile.CreateFromFile.Tests.cs b/src/libraries/System.IO.MemoryMappedFiles/tests/MemoryMappedFile.CreateFromFile.Tests.cs
index 3f5e9645f95343..2dd5b6303bcceb 100644
--- a/src/libraries/System.IO.MemoryMappedFiles/tests/MemoryMappedFile.CreateFromFile.Tests.cs
+++ b/src/libraries/System.IO.MemoryMappedFiles/tests/MemoryMappedFile.CreateFromFile.Tests.cs
@@ -5,6 +5,7 @@
using Microsoft.Win32.SafeHandles;
using System.Collections.Generic;
using System.Runtime.InteropServices;
+using Microsoft.DotNet.XUnitExtensions;
using Xunit;
namespace System.IO.MemoryMappedFiles.Tests
@@ -868,5 +869,81 @@ public void ReusingNames_Windows(string name)
}
}
+ private void ValidateDeviceAccess(MemoryMappedFile memMap, long viewCapacity, MemoryMappedFileAccess access)
+ {
+ using (MemoryMappedViewAccessor view = memMap.CreateViewAccessor(0, viewCapacity, access))
+ {
+ if (access != MemoryMappedFileAccess.Write)
+ {
+ byte b = view.ReadByte(0);
+ // /dev/zero return zeroes.
+ Assert.Equal(0, b);
+ }
+
+ if (access != MemoryMappedFileAccess.Read)
+ {
+ view.Write(0, (byte)1);
+ }
+ }
+ }
+
+ ///
+ /// Test that we can map special character devices on Unix using FileStream.
+ ///
+ [ConditionalTheory]
+ [InlineData(MemoryMappedFileAccess.Read)]
+ [InlineData(MemoryMappedFileAccess.ReadWrite)]
+ [PlatformSpecific(TestPlatforms.AnyUnix)]
+ public void OpenCharacterDeviceAsStream(MemoryMappedFileAccess access)
+ {
+ const string device = "/dev/zero";
+ if (!File.Exists(device))
+ {
+ throw new SkipTestException($"'{device}' is not available.");
+ }
+
+ long viewCapacity = 0xFF;
+
+ try
+ {
+ using (FileStream fs = new FileStream(device, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite))
+ using (MemoryMappedFile memMap = MemoryMappedFile.CreateFromFile(fs, null, viewCapacity, access, HandleInheritability.None, false))
+ {
+ ValidateDeviceAccess(memMap, viewCapacity, access);
+ }
+ }
+ catch (UnauthorizedAccessException) { }
+ // ENODEV Operation not supported by device.
+ catch (IOException ex) when (ex.HResult == 19) { };
+ }
+
+ ///
+ /// Test that we can map special character devices on Unix using file name.
+ ///
+ [ConditionalTheory]
+ [InlineData(MemoryMappedFileAccess.Read)]
+ [InlineData(MemoryMappedFileAccess.ReadWrite)]
+ [PlatformSpecific(TestPlatforms.AnyUnix)]
+ public void OpenCharacterDeviceAsFile(MemoryMappedFileAccess access)
+ {
+ const string device = "/dev/zero";
+ if (!File.Exists(device))
+ {
+ throw new SkipTestException($"'{device}' is not available.");
+ }
+
+ long viewCapacity = 0xFF;
+
+ try
+ {
+ using (MemoryMappedFile memMap = MemoryMappedFile.CreateFromFile(device, FileMode.Open, null, viewCapacity, access))
+ {
+ ValidateDeviceAccess(memMap, viewCapacity, access);
+ }
+ }
+ catch (UnauthorizedAccessException) { }
+ // ENODEV Operation not supported by device.
+ catch (IOException ex) when (ex.HResult == 19) { };
+ }
}
}