1 /******************************************************************************* 2 3 Mapping from C errno to D exception 4 5 Copyright: 6 Copyright (c) 2009-2016 dunnhumby Germany GmbH. 7 All rights reserved. 8 9 License: 10 Boost Software License Version 1.0. See LICENSE_BOOST.txt for details. 11 Alternatively, this file may be distributed under the terms of the Tango 12 3-Clause BSD License (see LICENSE_BSD.txt for details). 13 14 *******************************************************************************/ 15 16 module ocean.sys.ErrnoException; 17 18 19 import ocean.transition; 20 import core.stdc.errno; 21 22 version (UnitTest) 23 { 24 import ocean.core.Test; 25 } 26 27 /******************************************************************************* 28 29 Exception class which reads, stores and resets the thread-local errno 30 31 *******************************************************************************/ 32 33 public class ErrnoException : Exception 34 { 35 import ocean.core.Exception : ReusableExceptionImplementation; 36 import ocean.meta.types.Function : ReturnTypeOf; 37 import ocean.meta.codegen.Identifier; 38 import ocean.stdc.string; 39 import ocean.text.util.StringC; 40 41 42 /************************************************************************** 43 44 Provides standard reusable exception API 45 46 **************************************************************************/ 47 48 mixin ReusableExceptionImplementation!() ReusableImpl; 49 50 /************************************************************************** 51 52 Last processed .errno 53 54 ***************************************************************************/ 55 56 private int last_errno = 0; 57 58 /************************************************************************** 59 60 Last failed function name 61 62 ***************************************************************************/ 63 64 private istring func_name; 65 66 /************************************************************************** 67 68 Returns: 69 last processed .errno getter 70 71 ***************************************************************************/ 72 73 public int errorNumber ( ) 74 { 75 return this.last_errno; 76 } 77 78 /************************************************************************** 79 80 Returns: 81 last failed function name getter 82 83 ***************************************************************************/ 84 85 public istring failedFunctionName ( ) 86 { 87 return this.func_name; 88 } 89 90 /************************************************************************** 91 92 Tries to evaluate `expr`. If it fails, checks the global errno and 93 uses matching message as base for exception message, as well as throws 94 the exception. 95 96 Params: 97 expr = expression that returns `false` upon failure that is 98 expected to set global errno 99 msg = extra error message to append after main error 100 description, can be empty 101 name = extern function name that is expected to set errno 102 file = file where the expr is evaluated 103 line = line where the expr is evaluated 104 105 Throws: 106 this if 'expr' is false 107 108 ***************************************************************************/ 109 110 public void enforce ( bool expr, lazy cstring msg, istring name = "", 111 istring file = __FILE__, int line = __LINE__ ) 112 { 113 if (!expr) 114 { 115 this.func_name = name; 116 throw this.useGlobalErrno(name, file, line).addMessage(msg); 117 } 118 } 119 120 /// 121 unittest 122 { 123 try 124 { 125 (new ErrnoException).enforce( 126 { .errno = EMFILE; return false; } (), 127 "extra", 128 "FUNCTION" 129 ); 130 test(false); 131 } 132 catch (ErrnoException e) 133 { 134 test!("==")(e.message(), 135 "FUNCTION: Too many open files (extra)"[]); 136 test!("==")(e.errorNumber(), EMFILE); 137 } 138 } 139 140 /************************************************************************** 141 142 Calls `Func` automatically checking errno and storing name 143 144 If `verify` returns `false` when called with return value of `Func`, 145 global `errno` gets checked and this ErrnoException instance gets 146 thrown with message set from that `errno` and caller name set to 147 `Func` identifier. 148 149 Presence of additional wrapper struct is dictated by limitations 150 of dmd1 type inference in method/function signatures. 151 152 Params: 153 Func = function alias, usually C library function 154 verify = lambda that takes return value of Func and returns 155 `true` itself if result is considered successful 156 file = file where the expr is evaluated 157 line = line where the expr is evaluated 158 159 Returns: 160 proxy `Caller` value with a single `call` method which accepts 161 same arguments as `Func` and throws this exception if 162 `verify(Func(args))` evaluates to `false`. 163 164 **************************************************************************/ 165 166 public Caller!(typeof(&Func)) enforceRet (alias Func) ( 167 bool function (ReturnTypeOf!(Func)) verify, 168 istring file = __FILE__, int line = __LINE__ ) 169 { 170 static assert (is(typeof(Func) == function)); 171 this.func_name = identifier!(Func); 172 return Caller!(typeof(&Func))(&Func, file, line, this, verify); 173 } 174 175 /************************************************************************** 176 177 Calls `enforceRet` expecting `Func` to return non-NULL 178 179 Wraps `enforceRet` with a lambda that verifies that return value is 180 not null 181 182 Returns: 183 same as `enforceRet` 184 185 **************************************************************************/ 186 187 public Caller!(typeof(&Func)) enforceRetPtr (alias Func) ( 188 istring file = __FILE__, int line = __LINE__ ) 189 { 190 static bool verify ( ReturnTypeOf!(Func) x) 191 { 192 return x !is null; 193 } 194 195 return enforceRet!(Func)(&verify, file, line); 196 } 197 198 /// 199 unittest 200 { 201 extern(C) static void* func ( ) 202 { 203 .errno = EMFILE; 204 return null; 205 } 206 207 try 208 { 209 (new ErrnoException).enforceRetPtr!(func).call(); 210 test(false); 211 } 212 catch (ErrnoException e) 213 { 214 test!("==")(e.message(), "func: Too many open files"[]); 215 test!("==")(e.line, __LINE__ - 6); 216 } 217 } 218 219 /************************************************************************** 220 221 Calls `enforceRet` interpreting return value as error code 222 223 Wraps `enforceRet` with a lambda that verifies that return value is 224 zero (success code). Any non-zero return value of `Func` is interpreted 225 as a failure. 226 227 Returns: 228 same as `enforceRet` 229 230 **************************************************************************/ 231 232 public Caller!(typeof(&Func)) enforceRetCode (alias Func) ( 233 istring file = __FILE__, int line = __LINE__ ) 234 { 235 static bool verify ( ReturnTypeOf!(Func) x ) 236 { 237 return x == 0; 238 } 239 240 return enforceRet!(Func)(&verify, file, line); 241 } 242 243 /// 244 unittest 245 { 246 extern(C) static int func(int a, int b) 247 { 248 .errno = EMFILE; 249 test!("==")(a, 41); 250 test!("==")(b, 43); 251 return -1; 252 } 253 254 try 255 { 256 (new ErrnoException).enforceRetCode!(func)().call(41, 43); 257 test(false); 258 } 259 catch (ErrnoException e) 260 { 261 test!("==")(e.message(), "func: Too many open files"[]); 262 test!("==")(e.failedFunctionName(), "func"[]); 263 test!("==")(e.line, __LINE__ - 7); 264 } 265 } 266 267 268 /************************************************************************** 269 270 Initializes local reusable error message based on global errno value 271 and resets errno to 0. 272 273 Params: 274 name = extern function name that is expected to set errno, optional 275 file = file where the exception errno is set 276 line = line where the exception errno is set 277 278 Returns: 279 this instance 280 281 **************************************************************************/ 282 283 public typeof (this) useGlobalErrno ( istring name = "", 284 istring file = __FILE__, int line = __LINE__ ) 285 { 286 return this.set(.errno, name, file, line); 287 } 288 289 /// 290 unittest 291 { 292 .errno = ENOTBLK; 293 auto e = new ErrnoException; 294 test!("==")( 295 e.useGlobalErrno("func").append(" str1").append(" str2").message(), 296 "func: Block device required str1 str2"[] 297 ); 298 test!("==")(.errno, ENOTBLK); 299 } 300 301 /************************************************************************** 302 303 Initializes local reusable error message based on supplied errno value 304 305 Params: 306 err_num = error number with same value set as in errno 307 name = extern function name that is expected to set errno, optional 308 file = file where the exception errno is set 309 line = line where the exception errno is set 310 311 Returns: 312 this 313 314 **************************************************************************/ 315 316 public typeof (this) set ( int err_num, istring name = "", 317 istring file = __FILE__, int line = __LINE__ ) 318 { 319 this.func_name = name; 320 this.last_errno = err_num; 321 322 if (this.func_name.length) 323 this.ReusableImpl.set(this.func_name, file, line).append(": "); 324 else 325 this.ReusableImpl.set("", file, line); 326 327 if (err_num == 0) 328 return this.append("Expected non-zero errno after failure"); 329 330 char[256] buf; 331 auto errmsg = StringC.toDString(strerror_r(err_num, buf.ptr, buf.length)); 332 return this.ReusableImpl.append(errmsg); 333 } 334 335 /// 336 unittest 337 { 338 auto e = new ErrnoException; 339 e.set(0); 340 test!("==")(e.message(), "Expected non-zero errno after failure"[]); 341 } 342 343 /************************************************************************** 344 345 Convenience method to append extra message in brackets 346 347 If `msg` is empty, nothing is done 348 349 Params: 350 msg = additional message to clarify the error, will be appended 351 after errno-based messaged inside parenthesis 352 353 Returns: 354 this 355 356 **************************************************************************/ 357 358 public typeof (this) addMessage ( cstring msg ) 359 { 360 if (msg.length) 361 return this.append(" (").append(msg).append(")"); 362 else 363 return this; 364 } 365 366 /// 367 unittest 368 { 369 auto e = new ErrnoException; 370 e.set(ENOTBLK).addMessage("msg"); 371 test!("==")(e.message(), "Block device required (msg)"[]); 372 } 373 } 374 375 /******************************************************************************* 376 377 Struct that captures callable object together with ErrnoException exception 378 object and line/file values of original context. 379 380 Used only as return type of `ErrnoException.enforceRet` 381 382 *******************************************************************************/ 383 384 public struct Caller ( T ) 385 { 386 import ocean.meta.types.Function /* : ParametersOf, ReturnTypeOf */; 387 import ocean.meta.traits.Basic /* : isCallableType */; 388 389 static assert (isCallableType!(T)); 390 static assert (!is(ReturnTypeOf!(T) == void)); 391 392 /// wrapped function to call/verify 393 private T fn; 394 /// file where `Caller` was created, used as exception file 395 private istring original_file; 396 /// line where `Caller` was created, used as exception line 397 private int original_line; 398 /// exception to throw if `verify` fails 399 private ErrnoException e; 400 /// function that checks if return value of `this.fn` is "success" 401 private bool function (ReturnTypeOf!(T)) verify; 402 403 /*************************************************************************** 404 405 Calls stored function pointer / delegate with `args` and throws 406 stored exception object if return value of callable evaulates to `false` 407 408 Params: 409 args = variadic argument list to proxy 410 411 Returns: 412 whatever `this.fn` returns 413 414 Throws: 415 this.e if stored function returns 'false' 416 417 ***************************************************************************/ 418 419 public ReturnTypeOf!(T) call ( ParametersOf!(T) args ) 420 { 421 auto ret = (&this).fn(args); 422 if (!verify(ret)) 423 throw e.useGlobalErrno(e.func_name, (&this).original_file, 424 (&this).original_line); 425 return ret; 426 } 427 }