7eed08157b0748ff075ad7bcc362aa8709d4b74a
[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                         InitStream (Fopen (path, "rb"), true);
64                 }
65
66                 public StdioFileStream (string path, string mode)
67                 {
68                         InitStream (Fopen (path, mode), true);
69                 }
70
71                 public StdioFileStream (string path, FileMode mode)
72                 {
73                         InitStream (Fopen (path, ToFopenMode (path, mode)), true);
74                 }
75
76                 public StdioFileStream (string path, FileAccess access)
77                 {
78                         InitStream (Fopen (path, ToFopenMode (path, access)), true);
79                         InitCanReadWrite (access);
80                 }
81
82                 public StdioFileStream (string path, FileMode mode, FileAccess access)
83                 {
84                         InitStream (Fopen (path, ToFopenMode (path, mode, access)), true);
85                         InitCanReadWrite (access);
86                 }
87
88                 private static IntPtr Fopen (string path, string mode)
89                 {
90                         if (path == null)
91                                 throw new ArgumentNullException ("path");
92                         if (path.Length == 0)
93                                 throw new ArgumentException ("path");
94                         if (mode == null)
95                                 throw new ArgumentNullException ("mode");
96                         IntPtr f = Native.Stdlib.fopen (path, mode);
97                         if (f == IntPtr.Zero)
98                                 throw new DirectoryNotFoundException ("path", 
99                                                 UnixMarshal.CreateExceptionForLastError ());
100                         return f;
101                 }
102
103                 private void InitStream (IntPtr fileStream, bool ownsHandle)
104                 {
105                         if (InvalidFileStream == fileStream)
106                                 throw new ArgumentException (Locale.GetText ("Invalid file stream"), "fileStream");
107                         
108                         this.file = fileStream;
109                         this.owner = ownsHandle;
110                         
111                         try {
112                                 long offset = Native.Stdlib.fseek (file, 0, Native.SeekFlags.SEEK_CUR);
113                                 if (offset != -1)
114                                         canSeek = true;
115                                 Native.Stdlib.fread (IntPtr.Zero, 0, 0, file);
116                                 if (Native.Stdlib.ferror (file) == 0)
117                                         canRead = true;
118                                 Native.Stdlib.fwrite (IntPtr.Zero, 0, 0, file);
119                                 if (Native.Stdlib.ferror (file) == 0)
120                                         canWrite = true;  
121                                 Native.Stdlib.clearerr (file);
122                         }
123                         catch (Exception) {
124                                 throw new ArgumentException (Locale.GetText ("Invalid file stream"), "fileStream");
125                         }
126                         GC.KeepAlive (this);
127                 }
128
129                 private void InitCanReadWrite (FileAccess access)
130                 {
131                         canRead = canRead && 
132                                 (access == FileAccess.Read || access == FileAccess.ReadWrite);
133                         canWrite = canWrite &&
134                                 (access == FileAccess.Write || access == FileAccess.ReadWrite);
135                 }
136
137                 private static string ToFopenMode (string file, FileMode mode)
138                 {
139                         string cmode = Native.NativeConvert.ToFopenMode (mode);
140                         AssertFileMode (file, mode);
141                         return cmode;
142                 }
143
144                 private static string ToFopenMode (string file, FileAccess access)
145                 {
146                         return Native.NativeConvert.ToFopenMode (access);
147                 }
148
149                 private static string ToFopenMode (string file, FileMode mode, FileAccess access)
150                 {
151                         string cmode = Native.NativeConvert.ToFopenMode (mode, access);
152                         bool exists = AssertFileMode (file, mode);
153                         // HACK: for open-or-create & read, mode is "rb", which doesn't create
154                         // files.  If the file doesn't exist, we need to use "w+b" to ensure
155                         // file creation.
156                         if (mode == FileMode.OpenOrCreate && access == FileAccess.Read && !exists)
157                                 cmode = "w+b";
158                         return cmode;
159                 }
160
161                 private static bool AssertFileMode (string file, FileMode mode)
162                 {
163                         bool exists = FileExists (file);
164                         if (mode == FileMode.CreateNew && exists)
165                                 throw new IOException ("File exists and FileMode.CreateNew specified");
166                         if ((mode == FileMode.Open || mode == FileMode.Truncate) && !exists)
167                                 throw new FileNotFoundException ("File doesn't exist and FileMode.Open specified", file);
168                         return exists;
169                 }
170
171                 private static bool FileExists (string file)
172                 {
173                         bool found = false;
174                         IntPtr f = Native.Stdlib.fopen (file, "r");
175                         found = f != IntPtr.Zero;
176                         if (f != IntPtr.Zero)
177                                 Native.Stdlib.fclose (f);
178                         return found;
179                 }
180
181                 private void AssertNotDisposed ()
182                 {
183                         if (file == InvalidFileStream)
184                                 throw new ObjectDisposedException ("Invalid File Stream");
185                         GC.KeepAlive (this);
186                 }
187
188                 public IntPtr Handle {
189                         get {
190                                 AssertNotDisposed (); 
191                                 GC.KeepAlive (this);
192                                 return file;
193                         }
194                 }
195
196                 public override bool CanRead {
197                         get {return canRead;}
198                 }
199
200                 public override bool CanSeek {
201                         get {return canSeek;}
202                 }
203
204                 public override bool CanWrite {
205                         get {return canWrite;}
206                 }
207
208                 public override long Length {
209                         get {
210                                 AssertNotDisposed ();
211                                 if (!CanSeek)
212                                         throw new NotSupportedException ("File Stream doesn't support seeking");
213                                 long curPos = Native.Stdlib.ftell (file);
214                                 if (curPos == -1)
215                                         throw new NotSupportedException ("Unable to obtain current file position");
216                                 int r = Native.Stdlib.fseek (file, 0, Native.SeekFlags.SEEK_END);
217                                 UnixMarshal.ThrowExceptionForLastErrorIf (r);
218
219                                 long endPos = Native.Stdlib.ftell (file);
220                                 if (endPos == -1)
221                                         UnixMarshal.ThrowExceptionForLastError ();
222
223                                 r = Native.Stdlib.fseek (file, curPos, Native.SeekFlags.SEEK_SET);
224                                 UnixMarshal.ThrowExceptionForLastErrorIf (r);
225
226                                 GC.KeepAlive (this);
227                                 return endPos;
228                         }
229                 }
230
231                 public override long Position {
232                         get {
233                                 AssertNotDisposed ();
234                                 if (!CanSeek)
235                                         throw new NotSupportedException ("The stream does not support seeking");
236                                 long pos = Native.Stdlib.ftell (file);
237                                 if (pos == -1)
238                                         UnixMarshal.ThrowExceptionForLastError ();
239                                 GC.KeepAlive (this);
240                                 return (long) pos;
241                         }
242                         set {
243                                 AssertNotDisposed ();
244                                 Seek (value, SeekOrigin.Begin);
245                         }
246                 }
247
248                 public void SaveFilePosition (Native.FilePosition pos)
249                 {
250                         AssertNotDisposed ();
251                         int r = Native.Stdlib.fgetpos (file, pos);
252                         UnixMarshal.ThrowExceptionForLastErrorIf (r);
253                         GC.KeepAlive (this);
254                 }
255
256                 public void RestoreFilePosition (Native.FilePosition pos)
257                 {
258                         AssertNotDisposed ();
259                         if (pos == null)
260                                 throw new ArgumentNullException ("value");
261                         int r = Native.Stdlib.fsetpos (file, pos);
262                         UnixMarshal.ThrowExceptionForLastErrorIf (r);
263                         GC.KeepAlive (this);
264                 }
265
266                 public override void Flush ()
267                 {
268                         AssertNotDisposed ();
269                         int r = Native.Stdlib.fflush (file);
270                         if (r != 0)
271                                 UnixMarshal.ThrowExceptionForLastError ();
272                         GC.KeepAlive (this);
273                 }
274
275                 public override unsafe int Read ([In, Out] byte[] buffer, int offset, int count)
276                 {
277                         AssertNotDisposed ();
278                         AssertValidBuffer (buffer, offset, count);
279                         if (!CanRead)
280                                 throw new NotSupportedException ("Stream does not support reading");
281                                  
282                         ulong r = 0;
283                         fixed (byte* buf = &buffer[offset]) {
284                                 r = Native.Stdlib.fread (buf, 1, (ulong) count, file);
285                         }
286                         if (r != (ulong) count) {
287                                 if (Native.Stdlib.ferror (file) != 0)
288                                         throw new IOException ();
289                         }
290                         GC.KeepAlive (this);
291                         return (int) r;
292                 }
293
294                 private void AssertValidBuffer (byte[] buffer, int offset, int count)
295                 {
296                         if (buffer == null)
297                                 throw new ArgumentNullException ("buffer");
298                         if (offset < 0)
299                                 throw new ArgumentOutOfRangeException ("offset", "< 0");
300                         if (count < 0)
301                                 throw new ArgumentOutOfRangeException ("count", "< 0");
302                         if (offset > buffer.Length)
303                                 throw new ArgumentException ("destination offset is beyond array size");
304                         if (offset > (buffer.Length - count))
305                                 throw new ArgumentException ("would overrun buffer");
306                 }
307
308                 public void Rewind ()
309                 {
310                         AssertNotDisposed ();
311                         Native.Stdlib.rewind (file);
312                         GC.KeepAlive (this);
313                 }
314
315                 public override long Seek (long offset, SeekOrigin origin)
316                 {
317                         AssertNotDisposed ();
318                         if (!CanSeek)
319                                 throw new NotSupportedException ("The File Stream does not support seeking");
320
321                         Native.SeekFlags sf = Native.SeekFlags.SEEK_CUR;
322                         switch (origin) {
323                                 case SeekOrigin.Begin:   sf = Native.SeekFlags.SEEK_SET; break;
324                                 case SeekOrigin.Current: sf = Native.SeekFlags.SEEK_CUR; break;
325                                 case SeekOrigin.End:     sf = Native.SeekFlags.SEEK_END; break;
326                                 default: throw new ArgumentException ("origin");
327                         }
328
329                         int r = Native.Stdlib.fseek (file, offset, sf);
330                         if (r != 0)
331                                 throw new IOException ("Unable to seek",
332                                                 UnixMarshal.CreateExceptionForLastError ());
333
334                         long pos = Native.Stdlib.ftell (file);
335                         if (pos == -1)
336                                 throw new IOException ("Unable to get current file position",
337                                                 UnixMarshal.CreateExceptionForLastError ());
338
339                         GC.KeepAlive (this);
340                         return pos;
341                 }
342
343                 public override void SetLength (long value)
344                 {
345                         throw new NotSupportedException ("ANSI C doesn't provide a way to truncate a file");
346                 }
347
348                 public override unsafe void Write (byte[] buffer, int offset, int count)
349                 {
350                         AssertNotDisposed ();
351                         AssertValidBuffer (buffer, offset, count);
352                         if (!CanWrite)
353                                 throw new NotSupportedException ("File Stream does not support writing");
354
355                         ulong r = 0;
356                         fixed (byte* buf = &buffer[offset]) {
357                                 r = Native.Stdlib.fwrite (buf, (ulong) 1, (ulong) count, file);
358                         }
359                         if (r != (ulong) count)
360                                 UnixMarshal.ThrowExceptionForLastError ();
361                         GC.KeepAlive (this);
362                 }
363                 
364                 ~StdioFileStream ()
365                 {
366                         Close ();
367                 }
368
369                 public override void Close ()
370                 {
371                         if (file == InvalidFileStream)
372                                 return;
373
374                         if (owner) {
375                                 int r = Native.Stdlib.fclose (file);
376                                 if (r != 0)
377                                         UnixMarshal.ThrowExceptionForLastError ();
378                         } else
379                                 Flush ();
380                                 
381                         file = InvalidFileStream;
382                         canRead = false;
383                         canSeek = false;
384                         canWrite = false;
385
386                         GC.SuppressFinalize (this);
387                         GC.KeepAlive (this);
388                 }
389                 
390                 private bool canSeek  = false;
391                 private bool canRead  = false;
392                 private bool canWrite = false;
393                 private bool owner    = true;
394                 private IntPtr file   = InvalidFileStream;
395         }
396 }
397
398 // vim: noexpandtab