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