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