1 //
2 // Mono.Unix/UnixStream.cs
3 //
4 // Authors:
5 //   Jonathan Pryor (jonpryor@vt.edu)
6 //
7 // (C) 2004-2006 Jonathan Pryor
8 // (C) 2007 Novell, Inc.
9 //
10 //
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
18 //
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
21 //
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29 //
30 
31 using System;
32 using System.IO;
33 using System.Runtime.InteropServices;
34 using System.Text;
35 using Mono.Unix;
36 
37 namespace Mono.Unix {
38 
39 	public sealed class UnixStream : Stream, IDisposable
40 	{
41 		public const int InvalidFileDescriptor = -1;
42 		public const int StandardInputFileDescriptor = 0;
43 		public const int StandardOutputFileDescriptor = 1;
44 		public const int StandardErrorFileDescriptor = 2;
45 
UnixStream(int fileDescriptor)46 		public UnixStream (int fileDescriptor)
47 			: this (fileDescriptor, true) {}
48 
UnixStream(int fileDescriptor, bool ownsHandle)49 		public UnixStream (int fileDescriptor, bool ownsHandle)
50 		{
51 			if (InvalidFileDescriptor == fileDescriptor)
52 				throw new ArgumentException (Locale.GetText ("Invalid file descriptor"), "fileDescriptor");
53 
54 			this.fileDescriptor = fileDescriptor;
55 			this.owner = ownsHandle;
56 
57 			long offset = Native.Syscall.lseek (fileDescriptor, 0, Native.SeekFlags.SEEK_CUR);
58 			if (offset != -1)
59 				canSeek = true;
60 			long read = Native.Syscall.read (fileDescriptor, IntPtr.Zero, 0);
61 			if (read != -1)
62 				canRead = true;
63 			long write = Native.Syscall.write (fileDescriptor, IntPtr.Zero, 0);
64 			if (write != -1)
65 				canWrite = true;
66 		}
67 
AssertNotDisposed()68 		private void AssertNotDisposed ()
69 		{
70 			if (fileDescriptor == InvalidFileDescriptor)
71 				throw new ObjectDisposedException ("Invalid File Descriptor");
72 		}
73 
74 		public int Handle {
75 			get {return fileDescriptor;}
76 		}
77 
78 		public override bool CanRead {
79 			get {return canRead;}
80 		}
81 
82 		public override bool CanSeek {
83 			get {return canSeek;}
84 		}
85 
86 		public override bool CanWrite {
87 			get {return canWrite;}
88 		}
89 
90 		public override long Length {
91 			get {
92 				AssertNotDisposed ();
93 				if (!CanSeek)
94 					throw new NotSupportedException ("File descriptor doesn't support seeking");
95 				RefreshStat ();
96 				return stat.st_size;
97 			}
98 		}
99 
100 		public override long Position {
101 			get {
102 				AssertNotDisposed ();
103 				if (!CanSeek)
104 					throw new NotSupportedException ("The stream does not support seeking");
105 				long pos = Native.Syscall.lseek (fileDescriptor, 0, Native.SeekFlags.SEEK_CUR);
106 				if (pos == -1)
107 					UnixMarshal.ThrowExceptionForLastError ();
108 				return (long) pos;
109 			}
110 			set {
111 				Seek (value, SeekOrigin.Begin);
112 			}
113 		}
114 
115 		[CLSCompliant (false)]
116 		public Native.FilePermissions Protection {
117 			get {
118 				RefreshStat ();
119 				return stat.st_mode;
120 			}
121 			set {
122 				// we can't change file type with fchmod, so clear out that portion
123 				value &= ~Native.FilePermissions.S_IFMT;
124 				int r = Native.Syscall.fchmod (fileDescriptor, value);
125 				UnixMarshal.ThrowExceptionForLastErrorIf (r);
126 			}
127 		}
128 
129 		public FileTypes FileType {
130 			get {
131 				int type = (int) Protection;
132 				return (FileTypes) (type & (int) UnixFileSystemInfo.AllFileTypes);
133 			}
134 			// no set as fchmod(2) won't accept changing the file type.
135 		}
136 
137 		public FileAccessPermissions FileAccessPermissions {
138 			get {
139 				int perms = (int) Protection;
140 				return (FileAccessPermissions) (perms & (int) FileAccessPermissions.AllPermissions);
141 			}
142 			set {
143 				int perms = (int) Protection;
144 				perms &= (int) ~FileAccessPermissions.AllPermissions;
145 				perms |= (int) value;
146 				Protection = (Native.FilePermissions) perms;
147 			}
148 		}
149 
150 		public FileSpecialAttributes FileSpecialAttributes {
151 			get {
152 				int attrs = (int) Protection;
153 				return (FileSpecialAttributes) (attrs & (int) UnixFileSystemInfo.AllSpecialAttributes);
154 			}
155 			set {
156 				int perms = (int) Protection;
157 				perms &= (int) ~UnixFileSystemInfo.AllSpecialAttributes;
158 				perms |= (int) value;
159 				Protection = (Native.FilePermissions) perms;
160 			}
161 		}
162 
163 		public UnixUserInfo OwnerUser {
164 			get {RefreshStat (); return new UnixUserInfo (stat.st_uid);}
165 		}
166 
167 		public long OwnerUserId {
168 			get {RefreshStat (); return stat.st_uid;}
169 		}
170 
171 		public UnixGroupInfo OwnerGroup {
172 			get {RefreshStat (); return new UnixGroupInfo (stat.st_gid);}
173 		}
174 
175 		public long OwnerGroupId {
176 			get {RefreshStat (); return stat.st_gid;}
177 		}
178 
RefreshStat()179 		private void RefreshStat ()
180 		{
181 			AssertNotDisposed ();
182 			int r = Native.Syscall.fstat (fileDescriptor, out stat);
183 			UnixMarshal.ThrowExceptionForLastErrorIf (r);
184 		}
185 
AdviseFileAccessPattern(FileAccessPattern pattern, long offset, long len)186 		public void AdviseFileAccessPattern (FileAccessPattern pattern, long offset, long len)
187 		{
188 			FileHandleOperations.AdviseFileAccessPattern (fileDescriptor, pattern, offset, len);
189 		}
190 
AdviseFileAccessPattern(FileAccessPattern pattern)191 		public void AdviseFileAccessPattern (FileAccessPattern pattern)
192 		{
193 			AdviseFileAccessPattern (pattern, 0, 0);
194 		}
195 
Flush()196 		public override void Flush ()
197 		{
198 		}
199 
Read([In, Out] byte[] buffer, int offset, int count)200 		public override unsafe int Read ([In, Out] byte[] buffer, int offset, int count)
201 		{
202 			AssertNotDisposed ();
203 			AssertValidBuffer (buffer, offset, count);
204 			if (!CanRead)
205 				throw new NotSupportedException ("Stream does not support reading");
206 
207 			if (buffer.Length == 0)
208 				return 0;
209 
210 			long r = 0;
211 			fixed (byte* buf = &buffer[offset]) {
212 				do {
213 					r = Native.Syscall.read (fileDescriptor, buf, (ulong) count);
214 				} while (UnixMarshal.ShouldRetrySyscall ((int) r));
215 			}
216 			if (r == -1)
217 				UnixMarshal.ThrowExceptionForLastError ();
218 			return (int) r;
219 		}
220 
AssertValidBuffer(byte[] buffer, int offset, int count)221 		private void AssertValidBuffer (byte[] buffer, int offset, int count)
222 		{
223 			if (buffer == null)
224 				throw new ArgumentNullException ("buffer");
225 			if (offset < 0)
226 				throw new ArgumentOutOfRangeException ("offset", "< 0");
227 			if (count < 0)
228 				throw new ArgumentOutOfRangeException ("count", "< 0");
229 			if (offset > buffer.Length)
230 				throw new ArgumentException ("destination offset is beyond array size");
231 			if (offset > (buffer.Length - count))
232 				throw new ArgumentException ("would overrun buffer");
233 		}
234 
ReadAtOffset([In, Out] byte[] buffer, int offset, int count, long fileOffset)235 		public unsafe int ReadAtOffset ([In, Out] byte[] buffer,
236 			int offset, int count, long fileOffset)
237 		{
238 			AssertNotDisposed ();
239 			AssertValidBuffer (buffer, offset, count);
240 			if (!CanRead)
241 				throw new NotSupportedException ("Stream does not support reading");
242 
243 			if (buffer.Length == 0)
244 				return 0;
245 
246 			long r = 0;
247 			fixed (byte* buf = &buffer[offset]) {
248 				do {
249 					r = Native.Syscall.pread (fileDescriptor, buf, (ulong) count, fileOffset);
250 				} while (UnixMarshal.ShouldRetrySyscall ((int) r));
251 			}
252 			if (r == -1)
253 				UnixMarshal.ThrowExceptionForLastError ();
254 			return (int) r;
255 		}
256 
Seek(long offset, SeekOrigin origin)257 		public override long Seek (long offset, SeekOrigin origin)
258 		{
259 			AssertNotDisposed ();
260 			if (!CanSeek)
261 				throw new NotSupportedException ("The File Descriptor does not support seeking");
262 
263 			Native.SeekFlags sf = Native.SeekFlags.SEEK_CUR;
264 			switch (origin) {
265 				case SeekOrigin.Begin:   sf = Native.SeekFlags.SEEK_SET; break;
266 				case SeekOrigin.Current: sf = Native.SeekFlags.SEEK_CUR; break;
267 				case SeekOrigin.End:     sf = Native.SeekFlags.SEEK_END; break;
268 			}
269 
270 			long pos = Native.Syscall.lseek (fileDescriptor, offset, sf);
271 			if (pos == -1)
272 				UnixMarshal.ThrowExceptionForLastError ();
273 			return (long) pos;
274 		}
275 
SetLength(long value)276 		public override void SetLength (long value)
277 		{
278 			AssertNotDisposed ();
279 			if (value < 0)
280 				throw new ArgumentOutOfRangeException ("value", "< 0");
281 			if (!CanSeek && !CanWrite)
282 				throw new NotSupportedException ("You can't truncating the current file descriptor");
283 
284 			int r;
285 			do {
286 				r = Native.Syscall.ftruncate (fileDescriptor, value);
287 			} while (UnixMarshal.ShouldRetrySyscall (r));
288 			UnixMarshal.ThrowExceptionForLastErrorIf (r);
289 		}
290 
Write(byte[] buffer, int offset, int count)291 		public override unsafe void Write (byte[] buffer, int offset, int count)
292 		{
293 			AssertNotDisposed ();
294 			AssertValidBuffer (buffer, offset, count);
295 			if (!CanWrite)
296 				throw new NotSupportedException ("File Descriptor does not support writing");
297 
298 			if (buffer.Length == 0)
299 				return;
300 
301 			long r = 0;
302 			fixed (byte* buf = &buffer[offset]) {
303 				do {
304 					r = Native.Syscall.write (fileDescriptor, buf, (ulong) count);
305 				} while (UnixMarshal.ShouldRetrySyscall ((int) r));
306 			}
307 			if (r == -1)
308 				UnixMarshal.ThrowExceptionForLastError ();
309 		}
310 
WriteAtOffset(byte[] buffer, int offset, int count, long fileOffset)311 		public unsafe void WriteAtOffset (byte[] buffer,
312 			int offset, int count, long fileOffset)
313 		{
314 			AssertNotDisposed ();
315 			AssertValidBuffer (buffer, offset, count);
316 			if (!CanWrite)
317 				throw new NotSupportedException ("File Descriptor does not support writing");
318 
319 			if (buffer.Length == 0)
320 				return;
321 
322 			long r = 0;
323 			fixed (byte* buf = &buffer[offset]) {
324 				do {
325 					r = Native.Syscall.pwrite (fileDescriptor, buf, (ulong) count, fileOffset);
326 				} while (UnixMarshal.ShouldRetrySyscall ((int) r));
327 			}
328 			if (r == -1)
329 				UnixMarshal.ThrowExceptionForLastError ();
330 		}
331 
SendTo(UnixStream output)332 		public void SendTo (UnixStream output)
333 		{
334 			SendTo (output, (ulong) output.Length);
335 		}
336 
337 		[CLSCompliant (false)]
SendTo(UnixStream output, ulong count)338 		public void SendTo (UnixStream output, ulong count)
339 		{
340 			SendTo (output.Handle, count);
341 		}
342 
343 		[CLSCompliant (false)]
SendTo(int out_fd, ulong count)344 		public void SendTo (int out_fd, ulong count)
345 		{
346 			if (!CanWrite)
347 				throw new NotSupportedException ("Unable to write to the current file descriptor");
348 			long offset = Position;
349 			long r = Native.Syscall.sendfile (out_fd, fileDescriptor, ref offset, count);
350 			if (r == -1)
351 				UnixMarshal.ThrowExceptionForLastError ();
352 		}
353 
SetOwner(long user, long group)354 		public void SetOwner (long user, long group)
355 		{
356 			AssertNotDisposed ();
357 
358 			int r = Native.Syscall.fchown (fileDescriptor,
359 					Convert.ToUInt32 (user), Convert.ToUInt32 (group));
360 			UnixMarshal.ThrowExceptionForLastErrorIf (r);
361 		}
362 
SetOwner(string user, string group)363 		public void SetOwner (string user, string group)
364 		{
365 			AssertNotDisposed ();
366 
367 			long uid = new UnixUserInfo (user).UserId;
368 			long gid = new UnixGroupInfo (group).GroupId;
369 			SetOwner (uid, gid);
370 		}
371 
SetOwner(string user)372 		public void SetOwner (string user)
373 		{
374 			AssertNotDisposed ();
375 
376 			Native.Passwd pw = Native.Syscall.getpwnam (user);
377 			if (pw == null)
378 				throw new ArgumentException (Locale.GetText ("invalid username"), "user");
379 			long uid = pw.pw_uid;
380 			long gid = pw.pw_gid;
381 			SetOwner (uid, gid);
382 		}
383 
384 		[CLSCompliant (false)]
GetConfigurationValue(Native.PathconfName name)385 		public long GetConfigurationValue (Native.PathconfName name)
386 		{
387 			AssertNotDisposed ();
388 			long r = Native.Syscall.fpathconf (fileDescriptor, name);
389 			if (r == -1 && Native.Syscall.GetLastError() != (Native.Errno) 0)
390 				UnixMarshal.ThrowExceptionForLastError ();
391 			return r;
392 		}
393 
~UnixStream()394 		~UnixStream ()
395 		{
396 			Close ();
397 		}
398 
Close()399 		public override void Close ()
400 		{
401 			if (fileDescriptor == InvalidFileDescriptor)
402 				return;
403 
404 			Flush ();
405 
406 			if (!owner)
407 				return;
408 
409 			int r;
410 			do {
411 				r = Native.Syscall.close (fileDescriptor);
412 			} while (UnixMarshal.ShouldRetrySyscall (r));
413 			UnixMarshal.ThrowExceptionForLastErrorIf (r);
414 			fileDescriptor = InvalidFileDescriptor;
415 			GC.SuppressFinalize (this);
416 		}
417 
IDisposable.Dispose()418 		void IDisposable.Dispose ()
419 		{
420 			if (fileDescriptor != InvalidFileDescriptor && owner) {
421 				Close ();
422 			}
423 			GC.SuppressFinalize (this);
424 		}
425 
426 		private bool canSeek = false;
427 		private bool canRead = false;
428 		private bool canWrite = false;
429 		private bool owner = true;
430 		private int fileDescriptor = InvalidFileDescriptor;
431 		private Native.Stat stat;
432 	}
433 }
434 
435 // vim: noexpandtab
436