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