[runtime] Updates comments.
[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 isZeroSize)
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, isZeroSize);
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 #if !NET_2_1
117                 public FileStream (SafeFileHandle handle, FileAccess access)
118                         :this(handle, access, DefaultBufferSize, false)
119                 {
120                 }
121                 
122                 public FileStream (SafeFileHandle handle, FileAccess access,
123                                    int bufferSize)
124                         :this(handle, access, bufferSize, false)
125                 {
126                 }
127
128                 public FileStream (SafeFileHandle handle, FileAccess access, int bufferSize, bool isAsync)
129                 {
130                         Init (handle, access, false, bufferSize, isAsync, false);
131                 }
132
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, bool checkHost)
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 isZeroSize)
295                 {
296                         if (access < FileAccess.Read || access > FileAccess.ReadWrite)
297                                 throw new ArgumentOutOfRangeException ("access");
298
299                         MonoIOError error;
300                         MonoFileType ftype = MonoIO.GetFileType (safeHandle, out error);
301
302                         if (error != MonoIOError.ERROR_SUCCESS) {
303                                 throw MonoIO.GetException (name, error);
304                         }
305
306                         if (ftype == MonoFileType.Unknown) {
307                                 throw new IOException ("Invalid handle.");
308                         } else if (ftype == MonoFileType.Disk) {
309                                 this.canseek = true;
310                         } else {
311                                 this.canseek = false;
312                         }
313
314                         this.safeHandle = safeHandle;
315                         ExposeHandle ();
316                         this.access = access;
317                         this.owner = ownsHandle;
318                         this.async = isAsync;
319                         this.anonymous = false;
320
321                         if (canseek) {
322                                 buf_start = MonoIO.Seek (safeHandle, 0, SeekOrigin.Current, out error);
323                                 if (error != MonoIOError.ERROR_SUCCESS) {
324                                         throw MonoIO.GetException (name, error);
325                                 }
326                         }
327
328                         /* Can't set append mode */
329                         this.append_startpos=0;
330                 }
331
332                 // properties
333                 
334                 public override bool CanRead {
335                         get {
336                                 return access == FileAccess.Read ||
337                                        access == FileAccess.ReadWrite;
338                         }
339                 }
340
341                 public override bool CanWrite {
342                         get {
343                                 return access == FileAccess.Write ||
344                                         access == FileAccess.ReadWrite;
345                         }
346                 }
347                 
348                 public override bool CanSeek {
349                         get {
350                                 return(canseek);
351                         }
352                 }
353
354                 public virtual bool IsAsync {
355                         get {
356                                 return (async);
357                         }
358                 }
359
360                 public string Name {
361                         get {
362                                 return name;
363                         }
364                 }
365
366                 public override long Length {
367                         get {
368                                 if (safeHandle.IsClosed)
369                                         throw new ObjectDisposedException ("Stream has been closed");
370
371                                 if (!CanSeek)
372                                         throw new NotSupportedException ("The stream does not support seeking");
373
374                                 // Buffered data might change the length of the stream
375                                 FlushBufferIfDirty ();
376
377                                 MonoIOError error;
378
379                                 long length = MonoIO.GetLength (safeHandle, out error);
380
381                                 if (error != MonoIOError.ERROR_SUCCESS) {
382                                         // don't leak the path information for isolated storage
383                                         throw MonoIO.GetException (GetSecureFileName (name), error);
384                                 }
385
386                                 return(length);
387                         }
388                 }
389
390                 public override long Position {
391                         get {
392                                 if (safeHandle.IsClosed)
393                                         throw new ObjectDisposedException ("Stream has been closed");
394
395                                 if (CanSeek == false)
396                                         throw new NotSupportedException("The stream does not support seeking");
397
398                                 if (!isExposed)
399                                         return(buf_start + buf_offset);
400
401                                 // If the handle was leaked outside we always ask the real handle
402                                 MonoIOError error;
403
404                                 long ret = MonoIO.Seek (safeHandle, 0, SeekOrigin.Current, out error);
405
406                                 if (error != MonoIOError.ERROR_SUCCESS) {
407                                         // don't leak the path information for isolated storage
408                                         throw MonoIO.GetException (GetSecureFileName (name), error);
409                                 }
410
411                                 return ret;
412                         }
413                         set {
414                                 if(value < 0) {
415                                         throw new ArgumentOutOfRangeException("Attempt to set the position to a negative value");
416                                 }
417                                 
418                                 Seek (value, SeekOrigin.Begin);
419                         }
420                 }
421
422                 [Obsolete ("Use SafeFileHandle instead")]
423                 public virtual IntPtr Handle {
424                         [SecurityPermission (SecurityAction.LinkDemand, UnmanagedCode = true)]
425                         [SecurityPermission (SecurityAction.InheritanceDemand, UnmanagedCode = true)]
426                         get {
427                                 var handle = safeHandle.DangerousGetHandle ();
428                                 if (!isExposed)
429                                         ExposeHandle ();
430                                 return handle;
431                         }
432                 }
433
434                 public virtual SafeFileHandle SafeFileHandle {
435                         [SecurityPermission (SecurityAction.LinkDemand, UnmanagedCode = true)]
436                         [SecurityPermission (SecurityAction.InheritanceDemand, UnmanagedCode = true)]
437                         get {
438                                 if (!isExposed)
439                                         ExposeHandle ();
440                                 return safeHandle;
441                         }
442                 }
443
444                 void ExposeHandle ()
445                 {
446                         isExposed = true;
447                         FlushBuffer ();
448                         InitBuffer (0, true);
449                 }
450
451                 // methods
452
453                 public override int ReadByte ()
454                 {
455                         if (safeHandle.IsClosed)
456                                 throw new ObjectDisposedException ("Stream has been closed");
457
458                         if (!CanRead)
459                                 throw new NotSupportedException ("Stream does not support reading");
460
461                         if (buf_size == 0) {
462                                 int n = ReadData (safeHandle, buf, 0, 1);
463                                 if (n == 0) return -1;
464                                 else return buf[0];
465                         }
466                         else if (buf_offset >= buf_length) {
467                                 RefillBuffer ();
468
469                                 if (buf_length == 0)
470                                         return -1;
471                         }
472                         
473                         return buf [buf_offset ++];
474                 }
475
476                 public override void WriteByte (byte value)
477                 {
478                         if (safeHandle.IsClosed)
479                                 throw new ObjectDisposedException ("Stream has been closed");
480
481                         if (!CanWrite)
482                                 throw new NotSupportedException ("Stream does not support writing");
483
484                         if (buf_offset == buf_size)
485                                 FlushBuffer ();
486
487                         if (buf_size == 0) { // No buffering
488                                 buf [0] = value;
489                                 buf_dirty = true;
490                                 buf_length = 1;
491                                 FlushBuffer ();
492                                 return;
493                         }
494
495                         buf [buf_offset ++] = value;
496                         if (buf_offset > buf_length)
497                                 buf_length = buf_offset;
498
499                         buf_dirty = true;
500                 }
501
502                 public override int Read ([In,Out] byte[] array, int offset, int count)
503                 {
504                         if (safeHandle.IsClosed)
505                                 throw new ObjectDisposedException ("Stream has been closed");
506                         if (array == null)
507                                 throw new ArgumentNullException ("array");
508                         if (!CanRead)
509                                 throw new NotSupportedException ("Stream does not support reading");
510                         int len = array.Length;
511                         if (offset < 0)
512                                 throw new ArgumentOutOfRangeException ("offset", "< 0");
513                         if (count < 0)
514                                 throw new ArgumentOutOfRangeException ("count", "< 0");
515                         if (offset > len)
516                                 throw new ArgumentException ("destination offset is beyond array size");
517                         // reordered to avoid possible integer overflow
518                         if (offset > len - count)
519                                 throw new ArgumentException ("Reading would overrun buffer");
520
521                         if (async) {
522                                 IAsyncResult ares = BeginRead (array, offset, count, null, null);
523                                 return EndRead (ares);
524                         }
525
526                         return ReadInternal (array, offset, count);
527                 }
528
529                 int ReadInternal (byte [] dest, int offset, int count)
530                 {
531                         int n = ReadSegment (dest, offset, count);
532                         if (n == count) {
533                                 return count;
534                         }
535                         
536                         int copied = n;
537                         count -= n;
538
539                         if (count > buf_size) {
540                                 /* Read as much as we can, up
541                                  * to count bytes
542                                  */
543                                 FlushBuffer();
544                                 n = ReadData (safeHandle, dest, offset+n, count);
545
546                                 /* Make the next buffer read
547                                  * start from the right place
548                                  */
549                                 buf_start += n;
550                         } else {
551                                 RefillBuffer ();
552                                 n = ReadSegment (dest, offset+copied, count);
553                         }
554
555                         return copied + n;
556                 }
557
558                 delegate int ReadDelegate (byte [] buffer, int offset, int count);
559
560                 public override IAsyncResult BeginRead (byte [] array, int offset, int numBytes,
561                                                         AsyncCallback userCallback, object stateObject)
562                 {
563                         if (safeHandle.IsClosed)
564                                 throw new ObjectDisposedException ("Stream has been closed");
565
566                         if (!CanRead)
567                                 throw new NotSupportedException ("This stream does not support reading");
568
569                         if (array == null)
570                                 throw new ArgumentNullException ("array");
571
572                         if (numBytes < 0)
573                                 throw new ArgumentOutOfRangeException ("numBytes", "Must be >= 0");
574
575                         if (offset < 0)
576                                 throw new ArgumentOutOfRangeException ("offset", "Must be >= 0");
577
578                         // reordered to avoid possible integer overflow
579                         if (numBytes > array.Length - offset)
580                                 throw new ArgumentException ("Buffer too small. numBytes/offset wrong.");
581
582                         if (!async)
583                                 return base.BeginRead (array, offset, numBytes, userCallback, stateObject);
584
585                         ReadDelegate r = new ReadDelegate (ReadInternal);
586                         return r.BeginInvoke (array, offset, numBytes, userCallback, stateObject);
587                 }
588                 
589                 public override int EndRead (IAsyncResult asyncResult)
590                 {
591                         if (asyncResult == null)
592                                 throw new ArgumentNullException ("asyncResult");
593
594                         if (!async)
595                                 return base.EndRead (asyncResult);
596
597                         AsyncResult ares = asyncResult as AsyncResult;
598                         if (ares == null)
599                                 throw new ArgumentException ("Invalid IAsyncResult", "asyncResult");
600
601                         ReadDelegate r = ares.AsyncDelegate as ReadDelegate;
602                         if (r == null)
603                                 throw new ArgumentException ("Invalid IAsyncResult", "asyncResult");
604
605                         return r.EndInvoke (asyncResult);
606                 }
607
608                 public override void Write (byte[] array, int offset, int count)
609                 {
610                         if (safeHandle.IsClosed)
611                                 throw new ObjectDisposedException ("Stream has been closed");
612                         if (array == null)
613                                 throw new ArgumentNullException ("array");
614                         if (offset < 0)
615                                 throw new ArgumentOutOfRangeException ("offset", "< 0");
616                         if (count < 0)
617                                 throw new ArgumentOutOfRangeException ("count", "< 0");
618                         // ordered to avoid possible integer overflow
619                         if (offset > array.Length - count)
620                                 throw new ArgumentException ("Reading would overrun buffer");
621                         if (!CanWrite)
622                                 throw new NotSupportedException ("Stream does not support writing");
623
624                         if (async) {
625                                 IAsyncResult ares = BeginWrite (array, offset, count, null, null);
626                                 EndWrite (ares);
627                                 return;
628                         }
629
630                         WriteInternal (array, offset, count);
631                 }
632
633                 void WriteInternal (byte [] src, int offset, int count)
634                 {
635                         if (count > buf_size) {
636                                 // shortcut for long writes
637                                 MonoIOError error;
638
639                                 FlushBuffer ();
640                                 int wcount = count;
641
642                                 while (wcount > 0){
643                                         int n = MonoIO.Write (safeHandle, src, offset, wcount, out error);
644                                         if (error != MonoIOError.ERROR_SUCCESS)
645                                                 throw MonoIO.GetException (GetSecureFileName (name), error);
646
647                                         wcount -= n;
648                                         offset += n;
649                                 }
650                                 buf_start += count;
651                         } else {
652
653                                 int copied = 0;
654                                 while (count > 0) {
655                                         
656                                         int n = WriteSegment (src, offset + copied, count);
657                                         copied += n;
658                                         count -= n;
659
660                                         if (count == 0) {
661                                                 break;
662                                         }
663
664                                         FlushBuffer ();
665                                 }
666                         }
667                 }
668
669                 delegate void WriteDelegate (byte [] buffer, int offset, int count);
670
671                 public override IAsyncResult BeginWrite (byte [] array, int offset, int numBytes,
672                                                         AsyncCallback userCallback, object stateObject)
673                 {
674                         if (safeHandle.IsClosed)
675                                 throw new ObjectDisposedException ("Stream has been closed");
676
677                         if (!CanWrite)
678                                 throw new NotSupportedException ("This stream does not support writing");
679
680                         if (array == null)
681                                 throw new ArgumentNullException ("array");
682
683                         if (numBytes < 0)
684                                 throw new ArgumentOutOfRangeException ("numBytes", "Must be >= 0");
685
686                         if (offset < 0)
687                                 throw new ArgumentOutOfRangeException ("offset", "Must be >= 0");
688
689                         // reordered to avoid possible integer overflow
690                         if (numBytes > array.Length - offset)
691                                 throw new ArgumentException ("array too small. numBytes/offset wrong.");
692
693                         if (!async)
694                                 return base.BeginWrite (array, offset, numBytes, userCallback, stateObject);
695
696                         FileStreamAsyncResult result = new FileStreamAsyncResult (userCallback, stateObject);
697                         result.BytesRead = -1;
698                         result.Count = numBytes;
699                         result.OriginalCount = numBytes;
700 /*
701                         if (buf_dirty) {
702                                 MemoryStream ms = new MemoryStream ();
703                                 FlushBuffer (ms);
704                                 ms.Write (array, offset, numBytes);
705
706                                 // Set arguments to new compounded buffer 
707                                 offset = 0;
708                                 array = ms.ToArray ();
709                                 numBytes = array.Length;
710                         }
711 */
712                         WriteDelegate w = WriteInternal;
713                         return w.BeginInvoke (array, offset, numBytes, userCallback, stateObject);      
714                 }
715                 
716                 public override void EndWrite (IAsyncResult asyncResult)
717                 {
718                         if (asyncResult == null)
719                                 throw new ArgumentNullException ("asyncResult");
720
721                         if (!async) {
722                                 base.EndWrite (asyncResult);
723                                 return;
724                         }
725
726                         AsyncResult ares = asyncResult as AsyncResult;
727                         if (ares == null)
728                                 throw new ArgumentException ("Invalid IAsyncResult", "asyncResult");
729
730                         WriteDelegate w = ares.AsyncDelegate as WriteDelegate;
731                         if (w == null)
732                                 throw new ArgumentException ("Invalid IAsyncResult", "asyncResult");
733
734                         w.EndInvoke (asyncResult);
735                         return;
736                 }
737
738                 public override long Seek (long offset, SeekOrigin origin)
739                 {
740                         long pos;
741
742                         if (safeHandle.IsClosed)
743                                 throw new ObjectDisposedException ("Stream has been closed");
744                         
745                         // make absolute
746
747                         if(CanSeek == false) {
748                                 throw new NotSupportedException("The stream does not support seeking");
749                         }
750
751                         switch (origin) {
752                         case SeekOrigin.End:
753                                 pos = Length + offset;
754                                 break;
755
756                         case SeekOrigin.Current:
757                                 pos = Position + offset;
758                                 break;
759
760                         case SeekOrigin.Begin:
761                                 pos = offset;
762                                 break;
763
764                         default:
765                                 throw new ArgumentException ("origin", "Invalid SeekOrigin");
766                         }
767
768                         if (pos < 0) {
769                                 /* LAMESPEC: shouldn't this be
770                                  * ArgumentOutOfRangeException?
771                                  */
772                                 throw new IOException("Attempted to Seek before the beginning of the stream");
773                         }
774
775                         if(pos < this.append_startpos) {
776                                 /* More undocumented crap */
777                                 throw new IOException("Can't seek back over pre-existing data in append mode");
778                         }
779
780                         FlushBuffer ();
781
782                         MonoIOError error;
783
784                         buf_start = MonoIO.Seek (safeHandle, pos, SeekOrigin.Begin, out error);
785
786                         if (error != MonoIOError.ERROR_SUCCESS) {
787                                 // don't leak the path information for isolated storage
788                                 throw MonoIO.GetException (GetSecureFileName (name), error);
789                         }
790                         
791                         return(buf_start);
792                 }
793
794                 public override void SetLength (long value)
795                 {
796                         if (safeHandle.IsClosed)
797                                 throw new ObjectDisposedException ("Stream has been closed");
798
799                         if(CanSeek == false)
800                                 throw new NotSupportedException("The stream does not support seeking");
801
802                         if(CanWrite == false)
803                                 throw new NotSupportedException("The stream does not support writing");
804
805                         if(value < 0)
806                                 throw new ArgumentOutOfRangeException("value is less than 0");
807                         
808                         FlushBuffer ();
809
810                         MonoIOError error;
811
812                         MonoIO.SetLength (safeHandle, value, out error);
813                         if (error != MonoIOError.ERROR_SUCCESS) {
814                                 // don't leak the path information for isolated storage
815                                 throw MonoIO.GetException (GetSecureFileName (name), error);
816                         }
817
818                         if (Position > value)
819                                 Position = value;
820                 }
821
822                 public override void Flush ()
823                 {
824                         if (safeHandle.IsClosed)
825                                 throw new ObjectDisposedException ("Stream has been closed");
826
827                         FlushBuffer ();
828                 }
829
830                 public virtual void Flush (bool flushToDisk)
831                 {
832                         if (safeHandle.IsClosed)
833                                 throw new ObjectDisposedException ("Stream has been closed");
834
835                         FlushBuffer ();
836
837                         // This does the fsync
838                         if (flushToDisk){
839                                 MonoIOError error;
840                                 MonoIO.Flush (safeHandle, out error);
841                         }
842                 }
843
844                 public virtual void Lock (long position, long length)
845                 {
846                         if (safeHandle.IsClosed)
847                                 throw new ObjectDisposedException ("Stream has been closed");
848                         if (position < 0) {
849                                 throw new ArgumentOutOfRangeException ("position must not be negative");
850                         }
851                         if (length < 0) {
852                                 throw new ArgumentOutOfRangeException ("length must not be negative");
853                         }
854
855                         MonoIOError error;
856
857                         MonoIO.Lock (safeHandle, position, length, out error);
858                         if (error != MonoIOError.ERROR_SUCCESS) {
859                                 // don't leak the path information for isolated storage
860                                 throw MonoIO.GetException (GetSecureFileName (name), error);
861                         }
862                 }
863
864                 public virtual void Unlock (long position, long length)
865                 {
866                         if (safeHandle.IsClosed)
867                                 throw new ObjectDisposedException ("Stream has been closed");
868                         if (position < 0) {
869                                 throw new ArgumentOutOfRangeException ("position must not be negative");
870                         }
871                         if (length < 0) {
872                                 throw new ArgumentOutOfRangeException ("length must not be negative");
873                         }
874
875                         MonoIOError error;
876
877                         MonoIO.Unlock (safeHandle, position, length, out error);
878                         if (error != MonoIOError.ERROR_SUCCESS) {
879                                 // don't leak the path information for isolated storage
880                                 throw MonoIO.GetException (GetSecureFileName (name), error);
881                         }
882                 }
883
884                 // protected
885
886                 ~FileStream ()
887                 {
888                         Dispose (false);
889                 }
890
891                 protected override void Dispose (bool disposing)
892                 {
893                         Exception exc = null;
894                         if (safeHandle != null && !safeHandle.IsClosed) {
895                                 try {
896                                         // If the FileStream is in "exposed" status
897                                         // it means that we do not have a buffer(we write the data without buffering)
898                                         // therefor we don't and can't flush the buffer becouse we don't have one.
899                                         FlushBuffer ();
900                                 } catch (Exception e) {
901                                         exc = e;
902                                 }
903
904                                 if (owner) {
905                                         MonoIOError error;
906
907                                         MonoIO.Close (safeHandle.DangerousGetHandle (), out error);
908                                         if (error != MonoIOError.ERROR_SUCCESS) {
909                                                 // don't leak the path information for isolated storage
910                                                 throw MonoIO.GetException (GetSecureFileName (name), error);
911                                         }
912
913                                         safeHandle.DangerousRelease ();
914                                 }
915                         }
916
917                         canseek = false;
918                         access = 0;
919                         
920                         if (disposing && buf != null) {
921                                 if (buf.Length == DefaultBufferSize && buf_recycle == null) {
922                                         lock (buf_recycle_lock) {
923                                                 if (buf_recycle == null) {
924                                                         buf_recycle = buf;
925                                                 }
926                                         }
927                                 }
928                                 
929                                 buf = null;
930                                 GC.SuppressFinalize (this);
931                         }
932                         if (exc != null)
933                                 throw exc;
934                 }
935
936 #if !NET_2_1
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 #endif
959
960                 public override Task FlushAsync (CancellationToken cancellationToken)
961                 {
962                         if (safeHandle.IsClosed)
963                                 throw new ObjectDisposedException ("Stream has been closed");
964
965                         return base.FlushAsync (cancellationToken);
966                 }
967
968                 public override Task<int> ReadAsync (byte[] buffer, int offset, int count, CancellationToken cancellationToken)
969                 {
970                         return base.ReadAsync (buffer, offset, count, cancellationToken);
971                 }
972
973                 public override Task WriteAsync (byte[] buffer, int offset, int count, CancellationToken cancellationToken)
974                 {
975                         return base.WriteAsync (buffer, offset, count, cancellationToken);
976                 }
977
978                 // private.
979
980                 // ReadSegment, WriteSegment, FlushBuffer,
981                 // RefillBuffer and ReadData should only be called
982                 // when the Monitor lock is held, but these methods
983                 // grab it again just to be safe.
984
985                 private int ReadSegment (byte [] dest, int dest_offset, int count)
986                 {
987                         count = Math.Min (count, buf_length - buf_offset);
988                         
989                         if (count > 0) {
990                                 // Use the fastest method, all range checks has been done
991                                 Buffer.BlockCopyInternal (buf, buf_offset, dest, dest_offset, count);
992                                 buf_offset += count;
993                         }
994                         
995                         return count;
996                 }
997
998                 private int WriteSegment (byte [] src, int src_offset,
999                                           int count)
1000                 {
1001                         if (count > buf_size - buf_offset) {
1002                                 count = buf_size - buf_offset;
1003                         }
1004                         
1005                         if (count > 0) {
1006                                 Buffer.BlockCopy (src, src_offset,
1007                                                   buf, buf_offset,
1008                                                   count);
1009                                 buf_offset += count;
1010                                 if (buf_offset > buf_length) {
1011                                         buf_length = buf_offset;
1012                                 }
1013                                 
1014                                 buf_dirty = true;
1015                         }
1016                         
1017                         return(count);
1018                 }
1019
1020                 void FlushBuffer ()
1021                 {
1022                         if (buf_dirty) {
1023 //                              if (st == null) {
1024                                         MonoIOError error;
1025
1026                                         if (CanSeek == true && !isExposed) {
1027                                                 MonoIO.Seek (safeHandle, buf_start, SeekOrigin.Begin, out error);
1028
1029                                                 if (error != MonoIOError.ERROR_SUCCESS) {
1030                                                         // don't leak the path information for isolated storage
1031                                                         throw MonoIO.GetException (GetSecureFileName (name), error);
1032                                                 }
1033                                         }
1034
1035                                         int wcount = buf_length;
1036                                         int offset = 0;
1037                                         while (wcount > 0){
1038                                                 int n = MonoIO.Write (safeHandle, buf, 0, buf_length, out error);
1039                                                 if (error != MonoIOError.ERROR_SUCCESS) {
1040                                                         // don't leak the path information for isolated storage
1041                                                         throw MonoIO.GetException (GetSecureFileName (name), error);
1042                                                 }
1043                                                 wcount -= n;
1044                                                 offset += n;
1045                                         }
1046 //                              } else {
1047 //                                      st.Write (buf, 0, buf_length);
1048 //                              }
1049                         }
1050
1051                         buf_start += buf_offset;
1052                         buf_offset = buf_length = 0;
1053                         buf_dirty = false;
1054                 }
1055
1056                 private void FlushBufferIfDirty ()
1057                 {
1058                         if (buf_dirty)
1059                                 FlushBuffer ();
1060                 }
1061
1062                 private void RefillBuffer ()
1063                 {
1064                         FlushBuffer ();
1065
1066                         buf_length = ReadData (safeHandle, buf, 0, buf_size);
1067                 }
1068
1069                 private int ReadData (SafeHandle safeHandle, byte[] buf, int offset,
1070                                       int count)
1071                 {
1072                         MonoIOError error;
1073                         int amount = 0;
1074
1075                         /* when async == true, if we get here we don't suport AIO or it's disabled
1076                          * and we're using the threadpool */
1077                         amount = MonoIO.Read (safeHandle, buf, offset, count, out error);
1078                         if (error == MonoIOError.ERROR_BROKEN_PIPE) {
1079                                 amount = 0; // might not be needed, but well...
1080                         } else if (error != MonoIOError.ERROR_SUCCESS) {
1081                                 // don't leak the path information for isolated storage
1082                                 throw MonoIO.GetException (GetSecureFileName (name), error);
1083                         }
1084                         
1085                         /* Check for read error */
1086                         if(amount == -1) {
1087                                 throw new IOException ();
1088                         }
1089                         
1090                         return(amount);
1091                 }
1092                                 
1093                 void InitBuffer (int size, bool isZeroSize)
1094                 {
1095                         if (isZeroSize) {
1096                                 size = 0;
1097                                 buf = new byte[1];
1098                         } else {
1099                                 if (size <= 0)
1100                                         throw new ArgumentOutOfRangeException ("bufferSize", "Positive number required.");
1101
1102                                 size = Math.Max (size, 8);
1103
1104                                 //
1105                                 // Instead of allocating a new default buffer use the
1106                                 // last one if there is any available
1107                                 //              
1108                                 if (size <= DefaultBufferSize && buf_recycle != null) {
1109                                         lock (buf_recycle_lock) {
1110                                                 if (buf_recycle != null) {
1111                                                         buf = buf_recycle;
1112                                                         buf_recycle = null;
1113                                                 }
1114                                         }
1115                                 }
1116
1117                                 if (buf == null)
1118                                         buf = new byte [size];
1119                                 else
1120                                         Array.Clear (buf, 0, size);
1121                         }
1122                                         
1123                         buf_size = size;
1124 //                      buf_start = 0;
1125 //                      buf_offset = buf_length = 0;
1126 //                      buf_dirty = false;
1127                 }
1128
1129                 private string GetSecureFileName (string filename)
1130                 {
1131                         return (anonymous) ? Path.GetFileName (filename) : Path.GetFullPath (filename);
1132                 }
1133
1134                 private string GetSecureFileName (string filename, bool full)
1135                 {
1136                         return (anonymous) ? Path.GetFileName (filename) : 
1137                                 (full) ? Path.GetFullPath (filename) : filename;
1138                 }
1139
1140                 // fields
1141
1142                 internal const int DefaultBufferSize = 4096;
1143
1144                 // Input buffer ready for recycling                             
1145                 static byte[] buf_recycle;
1146                 static readonly object buf_recycle_lock = new object ();
1147
1148                 private byte [] buf;                    // the buffer
1149                 private string name = "[Unknown]";      // name of file.
1150
1151                 private SafeFileHandle safeHandle;
1152                 private bool isExposed;
1153
1154                 private long append_startpos;
1155
1156                 private FileAccess access;
1157                 private bool owner;
1158                 private bool async;
1159                 private bool canseek;
1160                 private bool anonymous;
1161                 private bool buf_dirty;                 // true if buffer has been written to
1162
1163                 private int buf_size;                   // capacity in bytes
1164                 private int buf_length;                 // number of valid bytes in buffer
1165                 private int buf_offset;                 // position of next byte
1166                 private long buf_start;                 // location of buffer in file
1167         }
1168 }