5e6316757bd9869415a92bd90a4558d677365a59
[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-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 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 = Syscall.lseek (fileDescriptor, 0, SeekFlags.SEEK_CUR);
56                         if (offset != -1)
57                                 canSeek = true;
58                         long read = Syscall.read (fileDescriptor, IntPtr.Zero, 0);
59                         if (read != -1)
60                                 canRead = true;
61                         long write = 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 = Syscall.lseek (fileDescriptor, 0, 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                 [Obsolete ("Use Protection")]
115                 public FilePermissions Permissions {
116                         get {
117                                 RefreshStat ();
118                                 return (FilePermissions) stat.st_mode;
119                         }
120                         set {
121                                 int r = Syscall.fchmod (fileDescriptor, value);
122                                 UnixMarshal.ThrowExceptionForLastErrorIf (r);
123                         }
124                 }
125
126                 [CLSCompliant (false)]
127                 public Native.FilePermissions Protection {
128                         get {
129                                 RefreshStat ();
130                                 return stat.st_mode;
131                         }
132                         set {
133                                 // we can't change file type with fchmod, so clear out that portion
134                                 value &= ~Native.FilePermissions.S_IFMT;
135                                 int r = Native.Syscall.fchmod (fileDescriptor, value);
136                                 UnixMarshal.ThrowExceptionForLastErrorIf (r);
137                         }
138                 }
139
140                 public FileTypes FileType {
141                         get {
142                                 int type = (int) Protection;
143                                 return (FileTypes) (type & (int) FileTypes.AllTypes);
144                         }
145                         // no set as fchmod(2) won't accept changing the file type.
146                 }
147
148                 public FileAccessPermissions FileAccessPermissions {
149                         get {
150                                 int perms = (int) Protection;
151                                 return (FileAccessPermissions) (perms & (int) FileAccessPermissions.AllPermissions);
152                         }
153                         set {
154                                 int perms = (int) Protection;
155                                 perms &= (int) ~FileAccessPermissions.AllPermissions;
156                                 perms |= (int) value;
157                                 Protection = (Native.FilePermissions) perms;
158                         }
159                 }
160
161                 public FileSpecialAttributes FileSpecialAttributes {
162                         get {
163                                 int attrs = (int) Protection;
164                                 return (FileSpecialAttributes) (attrs & (int) FileSpecialAttributes.AllAttributes);
165                         }
166                         set {
167                                 int perms = (int) Protection;
168                                 perms &= (int) ~FileSpecialAttributes.AllAttributes;
169                                 perms |= (int) value;
170                                 Protection = (Native.FilePermissions) perms;
171                         }
172                 }
173
174                 public UnixUserInfo OwnerUser {
175                         get {RefreshStat (); return new UnixUserInfo (stat.st_uid);}
176                 }
177                                                                                                 
178                 public long OwnerUserId {
179                         get {RefreshStat (); return stat.st_uid;}
180                 }
181                                                                                                 
182                 public UnixGroupInfo OwnerGroup {
183                         get {RefreshStat (); return new UnixGroupInfo (stat.st_gid);}
184                 }
185                                                                                                 
186                 public long OwnerGroupId {
187                         get {RefreshStat (); return stat.st_gid;}
188                 }
189
190                 private void RefreshStat ()
191                 {
192                         int r = Native.Syscall.fstat (fileDescriptor, out stat);
193                         UnixMarshal.ThrowExceptionForLastErrorIf (r);
194                 }
195
196                 public void AdviseFileAccessPattern (FileAccessPattern pattern, long offset, long len)
197                 {
198                         FileHandleOperations.AdviseFileAccessPattern (fileDescriptor, pattern, offset, len);
199                 }
200
201                 public void AdviseFileAccessPattern (FileAccessPattern pattern)
202                 {
203                         AdviseFileAccessPattern (pattern, 0, 0);
204                 }
205
206                 [Obsolete ("Use AdviseFileAccessPattern (FileAccessPattern.Normal, offset, len)")]
207                 public void AdviseNormalAccess (long offset, long len)
208                 {
209                         UnixFile.AdviseNormalAccess (fileDescriptor, offset, len);
210                 }
211
212                 [Obsolete ("Use AdviseFileAccessPattern (FileAccessPattern.Normal)")]
213                 public void AdviseNormalAccess ()
214                 {
215                         UnixFile.AdviseNormalAccess (fileDescriptor);
216                 }
217
218                 [Obsolete ("Use AdviseFileAccessPattern (FileAccessPattern.Sequential, offset, len)")]
219                 public void AdviseSequentialAccess (long offset, long len)
220                 {
221                         UnixFile.AdviseSequentialAccess (fileDescriptor, offset, len);
222                 }
223
224                 [Obsolete ("Use AdviseFileAccessPattern (FileAccessPattern.Sequential)")]
225                 public void AdviseSequentialAccess ()
226                 {
227                         UnixFile.AdviseSequentialAccess (fileDescriptor);
228                 }
229
230                 [Obsolete ("Use AdviseFileAccessPattern (FileAccessPattern.Random, offset, len)")]
231                 public void AdviseRandomAccess (long offset, long len)
232                 {
233                         UnixFile.AdviseRandomAccess (fileDescriptor, offset, len);
234                 }
235
236                 [Obsolete ("Use AdviseFileAccessPattern (FileAccessPattern.Random)")]
237                 public void AdviseRandomAccess ()
238                 {
239                         UnixFile.AdviseRandomAccess (fileDescriptor);
240                 }
241
242                 [Obsolete ("Use AdviseFileAccessPattern (FileAccessPattern.UseSoon, offset, len)")]
243                 public void AdviseNeedAccess (long offset, long len)
244                 {
245                         UnixFile.AdviseNeedAccess (fileDescriptor, offset, len);
246                 }
247
248                 [Obsolete ("Use AdviseFileAccessPattern (FileAccessPattern.UseSoon)")]
249                 public void AdviseNeedAccess ()
250                 {
251                         UnixFile.AdviseNeedAccess (fileDescriptor);
252                 }
253
254                 [Obsolete ("Use AdviseFileAccessPattern (FileAccessPattern.WillNotUse, offset, len)")]
255                 public void AdviseNoAccess (long offset, long len)
256                 {
257                         UnixFile.AdviseNoAccess (fileDescriptor, offset, len);
258                 }
259
260                 [Obsolete ("Use AdviseFileAccessPattern (FileAccessPattern.WillNotUse)")]
261                 public void AdviseNoAccess ()
262                 {
263                         UnixFile.AdviseNoAccess (fileDescriptor);
264                 }
265
266                 [Obsolete ("Use AdviseFileAccessPattern (FileAccessPattern.UseOnce, offset, len)")]
267                 public void AdviseOnceAccess (long offset, long len)
268                 {
269                         UnixFile.AdviseOnceAccess (fileDescriptor, offset, len);
270                 }
271
272                 [Obsolete ("Use AdviseFileAccessPattern (FileAccessPattern.UseOnce)")]
273                 public void AdviseOnceAccess ()
274                 {
275                         UnixFile.AdviseOnceAccess (fileDescriptor);
276                 }
277
278                 public override void Flush ()
279                 {
280                         int r = Native.Syscall.fsync (fileDescriptor);
281
282                         if (r == -1) {
283                                 Native.Errno e = Native.Stdlib.GetLastError ();
284
285                                 // From the man page:
286                                 //  EROFS, EINVAL:
287                                 //    fd is bound to a special file which does not support
288                                 //    synchronization.
289                                 // Sockets are such a file, and since Close() calls Flush(), and we
290                                 // want to support manually opened sockets, we shouldn't generate an
291                                 // exception for these errors.
292                                 if (e == Native.Errno.EROFS || e == Native.Errno.EINVAL) {
293                                         return;
294                                 }
295
296                                 UnixMarshal.ThrowExceptionForError (e);
297                         }
298                 }
299
300                 public override unsafe int Read ([In, Out] byte[] buffer, int offset, int count)
301                 {
302                         AssertNotDisposed ();
303                         AssertValidBuffer (buffer, offset, count);
304                         if (!CanRead)
305                                 throw new NotSupportedException ("Stream does not support reading");
306                                  
307                         long r = 0;
308                         fixed (byte* buf = &buffer[offset]) {
309                                 do {
310                                         r = Syscall.read (fileDescriptor, buf, (ulong) count);
311                                 } while (UnixMarshal.ShouldRetrySyscall ((int) r));
312                         }
313                         if (r == -1)
314                                 UnixMarshal.ThrowExceptionForLastError ();
315                         return (int) r;
316                 }
317
318                 private void AssertValidBuffer (byte[] buffer, int offset, int count)
319                 {
320                         if (buffer == null)
321                                 throw new ArgumentNullException ("buffer");
322                         if (offset < 0)
323                                 throw new ArgumentOutOfRangeException ("offset", "< 0");
324                         if (count < 0)
325                                 throw new ArgumentOutOfRangeException ("count", "< 0");
326                         if (offset > buffer.Length)
327                                 throw new ArgumentException ("destination offset is beyond array size");
328                         if (offset > (buffer.Length - count))
329                                 throw new ArgumentException ("would overrun buffer");
330                 }
331
332                 public unsafe int ReadAtOffset ([In, Out] byte[] buffer, 
333                         int offset, int count, long fileOffset)
334                 {
335                         AssertNotDisposed ();
336                         AssertValidBuffer (buffer, offset, count);
337                         if (!CanRead)
338                                 throw new NotSupportedException ("Stream does not support reading");
339                                  
340                         long r = 0;
341                         fixed (byte* buf = &buffer[offset]) {
342                                 do {
343                                         r = Syscall.pread (fileDescriptor, buf, (ulong) count, fileOffset);
344                                 } while (UnixMarshal.ShouldRetrySyscall ((int) r));
345                         }
346                         if (r == -1)
347                                 UnixMarshal.ThrowExceptionForLastError ();
348                         return (int) r;
349                 }
350
351                 public override long Seek (long offset, SeekOrigin origin)
352                 {
353                         AssertNotDisposed ();
354                         if (!CanSeek)
355                                 throw new NotSupportedException ("The File Descriptor does not support seeking");
356                         if (offset > int.MaxValue)
357                                 throw new ArgumentOutOfRangeException ("offset", "too large");
358                                         
359                         SeekFlags sf = SeekFlags.SEEK_CUR;
360                         switch (origin) {
361                                 case SeekOrigin.Begin:   sf = SeekFlags.SEEK_SET; break;
362                                 case SeekOrigin.Current: sf = SeekFlags.SEEK_CUR; break;
363                                 case SeekOrigin.End:     sf = SeekFlags.SEEK_END; break;
364                         }
365
366                         long pos = Syscall.lseek (fileDescriptor, offset, sf);
367                         if (pos == -1)
368                                 UnixMarshal.ThrowExceptionForLastError ();
369                         return (long) pos;
370                 }
371
372                 public override void SetLength (long value)
373                 {
374                         AssertNotDisposed ();
375                         if (value < 0)
376                                 throw new ArgumentOutOfRangeException ("value", "< 0");
377                         if (!CanSeek && !CanWrite)
378                                 throw new NotSupportedException ("You can't truncating the current file descriptor");
379                         
380                         int r;
381                         do {
382                                 r = Syscall.ftruncate (fileDescriptor, value);
383                         } while (UnixMarshal.ShouldRetrySyscall (r));
384                         UnixMarshal.ThrowExceptionForLastErrorIf (r);
385                 }
386
387                 public override unsafe void Write (byte[] buffer, int offset, int count)
388                 {
389                         AssertNotDisposed ();
390                         AssertValidBuffer (buffer, offset, count);
391                         if (!CanWrite)
392                                 throw new NotSupportedException ("File Descriptor does not support writing");
393
394                         long r = 0;
395                         fixed (byte* buf = &buffer[offset]) {
396                                 do {
397                                         r = Syscall.write (fileDescriptor, buf, (ulong) count);
398                                 } while (UnixMarshal.ShouldRetrySyscall ((int) r));
399                         }
400                         if (r == -1)
401                                 UnixMarshal.ThrowExceptionForLastError ();
402                 }
403                 
404                 public unsafe void WriteAtOffset (byte[] buffer, 
405                         int offset, int count, long fileOffset)
406                 {
407                         AssertNotDisposed ();
408                         AssertValidBuffer (buffer, offset, count);
409                         if (!CanWrite)
410                                 throw new NotSupportedException ("File Descriptor does not support writing");
411
412                         long r = 0;
413                         fixed (byte* buf = &buffer[offset]) {
414                                 do {
415                                         r = Syscall.pwrite (fileDescriptor, buf, (ulong) count, fileOffset);
416                                 } while (UnixMarshal.ShouldRetrySyscall ((int) r));
417                         }
418                         if (r == -1)
419                                 UnixMarshal.ThrowExceptionForLastError ();
420                 }
421
422                 public void SendTo (UnixStream output)
423                 {
424                         SendTo (output, (ulong) output.Length);
425                 }
426
427                 [CLSCompliant (false)]
428                 public void SendTo (UnixStream output, ulong count)
429                 {
430                         SendTo (output.Handle, count);
431                 }
432
433                 [CLSCompliant (false)]
434                 public void SendTo (int out_fd, ulong count)
435                 {
436                         if (!CanWrite)
437                                 throw new NotSupportedException ("Unable to write to the current file descriptor");
438                         long offset = Position;
439                         long r = Syscall.sendfile (out_fd, fileDescriptor, ref offset, count);
440                         if (r == -1)
441                                 UnixMarshal.ThrowExceptionForLastError ();
442                 }
443                 
444                 [CLSCompliant (false)]
445                 [Obsolete ("Use SetOwner (long, long)")]
446                 public void SetOwner (uint user, uint group)
447                 {
448                         AssertNotDisposed ();
449
450                         int r = Syscall.fchown (fileDescriptor, user, group);
451                         UnixMarshal.ThrowExceptionForLastErrorIf (r);
452                 }
453
454                 public void SetOwner (long user, long group)
455                 {
456                         AssertNotDisposed ();
457
458                         int r = Syscall.fchown (fileDescriptor, 
459                                         Convert.ToUInt32 (user), Convert.ToUInt32 (group));
460                         UnixMarshal.ThrowExceptionForLastErrorIf (r);
461                 }
462
463                 public void SetOwner (string user, string group)
464                 {
465                         AssertNotDisposed ();
466
467                         uint uid = UnixUser.GetUserId (user);
468                         uint gid = UnixGroup.GetGroupId (group);
469                         SetOwner (uid, gid);
470                 }
471
472                 public void SetOwner (string user)
473                 {
474                         AssertNotDisposed ();
475
476                         Passwd pw = Syscall.getpwnam (user);
477                         if (pw == null)
478                                 throw new ArgumentException (Locale.GetText ("invalid username"), "user");
479                         uint uid = pw.pw_uid;
480                         uint gid = pw.pw_gid;
481                         SetOwner (uid, gid);
482                 }
483
484                 [CLSCompliant (false)]
485                 [Obsolete ("Use GetConfigurationValue (Mono.Unix.Native.PathconfName")]
486                 public long GetConfigurationValue (PathConf name)
487                 {
488                         AssertNotDisposed ();
489                         long r = Syscall.fpathconf (fileDescriptor, name);
490                         if (r == -1 && Syscall.GetLastError() != (Error) 0)
491                                 UnixMarshal.ThrowExceptionForLastError ();
492                         return r;
493                 }
494
495                 [CLSCompliant (false)]
496                 public long GetConfigurationValue (Native.PathconfName name)
497                 {
498                         AssertNotDisposed ();
499                         long r = Native.Syscall.fpathconf (fileDescriptor, name);
500                         if (r == -1 && Syscall.GetLastError() != (Error) 0)
501                                 UnixMarshal.ThrowExceptionForLastError ();
502                         return r;
503                 }
504
505                 ~UnixStream ()
506                 {
507                         Close ();
508                 }
509
510                 public override void Close ()
511                 {
512                         if (fileDescriptor == InvalidFileDescriptor)
513                                 return;
514                                 
515                         Flush ();
516                         int r;
517                         do {
518                                 r = Syscall.close (fileDescriptor);
519                         } while (UnixMarshal.ShouldRetrySyscall (r));
520                         UnixMarshal.ThrowExceptionForLastErrorIf (r);
521                         fileDescriptor = InvalidFileDescriptor;
522                         GC.SuppressFinalize (this);
523                 }
524                 
525                 void IDisposable.Dispose ()
526                 {
527                         AssertNotDisposed ();
528                         if (owner) {
529                                 Close ();
530                         }
531                         GC.SuppressFinalize (this);
532                 }
533
534                 private bool canSeek = false;
535                 private bool canRead = false;
536                 private bool canWrite = false;
537                 private bool owner = true;
538                 private int fileDescriptor = InvalidFileDescriptor;
539                 private Native.Stat stat;
540         }
541 }
542
543 // vim: noexpandtab