2 // System.IO/FileStream.cs
\r
5 // Dietmar Maurer (dietmar@ximian.com)
\r
6 // Dan Lewis (dihlewis@yahoo.co.uk)
\r
8 // (C) 2001 Ximian, Inc. http://www.ximian.com
\r
12 using System.Runtime.CompilerServices;
\r
13 using System.Runtime.InteropServices;
15 // FIXME: emit the correct exceptions everywhere. add error handling.
\r
20 public class FileStream : Stream
\r
22 // construct from handle
\r
24 public FileStream (IntPtr handle, FileAccess access)
\r
25 : this (handle, access, true, DefaultBufferSize, false) {}
\r
27 public FileStream (IntPtr handle, FileAccess access, bool ownsHandle)
\r
28 : this (handle, access, ownsHandle, DefaultBufferSize, false) {}
\r
30 public FileStream (IntPtr handle, FileAccess access, bool ownsHandle, int bufferSize)
\r
31 : this (handle, access, ownsHandle, bufferSize, false) {}
\r
33 public FileStream (IntPtr handle, FileAccess access, bool ownsHandle, int bufferSize, bool isAsync)
34 : this (handle, access, ownsHandle, bufferSize, isAsync, false) {}
36 internal FileStream (IntPtr handle, FileAccess access, bool ownsHandle, int bufferSize, bool isAsync, bool noBuffering)
38 if (access < FileAccess.Read || access > FileAccess.ReadWrite)
39 throw new ArgumentOutOfRangeException ("access");
43 this.owner = ownsHandle;
47 MonoFileType ftype = MonoIO.GetFileType (handle, out error);
49 if (ftype == MonoFileType.Unknown) {
50 throw new IOException ("Invalid handle.");
51 } else if (ftype == MonoFileType.Disk) {
57 InitBuffer (bufferSize, noBuffering);
59 /* Can't set append mode */
60 this.append_startpos=0;
63 // construct from filename
\r
65 public FileStream (string name, FileMode mode)
66 : this (name, mode, (mode == FileMode.Append ? FileAccess.Write : FileAccess.ReadWrite), FileShare.Read, DefaultBufferSize, false) { }
\r
68 public FileStream (string name, FileMode mode, FileAccess access)
\r
69 : this (name, mode, access, FileShare.ReadWrite, DefaultBufferSize, false) { }
\r
71 public FileStream (string name, FileMode mode, FileAccess access, FileShare share)
\r
72 : this (name, mode, access, share, DefaultBufferSize, false) { }
\r
74 public FileStream (string name, FileMode mode, FileAccess access, FileShare share, int bufferSize)
\r
75 : this (name, mode, access, share, bufferSize, false) { }
\r
77 public FileStream (string name, FileMode mode, FileAccess access, FileShare share, int bufferSize, bool isAsync)
80 throw new ArgumentNullException ("name");
84 throw new ArgumentException ("Name is empty");
87 if (mode < FileMode.CreateNew || mode > FileMode.Append)
88 throw new ArgumentOutOfRangeException ("mode");
90 if (access < FileAccess.Read || access > FileAccess.ReadWrite)
91 throw new ArgumentOutOfRangeException ("access");
93 if (share < FileShare.None || share > FileShare.ReadWrite)
94 throw new ArgumentOutOfRangeException ("share");
96 if (name.IndexOfAny (Path.InvalidPathChars) != -1) {
97 throw new ArgumentException ("Name has invalid chars");
100 if (Directory.Exists (name)) {
101 throw new UnauthorizedAccessException ("Access to the path '" + Path.GetFullPath (name) + "' is denied.");
104 InitBuffer (bufferSize, false);
106 /* Append streams can't be read (see FileMode
109 if (mode==FileMode.Append &&
110 (access&FileAccess.Read)==FileAccess.Read) {
111 throw new ArgumentException("Append streams can not be read");
114 if ((access & FileAccess.Write) == 0 &&
115 (mode != FileMode.Open && mode != FileMode.OpenOrCreate))
116 throw new ArgumentException ("access and mode not compatible");
120 // TODO: demand permissions
124 this.handle = MonoIO.Open (name, mode, access, share,
126 if (handle == MonoIO.InvalidHandle) {
127 throw MonoIO.GetException (name, error);
130 this.access = access;
132 this.async = isAsync;
134 /* Can we open non-files by name? */
136 if (MonoIO.GetFileType (handle, out error) ==
140 this.canseek = false;
144 if (mode==FileMode.Append) {
145 this.Seek (0, SeekOrigin.End);
146 this.append_startpos=this.Position;
148 this.append_startpos=0;
154 public override bool CanRead {
\r
156 return access == FileAccess.Read ||
\r
157 access == FileAccess.ReadWrite;
\r
161 public override bool CanWrite {
\r
163 return access == FileAccess.Write ||
\r
164 access == FileAccess.ReadWrite;
\r
168 public override bool CanSeek {
174 public virtual bool IsAsync {
180 public string Name {
\r
186 public override long Length {
188 if (handle == MonoIO.InvalidHandle)
189 throw new ObjectDisposedException ("Stream has been closed");
192 throw new NotSupportedException ("The stream does not support seeking");
194 // Buffered data might change the length of the stream
195 FlushBufferIfDirty ();
199 return MonoIO.GetLength (handle, out error);
203 public override long Position {
205 if (handle == MonoIO.InvalidHandle)
206 throw new ObjectDisposedException ("Stream has been closed");
209 throw new NotSupportedException("The stream does not support seeking");
212 return(buf_start + buf_offset);
216 if(CanSeek == false) {
217 throw new NotSupportedException("The stream does not support seeking");
221 throw new ArgumentOutOfRangeException("Attempt to set the position to a negative value");
224 Seek (value, SeekOrigin.Begin);
228 public virtual IntPtr Handle {
236 public override int ReadByte ()
238 if (handle == MonoIO.InvalidHandle)
239 throw new ObjectDisposedException ("Stream has been closed");
242 throw new NotSupportedException ("Stream does not support reading");
245 if (buf_offset >= buf_length) {
248 if (buf_length == 0) {
253 return(buf [buf_offset ++]);
257 public override void WriteByte (byte value)
259 if (handle == MonoIO.InvalidHandle)
260 throw new ObjectDisposedException ("Stream has been closed");
263 throw new NotSupportedException ("Stream does not support writing");
266 if (buf_offset == buf_size) {
270 buf [buf_offset ++] = value;
271 if (buf_offset > buf_length) {
272 buf_length = buf_offset;
279 public override int Read ([In,Out] byte[] dest, int dest_offset, int count)
281 if (handle == MonoIO.InvalidHandle)
282 throw new ObjectDisposedException ("Stream has been closed");
284 throw new ArgumentNullException ("destination array is null");
286 throw new NotSupportedException ("Stream does not support reading");
287 int len = dest.Length;
288 if (dest_offset < 0 || count < 0)
289 throw new ArgumentException ("dest or count is negative");
290 if (dest_offset > len)
291 throw new ArgumentException ("destination offset is beyond array size");
292 if ((dest_offset + count) > len)
293 throw new ArgumentException ("Reading would overrun buffer");
299 int n = ReadSegment (dest, dest_offset, count);
304 /* If there was already enough
305 * buffered, no need to read
306 * more from the file.
311 if (count > buf_size) {
312 /* Read as much as we can, up
316 n = ReadData (handle, dest,
320 /* Make the next buffer read
321 * start from the right place
326 n = ReadSegment (dest,
337 public override void Write (byte[] src, int src_offset, int count)
339 if (handle == MonoIO.InvalidHandle)
340 throw new ObjectDisposedException ("Stream has been closed");
343 throw new ArgumentNullException ("src");
346 throw new ArgumentOutOfRangeException ("src_offset");
349 throw new ArgumentOutOfRangeException ("count");
352 throw new NotSupportedException ("Stream does not support writing");
354 if (count > buf_size) {
355 // shortcut for long writes
360 MonoIO.Write (handle, src, src_offset, count, out error);
367 int n = WriteSegment (src, src_offset + copied, count);
380 public override long Seek (long offset, SeekOrigin origin)
384 if (handle == MonoIO.InvalidHandle)
385 throw new ObjectDisposedException ("Stream has been closed");
389 if(CanSeek == false) {
390 throw new NotSupportedException("The stream does not support seeking");
397 pos = Length + offset;
400 case SeekOrigin.Current:
401 pos = Position + offset;
404 case SeekOrigin.Begin: default:
410 /* LAMESPEC: shouldn't this be
411 * ArgumentOutOfRangeException?
413 throw new IOException("Attempted to Seek before the beginning of the stream");
416 if(pos < this.append_startpos) {
417 /* More undocumented crap */
418 throw new IOException("Can't seek back over pre-existing data in append mode");
421 if (buf_length > 0) {
422 if (pos >= buf_start &&
423 pos <= buf_start + buf_length) {
424 buf_offset = (int) (pos - buf_start);
433 buf_start = MonoIO.Seek (handle, pos,
441 public override void SetLength (long length)
443 if(CanSeek == false) {
444 throw new NotSupportedException("The stream does not support seeking");
447 if(CanWrite == false) {
448 throw new NotSupportedException("The stream does not support writing");
452 throw new ArgumentOutOfRangeException("Length is less than 0");
459 MonoIO.SetLength (handle, length, out error);
462 public override void Flush ()
464 if (handle == MonoIO.InvalidHandle)
465 throw new ObjectDisposedException ("Stream has been closed");
471 // The flushing is not actually required, in
472 //the mono runtime we were mapping flush to
473 //`fsync' which is not the same.
475 //MonoIO.Flush (handle);
478 public override void Close ()
\r
481 GC.SuppressFinalize (this); // remove from finalize queue
\r
485 public virtual void Lock (long position, long length)
487 throw new NotImplementedException ();
491 public virtual void Unlock (long position, long length)
493 throw new NotImplementedException ();
503 protected virtual void Dispose (bool disposing) {
504 if (handle != MonoIO.InvalidHandle) {
512 MonoIO.Close (handle, out error);
514 handle = MonoIO.InvalidHandle;
527 // ReadSegment, WriteSegment, FlushBuffer,
528 // RefillBuffer and ReadData should only be called
529 // when the Monitor lock is held, but these methods
530 // grab it again just to be safe.
532 private int ReadSegment (byte [] dest, int dest_offset,
535 if (count > buf_length - buf_offset) {
536 count = buf_length - buf_offset;
540 Buffer.BlockCopy (buf, buf_offset,
549 private int WriteSegment (byte [] src, int src_offset,
552 if (count > buf_size - buf_offset) {
553 count = buf_size - buf_offset;
557 Buffer.BlockCopy (src, src_offset,
561 if (buf_offset > buf_length) {
562 buf_length = buf_offset;
571 private void FlushBuffer ()
576 if (CanSeek == true) {
577 MonoIO.Seek (handle, buf_start,
581 MonoIO.Write (handle, buf, 0,
582 buf_length, out error);
585 buf_start += buf_offset;
586 buf_offset = buf_length = 0;
590 private void FlushBufferIfDirty ()
596 private void RefillBuffer ()
600 buf_length = ReadData (handle, buf, 0,
604 private int ReadData (IntPtr handle, byte[] buf, int offset,
609 int amount = MonoIO.Read (handle, buf, offset,
612 /* Check for read error */
614 /* Kludge around broken pipes */
615 if(error == MonoIOError.ERROR_BROKEN_PIPE) {
618 throw new IOException ();
625 private void InitBuffer (int size, bool noBuffering)
\r
631 throw new ArgumentOutOfRangeException ("bufferSize", "Positive number required.");
636 buf = new byte [size];
\r
639 buf_offset = buf_length = 0;
\r
645 private static int DefaultBufferSize = 8192;
\r
647 private FileAccess access;
\r
648 private bool owner;
\r
649 private bool async;
\r
650 private bool canseek;
651 private long append_startpos;
654 private byte [] buf; // the buffer
\r
655 private int buf_size; // capacity in bytes
\r
656 private int buf_length; // number of valid bytes in buffer
\r
657 private int buf_offset; // position of next byte
\r
658 private bool buf_dirty; // true if buffer has been written to
\r
659 private long buf_start; // location of buffer in file
\r
660 private string name = "[Unknown]"; // name of file.
\r
662 IntPtr handle; // handle to underlying file
\r