This changes the "ERR" code to have all access to state (a hash table of
authorGeoff Thorpe <geoff@openssl.org>
Sat, 25 Aug 2001 17:51:59 +0000 (17:51 +0000)
committerGeoff Thorpe <geoff@openssl.org>
Sat, 25 Aug 2001 17:51:59 +0000 (17:51 +0000)
error strings and a hash table storing per-thread error state) go via an
ERR_FNS function table. The first time an ERR operation occurs, the
implementation that will be used (from then on) is set to the internal
"defaults" implementation if it has not already been set. The actual LHASH
tables are only accessed by this implementation.

This is primarily for modules that can be loaded at run-time and bound into
an application (or a shared-library version of OpenSSL). If the module has
its own statically-linked copy of OpenSSL code - this mechanism allows it
to *not* create and use ERR information in its own linked "ERR" code, but
instead to use and interact with the state stored in the loader
(application or shared library). The loader calls ERR_get_implementation()
and the return value is what the module should use when calling its own
copy of ERR_set_implementation().

crypto/err/err.c
crypto/err/err.h

index b4183a7..0152d23 100644 (file)
 #include <openssl/bio.h>
 #include <openssl/err.h>
 
-
-static LHASH *error_hash=NULL;
-static LHASH *thread_hash=NULL;
-
-/* static unsigned long err_hash(ERR_STRING_DATA *a); */
-static unsigned long err_hash(const void *a_void);
-/* static int err_cmp(ERR_STRING_DATA *a, ERR_STRING_DATA *b); */
-static int err_cmp(const void *a_void, const void *b_void);
-/* static unsigned long pid_hash(ERR_STATE *pid); */
-static unsigned long pid_hash(const void *pid_void);
-/* static int pid_cmp(ERR_STATE *a,ERR_STATE *pid); */
-static int pid_cmp(const void *a_void,const void *pid_void);
-static unsigned long get_error_values(int inc,const char **file,int *line,
-                                     const char **data,int *flags);
-
 static void ERR_STATE_free(ERR_STATE *s);
 #ifndef OPENSSL_NO_ERR
 static ERR_STRING_DATA ERR_str_libraries[]=
@@ -225,6 +210,235 @@ static ERR_STRING_DATA ERR_str_reasons[]=
        };
 
 
