Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@
Link="Common\Interop\Unix\Interop.OpenFlags.cs" />
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.Permissions.cs"
Link="Common\Interop\Unix\Interop.Permissions.cs" />
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.Stat.cs"
Link="Common\Interop\Unix\Interop.Stat.cs" />
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.SysConf.cs"
Link="Common\Interop\Unix\Interop.SysConf.cs" />
<Compile Include="Microsoft\Win32\SafeMemoryMappedFileHandle.Unix.cs" />
Expand All @@ -119,4 +121,4 @@
<Reference Include="System.Threading" />
<Reference Include="System.Threading.Thread" />
</ItemGroup>
</Project>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
}
}
}

/// <summary>
/// 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
Expand All @@ -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
Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}

/// <summary>
/// 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).
/// </summary>

private static SafeMemoryMappedFileHandle CreateCore(
FileStream? fileStream, string? mapName, HandleInheritability inheritability,
MemoryMappedFileAccess access, MemoryMappedFileOptions options, long capacity)
Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -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));
Expand All @@ -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);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
}
}
}

/// <summary>
/// Test that we can map special character devices on Unix using FileStream.
/// </summary>
[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) { };
}

/// <summary>
/// Test that we can map special character devices on Unix using file name.
/// </summary>
[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) { };
}
}
}