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