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