Add a more functional (i.e. fewer-stubs) implementation of System.Data.Linq.
[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                 }
167
168                 public FileStream (string path, FileMode mode,
169                                    FileSystemRights rights, FileShare share,
170                                    int bufferSize, FileOptions options)
171                 {
172                         throw new NotImplementedException ();
173                 }
174                 
175                 public FileStream (string path, FileMode mode,
176                                    FileSystemRights rights, FileShare share,
177                                    int bufferSize, FileOptions options,
178                                    FileSecurity fileSecurity)
179                 {
180                         throw new NotImplementedException ();
181                 }
182 #endif
183
184                 internal FileStream (string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, bool isAsync, bool anonymous)
185                         : this (path, mode, access, share, bufferSize, anonymous, isAsync ? FileOptions.Asynchronous : FileOptions.None)
186                 {
187                 }
188
189                 internal FileStream (string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, bool anonymous, FileOptions options)
190                 {
191                         if (path == null) {
192                                 throw new ArgumentNullException ("path");
193                         }
194
195                         if (path.Length == 0) {
196                                 throw new ArgumentException ("Path is empty");
197                         }
198
199 #if NET_2_0
200                         // ignore the Inheritable flag
201                         share &= ~FileShare.Inheritable;
202
203 #endif
204
205                         if (bufferSize <= 0)
206                                 throw new ArgumentOutOfRangeException ("bufferSize", "Positive number required.");
207
208                         if (mode < FileMode.CreateNew || mode > FileMode.Append) {
209 #if NET_2_1
210                                 if (anonymous)
211                                         throw new ArgumentException ("mode", "Enum value was out of legal range.");
212                                 else
213 #endif
214                                 throw new ArgumentOutOfRangeException ("mode", "Enum value was out of legal range.");
215                         }
216
217                         if (access < FileAccess.Read || access > FileAccess.ReadWrite) {
218 #if NET_2_1
219                                 if (anonymous)
220                                         throw new IsolatedStorageException ("Enum value for FileAccess was out of legal range.");
221                                 else
222 #endif
223                                 throw new ArgumentOutOfRangeException ("access", "Enum value was out of legal range.");
224                         }
225
226 #if NET_2_0
227                         if (share < FileShare.None || share > (FileShare.ReadWrite | FileShare.Delete)) {
228 #else
229                         if (share < FileShare.None || share > FileShare.ReadWrite) {
230 #endif
231 #if NET_2_1
232                                 if (anonymous)
233                                         throw new IsolatedStorageException ("Enum value for FileShare was out of legal range.");
234                                 else
235 #endif
236                                 throw new ArgumentOutOfRangeException ("share", "Enum value was out of legal range.");
237                         }
238
239                         if (path.IndexOfAny (Path.InvalidPathChars) != -1) {
240                                 throw new ArgumentException ("Name has invalid chars");
241                         }
242
243                         if (Directory.Exists (path)) {
244                                 // don't leak the path information for isolated storage
245                                 string msg = Locale.GetText ("Access to the path '{0}' is denied.");
246                                 string fname = (anonymous) ? Path.GetFileName (path) : Path.GetFullPath (path);
247                                 throw new UnauthorizedAccessException (String.Format (msg, fname));
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                         string dname = Path.GetDirectoryName (path);
266                         if (dname.Length > 0) {
267                                 string fp = Path.GetFullPath (dname);
268                                 if (!Directory.Exists (fp)) {
269                                         // don't leak the path information for isolated storage
270                                         string msg = Locale.GetText ("Could not find a part of the path \"{0}\".");
271                                         string fname = (anonymous) ? dname : Path.GetFullPath (path);
272 #if NET_2_1
273                                         throw new IsolatedStorageException (String.Format (msg, fname));
274 #else
275                                         throw new DirectoryNotFoundException (String.Format (msg, fname));
276 #endif
277                                 }
278                         }
279
280                         if (access == FileAccess.Read && mode != FileMode.Create && mode != FileMode.OpenOrCreate &&
281                                         mode != FileMode.CreateNew && !File.Exists (path)) {
282                                 // don't leak the path information for isolated storage
283                                 string msg = Locale.GetText ("Could not find file \"{0}\".");
284                                 string fname = (anonymous) ? Path.GetFileName (path) : Path.GetFullPath (path);
285 #if NET_2_1
286                                 throw new IsolatedStorageException (String.Format (msg, fname));
287 #else
288                                 throw new FileNotFoundException (String.Format (msg, fname), fname);
289 #endif
290                         }
291
292                         // IsolatedStorage needs to keep the Name property to the default "[Unknown]"
293                         if (!anonymous)
294                                 this.name = path;
295
296                         // TODO: demand permissions
297
298                         MonoIOError error;
299
300                         this.handle = MonoIO.Open (path, mode, access, share, options, out error);
301                         if (handle == MonoIO.InvalidHandle) {
302                                 // don't leak the path information for isolated storage
303                                 string fname = (anonymous) ? Path.GetFileName (path) : Path.GetFullPath (path);
304                                 throw MonoIO.GetException (fname, error);
305                         }
306
307                         this.access = access;
308                         this.owner = true;
309                         this.anonymous = anonymous;
310
311                         /* Can we open non-files by name? */
312                         
313                         if (MonoIO.GetFileType (handle, out error) == MonoFileType.Disk) {
314                                 this.canseek = true;
315                                 this.async = (options & FileOptions.Asynchronous) != 0;
316                         } else {
317                                 this.canseek = false;
318                                 this.async = false;
319                         }
320
321
322                         if (access == FileAccess.Read && canseek && (bufferSize == DefaultBufferSize)) {
323                                 /* Avoid allocating a large buffer for small files */
324                                 long len = Length;
325                                 if (bufferSize > len) {
326                                         bufferSize = (int)(len < 1000 ? 1000 : len);
327                                 }
328                         }
329
330                         InitBuffer (bufferSize, false);
331
332                         if (mode==FileMode.Append) {
333                                 this.Seek (0, SeekOrigin.End);
334                                 this.append_startpos=this.Position;
335                         } else {
336                                 this.append_startpos=0;
337                         }
338                 }
339
340                 // properties
341                 
342                 public override bool CanRead {
343                         get {
344                                 return access == FileAccess.Read ||
345                                        access == FileAccess.ReadWrite;
346                         }
347                 }
348
349                 public override bool CanWrite {
350                         get {
351                                 return access == FileAccess.Write ||
352                                         access == FileAccess.ReadWrite;
353                         }
354                 }
355                 
356                 public override bool CanSeek {
357                         get {
358                                 return(canseek);
359                         }
360                 }
361
362                 public virtual bool IsAsync {
363                         get {
364                                 return (async);
365                         }
366                 }
367
368                 public string Name {
369                         get {
370                                 return name; 
371                         }
372                 }
373
374                 public override long Length {
375                         get {
376                                 if (handle == MonoIO.InvalidHandle)
377                                         throw new ObjectDisposedException ("Stream has been closed");
378
379                                 if (!CanSeek)
380                                         throw new NotSupportedException ("The stream does not support seeking");
381
382                                 // Buffered data might change the length of the stream
383                                 FlushBufferIfDirty ();
384
385                                 MonoIOError error;
386                                 long length;
387                                 
388                                 length = MonoIO.GetLength (handle, out error);
389                                 if (error != MonoIOError.ERROR_SUCCESS) {
390                                         // don't leak the path information for isolated storage
391                                         string fname = (anonymous) ? Path.GetFileName (name) : name;
392                                         throw MonoIO.GetException (fname, error);
393                                 }
394
395                                 return(length);
396                         }
397                 }
398
399                 public override long Position {
400                         get {
401                                 if (handle == MonoIO.InvalidHandle)
402                                         throw new ObjectDisposedException ("Stream has been closed");
403
404                                 if(CanSeek == false)
405                                         throw new NotSupportedException("The stream does not support seeking");
406                                 
407                                 return(buf_start + buf_offset);
408                         }
409                         set {
410                                 if (handle == MonoIO.InvalidHandle)
411                                         throw new ObjectDisposedException ("Stream has been closed");
412
413                                 if(CanSeek == false) {
414                                         throw new NotSupportedException("The stream does not support seeking");
415                                 }
416
417                                 if(value < 0) {
418                                         throw new ArgumentOutOfRangeException("Attempt to set the position to a negative value");
419                                 }
420                                 
421                                 Seek (value, SeekOrigin.Begin);
422                         }
423                 }
424
425 #if NET_2_0
426                 [Obsolete ("Use SafeFileHandle instead")]
427 #endif
428                 public virtual IntPtr Handle {
429                         [SecurityPermission (SecurityAction.LinkDemand, UnmanagedCode = true)]
430                         [SecurityPermission (SecurityAction.InheritanceDemand, UnmanagedCode = true)]
431                         get {
432                                 return handle;
433                         }
434                 }
435
436 #if NET_2_0
437                 public virtual SafeFileHandle SafeFileHandle {
438                         [SecurityPermission (SecurityAction.LinkDemand, UnmanagedCode = true)]
439                         [SecurityPermission (SecurityAction.InheritanceDemand, UnmanagedCode = true)]
440                         get { throw new NotImplementedException (); }
441                 }
442 #endif
443
444                 // methods
445
446                 public override int ReadByte ()
447                 {
448                         if (handle == MonoIO.InvalidHandle)
449                                 throw new ObjectDisposedException ("Stream has been closed");
450
451                         if (!CanRead)
452                                 throw new NotSupportedException ("Stream does not support reading");
453                         
454                         if (buf_size == 0) {
455                                 int n = ReadData (handle, buf, 0, 1);
456                                 if (n == 0) return -1;
457                                 else return buf[0];
458                         }
459                         else if (buf_offset >= buf_length) {
460                                 RefillBuffer ();
461
462                                 if (buf_length == 0)
463                                         return -1;
464                         }
465                         
466                         return buf [buf_offset ++];
467                 }
468
469                 public override void WriteByte (byte value)
470                 {
471                         if (handle == MonoIO.InvalidHandle)
472                                 throw new ObjectDisposedException ("Stream has been closed");
473
474                         if (!CanWrite)
475                                 throw new NotSupportedException ("Stream does not support writing");
476
477                         if (buf_offset == buf_size)
478                                 FlushBuffer ();
479
480                         if (buf_size == 0) { // No buffering
481                                 buf [0] = value;
482                                 buf_dirty = true;
483                                 buf_length = 1;
484                                 FlushBuffer ();
485                                 return;
486                         }
487
488                         buf [buf_offset ++] = value;
489                         if (buf_offset > buf_length)
490                                 buf_length = buf_offset;
491
492                         buf_dirty = true;
493                 }
494
495                 public override int Read ([In,Out] byte[] array, int offset, int count)
496                 {
497                         if (handle == MonoIO.InvalidHandle)
498                                 throw new ObjectDisposedException ("Stream has been closed");
499                         if (array == null)
500                                 throw new ArgumentNullException ("array");
501                         if (!CanRead)
502                                 throw new NotSupportedException ("Stream does not support reading");
503                         int len = array.Length;
504                         if (offset < 0)
505                                 throw new ArgumentOutOfRangeException ("offset", "< 0");
506                         if (count < 0)
507                                 throw new ArgumentOutOfRangeException ("count", "< 0");
508                         if (offset > len)
509                                 throw new ArgumentException ("destination offset is beyond array size");
510                         // reordered to avoid possible integer overflow
511                         if (offset > len - count)
512                                 throw new ArgumentException ("Reading would overrun buffer");
513
514                         if (async) {
515                                 IAsyncResult ares = BeginRead (array, offset, count, null, null);
516                                 return EndRead (ares);
517                         }
518
519                         return ReadInternal (array, offset, count);
520                 }
521
522                 int ReadInternal (byte [] dest, int offset, int count)
523                 {
524                         int copied = 0;
525
526                         int n = ReadSegment (dest, offset, count);
527                         copied += n;
528                         count -= n;
529                         
530                         if (count == 0) {
531                                 /* If there was already enough
532                                  * buffered, no need to read
533                                  * more from the file.
534                                  */
535                                 return (copied);
536                         }
537
538                         if (count > buf_size) {
539                                 /* Read as much as we can, up
540                                  * to count bytes
541                                  */
542                                 FlushBuffer();
543                                 n = ReadData (handle, dest,
544                                               offset+copied,
545                                               count);
546                         
547                                 /* Make the next buffer read
548                                  * start from the right place
549                                  */
550                                 buf_start += n;
551                         } else {
552                                 RefillBuffer ();
553                                 n = ReadSegment (dest,
554                                                  offset+copied,
555                                                  count);
556                         }
557
558                         copied += n;
559
560                         return copied;
561                 }
562
563                 delegate int ReadDelegate (byte [] buffer, int offset, int count);
564
565                 public override IAsyncResult BeginRead (byte [] array, int offset, int numBytes,
566                                                         AsyncCallback userCallback, object stateObject)
567                 {
568                         if (handle == MonoIO.InvalidHandle)
569                                 throw new ObjectDisposedException ("Stream has been closed");
570
571                         if (!CanRead)
572                                 throw new NotSupportedException ("This stream does not support reading");
573
574                         if (array == null)
575                                 throw new ArgumentNullException ("array");
576
577                         if (numBytes < 0)
578                                 throw new ArgumentOutOfRangeException ("numBytes", "Must be >= 0");
579
580                         if (offset < 0)
581                                 throw new ArgumentOutOfRangeException ("offset", "Must be >= 0");
582
583                         // reordered to avoid possible integer overflow
584                         if (numBytes > array.Length - offset)
585                                 throw new ArgumentException ("Buffer too small. numBytes/offset wrong.");
586
587                         if (!async)
588                                 return base.BeginRead (array, offset, numBytes, userCallback, stateObject);
589
590                         ReadDelegate r = new ReadDelegate (ReadInternal);
591                         return r.BeginInvoke (array, offset, numBytes, userCallback, stateObject);
592                 }
593                 
594                 public override int EndRead (IAsyncResult asyncResult)
595                 {
596                         if (asyncResult == null)
597                                 throw new ArgumentNullException ("asyncResult");
598
599                         if (!async)
600                                 return base.EndRead (asyncResult);
601
602                         AsyncResult ares = asyncResult as AsyncResult;
603                         if (ares == null)
604                                 throw new ArgumentException ("Invalid IAsyncResult", "asyncResult");
605
606                         ReadDelegate r = ares.AsyncDelegate as ReadDelegate;
607                         if (r == null)
608                                 throw new ArgumentException ("Invalid IAsyncResult", "asyncResult");
609
610                         return r.EndInvoke (asyncResult);
611                 }
612
613                 public override void Write (byte[] array, int offset, int count)
614                 {
615                         if (handle == MonoIO.InvalidHandle)
616                                 throw new ObjectDisposedException ("Stream has been closed");
617                         if (array == null)
618                                 throw new ArgumentNullException ("array");
619                         if (offset < 0)
620                                 throw new ArgumentOutOfRangeException ("offset", "< 0");
621                         if (count < 0)
622                                 throw new ArgumentOutOfRangeException ("count", "< 0");
623                         // ordered to avoid possible integer overflow
624                         if (offset > array.Length - count)
625                                 throw new ArgumentException ("Reading would overrun buffer");
626                         if (!CanWrite)
627                                 throw new NotSupportedException ("Stream does not support writing");
628
629                         if (async) {
630                                 IAsyncResult ares = BeginWrite (array, offset, count, null, null);
631                                 EndWrite (ares);
632                                 return;
633                         }
634
635                         WriteInternal (array, offset, count);
636                 }
637
638                 void WriteInternal (byte [] src, int offset, int count)
639                 {
640                         if (count > buf_size) {
641                                 // shortcut for long writes
642                                 MonoIOError error;
643
644                                 FlushBuffer ();
645
646                                 MonoIO.Write (handle, src, offset, count, out error);
647                                 if (error != MonoIOError.ERROR_SUCCESS) {
648                                         // don't leak the path information for isolated storage
649                                         string fname = (anonymous) ? Path.GetFileName (name) : name;
650                                         throw MonoIO.GetException (fname, error);
651                                 }
652                                 
653                                 buf_start += count;
654                         } else {
655
656                                 int copied = 0;
657                                 while (count > 0) {
658                                         
659                                         int n = WriteSegment (src, offset + copied, count);
660                                         copied += n;
661                                         count -= n;
662
663                                         if (count == 0) {
664                                                 break;
665                                         }
666
667                                         FlushBuffer ();
668                                 }
669                         }
670                 }
671
672                 delegate void WriteDelegate (byte [] buffer, int offset, int count);
673
674                 public override IAsyncResult BeginWrite (byte [] array, int offset, int numBytes,
675                                                         AsyncCallback userCallback, object stateObject)
676                 {
677                         if (handle == MonoIO.InvalidHandle)
678                                 throw new ObjectDisposedException ("Stream has been closed");
679
680                         if (!CanWrite)
681                                 throw new NotSupportedException ("This stream does not support writing");
682
683                         if (array == null)
684                                 throw new ArgumentNullException ("array");
685
686                         if (numBytes < 0)
687                                 throw new ArgumentOutOfRangeException ("numBytes", "Must be >= 0");
688
689                         if (offset < 0)
690                                 throw new ArgumentOutOfRangeException ("offset", "Must be >= 0");
691
692                         // reordered to avoid possible integer overflow
693                         if (numBytes > array.Length - offset)
694                                 throw new ArgumentException ("array too small. numBytes/offset wrong.");
695
696                         if (!async)
697                                 return base.BeginWrite (array, offset, numBytes, userCallback, stateObject);
698
699                         FileStreamAsyncResult result = new FileStreamAsyncResult (userCallback, stateObject);
700                         result.BytesRead = -1;
701                         result.Count = numBytes;
702                         result.OriginalCount = numBytes;
703
704                         if (buf_dirty) {
705                                 MemoryStream ms = new MemoryStream ();
706                                 FlushBufferToStream (ms);
707                                 ms.Write (array, offset, numBytes);
708                                 offset = 0;
709                                 numBytes = (int) ms.Length;
710                         }
711
712                         WriteDelegate w = new WriteDelegate (WriteInternal);
713                         return w.BeginInvoke (array, offset, numBytes, userCallback, stateObject);                      
714                 }
715                 
716                 public override void EndWrite (IAsyncResult asyncResult)
717                 {
718                         if (asyncResult == null)
719                                 throw new ArgumentNullException ("asyncResult");
720
721                         if (!async) {
722                                 base.EndWrite (asyncResult);
723                                 return;
724                         }
725
726                         AsyncResult ares = asyncResult as AsyncResult;
727                         if (ares == null)
728                                 throw new ArgumentException ("Invalid IAsyncResult", "asyncResult");
729
730                         WriteDelegate w = ares.AsyncDelegate as WriteDelegate;
731                         if (w == null)
732                                 throw new ArgumentException ("Invalid IAsyncResult", "asyncResult");
733
734                         w.EndInvoke (asyncResult);
735                         return;
736                 }
737
738                 public override long Seek (long offset, SeekOrigin origin)
739                 {
740                         long pos;
741
742                         if (handle == MonoIO.InvalidHandle)
743                                 throw new ObjectDisposedException ("Stream has been closed");
744                         
745                         // make absolute
746
747                         if(CanSeek == false) {
748                                 throw new NotSupportedException("The stream does not support seeking");
749                         }
750
751                         switch (origin) {
752                         case SeekOrigin.End:
753                                 pos = Length + offset;
754                                 break;
755
756                         case SeekOrigin.Current:
757                                 pos = Position + offset;
758                                 break;
759
760                         case SeekOrigin.Begin:
761                                 pos = offset;
762                                 break;
763
764                         default:
765                                 throw new ArgumentException ("origin", "Invalid SeekOrigin");
766                         }
767
768                         if (pos < 0) {
769                                 /* LAMESPEC: shouldn't this be
770                                  * ArgumentOutOfRangeException?
771                                  */
772                                 throw new IOException("Attempted to Seek before the beginning of the stream");
773                         }
774
775                         if(pos < this.append_startpos) {
776                                 /* More undocumented crap */
777                                 throw new IOException("Can't seek back over pre-existing data in append mode");
778                         }
779
780                         FlushBuffer ();
781
782                         MonoIOError error;
783                 
784                         buf_start = MonoIO.Seek (handle, pos,
785                                                  SeekOrigin.Begin,
786                                                  out error);
787
788                         if (error != MonoIOError.ERROR_SUCCESS) {
789                                 // don't leak the path information for isolated storage
790                                 string fname = (anonymous) ? Path.GetFileName (name) : name;
791                                 throw MonoIO.GetException (fname, error);
792                         }
793                         
794                         return(buf_start);
795                 }
796
797                 public override void SetLength (long value)
798                 {
799                         if (handle == MonoIO.InvalidHandle)
800                                 throw new ObjectDisposedException ("Stream has been closed");
801
802                         if(CanSeek == false)
803                                 throw new NotSupportedException("The stream does not support seeking");
804
805                         if(CanWrite == false)
806                                 throw new NotSupportedException("The stream does not support writing");
807
808                         if(value < 0)
809                                 throw new ArgumentOutOfRangeException("value is less than 0");
810                         
811                         Flush ();
812
813                         MonoIOError error;
814                         
815                         MonoIO.SetLength (handle, value, out error);
816                         if (error != MonoIOError.ERROR_SUCCESS) {
817                                 // don't leak the path information for isolated storage
818                                 string fname = (anonymous) ? Path.GetFileName (name) : name;
819                                 throw MonoIO.GetException (fname, error);
820                         }
821
822                         if (Position > value)
823                                 Position = value;
824                 }
825
826                 public override void Flush ()
827                 {
828                         if (handle == MonoIO.InvalidHandle)
829                                 throw new ObjectDisposedException ("Stream has been closed");
830
831                         FlushBuffer ();
832                         
833                         // The flushing is not actually required, in
834                         //the mono runtime we were mapping flush to
835                         //`fsync' which is not the same.
836                         //
837                         //MonoIO.Flush (handle);
838                 }
839
840 #if !NET_2_0
841                 public override void Close ()
842                 {
843                         Dispose (true);
844                 }
845 #endif
846
847                 public virtual void Lock (long position, long length)
848                 {
849                         if (handle == MonoIO.InvalidHandle)
850                                 throw new ObjectDisposedException ("Stream has been closed");
851                         if (position < 0) {
852                                 throw new ArgumentOutOfRangeException ("position must not be negative");
853                         }
854                         if (length < 0) {
855                                 throw new ArgumentOutOfRangeException ("length must not be negative");
856                         }
857                         if (handle == MonoIO.InvalidHandle) {
858                                 throw new ObjectDisposedException ("Stream has been closed");
859                         }
860                                 
861                         MonoIOError error;
862
863                         MonoIO.Lock (handle, position, length, out error);
864                         if (error != MonoIOError.ERROR_SUCCESS) {
865                                 // don't leak the path information for isolated storage
866                                 string fname = (anonymous) ? Path.GetFileName (name) : name;
867                                 throw MonoIO.GetException (fname, error);
868                         }
869                 }
870
871                 public virtual void Unlock (long position, long length)
872                 {
873                         if (handle == MonoIO.InvalidHandle)
874                                 throw new ObjectDisposedException ("Stream has been closed");
875                         if (position < 0) {
876                                 throw new ArgumentOutOfRangeException ("position must not be negative");
877                         }
878                         if (length < 0) {
879                                 throw new ArgumentOutOfRangeException ("length must not be negative");
880                         }
881                                 
882                         MonoIOError error;
883
884                         MonoIO.Unlock (handle, position, length, out error);
885                         if (error != MonoIOError.ERROR_SUCCESS) {
886                                 // don't leak the path information for isolated storage
887                                 string fname = (anonymous) ? Path.GetFileName (name) : name;
888                                 throw MonoIO.GetException (fname, error);
889                         }
890                 }
891
892                 // protected
893
894                 ~FileStream ()
895                 {
896                         Dispose (false);
897                 }
898
899 #if NET_2_0
900                 protected override void Dispose (bool disposing)
901 #else
902                 protected virtual void Dispose (bool disposing)
903 #endif
904                 {
905                         if (handle != MonoIO.InvalidHandle) {
906                                 FlushBuffer ();
907
908                                 if (owner) {
909                                         MonoIOError error;
910                                 
911                                         MonoIO.Close (handle, out error);
912                                         if (error != MonoIOError.ERROR_SUCCESS) {
913                                                 // don't leak the path information for isolated storage
914                                                 string fname = (anonymous) ? Path.GetFileName (name) : name;
915                                                 throw MonoIO.GetException (fname, error);
916                                         }
917
918                                         handle = MonoIO.InvalidHandle;
919                                 }
920                         }
921
922                         canseek = false;
923                         access = 0;
924                         if (disposing) {
925                                 buf = null;
926                         }
927                         if (disposing)
928                                 GC.SuppressFinalize (this);
929                 }
930
931 #if NET_2_0
932                 public FileSecurity GetAccessControl ()
933                 {
934                         throw new NotImplementedException ();
935                 }
936                 
937                 public void SetAccessControl (FileSecurity fileSecurity)
938                 {
939                         throw new NotImplementedException ();
940                 }
941 #endif
942
943                 // private.
944
945                 // ReadSegment, WriteSegment, FlushBuffer,
946                 // RefillBuffer and ReadData should only be called
947                 // when the Monitor lock is held, but these methods
948                 // grab it again just to be safe.
949
950                 private int ReadSegment (byte [] dest, int dest_offset, int count)
951                 {
952                         if (count > buf_length - buf_offset) {
953                                 count = buf_length - buf_offset;
954                         }
955                         
956                         if (count > 0) {
957                                 Buffer.BlockCopy (buf, buf_offset,
958                                                   dest, dest_offset,
959                                                   count);
960                                 buf_offset += count;
961                         }
962                         
963                         return(count);
964                 }
965
966                 private int WriteSegment (byte [] src, int src_offset,
967                                           int count)
968                 {
969                         if (count > buf_size - buf_offset) {
970                                 count = buf_size - buf_offset;
971                         }
972                         
973                         if (count > 0) {
974                                 Buffer.BlockCopy (src, src_offset,
975                                                   buf, buf_offset,
976                                                   count);
977                                 buf_offset += count;
978                                 if (buf_offset > buf_length) {
979                                         buf_length = buf_offset;
980                                 }
981                                 
982                                 buf_dirty = true;
983                         }
984                         
985                         return(count);
986                 }
987
988                 void FlushBufferToStream (Stream st)
989                 {
990                         if (buf_dirty) {
991                                 if (CanSeek == true) {
992                                         MonoIOError error;
993                                         MonoIO.Seek (handle, buf_start,
994                                                      SeekOrigin.Begin,
995                                                      out error);
996                                         if (error != MonoIOError.ERROR_SUCCESS) {
997                                                 // don't leak the path information for isolated storage
998                                                 string fname = (anonymous) ? Path.GetFileName (name) : name;
999                                                 throw MonoIO.GetException (fname, error);
1000                                         }
1001                                 }
1002                                 st.Write (buf, 0, buf_length);
1003                         }
1004
1005                         buf_start += buf_offset;
1006                         buf_offset = buf_length = 0;
1007                         buf_dirty = false;
1008                 }
1009
1010                 private void FlushBuffer ()
1011                 {
1012                         if (buf_dirty) {
1013                                 MonoIOError error;
1014                                 
1015                                 if (CanSeek == true) {
1016                                         MonoIO.Seek (handle, buf_start,
1017                                                      SeekOrigin.Begin,
1018                                                      out error);
1019                                         if (error != MonoIOError.ERROR_SUCCESS) {
1020                                                 // don't leak the path information for isolated storage
1021                                                 string fname = (anonymous) ? Path.GetFileName (name) : name;
1022                                                 throw MonoIO.GetException (fname, error);
1023                                         }
1024                                 }
1025                                 MonoIO.Write (handle, buf, 0,
1026                                               buf_length, out error);
1027
1028                                 if (error != MonoIOError.ERROR_SUCCESS) {
1029                                         // don't leak the path information for isolated storage
1030                                         string fname = (anonymous) ? Path.GetFileName (name) : name;
1031                                         throw MonoIO.GetException (fname, error);
1032                                 }
1033                         }
1034
1035                         buf_start += buf_offset;
1036                         buf_offset = buf_length = 0;
1037                         buf_dirty = false;
1038                 }
1039
1040                 private void FlushBufferIfDirty ()
1041                 {
1042                         if (buf_dirty)
1043                                 FlushBuffer ();
1044                 }
1045
1046                 private void RefillBuffer ()
1047                 {
1048                         FlushBuffer();
1049                         
1050                         buf_length = ReadData (handle, buf, 0,
1051                                                buf_size);
1052                 }
1053
1054                 private int ReadData (IntPtr handle, byte[] buf, int offset,
1055                                       int count)
1056                 {
1057                         MonoIOError error;
1058                         int amount = 0;
1059
1060                         /* when async == true, if we get here we don't suport AIO or it's disabled
1061                          * and we're using the threadpool */
1062                         amount = MonoIO.Read (handle, buf, offset, count, out error);
1063                         if (error == MonoIOError.ERROR_BROKEN_PIPE) {
1064                                 amount = 0; // might not be needed, but well...
1065                         } else if (error != MonoIOError.ERROR_SUCCESS) {
1066                                 // don't leak the path information for isolated storage
1067                                 string fname = (anonymous) ? Path.GetFileName (name) : name;
1068                                 throw MonoIO.GetException (fname, error);
1069                         }
1070                         
1071                         /* Check for read error */
1072                         if(amount == -1) {
1073                                 throw new IOException ();
1074                         }
1075                         
1076                         return(amount);
1077                 }
1078                                 
1079                 private void InitBuffer (int size, bool noBuffering)
1080                 {
1081                         if (noBuffering) {
1082                                 size = 0;
1083                                 // We need a buffer for the ReadByte method. This buffer won't
1084                                 // be used for anything else since buf_size==0.
1085                                 buf = new byte [1];
1086                         }
1087                         else {
1088                                 if (size <= 0)
1089                                         throw new ArgumentOutOfRangeException ("bufferSize", "Positive number required.");
1090                                 if (size < 8)
1091                                         size = 8;
1092                                 buf = new byte [size];
1093                         }
1094                                         
1095                         buf_size = size;
1096                         buf_start = 0;
1097                         buf_offset = buf_length = 0;
1098                         buf_dirty = false;
1099                 }
1100
1101                 // fields
1102
1103                 internal const int DefaultBufferSize = 8192;
1104
1105                 private FileAccess access;
1106                 private bool owner;
1107                 private bool async;
1108                 private bool canseek;
1109                 private long append_startpos;
1110                 private bool anonymous;
1111
1112                 private byte [] buf;                    // the buffer
1113                 private int buf_size;                   // capacity in bytes
1114                 private int buf_length;                 // number of valid bytes in buffer
1115                 private int buf_offset;                 // position of next byte
1116                 private bool buf_dirty;                 // true if buffer has been written to
1117                 private long buf_start;                 // location of buffer in file
1118                 private string name = "[Unknown]";      // name of file.
1119
1120                 IntPtr handle;                          // handle to underlying file
1121         }
1122 }