+/* Define the predeclared (but externally opaque) "ERR_FNS" type */
+struct st_ERR_FNS
+       {
+       /* Works on the "error_hash" string table */
+       LHASH *(*cb_err_get)(void);
+       void (*cb_err_del)(void);
+       ERR_STRING_DATA *(*cb_err_get_item)(const ERR_STRING_DATA *);
+       ERR_STRING_DATA *(*cb_err_set_item)(ERR_STRING_DATA *);
+       /* Works on the "thread_hash" error-state table */
+       LHASH *(*cb_thread_get)(void);
+       ERR_STATE *(*cb_thread_get_item)(const ERR_STATE *);
+       ERR_STATE *(*cb_thread_set_item)(ERR_STATE *);
+       void (*cb_thread_del_item)(const ERR_STATE *);
+       /* Returns the next available error "library" numbers */
+       int (*cb_get_next_lib)(void);
+       };
+
+/* Predeclarations of the "err_defaults" functions */
+static LHASH *int_err_get(void);
+static void int_err_del(void);
+static ERR_STRING_DATA *int_err_get_item(const ERR_STRING_DATA *);
+static ERR_STRING_DATA *int_err_set_item(ERR_STRING_DATA *);
+static LHASH *int_thread_get(void);
+static ERR_STATE *int_thread_get_item(const ERR_STATE *);
+static ERR_STATE *int_thread_set_item(ERR_STATE *);
+static void int_thread_del_item(const ERR_STATE *);
+static int int_err_get_next_lib(void);
+/* The static ERR_FNS table using these defaults functions */
+static const ERR_FNS err_defaults =
+       {
+       int_err_get,
+       int_err_del,
+       int_err_get_item,
+       int_err_set_item,
+       int_thread_get,
+       int_thread_get_item,
+       int_thread_set_item,
+       int_thread_del_item,
+       int_err_get_next_lib
+       };
+
+/* The replacable table of ERR_FNS functions we use at run-time */
+static const ERR_FNS *err_fns = NULL;
+
+/* Eg. rather than using "err_get()", use "ERRFN(err_get)()". */
+#define ERRFN(a) err_fns->cb_##a
+
+/* The internal state used by "err_defaults" - as such, the setting, reading,
+ * creating, and deleting of this data should only be permitted via the
+ * "err_defaults" functions. This way, a linked module can completely defer all
+ * ERR state operation (together with requisite locking) to the implementations
+ * and state in the loading application. */
+static LHASH *int_error_hash;
+static int int_error_hash_set = 0;
+static LHASH *int_thread_hash;
+static int int_thread_hash_set = 0;
+static int int_err_library_number=ERR_LIB_USER;
+
+/* Internal function that checks whether "err_fns" is set and if not, sets it to
+ * the defaults. */
+static void err_fns_check(void)
+       {
+       CRYPTO_w_lock(CRYPTO_LOCK_ERR);
+       if(!err_fns)
+               err_fns = &err_defaults;
+       CRYPTO_w_unlock(CRYPTO_LOCK_ERR);
+       }
+
+/* API functions to get or set the underlying ERR functions. */
+
+const ERR_FNS *ERR_get_implementation(void)
+       {
+       err_fns_check();
+       return err_fns;
+       }
+
+int ERR_set_implementation(const ERR_FNS *fns)
+       {
+       int toret = 0;
+       CRYPTO_w_lock(CRYPTO_LOCK_ERR);
+       /* It's too late if 'err_fns' is non-NULL. BTW: not much point setting
+        * an error is there?! */
+       if(!err_fns)
+               {
+               err_fns = fns;
+               toret = 1;
+               }
+       CRYPTO_w_unlock(CRYPTO_LOCK_ERR);
+       return toret;
+       }
+
+/* These are the callbacks provided to "lh_new()" when creating the LHASH tables
+ * internal to the "err_defaults" implementation. */
+
+/* static unsigned long err_hash(ERR_STRING_DATA *a); */
+static unsigned long err_hash(const void *a_void);
+/* static int err_cmp(ERR_STRING_DATA *a, ERR_STRING_DATA *b); */
+static int err_cmp(const void *a_void, const void *b_void);
+/* static unsigned long pid_hash(ERR_STATE *pid); */
+static unsigned long pid_hash(const void *pid_void);
+/* static int pid_cmp(ERR_STATE *a,ERR_STATE *pid); */
+static int pid_cmp(const void *a_void,const void *pid_void);
+static unsigned long get_error_values(int inc,const char **file,int *line,
+                                     const char **data,int *flags);
+
+/* The internal functions used in the "err_defaults" implementation */
+
+static LHASH *int_err_get(void)
+       {
+       LHASH *toret = NULL;
+       CRYPTO_w_lock(CRYPTO_LOCK_ERR);
+       if(!int_error_hash_set)
+               int_error_hash = lh_new(err_hash, err_cmp);
+       if(int_error_hash)
+               {
+               int_error_hash_set = 1;
+               toret = int_error_hash;
+               }
+       CRYPTO_w_unlock(CRYPTO_LOCK_ERR);
+       return(toret);
+       }
+static void int_err_del(void)
+       {
+       CRYPTO_w_lock(CRYPTO_LOCK_ERR);
+       if(int_error_hash_set)
+               {
+               lh_free(int_error_hash);
+               int_error_hash = NULL;
+               int_error_hash_set = 0;
+               }
+       CRYPTO_w_unlock(CRYPTO_LOCK_ERR);
+       }
+static ERR_STRING_DATA *int_err_get_item(const ERR_STRING_DATA *d)
+       {
+       ERR_STRING_DATA *p;
+       LHASH *hash;
+       err_fns_check();
+       hash = ERRFN(err_get)();
+       if(!hash)
+               return NULL;
+       CRYPTO_r_lock(CRYPTO_LOCK_ERR);
+       p = (ERR_STRING_DATA *)lh_retrieve(hash, d);
+       CRYPTO_r_unlock(CRYPTO_LOCK_ERR);
+       return p;
+       }
+static ERR_STRING_DATA *int_err_set_item(ERR_STRING_DATA *d)
+       {
+       ERR_STRING_DATA *p;
+       LHASH *hash;
+       err_fns_check();
+       hash = ERRFN(err_get)();
+       if(!hash)
+               return NULL;
+       CRYPTO_r_lock(CRYPTO_LOCK_ERR);
+       p = (ERR_STRING_DATA *)lh_insert(hash, d);
+       CRYPTO_r_unlock(CRYPTO_LOCK_ERR);
+       return p;
+       }
+static LHASH *int_thread_get(void)
+       {
+       LHASH *toret = NULL;
+       CRYPTO_w_lock(CRYPTO_LOCK_ERR);
+       if(!int_thread_hash_set)
+               int_thread_hash = lh_new(pid_hash, pid_cmp);
+       if(int_thread_hash)
+               {
+               int_thread_hash_set = 1;
+               toret = int_thread_hash;
+               }
+       CRYPTO_w_unlock(CRYPTO_LOCK_ERR);
+       return(toret);
+       }
+static ERR_STATE *int_thread_get_item(const ERR_STATE *d)
+       {
+       ERR_STATE *p;
+       LHASH *hash;
+       err_fns_check();
+       hash = ERRFN(thread_get)();
+       if(!hash)
+               return NULL;
+       CRYPTO_r_lock(CRYPTO_LOCK_ERR);
+       p = (ERR_STATE *)lh_retrieve(hash, d);
+       CRYPTO_r_unlock(CRYPTO_LOCK_ERR);
+       return p;
+       }
+static ERR_STATE *int_thread_set_item(ERR_STATE *d)
+       {
+       ERR_STATE *p;
+       LHASH *hash;
+       err_fns_check();
+       hash = ERRFN(thread_get)();
+       if(!hash)
+               return NULL;
+       CRYPTO_r_lock(CRYPTO_LOCK_ERR);
+       p = (ERR_STATE *)lh_insert(hash, d);
+       CRYPTO_r_unlock(CRYPTO_LOCK_ERR);
+       return p;
+       }
+static void int_thread_del_item(const ERR_STATE *d)
+       {
+       ERR_STATE *p;
+       LHASH *hash;
+       err_fns_check();
+       hash = ERRFN(thread_get)();
+       if(!hash)
+               return;
+       CRYPTO_w_lock(CRYPTO_LOCK_ERR);
+       p = (ERR_STATE *)lh_delete(hash, d);
+       /* make sure we don't leak memory */
+       if(int_thread_hash_set && (lh_num_items(int_thread_hash) == 0))
+               {
+               lh_free(int_thread_hash);
+               int_thread_hash = NULL;
+               int_thread_hash_set = 0;
+               }
+       CRYPTO_w_unlock(CRYPTO_LOCK_ERR);
+       if(p)
+               ERR_STATE_free(p);
+       }
+static int int_err_get_next_lib(void)
+       {
+       int toret;
+       CRYPTO_w_lock(CRYPTO_LOCK_ERR);
+       toret = int_err_library_number++;
+       CRYPTO_w_unlock(CRYPTO_LOCK_ERR);
+       return toret;
+       }
+
+
 #define NUM_SYS_STR_REASONS 127
 #define LEN_SYS_STR_REASON 32
 
