Merge pull request #3968 from BrzVlad/fix-monitor-exception
[mono.git] / mcs / class / Mono.Posix / Mono.Unix / StdioFileStream.cs
1 //
2 // Mono.Unix/StdioFileStream.cs
3 //
4 // Authors:
5 //   Jonathan Pryor (jonpryor@vt.edu)
6 //
7 // (C) 2005-2006 Jonathan Pryor
8 //
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
16 // 
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
19 // 
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27 //
28
29 using System;
30 using System.IO;
31 using System.Runtime.InteropServices;
32 using System.Text;
33 using Mono.Unix;
34
35 namespace Mono.Unix {
36
37         public class StdioFileStream : Stream
38         {
39                 public static readonly IntPtr InvalidFileStream  = IntPtr.Zero;
40                 public static readonly IntPtr StandardInput  = Native.Stdlib.stdin;
41                 public static readonly IntPtr StandardOutput = Native.Stdlib.stdout;
42                 public static readonly IntPtr StandardError  = Native.Stdlib.stderr;
43
44                 public StdioFileStream (IntPtr fileStream)
45                         : this (fileStream, true) {}
46
47                 public StdioFileStream (IntPtr fileStream, bool ownsHandle)
48                 {
49                         InitStream (fileStream, ownsHandle);
50                 }
51
52                 public StdioFileStream (IntPtr fileStream, FileAccess access)
53                         : this (fileStream, access, true) {}
54
55                 public StdioFileStream (IntPtr fileStream, FileAccess access, bool ownsHandle)
56                 {
57                         InitStream (fileStream, ownsHandle);
58                         InitCanReadWrite (access);
59                 }
60
61                 public StdioFileStream (string path)
62                 {
63                         if (path == null)
64                                 throw new ArgumentNullException ("path");
65                         InitStream (Fopen (path, "rb"), true);
66                 }
67
68                 public StdioFileStream (string path, string mode)
69                 {
70                         if (path == null)
71                                 throw new ArgumentNullException ("path");
72                         InitStream (Fopen (path, mode), true);
73                 }
74
75                 public StdioFileStream (string path, FileMode mode)
76                 {
77                         if (path == null)
78                                 throw new ArgumentNullException ("path");
79                         InitStream (Fopen (path, ToFopenMode (path, mode)), true);
80                 }
81
82                 public StdioFileStream (string path, FileAccess access)
83                 {
84                         if (path == null)
85                                 throw new ArgumentNullException ("path");
86                         InitStream (Fopen (path, ToFopenMode (path, access)), true);
87                         InitCanReadWrite (access);
88                 }
89
90                 public StdioFileStream (string path, FileMode mode, FileAccess access)
91                 {
92                         if (path == null)
93                                 throw new ArgumentNullException ("path");
94                         InitStream (Fopen (path, ToFopenMode (path, mode, access)), true);
95                         InitCanReadWrite (access);
96                 }
97
98                 private static IntPtr Fopen (string path, string mode)
99                 {
100                         if (path.Length == 0)
101                                 throw new ArgumentException ("path");
102                         if (mode == null)
103                                 throw new ArgumentNullException ("mode");
104                         IntPtr f = Native.Stdlib.fopen (path, mode);
105                         if (f == IntPtr.Zero)
106                                 throw new DirectoryNotFoundException ("path", 
107                                                 UnixMarshal.CreateExceptionForLastError ());
108                         return f;
109                 }
110
111                 private void InitStream (IntPtr fileStream, bool ownsHandle)
112                 {
113                         if (InvalidFileStream == fileStream)
114                                 throw new ArgumentException (Locale.GetText ("Invalid file stream"), "fileStream");
115                         
116                         this.file = fileStream;
117                         this.owner = ownsHandle;
118                         
119                         try {
120                                 long offset = Native.Stdlib.fseek (file, 0, Native.SeekFlags.SEEK_CUR);
121                                 if (offset != -1)
122                                         canSeek = true;
123                                 Native.Stdlib.fread (IntPtr.Zero, 0, 0, file);
124                                 if (Native.Stdlib.ferror (file) == 0)
125                                         canRead = true;
126                                 Native.Stdlib.fwrite (IntPtr.Zero, 0, 0, file);
127                                 if (Native.Stdlib.ferror (file) == 0)
128                                         canWrite = true;  
129                                 Native.Stdlib.clearerr (file);
130                         }
131                         catch (Exception) {
132                                 throw new ArgumentException (Locale.GetText ("Invalid file stream"), "fileStream");
133                         }
134                         GC.KeepAlive (this);
135                 }
136
137                 private void InitCanReadWrite (FileAccess access)
138                 {
139                         canRead = canRead && 
140                                 (access == FileAccess.Read || access == FileAccess.ReadWrite);
141                         canWrite = canWrite &&
142                                 (access == FileAccess.Write || access == FileAccess.ReadWrite);
143                 }
144
145                 private static string ToFopenMode (string file, FileMode mode)
146                 {
147                         string cmode = Native.NativeConvert.ToFopenMode (mode);
148                         AssertFileMode (file, mode);
149                         return cmode;
150                 }
151
152                 private static string ToFopenMode (string file, FileAccess access)
153                 {
154                         return Native.NativeConvert.ToFopenMode (access);
155                 }
156
157                 private static string ToFopenMode (string file, FileMode mode, FileAccess access)
158                 {
159                         string cmode = Native.NativeConvert.ToFopenMode (mode, access);
160                         bool exists = AssertFileMode (file, mode);
161                         // HACK: for open-or-create & read, mode is "rb", which doesn't create
162                         // files.  If the file doesn't exist, we need to use "w+b" to ensure
163                         // file creation.
164                         if (mode == FileMode.OpenOrCreate && access == FileAccess.Read && !exists)
165                                 cmode = "w+b";
166                         return cmode;
167                 }
168
169                 private static bool AssertFileMode (string file, FileMode mode)
170                 {
171                         bool exists = FileExists (file);
172                         if (mode == FileMode.CreateNew && exists)
173                                 throw new IOException ("File exists and FileMode.CreateNew specified");
174                         if ((mode == FileMode.Open || mode == FileMode.Truncate) && !exists)
175                                 throw new FileNotFoundException ("File doesn't exist and FileMode.Open specified", file);
176                         return exists;
177                 }
178
179                 private static bool FileExists (string file)
180                 {
181                         bool found = false;
182                         IntPtr f = Native.Stdlib.fopen (file, "r");
183                         found = f != IntPtr.Zero;
184                         if (f != IntPtr.Zero)
185                                 Native.Stdlib.fclose (f);
186                         return found;
187                 }
188
189                 private void AssertNotDisposed ()
190                 {
191                         if (file == InvalidFileStream)
192                                 throw new ObjectDisposedException ("Invalid File Stream");
193                         GC.KeepAlive (this);
194                 }
195
196                 public IntPtr Handle {
197                         get {
198                                 AssertNotDisposed (); 
199                                 GC.KeepAlive (this);
200                                 return file;
201                         }
202                 }
203
204                 public override bool CanRead {
205                         get {return canRead;}
206                 }
207
208                 public override bool CanSeek {
209                         get {return canSeek;}
210                 }
211
212                 public override bool CanWrite {
213                         get {return canWrite;}
214                 }
215
216                 public override long Length {
217                         get {
218                                 AssertNotDisposed ();
219                                 if (!CanSeek)
220                                         throw new NotSupportedException ("File Stream doesn't support seeking");
221                                 long curPos = Native.Stdlib.ftell (file);
222                                 if (curPos == -1)
223                                         throw new NotSupportedException ("Unable to obtain current file position");
224                                 int r = Native.Stdlib.fseek (file, 0, Native.SeekFlags.SEEK_END);
225                                 UnixMarshal.ThrowExceptionForLastErrorIf (r);
226
227                                 long endPos = Native.Stdlib.ftell (file);
228                                 if (endPos == -1)
229                                         UnixMarshal.ThrowExceptionForLastError ();
230
231                                 r = Native.Stdlib.fseek (file, curPos, Native.SeekFlags.SEEK_SET);
232                                 UnixMarshal.ThrowExceptionForLastErrorIf (r);
233
234                                 GC.KeepAlive (this);
235                                 return endPos;
236                         }
237                 }
238
239                 public override long Position {
240                         get {
241                                 AssertNotDisposed ();
242                                 if (!CanSeek)
243                                         throw new NotSupportedException ("The stream does not support seeking");
244                                 long pos = Native.Stdlib.ftell (file);
245                                 if (pos == -1)
246                                         UnixMarshal.ThrowExceptionForLastError ();
247                                 GC.KeepAlive (this);
248                                 return (long) pos;
249                         }
250                         set {
251                                 AssertNotDisposed ();
252                                 Seek (value, SeekOrigin.Begin);
253                         }
254                 }
255
256                 public void SaveFilePosition (Native.FilePosition pos)
257                 {
258                         AssertNotDisposed ();
259                         int r = Native.Stdlib.fgetpos (file, pos);
260                         UnixMarshal.ThrowExceptionForLastErrorIf (r);
261                         GC.KeepAlive (this);
262                 }
263
264                 public void RestoreFilePosition (Native.FilePosition pos)
265                 {
266                         AssertNotDisposed ();
267                         if (pos == null)
268                                 throw new ArgumentNullException ("value");
269                         int r = Native.Stdlib.fsetpos (file, pos);
270                         UnixMarshal.ThrowExceptionForLastErrorIf (r);
271                         GC.KeepAlive (this);
272                 }
273
274                 public override void Flush ()
275                 {
276                         AssertNotDisposed ();
277                         int r = Native.Stdlib.fflush (file);
278                         if (r != 0)
279                                 UnixMarshal.ThrowExceptionForLastError ();
280                         GC.KeepAlive (this);
281                 }
282
283                 public override unsafe int Read ([In, Out] byte[] buffer, int offset, int count)
284                 {
285                         AssertNotDisposed ();
286                         AssertValidBuffer (buffer, offset, count);
287                         if (!CanRead)
288                                 throw new NotSupportedException ("Stream does not support reading");
289                                  
290                         ulong r = 0;
291                         fixed (byte* buf = &buffer[offset]) {
292                                 r = Native.Stdlib.fread (buf, 1, (ulong) count, file);
293                         }
294                         if (r != (ulong) count) {
295                                 if (Native.Stdlib.ferror (file) != 0)
296                                         throw new IOException ();
297                         }
298                         GC.KeepAlive (this);
299                         return (int) r;
300                 }
301
302                 private void AssertValidBuffer (byte[] buffer, int offset, int count)
303                 {
304                         if (buffer == null)
305                                 throw new ArgumentNullException ("buffer");
306                         if (offset < 0)
307                                 throw new ArgumentOutOfRangeException ("offset", "< 0");
308                         if (count < 0)
309                                 throw new ArgumentOutOfRangeException ("count", "< 0");
310                         if (offset > buffer.Length)
311                                 throw new ArgumentException ("destination offset is beyond array size");
312                         if (offset > (buffer.Length - count))
313                                 throw new ArgumentException ("would overrun buffer");
314                 }
315
316                 public void Rewind ()
317                 {
318                         AssertNotDisposed ();
319                         Native.Stdlib.rewind (file);
320                         GC.KeepAlive (this);
321                 }
322
323                 public override long Seek (long offset, SeekOrigin origin)
324                 {
325                         AssertNotDisposed ();
326                         if (!CanSeek)
327                                 throw new NotSupportedException ("The File Stream does not support seeking");
328
329                         Native.SeekFlags sf = Native.SeekFlags.SEEK_CUR;
330                         switch (origin) {
331                                 case SeekOrigin.Begin:   sf = Native.SeekFlags.SEEK_SET; break;
332                                 case SeekOrigin.Current: sf = Native.SeekFlags.SEEK_CUR; break;
333                                 case SeekOrigin.End:     sf = Native.SeekFlags.SEEK_END; break;
334                                 default: throw new ArgumentException ("origin");
335                         }
336
337                         int r = Native.Stdlib.fseek (file, offset, sf);
338                         if (r != 0)
339                                 throw new IOException ("Unable to seek",
340                                                 UnixMarshal.CreateExceptionForLastError ());
341
342                         long pos = Native.Stdlib.ftell (file);
343                         if (pos == -1)
344                                 throw new IOException ("Unable to get current file position",
345                                                 UnixMarshal.CreateExceptionForLastError ());
346
347                         GC.KeepAlive (this);
348                         return pos;
349                 }
350
351                 public override void SetLength (long value)
352                 {
353                         throw new NotSupportedException ("ANSI C doesn't provide a way to truncate a file");
354                 }
355
356                 public override unsafe void Write (byte[] buffer, int offset, int count)
357                 {
358                         AssertNotDisposed ();
359                         AssertValidBuffer (buffer, offset, count);
360                         if (!CanWrite)
361                                 throw new NotSupportedException ("File Stream does not support writing");
362
363                         ulong r = 0;
364                         fixed (byte* buf = &buffer[offset]) {
365                                 r = Native.Stdlib.fwrite (buf, (ulong) 1, (ulong) count, file);
366                         }
367                         if (r != (ulong) count)
368                                 UnixMarshal.ThrowExceptionForLastError ();
369                         GC.KeepAlive (this);
370                 }
371                 
372                 ~StdioFileStream ()
373                 {
374                         Close ();
375                 }
376
377                 public override void Close ()
378                 {
379                         if (file == InvalidFileStream)
380                                 return;
381
382                         if (owner) {
383                                 int r = Native.Stdlib.fclose (file);
384                                 if (r != 0)
385                                         UnixMarshal.ThrowExceptionForLastError ();
386                         } else
387                                 Flush ();
388                                 
389                         file = InvalidFileStream;
390                         canRead = false;
391                         canSeek = false;
392                         canWrite = false;
393
394                         GC.SuppressFinalize (this);
395                         GC.KeepAlive (this);
396                 }
397                 
398                 private bool canSeek  = false;
399                 private bool canRead  = false;
400                 private bool canWrite = false;
401                 private bool owner    = true;
402                 private IntPtr file   = InvalidFileStream;
403         }
404 }
405
406 // vim: noexpandtab