diff options
Diffstat (limited to 'src/util/errors.c')
-rw-r--r-- | src/util/errors.c | 401 |
1 files changed, 401 insertions, 0 deletions
diff --git a/src/util/errors.c b/src/util/errors.c new file mode 100644 index 0000000..feed6a8 --- /dev/null +++ b/src/util/errors.c @@ -0,0 +1,401 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "git2_util.h" + +#include "errors.h" +#include "posix.h" +#include "str.h" +#include "runtime.h" + +/* + * Some static error data that is used when we're out of memory, TLS + * has not been setup, or TLS has failed. + */ + +static git_error oom_error = { + "Out of memory", + GIT_ERROR_NOMEMORY +}; + +static git_error uninitialized_error = { + "library has not been initialized", + GIT_ERROR_INVALID +}; + +static git_error tlsdata_error = { + "thread-local data initialization failure", + GIT_ERROR_THREAD +}; + +static git_error no_error = { + "no error", + GIT_ERROR_NONE +}; + +#define IS_STATIC_ERROR(err) \ + ((err) == &oom_error || (err) == &uninitialized_error || \ + (err) == &tlsdata_error || (err) == &no_error) + +/* Per-thread error state (TLS) */ + +static git_tlsdata_key tls_key; + +struct error_threadstate { + /* The error message buffer. */ + git_str message; + + /* Error information, set by `git_error_set` and friends. */ + git_error error; + + /* + * The last error to occur; points to the error member of this + * struct _or_ a static error. + */ + git_error *last; +}; + +static void threadstate_dispose(struct error_threadstate *threadstate) +{ + if (!threadstate) + return; + + git_str_dispose(&threadstate->message); +} + +static struct error_threadstate *threadstate_get(void) +{ + struct error_threadstate *threadstate; + + if ((threadstate = git_tlsdata_get(tls_key)) != NULL) + return threadstate; + + /* + * Avoid git__malloc here, since if it fails, it sets an error + * message, which requires thread state, which would allocate + * here, which would fail, which would set an error message... + */ + + if ((threadstate = git__allocator.gmalloc( + sizeof(struct error_threadstate), + __FILE__, __LINE__)) == NULL) + return NULL; + + memset(threadstate, 0, sizeof(struct error_threadstate)); + + if (git_str_init(&threadstate->message, 0) < 0) { + git__allocator.gfree(threadstate); + return NULL; + } + + git_tlsdata_set(tls_key, threadstate); + return threadstate; +} + +static void GIT_SYSTEM_CALL threadstate_free(void *threadstate) +{ + threadstate_dispose(threadstate); + git__free(threadstate); +} + +static void git_error_global_shutdown(void) +{ + struct error_threadstate *threadstate; + + threadstate = git_tlsdata_get(tls_key); + git_tlsdata_set(tls_key, NULL); + + threadstate_dispose(threadstate); + git__free(threadstate); + + git_tlsdata_dispose(tls_key); +} + +int git_error_global_init(void) +{ + if (git_tlsdata_init(&tls_key, &threadstate_free) != 0) + return -1; + + return git_runtime_shutdown_register(git_error_global_shutdown); +} + +static void set_error_from_buffer(int error_class) +{ + struct error_threadstate *threadstate = threadstate_get(); + git_error *error; + git_str *buf; + + if (!threadstate) + return; + + error = &threadstate->error; + buf = &threadstate->message; + + error->message = buf->ptr; + error->klass = error_class; + + threadstate->last = error; +} + +static void set_error(int error_class, char *string) +{ + struct error_threadstate *threadstate = threadstate_get(); + git_str *buf; + + if (!threadstate) + return; + + buf = &threadstate->message; + + git_str_clear(buf); + + if (string) + git_str_puts(buf, string); + + if (!git_str_oom(buf)) + set_error_from_buffer(error_class); +} + +void git_error_set_oom(void) +{ + struct error_threadstate *threadstate = threadstate_get(); + + if (!threadstate) + return; + + threadstate->last = &oom_error; +} + +void git_error_set(int error_class, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + git_error_vset(error_class, fmt, ap); + va_end(ap); +} + +void git_error_vset(int error_class, const char *fmt, va_list ap) +{ +#ifdef GIT_WIN32 + DWORD win32_error_code = (error_class == GIT_ERROR_OS) ? GetLastError() : 0; +#endif + + struct error_threadstate *threadstate = threadstate_get(); + int error_code = (error_class == GIT_ERROR_OS) ? errno : 0; + git_str *buf; + + if (!threadstate) + return; + + buf = &threadstate->message; + + git_str_clear(buf); + + if (fmt) { + git_str_vprintf(buf, fmt, ap); + if (error_class == GIT_ERROR_OS) + git_str_PUTS(buf, ": "); + } + + if (error_class == GIT_ERROR_OS) { +#ifdef GIT_WIN32 + char *win32_error = git_win32_get_error_message(win32_error_code); + if (win32_error) { + git_str_puts(buf, win32_error); + git__free(win32_error); + + SetLastError(0); + } + else +#endif + if (error_code) + git_str_puts(buf, strerror(error_code)); + + if (error_code) + errno = 0; + } + + if (!git_str_oom(buf)) + set_error_from_buffer(error_class); +} + +int git_error_set_str(int error_class, const char *string) +{ + struct error_threadstate *threadstate = threadstate_get(); + git_str *buf; + + GIT_ASSERT_ARG(string); + + if (!threadstate) + return -1; + + buf = &threadstate->message; + + git_str_clear(buf); + git_str_puts(buf, string); + + if (git_str_oom(buf)) + return -1; + + set_error_from_buffer(error_class); + return 0; +} + +void git_error_clear(void) +{ + struct error_threadstate *threadstate = threadstate_get(); + + if (!threadstate) + return; + + if (threadstate->last != NULL) { + set_error(0, NULL); + threadstate->last = NULL; + } + + errno = 0; +#ifdef GIT_WIN32 + SetLastError(0); +#endif +} + +bool git_error_exists(void) +{ + struct error_threadstate *threadstate; + + if ((threadstate = threadstate_get()) == NULL) + return true; + + return threadstate->last != NULL; +} + +const git_error *git_error_last(void) +{ + struct error_threadstate *threadstate; + + /* If the library is not initialized, return a static error. */ + if (!git_runtime_init_count()) + return &uninitialized_error; + + if ((threadstate = threadstate_get()) == NULL) + return &tlsdata_error; + + if (!threadstate->last) + return &no_error; + + return threadstate->last; +} + +int git_error_save(git_error **out) +{ + struct error_threadstate *threadstate = threadstate_get(); + git_error *error, *dup; + + if (!threadstate) { + *out = &tlsdata_error; + return -1; + } + + error = threadstate->last; + + if (!error || error == &no_error) { + *out = &no_error; + return 0; + } else if (IS_STATIC_ERROR(error)) { + *out = error; + return 0; + } + + if ((dup = git__malloc(sizeof(git_error))) == NULL) { + *out = &oom_error; + return -1; + } + + dup->klass = error->klass; + dup->message = git__strdup(error->message); + + if (!dup->message) { + *out = &oom_error; + return -1; + } + + *out = dup; + return 0; +} + +int git_error_restore(git_error *error) +{ + struct error_threadstate *threadstate = threadstate_get(); + + GIT_ASSERT_ARG(error); + + if (IS_STATIC_ERROR(error) && threadstate) + threadstate->last = error; + else + set_error(error->klass, error->message); + + git_error_free(error); + return 0; +} + +void git_error_free(git_error *error) +{ + if (!error) + return; + + if (IS_STATIC_ERROR(error)) + return; + + git__free(error->message); + git__free(error); +} + +int git_error_system_last(void) +{ +#ifdef GIT_WIN32 + return GetLastError(); +#else + return errno; +#endif +} + +void git_error_system_set(int code) +{ +#ifdef GIT_WIN32 + SetLastError(code); +#else + errno = code; +#endif +} + +/* Deprecated error values and functions */ + +#ifndef GIT_DEPRECATE_HARD + +#include "git2/deprecated.h" + +const git_error *giterr_last(void) +{ + return git_error_last(); +} + +void giterr_clear(void) +{ + git_error_clear(); +} + +void giterr_set_str(int error_class, const char *string) +{ + git_error_set_str(error_class, string); +} + +void giterr_set_oom(void) +{ + git_error_set_oom(); +} +#endif |