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 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
16 // Permission is hereby granted, free of charge, to any person obtaining
17 // a copy of this software and associated documentation files (the
18 // "Software"), to deal in the Software without restriction, including
19 // without limitation the rights to use, copy, modify, merge, publish,
20 // distribute, sublicense, and/or sell copies of the Software, and to
21 // permit persons to whom the Software is furnished to do so, subject to
22 // the following conditions:
24 // The above copyright notice and this permission notice shall be
25 // included in all copies or substantial portions of the Software.
27 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
28 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
29 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
30 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
31 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
32 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
33 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
37 using System.Collections;
38 using System.Globalization;
39 using System.Runtime.CompilerServices;
40 using System.Runtime.InteropServices;
41 using System.Runtime.Remoting.Messaging;
42 using System.Threading;
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) { }
105 public FileStream (string name, FileMode mode, FileAccess access)
106 : this (name, mode, access, access == FileAccess.Write ? FileShare.None : FileShare.Read, DefaultBufferSize, false) { }
108 public FileStream (string name, FileMode mode, FileAccess access, FileShare share)
109 : this (name, mode, access, share, DefaultBufferSize, false) { }
111 public FileStream (string name, FileMode mode, FileAccess access, FileShare share, int bufferSize)
112 : this (name, mode, access, share, bufferSize, false) { }
114 public FileStream (string name, FileMode mode, FileAccess access, FileShare share, int bufferSize, bool isAsync)
117 throw new ArgumentNullException ("name");
121 throw new ArgumentException ("Name is empty");
125 throw new ArgumentOutOfRangeException ("Positive number required.");
127 if (mode < FileMode.CreateNew || mode > FileMode.Append)
128 throw new ArgumentOutOfRangeException ("mode");
130 if (access < FileAccess.Read || access > FileAccess.ReadWrite)
131 throw new ArgumentOutOfRangeException ("access");
133 if (share < FileShare.None || share > FileShare.ReadWrite)
134 throw new ArgumentOutOfRangeException ("share");
136 if (name.IndexOfAny (Path.InvalidPathChars) != -1) {
137 throw new ArgumentException ("Name has invalid chars");
140 if (Directory.Exists (name)) {
141 throw new UnauthorizedAccessException ("Access to the path '" + Path.GetFullPath (name) + "' is denied.");
144 /* Append streams can't be read (see FileMode
147 if (mode==FileMode.Append &&
148 (access&FileAccess.Read)==FileAccess.Read) {
149 throw new ArgumentException("Append streams can not be read");
152 if ((access & FileAccess.Write) == 0 &&
153 (mode != FileMode.Open && mode != FileMode.OpenOrCreate))
154 throw new ArgumentException ("access and mode not compatible");
156 if (access == FileAccess.Read && mode != FileMode.Create && mode != FileMode.OpenOrCreate &&
157 mode != FileMode.CreateNew && !File.Exists (name))
158 throw new FileNotFoundException ("Could not find file \"" + name + "\".", name);
160 if (mode == FileMode.CreateNew) {
161 string dname = Path.GetDirectoryName (name);
163 if (dname != "" && !Directory.Exists ((fp = Path.GetFullPath (dname))))
164 throw new DirectoryNotFoundException ("Could not find a part of " +
165 "the path \"" + fp + "\".");
170 // TODO: demand permissions
174 bool openAsync = (isAsync && MonoIO.SupportsAsync);
175 this.handle = MonoIO.Open (name, mode, access, share, openAsync, out error);
176 if (handle == MonoIO.InvalidHandle) {
177 throw MonoIO.GetException (name, error);
180 this.access = access;
183 /* Can we open non-files by name? */
185 if (MonoIO.GetFileType (handle, out error) == MonoFileType.Disk) {
187 this.async = isAsync;
189 ThreadPool.BindHandle (handle);
191 this.canseek = false;
196 if (access == FileAccess.Read && canseek && (bufferSize == DefaultBufferSize)) {
197 /* Avoid allocating a large buffer for small files */
199 if (bufferSize > len) {
200 bufferSize = (int)(len < 1000 ? 1000 : len);
204 InitBuffer (bufferSize, false);
206 if (mode==FileMode.Append) {
207 this.Seek (0, SeekOrigin.End);
208 this.append_startpos=this.Position;
210 this.append_startpos=0;
216 public override bool CanRead {
218 return access == FileAccess.Read ||
219 access == FileAccess.ReadWrite;
223 public override bool CanWrite {
225 return access == FileAccess.Write ||
226 access == FileAccess.ReadWrite;
230 public override bool CanSeek {
236 public virtual bool IsAsync {
248 public override long Length {
250 if (handle == MonoIO.InvalidHandle)
251 throw new ObjectDisposedException ("Stream has been closed");
254 throw new NotSupportedException ("The stream does not support seeking");
256 // Buffered data might change the length of the stream
257 FlushBufferIfDirty ();
262 length = MonoIO.GetLength (handle, out error);
263 if (error != MonoIOError.ERROR_SUCCESS) {
264 throw MonoIO.GetException (name,
272 public override long Position {
274 if (handle == MonoIO.InvalidHandle)
275 throw new ObjectDisposedException ("Stream has been closed");
278 throw new NotSupportedException("The stream does not support seeking");
280 return(buf_start + buf_offset);
283 if (handle == MonoIO.InvalidHandle)
284 throw new ObjectDisposedException ("Stream has been closed");
286 if(CanSeek == false) {
287 throw new NotSupportedException("The stream does not support seeking");
291 throw new ArgumentOutOfRangeException("Attempt to set the position to a negative value");
294 Seek (value, SeekOrigin.Begin);
298 public virtual IntPtr Handle {
306 public override int ReadByte ()
308 if (handle == MonoIO.InvalidHandle)
309 throw new ObjectDisposedException ("Stream has been closed");
312 throw new NotSupportedException ("Stream does not support reading");
315 int n = ReadData (handle, buf, 0, 1);
316 if (n == 0) return -1;
319 else if (buf_offset >= buf_length) {
326 return buf [buf_offset ++];
329 public override void WriteByte (byte value)
331 if (handle == MonoIO.InvalidHandle)
332 throw new ObjectDisposedException ("Stream has been closed");
335 throw new NotSupportedException ("Stream does not support writing");
337 if (buf_offset == buf_size)
340 buf [buf_offset ++] = value;
341 if (buf_offset > buf_length)
342 buf_length = buf_offset;
347 public override int Read ([In,Out] byte[] dest, int dest_offset, int count)
349 if (handle == MonoIO.InvalidHandle)
350 throw new ObjectDisposedException ("Stream has been closed");
352 throw new ArgumentNullException ("destFile");
354 throw new NotSupportedException ("Stream does not support reading");
355 int len = dest.Length;
357 throw new ArgumentOutOfRangeException ("dest_offset", "< 0");
359 throw new ArgumentOutOfRangeException ("count", "< 0");
360 if (dest_offset > len)
361 throw new ArgumentException ("destination offset is beyond array size");
362 // reordered to avoid possible integer overflow
363 if (dest_offset > len - count)
364 throw new ArgumentException ("Reading would overrun buffer");
367 IAsyncResult ares = BeginRead (dest, dest_offset, count, null, null);
368 return EndRead (ares);
371 return ReadInternal (dest, dest_offset, count);
374 int ReadInternal (byte [] dest, int dest_offset, int count)
378 int n = ReadSegment (dest, dest_offset, count);
383 /* If there was already enough
384 * buffered, no need to read
385 * more from the file.
390 if (count > buf_size) {
391 /* Read as much as we can, up
395 n = ReadData (handle, dest,
399 /* Make the next buffer read
400 * start from the right place
405 n = ReadSegment (dest,
415 delegate int ReadDelegate (byte [] buffer, int offset, int count);
417 public override IAsyncResult BeginRead (byte [] buffer, int offset, int count,
418 AsyncCallback cback, object state)
420 if (handle == MonoIO.InvalidHandle)
421 throw new ObjectDisposedException ("Stream has been closed");
424 throw new NotSupportedException ("This stream does not support reading");
427 throw new ArgumentNullException ("buffer");
430 throw new ArgumentOutOfRangeException ("count", "Must be >= 0");
433 throw new ArgumentOutOfRangeException ("offset", "Must be >= 0");
435 // reordered to avoid possible integer overflow
436 if (count > buffer.Length - offset)
437 throw new ArgumentException ("Buffer too small. count/offset wrong.");
440 return base.BeginRead (buffer, offset, count, cback, state);
442 if (!MonoIO.SupportsAsync) {
443 ReadDelegate r = new ReadDelegate (ReadInternal);
444 return r.BeginInvoke (buffer, offset, count, cback, state);
447 FileStreamAsyncResult result = new FileStreamAsyncResult (cback, state);
448 result.Count = count;
449 result.OriginalCount = count;
450 int buffered = ReadSegment (buffer, offset, count);
451 if (buffered >= count) {
452 result.SetComplete (null, buffered, true);
456 result.Buffer = buffer;
457 result.Offset = offset + buffered;
458 result.Count -= buffered;
460 KeepReference (result);
461 MonoIO.BeginRead (handle, result);
466 public override int EndRead (IAsyncResult async_result)
468 if (async_result == null)
469 throw new ArgumentNullException ("async_result");
472 return base.EndRead (async_result);
474 if (!MonoIO.SupportsAsync) {
475 AsyncResult ares = async_result as AsyncResult;
477 throw new ArgumentException ("Invalid IAsyncResult", "async_result");
479 ReadDelegate r = ares.AsyncDelegate as ReadDelegate;
481 throw new ArgumentException ("Invalid IAsyncResult", "async_result");
483 return r.EndInvoke (async_result);
486 FileStreamAsyncResult result = async_result as FileStreamAsyncResult;
487 if (result == null || result.BytesRead == -1)
488 throw new ArgumentException ("Invalid IAsyncResult", "async_result");
490 RemoveReference (result);
492 throw new InvalidOperationException ("EndRead already called.");
495 if (!result.IsCompleted)
496 result.AsyncWaitHandle.WaitOne ();
498 if (result.Exception != null)
499 throw result.Exception;
501 buf_start += result.BytesRead;
502 return result.OriginalCount - result.Count + result.BytesRead;
505 public override void Write (byte[] src, int src_offset, int count)
507 if (handle == MonoIO.InvalidHandle)
508 throw new ObjectDisposedException ("Stream has been closed");
510 throw new ArgumentNullException ("src");
512 throw new ArgumentOutOfRangeException ("src_offset", "< 0");
514 throw new ArgumentOutOfRangeException ("count", "< 0");
515 // ordered to avoid possible integer overflow
516 if (src_offset > src.Length - count)
517 throw new ArgumentException ("Reading would overrun buffer");
519 throw new NotSupportedException ("Stream does not support writing");
522 IAsyncResult ares = BeginWrite (src, src_offset, count, null, null);
527 WriteInternal (src, src_offset, count);
530 void WriteInternal (byte [] src, int src_offset, int count)
532 if (count > buf_size) {
533 // shortcut for long writes
538 MonoIO.Write (handle, src, src_offset, count, out error);
539 if (error != MonoIOError.ERROR_SUCCESS) {
540 throw MonoIO.GetException (name,
550 int n = WriteSegment (src, src_offset + copied, count);
563 delegate void WriteDelegate (byte [] buffer, int offset, int count);
565 public override IAsyncResult BeginWrite (byte [] buffer, int offset, int count,
566 AsyncCallback cback, object state)
568 if (handle == MonoIO.InvalidHandle)
569 throw new ObjectDisposedException ("Stream has been closed");
572 throw new NotSupportedException ("This stream does not support writing");
575 throw new ArgumentNullException ("buffer");
578 throw new ArgumentOutOfRangeException ("count", "Must be >= 0");
581 throw new ArgumentOutOfRangeException ("offset", "Must be >= 0");
583 // reordered to avoid possible integer overflow
584 if (count > buffer.Length - offset)
585 throw new ArgumentException ("Buffer too small. count/offset wrong.");
588 return base.BeginWrite (buffer, offset, count, cback, state);
592 FileStreamAsyncResult result = new FileStreamAsyncResult (cback, state);
593 result.BytesRead = -1;
594 result.Count = count;
595 result.OriginalCount = count;
598 MemoryStream ms = new MemoryStream ();
599 FlushBufferToStream (ms);
600 buffered = (int) ms.Length;
601 ms.Write (buffer, offset, count);
602 bytes = ms.GetBuffer ();
604 count = (int) ms.Length;
609 if (!MonoIO.SupportsAsync) {
610 WriteDelegate w = new WriteDelegate (WriteInternal);
611 return w.BeginInvoke (buffer, offset, count, cback, state);
614 if (buffered >= count) {
615 result.SetComplete (null, buffered, true);
619 result.Buffer = buffer;
620 result.Offset = offset;
621 result.Count = count;
623 KeepReference (result);
624 MonoIO.BeginWrite (handle, result);
629 public override void EndWrite (IAsyncResult async_result)
631 if (async_result == null)
632 throw new ArgumentNullException ("async_result");
635 base.EndWrite (async_result);
639 if (!MonoIO.SupportsAsync) {
640 AsyncResult ares = async_result as AsyncResult;
642 throw new ArgumentException ("Invalid IAsyncResult", "async_result");
644 WriteDelegate w = ares.AsyncDelegate as WriteDelegate;
646 throw new ArgumentException ("Invalid IAsyncResult", "async_result");
648 w.EndInvoke (async_result);
652 FileStreamAsyncResult result = async_result as FileStreamAsyncResult;
653 if (result == null || result.BytesRead != -1)
654 throw new ArgumentException ("Invalid IAsyncResult", "async_result");
656 RemoveReference (result);
658 throw new InvalidOperationException ("EndWrite already called.");
661 if (!result.IsCompleted)
662 result.AsyncWaitHandle.WaitOne ();
664 if (result.Exception != null)
665 throw result.Exception;
667 buf_start += result.Count;
668 buf_offset = buf_length = 0;
671 public override long Seek (long offset, SeekOrigin origin)
675 if (handle == MonoIO.InvalidHandle)
676 throw new ObjectDisposedException ("Stream has been closed");
680 if(CanSeek == false) {
681 throw new NotSupportedException("The stream does not support seeking");
686 pos = Length + offset;
689 case SeekOrigin.Current:
690 pos = Position + offset;
693 case SeekOrigin.Begin:
698 throw new ArgumentException ("origin", "Invalid SeekOrigin");
702 /* LAMESPEC: shouldn't this be
703 * ArgumentOutOfRangeException?
705 throw new IOException("Attempted to Seek before the beginning of the stream");
708 if(pos < this.append_startpos) {
709 /* More undocumented crap */
710 throw new IOException("Can't seek back over pre-existing data in append mode");
713 if (buf_length > 0) {
714 if (pos >= buf_start &&
715 pos <= buf_start + buf_length) {
716 buf_offset = (int) (pos - buf_start);
725 buf_start = MonoIO.Seek (handle, pos,
729 if (error != MonoIOError.ERROR_SUCCESS) {
730 throw MonoIO.GetException (name, error);
736 public override void SetLength (long length)
738 if (handle == MonoIO.InvalidHandle)
739 throw new ObjectDisposedException ("Stream has been closed");
742 throw new NotSupportedException("The stream does not support seeking");
744 if(CanWrite == false)
745 throw new NotSupportedException("The stream does not support writing");
748 throw new ArgumentOutOfRangeException("Length is less than 0");
754 MonoIO.SetLength (handle, length, out error);
755 if (error != MonoIOError.ERROR_SUCCESS) {
756 throw MonoIO.GetException (name, error);
759 if (Position > length)
763 public override void Flush ()
765 if (handle == MonoIO.InvalidHandle)
766 throw new ObjectDisposedException ("Stream has been closed");
770 // The flushing is not actually required, in
771 //the mono runtime we were mapping flush to
772 //`fsync' which is not the same.
774 //MonoIO.Flush (handle);
777 public override void Close ()
780 GC.SuppressFinalize (this); // remove from finalize queue
783 public virtual void Lock (long position, long length)
785 if (handle == MonoIO.InvalidHandle)
786 throw new ObjectDisposedException ("Stream has been closed");
788 throw new ArgumentOutOfRangeException ("position must not be negative");
791 throw new ArgumentOutOfRangeException ("length must not be negative");
793 if (handle == MonoIO.InvalidHandle) {
794 throw new ObjectDisposedException ("Stream has been closed");
799 MonoIO.Lock (handle, position, length, out error);
800 if (error != MonoIOError.ERROR_SUCCESS) {
801 throw MonoIO.GetException (name, error);
805 public virtual void Unlock (long position, long length)
807 if (handle == MonoIO.InvalidHandle)
808 throw new ObjectDisposedException ("Stream has been closed");
810 throw new ArgumentOutOfRangeException ("position must not be negative");
813 throw new ArgumentOutOfRangeException ("length must not be negative");
818 MonoIO.Unlock (handle, position, length, out error);
819 if (error != MonoIOError.ERROR_SUCCESS) {
820 throw MonoIO.GetException (name, error);
831 protected virtual void Dispose (bool disposing) {
832 if (handle != MonoIO.InvalidHandle) {
838 MonoIO.Close (handle, out error);
839 if (error != MonoIOError.ERROR_SUCCESS) {
840 throw MonoIO.GetException (name, error);
843 handle = MonoIO.InvalidHandle;
856 // ReadSegment, WriteSegment, FlushBuffer,
857 // RefillBuffer and ReadData should only be called
858 // when the Monitor lock is held, but these methods
859 // grab it again just to be safe.
861 private int ReadSegment (byte [] dest, int dest_offset, int count)
863 if (count > buf_length - buf_offset) {
864 count = buf_length - buf_offset;
868 Buffer.BlockCopy (buf, buf_offset,
877 private int WriteSegment (byte [] src, int src_offset,
880 if (count > buf_size - buf_offset) {
881 count = buf_size - buf_offset;
885 Buffer.BlockCopy (src, src_offset,
889 if (buf_offset > buf_length) {
890 buf_length = buf_offset;
899 void FlushBufferToStream (Stream st)
902 if (CanSeek == true) {
904 MonoIO.Seek (handle, buf_start,
907 if (error != MonoIOError.ERROR_SUCCESS) {
908 throw MonoIO.GetException (name, error);
911 st.Write (buf, 0, buf_length);
914 buf_start += buf_offset;
915 buf_offset = buf_length = 0;
919 private void FlushBuffer ()
924 if (CanSeek == true) {
925 MonoIO.Seek (handle, buf_start,
928 if (error != MonoIOError.ERROR_SUCCESS) {
929 throw MonoIO.GetException (name, error);
932 MonoIO.Write (handle, buf, 0,
933 buf_length, out error);
935 if (error != MonoIOError.ERROR_SUCCESS) {
936 throw MonoIO.GetException (name, error);
940 buf_start += buf_offset;
941 buf_offset = buf_length = 0;
945 private void FlushBufferIfDirty ()
951 private void RefillBuffer ()
955 buf_length = ReadData (handle, buf, 0,
959 private int ReadData (IntPtr handle, byte[] buf, int offset,
965 /* when async == true, if we get here we don't suport AIO or it's disabled
966 * and we're using the threadpool */
967 amount = MonoIO.Read (handle, buf, offset, count, out error);
968 if (error == MonoIOError.ERROR_BROKEN_PIPE) {
969 amount = 0; // might not be needed, but well...
970 } else if (error != MonoIOError.ERROR_SUCCESS) {
971 throw MonoIO.GetException (name, error);
974 /* Check for read error */
976 throw new IOException ();
982 private void InitBuffer (int size, bool noBuffering)
986 // We need a buffer for the ReadByte method. This buffer won't
987 // be used for anything else since buf_size==0.
992 throw new ArgumentOutOfRangeException ("bufferSize", "Positive number required.");
995 buf = new byte [size];
1000 buf_offset = buf_length = 0;
1004 static void KeepReference (object o)
1006 lock (typeof (FileStream)) {
1007 if (asyncObjects == null)
1008 asyncObjects = new Hashtable ();
1010 asyncObjects [o] = o;
1014 static void RemoveReference (object o)
1016 lock (typeof (FileStream)) {
1017 if (asyncObjects == null)
1020 asyncObjects.Remove (o);
1026 const int DefaultBufferSize = 8192;
1027 private static Hashtable asyncObjects;
1029 private FileAccess access;
1032 private bool canseek;
1033 private long append_startpos;
1036 private byte [] buf; // the buffer
1037 private int buf_size; // capacity in bytes
1038 private int buf_length; // number of valid bytes in buffer
1039 private int buf_offset; // position of next byte
1040 private bool buf_dirty; // true if buffer has been written to
1041 private long buf_start; // location of buffer in file
1042 private string name = "[Unknown]"; // name of file.
1044 IntPtr handle; // handle to underlying file