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