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