// // Mono.Unix/StdioFileStream.cs // // Authors: // Jonathan Pryor (jonpryor@vt.edu) // // (C) 2005 Jonathan Pryor // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // using System; using System.IO; using System.Runtime.InteropServices; using System.Text; using Mono.Unix; namespace Mono.Unix { public class StdioFileStream : Stream { public static readonly IntPtr InvalidFileStream = IntPtr.Zero; public static readonly IntPtr StandardInput = Stdlib.stdin; public static readonly IntPtr StandardOutput = Stdlib.stdout; public static readonly IntPtr StandardError = Stdlib.stderr; public StdioFileStream (IntPtr fileStream) : this (fileStream, true) {} public StdioFileStream (IntPtr fileStream, bool ownsHandle) { InitStream (fileStream, ownsHandle); } public StdioFileStream (IntPtr fileStream, FileAccess access) : this (fileStream, access, true) {} public StdioFileStream (IntPtr fileStream, FileAccess access, bool ownsHandle) { InitStream (fileStream, ownsHandle); InitCanReadWrite (access); } public StdioFileStream (string path) { InitStream (Fopen (path, "r"), true); } public StdioFileStream (string path, string mode) { InitStream (Fopen (path, mode), true); } public StdioFileStream (string path, FileMode mode) { InitStream (Fopen (path, ToFopenMode (path, mode)), true); } public StdioFileStream (string path, FileAccess access) { InitStream (Fopen (path, ToFopenMode (path, access)), true); InitCanReadWrite (access); } public StdioFileStream (string path, FileMode mode, FileAccess access) { InitStream (Fopen (path, ToFopenMode (path, mode, access)), true); InitCanReadWrite (access); } private static IntPtr Fopen (string path, string mode) { if (path == null) throw new ArgumentNullException ("path"); if (path.Length == 0) throw new ArgumentException ("path"); if (mode == null) throw new ArgumentNullException ("path"); IntPtr f = Stdlib.fopen (path, mode); if (f == IntPtr.Zero) throw new DirectoryNotFoundException ("path", UnixMarshal.CreateExceptionForLastError ()); return f; } private void InitStream (IntPtr fileStream, bool ownsHandle) { if (InvalidFileStream == fileStream) throw new ArgumentException (Locale.GetText ("Invalid file stream"), "fileStream"); this.file = fileStream; this.owner = ownsHandle; try { long offset = Stdlib.fseek (file, 0, SeekFlags.SEEK_CUR); if (offset != -1) canSeek = true; Stdlib.fread (IntPtr.Zero, 0, 0, file); if (Stdlib.ferror (file) == 0) canRead = true; Stdlib.fwrite (IntPtr.Zero, 0, 0, file); if (Stdlib.ferror (file) == 0) canWrite = true; Stdlib.clearerr (file); } catch (Exception e) { throw new ArgumentException (Locale.GetText ("Invalid file stream"), "fileStream"); } } private void InitCanReadWrite (FileAccess access) { canRead = canRead && (access == FileAccess.Read || access == FileAccess.ReadWrite); canWrite = canWrite && (access == FileAccess.Write || access == FileAccess.ReadWrite); } private static string ToFopenMode (string file, FileMode mode) { string cmode = UnixConvert.ToFopenMode (mode); AssertFileMode (file, mode); return cmode; } private static string ToFopenMode (string file, FileAccess access) { return UnixConvert.ToFopenMode (access); } private static string ToFopenMode (string file, FileMode mode, FileAccess access) { string cmode = UnixConvert.ToFopenMode (mode, access); bool exists = AssertFileMode (file, mode); // HACK: for open-or-create & read, mode is "rb", which doesn't create // files. If the file doesn't exist, we need to use "w+b" to ensure // file creation. if (mode == FileMode.OpenOrCreate && access == FileAccess.Read && !exists) cmode = "w+b"; return cmode; } private static bool AssertFileMode (string file, FileMode mode) { bool exists = FileExists (file); if (mode == FileMode.CreateNew && exists) throw new IOException ("File exists and FileMode.CreateNew specified"); if ((mode == FileMode.Open || mode == FileMode.Truncate) && !exists) throw new FileNotFoundException ("File doesn't exist and FileMode.Open specified", file); return exists; } private static bool FileExists (string file) { bool found = false; IntPtr f = Stdlib.fopen (file, "r"); found = f != IntPtr.Zero; if (f != IntPtr.Zero) Stdlib.fclose (f); return found; } private void AssertNotDisposed () { if (file == InvalidFileStream) throw new ObjectDisposedException ("Invalid File Stream"); } public IntPtr Handle { get {AssertNotDisposed (); return file;} } public override bool CanRead { get {return canRead;} } public override bool CanSeek { get {return canSeek;} } public override bool CanWrite { get {return canWrite;} } public override long Length { get { AssertNotDisposed (); if (!CanSeek) throw new NotSupportedException ("File Stream doesn't support seeking"); long curPos = Stdlib.ftell (file); if (curPos == -1) throw new NotSupportedException ("Unable to obtain current file position"); int r = Stdlib.fseek (file, 0, SeekFlags.SEEK_END); UnixMarshal.ThrowExceptionForLastErrorIf (r); long endPos = Stdlib.ftell (file); if (endPos == -1) UnixMarshal.ThrowExceptionForLastError (); r = Stdlib.fseek (file, curPos, SeekFlags.SEEK_SET); UnixMarshal.ThrowExceptionForLastErrorIf (r); return endPos; } } public override long Position { get { AssertNotDisposed (); if (!CanSeek) throw new NotSupportedException ("The stream does not support seeking"); long pos = Stdlib.ftell (file); if (pos == -1) UnixMarshal.ThrowExceptionForLastError (); return (long) pos; } set { AssertNotDisposed (); Seek (value, SeekOrigin.Begin); } } public void SaveFilePosition (FilePosition pos) { AssertNotDisposed (); int r = Stdlib.fgetpos (file, pos); UnixMarshal.ThrowExceptionForLastErrorIf (r); } public void RestoreFilePosition (FilePosition pos) { AssertNotDisposed (); if (pos == null) throw new ArgumentNullException ("value"); int r = Stdlib.fsetpos (file, pos); UnixMarshal.ThrowExceptionForLastErrorIf (r); } public override void Flush () { AssertNotDisposed (); int r = Stdlib.fflush (file); if (r != 0) UnixMarshal.ThrowExceptionForLastError (); } public override unsafe int Read ([In, Out] byte[] buffer, int offset, int count) { AssertNotDisposed (); AssertValidBuffer (buffer, offset, count); if (!CanRead) throw new NotSupportedException ("Stream does not support reading"); ulong r = 0; fixed (byte* buf = &buffer[offset]) { r = Stdlib.fread (buf, 1, (ulong) count, file); } if (r != (ulong) count) { if (Stdlib.ferror (file) != 0) throw new IOException (); } return (int) r; } private void AssertValidBuffer (byte[] buffer, int offset, int count) { if (buffer == null) throw new ArgumentNullException ("buffer"); if (offset < 0) throw new ArgumentOutOfRangeException ("offset", "< 0"); if (count < 0) throw new ArgumentOutOfRangeException ("count", "< 0"); if (offset > buffer.Length) throw new ArgumentException ("destination offset is beyond array size"); if (offset > (buffer.Length - count)) throw new ArgumentException ("would overrun buffer"); } public void Rewind () { AssertNotDisposed (); Stdlib.rewind (file); } public override long Seek (long offset, SeekOrigin origin) { AssertNotDisposed (); if (!CanSeek) throw new NotSupportedException ("The File Stream does not support seeking"); SeekFlags sf = SeekFlags.SEEK_CUR; switch (origin) { case SeekOrigin.Begin: sf = SeekFlags.SEEK_SET; break; case SeekOrigin.Current: sf = SeekFlags.SEEK_CUR; break; case SeekOrigin.End: sf = SeekFlags.SEEK_END; break; default: throw new ArgumentException ("origin"); } int r = Stdlib.fseek (file, offset, sf); if (r != 0) throw new IOException ("Unable to seek", UnixMarshal.CreateExceptionForLastError ()); long pos = Stdlib.ftell (file); if (pos == -1) throw new IOException ("Unable to get current file position", UnixMarshal.CreateExceptionForLastError ()); return pos; } public override void SetLength (long value) { throw new NotSupportedException ("ANSI C doesn't provide a way to truncate a file"); } public override unsafe void Write (byte[] buffer, int offset, int count) { AssertNotDisposed (); AssertValidBuffer (buffer, offset, count); if (!CanWrite) throw new NotSupportedException ("File Stream does not support writing"); ulong r = 0; fixed (byte* buf = &buffer[offset]) { r = Stdlib.fwrite (buf, (ulong) 1, (ulong) count, file); } if (r != (ulong) count) UnixMarshal.ThrowExceptionForLastError (); } ~StdioFileStream () { Close (); } public override void Close () { if (file == InvalidFileStream) return; if (owner) { int r = Stdlib.fclose (file); if (r != 0) UnixMarshal.ThrowExceptionForLastError (); } else Flush (); file = InvalidFileStream; canRead = false; canSeek = false; canWrite = false; GC.SuppressFinalize (this); } private bool canSeek = false; private bool canRead = false; private bool canWrite = false; private bool owner = true; private IntPtr file = InvalidFileStream; } } // vim: noexpandtab