@@ -302,6 +516,11 @@ void ERR_load_ERR_strings(void)
 
        if (init)
                {
+               init=0;
+               /* We put the first-time check code here to reduce the number of
+                * times it is called (then it doesn't get called from
+                * ERR_load_strings() loads of times). */
+               err_fns_check();
 #ifndef OPENSSL_NO_ERR
                ERR_load_strings(0,ERR_str_libraries);
                ERR_load_strings(0,ERR_str_reasons);
@@ -309,47 +528,27 @@ void ERR_load_ERR_strings(void)
                build_SYS_str_reasons();
                ERR_load_strings(ERR_LIB_SYS,SYS_str_reasons);
 #endif
-               init=0;
                }
        }
 
 void ERR_load_strings(int lib, ERR_STRING_DATA *str)
        {
-       if (error_hash == NULL)
-               {
-               CRYPTO_w_lock(CRYPTO_LOCK_ERR_HASH);
-               error_hash=lh_new(err_hash, err_cmp);
-               if (error_hash == NULL)
-                       {
-                       CRYPTO_w_unlock(CRYPTO_LOCK_ERR_HASH);
-                       return;
-                       }
-               CRYPTO_w_unlock(CRYPTO_LOCK_ERR_HASH);
-
-               ERR_load_ERR_strings();
-               }
-
-       CRYPTO_w_lock(CRYPTO_LOCK_ERR_HASH);
+       /* Do this if it hasn't been done already (NB: The order of the "init=0"
+        * statement and the recursive calls back to this function prevent a
+        * loop). */
+       ERR_load_ERR_strings();
        while (str->error)
                {
                str->error|=ERR_PACK(lib,0,0);
-               lh_insert(error_hash,str);
+               ERRFN(err_set_item)(str);
                str++;
                }
-       CRYPTO_w_unlock(CRYPTO_LOCK_ERR_HASH);
        }
 
 void ERR_free_strings(void)
        {
-       CRYPTO_w_lock(CRYPTO_LOCK_ERR);
-
-       if (error_hash != NULL)
-               {
-               lh_free(error_hash);
-               error_hash=NULL;
-               }
-
-       CRYPTO_w_unlock(CRYPTO_LOCK_ERR);
+       err_fns_check();
+       ERRFN(err_del)();
        }
 
 /********************************************************/
