Merge pull request #2003 from esdrubal/seq_test_fix2
[mono.git] / mcs / class / corlib / System.IO / FileStream.cs
1 //
2 // System.IO.FileStream.cs
3 //
4 // Authors:
5 //      Dietmar Maurer (dietmar@ximian.com)
6 //      Dan Lewis (dihlewis@yahoo.co.uk)
7 //      Gonzalo Paniagua Javier (gonzalo@ximian.com)
8 //  Marek Safar (marek.safar@gmail.com)
9 //
10 // (C) 2001-2003 Ximian, Inc.  http://www.ximian.com
11 // Copyright (C) 2004-2005, 2008, 2010 Novell, Inc (http://www.novell.com)
12 // Copyright 2011 Xamarin Inc (http://www.xamarin.com).
13 //
14 // Permission is hereby granted, free of charge, to any person obtaining
15 // a copy of this software and associated documentation files (the
16 // "Software"), to deal in the Software without restriction, including
17 // without limitation the rights to use, copy, modify, merge, publish,
18 // distribute, sublicense, and/or sell copies of the Software, and to
19 // permit persons to whom the Software is furnished to do so, subject to
20 // the following conditions:
21 // 
22 // The above copyright notice and this permission notice shall be
23 // included in all copies or substantial portions of the Software.
24 // 
25 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
26 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
27 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
28 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
29 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
30 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
31 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
32 //
33
34 using System.Collections;
35 using System.Globalization;
36 using System.Runtime.CompilerServices;
37 using System.Runtime.InteropServices;
38 using System.Runtime.Remoting.Messaging;
39 using System.Security;
40 using System.Security.Permissions;
41 using System.Threading;
42 using Microsoft.Win32.SafeHandles;
43
44 #if NET_2_1
45 using System.IO.IsolatedStorage;
46 #else
47 using System.Security.AccessControl;
48 #endif
49
50 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 NET_2_1
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                                 int wcount = count;
643
644                                 while (wcount > 0){
645                                         int n = MonoIO.Write (safeHandle, src, offset, wcount, out error);
646                                         if (error != MonoIOError.ERROR_SUCCESS)
647                                                 throw MonoIO.GetException (GetSecureFileName (name), error);
648
649                                         wcount -= n;
650                                         offset += n;
651                                 }
652                                 buf_start += count;
653                         } else {
654
655                                 int copied = 0;
656                                 while (count > 0) {
657                                         
658                                         int n = WriteSegment (src, offset + copied, count);
659                                         copied += n;
660                                         count -= n;
661
662                                         if (count == 0) {
663                                                 break;
664                                         }
665
666                                         FlushBuffer ();
667                                 }
668                         }
669                 }
670
671                 delegate void WriteDelegate (byte [] buffer, int offset, int count);
672
673                 public override IAsyncResult BeginWrite (byte [] array, int offset, int numBytes,
674                                                         AsyncCallback userCallback, object stateObject)
675                 {
676                         if (safeHandle.IsClosed)
677                                 throw new ObjectDisposedException ("Stream has been closed");
678
679                         if (!CanWrite)
680                                 throw new NotSupportedException ("This stream does not support writing");
681
682                         if (array == null)
683                                 throw new ArgumentNullException ("array");
684
685                         if (numBytes < 0)
686                                 throw new ArgumentOutOfRangeException ("numBytes", "Must be >= 0");
687
688                         if (offset < 0)
689                                 throw new ArgumentOutOfRangeException ("offset", "Must be >= 0");
690
691                         // reordered to avoid possible integer overflow
692                         if (numBytes > array.Length - offset)
693                                 throw new ArgumentException ("array too small. numBytes/offset wrong.");
694
695                         if (!async)
696                                 return base.BeginWrite (array, offset, numBytes, userCallback, stateObject);
697
698                         FileStreamAsyncResult result = new FileStreamAsyncResult (userCallback, stateObject);
699                         result.BytesRead = -1;
700                         result.Count = numBytes;
701                         result.OriginalCount = numBytes;
702 /*
703                         if (buf_dirty) {
704                                 MemoryStream ms = new MemoryStream ();
705                                 FlushBuffer (ms);
706                                 ms.Write (array, offset, numBytes);
707
708                                 // Set arguments to new compounded buffer 
709                                 offset = 0;
710                                 array = ms.ToArray ();
711                                 numBytes = array.Length;
712                         }
713 */
714                         WriteDelegate w = WriteInternal;
715                         return w.BeginInvoke (array, offset, numBytes, userCallback, stateObject);      
716                 }
717                 
718                 public override void EndWrite (IAsyncResult asyncResult)
719                 {
720                         if (asyncResult == null)
721                                 throw new ArgumentNullException ("asyncResult");
722
723                         if (!async) {
724                                 base.EndWrite (asyncResult);
725                                 return;
726                         }
727
728                         AsyncResult ares = asyncResult as AsyncResult;
729                         if (ares == null)
730                                 throw new ArgumentException ("Invalid IAsyncResult", "asyncResult");
731
732                         WriteDelegate w = ares.AsyncDelegate as WriteDelegate;
733                         if (w == null)
734                                 throw new ArgumentException ("Invalid IAsyncResult", "asyncResult");
735
736                         w.EndInvoke (asyncResult);
737                         return;
738                 }
739
740                 public override long Seek (long offset, SeekOrigin origin)
741                 {
742                         long pos;
743
744                         if (safeHandle.IsClosed)
745                                 throw new ObjectDisposedException ("Stream has been closed");
746                         
747                         // make absolute
748
749                         if(CanSeek == false) {
750                                 throw new NotSupportedException("The stream does not support seeking");
751                         }
752
753                         switch (origin) {
754                         case SeekOrigin.End:
755                                 pos = Length + offset;
756                                 break;
757
758                         case SeekOrigin.Current:
759                                 pos = Position + offset;
760                                 break;
761
762                         case SeekOrigin.Begin:
763                                 pos = offset;
764                                 break;
765
766                         default:
767                                 throw new ArgumentException ("origin", "Invalid SeekOrigin");
768                         }
769
770                         if (pos < 0) {
771                                 /* LAMESPEC: shouldn't this be
772                                  * ArgumentOutOfRangeException?
773                                  */
774                                 throw new IOException("Attempted to Seek before the beginning of the stream");
775                         }
776
777                         if(pos < this.append_startpos) {
778                                 /* More undocumented crap */
779                                 throw new IOException("Can't seek back over pre-existing data in append mode");
780                         }
781
782                         FlushBuffer ();
783
784                         MonoIOError error;
785
786                         buf_start = MonoIO.Seek (safeHandle, pos, SeekOrigin.Begin, out error);
787
788                         if (error != MonoIOError.ERROR_SUCCESS) {
789                                 // don't leak the path information for isolated storage
790                                 throw MonoIO.GetException (GetSecureFileName (name), error);
791                         }
792                         
793                         return(buf_start);
794                 }
795
796                 public override void SetLength (long value)
797                 {
798                         if (safeHandle.IsClosed)
799                                 throw new ObjectDisposedException ("Stream has been closed");
800
801                         if(CanSeek == false)
802                                 throw new NotSupportedException("The stream does not support seeking");
803
804                         if(CanWrite == false)
805                                 throw new NotSupportedException("The stream does not support writing");
806
807                         if(value < 0)
808                                 throw new ArgumentOutOfRangeException("value is less than 0");
809                         
810                         FlushBuffer ();
811
812                         MonoIOError error;
813
814                         MonoIO.SetLength (safeHandle, value, out error);
815                         if (error != MonoIOError.ERROR_SUCCESS) {
816                                 // don't leak the path information for isolated storage
817                                 throw MonoIO.GetException (GetSecureFileName (name), error);
818                         }
819
820                         if (Position > value)
821                                 Position = value;
822                 }
823
824                 public override void Flush ()
825                 {
826                         if (safeHandle.IsClosed)
827                                 throw new ObjectDisposedException ("Stream has been closed");
828
829                         FlushBuffer ();
830                 }
831
832                 public virtual void Flush (bool flushToDisk)
833                 {
834                         if (safeHandle.IsClosed)
835                                 throw new ObjectDisposedException ("Stream has been closed");
836
837                         FlushBuffer ();
838
839                         // This does the fsync
840                         if (flushToDisk){
841                                 MonoIOError error;
842                                 MonoIO.Flush (safeHandle, out error);
843                         }
844                 }
845
846                 public virtual void Lock (long position, long length)
847                 {
848                         if (safeHandle.IsClosed)
849                                 throw new ObjectDisposedException ("Stream has been closed");
850                         if (position < 0) {
851                                 throw new ArgumentOutOfRangeException ("position must not be negative");
852                         }
853                         if (length < 0) {
854                                 throw new ArgumentOutOfRangeException ("length must not be negative");
855                         }
856
857                         MonoIOError error;
858
859                         MonoIO.Lock (safeHandle, position, length, out error);
860                         if (error != MonoIOError.ERROR_SUCCESS) {
861                                 // don't leak the path information for isolated storage
862                                 throw MonoIO.GetException (GetSecureFileName (name), error);
863                         }
864                 }
865
866                 public virtual void Unlock (long position, long length)
867                 {
868                         if (safeHandle.IsClosed)
869                                 throw new ObjectDisposedException ("Stream has been closed");
870                         if (position < 0) {
871                                 throw new ArgumentOutOfRangeException ("position must not be negative");
872                         }
873                         if (length < 0) {
874                                 throw new ArgumentOutOfRangeException ("length must not be negative");
875                         }
876
877                         MonoIOError error;
878
879                         MonoIO.Unlock (safeHandle, position, length, out error);
880                         if (error != MonoIOError.ERROR_SUCCESS) {
881                                 // don't leak the path information for isolated storage
882                                 throw MonoIO.GetException (GetSecureFileName (name), error);
883                         }
884                 }
885
886                 // protected
887
888                 ~FileStream ()
889                 {
890                         Dispose (false);
891                 }
892
893                 protected override void Dispose (bool disposing)
894                 {
895                         Exception exc = null;
896                         if (safeHandle != null && !safeHandle.IsClosed) {
897                                 try {
898                                         // If the FileStream is in "exposed" status
899                                         // it means that we do not have a buffer(we write the data without buffering)
900                                         // therefor we don't and can't flush the buffer becouse we don't have one.
901                                         FlushBuffer ();
902                                 } catch (Exception e) {
903                                         exc = e;
904                                 }
905
906                                 if (owner) {
907                                         MonoIOError error;
908
909                                         MonoIO.Close (safeHandle.DangerousGetHandle (), out error);
910                                         if (error != MonoIOError.ERROR_SUCCESS) {
911                                                 // don't leak the path information for isolated storage
912                                                 throw MonoIO.GetException (GetSecureFileName (name), error);
913                                         }
914
915                                         safeHandle.DangerousRelease ();
916                                 }
917                         }
918
919                         canseek = false;
920                         access = 0;
921                         
922                         if (disposing && buf != null) {
923                                 if (buf.Length == DefaultBufferSize && buf_recycle == null) {
924                                         lock (buf_recycle_lock) {
925                                                 if (buf_recycle == null) {
926                                                         buf_recycle = buf;
927                                                 }
928                                         }
929                                 }
930                                 
931                                 buf = null;
932                                 GC.SuppressFinalize (this);
933                         }
934                         if (exc != null)
935                                 throw exc;
936                 }
937
938 #if !NET_2_1
939                 public FileSecurity GetAccessControl ()
940                 {
941                         if (safeHandle.IsClosed)
942                                 throw new ObjectDisposedException ("Stream has been closed");
943
944                         return new FileSecurity (SafeFileHandle,
945                                                  AccessControlSections.Owner |
946                                                  AccessControlSections.Group |
947                                                  AccessControlSections.Access);
948                 }
949                 
950                 public void SetAccessControl (FileSecurity fileSecurity)
951                 {
952                         if (safeHandle.IsClosed)
953                                 throw new ObjectDisposedException ("Stream has been closed");
954
955                         if (null == fileSecurity)
956                                 throw new ArgumentNullException ("fileSecurity");
957                                 
958                         fileSecurity.PersistModifications (SafeFileHandle);
959                 }
960 #endif
961
962                 public override Task FlushAsync (CancellationToken cancellationToken)
963                 {
964                         if (safeHandle.IsClosed)
965                                 throw new ObjectDisposedException ("Stream has been closed");
966
967                         return base.FlushAsync (cancellationToken);
968                 }
969
970                 public override Task<int> ReadAsync (byte[] buffer, int offset, int count, CancellationToken cancellationToken)
971                 {
972                         return base.ReadAsync (buffer, offset, count, cancellationToken);
973                 }
974
975                 public override Task WriteAsync (byte[] buffer, int offset, int count, CancellationToken cancellationToken)
976                 {
977                         return base.WriteAsync (buffer, offset, count, cancellationToken);
978                 }
979
980                 // private.
981
982                 // ReadSegment, WriteSegment, FlushBuffer,
983                 // RefillBuffer and ReadData should only be called
984                 // when the Monitor lock is held, but these methods
985                 // grab it again just to be safe.
986
987                 private int ReadSegment (byte [] dest, int dest_offset, int count)
988                 {
989                         count = Math.Min (count, buf_length - buf_offset);
990                         
991                         if (count > 0) {
992                                 // Use the fastest method, all range checks has been done
993                                 Buffer.InternalBlockCopy (buf, buf_offset, dest, dest_offset, count);
994                                 buf_offset += count;
995                         }
996                         
997                         return count;
998                 }
999
1000                 private int WriteSegment (byte [] src, int src_offset,
1001                                           int count)
1002                 {
1003                         if (count > buf_size - buf_offset) {
1004                                 count = buf_size - buf_offset;
1005                         }
1006                         
1007                         if (count > 0) {
1008                                 Buffer.BlockCopy (src, src_offset,
1009                                                   buf, buf_offset,
1010                                                   count);
1011                                 buf_offset += count;
1012                                 if (buf_offset > buf_length) {
1013                                         buf_length = buf_offset;
1014                                 }
1015                                 
1016                                 buf_dirty = true;
1017                         }
1018                         
1019                         return(count);
1020                 }
1021
1022                 void FlushBuffer ()
1023                 {
1024                         if (buf_dirty) {
1025 //                              if (st == null) {
1026                                         MonoIOError error;
1027
1028                                         if (CanSeek == true && !isExposed) {
1029                                                 MonoIO.Seek (safeHandle, buf_start, SeekOrigin.Begin, out error);
1030
1031                                                 if (error != MonoIOError.ERROR_SUCCESS) {
1032                                                         // don't leak the path information for isolated storage
1033                                                         throw MonoIO.GetException (GetSecureFileName (name), error);
1034                                                 }
1035                                         }
1036
1037                                         int wcount = buf_length;
1038                                         int offset = 0;
1039                                         while (wcount > 0){
1040                                                 int n = MonoIO.Write (safeHandle, buf, 0, buf_length, out error);
1041                                                 if (error != MonoIOError.ERROR_SUCCESS) {
1042                                                         // don't leak the path information for isolated storage
1043                                                         throw MonoIO.GetException (GetSecureFileName (name), error);
1044                                                 }
1045                                                 wcount -= n;
1046                                                 offset += n;
1047                                         }
1048 //                              } else {
1049 //                                      st.Write (buf, 0, buf_length);
1050 //                              }
1051                         }
1052
1053                         buf_start += buf_offset;
1054                         buf_offset = buf_length = 0;
1055                         buf_dirty = false;
1056                 }
1057
1058                 private void FlushBufferIfDirty ()
1059                 {
1060                         if (buf_dirty)
1061                                 FlushBuffer ();
1062                 }
1063
1064                 private void RefillBuffer ()
1065                 {
1066                         FlushBuffer ();
1067
1068                         buf_length = ReadData (safeHandle, buf, 0, buf_size);
1069                 }
1070
1071                 private int ReadData (SafeHandle safeHandle, byte[] buf, int offset,
1072                                       int count)
1073                 {
1074                         MonoIOError error;
1075                         int amount = 0;
1076
1077                         /* when async == true, if we get here we don't suport AIO or it's disabled
1078                          * and we're using the threadpool */
1079                         amount = MonoIO.Read (safeHandle, buf, offset, count, out error);
1080                         if (error == MonoIOError.ERROR_BROKEN_PIPE) {
1081                                 amount = 0; // might not be needed, but well...
1082                         } else if (error != MonoIOError.ERROR_SUCCESS) {
1083                                 // don't leak the path information for isolated storage
1084                                 throw MonoIO.GetException (GetSecureFileName (name), error);
1085                         }
1086                         
1087                         /* Check for read error */
1088                         if(amount == -1) {
1089                                 throw new IOException ();
1090                         }
1091                         
1092                         return(amount);
1093                 }
1094                                 
1095                 void InitBuffer (int size, bool isZeroSize)
1096                 {
1097                         if (isZeroSize) {
1098                                 size = 0;
1099                                 buf = new byte[1];
1100                         } else {
1101                                 if (size <= 0)
1102                                         throw new ArgumentOutOfRangeException ("bufferSize", "Positive number required.");
1103
1104                                 size = Math.Max (size, 8);
1105
1106                                 //
1107                                 // Instead of allocating a new default buffer use the
1108                                 // last one if there is any available
1109                                 //              
1110                                 if (size <= DefaultBufferSize && buf_recycle != null) {
1111                                         lock (buf_recycle_lock) {
1112                                                 if (buf_recycle != null) {
1113                                                         buf = buf_recycle;
1114                                                         buf_recycle = null;
1115                                                 }
1116                                         }
1117                                 }
1118
1119                                 if (buf == null)
1120                                         buf = new byte [size];
1121                                 else
1122                                         Array.Clear (buf, 0, size);
1123                         }
1124                                         
1125                         buf_size = size;
1126 //                      buf_start = 0;
1127 //                      buf_offset = buf_length = 0;
1128 //                      buf_dirty = false;
1129                 }
1130
1131                 private string GetSecureFileName (string filename)
1132                 {
1133                         return (anonymous) ? Path.GetFileName (filename) : Path.GetFullPath (filename);
1134                 }
1135
1136                 private string GetSecureFileName (string filename, bool full)
1137                 {
1138                         return (anonymous) ? Path.GetFileName (filename) : 
1139                                 (full) ? Path.GetFullPath (filename) : filename;
1140                 }
1141
1142                 // fields
1143
1144                 internal const int DefaultBufferSize = 4096;
1145
1146                 // Input buffer ready for recycling                             
1147                 static byte[] buf_recycle;
1148                 static readonly object buf_recycle_lock = new object ();
1149
1150                 private byte [] buf;                    // the buffer
1151                 private string name = "[Unknown]";      // name of file.
1152
1153                 private SafeFileHandle safeHandle;
1154                 private bool isExposed;
1155
1156                 private long append_startpos;
1157
1158                 private FileAccess access;
1159                 private bool owner;
1160                 private bool async;
1161                 private bool canseek;
1162                 private bool anonymous;
1163                 private bool buf_dirty;                 // true if buffer has been written to
1164
1165                 private int buf_size;                   // capacity in bytes
1166                 private int buf_length;                 // number of valid bytes in buffer
1167                 private int buf_offset;                 // position of next byte
1168                 private long buf_start;                 // location of buffer in file
1169         }
1170 }