2003-11-14 Ben Maurer <bmaurer@users.sourceforge.net>
[mono.git] / mcs / class / corlib / System.IO / FileStream.cs
1 //\r
2 // System.IO/FileStream.cs\r
3 //\r
4 // Authors:\r
5 //   Dietmar Maurer (dietmar@ximian.com)\r
6 //   Dan Lewis (dihlewis@yahoo.co.uk)\r
7 //\r
8 // (C) 2001 Ximian, Inc.  http://www.ximian.com\r
9 //\r
10 \r
11 using System;\r
12 using System.Runtime.CompilerServices;\r
13 using System.Runtime.InteropServices;
14 \r
15 // FIXME: emit the correct exceptions everywhere. add error handling.\r
16 \r
17 namespace System.IO\r
18 {\r
19 \r
20         public class FileStream : Stream\r
21         {\r
22                 // construct from handle\r
23                 \r
24                 public FileStream (IntPtr handle, FileAccess access)\r
25                         : this (handle, access, true, DefaultBufferSize, false) {}\r
26 \r
27                 public FileStream (IntPtr handle, FileAccess access, bool ownsHandle)\r
28                         : this (handle, access, ownsHandle, DefaultBufferSize, false) {}\r
29                 \r
30                 public FileStream (IntPtr handle, FileAccess access, bool ownsHandle, int bufferSize)\r
31                         : this (handle, access, ownsHandle, bufferSize, false) {}\r
32
33                 public FileStream (IntPtr handle, FileAccess access, bool ownsHandle, int bufferSize, bool isAsync)
34                         : this (handle, access, ownsHandle, bufferSize, isAsync, false) {}
35
36                 internal FileStream (IntPtr handle, FileAccess access, bool ownsHandle, int bufferSize, bool isAsync, bool noBuffering)
37                 {
38                         if (access < FileAccess.Read || access > FileAccess.ReadWrite)
39                                 throw new ArgumentOutOfRangeException ("access");
40
41                         this.handle = handle;
42                         this.access = access;
43                         this.owner = ownsHandle;
44                         this.async = isAsync;
45
46                         MonoIOError error;
47                         MonoFileType ftype = MonoIO.GetFileType (handle, out error);
48                         
49                         if (ftype == MonoFileType.Unknown) {
50                                 throw new IOException ("Invalid handle.");
51                         } else if (ftype == MonoFileType.Disk) {
52                                 this.canseek = true;
53                         } else {
54                                 this.canseek = false;
55                         }
56
57                         InitBuffer (bufferSize, noBuffering);
58
59                         /* Can't set append mode */
60                         this.append_startpos=0;
61                 }
62
63                 // construct from filename\r
64                 \r
65                 public FileStream (string name, FileMode mode)
66                         : this (name, mode, (mode == FileMode.Append ? FileAccess.Write : FileAccess.ReadWrite), FileShare.Read, DefaultBufferSize, false) { }\r
67 \r
68                 public FileStream (string name, FileMode mode, FileAccess access)\r
69                         : this (name, mode, access, FileShare.ReadWrite, DefaultBufferSize, false) { }\r
70 \r
71                 public FileStream (string name, FileMode mode, FileAccess access, FileShare share)\r
72                         : this (name, mode, access, share, DefaultBufferSize, false) { }\r
73                 \r
74                 public FileStream (string name, FileMode mode, FileAccess access, FileShare share, int bufferSize)\r
75                         : this (name, mode, access, share, bufferSize, false) { }\r
76
77                 public FileStream (string name, FileMode mode, FileAccess access, FileShare share, int bufferSize, bool isAsync)
78                 {
79                         if (name == null) {
80                                 throw new ArgumentNullException ("name");
81                         }
82                         
83                         if (name == "") {
84                                 throw new ArgumentException ("Name is empty");
85                         }
86
87                         if (mode < FileMode.CreateNew || mode > FileMode.Append)
88                                 throw new ArgumentOutOfRangeException ("mode");
89
90                         if (access < FileAccess.Read || access > FileAccess.ReadWrite)
91                                 throw new ArgumentOutOfRangeException ("access");
92
93                         if (share < FileShare.None || share > FileShare.ReadWrite)
94                                 throw new ArgumentOutOfRangeException ("share");
95
96                         if (name.IndexOfAny (Path.InvalidPathChars) != -1) {
97                                 throw new ArgumentException ("Name has invalid chars");
98                         }
99
100                         if (Directory.Exists (name)) {
101                                 throw new UnauthorizedAccessException ("Access to the path '" + Path.GetFullPath (name) + "' is denied.");
102                         }
103
104                         InitBuffer (bufferSize, false);
105
106                         /* Append streams can't be read (see FileMode
107                          * docs)
108                          */
109                         if (mode==FileMode.Append &&
110                             (access&FileAccess.Read)==FileAccess.Read) {
111                                 throw new ArgumentException("Append streams can not be read");
112                         }
113
114                         if ((access & FileAccess.Write) == 0 &&
115                             (mode != FileMode.Open && mode != FileMode.OpenOrCreate))
116                                 throw new ArgumentException ("access and mode not compatible");
117
118                         this.name = name;
119
120                         // TODO: demand permissions
121
122                         MonoIOError error;
123
124                         this.handle = MonoIO.Open (name, mode, access, share,
125                                                    out error);
126                         if (handle == MonoIO.InvalidHandle) {
127                                 throw MonoIO.GetException (name, error);
128                         }
129
130                         this.access = access;
131                         this.owner = true;
132                         this.async = isAsync;
133
134                         /* Can we open non-files by name? */
135                         
136                         if (MonoIO.GetFileType (handle, out error) ==
137                             MonoFileType.Disk) {
138                                 this.canseek = true;
139                         } else {
140                                 this.canseek = false;
141                         }
142
143
144                         if (mode==FileMode.Append) {
145                                 this.Seek (0, SeekOrigin.End);
146                                 this.append_startpos=this.Position;
147                         } else {
148                                 this.append_startpos=0;
149                         }
150                 }
151
152                 // properties\r
153                 \r
154                 public override bool CanRead {\r
155                         get {\r
156                                 return access == FileAccess.Read ||\r
157                                        access == FileAccess.ReadWrite;\r
158                         }\r
159                 }\r
160 \r
161                 public override bool CanWrite {\r
162                         get {\r
163                                 return access == FileAccess.Write ||\r
164                                        access == FileAccess.ReadWrite;\r
165                         }\r
166                 }\r
167                 
168                 public override bool CanSeek {
169                         get {
170                                 return(canseek);
171                         }
172                 }
173
174                 public virtual bool IsAsync {
175                         get {
176                                 return (async);
177                         }
178                 }
179
180                 public string Name {\r
181                         get {\r
182                                 return name; \r
183                         }\r
184                 }\r
185
186                 public override long Length {
187                         get {
188                                 if (handle == MonoIO.InvalidHandle)
189                                         throw new ObjectDisposedException ("Stream has been closed");
190
191                                 if (!canseek)
192                                         throw new NotSupportedException ("The stream does not support seeking");
193
194                                 // Buffered data might change the length of the stream
195                                 FlushBufferIfDirty ();
196
197                                 MonoIOError error;
198                                 
199                                 return MonoIO.GetLength (handle, out error);
200                         }
201                 }
202
203                 public override long Position {
204                         get {
205                                 if (handle == MonoIO.InvalidHandle)
206                                         throw new ObjectDisposedException ("Stream has been closed");
207
208                                 if(CanSeek == false)
209                                         throw new NotSupportedException("The stream does not support seeking");
210                                 
211                                 lock(this) {
212                                         return(buf_start + buf_offset);
213                                 }
214                         }
215                         set {
216                                 if(CanSeek == false) {
217                                         throw new NotSupportedException("The stream does not support seeking");
218                                 }
219
220                                 if(value < 0) {
221                                         throw new ArgumentOutOfRangeException("Attempt to set the position to a negative value");
222                                 }
223                                 
224                                 Seek (value, SeekOrigin.Begin);
225                         }
226                 }
227
228                 public virtual IntPtr Handle {
229                         get {
230                                 return handle;
231                         }
232                 }
233
234                 // methods
235
236                 public override int ReadByte ()
237                 {
238                         if (handle == MonoIO.InvalidHandle)
239                                 throw new ObjectDisposedException ("Stream has been closed");
240
241                         if (!CanRead)
242                                 throw new NotSupportedException ("Stream does not support reading");
243                         
244                         lock(this) {
245                                 if (buf_offset >= buf_length) {
246                                         RefillBuffer ();
247
248                                         if (buf_length == 0) {
249                                                 return -1;
250                                         }
251                                 }
252                                 
253                                 return(buf [buf_offset ++]);
254                         }
255                 }
256
257                 public override void WriteByte (byte value)
258                 {
259                         if (handle == MonoIO.InvalidHandle)
260                                 throw new ObjectDisposedException ("Stream has been closed");
261
262                         if (!CanWrite)
263                                 throw new NotSupportedException ("Stream does not support writing");
264
265                         lock(this) {
266                                 if (buf_offset == buf_size) {
267                                         FlushBuffer ();
268                                 }
269
270                                 buf [buf_offset ++] = value;
271                                 if (buf_offset > buf_length) {
272                                         buf_length = buf_offset;
273                                 }
274
275                                 buf_dirty = true;
276                         }
277                 }
278
279                 public override int Read ([In,Out] byte[] dest, int dest_offset, int count)
280                 {
281                         if (handle == MonoIO.InvalidHandle)
282                                 throw new ObjectDisposedException ("Stream has been closed");
283                         if (dest == null)
284                                 throw new ArgumentNullException ("destination array is null");
285                         if (!CanRead)
286                                 throw new NotSupportedException ("Stream does not support reading");
287                         int len = dest.Length;
288                         if (dest_offset < 0 || count < 0)
289                                 throw new ArgumentException ("dest or count is negative");
290                         if (dest_offset > len)
291                                 throw new ArgumentException ("destination offset is beyond array size");
292                         if ((dest_offset + count) > len)
293                                 throw new ArgumentException ("Reading would overrun buffer");
294
295                         
296                         int copied = 0;
297
298                         lock(this) {
299                                 int n = ReadSegment (dest, dest_offset, count);
300                                 copied += n;
301                                 count -= n;
302                         
303                                 if (count == 0) {
304                                         /* If there was already enough
305                                          * buffered, no need to read
306                                          * more from the file.
307                                          */
308                                         return (copied);
309                                 }
310
311                                 if (count > buf_size) {
312                                         /* Read as much as we can, up
313                                          * to count bytes
314                                          */
315                                         FlushBuffer();
316                                         n = ReadData (handle, dest,
317                                                       dest_offset+copied,
318                                                       count);
319                                 
320                                         /* Make the next buffer read
321                                          * start from the right place
322                                          */
323                                         buf_start += n;
324                                 } else {
325                                         RefillBuffer ();
326                                         n = ReadSegment (dest,
327                                                          dest_offset+copied,
328                                                          count);
329                                 }
330
331                                 copied += n;
332
333                                 return(copied);
334                         }
335                 }
336
337                 public override void Write (byte[] src, int src_offset, int count)
338                 {
339                         if (handle == MonoIO.InvalidHandle)
340                                 throw new ObjectDisposedException ("Stream has been closed");
341
342                         if (src == null)
343                                 throw new ArgumentNullException ("src");
344
345                         if (src_offset < 0)
346                                 throw new ArgumentOutOfRangeException ("src_offset");
347
348                         if (count < 0)
349                                 throw new ArgumentOutOfRangeException ("count");
350
351                         if (!CanWrite)
352                                 throw new NotSupportedException ("Stream does not support writing");
353
354                         if (count > buf_size) {
355                                 // shortcut for long writes
356                                 MonoIOError error;
357
358                                 FlushBuffer ();
359
360                                 MonoIO.Write (handle, src, src_offset, count, out error);
361                                 buf_start += count;
362                         } else {
363
364                                 int copied = 0;
365                                 while (count > 0) {
366                                         
367                                         int n = WriteSegment (src, src_offset + copied, count);
368                                         copied += n;
369                                         count -= n;
370
371                                         if (count == 0) {
372                                                 break;
373                                         }
374
375                                         FlushBuffer ();
376                                 }
377                         }
378                 }
379
380                 public override long Seek (long offset, SeekOrigin origin)
381                 {
382                         long pos;
383
384                         if (handle == MonoIO.InvalidHandle)
385                                 throw new ObjectDisposedException ("Stream has been closed");
386                         
387                         // make absolute
388
389                         if(CanSeek == false) {
390                                 throw new NotSupportedException("The stream does not support seeking");
391                         }
392
393                         lock(this) {
394
395                                 switch (origin) {
396                                 case SeekOrigin.End:
397                                         pos = Length + offset;
398                                         break;
399
400                                 case SeekOrigin.Current:
401                                         pos = Position + offset;
402                                         break;
403
404                                 case SeekOrigin.Begin: default:
405                                         pos = offset;
406                                         break;
407                                 }
408
409                                 if (pos < 0) {
410                                         /* LAMESPEC: shouldn't this be
411                                          * ArgumentOutOfRangeException?
412                                          */
413                                         throw new IOException("Attempted to Seek before the beginning of the stream");
414                                 }
415
416                                 if(pos < this.append_startpos) {
417                                         /* More undocumented crap */
418                                         throw new IOException("Can't seek back over pre-existing data in append mode");
419                                 }
420
421                                 if (buf_length > 0) {
422                                         if (pos >= buf_start &&
423                                                 pos <= buf_start + buf_length) {
424                                                 buf_offset = (int) (pos - buf_start);
425                                                 return pos;
426                                         }
427                                 }
428
429                                 FlushBuffer ();
430
431                                 MonoIOError error;
432                         
433                                 buf_start = MonoIO.Seek (handle, pos,
434                                                          SeekOrigin.Begin,
435                                                          out error);
436                                 
437                                 return(buf_start);
438                         }
439                 }
440
441                 public override void SetLength (long length)
442                 {
443                         if(CanSeek == false) {
444                                 throw new NotSupportedException("The stream does not support seeking");
445                         }
446
447                         if(CanWrite == false) {
448                                 throw new NotSupportedException("The stream does not support writing");
449                         }
450
451                         if(length < 0) {
452                                 throw new ArgumentOutOfRangeException("Length is less than 0");
453                         }
454                         
455                         Flush ();
456
457                         MonoIOError error;
458                         
459                         MonoIO.SetLength (handle, length, out error);
460                 }
461
462                 public override void Flush ()
463                 {
464                         if (handle == MonoIO.InvalidHandle)
465                                 throw new ObjectDisposedException ("Stream has been closed");
466
467                         lock(this) {
468                                 FlushBuffer ();
469                         }
470                         
471                         // The flushing is not actually required, in
472                         //the mono runtime we were mapping flush to
473                         //`fsync' which is not the same.
474                         //
475                         //MonoIO.Flush (handle);
476                 }
477
478                 public override void Close ()\r
479                 {\r
480                         Dispose (true);\r
481                         GC.SuppressFinalize (this);     // remove from finalize queue\r
482                 }\r
483 \r
484                 [MonoTODO]
485                 public virtual void Lock (long position, long length)
486                 {
487                         throw new NotImplementedException ();
488                 }
489
490                 [MonoTODO]
491                 public virtual void Unlock (long position, long length)
492                 {
493                         throw new NotImplementedException ();
494                 }
495
496                 // protected\r
497 \r
498                 ~FileStream ()\r
499                 {\r
500                         Dispose (false);\r
501                 }\r
502
503                 protected virtual void Dispose (bool disposing) {
504                         if (handle != MonoIO.InvalidHandle) {
505                                 lock(this) {
506                                         FlushBuffer ();
507                                 }
508
509                                 if (owner) {
510                                         MonoIOError error;
511                                 
512                                         MonoIO.Close (handle, out error);
513
514                                         handle = MonoIO.InvalidHandle;
515                                 }
516                         }
517
518                         canseek = false;
519                         access = 0;
520                         if (disposing) {
521                                 buf = null;
522                         }
523                 }
524
525                 // private.
526
527                 // ReadSegment, WriteSegment, FlushBuffer,
528                 // RefillBuffer and ReadData should only be called
529                 // when the Monitor lock is held, but these methods
530                 // grab it again just to be safe.
531
532                 private int ReadSegment (byte [] dest, int dest_offset,
533                                          int count)
534                 {
535                         if (count > buf_length - buf_offset) {
536                                 count = buf_length - buf_offset;
537                         }
538                         
539                         if (count > 0) {
540                                 Buffer.BlockCopy (buf, buf_offset,
541                                                   dest, dest_offset,
542                                                   count);
543                                 buf_offset += count;
544                         }
545                         
546                         return(count);
547                 }
548
549                 private int WriteSegment (byte [] src, int src_offset,
550                                           int count)
551                 {
552                         if (count > buf_size - buf_offset) {
553                                 count = buf_size - buf_offset;
554                         }
555                         
556                         if (count > 0) {
557                                 Buffer.BlockCopy (src, src_offset,
558                                                   buf, buf_offset,
559                                                   count);
560                                 buf_offset += count;
561                                 if (buf_offset > buf_length) {
562                                         buf_length = buf_offset;
563                                 }
564                                 
565                                 buf_dirty = true;
566                         }
567                         
568                         return(count);
569                 }
570
571                 private void FlushBuffer ()
572                 {
573                         if (buf_dirty) {
574                                 MonoIOError error;
575                                 
576                                 if (CanSeek == true) {
577                                         MonoIO.Seek (handle, buf_start,
578                                                      SeekOrigin.Begin,
579                                                      out error);
580                                 }
581                                 MonoIO.Write (handle, buf, 0,
582                                               buf_length, out error);
583                         }
584
585                         buf_start += buf_offset;
586                         buf_offset = buf_length = 0;
587                         buf_dirty = false;
588                 }
589
590                 private void FlushBufferIfDirty ()
591                 {
592                         if (buf_dirty)
593                                 FlushBuffer ();
594                 }
595
596                 private void RefillBuffer ()
597                 {
598                         FlushBuffer();
599                         
600                         buf_length = ReadData (handle, buf, 0,
601                                                buf_size);
602                 }
603
604                 private int ReadData (IntPtr handle, byte[] buf, int offset,
605                                       int count)
606                 {
607                         MonoIOError error;
608                         
609                         int amount = MonoIO.Read (handle, buf, offset,
610                                                   count, out error);
611                         
612                         /* Check for read error */
613                         if(amount == -1) {
614                                 /* Kludge around broken pipes */
615                                 if(error == MonoIOError.ERROR_BROKEN_PIPE) {
616                                         amount = 0;
617                                 } else {
618                                         throw new IOException ();
619                                 }
620                         }
621                         
622                         return(amount);
623                 }
624                                 
625                 private void InitBuffer (int size, bool noBuffering)\r
626                 {\r
627                         if (noBuffering)
628                                 size = 0;
629                         else {
630                                 if (size <= 0)
631                                         throw new ArgumentOutOfRangeException ("bufferSize", "Positive number required.");
632                                 if (size < 8)
633                                         size = 8;
634                         }
635                                         
636                         buf = new byte [size];\r
637                         buf_size = size;\r
638                         buf_start = 0;\r
639                         buf_offset = buf_length = 0;\r
640                         buf_dirty = false;\r
641                 }\r
642 \r
643                 // fields\r
644 \r
645                 private static int DefaultBufferSize = 8192;\r
646 \r
647                 private FileAccess access;\r
648                 private bool owner;\r
649                 private bool async;\r
650                 private bool canseek;
651                 private long append_startpos;
652                 
653 \r
654                 private byte [] buf;                    // the buffer\r
655                 private int buf_size;                   // capacity in bytes\r
656                 private int buf_length;                 // number of valid bytes in buffer\r
657                 private int buf_offset;                 // position of next byte\r
658                 private bool buf_dirty;                 // true if buffer has been written to\r
659                 private long buf_start;                 // location of buffer in file\r
660                 private string name = "[Unknown]";      // name of file.\r
661 \r
662                 IntPtr handle;                          // handle to underlying file\r
663         }\r
664 }