make dlerror state and message thread-local and dynamically-allocated

this fixes truncation of error messages containing long pathnames or
symbol names.

the dlerror state was previously required by POSIX to be global. the
resolution of bug 97 relaxed the requirements to allow thread-safe
implementations of dlerror with thread-local state and message buffer.
diff --git a/src/ldso/dynlink.c b/src/ldso/dynlink.c
index ccd526f..62bfed8 100644
--- a/src/ldso/dynlink.c
+++ b/src/ldso/dynlink.c
@@ -21,8 +21,7 @@
 #include "libc.h"
 #include "dynlink.h"
 
-static int errflag;
-static char errbuf[128];
+static void error(const char *, ...);
 
 #ifdef SHARED
 
@@ -139,17 +138,6 @@
 	return 1;
 }
 
-static void error(const char *fmt, ...)
-{
-	va_list ap;
-	va_start(ap, fmt);
-	vsnprintf(errbuf, sizeof errbuf, fmt, ap);
-	va_end(ap);
-	if (runtime) longjmp(*rtld_fail, 1);
-	dprintf(2, "%s\n", errbuf);
-	ldso_fail = 1;
-}
-
 static uint32_t sysv_hash(const char *s0)
 {
 	const unsigned char *s = (void *)s0;
@@ -283,6 +271,7 @@
 			    || sym->st_info>>4 != STB_WEAK)) {
 				error("Error relocating %s: %s: symbol not found",
 					dso->name, name);
+				if (runtime) longjmp(*rtld_fail, 1);
 				continue;
 			}
 		} else {
@@ -347,9 +336,12 @@
 			if (stride<3) addend = reloc_addr[1];
 			if (runtime && def.dso->tls_id >= static_tls_cnt) {
 				struct td_index *new = malloc(sizeof *new);
-				if (!new) error(
+				if (!new) {
+					error(
 					"Error relocating %s: cannot allocate TLSDESC for %s",
 					dso->name, sym ? name : "(local)" );
+					if (runtime) longjmp(*rtld_fail, 1);
+				}
 				new->next = dso->td_index;
 				dso->td_index = new;
 				new->args[0] = def.dso->tls_id;
@@ -370,6 +362,7 @@
 		default:
 			error("Error relocating %s: unsupported relocation type %d",
 				dso->name, type);
+			if (runtime) longjmp(*rtld_fail, 1);
 			continue;
 		}
 	}
@@ -848,6 +841,7 @@
 			if (!dep) {
 				error("Error loading shared library %s: %m (needed by %s)",
 					p->strings + p->dynv[i+1], p->name);
+				if (runtime) longjmp(*rtld_fail, 1);
 				continue;
 			}
 			if (runtime) {
@@ -917,6 +911,7 @@
 		    mprotect(p->base+p->relro_start, p->relro_end-p->relro_start, PROT_READ) < 0) {
 			error("Error relocating %s: RELRO protection failed: %m",
 				p->name);
+			if (runtime) longjmp(*rtld_fail, 1);
 		}
 
 		p->relocated = 1;
@@ -1433,16 +1428,14 @@
 		tail = orig_tail;
 		tail->next = 0;
 		p = 0;
-		errflag = 1;
 		goto end;
 	} else p = load_library(file, head);
 
 	if (!p) {
-		snprintf(errbuf, sizeof errbuf, noload ?
+		error(noload ?
 			"Library %s is not already loaded" :
 			"Error loading shared library %s: %m",
 			file);
-		errflag = 1;
 		goto end;
 	}
 
@@ -1482,8 +1475,7 @@
 {
 	struct dso *p;
 	for (p=head; p; p=p->next) if (h==p) return 0;
-	snprintf(errbuf, sizeof errbuf, "Invalid library handle %p", (void *)h);
-	errflag = 1;
+	error("Invalid library handle %p", (void *)h);
 	return 1;
 }
 
@@ -1535,8 +1527,7 @@
 			return p->deps[i]->base + sym->st_value;
 	}
 failed:
-	errflag = 1;
-	snprintf(errbuf, sizeof errbuf, "Symbol not found: %s", s);
+	error("Symbol not found: %s", s);
 	return 0;
 }
 
@@ -1639,20 +1630,17 @@
 #else
 static int invalid_dso_handle(void *h)
 {
-	snprintf(errbuf, sizeof errbuf, "Invalid library handle %p", (void *)h);
-	errflag = 1;
+	error("Invalid library handle %p", (void *)h);
 	return 1;
 }
 void *dlopen(const char *file, int mode)
 {
-	strcpy(errbuf, "Dynamic loading not supported");
-	errflag = 1;
+	error("Dynamic loading not supported");
 	return 0;
 }
 void *__dlsym(void *restrict p, const char *restrict s, void *restrict ra)
 {
-	errflag = 1;
-	snprintf(errbuf, sizeof errbuf, "Symbol not found: %s", s);
+	error("Symbol not found: %s", s);
 	return 0;
 }
 int __dladdr (const void *addr, Dl_info *info)
@@ -1665,8 +1653,7 @@
 {
 	if (invalid_dso_handle(dso)) return -1;
 	if (req != RTLD_DI_LINKMAP) {
-		snprintf(errbuf, sizeof errbuf, "Unsupported request %d", req);
-		errflag = 1;
+		error("Unsupported request %d", req);
 		return -1;
 	}
 	*(struct link_map **)res = dso;
@@ -1675,12 +1662,54 @@
 
 char *dlerror()
 {
-	if (!errflag) return 0;
-	errflag = 0;
-	return errbuf;
+	pthread_t self = __pthread_self();
+	if (!self->dlerror_flag) return 0;
+	self->dlerror_flag = 0;
+	char *s = self->dlerror_buf;
+	if (s == (void *)-1)
+		return "Dynamic linker failed to allocate memory for error message";
+	else
+		return s;
 }
 
 int dlclose(void *p)
 {
 	return invalid_dso_handle(p);
 }
+
+void __dl_thread_cleanup(void)
+{
+	pthread_t self = __pthread_self();
+	if (self->dlerror_buf != (void *)-1)
+		free(self->dlerror_buf);
+}
+
+static void error(const char *fmt, ...)
+{
+	va_list ap;
+	va_start(ap, fmt);
+#ifdef SHARED
+	if (!runtime) {
+		vdprintf(2, fmt, ap);
+		dprintf(2, "\n");
+		ldso_fail = 1;
+		va_end(ap);
+		return;
+	}
+#endif
+	pthread_t self = __pthread_self();
+	if (self->dlerror_buf != (void *)-1)
+		free(self->dlerror_buf);
+	size_t len = vsnprintf(0, 0, fmt, ap);
+	va_end(ap);
+	char *buf = malloc(len+1);
+	if (buf) {
+		va_start(ap, fmt);
+		vsnprintf(buf, len+1, fmt, ap);
+		va_end(ap);
+	} else {
+		buf = (void *)-1;	
+	}
+	self->dlerror_buf = buf;
+	self->dlerror_flag = 1;
+}