72628370c2a0b0c676ce6740bd783aee4ed8f789
[mono.git] / mcs / class / Mono.Posix / Mono.Unix / UnixStream.cs
1 //
2 // Mono.Unix/UnixStream.cs
3 //
4 // Authors:
5 //   Jonathan Pryor (jonpryor@vt.edu)
6 //
7 // (C) 2004-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 sealed class UnixStream : Stream, IDisposable
38         {
39                 public const int InvalidFileDescriptor = -1;
40                 public const int StandardInputFileDescriptor = 0;
41                 public const int StandardOutputFileDescriptor = 1;
42                 public const int StandardErrorFileDescriptor = 2;
43
44                 public UnixStream (int fileDescriptor)
45                         : this (fileDescriptor, true) {}
46
47                 public UnixStream (int fileDescriptor, bool ownsHandle)
48                 {
49                         if (InvalidFileDescriptor == fileDescriptor)
50                                 throw new ArgumentException (Locale.GetText ("Invalid file descriptor"), "fileDescriptor");
51                         
52                         this.fileDescriptor = fileDescriptor;
53                         this.owner = ownsHandle;
54                         
55                         long offset = Native.Syscall.lseek (fileDescriptor, 0, Native.SeekFlags.SEEK_CUR);
56                         if (offset != -1)
57                                 canSeek = true;
58                         long read = Native.Syscall.read (fileDescriptor, IntPtr.Zero, 0);
59                         if (read != -1)
60                                 canRead = true;
61                         long write = Native.Syscall.write (fileDescriptor, IntPtr.Zero, 0);
62                         if (write != -1)
63                                 canWrite = true;  
64                 }
65
66                 private void AssertNotDisposed ()
67                 {
68                         if (fileDescriptor == InvalidFileDescriptor)
69                                 throw new ObjectDisposedException ("Invalid File Descriptor");
70                 }
71
72                 public int Handle {
73                         get {return fileDescriptor;}
74                 }
75
76                 public override bool CanRead {
77                         get {return canRead;}
78                 }
79
80                 public override bool CanSeek {
81                         get {return canSeek;}
82                 }
83
84                 public override bool CanWrite {
85                         get {return canWrite;}
86                 }
87
88                 public override long Length {
89                         get {
90                                 AssertNotDisposed ();
91                                 if (!CanSeek)
92                                         throw new NotSupportedException ("File descriptor doesn't support seeking");
93                                 RefreshStat ();
94                                 return stat.st_size;
95                         }
96                 }
97
98                 public override long Position {
99                         get {
100                                 AssertNotDisposed ();
101                                 if (!CanSeek)
102                                         throw new NotSupportedException ("The stream does not support seeking");
103                                 long pos = Native.Syscall.lseek (fileDescriptor, 0, Native.SeekFlags.SEEK_CUR);
104                                 if (pos == -1)
105                                         UnixMarshal.ThrowExceptionForLastError ();
106                                 return (long) pos;
107                         }
108                         set {
109                                 Seek (value, SeekOrigin.Begin);
110                         }
111                 }
112
113                 [CLSCompliant (false)]
114                 public Native.FilePermissions Protection {
115                         get {
116                                 RefreshStat ();
117                                 return stat.st_mode;
118                         }
119                         set {
120                                 // we can't change file type with fchmod, so clear out that portion
121                                 value &= ~Native.FilePermissions.S_IFMT;
122                                 int r = Native.Syscall.fchmod (fileDescriptor, value);
123                                 UnixMarshal.ThrowExceptionForLastErrorIf (r);
124                         }
125                 }
126
127                 public FileTypes FileType {
128                         get {
129                                 int type = (int) Protection;
130                                 return (FileTypes) (type & (int) UnixFileSystemInfo.AllFileTypes);
131                         }
132                         // no set as fchmod(2) won't accept changing the file type.
133                 }
134
135                 public FileAccessPermissions FileAccessPermissions {
136                         get {
137                                 int perms = (int) Protection;
138                                 return (FileAccessPermissions) (perms & (int) FileAccessPermissions.AllPermissions);
139                         }
140                         set {
141                                 int perms = (int) Protection;
142                                 perms &= (int) ~FileAccessPermissions.AllPermissions;
143                                 perms |= (int) value;
144                                 Protection = (Native.FilePermissions) perms;
145                         }
146                 }
147
148                 public FileSpecialAttributes FileSpecialAttributes {
149                         get {
150                                 int attrs = (int) Protection;
151                                 return (FileSpecialAttributes) (attrs & (int) UnixFileSystemInfo.AllSpecialAttributes);
152                         }
153                         set {
154                                 int perms = (int) Protection;
155                                 perms &= (int) ~UnixFileSystemInfo.AllSpecialAttributes;
156                                 perms |= (int) value;
157                                 Protection = (Native.FilePermissions) perms;
158                         }
159                 }
160
161                 public UnixUserInfo OwnerUser {
162                         get {RefreshStat (); return new UnixUserInfo (stat.st_uid);}
163                 }
164                                                                                                 
165                 public long OwnerUserId {
166                         get {RefreshStat (); return stat.st_uid;}
167                 }
168                                                                                                 
169                 public UnixGroupInfo OwnerGroup {
170                         get {RefreshStat (); return new UnixGroupInfo (stat.st_gid);}
171                 }
172                                                                                                 
173                 public long OwnerGroupId {
174                         get {RefreshStat (); return stat.st_gid;}
175                 }
176
177                 private void RefreshStat ()
178                 {
179                         int r = Native.Syscall.fstat (fileDescriptor, out stat);
180                         UnixMarshal.ThrowExceptionForLastErrorIf (r);
181                 }
182
183                 public void AdviseFileAccessPattern (FileAccessPattern pattern, long offset, long len)
184                 {
185                         FileHandleOperations.AdviseFileAccessPattern (fileDescriptor, pattern, offset, len);
186                 }
187
188                 public void AdviseFileAccessPattern (FileAccessPattern pattern)
189                 {
190                         AdviseFileAccessPattern (pattern, 0, 0);
191                 }
192
193                 public override void Flush ()
194                 {
195                         int r = Native.Syscall.fsync (fileDescriptor);
196
197                         if (r == -1) {
198                                 Native.Errno e = Native.Stdlib.GetLastError ();
199
200                                 // From the man page:
201                                 //  EROFS, EINVAL:
202                                 //    fd is bound to a special file which does not support
203                                 //    synchronization.
204                                 // Sockets are such a file, and since Close() calls Flush(), and we
205                                 // want to support manually opened sockets, we shouldn't generate an
206                                 // exception for these errors.
207                                 if (e == Native.Errno.EROFS || e == Native.Errno.EINVAL) {
208                                         return;
209                                 }
210
211                                 UnixMarshal.ThrowExceptionForError (e);
212                         }
213                 }
214
215                 public override unsafe int Read ([In, Out] byte[] buffer, int offset, int count)
216                 {
217                         AssertNotDisposed ();
218                         AssertValidBuffer (buffer, offset, count);
219                         if (!CanRead)
220                                 throw new NotSupportedException ("Stream does not support reading");
221                                  
222                         long r = 0;
223                         fixed (byte* buf = &buffer[offset]) {
224                                 do {
225                                         r = Native.Syscall.read (fileDescriptor, buf, (ulong) count);
226                                 } while (UnixMarshal.ShouldRetrySyscall ((int) r));
227                         }
228                         if (r == -1)
229                                 UnixMarshal.ThrowExceptionForLastError ();
230                         return (int) r;
231                 }
232
233                 private void AssertValidBuffer (byte[] buffer, int offset, int count)
234                 {
235                         if (buffer == null)
236                                 throw new ArgumentNullException ("buffer");
237                         if (offset < 0)
238                                 throw new ArgumentOutOfRangeException ("offset", "< 0");
239                         if (count < 0)
240                                 throw new ArgumentOutOfRangeException ("count", "< 0");
241                         if (offset > buffer.Length)
242                                 throw new ArgumentException ("destination offset is beyond array size");
243                         if (offset > (buffer.Length - count))
244                                 throw new ArgumentException ("would overrun buffer");
245                 }
246
247                 public unsafe int ReadAtOffset ([In, Out] byte[] buffer, 
248                         int offset, int count, long fileOffset)
249                 {
250                         AssertNotDisposed ();
251                         AssertValidBuffer (buffer, offset, count);
252                         if (!CanRead)
253                                 throw new NotSupportedException ("Stream does not support reading");
254                                  
255                         long r = 0;
256                         fixed (byte* buf = &buffer[offset]) {
257                                 do {
258                                         r = Native.Syscall.pread (fileDescriptor, buf, (ulong) count, fileOffset);
259                                 } while (UnixMarshal.ShouldRetrySyscall ((int) r));
260                         }
261                         if (r == -1)
262                                 UnixMarshal.ThrowExceptionForLastError ();
263                         return (int) r;
264                 }
265
266                 public override long Seek (long offset, SeekOrigin origin)
267                 {
268                         AssertNotDisposed ();
269                         if (!CanSeek)
270                                 throw new NotSupportedException ("The File Descriptor does not support seeking");
271                         if (offset > int.MaxValue)
272                                 throw new ArgumentOutOfRangeException ("offset", "too large");
273                                         
274                         Native.SeekFlags sf = Native.SeekFlags.SEEK_CUR;
275                         switch (origin) {
276                                 case SeekOrigin.Begin:   sf = Native.SeekFlags.SEEK_SET; break;
277                                 case SeekOrigin.Current: sf = Native.SeekFlags.SEEK_CUR; break;
278                                 case SeekOrigin.End:     sf = Native.SeekFlags.SEEK_END; break;
279                         }
280
281                         long pos = Native.Syscall.lseek (fileDescriptor, offset, sf);
282                         if (pos == -1)
283                                 UnixMarshal.ThrowExceptionForLastError ();
284                         return (long) pos;
285                 }
286
287                 public override void SetLength (long value)
288                 {
289                         AssertNotDisposed ();
290                         if (value < 0)
291                                 throw new ArgumentOutOfRangeException ("value", "< 0");
292                         if (!CanSeek && !CanWrite)
293                                 throw new NotSupportedException ("You can't truncating the current file descriptor");
294                         
295                         int r;
296                         do {
297                                 r = Native.Syscall.ftruncate (fileDescriptor, value);
298                         } while (UnixMarshal.ShouldRetrySyscall (r));
299                         UnixMarshal.ThrowExceptionForLastErrorIf (r);
300                 }
301
302                 public override unsafe void Write (byte[] buffer, int offset, int count)
303                 {
304                         AssertNotDisposed ();
305                         AssertValidBuffer (buffer, offset, count);
306                         if (!CanWrite)
307                                 throw new NotSupportedException ("File Descriptor does not support writing");
308
309                         long r = 0;
310                         fixed (byte* buf = &buffer[offset]) {
311                                 do {
312                                         r = Native.Syscall.write (fileDescriptor, buf, (ulong) count);
313                                 } while (UnixMarshal.ShouldRetrySyscall ((int) r));
314                         }
315                         if (r == -1)
316                                 UnixMarshal.ThrowExceptionForLastError ();
317                 }
318                 
319                 public unsafe void WriteAtOffset (byte[] buffer, 
320                         int offset, int count, long fileOffset)
321                 {
322                         AssertNotDisposed ();
323                         AssertValidBuffer (buffer, offset, count);
324                         if (!CanWrite)
325                                 throw new NotSupportedException ("File Descriptor does not support writing");
326
327                         long r = 0;
328                         fixed (byte* buf = &buffer[offset]) {
329                                 do {
330                                         r = Native.Syscall.pwrite (fileDescriptor, buf, (ulong) count, fileOffset);
331                                 } while (UnixMarshal.ShouldRetrySyscall ((int) r));
332                         }
333                         if (r == -1)
334                                 UnixMarshal.ThrowExceptionForLastError ();
335                 }
336
337                 public void SendTo (UnixStream output)
338                 {
339                         SendTo (output, (ulong) output.Length);
340                 }
341
342                 [CLSCompliant (false)]
343                 public void SendTo (UnixStream output, ulong count)
344                 {
345                         SendTo (output.Handle, count);
346                 }
347
348                 [CLSCompliant (false)]
349                 public void SendTo (int out_fd, ulong count)
350                 {
351                         if (!CanWrite)
352                                 throw new NotSupportedException ("Unable to write to the current file descriptor");
353                         long offset = Position;
354                         long r = Native.Syscall.sendfile (out_fd, fileDescriptor, ref offset, count);
355                         if (r == -1)
356                                 UnixMarshal.ThrowExceptionForLastError ();
357                 }
358                 
359                 public void SetOwner (long user, long group)
360                 {
361                         AssertNotDisposed ();
362
363                         int r = Native.Syscall.fchown (fileDescriptor, 
364                                         Convert.ToUInt32 (user), Convert.ToUInt32 (group));
365                         UnixMarshal.ThrowExceptionForLastErrorIf (r);
366                 }
367
368                 public void SetOwner (string user, string group)
369                 {
370                         AssertNotDisposed ();
371
372                         long uid = new UnixUserInfo (user).UserId;
373                         long gid = new UnixGroupInfo (group).GroupId;
374                         SetOwner (uid, gid);
375                 }
376
377                 public void SetOwner (string user)
378                 {
379                         AssertNotDisposed ();
380
381                         Native.Passwd pw = Native.Syscall.getpwnam (user);
382                         if (pw == null)
383                                 throw new ArgumentException (Locale.GetText ("invalid username"), "user");
384                         long uid = pw.pw_uid;
385                         long gid = pw.pw_gid;
386                         SetOwner (uid, gid);
387                 }
388
389                 [CLSCompliant (false)]
390                 public long GetConfigurationValue (Native.PathconfName name)
391                 {
392                         AssertNotDisposed ();
393                         long r = Native.Syscall.fpathconf (fileDescriptor, name);
394                         if (r == -1 && Native.Syscall.GetLastError() != (Native.Errno) 0)
395                                 UnixMarshal.ThrowExceptionForLastError ();
396                         return r;
397                 }
398
399                 ~UnixStream ()
400                 {
401                         Close ();
402                 }
403
404                 public override void Close ()
405                 {
406                         if (fileDescriptor == InvalidFileDescriptor)
407                                 return;
408                                 
409                         Flush ();
410                         int r;
411                         do {
412                                 r = Native.Syscall.close (fileDescriptor);
413                         } while (UnixMarshal.ShouldRetrySyscall (r));
414                         UnixMarshal.ThrowExceptionForLastErrorIf (r);
415                         fileDescriptor = InvalidFileDescriptor;
416                         GC.SuppressFinalize (this);
417                 }
418                 
419                 void IDisposable.Dispose ()
420                 {
421                         AssertNotDisposed ();
422                         if (owner) {
423                                 Close ();
424                         }
425                         GC.SuppressFinalize (this);
426                 }
427
428                 private bool canSeek = false;
429                 private bool canRead = false;
430                 private bool canWrite = false;
431                 private bool owner = true;
432                 private int fileDescriptor = InvalidFileDescriptor;
433                 private Native.Stat stat;
434         }
435 }
436
437 // vim: noexpandtab