/* * Error handling for libcdecl. * Copyright © 2011-2012, 2021 Nick Bowler * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include "cdecl.h" #include "cdecl-internal.h" #include #include gl_once_define(static, tls_initialized); static gl_tls_key_t tls_key; struct err_state { struct cdecl_error err; size_t nstr; char str[FLEXIBLE_ARRAY_MEMBER]; }; /* This error is reserved for extremely dire out-of-memory conditions. */ static struct err_state err_no_mem = { .err = { .code = CDECL_ENOMEM, .str = NULL, }, }; static void free_err(void *err) { if (err == &err_no_mem) return; free(err); } static void initialize(void) { cdecl__init_i18n(); err_no_mem.err.str = _("failed to allocate memory"); gl_tls_key_init(tls_key, free_err); } /* * cdecl__err(CDECL_ENOMEM); * cdecl__err(code, fmt, ...); * * Sets the library error to code, with a printf-style error string. */ void cdecl__err(unsigned code, ...) { const char *fmt; struct err_state *state; int rc, try = 0; va_list ap; gl_once(tls_initialized, initialize); state = gl_tls_get(tls_key); if (!state || state == &err_no_mem) { void *tmp = malloc(sizeof *state + 100); if (!tmp) { state = &err_no_mem; return; } gl_tls_set(tls_key, (state = tmp)); state->nstr = 100; } if (code == CDECL_ENOMEM) { if (state != &err_no_mem) state->err = err_no_mem.err; return; } retry: va_start(ap, code); fmt = va_arg(ap, const char *); rc = vsnprintf(state->str, state->nstr, fmt, ap); va_end(ap); if (rc > 0 && rc >= state->nstr) { void *tmp; size_t n; assert(try == 0 && rc < SIZE_MAX / 4); n = ((size_t)rc + 1) * 2; tmp = realloc(state, sizeof *state + n); if (tmp) { state = tmp; state->nstr = n; try++; goto retry; } state->err = err_no_mem.err; return; } state->err.str = state->str; state->err.code = code; } const struct cdecl_error *cdecl_get_error(void) { struct err_state *state; gl_once(tls_initialized, initialize); state = gl_tls_get(tls_key); return state ? &state->err : NULL; }