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