// Ada Milter API thread wrapper // Copyright 2013 B. Persson, Bjorn@Rombobeorn.se // // This library is free software: you can redistribute it and/or modify it // under the terms of the GNU General Public License version 3, as published // by the Free Software Foundation. // This module wraps any threads that are started from anywhere in the program, // making them call GNAT.Threads.Register_Thread when they start and // GNAT.Threads.Unregister_Thread when they terminate. Although Libgnat // registers foreign threads automatically on GNU/Linux, it leaks memory when // the threads terminate. Calling Unregister_Thread plugs the leak. // // Initially nothing special is done to threads, so that Ada tasks such as // Libgnat's Interrupt_Manager task can be started during elaboration, even // before Libgnat is ready to handle calls to Register_Thread. The wrapping // begins after start_wrapping_threads is called. // // To make the wrapping of threads possible this module contains a function // named pthread_create which will be called instead of the one in the Pthreads // library. This function in turn calls the real pthread_create, but instructs // it to have the thread run the thread start routine wrap_thread instead of // its normal start routine. wrap_thread calls Register_Thread, arranges for // Unregister_Thread to be called when the thread terminates, and then calls // the normal start routine. // // For this to work the Pthreads library must be linked in dynamically, but // this module must be statically linked into the main executable to ensure // that this pthread_create is found before the one in the Pthreads library. #include "pthread_create_locator.h" #include #include #include #include #include #include #include // declarations copied from g-thread.ads: extern void *__gnat_register_thread(); extern void __gnat_unregister_thread(); typedef struct thread_data { void* (*start_routine)(void*); void* arg; } thread_data; // A thread_data object holds a thread start routine and an argument to be // passed to it. static int (*real_pthread_create)(pthread_t*, const pthread_attr_t*, void* (*)(void*), void*) = NULL; static bool wrap_threads = false; void start_wrapping_threads() // start_wrapping_threads shall be called before any threads that need // wrapping are started, but after tasking packages have been elaborated so // that Libgnat is ready to handle calls to Register_Thread. { wrap_threads = true; } static void cleanup_handler(void* const dummy __attribute__ ((unused))) // cleanup_handler is a wrapper around Unregister_Thread. It exists because // clean-up handlers are specified to take a void pointer argument and // Unregister_Thread doesn't expect one. { __gnat_unregister_thread(); } static void* wrap_thread(void* const wrapped_thread_data) // wrap_thread is the thread start routine of all wrapped threads. It // performs the wrapping and calls the routine that wrapped_thread_data // holds. { // Copy the argument to a local variable and deallocate it so that it won't // leak regardless of how the thread terminates. const thread_data wrapped = *(thread_data*)wrapped_thread_data; void* output; free(wrapped_thread_data); // Register the thread and set up a clean-up handler to unregister it. The // clean-up handler will be called regardless of how the thread terminates, // except if the thread sets its cancelability type to asynchronous, which // would be quite wrong for a thread that doesn't have intimate knowledge of // all the code it executes. __gnat_register_thread(); pthread_cleanup_push(cleanup_handler, NULL); // Execute the wrapped thread. output = wrapped.start_routine(wrapped.arg); pthread_cleanup_pop(true); return output; } int pthread_create(pthread_t* const thread, const pthread_attr_t* const attr, void* (* const start_routine)(void*), void* const arg) { if(real_pthread_create == NULL) { // This is the first call to this function, so the real pthread_create // needs to be located. This also means that there are no other threads // yet, so there's no need for synchronization. const char* error; // Ensure that there's no undetected error left from other calls to the // dynamic loader. Complain if there is one. error = dlerror(); if(error != NULL) { syslog(LOG_ERR, "pthread_create wrapper: " "somebody neglected this error from the dynamic loader: %s", error); } // Look up the real pthread_create. *(const void**)(&real_pthread_create) = dlsym_next_pthread_create(); // According to the Linux man page for dlsym, which cites POSIX.1-2003, // the cast from a function pointer pointer to a void pointer pointer is // necessary because C99 leaves casting from a void pointer to a function // pointer undefined. error = dlerror(); if(error != NULL) { syslog(LOG_ERR, "pthread_create wrapper: dlsym failed: %s", error); return ELIBACC; // "Cannot access a needed shared library" } else if(real_pthread_create == NULL) { syslog(LOG_ERR, "pthread_create wrapper: pthread_create could not be found."); return ELIBACC; } } if(wrap_threads) { // Tell the real pthread_create to start the thread with wrap_thread and // pass start_routine and its argument as the argument to wrap_thread. thread_data* const wrapped = malloc(sizeof(thread_data)); if(wrapped == NULL) { // malloc failed. Return EAGAIN, which is the documented code for // insufficient resources to create another thread. return EAGAIN; } wrapped->start_routine = start_routine; wrapped->arg = arg; return real_pthread_create(thread, attr, wrap_thread, wrapped); } else { // Wrapping hasn't been enabled yet. Pass the arguments unmodified to the // real pthread_create. return real_pthread_create(thread, attr, start_routine, arg); } }