Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce RandomAccess.SetLength #63992

Merged
merged 6 commits into from
Jan 21, 2022
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
65 changes: 65 additions & 0 deletions src/libraries/System.IO.FileSystem/tests/RandomAccess/SetLength.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.Win32.SafeHandles;
using Xunit;

namespace System.IO.Tests
{
public class RandomAccess_SetLength : RandomAccess_Base<long>
{
private const long FileSize = 123;

protected override long MethodUnderTest(SafeFileHandle handle, byte[] bytes, long fileOffset)
{
RandomAccess.SetLength(handle, fileOffset);

return 0;
}

protected override bool UsesOffsets => false;

[Theory]
[MemberData(nameof(GetSyncAsyncOptions))]
public void ModifiesTheActualFileSize(FileOptions options)
{
using (SafeFileHandle handle = File.OpenHandle(GetTestFilePath(), FileMode.CreateNew, FileAccess.Write, options: options))
{
RandomAccess.SetLength(handle, FileSize);

Assert.Equal(FileSize, RandomAccess.GetLength(handle));
}
}

[Theory]
[MemberData(nameof(GetSyncAsyncOptions))]
public void AllowsForShrinking(FileOptions options)
{
using (SafeFileHandle handle = File.OpenHandle(GetTestFilePath(), FileMode.CreateNew, FileAccess.Write, options: options))
{
RandomAccess.SetLength(handle, FileSize);
Assert.Equal(FileSize, RandomAccess.GetLength(handle));

RandomAccess.SetLength(handle, FileSize / 2);
Assert.Equal(FileSize / 2, RandomAccess.GetLength(handle));
adamsitnik marked this conversation as resolved.
Show resolved Hide resolved

RandomAccess.SetLength(handle, 0);
Assert.Equal(0, RandomAccess.GetLength(handle));
}
}

[Theory]
[MemberData(nameof(GetSyncAsyncOptions))]
public void ZeroesTheFileContentsWhenExtendingTheFile(FileOptions options)
{
using (SafeFileHandle handle = File.OpenHandle(GetTestFilePath(), FileMode.CreateNew, FileAccess.Write, options: options))
{
RandomAccess.SetLength(handle, FileSize);

byte[] buffer = new byte[FileSize + 1];
Assert.Equal(FileSize, RandomAccess.Read(handle, buffer, 0));
Assert.All(buffer, @byte => Assert.Equal(0, @byte));
}
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about tests for error conditions? e.g. argument validation?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about tests for error conditions? e.g. argument validation?

This is covered by the base class: https://github.com/dotnet/runtime/blob/main/src/libraries/System.IO.FileSystem/tests/RandomAccess/Base.cs#L28

}
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,11 @@ internal bool SupportsRandomAccess

internal void EnsureThreadPoolBindingInitialized() { /* nop */ }

internal bool LengthCanBeCached => false;

internal bool HasCachedFileLength => false;
internal bool TryGetCachedLength(out long cachedLength)
{
cachedLength = -1;
return false;
}

private static SafeFileHandle Open(string path, Interop.Sys.OpenFlags flags, int mode,
Func<Interop.ErrorInfo, Interop.Sys.OpenFlags, string, Exception?>? createOpenException)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,11 @@ public SafeFileHandle() : base(true)

internal ThreadPoolBoundHandle? ThreadPoolBinding { get; set; }

internal bool LengthCanBeCached => _lengthCanBeCached;

internal bool HasCachedFileLength => _lengthCanBeCached && _length >= 0;
internal bool TryGetCachedLength(out long cachedLength)
{
cachedLength = _length;
return _lengthCanBeCached && cachedLength >= 0;
}

internal static unsafe SafeFileHandle Open(string fullPath, FileMode mode, FileAccess access, FileShare share, FileOptions options, long preallocationSize)
{
Expand Down Expand Up @@ -261,7 +263,7 @@ internal int GetFileType()
return fileType;
}

internal unsafe long GetFileLength()
internal long GetFileLength()
{
if (!_lengthCanBeCached)
{
Expand All @@ -277,7 +279,7 @@ internal unsafe long GetFileLength()

return _length;

long GetFileLengthCore()
unsafe long GetFileLengthCore()
stephentoub marked this conversation as resolved.
Show resolved Hide resolved
{
Interop.Kernel32.FILE_STANDARD_INFO info;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ public static partial class RandomAccess
// that get stackalloced in the Linux kernel.
private const int IovStackThreshold = 8;

internal static unsafe void SetFileLength(SafeFileHandle handle, long length) =>
FileStreamHelpers.CheckFileCall(Interop.Sys.FTruncate(handle, length), handle.Path);

internal static unsafe int ReadAtOffset(SafeFileHandle handle, Span<byte> buffer, long fileOffset)
{
fixed (byte* bufPtr = &MemoryMarshal.GetReference(buffer))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,27 @@ public static partial class RandomAccess
{
private static readonly IOCompletionCallback s_callback = AllocateCallback();

internal static unsafe void SetFileLength(SafeFileHandle handle, long length)
{
var eofInfo = new Interop.Kernel32.FILE_END_OF_FILE_INFO
{
EndOfFile = length
};

if (!Interop.Kernel32.SetFileInformationByHandle(
handle,
Interop.Kernel32.FileEndOfFileInfo,
&eofInfo,
(uint)sizeof(Interop.Kernel32.FILE_END_OF_FILE_INFO)))
{
int errorCode = Marshal.GetLastPInvokeError();

throw errorCode == Interop.Errors.ERROR_INVALID_PARAMETER
? new ArgumentOutOfRangeException(nameof(length), SR.ArgumentOutOfRange_FileLengthTooBig)
: Win32Marshal.GetExceptionForWin32Error(errorCode, handle.Path);
}
}

internal static unsafe int ReadAtOffset(SafeFileHandle handle, Span<byte> buffer, long fileOffset)
{
if (handle.IsAsync)
Expand Down
22 changes: 22 additions & 0 deletions src/libraries/System.Private.CoreLib/src/System/IO/RandomAccess.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,28 @@ public static long GetLength(SafeFileHandle handle)
return handle.GetFileLength();
}

/// <summary>
/// Sets the length of the file to the given value.
/// </summary>
/// <param name="handle">The file handle.</param>
/// <param name="length">A long value representing the length of the file in bytes.</param>
/// <exception cref="T:System.ArgumentNullException"><paramref name="handle" /> is <see langword="null" />.</exception>
/// <exception cref="T:System.ArgumentException"><paramref name="handle" /> is invalid.</exception>
/// <exception cref="T:System.ObjectDisposedException">The file is closed.</exception>
/// <exception cref="T:System.NotSupportedException">The file does not support seeking (pipe or socket).</exception>
/// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="length" /> is negative.</exception>
public static void SetLength(SafeFileHandle handle, long length)
{
ValidateInput(handle, fileOffset: 0);

if (length < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeException_NeedNonNegNum(nameof(length));
}

SetFileLength(handle, length);
}

/// <summary>
/// Reads a sequence of bytes from given file at given offset.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,6 @@ internal static long Seek(SafeFileHandle handle, long offset, SeekOrigin origin,
internal static void ThrowInvalidArgument(SafeFileHandle handle) =>
throw Interop.GetExceptionForIoErrno(new Interop.ErrorInfo(Interop.Error.EINVAL), handle.Path);

internal static unsafe void SetFileLength(SafeFileHandle handle, long length) =>
CheckFileCall(Interop.Sys.FTruncate(handle, length), handle.Path);

/// <summary>Flushes the file's OS buffer.</summary>
internal static void FlushToDisk(SafeFileHandle handle)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,37 +121,6 @@ internal static void Unlock(SafeFileHandle handle, long position, long length)
}
}

internal static unsafe void SetFileLength(SafeFileHandle handle, long length)
{
if (!TrySetFileLength(handle, length, out int errorCode))
{
throw errorCode == Interop.Errors.ERROR_INVALID_PARAMETER
? new ArgumentOutOfRangeException(nameof(length), SR.ArgumentOutOfRange_FileLengthTooBig)
: Win32Marshal.GetExceptionForWin32Error(errorCode, handle.Path);
}
}

internal static unsafe bool TrySetFileLength(SafeFileHandle handle, long length, out int errorCode)
{
var eofInfo = new Interop.Kernel32.FILE_END_OF_FILE_INFO
{
EndOfFile = length
};

if (!Interop.Kernel32.SetFileInformationByHandle(
handle,
Interop.Kernel32.FileEndOfFileInfo,
&eofInfo,
(uint)sizeof(Interop.Kernel32.FILE_END_OF_FILE_INFO)))
{
errorCode = Marshal.GetLastPInvokeError();
return false;
}

errorCode = Interop.Errors.ERROR_SUCCESS;
return true;
}

internal static unsafe int ReadFileNative(SafeFileHandle handle, Span<byte> bytes, NativeOverlapped* overlapped, out int errorCode)
{
Debug.Assert(handle != null, "handle != null");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -195,8 +195,8 @@ protected unsafe void SetLengthCore(long value)
{
Debug.Assert(value >= 0, "value >= 0");

FileStreamHelpers.SetFileLength(_fileHandle, value);
Debug.Assert(!_fileHandle.LengthCanBeCached, "If length can be cached (file opened for reading, not shared for writing), it should be impossible to modify file length");
RandomAccess.SetFileLength(_fileHandle, value);
Debug.Assert(!_fileHandle.TryGetCachedLength(out _), "If length can be cached (file opened for reading, not shared for writing), it should be impossible to modify file length");

if (_filePosition > value)
{
Expand Down Expand Up @@ -283,7 +283,7 @@ public sealed override ValueTask<int> ReadAsync(Memory<byte> destination, Cancel
return RandomAccess.ReadAtOffsetAsync(_fileHandle, destination, fileOffset: -1, cancellationToken);
}

if (_fileHandle.HasCachedFileLength && Volatile.Read(ref _filePosition) >= _fileHandle.GetFileLength())
if (_fileHandle.TryGetCachedLength(out long cachedLength) && Volatile.Read(ref _filePosition) >= cachedLength)
{
// We know for sure that the file length can be safely cached and it has already been obtained.
// If we have reached EOF we just return here and avoid a sys-call.
Expand Down
1 change: 1 addition & 0 deletions src/libraries/System.Runtime/ref/System.Runtime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10750,6 +10750,7 @@ public PathTooLongException(string? message, System.Exception? innerException) {
public static partial class RandomAccess
{
public static long GetLength(Microsoft.Win32.SafeHandles.SafeFileHandle handle) { throw null; }
public static void SetLength(Microsoft.Win32.SafeHandles.SafeFileHandle handle, long length) { throw null; }
public static long Read(Microsoft.Win32.SafeHandles.SafeFileHandle handle, System.Collections.Generic.IReadOnlyList<System.Memory<byte>> buffers, long fileOffset) { throw null; }
public static int Read(Microsoft.Win32.SafeHandles.SafeFileHandle handle, System.Span<byte> buffer, long fileOffset) { throw null; }
public static System.Threading.Tasks.ValueTask<long> ReadAsync(Microsoft.Win32.SafeHandles.SafeFileHandle handle, System.Collections.Generic.IReadOnlyList<System.Memory<byte>> buffers, long fileOffset, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
Expand Down