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