2 // System.IO/FileStream.cs
5 // Dietmar Maurer (dietmar@ximian.com)
6 // Dan Lewis (dihlewis@yahoo.co.uk)
7 // Gonzalo Paniagua Javier (gonzalo@ximian.com)
9 // (C) 2001-2003 Ximian, Inc. http://www.ximian.com
10 // (c) 2004 Novell, Inc. (http://www.novell.com)
14 using System.Collections;
15 using System.Globalization;
16 using System.Runtime.CompilerServices;
17 using System.Runtime.InteropServices;
18 using System.Runtime.Remoting.Messaging;
19 using System.Threading;
23 public class FileStream : Stream
25 // construct from handle
27 public FileStream (IntPtr handle, FileAccess access)
28 : this (handle, access, true, DefaultBufferSize, false) {}
30 public FileStream (IntPtr handle, FileAccess access, bool ownsHandle)
31 : this (handle, access, ownsHandle, DefaultBufferSize, false) {}
33 public FileStream (IntPtr handle, FileAccess access, bool ownsHandle, int bufferSize)
34 : this (handle, access, ownsHandle, bufferSize, false) {}
36 public FileStream (IntPtr handle, FileAccess access, bool ownsHandle, int bufferSize, bool isAsync)
37 : this (handle, access, ownsHandle, bufferSize, isAsync, false) {}
39 internal FileStream (IntPtr handle, FileAccess access, bool ownsHandle, int bufferSize, bool isAsync, bool noBuffering)
41 this.handle = MonoIO.InvalidHandle;
42 if (handle == this.handle)
43 throw new ArgumentException ("handle", Locale.GetText ("Invalid."));
45 if (access < FileAccess.Read || access > FileAccess.ReadWrite)
46 throw new ArgumentOutOfRangeException ("access");
49 MonoFileType ftype = MonoIO.GetFileType (handle, out error);
51 if (error != MonoIOError.ERROR_SUCCESS) {
52 throw MonoIO.GetException (name, error);
55 if (ftype == MonoFileType.Unknown) {
56 throw new IOException ("Invalid handle.");
57 } else if (ftype == MonoFileType.Disk) {
65 this.owner = ownsHandle;
68 if (isAsync && MonoIO.SupportsAsync)
69 ThreadPool.BindHandle (handle);
71 InitBuffer (bufferSize, noBuffering);
73 /* Can't set append mode */
74 this.append_startpos=0;
77 // construct from filename
79 public FileStream (string name, FileMode mode)
80 : this (name, mode, (mode == FileMode.Append ? FileAccess.Write : FileAccess.ReadWrite), FileShare.Read, DefaultBufferSize, false) { }
82 public FileStream (string name, FileMode mode, FileAccess access)
83 : this (name, mode, access, FileShare.ReadWrite, DefaultBufferSize, false) { }
85 public FileStream (string name, FileMode mode, FileAccess access, FileShare share)
86 : this (name, mode, access, share, DefaultBufferSize, false) { }
88 public FileStream (string name, FileMode mode, FileAccess access, FileShare share, int bufferSize)
89 : this (name, mode, access, share, bufferSize, false) { }
91 public FileStream (string name, FileMode mode, FileAccess access, FileShare share, int bufferSize, bool isAsync)
94 throw new ArgumentNullException ("name");
98 throw new ArgumentException ("Name is empty");
101 if (mode < FileMode.CreateNew || mode > FileMode.Append)
102 throw new ArgumentOutOfRangeException ("mode");
104 if (access < FileAccess.Read || access > FileAccess.ReadWrite)
105 throw new ArgumentOutOfRangeException ("access");
107 if (share < FileShare.None || share > FileShare.ReadWrite)
108 throw new ArgumentOutOfRangeException ("share");
110 if (name.IndexOfAny (Path.InvalidPathChars) != -1) {
111 throw new ArgumentException ("Name has invalid chars");
114 if (Directory.Exists (name)) {
115 throw new UnauthorizedAccessException ("Access to the path '" + Path.GetFullPath (name) + "' is denied.");
118 /* Append streams can't be read (see FileMode
121 if (mode==FileMode.Append &&
122 (access&FileAccess.Read)==FileAccess.Read) {
123 throw new ArgumentException("Append streams can not be read");
126 if ((access & FileAccess.Write) == 0 &&
127 (mode != FileMode.Open && mode != FileMode.OpenOrCreate))
128 throw new ArgumentException ("access and mode not compatible");
130 if (access == FileAccess.Read && mode != FileMode.Create && mode != FileMode.OpenOrCreate &&
131 mode != FileMode.CreateNew && !File.Exists (name))
132 throw new FileNotFoundException ("Could not find file \"" + name + "\".");
134 if (mode == FileMode.CreateNew) {
135 string dname = Path.GetDirectoryName (name);
137 if (dname != "" && !Directory.Exists ((fp = Path.GetFullPath (dname))))
138 throw new DirectoryNotFoundException ("Could not find a part of " +
139 "the path \"" + fp + "\".");
144 // TODO: demand permissions
148 bool openAsync = (isAsync && MonoIO.SupportsAsync);
149 this.handle = MonoIO.Open (name, mode, access, share, openAsync, out error);
150 if (handle == MonoIO.InvalidHandle) {
151 throw MonoIO.GetException (name, error);
154 this.access = access;
157 /* Can we open non-files by name? */
159 if (MonoIO.GetFileType (handle, out error) == MonoFileType.Disk) {
161 this.async = isAsync;
163 ThreadPool.BindHandle (handle);
165 this.canseek = false;
170 if (access == FileAccess.Read && canseek && (bufferSize == DefaultBufferSize)) {
171 /* Avoid allocating a large buffer for small files */
173 if (bufferSize > len) {
174 bufferSize = (int)(len < 1000 ? 1000 : len);
178 InitBuffer (bufferSize, false);
180 if (mode==FileMode.Append) {
181 this.Seek (0, SeekOrigin.End);
182 this.append_startpos=this.Position;
184 this.append_startpos=0;
190 public override bool CanRead {
192 return access == FileAccess.Read ||
193 access == FileAccess.ReadWrite;
197 public override bool CanWrite {
199 return access == FileAccess.Write ||
200 access == FileAccess.ReadWrite;
204 public override bool CanSeek {
210 public virtual bool IsAsync {
222 public override long Length {
224 if (handle == MonoIO.InvalidHandle)
225 throw new ObjectDisposedException ("Stream has been closed");
228 throw new NotSupportedException ("The stream does not support seeking");
230 // Buffered data might change the length of the stream
231 FlushBufferIfDirty ();
236 length = MonoIO.GetLength (handle, out error);
237 if (error != MonoIOError.ERROR_SUCCESS) {
238 throw MonoIO.GetException (name,
246 public override long Position {
248 if (handle == MonoIO.InvalidHandle)
249 throw new ObjectDisposedException ("Stream has been closed");
252 throw new NotSupportedException("The stream does not support seeking");
254 return(buf_start + buf_offset);
257 if (handle == MonoIO.InvalidHandle)
258 throw new ObjectDisposedException ("Stream has been closed");
260 if(CanSeek == false) {
261 throw new NotSupportedException("The stream does not support seeking");
265 throw new ArgumentOutOfRangeException("Attempt to set the position to a negative value");
268 Seek (value, SeekOrigin.Begin);
272 public virtual IntPtr Handle {
280 public override int ReadByte ()
282 if (handle == MonoIO.InvalidHandle)
283 throw new ObjectDisposedException ("Stream has been closed");
286 throw new NotSupportedException ("Stream does not support reading");
289 int n = ReadData (handle, buf, 0, 1);
290 if (n == 0) return -1;
293 else if (buf_offset >= buf_length) {
300 return buf [buf_offset ++];
303 public override void WriteByte (byte value)
305 if (handle == MonoIO.InvalidHandle)
306 throw new ObjectDisposedException ("Stream has been closed");
309 throw new NotSupportedException ("Stream does not support writing");
311 if (buf_offset == buf_size)
314 buf [buf_offset ++] = value;
315 if (buf_offset > buf_length)
316 buf_length = buf_offset;
321 public override int Read ([In,Out] byte[] dest, int dest_offset, int count)
323 if (handle == MonoIO.InvalidHandle)
324 throw new ObjectDisposedException ("Stream has been closed");
326 throw new ArgumentNullException ("destFile");
328 throw new NotSupportedException ("Stream does not support reading");
329 int len = dest.Length;
331 throw new ArgumentOutOfRangeException ("dest_offset", "< 0");
333 throw new ArgumentOutOfRangeException ("count", "< 0");
334 if (dest_offset > len)
335 throw new ArgumentException ("destination offset is beyond array size");
336 // reordered to avoid possible integer overflow
337 if (dest_offset > len - count)
338 throw new ArgumentException ("Reading would overrun buffer");
341 IAsyncResult ares = BeginRead (dest, dest_offset, count, null, null);
342 return EndRead (ares);
345 return ReadInternal (dest, dest_offset, count);
348 int ReadInternal (byte [] dest, int dest_offset, int count)
352 int n = ReadSegment (dest, dest_offset, count);
357 /* If there was already enough
358 * buffered, no need to read
359 * more from the file.
364 if (count > buf_size) {
365 /* Read as much as we can, up
369 n = ReadData (handle, dest,
373 /* Make the next buffer read
374 * start from the right place
379 n = ReadSegment (dest,
389 delegate int ReadDelegate (byte [] buffer, int offset, int count);
391 public override IAsyncResult BeginRead (byte [] buffer, int offset, int count,
392 AsyncCallback cback, object state)
394 if (handle == MonoIO.InvalidHandle)
395 throw new ObjectDisposedException ("Stream has been closed");
398 throw new NotSupportedException ("This stream does not support reading");
401 throw new ArgumentNullException ("buffer");
404 throw new ArgumentOutOfRangeException ("count", "Must be >= 0");
407 throw new ArgumentOutOfRangeException ("offset", "Must be >= 0");
409 // reordered to avoid possible integer overflow
410 if (count > buffer.Length - offset)
411 throw new ArgumentException ("Buffer too small. count/offset wrong.");
414 return base.BeginRead (buffer, offset, count, cback, state);
416 if (!MonoIO.SupportsAsync) {
417 ReadDelegate r = new ReadDelegate (ReadInternal);
418 return r.BeginInvoke (buffer, offset, count, cback, state);
421 FileStreamAsyncResult result = new FileStreamAsyncResult (cback, state);
422 result.Count = count;
423 result.OriginalCount = count;
424 int buffered = ReadSegment (buffer, offset, count);
425 if (buffered >= count) {
426 result.SetComplete (null, buffered, true);
430 result.Buffer = buffer;
431 result.Offset = offset + buffered;
432 result.Count -= buffered;
434 KeepReference (result);
435 MonoIO.BeginRead (handle, result);
440 public override int EndRead (IAsyncResult async_result)
442 if (async_result == null)
443 throw new ArgumentNullException ("async_result");
446 return base.EndRead (async_result);
448 if (!MonoIO.SupportsAsync) {
449 AsyncResult ares = async_result as AsyncResult;
451 throw new ArgumentException ("Invalid IAsyncResult", "async_result");
453 ReadDelegate r = ares.AsyncDelegate as ReadDelegate;
455 throw new ArgumentException ("Invalid IAsyncResult", "async_result");
457 return r.EndInvoke (async_result);
460 FileStreamAsyncResult result = async_result as FileStreamAsyncResult;
461 if (result == null || result.BytesRead == -1)
462 throw new ArgumentException ("Invalid IAsyncResult", "async_result");
464 RemoveReference (result);
466 throw new InvalidOperationException ("EndRead already called.");
469 if (!result.IsCompleted)
470 result.AsyncWaitHandle.WaitOne ();
472 if (result.Exception != null)
473 throw result.Exception;
475 buf_start += result.BytesRead;
476 return result.OriginalCount - result.Count + result.BytesRead;
479 public override void Write (byte[] src, int src_offset, int count)
481 if (handle == MonoIO.InvalidHandle)
482 throw new ObjectDisposedException ("Stream has been closed");
484 throw new ArgumentNullException ("src");
486 throw new ArgumentOutOfRangeException ("src_offset", "< 0");
488 throw new ArgumentOutOfRangeException ("count", "< 0");
489 // ordered to avoid possible integer overflow
490 if (src_offset > src.Length - count)
491 throw new ArgumentException ("Reading would overrun buffer");
493 throw new NotSupportedException ("Stream does not support writing");
496 IAsyncResult ares = BeginWrite (src, src_offset, count, null, null);
501 WriteInternal (src, src_offset, count);
504 void WriteInternal (byte [] src, int src_offset, int count)
506 if (count > buf_size) {
507 // shortcut for long writes
512 MonoIO.Write (handle, src, src_offset, count, out error);
513 if (error != MonoIOError.ERROR_SUCCESS) {
514 throw MonoIO.GetException (name,
524 int n = WriteSegment (src, src_offset + copied, count);
537 delegate void WriteDelegate (byte [] buffer, int offset, int count);
539 public override IAsyncResult BeginWrite (byte [] buffer, int offset, int count,
540 AsyncCallback cback, object state)
542 if (handle == MonoIO.InvalidHandle)
543 throw new ObjectDisposedException ("Stream has been closed");
546 throw new NotSupportedException ("This stream does not support writing");
549 throw new ArgumentNullException ("buffer");
552 throw new ArgumentOutOfRangeException ("count", "Must be >= 0");
555 throw new ArgumentOutOfRangeException ("offset", "Must be >= 0");
557 // reordered to avoid possible integer overflow
558 if (count > buffer.Length - offset)
559 throw new ArgumentException ("Buffer too small. count/offset wrong.");
562 return base.BeginWrite (buffer, offset, count, cback, state);
566 FileStreamAsyncResult result = new FileStreamAsyncResult (cback, state);
567 result.BytesRead = -1;
568 result.Count = count;
569 result.OriginalCount = count;
572 MemoryStream ms = new MemoryStream ();
573 FlushBufferToStream (ms);
574 buffered = (int) ms.Length;
575 ms.Write (buffer, offset, count);
576 bytes = ms.GetBuffer ();
578 count = (int) ms.Length;
583 if (!MonoIO.SupportsAsync) {
584 WriteDelegate w = new WriteDelegate (WriteInternal);
585 return w.BeginInvoke (buffer, offset, count, cback, state);
588 if (buffered >= count) {
589 result.SetComplete (null, buffered, true);
593 result.Buffer = buffer;
594 result.Offset = offset;
595 result.Count = count;
597 KeepReference (result);
598 MonoIO.BeginWrite (handle, result);
603 public override void EndWrite (IAsyncResult async_result)
605 if (async_result == null)
606 throw new ArgumentNullException ("async_result");
609 base.EndWrite (async_result);
613 if (!MonoIO.SupportsAsync) {
614 AsyncResult ares = async_result as AsyncResult;
616 throw new ArgumentException ("Invalid IAsyncResult", "async_result");
618 WriteDelegate w = ares.AsyncDelegate as WriteDelegate;
620 throw new ArgumentException ("Invalid IAsyncResult", "async_result");
622 w.EndInvoke (async_result);
626 FileStreamAsyncResult result = async_result as FileStreamAsyncResult;
627 if (result == null || result.BytesRead != -1)
628 throw new ArgumentException ("Invalid IAsyncResult", "async_result");
630 RemoveReference (result);
632 throw new InvalidOperationException ("EndWrite already called.");
635 if (!result.IsCompleted)
636 result.AsyncWaitHandle.WaitOne ();
638 if (result.Exception != null)
639 throw result.Exception;
641 buf_start += result.Count;
642 buf_offset = buf_length = 0;
645 public override long Seek (long offset, SeekOrigin origin)
649 if (handle == MonoIO.InvalidHandle)
650 throw new ObjectDisposedException ("Stream has been closed");
654 if(CanSeek == false) {
655 throw new NotSupportedException("The stream does not support seeking");
660 pos = Length + offset;
663 case SeekOrigin.Current:
664 pos = Position + offset;
667 case SeekOrigin.Begin:
672 throw new ArgumentException ("origin", "Invalid SeekOrigin");
676 /* LAMESPEC: shouldn't this be
677 * ArgumentOutOfRangeException?
679 throw new IOException("Attempted to Seek before the beginning of the stream");
682 if(pos < this.append_startpos) {
683 /* More undocumented crap */
684 throw new IOException("Can't seek back over pre-existing data in append mode");
687 if (buf_length > 0) {
688 if (pos >= buf_start &&
689 pos <= buf_start + buf_length) {
690 buf_offset = (int) (pos - buf_start);
699 buf_start = MonoIO.Seek (handle, pos,
703 if (error != MonoIOError.ERROR_SUCCESS) {
704 throw MonoIO.GetException (name, error);
710 public override void SetLength (long length)
712 if(CanSeek == false) {
713 throw new NotSupportedException("The stream does not support seeking");
716 if(CanWrite == false) {
717 throw new NotSupportedException("The stream does not support writing");
721 throw new ArgumentOutOfRangeException("Length is less than 0");
724 if (handle == MonoIO.InvalidHandle)
725 throw new IOException ("Stream has been closed");
731 MonoIO.SetLength (handle, length, out error);
732 if (error != MonoIOError.ERROR_SUCCESS) {
733 throw MonoIO.GetException (name, error);
736 if (Position > length)
740 public override void Flush ()
742 if (handle == MonoIO.InvalidHandle)
743 throw new ObjectDisposedException ("Stream has been closed");
747 // The flushing is not actually required, in
748 //the mono runtime we were mapping flush to
749 //`fsync' which is not the same.
751 //MonoIO.Flush (handle);
754 public override void Close ()
757 GC.SuppressFinalize (this); // remove from finalize queue
760 public virtual void Lock (long position, long length)
762 if (handle == MonoIO.InvalidHandle)
763 throw new ObjectDisposedException ("Stream has been closed");
765 throw new ArgumentOutOfRangeException ("position must not be negative");
768 throw new ArgumentOutOfRangeException ("length must not be negative");
770 if (handle == MonoIO.InvalidHandle) {
771 throw new ObjectDisposedException ("Stream has been closed");
776 MonoIO.Lock (handle, position, length, out error);
777 if (error != MonoIOError.ERROR_SUCCESS) {
778 throw MonoIO.GetException (name, error);
782 public virtual void Unlock (long position, long length)
784 if (handle == MonoIO.InvalidHandle)
785 throw new ObjectDisposedException ("Stream has been closed");
787 throw new ArgumentOutOfRangeException ("position must not be negative");
790 throw new ArgumentOutOfRangeException ("length must not be negative");
795 MonoIO.Unlock (handle, position, length, out error);
796 if (error != MonoIOError.ERROR_SUCCESS) {
797 throw MonoIO.GetException (name, error);
808 protected virtual void Dispose (bool disposing) {
809 if (handle != MonoIO.InvalidHandle) {
815 MonoIO.Close (handle, out error);
816 if (error != MonoIOError.ERROR_SUCCESS) {
817 throw MonoIO.GetException (name, error);
820 handle = MonoIO.InvalidHandle;
833 // ReadSegment, WriteSegment, FlushBuffer,
834 // RefillBuffer and ReadData should only be called
835 // when the Monitor lock is held, but these methods
836 // grab it again just to be safe.
838 private int ReadSegment (byte [] dest, int dest_offset, int count)
840 if (count > buf_length - buf_offset) {
841 count = buf_length - buf_offset;
845 Buffer.BlockCopy (buf, buf_offset,
854 private int WriteSegment (byte [] src, int src_offset,
857 if (count > buf_size - buf_offset) {
858 count = buf_size - buf_offset;
862 Buffer.BlockCopy (src, src_offset,
866 if (buf_offset > buf_length) {
867 buf_length = buf_offset;
876 void FlushBufferToStream (Stream st)
879 if (CanSeek == true) {
881 MonoIO.Seek (handle, buf_start,
884 if (error != MonoIOError.ERROR_SUCCESS) {
885 throw MonoIO.GetException (name, error);
888 st.Write (buf, 0, buf_length);
891 buf_start += buf_offset;
892 buf_offset = buf_length = 0;
896 private void FlushBuffer ()
901 if (CanSeek == true) {
902 MonoIO.Seek (handle, buf_start,
905 if (error != MonoIOError.ERROR_SUCCESS) {
906 throw MonoIO.GetException (name, error);
909 MonoIO.Write (handle, buf, 0,
910 buf_length, out error);
912 if (error != MonoIOError.ERROR_SUCCESS) {
913 throw MonoIO.GetException (name, error);
917 buf_start += buf_offset;
918 buf_offset = buf_length = 0;
922 private void FlushBufferIfDirty ()
928 private void RefillBuffer ()
932 buf_length = ReadData (handle, buf, 0,
936 private int ReadData (IntPtr handle, byte[] buf, int offset,
942 /* when async == true, if we get here we don't suport AIO or it's disabled
943 * and we're using the threadpool */
944 amount = MonoIO.Read (handle, buf, offset, count, out error);
945 if (error == MonoIOError.ERROR_BROKEN_PIPE) {
946 amount = 0; // might not be needed, but well...
947 } else if (error != MonoIOError.ERROR_SUCCESS) {
948 throw MonoIO.GetException (name, error);
951 /* Check for read error */
953 throw new IOException ();
959 private void InitBuffer (int size, bool noBuffering)
963 // We need a buffer for the ReadByte method. This buffer won't
964 // be used for anything else since buf_size==0.
969 throw new ArgumentOutOfRangeException ("bufferSize", "Positive number required.");
972 buf = new byte [size];
977 buf_offset = buf_length = 0;
981 static void KeepReference (object o)
983 lock (typeof (FileStream)) {
984 if (asyncObjects == null)
985 asyncObjects = new Hashtable ();
987 asyncObjects [o] = o;
991 static void RemoveReference (object o)
993 lock (typeof (FileStream)) {
994 if (asyncObjects == null)
997 asyncObjects.Remove (o);
1003 const int DefaultBufferSize = 8192;
1004 private static Hashtable asyncObjects;
1006 private FileAccess access;
1009 private bool canseek;
1010 private long append_startpos;
1013 private byte [] buf; // the buffer
1014 private int buf_size; // capacity in bytes
1015 private int buf_length; // number of valid bytes in buffer
1016 private int buf_offset; // position of next byte
1017 private bool buf_dirty; // true if buffer has been written to
1018 private long buf_start; // location of buffer in file
1019 private string name = "[Unknown]"; // name of file.
1021 IntPtr handle; // handle to underlying file