select: fix 64-bit timeout truncation on pre-time64 kernels
[musl] / src / ldso / dlerror.c
index c8c718a..dae0f3a 100644 (file)
@@ -2,7 +2,13 @@
 #include <stdlib.h>
 #include <stdarg.h>
 #include "pthread_impl.h"
-#include "libc.h"
+#include "dynlink.h"
+#include "atomic.h"
+
+#define malloc __libc_malloc
+#define calloc __libc_calloc
+#define realloc __libc_realloc
+#define free __libc_free
 
 char *dlerror()
 {
@@ -16,21 +22,45 @@ char *dlerror()
                return s;
 }
 
+/* Atomic singly-linked list, used to store list of thread-local dlerror
+ * buffers for deferred free. They cannot be freed at thread exit time
+ * because, by the time it's known they can be freed, the exiting thread
+ * is in a highly restrictive context where it cannot call (even the
+ * libc-internal) free. It also can't take locks; thus the atomic list. */
+
+static void *volatile freebuf_queue;
+
 void __dl_thread_cleanup(void)
 {
        pthread_t self = __pthread_self();
-       if (self->dlerror_buf != (void *)-1)
-               free(self->dlerror_buf);
+       if (!self->dlerror_buf || self->dlerror_buf == (void *)-1)
+               return;
+       void *h;
+       do {
+               h = freebuf_queue;
+               *(void **)self->dlerror_buf = h;
+       } while (a_cas_p(&freebuf_queue, h, self->dlerror_buf) != h);
 }
 
 hidden void __dl_vseterr(const char *fmt, va_list ap)
 {
+       void **q;
+       do q = freebuf_queue;
+       while (q && a_cas_p(&freebuf_queue, q, 0) != q);
+
+       while (q) {
+               void **p = *q;
+               free(q);
+               q = p;
+       }
+
        va_list ap2;
        va_copy(ap2, ap);
        pthread_t self = __pthread_self();
        if (self->dlerror_buf != (void *)-1)
                free(self->dlerror_buf);
        size_t len = vsnprintf(0, 0, fmt, ap2);
+       if (len < sizeof(void *)) len = sizeof(void *);
        va_end(ap2);
        char *buf = malloc(len+1);
        if (buf) {
@@ -50,8 +80,6 @@ hidden void __dl_seterr(const char *fmt, ...)
        va_end(ap);
 }
 
-hidden int __dl_invalid_handle(void *);
-
 static int stub_invalid_handle(void *h)
 {
        __dl_seterr("Invalid library handle %p", (void *)h);