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 
StdioFileStream(IntPtr fileStream)44 		public StdioFileStream (IntPtr fileStream)
45 			: this (fileStream, true) {}
46 
StdioFileStream(IntPtr fileStream, bool ownsHandle)47 		public StdioFileStream (IntPtr fileStream, bool ownsHandle)
48 		{
49 			InitStream (fileStream, ownsHandle);
50 		}
51 
StdioFileStream(IntPtr fileStream, FileAccess access)52 		public StdioFileStream (IntPtr fileStream, FileAccess access)
53 			: this (fileStream, access, true) {}
54 
StdioFileStream(IntPtr fileStream, FileAccess access, bool ownsHandle)55 		public StdioFileStream (IntPtr fileStream, FileAccess access, bool ownsHandle)
56 		{
57 			InitStream (fileStream, ownsHandle);
58 			InitCanReadWrite (access);
59 		}
60 
StdioFileStream(string path)61 		public StdioFileStream (string path)
62 		{
63 			if (path == null)
64 				throw new ArgumentNullException ("path");
65 			InitStream (Fopen (path, "rb"), true);
66 		}
67 
StdioFileStream(string path, string mode)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 
StdioFileStream(string path, FileMode mode)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 
StdioFileStream(string path, FileAccess access)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 
StdioFileStream(string path, FileMode mode, FileAccess access)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 
Fopen(string path, string mode)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 
InitStream(IntPtr fileStream, bool ownsHandle)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 
InitCanReadWrite(FileAccess access)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 
ToFopenMode(string file, FileMode mode)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 
ToFopenMode(string file, FileAccess access)152 		private static string ToFopenMode (string file, FileAccess access)
153 		{
154 			return Native.NativeConvert.ToFopenMode (access);
155 		}
156 
ToFopenMode(string file, FileMode mode, FileAccess access)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 
AssertFileMode(string file, FileMode mode)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 
FileExists(string file)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 
AssertNotDisposed()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 
SaveFilePosition(Native.FilePosition pos)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 
RestoreFilePosition(Native.FilePosition pos)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 
Flush()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 
Read([In, Out] byte[] buffer, int offset, int count)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 
AssertValidBuffer(byte[] buffer, int offset, int count)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 
Rewind()316 		public void Rewind ()
317 		{
318 			AssertNotDisposed ();
319 			Native.Stdlib.rewind (file);
320 			GC.KeepAlive (this);
321 		}
322 
Seek(long offset, SeekOrigin origin)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 
SetLength(long value)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 
Write(byte[] buffer, int offset, int count)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 
~StdioFileStream()372 		~StdioFileStream ()
373 		{
374 			Close ();
375 		}
376 
Close()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
407