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