@@ -551,53 +750,38 @@ char *ERR_error_string(unsigned long e, char *ret)
 
 LHASH *ERR_get_string_table(void)
        {
-       return(error_hash);
+       err_fns_check();
+       return ERRFN(err_get)();
        }
 
-/* not thread-safe */
 LHASH *ERR_get_err_state_table(void)
        {
-       return(thread_hash);
+       err_fns_check();
+       return ERRFN(thread_get)();
        }
 
 const char *ERR_lib_error_string(unsigned long e)
        {
-       ERR_STRING_DATA d,*p=NULL;
+       ERR_STRING_DATA d,*p;
        unsigned long l;
 
+       err_fns_check();
        l=ERR_GET_LIB(e);
-
-       CRYPTO_r_lock(CRYPTO_LOCK_ERR_HASH);
-
-       if (error_hash != NULL)
-               {
-               d.error=ERR_PACK(l,0,0);
-               p=(ERR_STRING_DATA *)lh_retrieve(error_hash,&d);
-               }
-
-       CRYPTO_r_unlock(CRYPTO_LOCK_ERR_HASH);
-
+       d.error=ERR_PACK(l,0,0);
+       p=ERRFN(err_get_item)(&d);
        return((p == NULL)?NULL:p->string);
        }
 
 const char *ERR_func_error_string(unsigned long e)
        {
-       ERR_STRING_DATA d,*p=NULL;
+       ERR_STRING_DATA d,*p;
        unsigned long l,f;
 
+       err_fns_check();
        l=ERR_GET_LIB(e);
        f=ERR_GET_FUNC(e);
-
-       CRYPTO_r_lock(CRYPTO_LOCK_ERR_HASH);
-
-       if (error_hash != NULL)
-               {
-               d.error=ERR_PACK(l,f,0);
-               p=(ERR_STRING_DATA *)lh_retrieve(error_hash,&d);
-               }
-
-       CRYPTO_r_unlock(CRYPTO_LOCK_ERR_HASH);
-
+       d.error=ERR_PACK(l,f,0);
+       p=ERRFN(err_get_item)(&d);
        return((p == NULL)?NULL:p->string);
        }
 
@@ -606,24 +790,16 @@ const char *ERR_reason_error_string(unsigned long e)
        ERR_STRING_DATA d,*p=NULL;
        unsigned long l,r;
 
+       err_fns_check();
        l=ERR_GET_LIB(e);
        r=ERR_GET_REASON(e);
-
-       CRYPTO_r_lock(CRYPTO_LOCK_ERR_HASH);
-
-       if (error_hash != NULL)
+       d.error=ERR_PACK(l,0,r);
+       p=ERRFN(err_get_item)(&d);
+       if(!p)
                {
-               d.error=ERR_PACK(l,0,r);
-               p=(ERR_STRING_DATA *)lh_retrieve(error_hash,&d);
-               if (p == NULL)
-                       {
-                       d.error=ERR_PACK(0,0,r);
-                       p=(ERR_STRING_DATA *)lh_retrieve(error_hash,&d);
-                       }
+               d.error=ERR_PACK(0,0,r);
+               p=ERRFN(err_get_item)(&d);
                }
-
-       CRYPTO_r_unlock(CRYPTO_LOCK_ERR_HASH);
-
        return((p == NULL)?NULL:p->string);
        }
 
