2005-08-11 Atsushi Enomoto <atsushi@ximian.com>
[mono.git] / mcs / class / corlib / System.IO / FileStream.cs
index e587c4a538146e72cce7197d33d43df16f9e6046..945f995d682a83bf69cd8e2612f102eae1dcd7c8 100644 (file)
 //
 // System.IO/FileStream.cs
 //
-// Author:
-//   Dietmar Maurer (dietmar@ximian.com)
+// Authors:
+//     Dietmar Maurer (dietmar@ximian.com)
+//     Dan Lewis (dihlewis@yahoo.co.uk)
+//     Gonzalo Paniagua Javier (gonzalo@ximian.com)
 //
-// (C) 2001 Ximian, Inc.  http://www.ximian.com
+// (C) 2001-2003 Ximian, Inc.  http://www.ximian.com
+// Copyright (C) 2004-2005 Novell, Inc (http://www.novell.com)
 //
-using System;
-using System.PAL;
+// 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;
 
-// fixme: I do not know how to handle errno when calling PInvoke functions
-// fixme: emit the correct exceptions everywhere
+#if NET_2_0
+using Microsoft.Win32.SafeHandles;
+#endif
 
 namespace System.IO
 {
-
        public class FileStream : Stream
        {
-               private OpSys _os = Platform.OS;
-               private IntPtr fdhandle;
-               private FileAccess acc;
-               private bool owner;
+               // construct from handle
                
                public FileStream (IntPtr handle, FileAccess access)
-                       : this (handle, access, true, 0, false) {}
+                       : this (handle, access, true, DefaultBufferSize, false) {}
 
                public FileStream (IntPtr handle, FileAccess access, bool ownsHandle)
-                       : this (handle, access, ownsHandle, 0, false) {}
+                       : 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)
+
+               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)
                {
-                       fdhandle = handle;
+                       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);
+                       }
                        
-                       //acc = access;
-                       //owner = ownsHandle;
+                       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;
+                       }
+
+                       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, FileAccess.ReadWrite, FileShare.ReadWrite, 0, false) {}
+                       : 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, FileShare.ReadWrite, 0, false) {}
+                       : 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, 0, false) {}
+                       : this (name, mode, access, share, DefaultBufferSize, false, false)
+               {
+               }
                
-               public FileStream (string name, FileMode mode, FileAccess access,
-                                  FileShare share, int buferSize)
-                       : this (name, mode, access, share, 0, 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)
+               {
+               }
 
