/* EINA - EFL data type library * Copyright (C) 2007-2008 Jorge Luis Zapata Muga, Cedric Bail * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; * if not, see . */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include "eina_config.h" #include "eina_private.h" /* undefs EINA_ARG_NONULL() so NULL checks are not compiled out! */ #include "eina_safety_checks.h" #include "eina_error.h" #include "eina_stringshare.h" #include "eina_lock.h" #include "eina_str.h" #ifdef EINA_HAVE_THREADS #include "eina_hash.h" #endif /* TODO * + add a wrapper for assert? * + add common error numbers, messages * + add a calltrace of errors, not only store the last error but a list of them * and also store the function that set it */ /*============================================================================* * Local * *============================================================================*/ /** * @cond LOCAL */ typedef struct _Eina_Error_Message Eina_Error_Message; struct _Eina_Error_Message { Eina_Bool string_allocated; const char *string; }; #ifdef EINA_HAVE_THREADS static Eina_Spinlock _eina_errno_msgs_lock; static Eina_Hash *_eina_errno_msgs = NULL; #endif static Eina_Error_Message *_eina_errors = NULL; static size_t _eina_errors_count = 0; static size_t _eina_errors_allocated = 0; /* used to differentiate registered errors from errno.h */ #define EINA_ERROR_REGISTERED_BIT (1 << 30) #define EINA_ERROR_REGISTERED_CHECK(err) ((err) & EINA_ERROR_REGISTERED_BIT) #define EINA_ERROR_FROM_INDEX(idx) ((idx) | EINA_ERROR_REGISTERED_BIT) #define EINA_ERROR_TO_INDEX(err) ((err) & (~EINA_ERROR_REGISTERED_BIT)) static Eina_Error _eina_last_error; static Eina_TLS _eina_last_key; static Eina_Error_Message * _eina_error_msg_alloc(void) { size_t idx; if (_eina_errors_count == _eina_errors_allocated) { void *tmp; size_t size; if (EINA_UNLIKELY(_eina_errors_allocated == 0)) size = 24; else size = _eina_errors_allocated + 8; tmp = realloc(_eina_errors, sizeof(Eina_Error_Message) * size); if (!tmp) return NULL; _eina_errors = tmp; _eina_errors_allocated = size; } idx = _eina_errors_count; _eina_errors_count++; return _eina_errors + idx; } #ifdef _WIN32 # define HAVE_STRERROR_R # ifdef STRERROR_R_CHAR_P # undef STRERROR_R_CHAR_P # endif /* Windows has strerror_s(), similar to POSIX strerror_r() */ static inline int strerror_r(int errnum, char *buf, size_t buflen) { int ret; ret = strerror_s(buf, buflen, errnum); if (strcmp(buf, "Unknown error") == 0) snprintf(buf, buflen, "Unknown error %d", errnum); return ret; } #endif /** * @endcond */ /*============================================================================* * Global * *============================================================================*/ /** * @cond LOCAL */ EAPI Eina_Error EINA_ERROR_OUT_OF_MEMORY = ENOMEM; /** * @endcond */ /** * @internal * @brief Initialize the error module. * * @return #EINA_TRUE on success, #EINA_FALSE on failure. * * This function sets up the error module of Eina. It is called by * eina_init(). * * This function registers the error #EINA_ERROR_OUT_OF_MEMORY. * * @see eina_init() */ Eina_Bool eina_error_init(void) { if (!eina_tls_new(&_eina_last_key)) return EINA_FALSE; #ifdef EINA_HAVE_THREADS if (!eina_spinlock_new(&_eina_errno_msgs_lock)) goto failed_lock; _eina_errno_msgs = eina_hash_int32_new(EINA_FREE_CB(eina_stringshare_del)); if (!_eina_errno_msgs) goto failed_hash; #endif return EINA_TRUE; #ifdef EINA_HAVE_THREADS failed_hash: eina_spinlock_free(&_eina_errno_msgs_lock); failed_lock: eina_tls_free(_eina_last_key); _eina_last_error = 0; return EINA_FALSE; #endif } /** * @internal * @brief Shut down the error module. * * @return #EINA_TRUE on success, #EINA_FALSE on failure. * * This function shuts down the error module set up by * eina_error_init(). It is called by eina_shutdown(). * * @see eina_shutdown() */ Eina_Bool eina_error_shutdown(void) { Eina_Error_Message *eem, *eem_end; eem = _eina_errors; eem_end = eem + _eina_errors_count; for (; eem < eem_end; eem++) if (eem->string_allocated) eina_stringshare_del(eem->string); free(_eina_errors); _eina_errors = NULL; _eina_errors_count = 0; _eina_errors_allocated = 0; #ifdef EINA_HAVE_THREADS eina_hash_free(_eina_errno_msgs); _eina_errno_msgs = NULL; eina_spinlock_free(&_eina_errno_msgs_lock); #endif eina_tls_free(_eina_last_key); _eina_last_error = 0; return EINA_TRUE; } /*============================================================================* * API * *============================================================================*/ EAPI Eina_Error eina_error_msg_register(const char *msg) { Eina_Error_Message *eem; EINA_SAFETY_ON_NULL_RETURN_VAL(msg, 0); eem = _eina_error_msg_alloc(); if (!eem) return 0; eem->string_allocated = EINA_TRUE; eem->string = eina_stringshare_add(msg); if (!eem->string) { _eina_errors_count--; return 0; } return EINA_ERROR_FROM_INDEX(_eina_errors_count); /* identifier = index + 1 (== _count). */ } EAPI Eina_Error eina_error_msg_static_register(const char *msg) { Eina_Error_Message *eem; EINA_SAFETY_ON_NULL_RETURN_VAL(msg, 0); eem = _eina_error_msg_alloc(); if (!eem) return 0; eem->string_allocated = EINA_FALSE; eem->string = msg; return EINA_ERROR_FROM_INDEX(_eina_errors_count); /* identifier = index + 1 (== _count). */ } EAPI Eina_Bool eina_error_msg_modify(Eina_Error error, const char *msg) { EINA_SAFETY_ON_NULL_RETURN_VAL(msg, EINA_FALSE); EINA_SAFETY_ON_FALSE_RETURN_VAL(EINA_ERROR_REGISTERED_CHECK(error), EINA_FALSE); error = EINA_ERROR_TO_INDEX(error); if (error < 1) return EINA_FALSE; if ((size_t)error > _eina_errors_count) return EINA_FALSE; if (_eina_errors[error - 1].string_allocated) { const char *tmp; if (!(tmp = eina_stringshare_add(msg))) return EINA_FALSE; eina_stringshare_del(_eina_errors[error - 1].string); _eina_errors[error - 1].string = tmp; return EINA_TRUE; } _eina_errors[error - 1].string = msg; return EINA_TRUE; } EAPI const char * eina_error_msg_get(Eina_Error error) { if (!EINA_ERROR_REGISTERED_CHECK(error)) { const char unknown_prefix[] = "Unknown error "; const char *msg; /* original behavior of this function did not return strings * for unknown errors, so skip 0 ("Success") and * "Unknown error $N". */ if (error == 0) return NULL; #ifndef EINA_HAVE_THREADS msg = strerror(error); if (strncmp(msg, unknown_prefix, sizeof(unknown_prefix) -1) == 0) msg = NULL; #else /* EINA_HAVE_THREADS */ /* strerror() is not thread safe, so use a local buffer with * strerror_r() and cache resolved strings in a hash so we can * return the stringshared refernece. */ if (eina_spinlock_take(&_eina_errno_msgs_lock) != EINA_LOCK_SUCCEED) { EINA_SAFETY_ERROR("could not take spinlock for errno messages hash!"); return NULL; } msg = eina_hash_find(_eina_errno_msgs, &error); eina_spinlock_release(&_eina_errno_msgs_lock); if (!msg) { char buf[256] = ""; const char *str = NULL; #ifdef HAVE_STRERROR_R # ifndef STRERROR_R_CHAR_P int ret; ret = strerror_r(error, buf, sizeof(buf)); /* XSI */ if (ret == 0) str = buf; else if (ret == EINVAL) return NULL; # else /* STRERROR_R_CHAR_P */ str = strerror_r(error, buf, sizeof(buf)); /* GNU */ # endif /* ! STRERROR_R_CHAR_P */ #else /* not so good fallback. Usually strerror(err) will * return a const string if a known error (what we use), * and will return a pointer to a global modified string * formatted with "Unknown error XXXX".. which we just * ignore... so while it's not super-correct, this * should work well. */ eina_strlcpy(buf, strerror(error), sizeof(buf)); str = buf; #endif /* HAVE_STRERROR_R */ if (!str) EINA_SAFETY_ERROR("strerror_r() failed"); else { if (strncmp(str, unknown_prefix, sizeof(unknown_prefix) -1) == 0) msg = NULL; else { msg = eina_stringshare_add(str); if (eina_spinlock_take(&_eina_errno_msgs_lock) != EINA_LOCK_SUCCEED) { EINA_SAFETY_ERROR("could not take spinlock for errno messages hash!"); return NULL; } eina_hash_add(_eina_errno_msgs, &error, msg); eina_spinlock_release(&_eina_errno_msgs_lock); } } } #endif return msg; } error = EINA_ERROR_TO_INDEX(error); if (error < 1) return NULL; if ((size_t)error > _eina_errors_count) return NULL; return _eina_errors[error - 1].string; } EAPI Eina_Error eina_error_get(void) { if (eina_main_loop_is()) return _eina_last_error; return (Eina_Error)(uintptr_t) eina_tls_get(_eina_last_key); } EAPI void eina_error_set(Eina_Error err) { if (eina_main_loop_is()) _eina_last_error = err; else eina_tls_set(_eina_last_key, (void*)(uintptr_t) err); } EAPI Eina_Error eina_error_find(const char *msg) { size_t i; EINA_SAFETY_ON_NULL_RETURN_VAL(msg, 0); for (i = 0; i < _eina_errors_count; i++) { if (_eina_errors[i].string_allocated) { if (_eina_errors[i].string == msg) return EINA_ERROR_FROM_INDEX(i + 1); } if (!strcmp(_eina_errors[i].string, msg)) return EINA_ERROR_FROM_INDEX(i + 1); } /* not bothering to lookup errno.h as we don't have a "maximum * error", thus we'd need to loop up to some arbitrary constant and * keep comparing if strerror() returns something meaningful. */ return 0; }