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.
32 using System.Collections;
33 using System.Globalization;
34 using System.Runtime.CompilerServices;
35 using System.Runtime.InteropServices;
36 using System.Runtime.Remoting.Messaging;
37 using System.Security.Permissions;
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 [SecurityPermission (SecurityAction.Demand, UnmanagedCode = true)]
63 internal FileStream (IntPtr handle, FileAccess access, bool ownsHandle, int bufferSize, bool isAsync, bool noBuffering)
65 this.handle = MonoIO.InvalidHandle;
66 if (handle == this.handle)
67 throw new ArgumentException ("handle", Locale.GetText ("Invalid."));
69 if (access < FileAccess.Read || access > FileAccess.ReadWrite)
70 throw new ArgumentOutOfRangeException ("access");
73 MonoFileType ftype = MonoIO.GetFileType (handle, out error);
75 if (error != MonoIOError.ERROR_SUCCESS) {
76 throw MonoIO.GetException (name, error);
79 if (ftype == MonoFileType.Unknown) {
80 throw new IOException ("Invalid handle.");
81 } else if (ftype == MonoFileType.Disk) {
91 this.owner = ownsHandle;
93 this.anonymous = false;
95 InitBuffer (bufferSize, noBuffering);
97 /* Can't set append mode */
98 this.append_startpos=0;
101 // construct from filename
103 public FileStream (string name, FileMode mode)
104 : this (name, mode, (mode == FileMode.Append ? FileAccess.Write : FileAccess.ReadWrite), FileShare.Read, DefaultBufferSize, false, false)
108 public FileStream (string name, FileMode mode, FileAccess access)
109 : this (name, mode, access, access == FileAccess.Write ? FileShare.None : FileShare.Read, DefaultBufferSize, false, false)
113 public FileStream (string name, FileMode mode, FileAccess access, FileShare share)
114 : this (name, mode, access, share, DefaultBufferSize, false, false)
118 public FileStream (string name, FileMode mode, FileAccess access, FileShare share, int bufferSize)
119 : this (name, mode, access, share, bufferSize, false, false)
123 public FileStream (string name, FileMode mode, FileAccess access, FileShare share, int bufferSize, bool isAsync)
124 : this (name, mode, access, share, bufferSize, isAsync, false)
128 internal FileStream (string name, FileMode mode, FileAccess access, FileShare share, int bufferSize, bool isAsync, bool anonymous)
131 throw new ArgumentNullException ("name");
134 if (name.Length == 0) {
135 throw new ArgumentException ("Name is empty");
139 throw new ArgumentOutOfRangeException ("Positive number required.");
141 if (mode < FileMode.CreateNew || mode > FileMode.Append)
142 throw new ArgumentOutOfRangeException ("mode");
144 if (access < FileAccess.Read || access > FileAccess.ReadWrite)
145 throw new ArgumentOutOfRangeException ("access");
147 if (share < FileShare.None || share > FileShare.ReadWrite)
148 throw new ArgumentOutOfRangeException ("share");
150 if (name.IndexOfAny (Path.InvalidPathChars) != -1) {
151 throw new ArgumentException ("Name has invalid chars");
154 if (Directory.Exists (name)) {
155 // don't leak the path information for isolated storage
156 string msg = Locale.GetText ("Access to the path '{0}' is denied.");
157 string fname = (anonymous) ? Path.GetFileName (name) : Path.GetFullPath (name);
158 throw new UnauthorizedAccessException (String.Format (msg, fname));
161 /* Append streams can't be read (see FileMode
164 if (mode==FileMode.Append &&
165 (access&FileAccess.Read)==FileAccess.Read) {
166 throw new ArgumentException("Append streams can not be read");
169 if ((access & FileAccess.Write) == 0 &&
170 (mode != FileMode.Open && mode != FileMode.OpenOrCreate))
171 throw new ArgumentException ("access and mode not compatible");
173 string dname = Path.GetDirectoryName (name);
174 if (dname.Length > 0) {
175 string fp = Path.GetFullPath (dname);
176 if (!Directory.Exists (fp)) {
177 // don't leak the path information for isolated storage
178 string msg = Locale.GetText ("Could not find a part of the path \"{0}\".");
179 string fname = (anonymous) ? dname : fp;
180 throw new DirectoryNotFoundException (String.Format (msg, fname));
184 if (access == FileAccess.Read && mode != FileMode.Create && mode != FileMode.OpenOrCreate &&
185 mode != FileMode.CreateNew && !File.Exists (name)) {
186 // don't leak the path information for isolated storage
187 string msg = Locale.GetText ("Could not find file \"{0}\".");
188 string fname = (anonymous) ? Path.GetFileName (name) : name;
189 throw new FileNotFoundException (String.Format (msg, fname), fname);
192 // IsolatedStorage needs to keep the Name property to the default "[Unknown]"
196 // TODO: demand permissions
200 this.handle = MonoIO.Open (name, mode, access, share, false, out error);
201 if (handle == MonoIO.InvalidHandle) {
202 // don't leak the path information for isolated storage
203 string fname = (anonymous) ? Path.GetFileName (name) : name;
204 throw MonoIO.GetException (fname, error);
207 this.access = access;
209 this.anonymous = anonymous;
211 /* Can we open non-files by name? */
213 if (MonoIO.GetFileType (handle, out error) == MonoFileType.Disk) {
215 this.async = isAsync;
217 this.canseek = false;
222 if (access == FileAccess.Read && canseek && (bufferSize == DefaultBufferSize)) {
223 /* Avoid allocating a large buffer for small files */
225 if (bufferSize > len) {
226 bufferSize = (int)(len < 1000 ? 1000 : len);
230 InitBuffer (bufferSize, false);
232 if (mode==FileMode.Append) {
233 this.Seek (0, SeekOrigin.End);
234 this.append_startpos=this.Position;
236 this.append_startpos=0;
242 public override bool CanRead {
244 return access == FileAccess.Read ||
245 access == FileAccess.ReadWrite;
249 public override bool CanWrite {
251 return access == FileAccess.Write ||
252 access == FileAccess.ReadWrite;
256 public override bool CanSeek {
262 public virtual bool IsAsync {
274 public override long Length {
276 if (handle == MonoIO.InvalidHandle)
277 throw new ObjectDisposedException ("Stream has been closed");
280 throw new NotSupportedException ("The stream does not support seeking");
282 // Buffered data might change the length of the stream
283 FlushBufferIfDirty ();
288 length = MonoIO.GetLength (handle, out error);
289 if (error != MonoIOError.ERROR_SUCCESS) {
290 // don't leak the path information for isolated storage
291 string fname = (anonymous) ? Path.GetFileName (name) : name;
292 throw MonoIO.GetException (fname, error);
299 public override long Position {
301 if (handle == MonoIO.InvalidHandle)
302 throw new ObjectDisposedException ("Stream has been closed");
305 throw new NotSupportedException("The stream does not support seeking");
307 return(buf_start + buf_offset);
310 if (handle == MonoIO.InvalidHandle)
311 throw new ObjectDisposedException ("Stream has been closed");
313 if(CanSeek == false) {
314 throw new NotSupportedException("The stream does not support seeking");
318 throw new ArgumentOutOfRangeException("Attempt to set the position to a negative value");
321 Seek (value, SeekOrigin.Begin);
325 public virtual IntPtr Handle {
326 [SecurityPermission (SecurityAction.LinkDemand, UnmanagedCode = true)]
327 [SecurityPermission (SecurityAction.InheritanceDemand, UnmanagedCode = true)]
334 public virtual SafeFileHandle SafeFileHandle {
335 [SecurityPermission (SecurityAction.LinkDemand, UnmanagedCode = true)]
336 [SecurityPermission (SecurityAction.InheritanceDemand, UnmanagedCode = true)]
337 get { throw new NotImplementedException (); }
343 public override int ReadByte ()
345 if (handle == MonoIO.InvalidHandle)
346 throw new ObjectDisposedException ("Stream has been closed");
349 throw new NotSupportedException ("Stream does not support reading");
352 int n = ReadData (handle, buf, 0, 1);
353 if (n == 0) return -1;
356 else if (buf_offset >= buf_length) {
363 return buf [buf_offset ++];
366 public override void WriteByte (byte value)
368 if (handle == MonoIO.InvalidHandle)
369 throw new ObjectDisposedException ("Stream has been closed");
372 throw new NotSupportedException ("Stream does not support writing");
374 if (buf_offset == buf_size)
377 if (buf_size == 0) { // No buffering
385 buf [buf_offset ++] = value;
386 if (buf_offset > buf_length)
387 buf_length = buf_offset;
392 public override int Read ([In,Out] byte[] dest, int dest_offset, int count)
394 if (handle == MonoIO.InvalidHandle)
395 throw new ObjectDisposedException ("Stream has been closed");
397 throw new ArgumentNullException ("destFile");
399 throw new NotSupportedException ("Stream does not support reading");
400 int len = dest.Length;
402 throw new ArgumentOutOfRangeException ("dest_offset", "< 0");
404 throw new ArgumentOutOfRangeException ("count", "< 0");
405 if (dest_offset > len)
406 throw new ArgumentException ("destination offset is beyond array size");
407 // reordered to avoid possible integer overflow
408 if (dest_offset > len - count)
409 throw new ArgumentException ("Reading would overrun buffer");
412 IAsyncResult ares = BeginRead (dest, dest_offset, count, null, null);
413 return EndRead (ares);
416 return ReadInternal (dest, dest_offset, count);
419 int ReadInternal (byte [] dest, int dest_offset, int count)
423 int n = ReadSegment (dest, dest_offset, count);
428 /* If there was already enough
429 * buffered, no need to read
430 * more from the file.
435 if (count > buf_size) {
436 /* Read as much as we can, up
440 n = ReadData (handle, dest,
444 /* Make the next buffer read
445 * start from the right place
450 n = ReadSegment (dest,
460 delegate int ReadDelegate (byte [] buffer, int offset, int count);
462 public override IAsyncResult BeginRead (byte [] buffer, int offset, int count,
463 AsyncCallback cback, object state)
465 if (handle == MonoIO.InvalidHandle)
466 throw new ObjectDisposedException ("Stream has been closed");
469 throw new NotSupportedException ("This stream does not support reading");
472 throw new ArgumentNullException ("buffer");
475 throw new ArgumentOutOfRangeException ("count", "Must be >= 0");
478 throw new ArgumentOutOfRangeException ("offset", "Must be >= 0");
480 // reordered to avoid possible integer overflow
481 if (count > buffer.Length - offset)
482 throw new ArgumentException ("Buffer too small. count/offset wrong.");
485 return base.BeginRead (buffer, offset, count, cback, state);
487 ReadDelegate r = new ReadDelegate (ReadInternal);
488 return r.BeginInvoke (buffer, offset, count, cback, state);
491 public override int EndRead (IAsyncResult async_result)
493 if (async_result == null)
494 throw new ArgumentNullException ("async_result");
497 return base.EndRead (async_result);
499 AsyncResult ares = async_result as AsyncResult;
501 throw new ArgumentException ("Invalid IAsyncResult", "async_result");
503 ReadDelegate r = ares.AsyncDelegate as ReadDelegate;
505 throw new ArgumentException ("Invalid IAsyncResult", "async_result");
507 return r.EndInvoke (async_result);
510 public override void Write (byte[] src, int src_offset, int count)
512 if (handle == MonoIO.InvalidHandle)
513 throw new ObjectDisposedException ("Stream has been closed");
515 throw new ArgumentNullException ("src");
517 throw new ArgumentOutOfRangeException ("src_offset", "< 0");
519 throw new ArgumentOutOfRangeException ("count", "< 0");
520 // ordered to avoid possible integer overflow
521 if (src_offset > src.Length - count)
522 throw new ArgumentException ("Reading would overrun buffer");
524 throw new NotSupportedException ("Stream does not support writing");
527 IAsyncResult ares = BeginWrite (src, src_offset, count, null, null);
532 WriteInternal (src, src_offset, count);
535 void WriteInternal (byte [] src, int src_offset, int count)
537 if (count > buf_size) {
538 // shortcut for long writes
543 MonoIO.Write (handle, src, src_offset, count, out error);
544 if (error != MonoIOError.ERROR_SUCCESS) {
545 // don't leak the path information for isolated storage
546 string fname = (anonymous) ? Path.GetFileName (name) : name;
547 throw MonoIO.GetException (fname, error);
556 int n = WriteSegment (src, src_offset + copied, count);
569 delegate void WriteDelegate (byte [] buffer, int offset, int count);
571 public override IAsyncResult BeginWrite (byte [] buffer, int offset, int count,
572 AsyncCallback cback, object state)
574 if (handle == MonoIO.InvalidHandle)
575 throw new ObjectDisposedException ("Stream has been closed");
578 throw new NotSupportedException ("This stream does not support writing");
581 throw new ArgumentNullException ("buffer");
584 throw new ArgumentOutOfRangeException ("count", "Must be >= 0");
587 throw new ArgumentOutOfRangeException ("offset", "Must be >= 0");
589 // reordered to avoid possible integer overflow
590 if (count > buffer.Length - offset)
591 throw new ArgumentException ("Buffer too small. count/offset wrong.");
594 return base.BeginWrite (buffer, offset, count, cback, state);
596 FileStreamAsyncResult result = new FileStreamAsyncResult (cback, state);
597 result.BytesRead = -1;
598 result.Count = count;
599 result.OriginalCount = count;
602 MemoryStream ms = new MemoryStream ();
603 FlushBufferToStream (ms);
604 ms.Write (buffer, offset, count);
606 count = (int) ms.Length;
609 WriteDelegate w = new WriteDelegate (WriteInternal);
610 return w.BeginInvoke (buffer, offset, count, cback, state);
613 public override void EndWrite (IAsyncResult async_result)
615 if (async_result == null)
616 throw new ArgumentNullException ("async_result");
619 base.EndWrite (async_result);
623 AsyncResult ares = async_result as AsyncResult;
625 throw new ArgumentException ("Invalid IAsyncResult", "async_result");
627 WriteDelegate w = ares.AsyncDelegate as WriteDelegate;
629 throw new ArgumentException ("Invalid IAsyncResult", "async_result");
631 w.EndInvoke (async_result);
635 public override long Seek (long offset, SeekOrigin origin)
639 if (handle == MonoIO.InvalidHandle)
640 throw new ObjectDisposedException ("Stream has been closed");
644 if(CanSeek == false) {
645 throw new NotSupportedException("The stream does not support seeking");
650 pos = Length + offset;
653 case SeekOrigin.Current:
654 pos = Position + offset;
657 case SeekOrigin.Begin:
662 throw new ArgumentException ("origin", "Invalid SeekOrigin");
666 /* LAMESPEC: shouldn't this be
667 * ArgumentOutOfRangeException?
669 throw new IOException("Attempted to Seek before the beginning of the stream");
672 if(pos < this.append_startpos) {
673 /* More undocumented crap */
674 throw new IOException("Can't seek back over pre-existing data in append mode");
677 if (buf_length > 0) {
678 if (pos >= buf_start &&
679 pos <= buf_start + buf_length) {
680 buf_offset = (int) (pos - buf_start);
689 buf_start = MonoIO.Seek (handle, pos,
693 if (error != MonoIOError.ERROR_SUCCESS) {
694 // don't leak the path information for isolated storage
695 string fname = (anonymous) ? Path.GetFileName (name) : name;
696 throw MonoIO.GetException (fname, error);
702 public override void SetLength (long length)
704 if (handle == MonoIO.InvalidHandle)
705 throw new ObjectDisposedException ("Stream has been closed");
708 throw new NotSupportedException("The stream does not support seeking");
710 if(CanWrite == false)
711 throw new NotSupportedException("The stream does not support writing");
714 throw new ArgumentOutOfRangeException("Length is less than 0");
720 MonoIO.SetLength (handle, length, out error);
721 if (error != MonoIOError.ERROR_SUCCESS) {
722 // don't leak the path information for isolated storage
723 string fname = (anonymous) ? Path.GetFileName (name) : name;
724 throw MonoIO.GetException (fname, error);
727 if (Position > length)
731 public override void Flush ()
733 if (handle == MonoIO.InvalidHandle)
734 throw new ObjectDisposedException ("Stream has been closed");
738 // The flushing is not actually required, in
739 //the mono runtime we were mapping flush to
740 //`fsync' which is not the same.
742 //MonoIO.Flush (handle);
745 public override void Close ()
748 GC.SuppressFinalize (this); // remove from finalize queue
751 public virtual void Lock (long position, long length)
753 if (handle == MonoIO.InvalidHandle)
754 throw new ObjectDisposedException ("Stream has been closed");
756 throw new ArgumentOutOfRangeException ("position must not be negative");
759 throw new ArgumentOutOfRangeException ("length must not be negative");
761 if (handle == MonoIO.InvalidHandle) {
762 throw new ObjectDisposedException ("Stream has been closed");
767 MonoIO.Lock (handle, position, length, out error);
768 if (error != MonoIOError.ERROR_SUCCESS) {
769 // don't leak the path information for isolated storage
770 string fname = (anonymous) ? Path.GetFileName (name) : name;
771 throw MonoIO.GetException (fname, error);
775 public virtual void Unlock (long position, long length)
777 if (handle == MonoIO.InvalidHandle)
778 throw new ObjectDisposedException ("Stream has been closed");
780 throw new ArgumentOutOfRangeException ("position must not be negative");
783 throw new ArgumentOutOfRangeException ("length must not be negative");
788 MonoIO.Unlock (handle, position, length, out error);
789 if (error != MonoIOError.ERROR_SUCCESS) {
790 // don't leak the path information for isolated storage
791 string fname = (anonymous) ? Path.GetFileName (name) : name;
792 throw MonoIO.GetException (fname, error);
803 protected virtual void Dispose (bool disposing) {
804 if (handle != MonoIO.InvalidHandle) {
810 MonoIO.Close (handle, out error);
811 if (error != MonoIOError.ERROR_SUCCESS) {
812 // don't leak the path information for isolated storage
813 string fname = (anonymous) ? Path.GetFileName (name) : name;
814 throw MonoIO.GetException (fname, error);
817 handle = MonoIO.InvalidHandle;
830 // ReadSegment, WriteSegment, FlushBuffer,
831 // RefillBuffer and ReadData should only be called
832 // when the Monitor lock is held, but these methods
833 // grab it again just to be safe.
835 private int ReadSegment (byte [] dest, int dest_offset, int count)
837 if (count > buf_length - buf_offset) {
838 count = buf_length - buf_offset;
842 Buffer.BlockCopy (buf, buf_offset,
851 private int WriteSegment (byte [] src, int src_offset,
854 if (count > buf_size - buf_offset) {
855 count = buf_size - buf_offset;
859 Buffer.BlockCopy (src, src_offset,
863 if (buf_offset > buf_length) {
864 buf_length = buf_offset;
873 void FlushBufferToStream (Stream st)
876 if (CanSeek == true) {
878 MonoIO.Seek (handle, buf_start,
881 if (error != MonoIOError.ERROR_SUCCESS) {
882 // don't leak the path information for isolated storage
883 string fname = (anonymous) ? Path.GetFileName (name) : name;
884 throw MonoIO.GetException (fname, error);
887 st.Write (buf, 0, buf_length);
890 buf_start += buf_offset;
891 buf_offset = buf_length = 0;
895 private void FlushBuffer ()
900 if (CanSeek == true) {
901 MonoIO.Seek (handle, buf_start,
904 if (error != MonoIOError.ERROR_SUCCESS) {
905 // don't leak the path information for isolated storage
906 string fname = (anonymous) ? Path.GetFileName (name) : name;
907 throw MonoIO.GetException (fname, error);
910 MonoIO.Write (handle, buf, 0,
911 buf_length, out error);
913 if (error != MonoIOError.ERROR_SUCCESS) {
914 // don't leak the path information for isolated storage
915 string fname = (anonymous) ? Path.GetFileName (name) : name;
916 throw MonoIO.GetException (fname, error);
920 buf_start += buf_offset;
921 buf_offset = buf_length = 0;
925 private void FlushBufferIfDirty ()
931 private void RefillBuffer ()
935 buf_length = ReadData (handle, buf, 0,
939 private int ReadData (IntPtr handle, byte[] buf, int offset,
945 /* when async == true, if we get here we don't suport AIO or it's disabled
946 * and we're using the threadpool */
947 amount = MonoIO.Read (handle, buf, offset, count, out error);
948 if (error == MonoIOError.ERROR_BROKEN_PIPE) {
949 amount = 0; // might not be needed, but well...
950 } else if (error != MonoIOError.ERROR_SUCCESS) {
951 // don't leak the path information for isolated storage
952 string fname = (anonymous) ? Path.GetFileName (name) : name;
953 throw MonoIO.GetException (fname, error);
956 /* Check for read error */
958 throw new IOException ();
964 private void InitBuffer (int size, bool noBuffering)
968 // We need a buffer for the ReadByte method. This buffer won't
969 // be used for anything else since buf_size==0.
974 throw new ArgumentOutOfRangeException ("bufferSize", "Positive number required.");
977 buf = new byte [size];
982 buf_offset = buf_length = 0;
988 internal const int DefaultBufferSize = 8192;
990 private FileAccess access;
993 private bool canseek;
994 private long append_startpos;
995 private bool anonymous;
997 private byte [] buf; // the buffer
998 private int buf_size; // capacity in bytes
999 private int buf_length; // number of valid bytes in buffer
1000 private int buf_offset; // position of next byte
1001 private bool buf_dirty; // true if buffer has been written to
1002 private long buf_start; // location of buffer in file
1003 private string name = "[Unknown]"; // name of file.
1005 IntPtr handle; // handle to underlying file