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