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