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