+#include <config.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <glthread/lock.h>
+#include <glthread/tls.h>
+#include "cdecl.h"
+#include "error.h"
+#include "i18n.h"
+
+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[];
+};
+
+/* This error is reserved for extremely dire out-of-memory conditions. */
+static struct err_state err_no_mem = {
+ .err = {
+ .code = CDECL_ENOMEM,
+ .str = "failed to allocate memory",
+ },
+};
+
+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 = gettext(err_no_mem.err.str);
+
+ gl_tls_key_init(tls_key, free_err);
+
+ /*
+ * This default error message is a stop-gap measure until all library
+ * error conditions use the new interface.
+ */
+ cdecl__set_error(&(const struct cdecl_error){ .code = CDECL_ENOPARSE });
+}
+
+const struct cdecl_error *cdecl_get_error(void)
+{
+ struct err_state *state;
+
+ gl_once(tls_initialized, initialize);
+
+ state = gl_tls_get(tls_key);
+ assert(state);
+
+ return &state->err;
+}
+
+void cdecl__set_error(const struct cdecl_error *err)
+{
+ struct err_state *state;
+ size_t need_len = 0;
+
+ gl_once(tls_initialized, initialize);
+
+ if (err->str) {
+ need_len = strlen(err->str) + 1;
+ }
+
+ state = gl_tls_get(tls_key);
+ if (state == &err_no_mem)
+ state = NULL;
+ if (!state || state->nstr < need_len) {
+ struct err_state *tmp;
+
+ tmp = realloc(state, sizeof *state + need_len);
+ if (!tmp) {
+ /* Re-use the existing state buffer, if any. */
+ if (state)
+ state->err = err_no_mem.err;
+ else
+ state = &err_no_mem;
+
+ gl_tls_set(tls_key, state);
+ return;
+ }
+
+ state = tmp;
+ state->nstr = need_len;
+ gl_tls_set(tls_key, state);
+ }
+
+ state->err = *err;
+ if (err->str) {
+ memcpy(state->str, err->str, need_len);
+ state->err.str = state->str;
+ } else {
+ state->err.str = "unknown error";
+ }
+}