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