2008-09-30 Michael Hutchinson <mhutchinson@novell.com>
[mono-project.git] / mcs / class / Mono.Posix / Mono.Unix / StdioFileStream.cs
blobee2436a9f2be24cbec6b9debdd0b1b65327721db
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.
29 using System;
30 using System.IO;
31 using System.Runtime.InteropServices;
32 using System.Text;
33 using Mono.Unix;
35 namespace Mono.Unix {
37 public class StdioFileStream : Stream
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;
44 public StdioFileStream (IntPtr fileStream)
45 : this (fileStream, true) {}
47 public StdioFileStream (IntPtr fileStream, bool ownsHandle)
49 InitStream (fileStream, ownsHandle);
52 public StdioFileStream (IntPtr fileStream, FileAccess access)
53 : this (fileStream, access, true) {}
55 public StdioFileStream (IntPtr fileStream, FileAccess access, bool ownsHandle)
57 InitStream (fileStream, ownsHandle);
58 InitCanReadWrite (access);
61 public StdioFileStream (string path)
63 InitStream (Fopen (path, "rb"), true);
66 public StdioFileStream (string path, string mode)
68 InitStream (Fopen (path, mode), true);
71 public StdioFileStream (string path, FileMode mode)
73 InitStream (Fopen (path, ToFopenMode (path, mode)), true);
76 public StdioFileStream (string path, FileAccess access)
78 InitStream (Fopen (path, ToFopenMode (path, access)), true);
79 InitCanReadWrite (access);
82 public StdioFileStream (string path, FileMode mode, FileAccess access)
84 InitStream (Fopen (path, ToFopenMode (path, mode, access)), true);
85 InitCanReadWrite (access);
88 private static IntPtr Fopen (string path, string mode)
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;
103 private void InitStream (IntPtr fileStream, bool ownsHandle)
105 if (InvalidFileStream == fileStream)
106 throw new ArgumentException (Locale.GetText ("Invalid file stream"), "fileStream");
108 this.file = fileStream;
109 this.owner = ownsHandle;
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);
123 catch (Exception e) {
124 throw new ArgumentException (Locale.GetText ("Invalid file stream"), "fileStream");
126 GC.KeepAlive (this);
129 private void InitCanReadWrite (FileAccess access)
131 canRead = canRead &&
132 (access == FileAccess.Read || access == FileAccess.ReadWrite);
133 canWrite = canWrite &&
134 (access == FileAccess.Write || access == FileAccess.ReadWrite);
137 private static string ToFopenMode (string file, FileMode mode)
139 string cmode = Native.NativeConvert.ToFopenMode (mode);
140 AssertFileMode (file, mode);
141 return cmode;
144 private static string ToFopenMode (string file, FileAccess access)
146 return Native.NativeConvert.ToFopenMode (access);
149 private static string ToFopenMode (string file, FileMode mode, FileAccess access)
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;
161 private static bool AssertFileMode (string file, FileMode mode)
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;
171 private static bool FileExists (string file)
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;
181 private void AssertNotDisposed ()
183 if (file == InvalidFileStream)
184 throw new ObjectDisposedException ("Invalid File Stream");
185 GC.KeepAlive (this);
188 public IntPtr Handle {
189 get {
190 AssertNotDisposed ();
191 GC.KeepAlive (this);
192 return file;
196 public override bool CanRead {
197 get {return canRead;}
200 public override bool CanSeek {
201 get {return canSeek;}
204 public override bool CanWrite {
205 get {return canWrite;}
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);
219 long endPos = Native.Stdlib.ftell (file);
220 if (endPos == -1)
221 UnixMarshal.ThrowExceptionForLastError ();
223 r = Native.Stdlib.fseek (file, curPos, Native.SeekFlags.SEEK_SET);
224 UnixMarshal.ThrowExceptionForLastErrorIf (r);
226 GC.KeepAlive (this);
227 return endPos;
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;
242 set {
243 AssertNotDisposed ();
244 Seek (value, SeekOrigin.Begin);
248 public void SaveFilePosition (Native.FilePosition pos)
250 AssertNotDisposed ();
251 int r = Native.Stdlib.fgetpos (file, pos);
252 UnixMarshal.ThrowExceptionForLastErrorIf (r);
253 GC.KeepAlive (this);
256 public void RestoreFilePosition (Native.FilePosition pos)
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);
266 public override void Flush ()
268 AssertNotDisposed ();
269 int r = Native.Stdlib.fflush (file);
270 if (r != 0)
271 UnixMarshal.ThrowExceptionForLastError ();
272 GC.KeepAlive (this);
275 public override unsafe int Read ([In, Out] byte[] buffer, int offset, int count)
277 AssertNotDisposed ();
278 AssertValidBuffer (buffer, offset, count);
279 if (!CanRead)
280 throw new NotSupportedException ("Stream does not support reading");
282 ulong r = 0;
283 fixed (byte* buf = &buffer[offset]) {
284 r = Native.Stdlib.fread (buf, 1, (ulong) count, file);
286 if (r != (ulong) count) {
287 if (Native.Stdlib.ferror (file) != 0)
288 throw new IOException ();
290 GC.KeepAlive (this);
291 return (int) r;
294 private void AssertValidBuffer (byte[] buffer, int offset, int count)
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");
308 public void Rewind ()
310 AssertNotDisposed ();
311 Native.Stdlib.rewind (file);
312 GC.KeepAlive (this);
315 public override long Seek (long offset, SeekOrigin origin)
317 AssertNotDisposed ();
318 if (!CanSeek)
319 throw new NotSupportedException ("The File Stream does not support seeking");
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");
329 int r = Native.Stdlib.fseek (file, offset, sf);
330 if (r != 0)
331 throw new IOException ("Unable to seek",
332 UnixMarshal.CreateExceptionForLastError ());
334 long pos = Native.Stdlib.ftell (file);
335 if (pos == -1)
336 throw new IOException ("Unable to get current file position",
337 UnixMarshal.CreateExceptionForLastError ());
339 GC.KeepAlive (this);
340 return pos;
343 public override void SetLength (long value)
345 throw new NotSupportedException ("ANSI C doesn't provide a way to truncate a file");
348 public override unsafe void Write (byte[] buffer, int offset, int count)
350 AssertNotDisposed ();
351 AssertValidBuffer (buffer, offset, count);
352 if (!CanWrite)
353 throw new NotSupportedException ("File Stream does not support writing");
355 ulong r = 0;
356 fixed (byte* buf = &buffer[offset]) {
357 r = Native.Stdlib.fwrite (buf, (ulong) 1, (ulong) count, file);
359 if (r != (ulong) count)
360 UnixMarshal.ThrowExceptionForLastError ();
361 GC.KeepAlive (this);
364 ~StdioFileStream ()
366 Close ();
369 public override void Close ()
371 if (file == InvalidFileStream)
372 return;
374 if (owner) {
375 int r = Native.Stdlib.fclose (file);
376 if (r != 0)
377 UnixMarshal.ThrowExceptionForLastError ();
378 } else
379 Flush ();
381 file = InvalidFileStream;
382 canRead = false;
383 canSeek = false;
384 canWrite = false;
386 GC.SuppressFinalize (this);
387 GC.KeepAlive (this);
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;
398 // vim: noexpandtab