2005-08-11 Atsushi Enomoto <atsushi@ximian.com>
[mono.git] / mcs / class / corlib / System.IO / FileStream.cs
index 6c4bf9e49bce0a8a1ef0a11651da91b59e86af39..945f995d682a83bf69cd8e2612f102eae1dcd7c8 100644 (file)
-//\r
-// System.IO/FileStream.cs\r
-//\r
-// Authors:\r
-//   Dietmar Maurer (dietmar@ximian.com)\r
-//   Dan Lewis (dihlewis@yahoo.co.uk)\r
-//\r
-// (C) 2001 Ximian, Inc.  http://www.ximian.com\r
-//\r
-\r
-using System;\r
-using System.Runtime.CompilerServices;\r
-\r
-// FIXME: emit the correct exceptions everywhere. add error handling.\r
-\r
-namespace System.IO\r
-{\r
-\r
-       public class FileStream : Stream\r
-       {\r
-               // construct from handle\r
-               \r
-               public FileStream (IntPtr handle, FileAccess access)\r
-                       : this (handle, access, true, DefaultBufferSize, false) {}\r
-\r
-               public FileStream (IntPtr handle, FileAccess access, bool ownsHandle)\r
-                       : this (handle, access, ownsHandle, DefaultBufferSize, false) {}\r
-               \r
-               public FileStream (IntPtr handle, FileAccess access, bool ownsHandle, int bufferSize)\r
-                       : this (handle, access, ownsHandle, bufferSize, false) {}\r
+//
+// System.IO/FileStream.cs
+//
+// Authors:
+//     Dietmar Maurer (dietmar@ximian.com)
+//     Dan Lewis (dihlewis@yahoo.co.uk)
+//     Gonzalo Paniagua Javier (gonzalo@ximian.com)
+//
+// (C) 2001-2003 Ximian, Inc.  http://www.ximian.com
+// Copyright (C) 2004-2005 Novell, Inc (http://www.novell.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+// 
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System.Collections;
+using System.Globalization;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Runtime.Remoting.Messaging;
+using System.Security.Permissions;
+using System.Threading;
+
+#if NET_2_0
+using Microsoft.Win32.SafeHandles;
+#endif
+
+namespace System.IO
+{
+       public class FileStream : Stream
+       {
+               // construct from handle
+               
+               public FileStream (IntPtr handle, FileAccess access)
+                       : this (handle, access, true, DefaultBufferSize, false) {}
+
+               public FileStream (IntPtr handle, FileAccess access, bool ownsHandle)
+                       : this (handle, access, ownsHandle, DefaultBufferSize, false) {}
+               
+               public FileStream (IntPtr handle, FileAccess access, bool ownsHandle, int bufferSize)
+                       : this (handle, access, ownsHandle, bufferSize, false) {}
 
                public FileStream (IntPtr handle, FileAccess access, bool ownsHandle, int bufferSize, bool isAsync)
+                       : this (handle, access, ownsHandle, bufferSize, isAsync, false) {}
+
+               [SecurityPermission (SecurityAction.Demand, UnmanagedCode = true)]
+               internal FileStream (IntPtr handle, FileAccess access, bool ownsHandle, int bufferSize, bool isAsync, bool noBuffering)
                {
-                       this.handle = handle;
-                       this.access = access;
-                       this.owner = ownsHandle;
-                       this.async = isAsync;
+                       this.handle = MonoIO.InvalidHandle;
+                       if (handle == this.handle)
+                               throw new ArgumentException ("handle", Locale.GetText ("Invalid."));
+
+                       if (access < FileAccess.Read || access > FileAccess.ReadWrite)
+                               throw new ArgumentOutOfRangeException ("access");
 
                        MonoIOError error;
+                       MonoFileType ftype = MonoIO.GetFileType (handle, out error);
+
+                       if (error != MonoIOError.ERROR_SUCCESS) {
+                               throw MonoIO.GetException (name, error);
+                       }
                        
-                       if(MonoIO.GetFileType (handle, out error) ==
-                          MonoFileType.Disk) {
+                       if (ftype == MonoFileType.Unknown) {
+                               throw new IOException ("Invalid handle.");
+                       } else if (ftype == MonoFileType.Disk) {
                                this.canseek = true;
                        } else {
                                this.canseek = false;
+                               noBuffering = true;
+                               bufferSize = 0;
                        }
-                       
-                       InitBuffer (bufferSize);
-               }
-
-               // construct from filename\r
-               \r
-               public FileStream (string name, FileMode mode)\r
-                       : this (name, mode, FileAccess.ReadWrite, FileShare.ReadWrite, DefaultBufferSize, false) { }\r
-\r
-               public FileStream (string name, FileMode mode, FileAccess access)\r
-                       : this (name, mode, access, FileShare.ReadWrite, DefaultBufferSize, false) { }\r
-\r
-               public FileStream (string name, FileMode mode, FileAccess access, FileShare share)\r
-                       : this (name, mode, access, share, DefaultBufferSize, false) { }\r
-               \r
-               public FileStream (string name, FileMode mode, FileAccess access, FileShare share, int bufferSize)\r
-                       : this (name, mode, access, share, bufferSize, false) { }\r
+
+                       this.handle = handle;
+                       this.access = access;
+                       this.owner = ownsHandle;
+                       this.async = isAsync;
+                       this.anonymous = false;
+
+                       InitBuffer (bufferSize, noBuffering);
+
+                       /* Can't set append mode */
+                       this.append_startpos=0;
+               }
+
+               // construct from filename
+               
+               public FileStream (string name, FileMode mode)
+                       : this (name, mode, (mode == FileMode.Append ? FileAccess.Write : FileAccess.ReadWrite), FileShare.Read, DefaultBufferSize, false, false)
+               {
+               }
+
+               public FileStream (string name, FileMode mode, FileAccess access)
+                       : this (name, mode, access, access == FileAccess.Write ? FileShare.None : FileShare.Read, DefaultBufferSize, false, false)
+               {
+               }
+
+               public FileStream (string name, FileMode mode, FileAccess access, FileShare share)
+                       : this (name, mode, access, share, DefaultBufferSize, false, false)
+               {
+               }
+               
+               public FileStream (string name, FileMode mode, FileAccess access, FileShare share, int bufferSize)
+                       : this (name, mode, access, share, bufferSize, false, false)
+               {
+               }
 
                public FileStream (string name, FileMode mode, FileAccess access, FileShare share, int bufferSize, bool isAsync)
+                       : this (name, mode, access, share, bufferSize, isAsync, false)
+               {
+               }
+
+               internal FileStream (string name, FileMode mode, FileAccess access, FileShare share, int bufferSize, bool isAsync, bool anonymous)
                {
                        if (name == null) {
-                               throw new ArgumentNullException ("Name is null");
+                               throw new ArgumentNullException ("name");
                        }
                        
-                       if (name == "") {
+                       if (name.Length == 0) {
                                throw new ArgumentException ("Name is empty");
                        }
 
+                       if (bufferSize <= 0)
+                               throw new ArgumentOutOfRangeException ("Positive number required.");
+
+                       if (mode < FileMode.CreateNew || mode > FileMode.Append)
+                               throw new ArgumentOutOfRangeException ("mode");
+
+                       if (access < FileAccess.Read || access > FileAccess.ReadWrite)
+                               throw new ArgumentOutOfRangeException ("access");
+
+                       if (share < FileShare.None || share > FileShare.ReadWrite)
+                               throw new ArgumentOutOfRangeException ("share");
+
                        if (name.IndexOfAny (Path.InvalidPathChars) != -1) {
                                throw new ArgumentException ("Name has invalid chars");
                        }
 
                        if (Directory.Exists (name)) {
-                               throw new UnauthorizedAccessException ("Access to the path '" + Path.GetFullPath (name) + "' is denied.");
+                               // don't leak the path information for isolated storage
+                               string msg = Locale.GetText ("Access to the path '{0}' is denied.");
+                               string fname = (anonymous) ? Path.GetFileName (name) : Path.GetFullPath (name);
+                               throw new UnauthorizedAccessException (String.Format (msg, fname));
+                       }
+
+                       /* Append streams can't be read (see FileMode
+                        * docs)
+                        */
+                       if (mode==FileMode.Append &&
+                           (access&FileAccess.Read)==FileAccess.Read) {
+                               throw new ArgumentException("Append streams can not be read");
+                       }
+
+                       if ((access & FileAccess.Write) == 0 &&
+                           (mode != FileMode.Open && mode != FileMode.OpenOrCreate))
+                               throw new ArgumentException ("access and mode not compatible");
+
+                       string dname = Path.GetDirectoryName (name);
+                       if (dname.Length > 0) {
+                               string fp = Path.GetFullPath (dname);
+                               if (!Directory.Exists (fp)) {
+                                       // don't leak the path information for isolated storage
+                                       string msg = Locale.GetText ("Could not find a part of the path \"{0}\".");
+                                       string fname = (anonymous) ? dname : fp;
+                                       throw new DirectoryNotFoundException (String.Format (msg, fname));
+                               }
                        }
 
-                       this.name = name;
+                       if (access == FileAccess.Read && mode != FileMode.Create && mode != FileMode.OpenOrCreate &&
+                                       mode != FileMode.CreateNew && !File.Exists (name)) {
+                               // don't leak the path information for isolated storage
+                               string msg = Locale.GetText ("Could not find file \"{0}\".");
+                               string fname = (anonymous) ? Path.GetFileName (name) : name;
+                               throw new FileNotFoundException (String.Format (msg, fname), fname);
+                       }
+
+                       // IsolatedStorage needs to keep the Name property to the default "[Unknown]"
+                       if (!anonymous)
+                               this.name = name;
 
                        // TODO: demand permissions
 
                        MonoIOError error;
-                       
-                       this.handle = MonoIO.Open (name, mode, access, share,
-                                                  out error);
+
+                       this.handle = MonoIO.Open (name, mode, access, share, false, out error);
                        if (handle == MonoIO.InvalidHandle) {
-                               throw MonoIO.GetException (name, error);
+                               // don't leak the path information for isolated storage
+                               string fname = (anonymous) ? Path.GetFileName (name) : name;
+                               throw MonoIO.GetException (fname, error);
                        }
 
                        this.access = access;
                        this.owner = true;
-                       this.async = isAsync;
+                       this.anonymous = anonymous;
 
                        /* Can we open non-files by name? */
                        
-                       if (MonoIO.GetFileType (handle, out error) ==
-                           MonoFileType.Disk) {
+                       if (MonoIO.GetFileType (handle, out error) == MonoFileType.Disk) {
                                this.canseek = true;
+                               this.async = isAsync;
                        } else {
                                this.canseek = false;
+                               this.async = false;
+                       }
+
+
+                       if (access == FileAccess.Read && canseek && (bufferSize == DefaultBufferSize)) {
+                               /* Avoid allocating a large buffer for small files */
+                               long len = Length;
+                               if (bufferSize > len) {
+                                       bufferSize = (int)(len < 1000 ? 1000 : len);
+                               }
+                       }
+
+                       InitBuffer (bufferSize, false);
+
+                       if (mode==FileMode.Append) {
+                               this.Seek (0, SeekOrigin.End);
+                               this.append_startpos=this.Position;
+                       } else {
+                               this.append_startpos=0;
+                       }
+               }
+
+               // properties
+               
+               public override bool CanRead {
+                       get {
+                               return access == FileAccess.Read ||
+                                      access == FileAccess.ReadWrite;
                        }
+               }
 
-                       InitBuffer (bufferSize);
-               }
-
-               // properties\r
-               \r
-               public override bool CanRead {\r
-                       get {\r
-                               return access == FileAccess.Read ||\r
-                                      access == FileAccess.ReadWrite;\r
-                       }\r
-               }\r
-\r
-                public override bool CanWrite {\r
-                        get {\r
-                               return access == FileAccess.Write ||\r
-                                      access == FileAccess.ReadWrite;\r
-                        }\r
-                }\r
+                public override bool CanWrite {
+                        get {
+                               return access == FileAccess.Write ||
+                                      access == FileAccess.ReadWrite;
+                        }
+                }
                
                public override bool CanSeek {
                         get {
@@ -136,29 +265,51 @@ namespace System.IO
                        }
                }
 
-               public string Name {\r
-                       get {\r
-                               return name; \r
-                       }\r
-               }\r
+               public string Name {
+                       get {
+                               return name; 
+                       }
+               }
 
                public override long Length {
                        get {
+                               if (handle == MonoIO.InvalidHandle)
+                                       throw new ObjectDisposedException ("Stream has been closed");
+
+                               if (!CanSeek)
+                                       throw new NotSupportedException ("The stream does not support seeking");
+
+                               // Buffered data might change the length of the stream
+                               FlushBufferIfDirty ();
+
                                MonoIOError error;
+                               long length;
                                
-                               return MonoIO.GetLength (handle, out error);
+                               length = MonoIO.GetLength (handle, out error);
+                               if (error != MonoIOError.ERROR_SUCCESS) {
+                                       // don't leak the path information for isolated storage
+                                       string fname = (anonymous) ? Path.GetFileName (name) : name;
+                                       throw MonoIO.GetException (fname, error);
+                               }
+
+                               return(length);
                        }
                }
 
                public override long Position {
                        get {
-                               if(CanSeek == false) {
+                               if (handle == MonoIO.InvalidHandle)
+                                       throw new ObjectDisposedException ("Stream has been closed");
+
+                               if(CanSeek == false)
                                        throw new NotSupportedException("The stream does not support seeking");
-                               }
                                
                                return(buf_start + buf_offset);
                        }
                        set {
+                               if (handle == MonoIO.InvalidHandle)
+                                       throw new ObjectDisposedException ("Stream has been closed");
+
                                if(CanSeek == false) {
                                        throw new NotSupportedException("The stream does not support seeking");
                                }
@@ -171,37 +322,101 @@ namespace System.IO
                        }
                }
 
-               public virtual IntPtr Handle {\r
-                       get { return handle; }\r
-               }\r
-\r
-               // methods\r
-\r
-               public override int ReadByte ()\r
-               {\r
-                       if (buf_offset >= buf_length) {\r
-                               RefillBuffer ();\r
-\r
-                               if (buf_length == 0)\r
-                                       return -1;\r
-                       }\r
-\r
-                       return buf [buf_offset ++];\r
-               }\r
-\r
-               public override void WriteByte (byte value)\r
-               {\r
-                       if (buf_offset == buf_size)\r
-                               FlushBuffer ();\r
-\r
-                       buf [buf_offset ++] = value;\r
-                       if (buf_offset > buf_length)\r
-                               buf_length = buf_offset;\r
-\r
-                       buf_dirty = true;\r
-               }\r
-
-               public override int Read (byte[] dest, int dest_offset, int count)
+               public virtual IntPtr Handle {
+                       [SecurityPermission (SecurityAction.LinkDemand, UnmanagedCode = true)]
+                       [SecurityPermission (SecurityAction.InheritanceDemand, UnmanagedCode = true)]
+                       get {
+                               return handle;
+                       }
+               }
+
+#if NET_2_0
+               public virtual SafeFileHandle SafeFileHandle {
+                       [SecurityPermission (SecurityAction.LinkDemand, UnmanagedCode = true)]
+                       [SecurityPermission (SecurityAction.InheritanceDemand, UnmanagedCode = true)]
+                       get { throw new NotImplementedException (); }
+               }
+#endif
+
+               // methods
+
+               public override int ReadByte ()
+               {
+                       if (handle == MonoIO.InvalidHandle)
+                               throw new ObjectDisposedException ("Stream has been closed");
+
+                       if (!CanRead)
+                               throw new NotSupportedException ("Stream does not support reading");
+                       
+                       if (buf_size == 0) {
+                               int n = ReadData (handle, buf, 0, 1);
+                               if (n == 0) return -1;
+                               else return buf[0];
+                       }
+                       else if (buf_offset >= buf_length) {
+                               RefillBuffer ();
+
+                               if (buf_length == 0)
+                                       return -1;
+                       }
+                       
+                       return buf [buf_offset ++];
+               }
+
+               public override void WriteByte (byte value)
+               {
+                       if (handle == MonoIO.InvalidHandle)
+                               throw new ObjectDisposedException ("Stream has been closed");
+
+                       if (!CanWrite)
+                               throw new NotSupportedException ("Stream does not support writing");
+
+                       if (buf_offset == buf_size)
+                               FlushBuffer ();
+
+                       if (buf_size == 0) { // No buffering
+                               buf [0] = value;
+                               buf_dirty = true;
+                               buf_length = 1;
+                               FlushBuffer ();
+                               return;
+                       }
+
+                       buf [buf_offset ++] = value;
+                       if (buf_offset > buf_length)
+                               buf_length = buf_offset;
+
+                       buf_dirty = true;
+               }
+
+               public override int Read ([In,Out] byte[] dest, int dest_offset, int count)
+               {
+                       if (handle == MonoIO.InvalidHandle)
+                               throw new ObjectDisposedException ("Stream has been closed");
+                       if (dest == null)
+                               throw new ArgumentNullException ("destFile");
+                       if (!CanRead)
+                               throw new NotSupportedException ("Stream does not support reading");
+                       int len = dest.Length;
+                       if (dest_offset < 0)
+                               throw new ArgumentOutOfRangeException ("dest_offset", "< 0");
+                       if (count < 0)
+                               throw new ArgumentOutOfRangeException ("count", "< 0");
+                       if (dest_offset > len)
+                               throw new ArgumentException ("destination offset is beyond array size");
+                       // reordered to avoid possible integer overflow
+                       if (dest_offset > len - count)
+                               throw new ArgumentException ("Reading would overrun buffer");
+
+                       if (async) {
+                               IAsyncResult ares = BeginRead (dest, dest_offset, count, null, null);
+                               return EndRead (ares);
+                       }
+
+                       return ReadInternal (dest, dest_offset, count);
+               }
+
+               int ReadInternal (byte [] dest, int dest_offset, int count)
                {
                        int copied = 0;
 
@@ -211,69 +426,225 @@ namespace System.IO
                        
                        if (count == 0) {
                                /* If there was already enough
-                                * buffered, no need to read more from
-                                * the file.
+                                * buffered, no need to read
+                                * more from the file.
                                 */
                                return (copied);
                        }
 
                        if (count > buf_size) {
-                               /* Read as much as we can, up to count
-                                * bytes
+                               /* Read as much as we can, up
+                                * to count bytes
                                 */
                                FlushBuffer();
                                n = ReadData (handle, dest,
-                                             dest_offset+copied, count);
-                               
-                               /* Make the next buffer read start
-                                * from the right place
+                                             dest_offset+copied,
+                                             count);
+                       
+                               /* Make the next buffer read
+                                * start from the right place
                                 */
                                buf_start += n;
                        } else {
                                RefillBuffer ();
-                               n = ReadSegment (dest, dest_offset+copied,
+                               n = ReadSegment (dest,
+                                                dest_offset+copied,
                                                 count);
                        }
 
                        copied += n;
 
-                       return(copied);
+                       return copied;
                }
 
-               public override void Write (byte[] src, int src_offset, int count)\r
-               {\r
-                       int copied = 0;\r
-                       while (count > 0) {\r
-                               int n = WriteSegment (src, src_offset + copied, count);\r
-                               copied += n;\r
-                               count -= n;\r
-\r
-                               FlushBuffer ();\r
+               delegate int ReadDelegate (byte [] buffer, int offset, int count);
+
+               public override IAsyncResult BeginRead (byte [] buffer, int offset, int count,
+                                                       AsyncCallback cback, object state)
+               {
+                       if (handle == MonoIO.InvalidHandle)
+                               throw new ObjectDisposedException ("Stream has been closed");
 
-                               if (count == 0)
-                                       break;
+                       if (!CanRead)
+                               throw new NotSupportedException ("This stream does not support reading");
 
-                               if (count > buf_size) {
-                                       // shortcut for long writes
-                                       MonoIOError error;
+                       if (buffer == null)
+                               throw new ArgumentNullException ("buffer");
+
+                       if (count < 0)
+                               throw new ArgumentOutOfRangeException ("count", "Must be >= 0");
+
+                       if (offset < 0)
+                               throw new ArgumentOutOfRangeException ("offset", "Must be >= 0");
+
+                       // reordered to avoid possible integer overflow
+                       if (count > buffer.Length - offset)
+                               throw new ArgumentException ("Buffer too small. count/offset wrong.");
+
+                       if (!async)
+                               return base.BeginRead (buffer, offset, count, cback, state);
+
+                       ReadDelegate r = new ReadDelegate (ReadInternal);
+                       return r.BeginInvoke (buffer, offset, count, cback, state);                     
+               }
+               
+               public override int EndRead (IAsyncResult async_result)
+               {
+                       if (async_result == null)
+                               throw new ArgumentNullException ("async_result");
+
+                       if (!async)
+                               return base.EndRead (async_result);
+
+                       AsyncResult ares = async_result as AsyncResult;
+                       if (ares == null)
+                               throw new ArgumentException ("Invalid IAsyncResult", "async_result");
+
+                       ReadDelegate r = ares.AsyncDelegate as ReadDelegate;
+                       if (r == null)
+                               throw new ArgumentException ("Invalid IAsyncResult", "async_result");
+
+                       return r.EndInvoke (async_result);
+               }
+
+               public override void Write (byte[] src, int src_offset, int count)
+               {
+                       if (handle == MonoIO.InvalidHandle)
+                               throw new ObjectDisposedException ("Stream has been closed");
+                       if (src == null)
+                               throw new ArgumentNullException ("src");
+                       if (src_offset < 0)
+                               throw new ArgumentOutOfRangeException ("src_offset", "< 0");
+                       if (count < 0)
+                               throw new ArgumentOutOfRangeException ("count", "< 0");
+                       // ordered to avoid possible integer overflow
+                       if (src_offset > src.Length - count)
+                               throw new ArgumentException ("Reading would overrun buffer");
+                       if (!CanWrite)
+                               throw new NotSupportedException ("Stream does not support writing");
+
+                       if (async) {
+                               IAsyncResult ares = BeginWrite (src, src_offset, count, null, null);
+                               EndWrite (ares);
+                               return;
+                       }
+
+                       WriteInternal (src, src_offset, count);
+               }
+
+               void WriteInternal (byte [] src, int src_offset, int count)
+               {
+                       if (count > buf_size) {
+                               // shortcut for long writes
+                               MonoIOError error;
+
+                               FlushBuffer ();
+
+                               MonoIO.Write (handle, src, src_offset, count, out error);
+                               if (error != MonoIOError.ERROR_SUCCESS) {
+                                       // don't leak the path information for isolated storage
+                                       string fname = (anonymous) ? Path.GetFileName (name) : name;
+                                       throw MonoIO.GetException (fname, error);
+                               }
+                               
+                               buf_start += count;
+                       } else {
+
+                               int copied = 0;
+                               while (count > 0) {
                                        
-                                       MonoIO.Write (handle, src, src_offset + copied, count, out error);
-                                       buf_start += count;
-                                       break;
+                                       int n = WriteSegment (src, src_offset + copied, count);
+                                       copied += n;
+                                       count -= n;
+
+                                       if (count == 0) {
+                                               break;
+                                       }
+
+                                       FlushBuffer ();
                                }
-                       }\r
-               }\r
+                       }
+               }
+
+               delegate void WriteDelegate (byte [] buffer, int offset, int count);
+
+               public override IAsyncResult BeginWrite (byte [] buffer, int offset, int count,
+                                                       AsyncCallback cback, object state)
+               {
+                       if (handle == MonoIO.InvalidHandle)
+                               throw new ObjectDisposedException ("Stream has been closed");
+
+                       if (!CanWrite)
+                               throw new NotSupportedException ("This stream does not support writing");
+
+                       if (buffer == null)
+                               throw new ArgumentNullException ("buffer");
+
+                       if (count < 0)
+                               throw new ArgumentOutOfRangeException ("count", "Must be >= 0");
+
+                       if (offset < 0)
+                               throw new ArgumentOutOfRangeException ("offset", "Must be >= 0");
+
+                       // reordered to avoid possible integer overflow
+                       if (count > buffer.Length - offset)
+                               throw new ArgumentException ("Buffer too small. count/offset wrong.");
+
+                       if (!async)
+                               return base.BeginWrite (buffer, offset, count, cback, state);
+
+                       FileStreamAsyncResult result = new FileStreamAsyncResult (cback, state);
+                       result.BytesRead = -1;
+                       result.Count = count;
+                       result.OriginalCount = count;
+
+                       if (buf_dirty) {
+                               MemoryStream ms = new MemoryStream ();
+                               FlushBufferToStream (ms);
+                               ms.Write (buffer, offset, count);
+                               offset = 0;
+                               count = (int) ms.Length;
+                       }
+
+                       WriteDelegate w = new WriteDelegate (WriteInternal);
+                       return w.BeginInvoke (buffer, offset, count, cback, state);                     
+               }
+               
+               public override void EndWrite (IAsyncResult async_result)
+               {
+                       if (async_result == null)
+                               throw new ArgumentNullException ("async_result");
+
+                       if (!async) {
+                               base.EndWrite (async_result);
+                               return;
+                       }
+
+                       AsyncResult ares = async_result as AsyncResult;
+                       if (ares == null)
+                               throw new ArgumentException ("Invalid IAsyncResult", "async_result");
+
+                       WriteDelegate w = ares.AsyncDelegate as WriteDelegate;
+                       if (w == null)
+                               throw new ArgumentException ("Invalid IAsyncResult", "async_result");
+
+                       w.EndInvoke (async_result);
+                       return;
+               }
 
                public override long Seek (long offset, SeekOrigin origin)
                {
                        long pos;
 
+                       if (handle == MonoIO.InvalidHandle)
+                               throw new ObjectDisposedException ("Stream has been closed");
+                       
                        // make absolute
 
                        if(CanSeek == false) {
                                throw new NotSupportedException("The stream does not support seeking");
                        }
-                       
+
                        switch (origin) {
                        case SeekOrigin.End:
                                pos = Length + offset;
@@ -283,125 +654,243 @@ namespace System.IO
                                pos = Position + offset;
                                break;
 
-                       case SeekOrigin.Begin: default:
+                       case SeekOrigin.Begin:
                                pos = offset;
                                break;
+
+                       default:
+                               throw new ArgumentException ("origin", "Invalid SeekOrigin");
                        }
 
                        if (pos < 0) {
                                /* LAMESPEC: shouldn't this be
                                 * ArgumentOutOfRangeException?
                                 */
-                               throw new ArgumentException("Attempted to Seek before the beginning of the stream");
+                               throw new IOException("Attempted to Seek before the beginning of the stream");
                        }
-                       
-                       if (pos >= buf_start && pos <= buf_start + buf_length) {
-                               buf_offset = (int) (pos - buf_start);
-                               return pos;
+
+                       if(pos < this.append_startpos) {
+                               /* More undocumented crap */
+                               throw new IOException("Can't seek back over pre-existing data in append mode");
+                       }
+
+                       if (buf_length > 0) {
+                               if (pos >= buf_start &&
+                                       pos <= buf_start + buf_length) {
+                                       buf_offset = (int) (pos - buf_start);
+                                       return pos;
+                               }
                        }
 
                        FlushBuffer ();
 
                        MonoIOError error;
-                       
+               
                        buf_start = MonoIO.Seek (handle, pos,
-                                                SeekOrigin.Begin, out error);
+                                                SeekOrigin.Begin,
+                                                out error);
 
-                       return buf_start;
+                       if (error != MonoIOError.ERROR_SUCCESS) {
+                               // don't leak the path information for isolated storage
+                               string fname = (anonymous) ? Path.GetFileName (name) : name;
+                               throw MonoIO.GetException (fname, error);
+                       }
+                       
+                       return(buf_start);
                }
 
                public override void SetLength (long length)
                {
-                       if(CanSeek == false) {
+                       if (handle == MonoIO.InvalidHandle)
+                               throw new ObjectDisposedException ("Stream has been closed");
+
+                       if(CanSeek == false)
                                throw new NotSupportedException("The stream does not support seeking");
-                       }
 
-                       if(CanWrite == false) {
+                       if(CanWrite == false)
                                throw new NotSupportedException("The stream does not support writing");
-                       }
 
-                       if(length < 0) {
+                       if(length < 0)
                                throw new ArgumentOutOfRangeException("Length is less than 0");
-                       }
                        
                        Flush ();
 
                        MonoIOError error;
                        
                        MonoIO.SetLength (handle, length, out error);
+                       if (error != MonoIOError.ERROR_SUCCESS) {
+                               // don't leak the path information for isolated storage
+                               string fname = (anonymous) ? Path.GetFileName (name) : name;
+                               throw MonoIO.GetException (fname, error);
+                       }
+
+                       if (Position > length)
+                               Position = length;
+               }
+
+               public override void Flush ()
+               {
+                       if (handle == MonoIO.InvalidHandle)
+                               throw new ObjectDisposedException ("Stream has been closed");
+
+                       FlushBuffer ();
+                       
+                       // The flushing is not actually required, in
+                       //the mono runtime we were mapping flush to
+                       //`fsync' which is not the same.
+                       //
+                       //MonoIO.Flush (handle);
                }
 
-               public override void Flush ()\r
-               {\r
-                       FlushBuffer ();\r
-\r
-                       //\r
-                       // The flushing is not actually required, in the mono runtime we were\r
-                       // mapping flush to `fsync' which is not the same.\r
-                       //\r
-                       //MonoIO.Flush (handle);\r
-               }\r
-\r
-               public override void Close ()\r
-               {\r
-                       Dispose (true);\r
-                       GC.SuppressFinalize (this);     // remove from finalize queue\r
-               }\r
-\r
-               // protected\r
-\r
-               ~FileStream ()\r
-               {\r
-                       Dispose (false);\r
-               }\r
+               public override void Close ()
+               {
+                       Dispose (true);
+                       GC.SuppressFinalize (this);     // remove from finalize queue
+               }
+
+               public virtual void Lock (long position, long length)
+               {
+                       if (handle == MonoIO.InvalidHandle)
+                               throw new ObjectDisposedException ("Stream has been closed");
+                       if (position < 0) {
+                               throw new ArgumentOutOfRangeException ("position must not be negative");
+                       }
+                       if (length < 0) {
+                               throw new ArgumentOutOfRangeException ("length must not be negative");
+                       }
+                       if (handle == MonoIO.InvalidHandle) {
+                               throw new ObjectDisposedException ("Stream has been closed");
+                       }
+                               
+                       MonoIOError error;
+
+                       MonoIO.Lock (handle, position, length, out error);
+                       if (error != MonoIOError.ERROR_SUCCESS) {
+                               // don't leak the path information for isolated storage
+                               string fname = (anonymous) ? Path.GetFileName (name) : name;
+                               throw MonoIO.GetException (fname, error);
+                       }
+               }
+
+               public virtual void Unlock (long position, long length)
+               {
+                       if (handle == MonoIO.InvalidHandle)
+                               throw new ObjectDisposedException ("Stream has been closed");
+                       if (position < 0) {
+                               throw new ArgumentOutOfRangeException ("position must not be negative");
+                       }
+                       if (length < 0) {
+                               throw new ArgumentOutOfRangeException ("length must not be negative");
+                       }
+                               
+                       MonoIOError error;
+
+                       MonoIO.Unlock (handle, position, length, out error);
+                       if (error != MonoIOError.ERROR_SUCCESS) {
+                               // don't leak the path information for isolated storage
+                               string fname = (anonymous) ? Path.GetFileName (name) : name;
+                               throw MonoIO.GetException (fname, error);
+                       }
+               }
+
+               // protected
+
+               ~FileStream ()
+               {
+                       Dispose (false);
+               }
 
                protected virtual void Dispose (bool disposing) {
                        if (handle != MonoIO.InvalidHandle) {
                                FlushBuffer ();
 
-                               MonoIOError error;
+                               if (owner) {
+                                       MonoIOError error;
                                
-                               MonoIO.Close (handle, out error);
-
-                               handle = MonoIO.InvalidHandle;
+                                       MonoIO.Close (handle, out error);
+                                       if (error != MonoIOError.ERROR_SUCCESS) {
+                                               // don't leak the path information for isolated storage
+                                               string fname = (anonymous) ? Path.GetFileName (name) : name;
+                                               throw MonoIO.GetException (fname, error);
+                                       }
+
+                                       handle = MonoIO.InvalidHandle;
+                               }
                        }
 
+                       canseek = false;
+                       access = 0;
                        if (disposing) {
                                buf = null;
                        }
                }
 
-               // private\r
-\r
-               private int ReadSegment (byte [] dest, int dest_offset, int count)\r
-               {\r
-                       if (count > buf_length - buf_offset)\r
-                               count = buf_length - buf_offset;\r
-\r
-                       if (count > 0) {\r
-                               Buffer.BlockCopy (buf, buf_offset, dest, dest_offset, count);\r
-                               buf_offset += count;\r
-                       }\r
-\r
-                       return count;\r
-               }\r
-\r
-               private int WriteSegment (byte [] src, int src_offset, int count)\r
-               {\r
-                       if (count > buf_size - buf_offset)\r
-                               count = buf_size - buf_offset;\r
-\r
-                       if (count > 0) {\r
-                               Buffer.BlockCopy (src, src_offset, buf, buf_offset, count);\r
-                               buf_offset += count;\r
-                               if (buf_offset > buf_length)\r
-                                       buf_length = buf_offset;\r
-\r
-                               buf_dirty = true;\r
-                       }\r
-\r
-                       return count;\r
-               }\r
+               // private.
+
+               // ReadSegment, WriteSegment, FlushBuffer,
+               // RefillBuffer and ReadData should only be called
+               // when the Monitor lock is held, but these methods
+               // grab it again just to be safe.
+
+               private int ReadSegment (byte [] dest, int dest_offset, int count)
+               {
+                       if (count > buf_length - buf_offset) {
+                               count = buf_length - buf_offset;
+                       }
+                       
+                       if (count > 0) {
+                               Buffer.BlockCopy (buf, buf_offset,
+                                                 dest, dest_offset,
+                                                 count);
+                               buf_offset += count;
+                       }
+                       
+                       return(count);
+               }
+
+               private int WriteSegment (byte [] src, int src_offset,
+                                         int count)
+               {
+                       if (count > buf_size - buf_offset) {
+                               count = buf_size - buf_offset;
+                       }
+                       
+                       if (count > 0) {
+                               Buffer.BlockCopy (src, src_offset,
+                                                 buf, buf_offset,
+                                                 count);
+                               buf_offset += count;
+                               if (buf_offset > buf_length) {
+                                       buf_length = buf_offset;
+                               }
+                               
+                               buf_dirty = true;
+                       }
+                       
+                       return(count);
+               }
+
+               void FlushBufferToStream (Stream st)
+               {
+                       if (buf_dirty) {
+                               if (CanSeek == true) {
+                                       MonoIOError error;
+                                       MonoIO.Seek (handle, buf_start,
+                                                    SeekOrigin.Begin,
+                                                    out error);
+                                       if (error != MonoIOError.ERROR_SUCCESS) {
+                                               // don't leak the path information for isolated storage
+                                               string fname = (anonymous) ? Path.GetFileName (name) : name;
+                                               throw MonoIO.GetException (fname, error);
+                                       }
+                               }
+                               st.Write (buf, 0, buf_length);
+                       }
+
+                       buf_start += buf_offset;
+                       buf_offset = buf_length = 0;
+                       buf_dirty = false;
+               }
 
                private void FlushBuffer ()
                {
@@ -412,76 +901,108 @@ namespace System.IO
                                        MonoIO.Seek (handle, buf_start,
                                                     SeekOrigin.Begin,
                                                     out error);
+                                       if (error != MonoIOError.ERROR_SUCCESS) {
+                                               // don't leak the path information for isolated storage
+                                               string fname = (anonymous) ? Path.GetFileName (name) : name;
+                                               throw MonoIO.GetException (fname, error);
+                                       }
+                               }
+                               MonoIO.Write (handle, buf, 0,
+                                             buf_length, out error);
+
+                               if (error != MonoIOError.ERROR_SUCCESS) {
+                                       // don't leak the path information for isolated storage
+                                       string fname = (anonymous) ? Path.GetFileName (name) : name;
+                                       throw MonoIO.GetException (fname, error);
                                }
-                               MonoIO.Write (handle, buf, 0, buf_length,
-                                             out error);
                        }
 
-                       buf_start += buf_length;
+                       buf_start += buf_offset;
                        buf_offset = buf_length = 0;
                        buf_dirty = false;
                }
 
+               private void FlushBufferIfDirty ()
+               {
+                       if (buf_dirty)
+                               FlushBuffer ();
+               }
+
                private void RefillBuffer ()
                {
                        FlushBuffer();
-
-                       buf_length = ReadData (handle, buf, 0, buf_size);
+                       
+                       buf_length = ReadData (handle, buf, 0,
+                                              buf_size);
                }
 
                private int ReadData (IntPtr handle, byte[] buf, int offset,
                                      int count)
                {
                        MonoIOError error;
+                       int amount = 0;
+
+                       /* when async == true, if we get here we don't suport AIO or it's disabled
+                        * and we're using the threadpool */
+                       amount = MonoIO.Read (handle, buf, offset, count, out error);
+                       if (error == MonoIOError.ERROR_BROKEN_PIPE) {
+                               amount = 0; // might not be needed, but well...
+                       } else if (error != MonoIOError.ERROR_SUCCESS) {
+                               // don't leak the path information for isolated storage
+                               string fname = (anonymous) ? Path.GetFileName (name) : name;
+                               throw MonoIO.GetException (fname, error);
+                       }
                        
-                       int amount = MonoIO.Read (handle, buf, offset, count,
-                                                 out error);
-
                        /* Check for read error */
                        if(amount == -1) {
-                               /* Kludge around broken pipes */
-                               if(error == MonoIOError.ERROR_BROKEN_PIPE) {
-                                       amount = 0;
-                               } else {
-                                       throw new IOException ();
-                               }
+                               throw new IOException ();
                        }
-
+                       
                        return(amount);
                }
-               
-               
-               private void InitBuffer (int size)\r
-               {\r
-                       if (size < 0)\r
-                               throw new ArgumentOutOfRangeException ("Buffer size cannot be negative.");\r
-                       if (size < 8)\r
-                               size = 8;\r
-               \r
-                       buf = new byte [size];\r
-                       buf_size = size;\r
-                       buf_start = 0;\r
-                       buf_offset = buf_length = 0;\r
-                       buf_dirty = false;\r
-               }\r
-\r
-               // fields\r
-\r
-               private static int DefaultBufferSize = 8192;\r
-\r
-               private FileAccess access;\r
-               private bool owner;\r
-               private bool async;\r
+                               
+               private void InitBuffer (int size, bool noBuffering)
+               {
+                       if (noBuffering) {
+                               size = 0;
+                               // We need a buffer for the ReadByte method. This buffer won't
+                               // be used for anything else since buf_size==0.
+                               buf = new byte [1];
+                       }
+                       else {
+                               if (size <= 0)
+                                       throw new ArgumentOutOfRangeException ("bufferSize", "Positive number required.");
+                               if (size < 8)
+                                       size = 8;
+                               buf = new byte [size];
+                       }
+                                       
+                       buf_size = size;
+                       buf_start = 0;
+                       buf_offset = buf_length = 0;
+                       buf_dirty = false;
+               }
+
+               // fields
+
+               internal const int DefaultBufferSize = 8192;
+
+               private FileAccess access;
+               private bool owner;
+               private bool async;
                private bool canseek;
-\r
-               private byte [] buf;                    // the buffer\r
-               private int buf_size;                   // capacity in bytes\r
-               private int buf_length;                 // number of valid bytes in buffer\r
-               private int buf_offset;                 // position of next byte\r
-               private bool buf_dirty;                 // true if buffer has been written to\r
-               private long buf_start;                 // location of buffer in file\r
-               private string name = "[Unknown]";      // name of file.\r
-\r
-               IntPtr handle;                          // handle to underlying file\r
-       }\r
-}\r
+               private long append_startpos;
+               private bool anonymous;
+
+               private byte [] buf;                    // the buffer
+               private int buf_size;                   // capacity in bytes
+               private int buf_length;                 // number of valid bytes in buffer
+               private int buf_offset;                 // position of next byte
+               private bool buf_dirty;                 // true if buffer has been written to
+               private long buf_start;                 // location of buffer in file
+               private string name = "[Unknown]";      // name of file.
+
+               IntPtr handle;                          // handle to underlying file
+       }
+}
+