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