error info handling in MIT krb5 code
raeburn at MIT.EDU
Thu Mar 16 16:17:50 EST 2006
So, I'm getting around to adding to our error-handling support as was
discussed on one of the mailing lists a long time ago. I took a look
at what Heimdal is doing, and I like it.
These are the Heimdal interfaces in 0.7.2:
krb5_error_code krb5_set_error_string(krb5_context, const char *, ...)
krb5_error_code krb5_vset_error_string(krb5_context, const char *,
Calls vasprintf to allocate and format the message, or if that
fails, uses vsnprintf and the preallocated per-context error_buf.
Stores the pointer to the formatted message into error_string.
Returns the error_string field value, after clearing it, so only
one call can be made after any error.
Indicates whether error_string is non-null.
void krb5_free_error_string(krb5_context, char *)
Frees the supplied string if it's not the error_buf of the
Sets the context's error_string to NULL. If it was non-null and
didn't match the error_buf, free it, too.
New in snapshots:
char *krb5_get_error_message(krb5_context, krb5_error_code)
Returns the error_string if any; otherwise, tries the error
message for the supplied error code; if none, formats an "unknown
error" message. Always returns NULL or heap storage that the
caller should eventually free.
Doug's suggestion: Also store the error code, and if asking for a
message associated with a *different* error code, throw away the
stored data. Also look at OpenSSL ERR_* routines.
I took a look at OpenSSL. They store file and line number where the
error is raised; that's possibly interesting for debugging. They
maintain what sounds like a FIFO queue of errors, and have a routine
which prints out all the errors in the queue. I'm not convinced
that's better than having each site where an error code is triggered
by an earlier error do something like:
krb5_set_error(ctx, new_code, "Can't open credentials cache %s: %s",
cachename, krb5_get_error(ctx, underlying_error_code))
(well, aside from memory management issues)
Without something like this, I think you're just stuck printing a
series of messages in a particular order with some chosen separator
string like ":\n" in between. I'm certainly willing to be convinced.
Either way, we need an approach that's amenable to
So, I'd propose we do something like this:
void krb5_set_error(krb5_context, krb5_error_code, const char *, ...)
void krb5_vset_error(krb5_context, krb5_error_code, const char *,
Formats a message in newly allocated storage and saves it in the
context, along with the error code associated with it. If
allocation fails, uses pre-allocated storage associated with (in?)
If we go with *printf formatting, the implementation is pretty
easy on most platforms. If we want to get fancy instead, we could
do our own formatting, and add a new format code which takes a
krb5_error_code argument, uses it to fetch the previous error
message from the current context, and substitutes the resulting
string, freeing it up after use. That would side-step the little
memory management issue I glossed over above. It would take a bit
longer, but it may be more convenient, and I've written such code
before; there are one or two instances in the krb5 code already.
char *krb5_get_error(krb5_context, krb5_error_code)
If the supplied error code matches the saved one, returns the most
recently stored message, and clears the pointer in the context
(thus leaving the caller with the only reference to the allocated
If the two error codes don't match, returns a copy of the error
string associated with the supplied error code (again, leaving the
caller with the only reference *or* a reference to the no-memory
scratch buffer), and clears out any saved value.
Cannot return NULL.
Note that in the case of two out-of-memory errors, if the handle
to the no-memory buffer has been retrieved twice and never
"freed", the first-retrieved error message may change. Thus, the
caller should retrieve and use one error message before retrieving
This is similar to Heimdal's krb5_get_error_message, but compares
the supplied error code to the saved one, rather than only using
it as a fallback.
void krb5_free_error(krb5_context, char *)
Frees up the storage if it's not the special no-memory buffer
associated with the context.
Frees up the message directly.
Some are equivalent to the Heimdal ones with different names, but I
wanted consistent names, and I didn't necessarily want to use the
Heimdal name when I'm changing the interfaces slightly.
All these function names should be in the krb5 library, though we
might stick helper functions in the support library. Note that the
crypto library may need to store error info, but need not use the
Q: Custom or *printf formatting?
Q: Should there be a get_error version that doesn't require having the
correct numeric error code? Or maybe a magic value (maybe 0 or -1)
which matches any saved code?
Q: How about a "peek" function that doesn't reset the state?
Q: Should we save file name and line number? Function name? The
above interfaces could be macros that wrap calls to functions taking
the extra arguments. They might help in debugging, but currently
there's no way to retrieve the information. (Perhaps an environment
variable could cause krb5_set_error to include them in the generated
string. In a locale-dependent form, of course.)
More information about the krbdev