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