Merge pull request #309 from i59/patch-1
[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                         return new FileSecurity (SafeFileHandle,
974                                                  AccessControlSections.Owner |
975                                                  AccessControlSections.Group |
976                                                  AccessControlSections.Access);
977                 }
978                 
979                 public void SetAccessControl (FileSecurity fileSecurity)
980                 {
981                         if (null == fileSecurity)
982                                 throw new ArgumentNullException ("fileSecurity");
983                                 
984                         fileSecurity.PersistModifications (SafeFileHandle);
985                 }
986 #endif
987
988 #if NET_4_5
989                 public override Task FlushAsync (CancellationToken cancellationToken)
990                 {
991                         return base.FlushAsync (cancellationToken);
992                 }
993
994                 public override Task<int> ReadAsync (byte[] buffer, int offset, int count, CancellationToken cancellationToken)
995                 {
996                         return base.ReadAsync (buffer, offset, count, cancellationToken);
997                 }
998
999                 public override Task WriteAsync (byte[] buffer, int offset, int count, CancellationToken cancellationToken)
1000                 {
1001                         return base.WriteAsync (buffer, offset, count, cancellationToken);
1002                 }
1003 #endif
1004
1005                 // private.
1006
1007                 // ReadSegment, WriteSegment, FlushBuffer,
1008                 // RefillBuffer and ReadData should only be called
1009                 // when the Monitor lock is held, but these methods
1010                 // grab it again just to be safe.
1011
1012                 private int ReadSegment (byte [] dest, int dest_offset, int count)
1013                 {
1014                         count = Math.Min (count, buf_length - buf_offset);
1015                         
1016                         if (count > 0) {
1017                                 // Use the fastest method, all range checks has been done
1018                                 Buffer.BlockCopyInternal (buf, buf_offset, dest, dest_offset, count);
1019                                 buf_offset += count;
1020                         }
1021                         
1022                         return count;
1023                 }
1024
1025                 private int WriteSegment (byte [] src, int src_offset,
1026                                           int count)
1027                 {
1028                         if (count > buf_size - buf_offset) {
1029                                 count = buf_size - buf_offset;
1030                         }
1031                         
1032                         if (count > 0) {
1033                                 Buffer.BlockCopy (src, src_offset,
1034                                                   buf, buf_offset,
1035                                                   count);
1036                                 buf_offset += count;
1037                                 if (buf_offset > buf_length) {
1038                                         buf_length = buf_offset;
1039                                 }
1040                                 
1041                                 buf_dirty = true;
1042                         }
1043                         
1044                         return(count);
1045                 }
1046
1047                 void FlushBuffer (Stream st)
1048                 {
1049                         if (buf_dirty) {
1050                                 MonoIOError error;
1051
1052                                 if (CanSeek == true && safeHandle == null) {
1053                                         MonoIO.Seek (handle, buf_start,
1054                                                      SeekOrigin.Begin,
1055                                                      out error);
1056                                         if (error != MonoIOError.ERROR_SUCCESS) {
1057                                                 // don't leak the path information for isolated storage
1058                                                 throw MonoIO.GetException (GetSecureFileName (name), error);
1059                                         }
1060                                 }
1061                                 if (st == null) {
1062                                         int wcount = buf_length;
1063                                         int offset = 0;
1064                                         while (wcount > 0){
1065                                                 int n = MonoIO.Write (handle, buf, 0, buf_length, out error);
1066                                                 if (error != MonoIOError.ERROR_SUCCESS) {
1067                                                         // don't leak the path information for isolated storage
1068                                                         throw MonoIO.GetException (GetSecureFileName (name), error);
1069                                                 }
1070                                                 wcount -= n;
1071                                                 offset += n;
1072                                         }
1073                                 } else {
1074                                         st.Write (buf, 0, buf_length);
1075                                 }
1076                         }
1077
1078                         buf_start += buf_offset;
1079                         buf_offset = buf_length = 0;
1080                         buf_dirty = false;
1081                 }
1082
1083                 private void FlushBuffer ()
1084                 {
1085                         FlushBuffer (null);
1086                 }
1087
1088                 private void FlushBufferIfDirty ()
1089                 {
1090                         if (buf_dirty)
1091                                 FlushBuffer (null);
1092                 }
1093
1094                 private void RefillBuffer ()
1095                 {
1096                         FlushBuffer (null);
1097                         
1098                         buf_length = ReadData (handle, buf, 0,
1099                                                buf_size);
1100                 }
1101
1102                 private int ReadData (IntPtr handle, byte[] buf, int offset,
1103                                       int count)
1104                 {
1105                         MonoIOError error;
1106                         int amount = 0;
1107
1108                         /* when async == true, if we get here we don't suport AIO or it's disabled
1109                          * and we're using the threadpool */
1110                         amount = MonoIO.Read (handle, buf, offset, count, out error);
1111                         if (error == MonoIOError.ERROR_BROKEN_PIPE) {
1112                                 amount = 0; // might not be needed, but well...
1113                         } else if (error != MonoIOError.ERROR_SUCCESS) {
1114                                 // don't leak the path information for isolated storage
1115                                 throw MonoIO.GetException (GetSecureFileName (name), error);
1116                         }
1117                         
1118                         /* Check for read error */
1119                         if(amount == -1) {
1120                                 throw new IOException ();
1121                         }
1122                         
1123                         return(amount);
1124                 }
1125                                 
1126                 void InitBuffer (int size, bool isZeroSize)
1127                 {
1128                         if (isZeroSize) {
1129                                 size = 0;
1130                                 buf = new byte[1];
1131                         } else {
1132                                 if (size <= 0)
1133                                         throw new ArgumentOutOfRangeException ("bufferSize", "Positive number required.");
1134
1135                                 size = Math.Max (size, 8);
1136
1137                                 //
1138                                 // Instead of allocating a new default buffer use the
1139                                 // last one if there is any available
1140                                 //              
1141                                 if (size <= DefaultBufferSize && buf_recycle != null) {
1142                                         lock (buf_recycle_lock) {
1143                                                 if (buf_recycle != null) {
1144                                                         buf = buf_recycle;
1145                                                         buf_recycle = null;
1146                                                 }
1147                                         }
1148                                 }
1149
1150                                 if (buf == null)
1151                                         buf = new byte [size];
1152                                 else
1153                                         Array.Clear (buf, 0, size);
1154                         }
1155                                         
1156                         buf_size = size;
1157 //                      buf_start = 0;
1158 //                      buf_offset = buf_length = 0;
1159 //                      buf_dirty = false;
1160                 }
1161
1162                 private string GetSecureFileName (string filename)
1163                 {
1164                         return (anonymous) ? Path.GetFileName (filename) : Path.GetFullPath (filename);
1165                 }
1166
1167                 private string GetSecureFileName (string filename, bool full)
1168                 {
1169                         return (anonymous) ? Path.GetFileName (filename) : 
1170                                 (full) ? Path.GetFullPath (filename) : filename;
1171                 }
1172
1173                 // fields
1174
1175                 internal const int DefaultBufferSize = 4096;
1176
1177                 // Input buffer ready for recycling                             
1178                 static byte[] buf_recycle;
1179                 static readonly object buf_recycle_lock = new object ();
1180
1181                 private byte [] buf;                    // the buffer
1182                 private string name = "[Unknown]";      // name of file.
1183
1184                 SafeFileHandle safeHandle;              // set only when using one of the
1185                                                         // constructors taking SafeFileHandle
1186
1187                 private long append_startpos;
1188                 IntPtr handle;                          // handle to underlying file
1189
1190                 private FileAccess access;
1191                 private bool owner;
1192                 private bool async;
1193                 private bool canseek;
1194                 private bool anonymous;
1195                 private bool buf_dirty;                 // true if buffer has been written to
1196
1197                 private int buf_size;                   // capacity in bytes
1198                 private int buf_length;                 // number of valid bytes in buffer
1199                 private int buf_offset;                 // position of next byte
1200                 private long buf_start;                 // location of buffer in file
1201         }
1202 }