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