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