-               // fixme: implement all share, buffer, async
-               public FileStream (string name, FileMode mode, FileAccess access, FileShare share,
-                                  int buferSize, bool useAsync)
+               internal FileStream (string name, FileMode mode, FileAccess access, FileShare share, int bufferSize, bool isAsync, bool anonymous)
                {
-                       fdhandle = _os.OpenFile (name, mode, access, share);
+                       if (name == null) {
+                               throw new ArgumentNullException ("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)) {
+                               // 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));
+                               }
+                       }
+
+                       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, false, out error);
+                       if (handle == MonoIO.InvalidHandle) {
+                               // 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.anonymous = anonymous;
+
+                       /* Can we open non-files by name? */
                        
-                       /* Implement error checking, with some sort of access
-                          to the errno error reason
-                       if(fdhandle == error) {
-                               throw new IOException();
+                       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);
+                               }
                        }
-                       */
 
-                       acc = access;
-                       owner = true;
+                       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
-               {
+               public override bool CanRead {
                        get {
-                               switch (acc) {
-                               case FileAccess.Read:
-                               case FileAccess.ReadWrite:
-                                       return true;
-                               case FileAccess.Write:
-                               default:
-                                       return false;
-                               }
+                               return access == FileAccess.Read ||
+                                      access == FileAccess.ReadWrite;
                        }
                }
 
-               public override bool CanSeek
-               {
+                public override bool CanWrite {
                         get {
-                               // fixme: not alway true
-                                return true;
+                               return access == FileAccess.Write ||
+                                      access == FileAccess.ReadWrite;
                         }
                 }
-
-                public override bool CanWrite
-               {
+               
+               public override bool CanSeek {
                         get {
-                               switch (acc) {
-                               case FileAccess.Write:
-                               case FileAccess.ReadWrite:
-                                       return true;
-                               default:
-                                       return false;
-                               }
+                                return(canseek);
                         }
                 }
 
-               unsafe public override long Length
-               {
+               public virtual bool IsAsync {
                        get {
-                               return _os.FileLength (fdhandle);
+                               return (async);
                        }
                }
 
-               public override long Position
-               {
+               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;
+                               
+                               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 {
-                               return _os.SeekFile (fdhandle, 0,  SeekOrigin.Current);
+                               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 {
-                               _os.SeekFile (fdhandle, value, SeekOrigin.Begin);
+                               if (handle == MonoIO.InvalidHandle)
+                                       throw new ObjectDisposedException ("Stream has been closed");
+
+                               if(CanSeek == false) {
+                                       throw new NotSupportedException("The stream does not support seeking");
+                               }
+
+                               if(value < 0) {
+                                       throw new ArgumentOutOfRangeException("Attempt to set the position to a negative value");
+                               }
+                               
+                               Seek (value, SeekOrigin.Begin);
                        }
                }
 
-               public override void Flush ()
+               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 Close ()
+               public override void WriteByte (byte value)
                {
-                       if (owner) {
-                               _os.CloseFile (fdhandle);
+                       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 unsafe override int Read (byte[] buffer,
-                                         int offset,
-                                         int count)
+               public override int Read ([In,Out] byte[] dest, int dest_offset, int count)
                {
-                       return _os.ReadFile (fdhandle, buffer, offset, 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);
                }
 
-               public unsafe override int ReadByte ()
+               int ReadInternal (byte [] dest, int dest_offset, int count)
                {
-                       byte[] val = new byte[1];
-                       int res = Read (val, 0, 1);
+                       int copied = 0;
+
+                       int n = ReadSegment (dest, dest_offset, count);
+                       copied += n;
+                       count -= n;
                        
-                       if (res == -1)
-                               throw new IOException();
-                       if (res == 0)
-                               return -1;
+                       if (count == 0) {
+                               /* If there was already enough
+                                * 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
+                                */
+                               FlushBuffer();
+                               n = ReadData (handle, dest,
+                                             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,
+                                                count);
+                       }
+
+                       copied += n;
 
-                       return val[0];
+                       return copied;
                }
 
-               public override long Seek (long offset,
-                                          SeekOrigin origin)
+               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 (!CanRead)
+                               throw new NotSupportedException ("This stream does not support reading");
+
+                       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)
                {
-                       return _os.SeekFile (fdhandle, offset, origin);
+                       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 SetLength (long value)
+               public override void Write (byte[] src, int src_offset, int count)
                {
-                       _os.SetLengthFile (fdhandle, value);
+                       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);
                }
 
-               public unsafe override void Write (byte[] buffer,
-                                                  int offset,
-                                                  int count)
+               void WriteInternal (byte [] src, int src_offset, int count)
                {
-                       int res = _os.WriteFile (fdhandle, buffer, offset, 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) {
+                                       
+                                       int n = WriteSegment (src, src_offset + copied, count);
+                                       copied += n;
+                                       count -= n;
+
+                                       if (count == 0) {
+                                               break;
+                                       }
+
+                                       FlushBuffer ();
+                               }
+                       }
+               }
+
+               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;
+                               break;
+
+                       case SeekOrigin.Current:
+                               pos = Position + offset;
+                               break;
+
+                       case SeekOrigin.Begin:
+                               pos = offset;
+                               break;
+
+                       default:
+                               throw new ArgumentException ("origin", "Invalid SeekOrigin");
+                       }
+
+                       if (pos < 0) {
+                               /* LAMESPEC: shouldn't this be
+                                * ArgumentOutOfRangeException?
+                                */
+                               throw new IOException("Attempted to Seek before the beginning of the stream");
+                       }
+
+                       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);
+
+                       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 (res != count)
-                               throw new IOException();
+                       return(buf_start);
                }
 
-               public unsafe override void WriteByte (byte value)
+               public override void SetLength (long length)
                {
-                       byte[] buf = new byte[1];
+                       if (handle == MonoIO.InvalidHandle)
+                               throw new ObjectDisposedException ("Stream has been closed");
 
-                       buf[0] = value;
+                       if(CanSeek == false)
+                               throw new NotSupportedException("The stream does not support seeking");
 
-                       Write (buf, 0, 1);
+                       if(CanWrite == false)
+                               throw new NotSupportedException("The stream does not support writing");
+
+                       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 virtual IntPtr Handle
+               public override void Flush ()
                {
-                       get {
-                               return(fdhandle);
+                       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 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 ();
+
+                               if (owner) {
+                                       MonoIOError error;
+                               
+                                       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.
+
+               // 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 ()
+               {
+                       if (buf_dirty) {
+                               MonoIOError error;
+                               
+                               if (CanSeek == true) {
+                                       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);
+                               }
+                       }
+
+                       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);
+               }
+
+               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);
+                       }
+                       
+                       /* Check for read error */
+                       if(amount == -1) {
+                               throw new IOException ();
+                       }
+                       
+                       return(amount);
+               }
+                               
+               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;
+               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
        }
 }
+