Flush (work in progress)
[mono.git] / mcs / class / corlib / System.IO / FileStream.cs
1 //
2 // System.IO.FileStream.cs
3 //
4 // Authors:
5 //      Dietmar Maurer (dietmar@ximian.com)
6 //      Dan Lewis (dihlewis@yahoo.co.uk)
7 //      Gonzalo Paniagua Javier (gonzalo@ximian.com)
8 //  Marek Safar (marek.safar@gmail.com)
9 //
10 // (C) 2001-2003 Ximian, Inc.  http://www.ximian.com
11 // Copyright (C) 2004-2005, 2008, 2010 Novell, Inc (http://www.novell.com)
12 //
13 // Permission is hereby granted, free of charge, to any person obtaining
14 // a copy of this software and associated documentation files (the
15 // "Software"), to deal in the Software without restriction, including
16 // without limitation the rights to use, copy, modify, merge, publish,
17 // distribute, sublicense, and/or sell copies of the Software, and to
18 // permit persons to whom the Software is furnished to do so, subject to
19 // the following conditions:
20 // 
21 // The above copyright notice and this permission notice shall be
22 // included in all copies or substantial portions of the Software.
23 // 
24 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
25 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
26 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
27 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
28 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
29 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
30 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31 //
32
33 using System.Collections;
34 using System.Globalization;
35 using System.Runtime.CompilerServices;
36 using System.Runtime.InteropServices;
37 using System.Runtime.Remoting.Messaging;
38 using System.Security;
39 using System.Security.Permissions;
40 using System.Threading;
41
42 using Microsoft.Win32.SafeHandles;
43 #if NET_2_1
44 using System.IO.IsolatedStorage;
45 #else
46 using System.Security.AccessControl;
47 #endif
48
49 namespace System.IO
50 {
51         [ComVisible (true)]
52         public class FileStream : Stream
53         {
54                 // construct from handle
55                 
56                 [Obsolete ("Use FileStream(SafeFileHandle handle, FileAccess access) instead")]
57                 public FileStream (IntPtr handle, FileAccess access)
58                         : this (handle, access, true, DefaultBufferSize, false) {}
59
60                 [Obsolete ("Use FileStream(SafeFileHandle handle, FileAccess access) instead")]
61                 public FileStream (IntPtr handle, FileAccess access, bool ownsHandle)
62                         : this (handle, access, ownsHandle, DefaultBufferSize, false) {}
63                 
64                 [Obsolete ("Use FileStream(SafeFileHandle handle, FileAccess access, int bufferSize) instead")]
65                 public FileStream (IntPtr handle, FileAccess access, bool ownsHandle, int bufferSize)
66                         : this (handle, access, ownsHandle, bufferSize, false) {}
67
68                 [Obsolete ("Use FileStream(SafeFileHandle handle, FileAccess access, int bufferSize, bool isAsync) instead")]
69                 public FileStream (IntPtr handle, FileAccess access, bool ownsHandle, int bufferSize, bool isAsync)
70                         : this (handle, access, ownsHandle, bufferSize, isAsync, false) {}
71
72                 [SecurityPermission (SecurityAction.Demand, UnmanagedCode = true)]
73                 internal FileStream (IntPtr handle, FileAccess access, bool ownsHandle, int bufferSize, bool isAsync, bool isZeroSize)
74                 {
75                         this.handle = MonoIO.InvalidHandle;
76                         if (handle == this.handle)
77                                 throw new ArgumentException ("handle", Locale.GetText ("Invalid."));
78
79                         if (access < FileAccess.Read || access > FileAccess.ReadWrite)
80                                 throw new ArgumentOutOfRangeException ("access");
81
82                         MonoIOError error;
83                         MonoFileType ftype = MonoIO.GetFileType (handle, out error);
84
85                         if (error != MonoIOError.ERROR_SUCCESS) {
86                                 throw MonoIO.GetException (name, error);
87                         }
88                         
89                         if (ftype == MonoFileType.Unknown) {
90                                 throw new IOException ("Invalid handle.");
91                         } else if (ftype == MonoFileType.Disk) {
92                                 this.canseek = true;
93                         } else {
94                                 this.canseek = false;
95                         }
96
97                         this.handle = handle;
98                         this.access = access;
99                         this.owner = ownsHandle;
100                         this.async = isAsync;
101 #if MOONLIGHT
102                         // default the browser to 'all' anonymous files and let other usage (like smcs) with 'normal'
103                         // (i.e. non-anonymous except for isolated storage) files and paths
104                         this.anonymous = SecurityManager.SecurityEnabled;
105 #else
106                         this.anonymous = false;
107 #endif
108                         if (isZeroSize)
109                                 bufferSize = 1;
110
111                         InitBuffer (bufferSize);
112
113                         if (canseek) {
114                                 buf_start = MonoIO.Seek (handle, 0, SeekOrigin.Current, out error);
115                                 if (error != MonoIOError.ERROR_SUCCESS) {
116                                         throw MonoIO.GetException (name, error);
117                                 }
118                         }
119
120                         /* Can't set append mode */
121                         this.append_startpos=0;
122                 }
123
124                 // construct from filename
125                 
126                 public FileStream (string path, FileMode mode)
127                         : this (path, mode, (mode == FileMode.Append ? FileAccess.Write : FileAccess.ReadWrite), FileShare.Read, DefaultBufferSize, false, FileOptions.None)
128                 {
129                 }
130
131                 public FileStream (string path, FileMode mode, FileAccess access)
132                         : this (path, mode, access, access == FileAccess.Write ? FileShare.None : FileShare.Read, DefaultBufferSize, false, false)
133                 {
134                 }
135
136                 public FileStream (string path, FileMode mode, FileAccess access, FileShare share)
137                         : this (path, mode, access, share, DefaultBufferSize, false, FileOptions.None)
138                 {
139                 }
140
141                 public FileStream (string path, FileMode mode, FileAccess access, FileShare share, int bufferSize)
142                         : this (path, mode, access, share, bufferSize, false, FileOptions.None)
143                 {
144                 }
145
146                 public FileStream (string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, bool useAsync)
147                         : this (path, mode, access, share, bufferSize, useAsync, FileOptions.None)
148                 {
149                 }
150
151 #if !NET_2_1
152                 public FileStream (string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options)
153                         : this (path, mode, access, share, bufferSize, false, options)
154                 {
155                 }
156
157                 public FileStream (SafeFileHandle handle, FileAccess access)
158                         :this(handle, access, DefaultBufferSize, false)
159                 {
160                 }
161                 
162                 public FileStream (SafeFileHandle handle, FileAccess access,
163                                    int bufferSize)
164                         :this(handle, access, bufferSize, false)
165                 {
166                 }
167                 
168                 [MonoLimitationAttribute("Need to use SafeFileHandle instead of underlying handle")]
169                 public FileStream (SafeFileHandle handle, FileAccess access,
170                                    int bufferSize, bool isAsync)
171                         :this (handle.DangerousGetHandle (), access, false, bufferSize, isAsync)
172                 {
173                         this.safeHandle = handle;
174                 }
175
176                 [MonoLimitation ("This ignores the rights parameter")]
177                 public FileStream (string path, FileMode mode,
178                                    FileSystemRights rights, FileShare share,
179                                    int bufferSize, FileOptions options)
180                         : this (path, mode, (mode == FileMode.Append ? FileAccess.Write : FileAccess.ReadWrite), share, bufferSize, false, options)
181                 {
182                 }
183                 
184                 [MonoLimitation ("This ignores the rights and fileSecurity parameters")]
185                 public FileStream (string path, FileMode mode,
186                                    FileSystemRights rights, FileShare share,
187                                    int bufferSize, FileOptions options,
188                                    FileSecurity fileSecurity)
189                         : this (path, mode, (mode == FileMode.Append ? FileAccess.Write : FileAccess.ReadWrite), share, bufferSize, false, options)
190                 {
191                 }
192 #endif
193
194                 internal FileStream (string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, bool isAsync, bool anonymous)
195                         : this (path, mode, access, share, bufferSize, anonymous, isAsync ? FileOptions.Asynchronous : FileOptions.None)
196                 {
197                 }
198
199                 internal FileStream (string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, bool anonymous, FileOptions options)
200                 {
201                         if (path == null) {
202                                 throw new ArgumentNullException ("path");
203                         }
204
205                         if (path.Length == 0) {
206                                 throw new ArgumentException ("Path is empty");
207                         }
208
209                         // ignore the Inheritable flag
210                         share &= ~FileShare.Inheritable;
211
212                         if (bufferSize <= 0)
213                                 throw new ArgumentOutOfRangeException ("bufferSize", "Positive number required.");
214
215                         if (mode < FileMode.CreateNew || mode > FileMode.Append) {
216 #if NET_2_1
217                                 if (anonymous)
218                                         throw new ArgumentException ("mode", "Enum value was out of legal range.");
219                                 else
220 #endif
221                                 throw new ArgumentOutOfRangeException ("mode", "Enum value was out of legal range.");
222                         }
223
224                         if (access < FileAccess.Read || access > FileAccess.ReadWrite) {
225 #if NET_2_1
226                                 if (anonymous)
227                                         throw new IsolatedStorageException ("Enum value for FileAccess was out of legal range.");
228                                 else
229 #endif
230                                 throw new ArgumentOutOfRangeException ("access", "Enum value was out of legal range.");
231                         }
232
233                         if (share < FileShare.None || share > (FileShare.ReadWrite | FileShare.Delete)) {
234 #if NET_2_1
235                                 if (anonymous)
236                                         throw new IsolatedStorageException ("Enum value for FileShare was out of legal range.");
237                                 else
238 #endif
239                                 throw new ArgumentOutOfRangeException ("share", "Enum value was out of legal range.");
240                         }
241
242                         if (path.IndexOfAny (Path.InvalidPathChars) != -1) {
243                                 throw new ArgumentException ("Name has invalid chars");
244                         }
245
246                         if (Directory.Exists (path)) {
247                                 // don't leak the path information for isolated storage
248                                 string msg = Locale.GetText ("Access to the path '{0}' is denied.");
249                                 throw new UnauthorizedAccessException (String.Format (msg, GetSecureFileName (path, false)));
250                         }
251
252                         /* Append streams can't be read (see FileMode
253                          * docs)
254                          */
255                         if (mode==FileMode.Append &&
256                                 (access&FileAccess.Read)==FileAccess.Read) {
257                                 throw new ArgumentException("Append access can be requested only in write-only mode.");
258                         }
259
260                         if ((access & FileAccess.Write) == 0 &&
261                                 (mode != FileMode.Open && mode != FileMode.OpenOrCreate)) {
262                                 string msg = Locale.GetText ("Combining FileMode: {0} with " +
263                                         "FileAccess: {1} is invalid.");
264                                 throw new ArgumentException (string.Format (msg, access, mode));
265                         }
266
267                         SecurityManager.EnsureElevatedPermissions (); // this is a no-op outside moonlight
268
269                         string dname = Path.GetDirectoryName (path);
270                         if (dname.Length > 0) {
271                                 string fp = Path.GetFullPath (dname);
272                                 if (!Directory.Exists (fp)) {
273                                         // don't leak the path information for isolated storage
274                                         string msg = Locale.GetText ("Could not find a part of the path \"{0}\".");
275                                         string fname = (anonymous) ? dname : Path.GetFullPath (path);
276 #if NET_2_1
277                                         // don't use GetSecureFileName for the directory name
278                                         throw new IsolatedStorageException (String.Format (msg, fname));
279 #else
280                                         throw new DirectoryNotFoundException (String.Format (msg, fname));
281 #endif
282                                 }
283                         }
284
285                         if (access == FileAccess.Read && mode != FileMode.Create && mode != FileMode.OpenOrCreate &&
286                                         mode != FileMode.CreateNew && !File.Exists (path)) {
287                                 // don't leak the path information for isolated storage
288                                 string msg = Locale.GetText ("Could not find file \"{0}\".");
289                                 string fname = GetSecureFileName (path);
290 #if NET_2_1
291                                 throw new IsolatedStorageException (String.Format (msg, fname));
292 #else
293                                 throw new FileNotFoundException (String.Format (msg, fname), fname);
294 #endif
295                         }
296
297                         // IsolatedStorage needs to keep the Name property to the default "[Unknown]"
298                         if (!anonymous)
299                                 this.name = path;
300
301                         // TODO: demand permissions
302
303                         MonoIOError error;
304
305                         this.handle = MonoIO.Open (path, mode, access, share, options, out error);
306                         if (handle == MonoIO.InvalidHandle) {
307                                 // don't leak the path information for isolated storage
308                                 throw MonoIO.GetException (GetSecureFileName (path), error);
309                         }
310
311                         this.access = access;
312                         this.owner = true;
313                         this.anonymous = anonymous;
314
315                         /* Can we open non-files by name? */
316                         
317                         if (MonoIO.GetFileType (handle, out error) == MonoFileType.Disk) {
318                                 this.canseek = true;
319                                 this.async = (options & FileOptions.Asynchronous) != 0;
320                         } else {
321                                 this.canseek = false;
322                                 this.async = false;
323                         }
324
325
326                         if (access == FileAccess.Read && canseek && (bufferSize == DefaultBufferSize)) {
327                                 /* Avoid allocating a large buffer for small files */
328                                 long len = Length;
329                                 if (bufferSize > len) {
330                                         bufferSize = (int)(len < 1000 ? 1000 : len);
331                                 }
332                         }
333
334                         InitBuffer (bufferSize);
335
336                         if (mode==FileMode.Append) {
337                                 this.Seek (0, SeekOrigin.End);
338                                 this.append_startpos=this.Position;
339                         } else {
340                                 this.append_startpos=0;
341                         }
342                 }
343
344                 // properties
345                 
346                 public override bool CanRead {
347                         get {
348                                 return access == FileAccess.Read ||
349                                        access == FileAccess.ReadWrite;
350                         }
351                 }
352
353                 public override bool CanWrite {
354                         get {
355                                 return access == FileAccess.Write ||
356                                         access == FileAccess.ReadWrite;
357                         }
358                 }
359                 
360                 public override bool CanSeek {
361                         get {
362                                 return(canseek);
363                         }
364                 }
365
366                 public virtual bool IsAsync {
367                         get {
368                                 return (async);
369                         }
370                 }
371
372                 public string Name {
373                         get {
374 #if MOONLIGHT
375                                 return SecurityManager.CheckElevatedPermissions () ? name : "[Unknown]";
376 #else
377                                 return name;
378 #endif
379                         }
380                 }
381
382                 public override long Length {
383                         get {
384                                 if (handle == MonoIO.InvalidHandle)
385                                         throw new ObjectDisposedException ("Stream has been closed");
386
387                                 if (!CanSeek)
388                                         throw new NotSupportedException ("The stream does not support seeking");
389
390                                 // Buffered data might change the length of the stream
391                                 FlushBufferIfDirty ();
392
393                                 MonoIOError error;
394                                 long length;
395                                 
396                                 length = MonoIO.GetLength (handle, out error);
397                                 if (error != MonoIOError.ERROR_SUCCESS) {
398                                         // don't leak the path information for isolated storage
399                                         throw MonoIO.GetException (GetSecureFileName (name), error);
400                                 }
401
402                                 return(length);
403                         }
404                 }
405
406                 public override long Position {
407                         get {
408                                 if (handle == MonoIO.InvalidHandle)
409                                         throw new ObjectDisposedException ("Stream has been closed");
410
411                                 if(CanSeek == false)
412                                         throw new NotSupportedException("The stream does not support seeking");
413                                 
414                                 return(buf_start + buf_offset);
415                         }
416                         set {
417                                 if (handle == MonoIO.InvalidHandle)
418                                         throw new ObjectDisposedException ("Stream has been closed");
419
420                                 if(CanSeek == false) {
421                                         throw new NotSupportedException("The stream does not support seeking");
422                                 }
423
424                                 if(value < 0) {
425                                         throw new ArgumentOutOfRangeException("Attempt to set the position to a negative value");
426                                 }
427                                 
428                                 Seek (value, SeekOrigin.Begin);
429                         }
430                 }
431
432                 [Obsolete ("Use SafeFileHandle instead")]
433                 public virtual IntPtr Handle {
434                         [SecurityPermission (SecurityAction.LinkDemand, UnmanagedCode = true)]
435                         [SecurityPermission (SecurityAction.InheritanceDemand, UnmanagedCode = true)]
436                         get {
437                                 return handle;
438                         }
439                 }
440
441                 public virtual SafeFileHandle SafeFileHandle {
442                         [SecurityPermission (SecurityAction.LinkDemand, UnmanagedCode = true)]
443                         [SecurityPermission (SecurityAction.InheritanceDemand, UnmanagedCode = true)]
444                         get {
445                                 SafeFileHandle ret;
446
447                                 if (safeHandle != null)
448                                         ret = safeHandle;
449                                 else
450                                         ret = new SafeFileHandle (handle, owner);
451
452                                 FlushBuffer ();
453                                 return ret;
454                         }
455                 }
456
457                 // methods
458
459                 public override int ReadByte ()
460                 {
461                         if (handle == MonoIO.InvalidHandle)
462                                 throw new ObjectDisposedException ("Stream has been closed");
463
464                         if (!CanRead)
465                                 throw new NotSupportedException ("Stream does not support reading");
466                         
467                         if (buf_size == 0) {
468                                 int n = ReadData (handle, buf, 0, 1);
469                                 if (n == 0) return -1;
470                                 else return buf[0];
471                         }
472                         else if (buf_offset >= buf_length) {
473                                 RefillBuffer ();
474
475                                 if (buf_length == 0)
476                                         return -1;
477                         }
478                         
479                         return buf [buf_offset ++];
480                 }
481
482                 public override void WriteByte (byte value)
483                 {
484                         if (handle == MonoIO.InvalidHandle)
485                                 throw new ObjectDisposedException ("Stream has been closed");
486
487                         if (!CanWrite)
488                                 throw new NotSupportedException ("Stream does not support writing");
489
490                         if (buf_offset == buf_size)
491                                 FlushBuffer ();
492
493                         if (buf_size == 0) { // No buffering
494                                 buf [0] = value;
495                                 buf_dirty = true;
496                                 buf_length = 1;
497                                 FlushBuffer ();
498                                 return;
499                         }
500
501                         buf [buf_offset ++] = value;
502                         if (buf_offset > buf_length)
503                                 buf_length = buf_offset;
504
505                         buf_dirty = true;
506                 }
507
508                 public override int Read ([In,Out] byte[] array, int offset, int count)
509                 {
510                         if (handle == MonoIO.InvalidHandle)
511                                 throw new ObjectDisposedException ("Stream has been closed");
512                         if (array == null)
513                                 throw new ArgumentNullException ("array");
514                         if (!CanRead)
515                                 throw new NotSupportedException ("Stream does not support reading");
516                         int len = array.Length;
517                         if (offset < 0)
518                                 throw new ArgumentOutOfRangeException ("offset", "< 0");
519                         if (count < 0)
520                                 throw new ArgumentOutOfRangeException ("count", "< 0");
521                         if (offset > len)
522                                 throw new ArgumentException ("destination offset is beyond array size");
523                         // reordered to avoid possible integer overflow
524                         if (offset > len - count)
525                                 throw new ArgumentException ("Reading would overrun buffer");
526
527                         if (async) {
528                                 IAsyncResult ares = BeginRead (array, offset, count, null, null);
529                                 return EndRead (ares);
530                         }
531
532                         return ReadInternal (array, offset, count);
533                 }
534
535                 int ReadInternal (byte [] dest, int offset, int count)
536                 {
537                         int copied = 0;
538
539                         int n = ReadSegment (dest, offset, count);
540                         copied += n;
541                         count -= n;
542                         
543                         if (count == 0) {
544                                 /* If there was already enough
545                                  * buffered, no need to read
546                                  * more from the file.
547                                  */
548                                 return (copied);
549                         }
550
551                         if (count > buf_size) {
552                                 /* Read as much as we can, up
553                                  * to count bytes
554                                  */
555                                 FlushBuffer();
556                                 n = ReadData (handle, dest,
557                                               offset+copied,
558                                               count);
559                         
560                                 /* Make the next buffer read
561                                  * start from the right place
562                                  */
563                                 buf_start += n;
564                         } else {
565                                 RefillBuffer ();
566                                 n = ReadSegment (dest,
567                                                  offset+copied,
568                                                  count);
569                         }
570
571                         copied += n;
572
573                         return copied;
574                 }
575
576                 delegate int ReadDelegate (byte [] buffer, int offset, int count);
577
578                 public override IAsyncResult BeginRead (byte [] array, int offset, int numBytes,
579                                                         AsyncCallback userCallback, object stateObject)
580                 {
581                         if (handle == MonoIO.InvalidHandle)
582                                 throw new ObjectDisposedException ("Stream has been closed");
583
584                         if (!CanRead)
585                                 throw new NotSupportedException ("This stream does not support reading");
586
587                         if (array == null)
588                                 throw new ArgumentNullException ("array");
589
590                         if (numBytes < 0)
591                                 throw new ArgumentOutOfRangeException ("numBytes", "Must be >= 0");
592
593                         if (offset < 0)
594                                 throw new ArgumentOutOfRangeException ("offset", "Must be >= 0");
595
596                         // reordered to avoid possible integer overflow
597                         if (numBytes > array.Length - offset)
598                                 throw new ArgumentException ("Buffer too small. numBytes/offset wrong.");
599
600                         if (!async)
601                                 return base.BeginRead (array, offset, numBytes, userCallback, stateObject);
602
603                         ReadDelegate r = new ReadDelegate (ReadInternal);
604                         return r.BeginInvoke (array, offset, numBytes, userCallback, stateObject);
605                 }
606                 
607                 public override int EndRead (IAsyncResult asyncResult)
608                 {
609                         if (asyncResult == null)
610                                 throw new ArgumentNullException ("asyncResult");
611
612                         if (!async)
613                                 return base.EndRead (asyncResult);
614
615                         AsyncResult ares = asyncResult as AsyncResult;
616                         if (ares == null)
617                                 throw new ArgumentException ("Invalid IAsyncResult", "asyncResult");
618
619                         ReadDelegate r = ares.AsyncDelegate as ReadDelegate;
620                         if (r == null)
621                                 throw new ArgumentException ("Invalid IAsyncResult", "asyncResult");
622
623                         return r.EndInvoke (asyncResult);
624                 }
625
626                 public override void Write (byte[] array, int offset, int count)
627                 {
628                         if (handle == MonoIO.InvalidHandle)
629                                 throw new ObjectDisposedException ("Stream has been closed");
630                         if (array == null)
631                                 throw new ArgumentNullException ("array");
632                         if (offset < 0)
633                                 throw new ArgumentOutOfRangeException ("offset", "< 0");
634                         if (count < 0)
635                                 throw new ArgumentOutOfRangeException ("count", "< 0");
636                         // ordered to avoid possible integer overflow
637                         if (offset > array.Length - count)
638                                 throw new ArgumentException ("Reading would overrun buffer");
639                         if (!CanWrite)
640                                 throw new NotSupportedException ("Stream does not support writing");
641
642                         if (async) {
643                                 IAsyncResult ares = BeginWrite (array, offset, count, null, null);
644                                 EndWrite (ares);
645                                 return;
646                         }
647
648                         WriteInternal (array, offset, count);
649                 }
650
651                 void WriteInternal (byte [] src, int offset, int count)
652                 {
653                         if (count > buf_size) {
654                                 // shortcut for long writes
655                                 MonoIOError error;
656
657                                 FlushBuffer ();
658                                 int wcount = count;
659                                 
660                                 while (wcount > 0){
661                                         int n = MonoIO.Write (handle, src, offset, wcount, out error);
662                                         if (error != MonoIOError.ERROR_SUCCESS)
663                                                 throw MonoIO.GetException (GetSecureFileName (name), error);
664                                         
665                                         wcount -= n;
666                                         offset += n;
667                                 } 
668                                 buf_start += count;
669                         } else {
670
671                                 int copied = 0;
672                                 while (count > 0) {
673                                         
674                                         int n = WriteSegment (src, offset + copied, count);
675                                         copied += n;
676                                         count -= n;
677
678                                         if (count == 0) {
679                                                 break;
680                                         }
681
682                                         FlushBuffer ();
683                                 }
684                         }
685                 }
686
687                 delegate void WriteDelegate (byte [] buffer, int offset, int count);
688
689                 public override IAsyncResult BeginWrite (byte [] array, int offset, int numBytes,
690                                                         AsyncCallback userCallback, object stateObject)
691                 {
692                         if (handle == MonoIO.InvalidHandle)
693                                 throw new ObjectDisposedException ("Stream has been closed");
694
695                         if (!CanWrite)
696                                 throw new NotSupportedException ("This stream does not support writing");
697
698                         if (array == null)
699                                 throw new ArgumentNullException ("array");
700
701                         if (numBytes < 0)
702                                 throw new ArgumentOutOfRangeException ("numBytes", "Must be >= 0");
703
704                         if (offset < 0)
705                                 throw new ArgumentOutOfRangeException ("offset", "Must be >= 0");
706
707                         // reordered to avoid possible integer overflow
708                         if (numBytes > array.Length - offset)
709                                 throw new ArgumentException ("array too small. numBytes/offset wrong.");
710
711                         if (!async)
712                                 return base.BeginWrite (array, offset, numBytes, userCallback, stateObject);
713
714                         FileStreamAsyncResult result = new FileStreamAsyncResult (userCallback, stateObject);
715                         result.BytesRead = -1;
716                         result.Count = numBytes;
717                         result.OriginalCount = numBytes;
718
719                         if (buf_dirty) {
720                                 MemoryStream ms = new MemoryStream ();
721                                 FlushBuffer (ms);
722                                 ms.Write (array, offset, numBytes);
723                                 offset = 0;
724                                 numBytes = (int) ms.Length;
725                         }
726
727                         WriteDelegate w = new WriteDelegate (WriteInternal);
728                         return w.BeginInvoke (array, offset, numBytes, userCallback, stateObject);                      
729                 }
730                 
731                 public override void EndWrite (IAsyncResult asyncResult)
732                 {
733                         if (asyncResult == null)
734                                 throw new ArgumentNullException ("asyncResult");
735
736                         if (!async) {
737                                 base.EndWrite (asyncResult);
738                                 return;
739                         }
740
741                         AsyncResult ares = asyncResult as AsyncResult;
742                         if (ares == null)
743                                 throw new ArgumentException ("Invalid IAsyncResult", "asyncResult");
744
745                         WriteDelegate w = ares.AsyncDelegate as WriteDelegate;
746                         if (w == null)
747                                 throw new ArgumentException ("Invalid IAsyncResult", "asyncResult");
748
749                         w.EndInvoke (asyncResult);
750                         return;
751                 }
752
753                 public override long Seek (long offset, SeekOrigin origin)
754                 {
755                         long pos;
756
757                         if (handle == MonoIO.InvalidHandle)
758                                 throw new ObjectDisposedException ("Stream has been closed");
759                         
760                         // make absolute
761
762                         if(CanSeek == false) {
763                                 throw new NotSupportedException("The stream does not support seeking");
764                         }
765
766                         switch (origin) {
767                         case SeekOrigin.End:
768                                 pos = Length + offset;
769                                 break;
770
771                         case SeekOrigin.Current:
772                                 pos = Position + offset;
773                                 break;
774
775                         case SeekOrigin.Begin:
776                                 pos = offset;
777                                 break;
778
779                         default:
780                                 throw new ArgumentException ("origin", "Invalid SeekOrigin");
781                         }
782
783                         if (pos < 0) {
784                                 /* LAMESPEC: shouldn't this be
785                                  * ArgumentOutOfRangeException?
786                                  */
787                                 throw new IOException("Attempted to Seek before the beginning of the stream");
788                         }
789
790                         if(pos < this.append_startpos) {
791                                 /* More undocumented crap */
792                                 throw new IOException("Can't seek back over pre-existing data in append mode");
793                         }
794
795                         FlushBuffer ();
796
797                         MonoIOError error;
798                 
799                         buf_start = MonoIO.Seek (handle, pos,
800                                                  SeekOrigin.Begin,
801                                                  out error);
802
803                         if (error != MonoIOError.ERROR_SUCCESS) {
804                                 // don't leak the path information for isolated storage
805                                 throw MonoIO.GetException (GetSecureFileName (name), error);
806                         }
807                         
808                         return(buf_start);
809                 }
810
811                 public override void SetLength (long value)
812                 {
813                         if (handle == MonoIO.InvalidHandle)
814                                 throw new ObjectDisposedException ("Stream has been closed");
815
816                         if(CanSeek == false)
817                                 throw new NotSupportedException("The stream does not support seeking");
818
819                         if(CanWrite == false)
820                                 throw new NotSupportedException("The stream does not support writing");
821
822                         if(value < 0)
823                                 throw new ArgumentOutOfRangeException("value is less than 0");
824                         
825                         Flush ();
826
827                         MonoIOError error;
828                         
829                         MonoIO.SetLength (handle, value, out error);
830                         if (error != MonoIOError.ERROR_SUCCESS) {
831                                 // don't leak the path information for isolated storage
832                                 throw MonoIO.GetException (GetSecureFileName (name), error);
833                         }
834
835                         if (Position > value)
836                                 Position = value;
837                 }
838
839                 public override void Flush ()
840                 {
841                         if (handle == MonoIO.InvalidHandle)
842                                 throw new ObjectDisposedException ("Stream has been closed");
843
844                         FlushBuffer ();
845                 }
846
847 #if NET_4_0
848                 public virtual void Flush (bool flushToDisk)
849                 {
850                         FlushBuffer ();
851
852                         // This does the fsync
853                         if (flushToDisk){
854                                 MonoIOError error;
855                                 MonoIO.Flush (handle, out error);
856                         }
857                 }
858 #endif
859
860                 public virtual void Lock (long position, long length)
861                 {
862                         if (handle == MonoIO.InvalidHandle)
863                                 throw new ObjectDisposedException ("Stream has been closed");
864                         if (position < 0) {
865                                 throw new ArgumentOutOfRangeException ("position must not be negative");
866                         }
867                         if (length < 0) {
868                                 throw new ArgumentOutOfRangeException ("length must not be negative");
869                         }
870                         if (handle == MonoIO.InvalidHandle) {
871                                 throw new ObjectDisposedException ("Stream has been closed");
872                         }
873                                 
874                         MonoIOError error;
875
876                         MonoIO.Lock (handle, position, length, out error);
877                         if (error != MonoIOError.ERROR_SUCCESS) {
878                                 // don't leak the path information for isolated storage
879                                 throw MonoIO.GetException (GetSecureFileName (name), error);
880                         }
881                 }
882
883                 public virtual void Unlock (long position, long length)
884                 {
885                         if (handle == MonoIO.InvalidHandle)
886                                 throw new ObjectDisposedException ("Stream has been closed");
887                         if (position < 0) {
888                                 throw new ArgumentOutOfRangeException ("position must not be negative");
889                         }
890                         if (length < 0) {
891                                 throw new ArgumentOutOfRangeException ("length must not be negative");
892                         }
893                                 
894                         MonoIOError error;
895
896                         MonoIO.Unlock (handle, position, length, out error);
897                         if (error != MonoIOError.ERROR_SUCCESS) {
898                                 // don't leak the path information for isolated storage
899                                 throw MonoIO.GetException (GetSecureFileName (name), error);
900                         }
901                 }
902
903                 // protected
904
905                 ~FileStream ()
906                 {
907                         Dispose (false);
908                 }
909
910                 protected override void Dispose (bool disposing)
911                 {
912                         Exception exc = null;
913                         if (handle != MonoIO.InvalidHandle) {
914                                 try {
915                                         FlushBuffer ();
916                                 } catch (Exception e) {
917                                         exc = e;
918                                 }
919
920                                 if (owner) {
921                                         MonoIOError error;
922                                 
923                                         MonoIO.Close (handle, out error);
924                                         if (error != MonoIOError.ERROR_SUCCESS) {
925                                                 // don't leak the path information for isolated storage
926                                                 throw MonoIO.GetException (GetSecureFileName (name), error);
927                                         }
928
929                                         handle = MonoIO.InvalidHandle;
930                                 }
931                         }
932
933                         canseek = false;
934                         access = 0;
935                         
936                         if (disposing && buf != null) {
937                                 if (buf.Length == DefaultBufferSize && buf_recycle == null) {
938                                         lock (buf_recycle_lock) {
939                                                 if (buf_recycle == null) {
940                                                         buf_recycle = buf;
941                                                 }
942                                         }
943                                 }
944                                 
945                                 buf = null;
946                                 GC.SuppressFinalize (this);
947                         }
948                         if (exc != null)
949                                 throw exc;
950                 }
951
952 #if !NET_2_1
953                 public FileSecurity GetAccessControl ()
954                 {
955                         throw new NotImplementedException ();
956                 }
957                 
958                 public void SetAccessControl (FileSecurity fileSecurity)
959                 {
960                         throw new NotImplementedException ();
961                 }
962 #endif
963
964                 // private.
965
966                 // ReadSegment, WriteSegment, FlushBuffer,
967                 // RefillBuffer and ReadData should only be called
968                 // when the Monitor lock is held, but these methods
969                 // grab it again just to be safe.
970
971                 private int ReadSegment (byte [] dest, int dest_offset, int count)
972                 {
973                         if (count > buf_length - buf_offset) {
974                                 count = buf_length - buf_offset;
975                         }
976                         
977                         if (count > 0) {
978                                 Buffer.BlockCopy (buf, buf_offset,
979                                                   dest, dest_offset,
980                                                   count);
981                                 buf_offset += count;
982                         }
983                         
984                         return(count);
985                 }
986
987                 private int WriteSegment (byte [] src, int src_offset,
988                                           int count)
989                 {
990                         if (count > buf_size - buf_offset) {
991                                 count = buf_size - buf_offset;
992                         }
993                         
994                         if (count > 0) {
995                                 Buffer.BlockCopy (src, src_offset,
996                                                   buf, buf_offset,
997                                                   count);
998                                 buf_offset += count;
999                                 if (buf_offset > buf_length) {
1000                                         buf_length = buf_offset;
1001                                 }
1002                                 
1003                                 buf_dirty = true;
1004                         }
1005                         
1006                         return(count);
1007                 }
1008
1009                 void FlushBuffer (Stream st)
1010                 {
1011                         if (buf_dirty) {
1012                                 MonoIOError error;
1013
1014                                 if (CanSeek == true) {
1015                                         MonoIO.Seek (handle, buf_start,
1016                                                      SeekOrigin.Begin,
1017                                                      out error);
1018                                         if (error != MonoIOError.ERROR_SUCCESS) {
1019                                                 // don't leak the path information for isolated storage
1020                                                 throw MonoIO.GetException (GetSecureFileName (name), error);
1021                                         }
1022                                 }
1023                                 if (st == null) {
1024                                         int wcount = buf_length;
1025                                         int offset = 0;
1026                                         while (wcount > 0){
1027                                                 int n = MonoIO.Write (handle, buf, 0, buf_length, out error);
1028                                                 if (error != MonoIOError.ERROR_SUCCESS) {
1029                                                         // don't leak the path information for isolated storage
1030                                                         throw MonoIO.GetException (GetSecureFileName (name), error);
1031                                                 }
1032                                                 wcount -= n;
1033                                                 offset += n;
1034                                         }
1035                                 } else {
1036                                         st.Write (buf, 0, buf_length);
1037                                 }
1038                         }
1039
1040                         buf_start += buf_offset;
1041                         buf_offset = buf_length = 0;
1042                         buf_dirty = false;
1043                 }
1044
1045                 private void FlushBuffer ()
1046                 {
1047                         FlushBuffer (null);
1048                 }
1049
1050                 private void FlushBufferIfDirty ()
1051                 {
1052                         if (buf_dirty)
1053                                 FlushBuffer (null);
1054                 }
1055
1056                 private void RefillBuffer ()
1057                 {
1058                         FlushBuffer (null);
1059                         
1060                         buf_length = ReadData (handle, buf, 0,
1061                                                buf_size);
1062                 }
1063
1064                 private int ReadData (IntPtr handle, byte[] buf, int offset,
1065                                       int count)
1066                 {
1067                         MonoIOError error;
1068                         int amount = 0;
1069
1070                         /* when async == true, if we get here we don't suport AIO or it's disabled
1071                          * and we're using the threadpool */
1072                         amount = MonoIO.Read (handle, buf, offset, count, out error);
1073                         if (error == MonoIOError.ERROR_BROKEN_PIPE) {
1074                                 amount = 0; // might not be needed, but well...
1075                         } else if (error != MonoIOError.ERROR_SUCCESS) {
1076                                 // don't leak the path information for isolated storage
1077                                 throw MonoIO.GetException (GetSecureFileName (name), error);
1078                         }
1079                         
1080                         /* Check for read error */
1081                         if(amount == -1) {
1082                                 throw new IOException ();
1083                         }
1084                         
1085                         return(amount);
1086                 }
1087                                 
1088                 void InitBuffer (int size)
1089                 {
1090                         if (size <= 0)
1091                                 throw new ArgumentOutOfRangeException ("bufferSize", "Positive number required.");
1092                         
1093                         size = Math.Max (size, 8);
1094                         
1095                         //
1096                         // Instead of allocating a new default buffer use the
1097                         // last one if there is any available
1098                         //              
1099                         if (size <= DefaultBufferSize && buf_recycle != null) {
1100                                 lock (buf_recycle_lock) {
1101                                         if (buf_recycle != null) {
1102                                                 buf = buf_recycle;
1103                                                 buf_recycle = null;
1104                                         }
1105                                 }
1106                         }
1107                         
1108                         if (buf == null)
1109                                 buf = new byte [size];
1110                         else
1111                                 Array.Clear (buf, 0, size);
1112                                         
1113                         buf_size = size;
1114 //                      buf_start = 0;
1115 //                      buf_offset = buf_length = 0;
1116 //                      buf_dirty = false;
1117                 }
1118
1119                 private string GetSecureFileName (string filename)
1120                 {
1121                         return (anonymous) ? Path.GetFileName (filename) : Path.GetFullPath (filename);
1122                 }
1123
1124                 private string GetSecureFileName (string filename, bool full)
1125                 {
1126                         return (anonymous) ? Path.GetFileName (filename) : 
1127                                 (full) ? Path.GetFullPath (filename) : filename;
1128                 }
1129
1130                 // fields
1131
1132                 internal const int DefaultBufferSize = 8192;
1133
1134                 // Input buffer ready for recycling                             
1135                 static byte[] buf_recycle;
1136                 static readonly object buf_recycle_lock = new object ();
1137
1138                 private FileAccess access;
1139                 private bool owner;
1140                 private bool async;
1141                 private bool canseek;
1142                 private long append_startpos;
1143                 private bool anonymous;
1144
1145                 private byte [] buf;                    // the buffer
1146                 private int buf_size;                   // capacity in bytes
1147                 private int buf_length;                 // number of valid bytes in buffer
1148                 private int buf_offset;                 // position of next byte
1149                 private bool buf_dirty;                 // true if buffer has been written to
1150                 private long buf_start;                 // location of buffer in file
1151                 private string name = "[Unknown]";      // name of file.
1152
1153                 IntPtr handle;                          // handle to underlying file
1154                 SafeFileHandle safeHandle;              // set only when using one of the
1155                                                         // constructors taking SafeFileHandle
1156         }
1157 }