diff options
Diffstat (limited to 'contrib/sendmail/libsm/exc.html')
-rw-r--r-- | contrib/sendmail/libsm/exc.html | 757 |
1 files changed, 757 insertions, 0 deletions
diff --git a/contrib/sendmail/libsm/exc.html b/contrib/sendmail/libsm/exc.html new file mode 100644 index 0000000..a299036 --- /dev/null +++ b/contrib/sendmail/libsm/exc.html @@ -0,0 +1,757 @@ +<html> +<head> + <title>libsm : Exception Handling</title> +</head> +<body> + +<a href="index.html">Back to libsm overview</a> + +<center> + <h1> libsm : Exception Handling </h1> + <br> $Id: exc.html,v 1.12 2001/02/13 21:21:25 gshapiro Exp $ +</center> + +<h2> Introduction </h2> + +The exception handling package provides the facilities that +functions in libsm use to report errors. +Here are the basic concepts: + +<ol> +<li> + When a function detects an exceptional condition at the library level, + it does not print an error message, or call syslog, or + exit the program. Instead, it reports the error back to its + caller, and lets the caller decide what to do. + This improves modularity, because error handling is separated + from error reporting. + <p> +<li> + Errors are not represented by a single integer error code, + because that you can't represent everything that an error handler + might need to know about an error by a single integer. + Instead, errors are represented by exception objects. + An exception object contains an exception code and an array + of zero or more exception arguments. + The exception code is a string that specifies what kind of exception + this is, and the arguments may be integers, strings or exception objects. + <p> +<li> + Errors are not reported using a special return value, + because if you religiously check for error returns from every + function call that could fail, then most of your code ends up being + error handling code. Errors are reported by raising an exception. + When an exception is raised, we unwind the call stack + until we find an exception handler. If the exception is + not handled, then we print the exception on stderr and + exit the program. +</ol> + +<h2> Synopsis </h2> + +<pre> +#include <sm/exc.h> + +typedef struct sm_exc_type SM_EXC_TYPE_T; +typedef struct sm_exc SM_EXC_T; +typedef union sm_val SM_VAL_T; + +/* +** Exception types +*/ + +extern const char SmExcTypeMagic[]; + +struct sm_exc_type +{ + const char *sm_magic; + const char *etype_category; + const char *etype_argformat; + void (*etype_print)(SM_EXC_T *exc, SM_FILE_T *stream); + const char *etype_printcontext; +}; + +extern const SM_EXC_TYPE_T SmEtypeOs; +extern const SM_EXC_TYPE_T SmEtypeErr; + +void +sm_etype_printf( + SM_EXC_T *exc, + SM_FILE_T *stream); + +/* +** Exception objects +*/ + +extern const char SmExcMagic[]; + +union sm_val +{ + int v_int; + long v_long; + char *v_str; + SM_EXC_T *v_exc; +}; + +struct sm_exc +{ + const char *sm_magic; + size_t exc_refcount; + const SM_EXC_TYPE_T *exc_type; + SM_VAL_T *exc_argv; +}; + +SM_EXC_T * +sm_exc_new_x( + const SM_EXC_TYPE_T *type, + ...); + +SM_EXC_T * +sm_exc_addref( + SM_EXC_T *exc); + +void +sm_exc_free( + SM_EXC_T *exc); + +bool +sm_exc_match( + SM_EXC_T *exc, + const char *pattern); + +void +sm_exc_print( + SM_EXC_T *exc, + SM_FILE_T *stream); + +void +sm_exc_write( + SM_EXC_T *exc, + SM_FILE_T *stream); + +void +sm_exc_raise_x( + SM_EXC_T *exc); + +void +sm_exc_raisenew_x( + const SM_EXC_TYPE_T *type, + ...); + +/* +** Ensure that cleanup code is executed, +** and/or handle an exception. +*/ +SM_TRY + Block of code that may raise an exception. +SM_FINALLY + Cleanup code that may raise an exception. + This clause is guaranteed to be executed even if an exception is + raised by the SM_TRY clause or by an earlier SM_FINALLY clause. + You may have 0 or more SM_FINALLY clauses. +SM_EXCEPT(exc, pattern) + Exception handling code, triggered by an exception + whose category matches 'pattern'. + You may have 0 or more SM_EXCEPT clauses. +SM_END_TRY +</pre> + +<h2> Overview </h2> + + An exception is an object which represents an exceptional condition, + which might be an error condition like "out of memory", or might be + a condition like "end of file". +<p> + Functions in libsm report errors and other unusual conditions by + raising an exception, rather than by returning an error code or + setting a global variable such as errno. If a libsm function is + capable of raising an exception, its name ends in "_x". + (We do not raise an exception when a bug is detected in the + program; instead, we terminate the program using <tt>sm_abort</tt>. + See <a href="assert.html">the assertion package</a> + for details.) +<p> + When you are using the libsm exception handling package, + you are using a new programming paradigm. + You will need to abandon some of the programming idioms + you are accustomed to, and switch to new idioms. + Here is an overview of some of these idioms. +<ol> +<li> + When a function is unable to complete its task because + of an exceptional condition, it reports this condition + by raising an exception. + <p> + Here is an example of how to construct an exception object + and raise an exception. + In this example, we convert a Unix system error into an exception. +<blockquote><pre> +fd = open(path, O_RDONLY); +if (fd == -1) + sm_exc_raise_x(sm_exc_new_x(&SmEtypeOs, errno, "open", "%s", path)); +</pre></blockquote> + + Because the idiom <tt>sm_exc_raise_x(sm_exc_new_x(...))</tt> + is so common, it can be abbreviated as <tt>sm_exc_raisenew_x(...)</tt>. +<p> +<li> + When you detect an error at the application level, + you don't call a function like BSD's <tt>errx</tt>, + which prints an error message on stderr and exits the program. + Instead, you raise an exception. + This causes cleanup code in surrounding exception handlers + to be run before the program exits. + For example, instead of this: +<blockquote><pre> +errx(1, "%s:%d: syntax error", filename, lineno); +</pre></blockquote> + + use this: + +<blockquote><pre> +sm_exc_raisenew_x(&SmEtypeErr, "%s:%d: syntax error", filename, lineno); +</pre></blockquote> + + The latter code raises an exception, unwinding the call stack + and executing cleanup code. + If the exception is not handled, then the exception is printed + to stderr and the program exits. + The end result is substantially the same as a call to <tt>errx</tt>. +<p> +<li> + The SM_TRY ... SM_FINALLY ... control structure + ensures that cleanup code is executed and resources are released + in the presence of exceptions. +<p> + For example, suppose that you have written the following code: + +<blockquote><pre> +rpool = sm_rpool_new_x(&SmRpoolRoot, 0); +... some code ... +sm_rpool_free_x(rpool); +</pre></blockquote> + + If any of the functions called within "... some code ..." have + names ending in _x, then it is possible that an exception will be + raised, and if that happens, then "rpool" will not be freed. + And that's a bug. To fix this bug, change your code so it looks + like this: + +<blockquote><pre> +rpool = sm_rpool_new_x(&SmRpoolRoot, 0); +SM_TRY + ... some code that can raise an exception ... +SM_FINALLY + sm_rpool_free_x(rpool); +SM_END_TRY +</pre></blockquote> + +<li> + The SM_TRY ... SM_EXCEPT ... control structure handles an exception. + Unhandled exceptions terminate the program. + For example, here is a simple exception handler + that traps all exceptions, and prints the exceptions: + +<blockquote><pre> +SM_TRY + /* code that can raise an exception */ + ... +SM_EXCEPT(exc, "*") + /* catch all exceptions */ + sm_exc_print(exc, stderr); +SM_END_TRY +</pre></blockquote> + + Exceptions are reference counted. The SM_END_TRY macro contains a + call to sm_exc_free, so you don't normally need to worry about freeing + an exception after handling it. In the rare case that you want an + exception to outlive an exception handler, then you increment its + reference count by calling sm_exc_addref. +<p> +<li> + The second argument of the SM_EXCEPT macro is a glob pattern + which specifies the types of exceptions that are to be handled. + For example, you might want to handle an end-of-file exception + differently from other exceptions. + Here's how you do that: + +<blockquote><pre> +SM_TRY + /* code that might raise end-of-file, or some other exception */ + ... +SM_EXCEPT(exc, "E:sm.eof") + /* what to do if end-of-file is encountered */ + ... +SM_EXCEPT(exc, "*") + /* what to do if some other exception is raised */ + ... +SM_END_TRY +</pre></blockquote> +</ol> + +<h2> Exception Values </h2> + +In traditional C code, errors are usually denoted by a single integer, +such as errno. In practice, errno does not carry enough information +to describe everything that an error handler might want to know about +an error. And the scheme is not very extensible: if several different +packages want to add additional error codes, it is hard to avoid +collisions. + +<p> +In libsm, an exceptional condition is described +by an object of type SM_EXC_T. +An exception object is created by specifying an exception type +and a list of exception arguments. + +<p> +The exception arguments are an array of zero or more values. +The values may be a mixture of ints, longs, strings, and exceptions. +In the SM_EXC_T structure, the argument vector is represented +by <tt>SM_VAL_T *exc_argv</tt>, where <tt>SM_VAL_T</tt> +is a union of the possible argument types. +The number and types of exception arguments is determined by +the exception type. + +<p> +An exception type is a statically initialized const object +of type SM_EXC_TYPE_T, which has the following members: + +<dl> +<dt> +<tt> const char *sm_magic </tt> +<dd> + A pointer to <tt>SmExcTypeMagic</tt>. + <p> +<dt> +<tt> const char *etype_category </tt> +<dd> + This is a string of the form + <tt>"</tt><i>class</i><tt>:</tt><i>name</i><tt>"</tt>. + <p> + The <i>class</i> is used to assign the exception type to + one of a number of broad categories of exceptions on which an + exception handler might want to discriminate. + I suspect that what we want is a hierarchical taxonomy, + but I don't have a full design for this yet. + For now, I am recommending the following classes: + <dl> + <dt><tt>"F"</tt> + <dd>A fatal error has occurred. + This is an error that prevents the application + from making any further progress, so the only + recourse is to raise an exception, execute cleanup code + as the stack is unwound, then exit the application. + The out-of-memory exception raised by sm_malloc_x + has category "F:sm.heap" because sendmail commits suicide + (after logging the error and cleaning up) when it runs out + of memory. + + <dt><tt>"E"</tt> + <dd>The function could not complete its task because an error occurred. + (It might be useful to define subclasses of this category, + in which case our taxonony becomes a tree, and 'F' becomes + a subclass of 'E'.) + + <dt><tt>"J"</tt> + <dd>This exception is being raised in order to effect a + non-local jump. No error has occurred; we are just + performing the non-local equivalent of a <tt>continue</tt>, + <tt>break</tt> or <tt>return</tt>. + + <dt><tt>"S"</tt> + <dd>The function was interrupted by a signal. + Signals are not errors because they occur asynchronously, + and they are semantically unrelated to the function that + happens to be executing when the signal arrives. + Note that it is extremely dangerous to raise an exception + from a signal handler. For example, if you are in the middle + of a call to malloc, you might corrupt the heap. + </dl> + Eric's libsm paper defines <tt>"W"</tt>, <tt>"D"</tt> and <tt>"I"</tt> + for Warning, Debug and Informational: + I suspect these categories only make sense in the context of + Eric's 1985 exception handling system which allowed you to + raise conditions without terminating the calling function. + <p> + The <i>name</i> uniquely identifies the exception type. + I recommend a string of the form + <i>library</i><tt>.</tt><i>package</i><tt>.</tt><i>detail</i>. + <p> +<dt> +<tt> const char *etype_argformat </tt> +<dd> + This is an array of single character codes. + Each code indicates the type of one of the exception arguments. + <tt>sm_exc_new_x</tt> uses this string to decode its variable + argument list into an exception argument vector. + The following type codes are supported: + <dl> + <dt><tt>i</tt> + <dd> + The exception argument has type <tt>int</tt>. + <dt><tt>l</tt> + <dd> + The exception argument has type <tt>long</tt>. + <dt><tt>e</tt> + <dd> + The exception argument has type <tt>SM_EXC_T*</tt>. + The value may either be <tt>NULL</tt> or a pointer + to an exception. The pointer value is simply copied + into the exception argument vector. + <dt><tt>s</tt> + <dd> + The exception argument has type <tt>char*</tt>. + The value may either be <tt>NULL</tt> or a pointer + to a character string. In the latter case, + <tt>sm_exc_new_x</tt> will make a copy of the string. + <dt><tt>r</tt> + <dd> + The exception argument has type <tt>char*</tt>. + <tt>sm_exc_new_x</tt> will read a printf-style + format string argument followed by a list of printf + arguments from its variable argument list, and convert + these into a string. + This type code can only occur as the last element + of <tt>exc_argformat</tt>. + </dl> + <p> +<dt> +<tt> void (*etype_print)(SM_EXC_T *exc, SM_FILE_T *stream) </tt> +<dd> + This function prints an exception of the specified type + onto an output stream. + The final character printed is not a newline. +</dl> + +<h2> Standard Exceptions and Exception Types </h2> + +Libsm defines one standard exception value, <tt>SmHeapOutOfMemory</tt>. +This is a statically initialized const variable, because it seems +like a bad idea to dynamically allocate an exception object to +report a low memory condition. +This exception has category <tt>"F:sm.heap"</tt>. +If you need to, you can explicitly raise this exception +with <tt>sm_exc_raise_x(&SmHeapOutOfMemory)</tt>. + +<p> +Statically initialized exception values cannot contain any +run-time parameters, so the normal case is to dynamically allocate +a new exception object whenever you raise an exception. +Before you can create an exception, you need an exception type. +Libsm defines the following standard exception types. + +<dl> +<dt> +<tt> SmEtypeOs </tt> +<dd> + This represents a generic operating system error. + The category is <tt>"E:sm.os"</tt>. + The argformat is <tt>"isr"</tt>, + where argv[0] is the value of <tt>errno</tt> + after a system call has failed, + argv[1] is the name of the function (usually a system call) that failed, + and argv[2] is either <tt>NULL</tt> + or a character string which describes some of the arguments + to the failing system call (usually it is just a file name). + Here's an example of raising an exception: + +<blockquote><pre> +fd = open(filename, O_RDONLY); +if (fd == -1) + sm_exc_raisenew_x(&SmEtypeOs, errno, "open", "%s", filename); +</pre></blockquote> + + If errno is ENOENT and filename is "/etc/mail/snedmail.cf", + then the exception raised by the above code will be printed as + +<blockquote><pre> +/etc/mail/snedmail.cf: open failed: No such file or directory +</pre></blockquote> + +<dt> +<tt> SmEtypeErr </tt> +<dd> + This represents a generic error. + The category is <tt>"E:sm.err"</tt>, + and the argformat is <tt>"r"</tt>. + You can use it + in application contexts where you are raising an exception + for the purpose of terminating the program. + You know the exception won't be handled, + so you don't need to worry about packaging the error for + later analysis by an exception handler. + All you need to specify is the message string that + will be printed to stderr before the program exits. + For example, + +<blockquote><pre> +sm_exc_raisenew_x(&SmEtypeErr, "name lookup failed: %s", name); +</pre></blockquote> +</dl> + +<h2> Custom Exception Types </h2> + +If you are writing a library package, and you need to raise +exceptions that are not standard Unix system errors, +then you need to define one or more new exception types. + +<p> +Every new exception type needs a print function. +The standard print function <tt>sm_etype_printf</tt> +is all you need in the majority of cases. +It prints the <tt>etype_printcontext</tt> string of the exception type, +substituting occurrences of %0 through %9 with the corresponding +exception argument. +If exception argument 3 is an int or long, +then %3 will print the argument in decimal, +and %o3 or %x3 will print it in octal or hex. + +<p> +In the following example, I will assume that your library +package implements regular expressions, and can raise 5 different exceptions. +When compiling a regular expression, 3 different syntax errors +can be reported: +<ul> +<li>unbalanced parenthesis +<li>unbalanced bracket +<li>missing argument for repetition operator +</ul> +Whenever one of these errors is reported, you will also report +the index of the character within the regex string at which the +syntax error was detected. +The fourth exception is raised if a compiled regular expression +is invalid: this exception has no arguments. +The fifth exception is raised if the package runs out of memory: +for this, you use the standard <tt>SmHeapOutOfMemory</tt> exception. + +<p> +The obvious approach is to define 4 separate exception types. +Here they are: + +<blockquote><pre> +/* print a regular expression syntax error */ +void +rx_esyntax_print(SM_EXC_T *exc, SM_FILE_T *stream) +{ + sm_io_fprintf(stream, "rx syntax error at character %d: %s", + exc->exc_argv[0].v_int, + exc->exc_type->etype_printcontext); +} +SM_EXC_TYPE_T RxSyntaxParen = { + SmExcTypeMagic, + "E:mylib.rx.syntax.paren", + "i", + rx_esyntax_print, + "unbalanced parenthesis" +}; +SM_EXC_TYPE_T RxSyntaxBracket = { + SmExcTypeMagic, + "E:mylib.rx.syntax.bracket", + "i", + rx_esyntax_print, + "unbalanced bracket" +}; +SM_EXC_TYPE_T RxSyntaxMissingArg = { + SmExcTypeMagic, + "E:mylib.rx.syntax.missingarg", + "i", + rx_esyntax_print, + "missing argument for repetition operator" +}; + +SM_EXC_TYPE_T RxRunCorrupt = { + SmExcTypeMagic, + "E:mylib.rx.run.corrupt", + "", + sm_etype_printf, + "rx runtime error: compiled regular expression is corrupt" +}; +</pre></blockquote> + +<p> +With the above definitions, you can raise a syntax error reporting +an unbalanced parenthesis at string offset <tt>i</tt> using: +<blockquote><pre> +sm_exc_raisenew_x(&RxSyntaxParen, i); +</pre></blockquote> + +If <tt>i==42</tt> then this exception will be printed as: +<blockquote><pre> +rx syntax error at character 42: unbalanced parenthesis +</pre></blockquote> + +An exception handler can provide special handling for regular +expression syntax errors using this code: +<blockquote><pre> +SM_TRY + ... code that might raise an exception ... +SM_EXCEPT(exc, "E:mylib.rx.syntax.*") + int i = exc->exc_argv[0].v_int; + ... handle a regular expression syntax error ... +SM_END_TRY +</pre></blockquote> + +<p> +External requirements may force you to define an integer code +for each error reported by your package. Or you may be wrapping +an existing package that works this way. In this case, it might +make sense to define a single exception type, patterned after SmEtypeOs, +and include the integer code as an exception argument. + +<p> +Your package might intercept an exception E generated by a lower +level package, and then reclassify it as a different expression E'. +For example, a package for reading a configuration file might +reclassify one of the regular expression syntax errors from the +previous example as a configuration file syntax error. +When you do this, the new exception E' should include the original +exception E as an exception parameter, and the print function for +exception E' should print the high level description of the exception +(eg, "syntax error in configuration file %s at line %d\n"), +then print the subexception that is stored as an exception parameter. + +<h2> Function Reference </h2> + +<dl> +<dt> +<tt> SM_EXC_T *sm_exc_new_x(const SM_EXC_TYPE_T *type, ...) </tt> +<dd> + Create a new exception. Raise an exception on heap exhaustion. + The new exception has a reference count of 1. + <p> + + A list of zero or more exception arguments follows the exception type; + these are copied into the new exception object. + The number and types of these arguments is determined + by <tt>type->etype_argformat</tt>. + <p> + + Note that there is no rpool argument to sm_exc_new_x. + Exceptions are allocated directly from the heap. + This is because exceptions are normally raised at low levels + of abstraction and handled at high levels. Because the low + level code typically has no idea of how or at what level the + exception will be handled, it also has no idea of which resource + pool, if any, should own the exception. + <p> +<dt> +<tt> SM_EXC_T *sm_exc_addref(SM_EXC_T *exc) </tt> +<dd> + Increment the reference count of an exception. + Return the first argument. + <p> +<dt> +<tt> void sm_exc_free(SM_EXC_T *exc) </tt> +<dd> + Decrement the reference count of an exception. + If it reaches 0, free the exception object. + <p> +<dt> +<tt> bool sm_exc_match(SM_EXC_T *exc, const char *pattern) </tt> +<dd> + Compare the exception's category to the specified glob pattern, + return true if they match. + <p> +<dt> +<tt> void sm_exc_print(SM_EXC_T *exc, SM_FILE_T *stream) </tt> +<dd> + Print the exception on the stream + as a sequence of one or more newline terminated lines. + <p> +<dt> +<tt> void sm_exc_write(SM_EXC_T *exc, SM_FILE_T *stream) </tt> +<dd> + Write the exception on the stream without a terminating newline. + <p> +<dt> +<tt> void sm_exc_raise_x(SM_EXC_T *exc) </tt> +<dd> + Raise the exception. This function does not return to its caller. + <p> +<dt> +<tt> void sm_exc_raisenew_x(const SM_EXC_TYPE_T *type, ...) </tt> +<dd> + A short form for <tt>sm_exc_raise_x(sm_exc_new_x(type,...))</tt>. +</dl> + +<h2> Macro Reference </h2> + +The SM_TRY ... SM_END_TRY control structure +ensures that cleanup code is executed in the presence of exceptions, +and permits exceptions to be handled. + +<blockquote><pre> +SM_TRY + A block of code that may raise an exception. +SM_FINALLY + Cleanup code that may raise an exception. + This code is guaranteed to be executed whether or not + an exception was raised by a previous clause. + You may have 0 or more SM_FINALLY clauses. +SM_EXCEPT(e, pat) + Exception handling code, which is triggered by an exception + whose category matches the glob pattern 'pat'. + The exception value is bound to the local variable 'e'. + You may have 0 or more SM_EXCEPT clauses. +SM_END_TRY +</pre></blockquote> + +First, the SM_TRY clause is executed, then each SM_FINALLY clause is +executed in sequence. +If one or more of these clauses was terminated by an exception, +then the first such exception is remembered, and the other exceptions +are lost. + +If no exception was raised, then we are done. + +Otherwise, each of the SM_EXCEPT clauses is examined in sequence. +and the first SM_EXCEPT clause whose pattern argument matches the exception +(see <tt>sm_exc_match</tt>) is executed. +If none of the SM_EXCEPT clauses matched the exception, or if there are +no SM_EXCEPT clauses, then the remembered exception is re-raised. + +<p> +SM_TRY .. SM_END_TRY clauses may be nested arbitrarily. + +<p> +It is illegal to jump out of a SM_TRY or SM_FINALLY clause +using goto, break, continue, return or longjmp. +If you do this, you will corrupt the internal exception handling stack. +You can't use <tt>break</tt> or <tt>continue</tt> in an SM_EXCEPT clause; +these are reserved for use by the implementation. +It is legal to jump out of an SM_EXCEPT clause using goto or return; +however, in this case, you must take responsibility +for freeing the exception object. + +<p> +The SM_TRY and SM_FINALLY macros contain calls to setjmp, +and consequently, they suffer from the limitations imposed on setjmp +by the C standard. +Suppose you declare an auto variable <tt>i</tt> outside of a +SM_TRY ... SM_END_TRY statement, initializing it to 0. +Then you modify <tt>i</tt> inside of a SM_TRY or SM_FINALLY clause, +setting it to 1. +If you reference <tt>i</tt> in a different SM_FINALLY clause, or in +an SM_EXCEPT clause, then it is implementation dependent whether <tt>i</tt> +will be 0 or 1, unless you have declared <tt>i</tt> to be <tt>volatile</tt>. + +<blockquote><pre> +int volatile i = 0; + +SM_TRY + i = 1; + ... +SM_FINALLY + /* the following reference to i only works if i is declared volatile */ + use(i); + ... +SM_EXCEPT(exc, "*") + /* the following reference to i only works if i is declared volatile */ + use(i); + ... +SM_END_TRY +</pre></blockquote> + +</body> +</html> |