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