Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ public sealed partial class SafeFileHandle : SafeHandleZeroOrMinusOneIsInvalid
|| AppContextConfigHelper.GetBooleanConfig("System.IO.DisableFileLocking", "DOTNET_SYSTEM_IO_DISABLEFILELOCKING", defaultValue: false);

// not using bool? as it's not thread safe
Comment thread
adamsitnik marked this conversation as resolved.
private NullableBool _canSeek /* = NullableBool.Undefined */;
private NullableBool _supportsRandomAccess /* = NullableBool.Undefined */;
private NullableBool _isAsync /* = NullableBool.Undefined */;
private bool _deleteOnClose;
Expand Down Expand Up @@ -74,16 +73,14 @@ public bool IsAsync
private set => _isAsync = value ? NullableBool.True : NullableBool.False;
}

internal bool CanSeek => !IsClosed && GetCanSeek();

internal bool SupportsRandomAccess
{
get
{
NullableBool supportsRandomAccess = _supportsRandomAccess;
if (supportsRandomAccess == NullableBool.Undefined)
{
_supportsRandomAccess = supportsRandomAccess = GetCanSeek() ? NullableBool.True : NullableBool.False;
_supportsRandomAccess = supportsRandomAccess = CanSeek ? NullableBool.True : NullableBool.False;
}

return supportsRandomAccess == NullableBool.True;
Expand Down Expand Up @@ -545,19 +542,7 @@ private void FStatCheckIO(string path, ref Interop.Sys.FileStatus status, ref bo
}
}

private bool GetCanSeek()
{
Debug.Assert(!IsClosed);
Debug.Assert(!IsInvalid);

NullableBool canSeek = _canSeek;
if (canSeek == NullableBool.Undefined)
{
_canSeek = canSeek = Interop.Sys.LSeek(this, 0, Interop.Sys.SeekWhence.SEEK_CUR) >= 0 ? NullableBool.True : NullableBool.False;
}

return canSeek == NullableBool.True;
}
private bool GetCanSeekCore() => Interop.Sys.LSeek(this, 0, Interop.Sys.SeekWhence.SEEK_CUR) >= 0;

internal FileHandleType GetFileTypeCore()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,6 @@ public static partial void CreateAnonymousPipe(out SafeFileHandle readHandle, ou

internal bool IsNoBuffering => (GetFileOptions() & FileStreamHelpers.NoBuffering) != 0;

internal bool CanSeek => !IsClosed && Type == FileHandleType.RegularFile;

internal ThreadPoolBoundHandle? ThreadPoolBinding { get; set; }

internal bool TryGetCachedLength(out long cachedLength)
Expand Down Expand Up @@ -329,6 +327,20 @@ internal unsafe FileOptions GetFileOptions()
return _fileOptions = result;
}

// We need to figure out whether given file is seekable or not.
// The options that were considered:
// 1. Using SetFilePointerEx to move by 0 from current position:
// - It can succeed for non-seekable files (pipes, sockets) opened for sync IO,
// - It can fail for non-seekable files (pipes, sockets) opened for async IO.
// 2. Using Type property which internally uses GetFileType and then GetNamedPipeInfo to distinguish pipes from sockets.
// GetNamedPipeInfo requires GENERIC_READ or GENERIC_WRITE and FILE_READ_ATTRIBUTES access.
// There are valid scenarios like CreateNamedPipeW(PIPE_ACCESS_OUTBOUND) where GetNamedPipeInfo fails with ERROR_ACCESS_DENIED.
// 3. Switching from GetNamedPipeInfo to getsockopt to distinguish pipes from sockets. It would add a dependency on ws2_32.dll, which is not desirable.
// That is why we use the cached file type (if available), and if not available, we use GetFileType to determine whether the file is seekable or not.
private bool GetCanSeekCore() => _cachedFileType != -1
? (FileHandleType)_cachedFileType == FileHandleType.RegularFile
: Interop.Kernel32.GetFileType(this) == Interop.Kernel32.FileTypes.FILE_TYPE_DISK;
Comment thread
adamsitnik marked this conversation as resolved.

internal FileHandleType GetFileTypeCore()
{
Debug.Assert(Path is null || _fileOptions != UninitializedOptions, "When Path is set, _fileOptions are also provided.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ public sealed partial class SafeFileHandle : SafeHandleZeroOrMinusOneIsInvalid
{
private string? _path;
private int _cachedFileType = -1;
// not using bool? as it's not thread safe
private NullableBool _canSeek /* = NullableBool.Undefined */;

/// <summary>
/// Creates an anonymous pipe.
Expand Down Expand Up @@ -40,6 +42,25 @@ public SafeFileHandle(IntPtr preexistingHandle, bool ownsHandle) : base(ownsHand

internal string? Path => _path;

internal bool CanSeek
{
get
{
if (IsClosed)
{
return false;
}

NullableBool canSeek = _canSeek;
if (canSeek == NullableBool.Undefined)
{
_canSeek = canSeek = GetCanSeekCore() ? NullableBool.True : NullableBool.False;
}

return canSeek == NullableBool.True;
}
}

/// <summary>
/// Gets the type of the file that this handle represents.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,34 @@ public async Task GetFileType_NamedPipe()
Assert.Equal(FileHandleType.Pipe, clientHandle.Type);
}

[Theory]
[InlineData(false)]
[InlineData(true)]
public void FileStream_WriteOnlyNamedPipe_CanSeek_IsAsync(bool isAsync)
{
int openMode = (int)Interop.Kernel32.PipeOptions.PIPE_ACCESS_OUTBOUND;
if (isAsync)
{
openMode |= Interop.Kernel32.FileOperations.FILE_FLAG_OVERLAPPED;
}

Interop.Kernel32.SECURITY_ATTRIBUTES securityAttributes = Interop.Kernel32.SECURITY_ATTRIBUTES.Create();
using SafeFileHandle handle = Interop.Kernel32.CreateNamedPipeFileHandle(
Comment thread
adamsitnik marked this conversation as resolved.
$@"\\.\pipe\{Guid.NewGuid():N}",
openMode,
pipeMode: (int)Interop.Kernel32.PipeOptions.PIPE_TYPE_BYTE,
maxInstances: 1,
outBufferSize: 0,
inBufferSize: 0,
defaultTimeout: 0,
ref securityAttributes);
Assert.False(handle.IsInvalid);

using FileStream stream = new(handle, FileAccess.Write, bufferSize: 4096, isAsync);
Assert.False(stream.CanSeek);
Assert.Equal(isAsync, stream.IsAsync);
}

[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindowsNanoServer))]
public void GetFileType_ConsoleInput()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,12 +134,14 @@
<Compile Include="$(CommonPath)Interop\Windows\Interop.BOOL.cs" Link="Common\Interop\Windows\Interop.BOOL.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Interop.Libraries.cs" Link="Common\Interop\Windows\Interop.Libraries.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.CreateFile.cs" Link="Common\Interop\Windows\Interop.CreateFile.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.CreateNamedPipe_SafeFileHandle.cs" Link="Common\Interop\Windows\Interop.CreateNamedPipe_SafeFileHandle.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.FILE_STANDARD_INFO.cs" Link="Common\Interop\Windows\Interop.FILE_STANDARD_INFO.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.GetFileInformationByHandleEx.cs" Link="Common\Interop\Windows\Interop.GetFileInformationByHandleEx.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.FILE_TIME.cs" Link="Common\Interop\Windows\Interop.FILE_TIME.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.BY_HANDLE_FILE_INFORMATION.cs" Link="Common\Interop\Windows\Interop.BY_HANDLE_FILE_INFORMATION.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.GetFinalPathNameByHandle.cs" Link="Common\Interop\Windows\Interop.GetFinalPathNameByHandle.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.MemOptions.cs" Link="Common\Interop\Windows\Interop.MemOptions.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.PipeOptions.cs" Link="Common\Interop\Windows\Interop.PipeOptions.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.SECURITY_ATTRIBUTES.cs" Link="Common\Interop\Windows\Interop.SECURITY_ATTRIBUTES.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.VirtualAlloc_Ptr.cs" Link="Common\Interop\Windows\Interop.VirtualAlloc_Ptr.cs" />
<Compile Include="$(CommonPath)System\Text\ValueStringBuilder.cs" Link="Common\System\Text\ValueStringBuilder.cs" />
Expand Down
Loading