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.meta.types.Qualifiers;
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.text.util.StringC;
39 
40     import core.stdc.string;
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 }