@@ -659,46 +835,26 @@ static int pid_cmp(const void *a_void, const void *b_void)
 
 void ERR_remove_state(unsigned long pid)
        {
-       ERR_STATE *p = NULL,tmp;
-
-       if (thread_hash == NULL)
-               return;
+       ERR_STATE tmp;
+       err_fns_check();
        if (pid == 0)
                pid=(unsigned long)CRYPTO_thread_id();
        tmp.pid=pid;
-       CRYPTO_w_lock(CRYPTO_LOCK_ERR);
-       if (thread_hash)
-               {
-               p=(ERR_STATE *)lh_delete(thread_hash,&tmp);
-               if (lh_num_items(thread_hash) == 0)
-                       {
-                       /* make sure we don't leak memory */
-                       lh_free(thread_hash);
-                       thread_hash = NULL;
-                       }
-               }
-       CRYPTO_w_unlock(CRYPTO_LOCK_ERR);
-
-       if (p != NULL) ERR_STATE_free(p);
+       /* thread_del_item automatically destroys the LHASH if the number of
+        * items reaches zero. */
+       ERRFN(thread_del_item)(&tmp);
        }
 
 ERR_STATE *ERR_get_state(void)
        {
        static ERR_STATE fallback;
-       ERR_STATE *ret=NULL,tmp,*tmpp=NULL;
-       int thread_state_exists;
+       ERR_STATE *ret,tmp,*tmpp=NULL;
        int i;
        unsigned long pid;
-
+       err_fns_check();
        pid=(unsigned long)CRYPTO_thread_id();
-
-       CRYPTO_r_lock(CRYPTO_LOCK_ERR);
-       if (thread_hash != NULL)
-               {
-               tmp.pid=pid;
-               ret=(ERR_STATE *)lh_retrieve(thread_hash,&tmp);
-               }
-       CRYPTO_r_unlock(CRYPTO_LOCK_ERR);
+       tmp.pid=pid;
+       ret=ERRFN(thread_get_item)(&tmp);
 
        /* ret == the error state, if NULL, make a new one */
        if (ret == NULL)
@@ -713,42 +869,25 @@ ERR_STATE *ERR_get_state(void)
                        ret->err_data[i]=NULL;
                        ret->err_data_flags[i]=0;
                        }
-
-               CRYPTO_w_lock(CRYPTO_LOCK_ERR);
-
-               /* no entry yet in thread_hash for current thread -
-                * thus, it may have changed since we last looked at it */
-               if (thread_hash == NULL)
-                       thread_hash = lh_new(pid_hash, pid_cmp);
-               if (thread_hash == NULL)
-                       thread_state_exists = 0; /* allocation error */
-               else
-                       {
-                       tmpp=(ERR_STATE *)lh_insert(thread_hash,ret);
-                       thread_state_exists = 1;
-                       }
-
-               CRYPTO_w_unlock(CRYPTO_LOCK_ERR);
-
-               if (!thread_state_exists)
+               tmpp = ERRFN(thread_set_item)(ret);
+               /* To check if insertion failed, do a get. */
+               if(ERRFN(thread_get_item)(ret) != ret)
                        {
                        ERR_STATE_free(ret); /* could not insert it */
                        return(&fallback);
                        }
-               
-               if (tmpp != NULL) /* old entry - should not happen */
-                       {
+               /* If a race occured in this function and we came second, tmpp
+                * is the first one that we just replaced. */
+               if(tmpp)
                        ERR_STATE_free(tmpp);
-                       }
                }
        return(ret);
        }
 
 int ERR_get_next_error_library(void)
        {
-       static int value=ERR_LIB_USER;
-
-       return(value++);
+       err_fns_check();
+       return ERRFN(get_next_lib)();
        }
 
 void ERR_set_error_data(char *data, int flags)
index 0fb8e02..70a3566 100644 (file)
@@ -274,6 +274,15 @@ LHASH *ERR_get_err_state_table(void); /* even less thread-safe than
 
 int ERR_get_next_error_library(void);
 
+/* This opaque type encapsulates the low-level error-state functions */
+typedef struct st_ERR_FNS ERR_FNS;
+/* An application can use this function and provide the return value to loaded
+ * modules that should use the application's ERR state/functionality */
+const ERR_FNS *ERR_get_implementation(void);
+/* A loaded module should call this function prior to any ERR operations using
+ * the application's "ERR_FNS". */
+int ERR_set_implementation(const ERR_FNS *fns);
+
 #ifdef __cplusplus
 }
 #endif