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 // Copyright (C) 2004-2005 Novell, Inc (http://www.novell.com)
12 // Permission is hereby granted, free of charge, to any person obtaining
13 // a copy of this software and associated documentation files (the
14 // "Software"), to deal in the Software without restriction, including
15 // without limitation the rights to use, copy, modify, merge, publish,
16 // distribute, sublicense, and/or sell copies of the Software, and to
17 // permit persons to whom the Software is furnished to do so, subject to
18 // the following conditions:
20 // The above copyright notice and this permission notice shall be
21 // included in all copies or substantial portions of the Software.
23 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
27 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
29 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
33 using System.Collections;
34 using System.Globalization;
35 using System.Runtime.CompilerServices;
36 using System.Runtime.InteropServices;
37 using System.Runtime.Remoting.Messaging;
38 using System.Threading;
41 using Microsoft.Win32.SafeHandles;
46 public class FileStream : Stream
48 // construct from handle
50 public FileStream (IntPtr handle, FileAccess access)
51 : this (handle, access, true, DefaultBufferSize, false) {}
53 public FileStream (IntPtr handle, FileAccess access, bool ownsHandle)
54 : this (handle, access, ownsHandle, DefaultBufferSize, false) {}
56 public FileStream (IntPtr handle, FileAccess access, bool ownsHandle, int bufferSize)
57 : this (handle, access, ownsHandle, bufferSize, false) {}
59 public FileStream (IntPtr handle, FileAccess access, bool ownsHandle, int bufferSize, bool isAsync)
60 : this (handle, access, ownsHandle, bufferSize, isAsync, false) {}
62 internal FileStream (IntPtr handle, FileAccess access, bool ownsHandle, int bufferSize, bool isAsync, bool noBuffering)
64 this.handle = MonoIO.InvalidHandle;
65 if (handle == this.handle)
66 throw new ArgumentException ("handle", Locale.GetText ("Invalid."));
68 if (access < FileAccess.Read || access > FileAccess.ReadWrite)
69 throw new ArgumentOutOfRangeException ("access");
72 MonoFileType ftype = MonoIO.GetFileType (handle, out error);
74 if (error != MonoIOError.ERROR_SUCCESS) {
75 throw MonoIO.GetException (name, error);
78 if (ftype == MonoFileType.Unknown) {
79 throw new IOException ("Invalid handle.");
80 } else if (ftype == MonoFileType.Disk) {
88 this.owner = ownsHandle;
91 if (isAsync && MonoIO.SupportsAsync)
92 ThreadPool.BindHandle (handle);
94 InitBuffer (bufferSize, noBuffering);
96 /* Can't set append mode */
97 this.append_startpos=0;
100 // construct from filename
102 public FileStream (string name, FileMode mode)
103 : this (name, mode, (mode == FileMode.Append ? FileAccess.Write : FileAccess.ReadWrite), FileShare.Read, DefaultBufferSize, false, false)
107 public FileStream (string name, FileMode mode, FileAccess access)
108 : this (name, mode, access, access == FileAccess.Write ? FileShare.None : FileShare.Read, DefaultBufferSize, false, false)
112 public FileStream (string name, FileMode mode, FileAccess access, FileShare share)
113 : this (name, mode, access, share, DefaultBufferSize, false, false)
117 public FileStream (string name, FileMode mode, FileAccess access, FileShare share, int bufferSize)
118 : this (name, mode, access, share, bufferSize, false, false)
122 public FileStream (string name, FileMode mode, FileAccess access, FileShare share, int bufferSize, bool isAsync)
123 : this (name, mode, access, share, bufferSize, isAsync, false)
127 internal FileStream (string name, FileMode mode, FileAccess access, FileShare share, int bufferSize, bool isAsync, bool anonymous)
130 throw new ArgumentNullException ("name");
133 if (name.Length == 0) {
134 throw new ArgumentException ("Name is empty");
138 throw new ArgumentOutOfRangeException ("Positive number required.");
140 if (mode < FileMode.CreateNew || mode > FileMode.Append)
141 throw new ArgumentOutOfRangeException ("mode");
143 if (access < FileAccess.Read || access > FileAccess.ReadWrite)
144 throw new ArgumentOutOfRangeException ("access");
146 if (share < FileShare.None || share > FileShare.ReadWrite)
147 throw new ArgumentOutOfRangeException ("share");
149 if (name.IndexOfAny (Path.InvalidPathChars) != -1) {
150 throw new ArgumentException ("Name has invalid chars");
153 if (Directory.Exists (name)) {
154 throw new UnauthorizedAccessException ("Access to the path '" + Path.GetFullPath (name) + "' is denied.");
157 /* Append streams can't be read (see FileMode
160 if (mode==FileMode.Append &&
161 (access&FileAccess.Read)==FileAccess.Read) {
162 throw new ArgumentException("Append streams can not be read");
165 if ((access & FileAccess.Write) == 0 &&
166 (mode != FileMode.Open && mode != FileMode.OpenOrCreate))
167 throw new ArgumentException ("access and mode not compatible");
169 if (access == FileAccess.Read && mode != FileMode.Create && mode != FileMode.OpenOrCreate &&
170 mode != FileMode.CreateNew && !File.Exists (name))
171 throw new FileNotFoundException ("Could not find file \"" + name + "\".", name);
173 if (mode == FileMode.CreateNew) {
174 string dname = Path.GetDirectoryName (name);
176 if (dname != "" && !Directory.Exists ((fp = Path.GetFullPath (dname))))
177 throw new DirectoryNotFoundException ("Could not find a part of " +
178 "the path \"" + fp + "\".");
181 // IsolatedStorage needs to keep the Name property to the default "[Unknown]"
185 // TODO: demand permissions
189 bool openAsync = (isAsync && MonoIO.SupportsAsync);
190 this.handle = MonoIO.Open (name, mode, access, share, openAsync, out error);
191 if (handle == MonoIO.InvalidHandle) {
192 throw MonoIO.GetException (name, error);
195 this.access = access;
198 /* Can we open non-files by name? */
200 if (MonoIO.GetFileType (handle, out error) == MonoFileType.Disk) {
202 this.async = isAsync;
204 ThreadPool.BindHandle (handle);
206 this.canseek = false;
211 if (access == FileAccess.Read && canseek && (bufferSize == DefaultBufferSize)) {
212 /* Avoid allocating a large buffer for small files */
214 if (bufferSize > len) {
215 bufferSize = (int)(len < 1000 ? 1000 : len);
219 InitBuffer (bufferSize, false);
221 if (mode==FileMode.Append) {
222 this.Seek (0, SeekOrigin.End);
223 this.append_startpos=this.Position;
225 this.append_startpos=0;
231 public override bool CanRead {
233 return access == FileAccess.Read ||
234 access == FileAccess.ReadWrite;
238 public override bool CanWrite {
240 return access == FileAccess.Write ||
241 access == FileAccess.ReadWrite;
245 public override bool CanSeek {
251 public virtual bool IsAsync {
263 public override long Length {
265 if (handle == MonoIO.InvalidHandle)
266 throw new ObjectDisposedException ("Stream has been closed");
269 throw new NotSupportedException ("The stream does not support seeking");
271 // Buffered data might change the length of the stream
272 FlushBufferIfDirty ();
277 length = MonoIO.GetLength (handle, out error);
278 if (error != MonoIOError.ERROR_SUCCESS) {
279 throw MonoIO.GetException (name,
287 public override long Position {
289 if (handle == MonoIO.InvalidHandle)
290 throw new ObjectDisposedException ("Stream has been closed");
293 throw new NotSupportedException("The stream does not support seeking");
295 return(buf_start + buf_offset);
298 if (handle == MonoIO.InvalidHandle)
299 throw new ObjectDisposedException ("Stream has been closed");
301 if(CanSeek == false) {
302 throw new NotSupportedException("The stream does not support seeking");
306 throw new ArgumentOutOfRangeException("Attempt to set the position to a negative value");
309 Seek (value, SeekOrigin.Begin);
313 public virtual IntPtr Handle {
320 public virtual SafeFileHandle SafeFileHandle {
321 get { throw new NotImplementedException (); }
327 public override int ReadByte ()
329 if (handle == MonoIO.InvalidHandle)
330 throw new ObjectDisposedException ("Stream has been closed");
333 throw new NotSupportedException ("Stream does not support reading");
336 int n = ReadData (handle, buf, 0, 1);
337 if (n == 0) return -1;
340 else if (buf_offset >= buf_length) {
347 return buf [buf_offset ++];
350 public override void WriteByte (byte value)
352 if (handle == MonoIO.InvalidHandle)
353 throw new ObjectDisposedException ("Stream has been closed");
356 throw new NotSupportedException ("Stream does not support writing");
358 if (buf_offset == buf_size)
361 buf [buf_offset ++] = value;
362 if (buf_offset > buf_length)
363 buf_length = buf_offset;
368 public override int Read ([In,Out] byte[] dest, int dest_offset, int count)
370 if (handle == MonoIO.InvalidHandle)
371 throw new ObjectDisposedException ("Stream has been closed");
373 throw new ArgumentNullException ("destFile");
375 throw new NotSupportedException ("Stream does not support reading");
376 int len = dest.Length;
378 throw new ArgumentOutOfRangeException ("dest_offset", "< 0");
380 throw new ArgumentOutOfRangeException ("count", "< 0");
381 if (dest_offset > len)
382 throw new ArgumentException ("destination offset is beyond array size");
383 // reordered to avoid possible integer overflow
384 if (dest_offset > len - count)
385 throw new ArgumentException ("Reading would overrun buffer");
388 IAsyncResult ares = BeginRead (dest, dest_offset, count, null, null);
389 return EndRead (ares);
392 return ReadInternal (dest, dest_offset, count);
395 int ReadInternal (byte [] dest, int dest_offset, int count)
399 int n = ReadSegment (dest, dest_offset, count);
404 /* If there was already enough
405 * buffered, no need to read
406 * more from the file.
411 if (count > buf_size) {
412 /* Read as much as we can, up
416 n = ReadData (handle, dest,
420 /* Make the next buffer read
421 * start from the right place
426 n = ReadSegment (dest,
436 delegate int ReadDelegate (byte [] buffer, int offset, int count);
438 public override IAsyncResult BeginRead (byte [] buffer, int offset, int count,
439 AsyncCallback cback, object state)
441 if (handle == MonoIO.InvalidHandle)
442 throw new ObjectDisposedException ("Stream has been closed");
445 throw new NotSupportedException ("This stream does not support reading");
448 throw new ArgumentNullException ("buffer");
451 throw new ArgumentOutOfRangeException ("count", "Must be >= 0");
454 throw new ArgumentOutOfRangeException ("offset", "Must be >= 0");
456 // reordered to avoid possible integer overflow
457 if (count > buffer.Length - offset)
458 throw new ArgumentException ("Buffer too small. count/offset wrong.");
461 return base.BeginRead (buffer, offset, count, cback, state);
463 if (!MonoIO.SupportsAsync) {
464 ReadDelegate r = new ReadDelegate (ReadInternal);
465 return r.BeginInvoke (buffer, offset, count, cback, state);
468 FileStreamAsyncResult result = new FileStreamAsyncResult (cback, state);
469 result.Count = count;
470 result.OriginalCount = count;
471 int buffered = ReadSegment (buffer, offset, count);
472 if (buffered >= count) {
473 result.SetComplete (null, buffered, true);
477 result.Buffer = buffer;
478 result.Offset = offset + buffered;
479 result.Count -= buffered;
481 KeepReference (result);
482 MonoIO.BeginRead (handle, result);
487 public override int EndRead (IAsyncResult async_result)
489 if (async_result == null)
490 throw new ArgumentNullException ("async_result");
493 return base.EndRead (async_result);
495 if (!MonoIO.SupportsAsync) {
496 AsyncResult ares = async_result as AsyncResult;
498 throw new ArgumentException ("Invalid IAsyncResult", "async_result");
500 ReadDelegate r = ares.AsyncDelegate as ReadDelegate;
502 throw new ArgumentException ("Invalid IAsyncResult", "async_result");
504 return r.EndInvoke (async_result);
507 FileStreamAsyncResult result = async_result as FileStreamAsyncResult;
508 if (result == null || result.BytesRead == -1)
509 throw new ArgumentException ("Invalid IAsyncResult", "async_result");
511 RemoveReference (result);
513 throw new InvalidOperationException ("EndRead already called.");
516 if (!result.IsCompleted)
517 result.AsyncWaitHandle.WaitOne ();
519 if (result.Exception != null)
520 throw result.Exception;
522 buf_start += result.BytesRead;
523 return result.OriginalCount - result.Count + result.BytesRead;
526 public override void Write (byte[] src, int src_offset, int count)
528 if (handle == MonoIO.InvalidHandle)
529 throw new ObjectDisposedException ("Stream has been closed");
531 throw new ArgumentNullException ("src");
533 throw new ArgumentOutOfRangeException ("src_offset", "< 0");
535 throw new ArgumentOutOfRangeException ("count", "< 0");
536 // ordered to avoid possible integer overflow
537 if (src_offset > src.Length - count)
538 throw new ArgumentException ("Reading would overrun buffer");
540 throw new NotSupportedException ("Stream does not support writing");
543 IAsyncResult ares = BeginWrite (src, src_offset, count, null, null);
548 WriteInternal (src, src_offset, count);
551 void WriteInternal (byte [] src, int src_offset, int count)
553 if (count > buf_size) {
554 // shortcut for long writes
559 MonoIO.Write (handle, src, src_offset, count, out error);
560 if (error != MonoIOError.ERROR_SUCCESS) {
561 throw MonoIO.GetException (name,
571 int n = WriteSegment (src, src_offset + copied, count);
584 delegate void WriteDelegate (byte [] buffer, int offset, int count);
586 public override IAsyncResult BeginWrite (byte [] buffer, int offset, int count,
587 AsyncCallback cback, object state)
589 if (handle == MonoIO.InvalidHandle)
590 throw new ObjectDisposedException ("Stream has been closed");
593 throw new NotSupportedException ("This stream does not support writing");
596 throw new ArgumentNullException ("buffer");
599 throw new ArgumentOutOfRangeException ("count", "Must be >= 0");
602 throw new ArgumentOutOfRangeException ("offset", "Must be >= 0");
604 // reordered to avoid possible integer overflow
605 if (count > buffer.Length - offset)
606 throw new ArgumentException ("Buffer too small. count/offset wrong.");
609 return base.BeginWrite (buffer, offset, count, cback, state);
613 FileStreamAsyncResult result = new FileStreamAsyncResult (cback, state);
614 result.BytesRead = -1;
615 result.Count = count;
616 result.OriginalCount = count;
619 MemoryStream ms = new MemoryStream ();
620 FlushBufferToStream (ms);
621 buffered = (int) ms.Length;
622 ms.Write (buffer, offset, count);
623 bytes = ms.GetBuffer ();
625 count = (int) ms.Length;
630 if (!MonoIO.SupportsAsync) {
631 WriteDelegate w = new WriteDelegate (WriteInternal);
632 return w.BeginInvoke (buffer, offset, count, cback, state);
635 if (buffered >= count) {
636 result.SetComplete (null, buffered, true);
640 result.Buffer = buffer;
641 result.Offset = offset;
642 result.Count = count;
644 KeepReference (result);
645 MonoIO.BeginWrite (handle, result);
650 public override void EndWrite (IAsyncResult async_result)
652 if (async_result == null)
653 throw new ArgumentNullException ("async_result");
656 base.EndWrite (async_result);
660 if (!MonoIO.SupportsAsync) {
661 AsyncResult ares = async_result as AsyncResult;
663 throw new ArgumentException ("Invalid IAsyncResult", "async_result");
665 WriteDelegate w = ares.AsyncDelegate as WriteDelegate;
667 throw new ArgumentException ("Invalid IAsyncResult", "async_result");
669 w.EndInvoke (async_result);
673 FileStreamAsyncResult result = async_result as FileStreamAsyncResult;
674 if (result == null || result.BytesRead != -1)
675 throw new ArgumentException ("Invalid IAsyncResult", "async_result");
677 RemoveReference (result);
679 throw new InvalidOperationException ("EndWrite already called.");
682 if (!result.IsCompleted)
683 result.AsyncWaitHandle.WaitOne ();
685 if (result.Exception != null)
686 throw result.Exception;
688 buf_start += result.Count;
689 buf_offset = buf_length = 0;
692 public override long Seek (long offset, SeekOrigin origin)
696 if (handle == MonoIO.InvalidHandle)
697 throw new ObjectDisposedException ("Stream has been closed");
701 if(CanSeek == false) {
702 throw new NotSupportedException("The stream does not support seeking");
707 pos = Length + offset;
710 case SeekOrigin.Current:
711 pos = Position + offset;
714 case SeekOrigin.Begin:
719 throw new ArgumentException ("origin", "Invalid SeekOrigin");
723 /* LAMESPEC: shouldn't this be
724 * ArgumentOutOfRangeException?
726 throw new IOException("Attempted to Seek before the beginning of the stream");
729 if(pos < this.append_startpos) {
730 /* More undocumented crap */
731 throw new IOException("Can't seek back over pre-existing data in append mode");
734 if (buf_length > 0) {
735 if (pos >= buf_start &&
736 pos <= buf_start + buf_length) {
737 buf_offset = (int) (pos - buf_start);
746 buf_start = MonoIO.Seek (handle, pos,
750 if (error != MonoIOError.ERROR_SUCCESS) {
751 throw MonoIO.GetException (name, error);
757 public override void SetLength (long length)
759 if (handle == MonoIO.InvalidHandle)
760 throw new ObjectDisposedException ("Stream has been closed");
763 throw new NotSupportedException("The stream does not support seeking");
765 if(CanWrite == false)
766 throw new NotSupportedException("The stream does not support writing");
769 throw new ArgumentOutOfRangeException("Length is less than 0");
775 MonoIO.SetLength (handle, length, out error);
776 if (error != MonoIOError.ERROR_SUCCESS) {
777 throw MonoIO.GetException (name, error);
780 if (Position > length)
784 public override void Flush ()
786 if (handle == MonoIO.InvalidHandle)
787 throw new ObjectDisposedException ("Stream has been closed");
791 // The flushing is not actually required, in
792 //the mono runtime we were mapping flush to
793 //`fsync' which is not the same.
795 //MonoIO.Flush (handle);
798 public override void Close ()
801 GC.SuppressFinalize (this); // remove from finalize queue
804 public virtual void Lock (long position, long length)
806 if (handle == MonoIO.InvalidHandle)
807 throw new ObjectDisposedException ("Stream has been closed");
809 throw new ArgumentOutOfRangeException ("position must not be negative");
812 throw new ArgumentOutOfRangeException ("length must not be negative");
814 if (handle == MonoIO.InvalidHandle) {
815 throw new ObjectDisposedException ("Stream has been closed");
820 MonoIO.Lock (handle, position, length, out error);
821 if (error != MonoIOError.ERROR_SUCCESS) {
822 throw MonoIO.GetException (name, error);
826 public virtual void Unlock (long position, long length)
828 if (handle == MonoIO.InvalidHandle)
829 throw new ObjectDisposedException ("Stream has been closed");
831 throw new ArgumentOutOfRangeException ("position must not be negative");
834 throw new ArgumentOutOfRangeException ("length must not be negative");
839 MonoIO.Unlock (handle, position, length, out error);
840 if (error != MonoIOError.ERROR_SUCCESS) {
841 throw MonoIO.GetException (name, error);
852 protected virtual void Dispose (bool disposing) {
853 if (handle != MonoIO.InvalidHandle) {
859 MonoIO.Close (handle, out error);
860 if (error != MonoIOError.ERROR_SUCCESS) {
861 throw MonoIO.GetException (name, error);
864 handle = MonoIO.InvalidHandle;
877 // ReadSegment, WriteSegment, FlushBuffer,
878 // RefillBuffer and ReadData should only be called
879 // when the Monitor lock is held, but these methods
880 // grab it again just to be safe.
882 private int ReadSegment (byte [] dest, int dest_offset, int count)
884 if (count > buf_length - buf_offset) {
885 count = buf_length - buf_offset;
889 Buffer.BlockCopy (buf, buf_offset,
898 private int WriteSegment (byte [] src, int src_offset,
901 if (count > buf_size - buf_offset) {
902 count = buf_size - buf_offset;
906 Buffer.BlockCopy (src, src_offset,
910 if (buf_offset > buf_length) {
911 buf_length = buf_offset;
920 void FlushBufferToStream (Stream st)
923 if (CanSeek == true) {
925 MonoIO.Seek (handle, buf_start,
928 if (error != MonoIOError.ERROR_SUCCESS) {
929 throw MonoIO.GetException (name, error);
932 st.Write (buf, 0, buf_length);
935 buf_start += buf_offset;
936 buf_offset = buf_length = 0;
940 private void FlushBuffer ()
945 if (CanSeek == true) {
946 MonoIO.Seek (handle, buf_start,
949 if (error != MonoIOError.ERROR_SUCCESS) {
950 throw MonoIO.GetException (name, error);
953 MonoIO.Write (handle, buf, 0,
954 buf_length, out error);
956 if (error != MonoIOError.ERROR_SUCCESS) {
957 throw MonoIO.GetException (name, error);
961 buf_start += buf_offset;
962 buf_offset = buf_length = 0;
966 private void FlushBufferIfDirty ()
972 private void RefillBuffer ()
976 buf_length = ReadData (handle, buf, 0,
980 private int ReadData (IntPtr handle, byte[] buf, int offset,
986 /* when async == true, if we get here we don't suport AIO or it's disabled
987 * and we're using the threadpool */
988 amount = MonoIO.Read (handle, buf, offset, count, out error);
989 if (error == MonoIOError.ERROR_BROKEN_PIPE) {
990 amount = 0; // might not be needed, but well...
991 } else if (error != MonoIOError.ERROR_SUCCESS) {
992 throw MonoIO.GetException (name, error);
995 /* Check for read error */
997 throw new IOException ();
1003 private void InitBuffer (int size, bool noBuffering)
1007 // We need a buffer for the ReadByte method. This buffer won't
1008 // be used for anything else since buf_size==0.
1013 throw new ArgumentOutOfRangeException ("bufferSize", "Positive number required.");
1016 buf = new byte [size];
1021 buf_offset = buf_length = 0;
1025 static void KeepReference (object o)
1027 lock (typeof (FileStream)) {
1028 if (asyncObjects == null)
1029 asyncObjects = new Hashtable ();
1031 asyncObjects [o] = o;
1035 static void RemoveReference (object o)
1037 lock (typeof (FileStream)) {
1038 if (asyncObjects == null)
1041 asyncObjects.Remove (o);
1047 internal const int DefaultBufferSize = 8192;
1048 private static Hashtable asyncObjects;
1050 private FileAccess access;
1053 private bool canseek;
1054 private long append_startpos;
1057 private byte [] buf; // the buffer
1058 private int buf_size; // capacity in bytes
1059 private int buf_length; // number of valid bytes in buffer
1060 private int buf_offset; // position of next byte
1061 private bool buf_dirty; // true if buffer has been written to
1062 private long buf_start; // location of buffer in file
1063 private string name = "[Unknown]"; // name of file.
1065 IntPtr handle; // handle to underlying file