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