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