/*
* COPYRIGHT (c) 2012 Chris Johns <chrisj@rtems.org>
*
* The license and distribution terms for this file may be
* found in the file LICENSE in this distribution or at
* http://www.rtems.org/license/LICENSE.
*/
/**
* @file
*
* @ingroup rtems_rap
*
* @brief RTEMS Application Loader
*
* This is the RAP implementation.
*/
#if HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <rtems/libio_.h>
#include <dlfcn.h>
#include <rtems/rtl/rap.h>
#include <rtems/rtl/rtl.h>
#include "rtl-find-file.h"
/**
* The global RAP data. This structure is allocated on the heap when the first
* call to location an application and is never released.
*/
typedef struct rtems_rap_data_s
{
rtems_id lock; /**< The RAP lock id */
rtems_chain_control apps; /**< List if loaded application. */
int last_errno; /**< Last error number. */
char last_error[64]; /**< Last error string. */
} rtems_rap_data_t;
/**
* The RAP file data. This structure is allocated on the heap when a file is
* loaded.
*/
typedef struct rtems_rap_app_s
{
rtems_chain_node node; /**< The node's link in the chain. */
const char* name; /**< The file name */
void* handle; /**< The dlopen handle. */
} rtems_rap_app_t;
/**
* Semaphore configuration to create a mutex.
*/
#define RTEMS_MUTEX_ATTRIBS \
(RTEMS_PRIORITY | RTEMS_BINARY_SEMAPHORE | \
RTEMS_INHERIT_PRIORITY | RTEMS_NO_PRIORITY_CEILING | RTEMS_LOCAL)
/**
* RTL entry.
*/
#if (RTL_GLUE(__USER_LABEL_PREFIX__, 1) == RTL_GLUE(_, 1))
#define RTL_ENTRY_POINT "_rtems"
#else
#define RTL_ENTRY_POINT "rtems"
#endif
/**
* Static RAP data is returned to the user when the loader is locked.
*/
static rtems_rap_data_t rap_;
/**
* Verbose level for the RAP loader.
*/
static bool rap_verbose;
/**
* RAP entry call signature.
*/
typedef int (*rtems_rap_entry_t)(int argc, const char* argv[]);
/**
* Forward decl.
*/
static bool rtems_rap_unlock (void);
static bool
rtems_rap_data_init (void)
{
/*
* Lock the RAP. We only create a lock if a call is made. First we test if a
* lock is present. If one is present we lock it. If not the libio lock is
* locked and we then test the lock again. If not present we create the lock
* then release libio lock.
*/
if (!rap_.lock)
{
rtems_libio_lock ();
if (!rap_.lock)
{
rtems_status_code sc;
rtems_id lock;
/*
* Create the RAP lock.
*/
sc = rtems_semaphore_create (rtems_build_name ('R', 'A', 'P', '_'),
1, RTEMS_MUTEX_ATTRIBS,
RTEMS_NO_PRIORITY, &lock);
if (sc != RTEMS_SUCCESSFUL)
return false;
sc = rtems_semaphore_obtain (lock, RTEMS_WAIT, RTEMS_NO_TIMEOUT);
if (sc != RTEMS_SUCCESSFUL)
{
rtems_semaphore_delete (lock);
return false;
}
rap_.lock = lock;
/*
* Initialise the objects list and create any required services.
*/
rtems_chain_initialize_empty (&rap_.apps);
}
rtems_libio_unlock ();
rtems_rap_unlock ();
}
return true;
}
static rtems_rap_data_t*
rtems_rap_lock (void)
{
rtems_status_code sc;
if (!rtems_rap_data_init ())
return NULL;
sc = rtems_semaphore_obtain (rap_.lock,
RTEMS_WAIT, RTEMS_NO_TIMEOUT);
if (sc != RTEMS_SUCCESSFUL)
{
errno = EINVAL;
return NULL;
}
return &rap_;
}
static bool
rtems_rap_unlock (void)
{
/*
* Not sure any error should be returned or an assert.
*/
rtems_status_code sc;
sc = rtems_semaphore_release (rap_.lock);
if ((sc != RTEMS_SUCCESSFUL) && (errno == 0))
{
errno = EINVAL;
return false;
}
return true;
}
static rtems_rap_app_t*
rtems_rap_check_handle (void* handle)
{
rtems_rap_app_t* app;
rtems_chain_node* node;
app = handle;
node = rtems_chain_first (&rap_.apps);
while (!rtems_chain_is_tail (&rap_.apps, node))
{
rtems_rap_app_t* check = (rtems_rap_app_t*) node;
if (check == app)
return app;
node = rtems_chain_next (node);
}
return NULL;
}
static rtems_rap_app_t*
rtems_rap_app_alloc (void)
{
rtems_rap_app_t* app = malloc (sizeof (rtems_rap_app_t));
memset (app, 0, sizeof (rtems_rap_app_t));
rtems_chain_append (&rap_.apps, &app->node);
return app;
}
static void
rtems_rap_app_free (rtems_rap_app_t* app)
{
if (app->handle)
{
dlclose (app->handle);
app->handle = NULL;
}
if (!rtems_chain_is_node_off_chain (&app->node))
rtems_chain_extract (&app->node);
}
static bool
rtems_rap_match_name (rtems_rap_app_t* app, const char* name)
{
const char* a;
/*
* Assume the app name is absolute, ie points to the file on disk. This means
* there is at least one delimiter in the name.
*/
if (strncmp (app->name, name, strlen (name)) == 0)
return true;
a = app->name + strlen (app->name) - 1;
while (a >= app->name)
{
if (rtems_filesystem_is_delimiter (*a))
{
const char* n = name;
++a;
while (*a && *n)
{
if (*a == '.')
{
if (*n == '\0')
return true;
}
++a;
++n;
}
return false;
}
--a;
}
return false;
}
static void
rtems_rap_get_rtl_error (void)
{
rap_.last_errno =
rtems_rtl_get_error (rap_.last_error, sizeof (rap_.last_error));
}
static void
rtems_rap_set_error (int error, const char* format, ...)
{
rtems_rap_data_t* rap = rtems_rap_lock ();
va_list ap;
va_start (ap, format);
rap->last_errno = error;
vsnprintf (rap->last_error, sizeof (rap->last_error), format, ap);
rtems_rap_unlock ();
va_end (ap);
}
bool
rtems_rap_load (const char* name, int mode, int argc, const char* argv[])
{
rtems_rap_data_t* rap = rtems_rap_lock ();
if (!rap)
return false;
if (rap_verbose)
printf ("rap: loading '%s'\n", name);
/*
* See if the app has already been loaded.
*/
if (!rtems_rap_find (name))
{
rtems_rap_app_t* app;
rtems_rap_entry_t init;
rtems_rap_entry_t fini;
size_t size = 0;
int r;
/*
* Allocate a new application descriptor and attempt to load it.
*/
app = rtems_rap_app_alloc ();
if (app == NULL)
{
rtems_rap_set_error (ENOMEM, "no memory for application");
rtems_rap_unlock ();
return false;
}
/*
* Find the file in the file system using the search path.
*/
if (!rtems_rtl_find_file (name, getenv ("PATH"), &app->name, &size))
{
rtems_rap_set_error (ENOENT, "file not found");
rtems_rap_app_free (app);
rtems_rap_unlock ();
return false;
}
app->handle = dlopen (app->name, RTLD_NOW | mode);
if (!app->handle)
{
rtems_rap_get_rtl_error ();
rtems_rap_app_free (app);
rtems_rap_unlock ();
return false;
}
init = dlsym (app->handle, RTL_ENTRY_POINT);
if (!init)
{
rtems_rap_get_rtl_error ();
rtems_rap_app_free (app);
rtems_rap_unlock ();
return false;
}
fini = dlsym (app->handle, RTL_ENTRY_POINT);
if (!fini)
{
rtems_rap_get_rtl_error ();
rtems_rap_app_free (app);
rtems_rap_unlock ();
return false;
}
r = init (argc, argv);
if (r != 0)
{
rtems_rap_set_error (r, "init call failure");
rtems_rap_app_free (app);
rtems_rap_unlock ();
return false;
}
}
rtems_rap_unlock ();
return true;
}
bool
rtems_rap_unload (const char* name)
{
rtems_rap_app_t* app;
rtems_rap_entry_t fini;
int r;
rtems_rap_lock ();
app = rtems_rap_find (name);
if (rap_verbose)
printf ("rap: unloading '%s'\n", name);
if (!app)
{
rtems_rap_set_error (ENOENT, "invalid handle");
rtems_rap_unlock ();
return false;
}
fini = dlsym (app->handle, RTL_ENTRY_POINT);
if (!fini)
{
rtems_rap_get_rtl_error ();
rtems_rap_unlock ();
return false;
}
r = fini (0, NULL);
if (r != 0)
{
rtems_rap_set_error (r, "fini failure");
rtems_rap_unlock ();
return false;
}
rtems_rap_app_free (app);
rtems_rap_unlock ();
return true;
}
void*
rtems_rap_find (const char* name)
{
rtems_rap_data_t* rap = rtems_rap_lock ();
rtems_chain_node* node;
node = rtems_chain_first (&rap->apps);
while (!rtems_chain_is_tail (&rap->apps, node))
{
rtems_rap_app_t* app = (rtems_rap_app_t*) node;
if (rtems_rap_match_name (app, name))
{
rtems_rap_unlock ();
return app;
}
node = rtems_chain_next (node);
}
rtems_rap_unlock ();
return NULL;
}
bool
rtems_rap_iterate (rtems_rap_iterator_t iterator)
{
rtems_rap_data_t* rap = rtems_rap_lock ();
rtems_chain_node* node;
bool result = true;
node = rtems_chain_first (&rap->apps);
while (!rtems_chain_is_tail (&rap->apps, node))
{
rtems_rap_app_t* app = (rtems_rap_app_t*) node;
result = iterator (app);
if (!result)
break;
node = rtems_chain_next (node);
}
rtems_rap_unlock ();
return result;
}
const char*
rtems_rap_name (void* handle)
{
rtems_rap_app_t* app = rtems_rap_check_handle (handle);
if (app)
return app->name;
return NULL;
}
void*
rtems_rap_dl_handle (void* handle)
{
rtems_rap_app_t* app = rtems_rap_check_handle (handle);
if (app)
return app->handle;
return NULL;
}
int
rtems_rap_get_error (char* message, size_t max_message)
{
rtems_rap_data_t* rap = rtems_rap_lock ();
int last_errno = rap->last_errno;
strncpy (message, rap->last_error, sizeof (rap->last_error));
rtems_rap_unlock ();
return last_errno;
}