1 //
2 // Mono.Unix/UnixMarshal.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.Net.Sockets;
32 using System.Runtime.InteropServices;
33 using System.Runtime.Serialization;
34 using System.Text;
35 using Mono.Unix;
36 
37 namespace Mono.Unix {
38 
39 	// Scenario:  We want to be able to translate an Error to a string.
40 	//  Problem:  Thread-safety.  Strerror(3) isn't thread safe (unless
41 	//            thread-local-variables are used, which is probably only
42 	//            true on Windows).
43 	// Solution:  Use strerror_r().
44 	//  Problem:  strerror_r() isn't portable.
45 	//            (Apparently Solaris doesn't provide it.)
46 	// Solution:  Cry.  Then introduce an intermediary, ErrorMarshal.
47 	//            ErrorMarshal exposes a single public delegate, Translator,
48 	//            which will convert an Error to a string.  It's static
49 	//            constructor first tries using strerror_r().  If it works,
50 	//            great; use it in the future.  If it doesn't work, fallback to
51 	//            using strerror(3).
52 	//            This should be thread safe, since the check is done within the
53 	//            class constructor lock.
54 	//            Strerror(3) will be thread-safe from managed code, but won't
55 	//            be thread-safe between managed & unmanaged code.
56 	internal class ErrorMarshal
57 	{
ErrorTranslator(Native.Errno errno)58 		internal delegate string ErrorTranslator (Native.Errno errno);
59 
60 		internal static readonly ErrorTranslator Translate;
61 
ErrorMarshal()62 		static ErrorMarshal ()
63 		{
64 			try {
65 				Translate = new ErrorTranslator (strerror_r);
66 				Translate (Native.Errno.ERANGE);
67 			}
68 			catch (EntryPointNotFoundException) {
69 				Translate = new ErrorTranslator (strerror);
70 			}
71 		}
72 
strerror(Native.Errno errno)73 		private static string strerror (Native.Errno errno)
74 		{
75 			return Native.Stdlib.strerror (errno);
76 		}
77 
strerror_r(Native.Errno errno)78 		private static string strerror_r (Native.Errno errno)
79 		{
80 			StringBuilder buf = new StringBuilder (16);
81 			int r = 0;
82 			do {
83 				buf.Capacity *= 2;
84 				r = Native.Syscall.strerror_r (errno, buf);
85 			} while (r == -1 && Native.Stdlib.GetLastError() == Native.Errno.ERANGE);
86 
87 			if (r == -1)
88 				return "** Unknown error code: " + ((int) errno) + "**";
89 			return buf.ToString();
90 		}
91 	}
92 
93 	public sealed /* static */ class UnixMarshal
94 	{
UnixMarshal()95 		private UnixMarshal () {}
96 
97 		[CLSCompliant (false)]
GetErrorDescription(Native.Errno errno)98 		public static string GetErrorDescription (Native.Errno errno)
99 		{
100 			return ErrorMarshal.Translate (errno);
101 		}
102 
AllocHeap(long size)103 		public static IntPtr AllocHeap (long size)
104 		{
105 			if (size < 0)
106 				throw new ArgumentOutOfRangeException ("size", "< 0");
107 			return Native.Stdlib.malloc ((ulong) size);
108 		}
109 
ReAllocHeap(IntPtr ptr, long size)110 		public static IntPtr ReAllocHeap (IntPtr ptr, long size)
111 		{
112 			if (size < 0)
113 				throw new ArgumentOutOfRangeException ("size", "< 0");
114 			return Native.Stdlib.realloc (ptr, (ulong) size);
115 		}
116 
FreeHeap(IntPtr ptr)117 		public static void FreeHeap (IntPtr ptr)
118 		{
119 			Native.Stdlib.free (ptr);
120 		}
121 
PtrToStringUnix(IntPtr p)122 		public static unsafe string PtrToStringUnix (IntPtr p)
123 		{
124 			if (p == IntPtr.Zero)
125 				return null;
126 
127 			int len = checked ((int) Native.Stdlib.strlen (p));
128 			return new string ((sbyte*) p, 0, len, UnixEncoding.Instance);
129 		}
130 
PtrToString(IntPtr p)131 		public static string PtrToString (IntPtr p)
132 		{
133 			if (p == IntPtr.Zero)
134 				return null;
135 			return PtrToString (p, UnixEncoding.Instance);
136 		}
137 
PtrToString(IntPtr p, Encoding encoding)138 		public static unsafe string PtrToString (IntPtr p, Encoding encoding)
139 		{
140 			if (p == IntPtr.Zero)
141 				return null;
142 
143 			if (encoding == null)
144 				throw new ArgumentNullException ("encoding");
145 
146 			int len = GetStringByteLength (p, encoding);
147 
148 			// Due to variable-length encoding schemes, GetStringByteLength() may
149 			// have returned multiple "null" characters.  (For example, when
150 			// encoding a string into UTF-8 there will be 4 terminating nulls.)
151 			// We don't want these null's to be in the returned string, so strip
152 			// them off.
153 			string s = new string ((sbyte*) p, 0, len, encoding);
154 			len = s.Length;
155 			while (len > 0 && s [len-1] == 0)
156 				--len;
157 			if (len == s.Length)
158 				return s;
159 			return s.Substring (0, len);
160 		}
161 
GetStringByteLength(IntPtr p, Encoding encoding)162 		private static int GetStringByteLength (IntPtr p, Encoding encoding)
163 		{
164 			Type encodingType = encoding.GetType ();
165 
166 			int len = -1;
167 
168 			// Encodings that will always end with a single null byte
169 			if (typeof(UTF8Encoding).IsAssignableFrom (encodingType) ||
170 					typeof(UTF7Encoding).IsAssignableFrom (encodingType) ||
171 					typeof(UnixEncoding).IsAssignableFrom (encodingType) ||
172 					typeof(ASCIIEncoding).IsAssignableFrom (encodingType)) {
173 				len = checked ((int) Native.Stdlib.strlen (p));
174 			}
175 			// Encodings that will always end with a 0x0000 16-bit word
176 			else if (typeof(UnicodeEncoding).IsAssignableFrom (encodingType)) {
177 				len = GetInt16BufferLength (p);
178 			}
179 			// Encodings that will always end with a 0x00000000 32-bit word
180 			else if (typeof(UTF32Encoding).IsAssignableFrom (encodingType)) {
181 				len = GetInt32BufferLength (p);
182 			}
183 			// Some non-public encoding, such as Latin1 or a DBCS charset.
184 			// Look for a sequence of encoding.GetMaxByteCount() bytes that are all
185 			// 0, which should be the terminating null.
186 			// This is "iffy", since it may fail for variable-width encodings; for
187 			// example, UTF8Encoding.GetMaxByteCount(1) = 4, so this would read 3
188 			// bytes past the end of the string, possibly into garbage memory
189 			// (which is why we special case UTF above).
190 			else {
191 				len = GetRandomBufferLength (p, encoding.GetMaxByteCount(1));
192 			}
193 
194 			if (len == -1)
195 				throw new NotSupportedException ("Unable to determine native string buffer length");
196 			return len;
197 		}
198 
GetInt16BufferLength(IntPtr p)199 		private static int GetInt16BufferLength (IntPtr p)
200 		{
201 			int len = 0;
202 			while (Marshal.ReadInt16 (p, len*2) != 0)
203 				checked {++len;}
204 			return checked(len*2);
205 		}
206 
GetInt32BufferLength(IntPtr p)207 		private static int GetInt32BufferLength (IntPtr p)
208 		{
209 			int len = 0;
210 			while (Marshal.ReadInt32 (p, len*4) != 0)
211 				checked {++len;}
212 			return checked(len*4);
213 		}
214 
GetRandomBufferLength(IntPtr p, int nullLength)215 		private static int GetRandomBufferLength (IntPtr p, int nullLength)
216 		{
217 			switch (nullLength) {
218 				case 1: return checked ((int) Native.Stdlib.strlen (p));
219 				case 2: return GetInt16BufferLength (p);
220 				case 4: return GetInt32BufferLength (p);
221 			}
222 
223 			int len = 0;
224 			int num_null_seen = 0;
225 
226 			do {
227 				byte b = Marshal.ReadByte (p, len++);
228 				if (b == 0)
229 					++num_null_seen;
230 				else
231 					num_null_seen = 0;
232 			} while (num_null_seen != nullLength);
233 
234 			return len;
235 		}
236 
237 		/*
238 		 * Marshal a C `char **'.  ANSI C `main' requirements are assumed:
239 		 *
240 		 *   stringArray is an array of pointers to C strings
241 		 *   stringArray has a terminating NULL string.
242 		 *
243 		 * For example:
244 		 *   stringArray[0] = "string 1";
245 		 *   stringArray[1] = "string 2";
246 		 *   stringArray[2] = NULL
247 		 *
248 		 * The terminating NULL is required so that we know when to stop looking
249 		 * for strings.
250 		 */
PtrToStringArray(IntPtr stringArray)251 		public static string[] PtrToStringArray (IntPtr stringArray)
252 		{
253 			return PtrToStringArray (stringArray, UnixEncoding.Instance);
254 		}
255 
PtrToStringArray(IntPtr stringArray, Encoding encoding)256 		public static string[] PtrToStringArray (IntPtr stringArray, Encoding encoding)
257 		{
258 			if (stringArray == IntPtr.Zero)
259 				return new string[]{};
260 
261 			int argc = CountStrings (stringArray);
262 			return PtrToStringArray (argc, stringArray, encoding);
263 		}
264 
CountStrings(IntPtr stringArray)265 		private static int CountStrings (IntPtr stringArray)
266 		{
267 			int count = 0;
268 			while (Marshal.ReadIntPtr (stringArray, count*IntPtr.Size) != IntPtr.Zero)
269 				++count;
270 			return count;
271 		}
272 
273 		/*
274 		 * Like PtrToStringArray(IntPtr), but it allows the user to specify how
275 		 * many strings to look for in the array.  As such, the requirement for a
276 		 * terminating NULL element is not required.
277 		 *
278 		 * Usage is similar to ANSI C `main': count is argc, stringArray is argv.
279 		 * stringArray[count] is NOT accessed (though ANSI C requires that
280 		 * argv[argc] = NULL, which PtrToStringArray(IntPtr) requires).
281 		 */
PtrToStringArray(int count, IntPtr stringArray)282 		public static string[] PtrToStringArray (int count, IntPtr stringArray)
283 		{
284 			return PtrToStringArray (count, stringArray, UnixEncoding.Instance);
285 		}
286 
PtrToStringArray(int count, IntPtr stringArray, Encoding encoding)287 		public static string[] PtrToStringArray (int count, IntPtr stringArray, Encoding encoding)
288 		{
289 			if (count < 0)
290 				throw new ArgumentOutOfRangeException ("count", "< 0");
291 			if (encoding == null)
292 				throw new ArgumentNullException ("encoding");
293 			if (stringArray == IntPtr.Zero)
294 				return new string[count];
295 
296 			string[] members = new string[count];
297 			for (int i = 0; i < count; ++i) {
298 				IntPtr s = Marshal.ReadIntPtr (stringArray, i * IntPtr.Size);
299 				members[i] = PtrToString (s, encoding);
300 			}
301 
302 			return members;
303 		}
304 
StringToHeap(string s)305 		public static IntPtr StringToHeap (string s)
306 		{
307 			return StringToHeap (s, UnixEncoding.Instance);
308 		}
309 
StringToHeap(string s, Encoding encoding)310 		public static IntPtr StringToHeap (string s, Encoding encoding)
311 		{
312 			if (s == null)
313 				return IntPtr.Zero;
314 
315 			return StringToHeap (s, 0, s.Length, encoding);
316 		}
317 
StringToHeap(string s, int index, int count)318 		public static IntPtr StringToHeap (string s, int index, int count)
319 		{
320 			return StringToHeap (s, index, count, UnixEncoding.Instance);
321 		}
322 
StringToHeap(string s, int index, int count, Encoding encoding)323 		public static IntPtr StringToHeap (string s, int index, int count, Encoding encoding)
324 		{
325 			if (s == null)
326 				return IntPtr.Zero;
327 
328 			if (encoding == null)
329 				throw new ArgumentNullException ("encoding");
330 
331 			if (index < 0 || count < 0)
332 				throw new ArgumentOutOfRangeException ((index < 0 ? "index" : "count"),
333 					 "Non - negative number required.");
334 
335 			if (s.Length - index < count)
336 				throw new ArgumentOutOfRangeException ("s", "Index and count must refer to a location within the string.");
337 
338 			int null_terminator_count = encoding.GetMaxByteCount (1);
339 			int length_without_null = encoding.GetByteCount (s);
340 			int marshalLength = checked (length_without_null + null_terminator_count);
341 
342 			IntPtr mem = AllocHeap (marshalLength);
343 			if (mem == IntPtr.Zero)
344 				throw new UnixIOException (Native.Errno.ENOMEM);
345 
346 			unsafe {
347 				fixed (char* p = s) {
348 					byte* marshal = (byte*)mem;
349 					int bytes_copied;
350 
351 					try {
352 						bytes_copied = encoding.GetBytes (p + index, count, marshal, marshalLength);
353 					} catch {
354 						FreeHeap (mem);
355 						throw;
356 					}
357 
358 					if (bytes_copied != length_without_null) {
359 						FreeHeap (mem);
360 						throw new NotSupportedException ("encoding.GetBytes() doesn't equal encoding.GetByteCount()!");
361 					}
362 
363 					marshal += length_without_null;
364 					for (int i = 0; i < null_terminator_count; ++i)
365 						marshal[i] = 0;
366 				}
367 			}
368 
369 			return mem;
370 		}
371 
ShouldRetrySyscall(int r)372 		public static bool ShouldRetrySyscall (int r)
373 		{
374 			if (r == -1 && Native.Stdlib.GetLastError () == Native.Errno.EINTR)
375 				return true;
376 			return false;
377 		}
378 
379 		[CLSCompliant (false)]
ShouldRetrySyscall(int r, out Native.Errno errno)380 		public static bool ShouldRetrySyscall (int r, out Native.Errno errno)
381 		{
382 			errno = (Native.Errno) 0;
383 			if (r == -1 && (errno = Native.Stdlib.GetLastError ()) == Native.Errno.EINTR)
384 				return true;
385 			return false;
386 		}
387 
388 		// we can't permit any printf(3)-style formatting information, since that
389 		// would kill the stack.  However, replacing %% is silly, and some %* are
390 		// permitted (such as %m in syslog to print strerror(errno)).
EscapeFormatString(string message, char [] permitted)391 		internal static string EscapeFormatString (string message,
392 				char [] permitted)
393 		{
394 			if (message == null)
395 				return "";
396 			StringBuilder sb = new StringBuilder (message.Length);
397 			for (int i = 0; i < message.Length; ++i) {
398 				char c = message [i];
399 				sb.Append (c);
400 				if (c == '%' && (i+1) < message.Length) {
401 					char n = message [i+1];
402 					if (n == '%' || IsCharPresent (permitted, n))
403 						sb.Append (n);
404 					else
405 						sb.Append ('%').Append (n);
406 					++i;
407 				}
408 				// invalid format string: % at EOS.
409 				else if (c == '%')
410 					sb.Append ('%');
411 			}
412 			return sb.ToString ();
413 		}
414 
IsCharPresent(char[] array, char c)415 		private static bool IsCharPresent (char[] array, char c)
416 		{
417 			if (array == null)
418 				return false;
419 			for (int i = 0; i < array.Length; ++i)
420 				if (array [i] == c)
421 					return true;
422 			return false;
423 		}
424 
CreateExceptionForError(Native.Errno errno)425 		internal static Exception CreateExceptionForError (Native.Errno errno)
426 		{
427 			string message = GetErrorDescription (errno);
428 			UnixIOException p = new UnixIOException (errno);
429 
430 			// Ordering: Order alphabetically by exception first (right column),
431 			// then order alphabetically by Errno value (left column) for the given
432 			// exception.
433 			switch (errno) {
434 				case Native.Errno.EBADF:
435 				case Native.Errno.EINVAL:        return new ArgumentException (message, p);
436 
437 				case Native.Errno.ERANGE:        return new ArgumentOutOfRangeException (message);
438 				case Native.Errno.ENOTDIR:       return new DirectoryNotFoundException (message, p);
439 				case Native.Errno.ENOENT:        return new FileNotFoundException (message, p);
440 
441 				case Native.Errno.EOPNOTSUPP:
442 				case Native.Errno.EPERM:         return new InvalidOperationException (message, p);
443 
444 				case Native.Errno.ENOEXEC:       return new InvalidProgramException (message, p);
445 
446 				case Native.Errno.EIO:
447 				case Native.Errno.ENOSPC:
448 				case Native.Errno.ENOTEMPTY:
449 				case Native.Errno.ENXIO:
450 				case Native.Errno.EROFS:
451 				case Native.Errno.ESPIPE:        return new IOException (message, p);
452 
453 				case Native.Errno.EFAULT:        return new NullReferenceException (message, p);
454 				case Native.Errno.EOVERFLOW:     return new OverflowException (message, p);
455 				case Native.Errno.ENAMETOOLONG:  return new PathTooLongException (message, p);
456 
457 				case Native.Errno.EACCES:
458 				case Native.Errno.EISDIR:        return new UnauthorizedAccessException (message, p);
459 
460 				default: /* ignore */     break;
461 			}
462 			return p;
463 		}
464 
CreateExceptionForLastError()465 		internal static Exception CreateExceptionForLastError ()
466 		{
467 			return CreateExceptionForError (Native.Stdlib.GetLastError());
468 		}
469 
470 		[CLSCompliant (false)]
ThrowExceptionForError(Native.Errno errno)471 		public static void ThrowExceptionForError (Native.Errno errno)
472 		{
473 			throw CreateExceptionForError (errno);
474 		}
475 
ThrowExceptionForLastError()476 		public static void ThrowExceptionForLastError ()
477 		{
478 			throw CreateExceptionForLastError ();
479 		}
480 
481 		[CLSCompliant (false)]
ThrowExceptionForErrorIf(int retval, Native.Errno errno)482 		public static void ThrowExceptionForErrorIf (int retval, Native.Errno errno)
483 		{
484 			if (retval == -1)
485 				ThrowExceptionForError (errno);
486 		}
487 
ThrowExceptionForLastErrorIf(int retval)488 		public static void ThrowExceptionForLastErrorIf (int retval)
489 		{
490 			if (retval == -1)
491 				ThrowExceptionForLastError ();
492 		}
493 	}
494 }
495 
496 // vim: noexpandtab
497