]> git.draconx.ca Git - cdecl99.git/blob - src/error.c
Include glthread headers late.
[cdecl99.git] / src / error.c
1 /*
2  *  Error handling for libcdecl.
3  *  Copyright © 2011-2012, 2021 Nick Bowler
4  *
5  *  This program is free software: you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation, either version 3 of the License, or
8  *  (at your option) any later version.
9  *
10  *  This program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18
19 #include <config.h>
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <assert.h>
24 #include <stdarg.h>
25
26 #include "cdecl.h"
27 #include "cdecl-internal.h"
28
29 #include <glthread/lock.h>
30 #include <glthread/tls.h>
31
32 gl_once_define(static, tls_initialized);
33 static gl_tls_key_t tls_key;
34
35 struct err_state {
36         struct cdecl_error err;
37         size_t nstr;
38         char str[FLEXIBLE_ARRAY_MEMBER];
39 };
40
41 /* This error is reserved for extremely dire out-of-memory conditions. */
42 static struct err_state err_no_mem = {
43         .err = {
44                 .code = CDECL_ENOMEM,
45                 .str  = NULL,
46         },
47 };
48
49 static void free_err(void *err)
50 {
51         if (err == &err_no_mem)
52                 return;
53
54         free(err);
55 }
56
57 static void initialize(void)
58 {
59         cdecl__init_i18n();
60         err_no_mem.err.str = _("failed to allocate memory");
61
62         gl_tls_key_init(tls_key, free_err);
63 }
64
65 /*
66  * cdecl__err(CDECL_ENOMEM);
67  * cdecl__err(code, fmt, ...);
68  *
69  * Sets the library error to code, with a printf-style error string.
70  */
71 void cdecl__err(unsigned code, ...)
72 {
73         const char *fmt;
74         struct err_state *state;
75         int rc, try = 0;
76         va_list ap;
77
78         gl_once(tls_initialized, initialize);
79
80         state = gl_tls_get(tls_key);
81         if (!state || state == &err_no_mem) {
82                 void *tmp = malloc(sizeof *state + 100);
83
84                 if (!tmp) {
85                         state = &err_no_mem;
86                         return;
87                 }
88
89                 gl_tls_set(tls_key, (state = tmp));
90                 state->nstr = 100;
91         }
92
93         if (code == CDECL_ENOMEM) {
94                 if (state != &err_no_mem)
95                         state->err = err_no_mem.err;
96                 return;
97         }
98 retry:
99         va_start(ap, code);
100         fmt = va_arg(ap, const char *);
101         rc = vsnprintf(state->str, state->nstr, fmt, ap);
102         va_end(ap);
103
104         if (rc > 0 && rc >= state->nstr) {
105                 void *tmp;
106                 size_t n;
107
108                 assert(try == 0 && rc < SIZE_MAX / 4);
109
110                 n = ((size_t)rc + 1) * 2;
111                 tmp = realloc(state, sizeof *state + n);
112                 if (tmp) {
113                         state = tmp;
114                         state->nstr = n;
115                         try++;
116
117                         goto retry;
118                 }
119
120                 state->err = err_no_mem.err;
121                 return;
122         }
123
124         state->err.str = state->str;
125         state->err.code = code;
126 }
127
128 const struct cdecl_error *cdecl_get_error(void)
129 {
130         struct err_state *state;
131
132         gl_once(tls_initialized, initialize);
133
134         state = gl_tls_get(tls_key);
135         return state ? &state->err : NULL;
136 }