|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.ComponentModel;
using System.IO.Strategies;
using System.Runtime.Versioning;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Win32.SafeHandles;
namespace System.IO
{
public class FileStream : Stream
{
internal const int DefaultBufferSize = 4096;
internal const FileShare DefaultShare = FileShare.Read;
private const bool DefaultIsAsync = false;
private readonly FileStreamStrategy _strategy;
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("This constructor has been deprecated. Use FileStream(SafeFileHandle handle, FileAccess access) instead.")]
public FileStream(IntPtr handle, FileAccess access)
: this(handle, access, true, DefaultBufferSize, DefaultIsAsync)
{
}
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("This constructor has been deprecated. Use FileStream(SafeFileHandle handle, FileAccess access) and optionally make a new SafeFileHandle with ownsHandle=false if needed instead.")]
public FileStream(IntPtr handle, FileAccess access, bool ownsHandle)
: this(handle, access, ownsHandle, DefaultBufferSize, DefaultIsAsync)
{
}
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("This constructor has been deprecated. Use FileStream(SafeFileHandle handle, FileAccess access, int bufferSize) and optionally make a new SafeFileHandle with ownsHandle=false if needed instead.")]
public FileStream(IntPtr handle, FileAccess access, bool ownsHandle, int bufferSize)
: this(handle, access, ownsHandle, bufferSize, DefaultIsAsync)
{
}
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("This constructor has been deprecated. Use FileStream(SafeFileHandle handle, FileAccess access, int bufferSize, bool isAsync) and optionally make a new SafeFileHandle with ownsHandle=false if needed instead.")]
public FileStream(IntPtr handle, FileAccess access, bool ownsHandle, int bufferSize, bool isAsync)
{
SafeFileHandle safeHandle = new SafeFileHandle(handle, ownsHandle: ownsHandle);
try
{
ValidateHandle(safeHandle, access, bufferSize, isAsync);
_strategy = FileStreamHelpers.ChooseStrategy(this, safeHandle, access, bufferSize, isAsync);
}
catch
{
// We don't want to take ownership of closing passed in handles
// *unless* the constructor completes successfully.
GC.SuppressFinalize(safeHandle);
// This would also prevent Close from being called, but is unnecessary
// as we've removed the object from the finalizer queue.
//
// safeHandle.SetHandleAsInvalid();
throw;
}
}
private static void ValidateHandle(SafeFileHandle handle, FileAccess access, int bufferSize)
{
if (handle.IsInvalid)
{
throw new ArgumentException(SR.Arg_InvalidHandle, nameof(handle));
}
else if (access < FileAccess.Read || access > FileAccess.ReadWrite)
{
throw new ArgumentOutOfRangeException(nameof(access), SR.ArgumentOutOfRange_Enum);
}
else if (bufferSize < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeException_NeedNonNegNum(nameof(bufferSize));
}
else if (handle.IsClosed)
{
ThrowHelper.ThrowObjectDisposedException_FileClosed();
}
}
private static void ValidateHandle(SafeFileHandle handle, FileAccess access, int bufferSize, bool isAsync)
{
ValidateHandle(handle, access, bufferSize);
if (isAsync && !handle.IsAsync)
{
ThrowHelper.ThrowArgumentException_HandleNotAsync(nameof(handle));
}
else if (!isAsync && handle.IsAsync)
{
ThrowHelper.ThrowArgumentException_HandleNotSync(nameof(handle));
}
}
public FileStream(SafeFileHandle handle, FileAccess access)
: this(handle, access, DefaultBufferSize)
{
}
public FileStream(SafeFileHandle handle, FileAccess access, int bufferSize)
{
ValidateHandle(handle, access, bufferSize);
_strategy = FileStreamHelpers.ChooseStrategy(this, handle, access, bufferSize, handle.IsAsync);
}
public FileStream(SafeFileHandle handle, FileAccess access, int bufferSize, bool isAsync)
{
ValidateHandle(handle, access, bufferSize, isAsync);
_strategy = FileStreamHelpers.ChooseStrategy(this, handle, access, bufferSize, isAsync);
}
public FileStream(string path, FileMode mode)
: this(path, mode, mode == FileMode.Append ? FileAccess.Write : FileAccess.ReadWrite, DefaultShare, DefaultBufferSize, DefaultIsAsync)
{
}
public FileStream(string path, FileMode mode, FileAccess access)
: this(path, mode, access, DefaultShare, DefaultBufferSize, DefaultIsAsync)
{
}
public FileStream(string path, FileMode mode, FileAccess access, FileShare share)
: this(path, mode, access, share, DefaultBufferSize, DefaultIsAsync)
{
}
public FileStream(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize)
: this(path, mode, access, share, bufferSize, DefaultIsAsync)
{
}
public FileStream(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, bool useAsync)
: this(path, mode, access, share, bufferSize, useAsync ? FileOptions.Asynchronous : FileOptions.None)
{
}
public FileStream(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options)
: this(path, mode, access, share, bufferSize, options, 0)
{
}
~FileStream()
{
// Preserved for compatibility since FileStream has defined a
// finalizer in past releases and derived classes may depend
// on Dispose(false) call.
Dispose(false);
}
/// <summary>
/// Initializes a new instance of the <see cref="FileStream" /> class with the specified path, creation mode, read/write and sharing permission, the access other FileStreams can have to the same file, the buffer size, additional file options and the allocation size.
/// </summary>
/// <param name="path">A relative or absolute path for the file that the current <see cref="FileStream" /> instance will encapsulate.</param>
/// <param name="options">An object that describes optional <see cref="FileStream" /> parameters to use.</param>
/// <exception cref="T:System.ArgumentNullException"><paramref name="path" /> or <paramref name="options" /> is <see langword="null" />.</exception>
/// <exception cref="T:System.ArgumentException"><paramref name="path" /> is an empty string (""), contains only white space, or contains one or more invalid characters.
/// -or-
/// <paramref name="path" /> refers to a non-file device, such as <c>CON:</c>, <c>COM1:</c>, <c>LPT1:</c>, etc. in an NTFS environment.</exception>
/// <exception cref="T:System.NotSupportedException"><paramref name="path" /> refers to a non-file device, such as <c>CON:</c>, <c>COM1:</c>, <c>LPT1:</c>, etc. in a non-NTFS environment.</exception>
/// <exception cref="T:System.IO.FileNotFoundException">The file cannot be found, such as when <see cref="FileStreamOptions.Mode" /> is <see langword="FileMode.Truncate" /> or <see langword="FileMode.Open" />, and the file specified by <paramref name="path" /> does not exist. The file must already exist in these modes.</exception>
/// <exception cref="T:System.IO.IOException">An I/O error, such as specifying <see langword="FileMode.CreateNew" /> when the file specified by <paramref name="path" /> already exists, occurred.
/// -or-
/// The stream has been closed.
/// -or-
/// The disk was full (when <see cref="FileStreamOptions.PreallocationSize" /> was provided and <paramref name="path" /> was pointing to a regular file).
/// -or-
/// The file was too large (when <see cref="FileStreamOptions.PreallocationSize" /> was provided and <paramref name="path" /> was pointing to a regular file).</exception>
/// <exception cref="T:System.Security.SecurityException">The caller does not have the required permission.</exception>
/// <exception cref="T:System.IO.DirectoryNotFoundException">The specified path is invalid, such as being on an unmapped drive.</exception>
/// <exception cref="T:System.UnauthorizedAccessException">The <see cref="FileStreamOptions.Access" /> requested is not permitted by the operating system for the specified <paramref name="path" />, such as when <see cref="FileStreamOptions.Access" /> is <see cref="FileAccess.Write" /> or <see cref="FileAccess.ReadWrite" /> and the file or directory is set for read-only access.
/// -or-
/// <see cref="F:System.IO.FileOptions.Encrypted" /> is specified for <see cref="FileStreamOptions.Options" /> , but file encryption is not supported on the current platform.</exception>
/// <exception cref="T:System.IO.PathTooLongException">The specified path, file name, or both exceed the system-defined maximum length. </exception>
public FileStream(string path, FileStreamOptions options)
{
ArgumentException.ThrowIfNullOrEmpty(path);
ArgumentNullException.ThrowIfNull(options);
if ((options.Access & FileAccess.Read) != 0 && options.Mode == FileMode.Append)
{
throw new ArgumentException(SR.Argument_InvalidAppendMode, nameof(options));
}
else if ((options.Access & FileAccess.Write) == 0)
{
if (options.Mode == FileMode.Truncate || options.Mode == FileMode.CreateNew || options.Mode == FileMode.Create || options.Mode == FileMode.Append)
{
throw new ArgumentException(SR.Format(SR.Argument_InvalidFileModeAndAccessCombo, options.Mode, options.Access), nameof(options));
}
}
if (options.PreallocationSize > 0)
{
FileStreamHelpers.ValidateArgumentsForPreallocation(options.Mode, options.Access);
}
if (options.UnixCreateMode.HasValue)
{
// Only allow UnixCreateMode for file modes that can create a new file.
if (options.Mode == FileMode.Truncate || options.Mode == FileMode.Open)
{
throw new ArgumentException(SR.Argument_InvalidUnixCreateMode, nameof(options));
}
}
FileStreamHelpers.SerializationGuard(options.Access);
_strategy = FileStreamHelpers.ChooseStrategy(
this, path, options.Mode, options.Access, options.Share, options.BufferSize, options.Options, options.PreallocationSize, options.UnixCreateMode);
}
private FileStream(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options, long preallocationSize)
{
FileStreamHelpers.ValidateArguments(path, mode, access, share, bufferSize, options, preallocationSize);
_strategy = FileStreamHelpers.ChooseStrategy(this, path, mode, access, share, bufferSize, options, preallocationSize, unixCreateMode: null);
}
[Obsolete("FileStream.Handle has been deprecated. Use FileStream's SafeFileHandle property instead.")]
public virtual IntPtr Handle => _strategy.Handle;
[UnsupportedOSPlatform("ios")]
[UnsupportedOSPlatform("macos")]
[UnsupportedOSPlatform("tvos")]
[UnsupportedOSPlatform("freebsd")]
public virtual void Lock(long position, long length)
{
if (position < 0 || length < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeException_NeedNonNegNum(position < 0 ? nameof(position) : nameof(length));
}
else if (_strategy.IsClosed)
{
ThrowHelper.ThrowObjectDisposedException_FileClosed();
}
_strategy.Lock(position, length);
}
[UnsupportedOSPlatform("ios")]
[UnsupportedOSPlatform("macos")]
[UnsupportedOSPlatform("tvos")]
[UnsupportedOSPlatform("freebsd")]
public virtual void Unlock(long position, long length)
{
if (position < 0 || length < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeException_NeedNonNegNum(position < 0 ? nameof(position) : nameof(length));
}
else if (_strategy.IsClosed)
{
ThrowHelper.ThrowObjectDisposedException_FileClosed();
}
_strategy.Unlock(position, length);
}
public override Task FlushAsync(CancellationToken cancellationToken)
{
if (cancellationToken.IsCancellationRequested)
{
return Task.FromCanceled(cancellationToken);
}
else if (_strategy.IsClosed)
{
ThrowHelper.ThrowObjectDisposedException_FileClosed();
}
return _strategy.FlushAsync(cancellationToken);
}
public override int Read(byte[] buffer, int offset, int count)
{
ValidateReadWriteArgs(buffer, offset, count);
return _strategy.Read(buffer, offset, count);
}
public override int Read(Span<byte> buffer) => _strategy.Read(buffer);
public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
ValidateBufferArguments(buffer, offset, count);
if (cancellationToken.IsCancellationRequested)
{
return Task.FromCanceled<int>(cancellationToken);
}
else if (!_strategy.CanRead)
{
if (_strategy.IsClosed)
{
ThrowHelper.ThrowObjectDisposedException_FileClosed();
}
ThrowHelper.ThrowNotSupportedException_UnreadableStream();
}
return _strategy.ReadAsync(buffer, offset, count, cancellationToken);
}
public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
{
if (cancellationToken.IsCancellationRequested)
{
return ValueTask.FromCanceled<int>(cancellationToken);
}
else if (!_strategy.CanRead)
{
if (_strategy.IsClosed)
{
ThrowHelper.ThrowObjectDisposedException_FileClosed();
}
ThrowHelper.ThrowNotSupportedException_UnreadableStream();
}
return _strategy.ReadAsync(buffer, cancellationToken);
}
public override void Write(byte[] buffer, int offset, int count)
{
ValidateReadWriteArgs(buffer, offset, count);
_strategy.Write(buffer, offset, count);
}
public override void Write(ReadOnlySpan<byte> buffer) => _strategy.Write(buffer);
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
ValidateBufferArguments(buffer, offset, count);
if (cancellationToken.IsCancellationRequested)
{
return Task.FromCanceled(cancellationToken);
}
else if (!_strategy.CanWrite)
{
if (_strategy.IsClosed)
{
ThrowHelper.ThrowObjectDisposedException_FileClosed();
}
ThrowHelper.ThrowNotSupportedException_UnwritableStream();
}
return _strategy.WriteAsync(buffer, offset, count, cancellationToken);
}
public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
{
if (cancellationToken.IsCancellationRequested)
{
return ValueTask.FromCanceled(cancellationToken);
}
else if (!_strategy.CanWrite)
{
if (_strategy.IsClosed)
{
ThrowHelper.ThrowObjectDisposedException_FileClosed();
}
ThrowHelper.ThrowNotSupportedException_UnwritableStream();
}
return _strategy.WriteAsync(buffer, cancellationToken);
}
/// <summary>
/// Clears buffers for this stream and causes any buffered data to be written to the file.
/// </summary>
public override void Flush()
{
// Make sure that we call through the public virtual API
Flush(flushToDisk: false);
}
/// <summary>
/// Clears buffers for this stream, and if <param name="flushToDisk"/> is true,
/// causes any buffered data to be written to the file.
/// </summary>
public virtual void Flush(bool flushToDisk)
{
if (_strategy.IsClosed)
{
ThrowHelper.ThrowObjectDisposedException_FileClosed();
}
_strategy.Flush(flushToDisk);
}
/// <summary>Gets a value indicating whether the current stream supports reading.</summary>
public override bool CanRead => _strategy.CanRead;
/// <summary>Gets a value indicating whether the current stream supports writing.</summary>
public override bool CanWrite => _strategy.CanWrite;
/// <summary>Validates arguments to Read and Write and throws resulting exceptions.</summary>
/// <param name="buffer">The buffer to read from or write to.</param>
/// <param name="offset">The zero-based offset into the buffer.</param>
/// <param name="count">The maximum number of bytes to read or write.</param>
private void ValidateReadWriteArgs(byte[] buffer, int offset, int count)
{
ValidateBufferArguments(buffer, offset, count);
if (_strategy.IsClosed)
{
ThrowHelper.ThrowObjectDisposedException_FileClosed();
}
}
/// <summary>Sets the length of this stream to the given value.</summary>
/// <param name="value">The new length of the stream.</param>
public override void SetLength(long value)
{
if (value < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.value, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum);
}
else if (_strategy.IsClosed)
{
ThrowHelper.ThrowObjectDisposedException_FileClosed();
}
else if (!CanSeek)
{
ThrowHelper.ThrowNotSupportedException_UnseekableStream();
}
else if (!CanWrite)
{
ThrowHelper.ThrowNotSupportedException_UnwritableStream();
}
_strategy.SetLength(value);
}
public virtual SafeFileHandle SafeFileHandle => _strategy.SafeFileHandle;
/// <summary>Gets the path that was passed to the constructor.</summary>
public virtual string Name => _strategy.Name;
/// <summary>Gets a value indicating whether the stream was opened for I/O to be performed synchronously or asynchronously.</summary>
public virtual bool IsAsync => _strategy.IsAsync;
/// <summary>Gets the length of the stream in bytes.</summary>
public override long Length
{
get
{
if (_strategy.IsClosed)
{
ThrowHelper.ThrowObjectDisposedException_FileClosed();
}
else if (!CanSeek)
{
ThrowHelper.ThrowNotSupportedException_UnseekableStream();
}
return _strategy.Length;
}
}
/// <summary>Gets or sets the position within the current stream</summary>
public override long Position
{
get
{
if (_strategy.IsClosed)
{
ThrowHelper.ThrowObjectDisposedException_FileClosed();
}
else if (!CanSeek)
{
ThrowHelper.ThrowNotSupportedException_UnseekableStream();
}
return _strategy.Position;
}
set
{
if (value < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.value, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum);
}
else if (!CanSeek)
{
if (_strategy.IsClosed)
{
ThrowHelper.ThrowObjectDisposedException_FileClosed();
}
ThrowHelper.ThrowNotSupportedException_UnseekableStream();
}
_strategy.Position = value;
}
}
/// <summary>
/// Reads a byte from the file stream. Returns the byte cast to an int
/// or -1 if reading from the end of the stream.
/// </summary>
public override int ReadByte() => _strategy.ReadByte();
/// <summary>
/// Writes a byte to the current position in the stream and advances the position
/// within the stream by one byte.
/// </summary>
/// <param name="value">The byte to write to the stream.</param>
public override void WriteByte(byte value) => _strategy.WriteByte(value);
// _strategy can be null only when ctor has thrown
protected override void Dispose(bool disposing) => _strategy?.DisposeInternal(disposing);
public override async ValueTask DisposeAsync()
{
await _strategy.DisposeAsync().ConfigureAwait(false);
// For compatibility, derived classes must only call base.DisposeAsync(),
// otherwise we would end up calling Dispose twice (one from base.DisposeAsync() and one from here).
if (!_strategy.IsDerived)
{
Dispose(false);
GC.SuppressFinalize(this);
}
}
public override void CopyTo(Stream destination, int bufferSize)
{
ValidateCopyToArguments(destination, bufferSize);
_strategy.CopyTo(destination, bufferSize);
}
/// <inheritdoc />
public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken)
{
ValidateCopyToArguments(destination, bufferSize);
return _strategy.CopyToAsync(destination, bufferSize, cancellationToken);
}
public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback? callback, object? state)
{
ValidateBufferArguments(buffer, offset, count);
if (_strategy.IsClosed)
{
ThrowHelper.ThrowObjectDisposedException_FileClosed();
}
else if (!CanRead)
{
ThrowHelper.ThrowNotSupportedException_UnreadableStream();
}
return _strategy.BeginRead(buffer, offset, count, callback, state);
}
public override int EndRead(IAsyncResult asyncResult)
{
ArgumentNullException.ThrowIfNull(asyncResult);
return _strategy.EndRead(asyncResult);
}
public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback? callback, object? state)
{
ValidateBufferArguments(buffer, offset, count);
if (_strategy.IsClosed)
{
ThrowHelper.ThrowObjectDisposedException_FileClosed();
}
else if (!CanWrite)
{
ThrowHelper.ThrowNotSupportedException_UnwritableStream();
}
return _strategy.BeginWrite(buffer, offset, count, callback, state);
}
public override void EndWrite(IAsyncResult asyncResult)
{
ArgumentNullException.ThrowIfNull(asyncResult);
_strategy.EndWrite(asyncResult);
}
public override bool CanSeek => _strategy.CanSeek;
public override long Seek(long offset, SeekOrigin origin)
{
if (origin < SeekOrigin.Begin || origin > SeekOrigin.End)
{
throw new ArgumentException(SR.Argument_InvalidSeekOrigin, nameof(origin));
}
else if (!CanSeek)
{
if (_strategy.IsClosed)
{
ThrowHelper.ThrowObjectDisposedException_FileClosed();
}
ThrowHelper.ThrowNotSupportedException_UnseekableStream();
}
return _strategy.Seek(offset, origin);
}
internal Task BaseFlushAsync(CancellationToken cancellationToken)
=> base.FlushAsync(cancellationToken);
internal int BaseRead(Span<byte> buffer) => base.Read(buffer);
internal Task<int> BaseReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
=> base.ReadAsync(buffer, offset, count, cancellationToken);
internal ValueTask<int> BaseReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
=> base.ReadAsync(buffer, cancellationToken);
internal void BaseWrite(ReadOnlySpan<byte> buffer) => base.Write(buffer);
internal Task BaseWriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
=> base.WriteAsync(buffer, offset, count, cancellationToken);
internal ValueTask BaseWriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
=> base.WriteAsync(buffer, cancellationToken);
internal ValueTask BaseDisposeAsync()
=> base.DisposeAsync();
internal Task BaseCopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken)
=> base.CopyToAsync(destination, bufferSize, cancellationToken);
internal IAsyncResult BaseBeginRead(byte[] buffer, int offset, int count, AsyncCallback? callback, object? state)
=> base.BeginRead(buffer, offset, count, callback, state);
internal int BaseEndRead(IAsyncResult asyncResult) => base.EndRead(asyncResult);
internal IAsyncResult BaseBeginWrite(byte[] buffer, int offset, int count, AsyncCallback? callback, object? state)
=> base.BeginWrite(buffer, offset, count, callback, state);
internal void BaseEndWrite(IAsyncResult asyncResult) => base.EndWrite(asyncResult);
}
}
|