summaryrefslogtreecommitdiff
path: root/src/util/support/plugins.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/util/support/plugins.c')
-rw-r--r--src/util/support/plugins.c787
1 files changed, 787 insertions, 0 deletions
diff --git a/src/util/support/plugins.c b/src/util/support/plugins.c
new file mode 100644
index 000000000000..b0bb2ada8755
--- /dev/null
+++ b/src/util/support/plugins.c
@@ -0,0 +1,787 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* util/support/plugins.c - Plugin module support functions */
+/*
+ * Copyright 2006, 2008 by the Massachusetts Institute of Technology.
+ * All Rights Reserved.
+ *
+ * Export of this software from the United States of America may
+ * require a specific license from the United States Government.
+ * It is the responsibility of any person or organization contemplating
+ * export to obtain such a license before exporting.
+ *
+ * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
+ * distribute this software and its documentation for any purpose and
+ * without fee is hereby granted, provided that the above copyright
+ * notice appear in all copies and that both that copyright notice and
+ * this permission notice appear in supporting documentation, and that
+ * the name of M.I.T. not be used in advertising or publicity pertaining
+ * to distribution of the software without specific, written prior
+ * permission. Furthermore if you modify this software you must label
+ * your software as modified software and not distribute it in such a
+ * fashion that it might be confused with the original M.I.T. software.
+ * M.I.T. makes no representations about the suitability of
+ * this software for any purpose. It is provided "as is" without express
+ * or implied warranty.
+ */
+
+#include "k5-platform.h"
+#include "k5-plugin.h"
+#if USE_DLOPEN
+#include <dlfcn.h>
+#endif
+#include <sys/types.h>
+#ifdef HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+#ifdef HAVE_SYS_PARAM_H
+#include <sys/param.h>
+#endif
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#if USE_DLOPEN
+#ifdef RTLD_GROUP
+#define GROUP RTLD_GROUP
+#else
+#define GROUP 0
+#endif
+#ifdef RTLD_NODELETE
+#define NODELETE RTLD_NODELETE
+#else
+#define NODELETE 0
+#endif
+#define PLUGIN_DLOPEN_FLAGS (RTLD_NOW | RTLD_LOCAL | GROUP | NODELETE)
+#endif
+
+#if USE_DLOPEN && USE_CFBUNDLE
+#include <CoreFoundation/CoreFoundation.h>
+
+/* Currently CoreFoundation only exists on the Mac so we just use
+ * pthreads directly to avoid creating empty function calls on other
+ * platforms. If a thread initializer ever gets created in the common
+ * plugin code, move this there */
+static pthread_mutex_t krb5int_bundle_mutex = PTHREAD_MUTEX_INITIALIZER;
+#endif
+
+#include <stdarg.h>
+static void Tprintf (const char *fmt, ...)
+{
+#ifdef DEBUG
+ va_list va;
+ va_start (va, fmt);
+ vfprintf (stderr, fmt, va);
+ va_end (va);
+#endif
+}
+
+struct plugin_file_handle {
+#if USE_DLOPEN
+ void *dlhandle;
+#endif
+#ifdef _WIN32
+ HMODULE hinstPlugin;
+#endif
+#if !defined (USE_DLOPEN) && !defined (_WIN32)
+ char dummy;
+#endif
+};
+
+#ifdef _WIN32
+struct dirent {
+ long d_ino; /* inode (always 1 in WIN32) */
+ off_t d_off; /* offset to this dirent */
+ unsigned short d_reclen; /* length of d_name */
+ char d_name[_MAX_FNAME+1]; /* filename (null terminated) */
+};
+
+typedef struct {
+ intptr_t handle; /* _findfirst/_findnext handle */
+ short offset; /* offset into directory */
+ short finished; /* 1 if there are not more files */
+ struct _finddata_t fileinfo;/* from _findfirst/_findnext */
+ char *dir; /* the dir we are reading */
+ struct dirent dent; /* the dirent to return */
+} DIR;
+
+DIR * opendir(const char *dir)
+{
+ DIR *dp;
+ char *filespec;
+ intptr_t handle;
+ int index;
+
+ filespec = malloc(strlen(dir) + 2 + 1);
+ strcpy(filespec, dir);
+ index = strlen(filespec) - 1;
+ if (index >= 0 && (filespec[index] == '/' || filespec[index] == '\\'))
+ filespec[index] = '\0';
+ strcat(filespec, "/*");
+
+ dp = (DIR *)malloc(sizeof(DIR));
+ dp->offset = 0;
+ dp->finished = 0;
+ dp->dir = strdup(dir);
+
+ if ((handle = _findfirst(filespec, &(dp->fileinfo))) < 0) {
+ if (errno == ENOENT)
+ dp->finished = 1;
+ else {
+ free(filespec);
+ free(dp->dir);
+ free(dp);
+ return NULL;
+ }
+ }
+
+ dp->handle = handle;
+ free(filespec);
+
+ return dp;
+}
+
+struct dirent * readdir(DIR *dp)
+{
+ if (!dp || dp->finished) return NULL;
+
+ if (dp->offset != 0) {
+ if (_findnext(dp->handle, &(dp->fileinfo)) < 0) {
+ dp->finished = 1;
+ return NULL;
+ }
+ }
+ dp->offset++;
+
+ strncpy(dp->dent.d_name, dp->fileinfo.name, _MAX_FNAME);
+ dp->dent.d_ino = 1;
+ dp->dent.d_reclen = (unsigned short)strlen(dp->dent.d_name);
+ dp->dent.d_off = dp->offset;
+
+ return &(dp->dent);
+}
+
+int closedir(DIR *dp)
+{
+ if (!dp) return 0;
+ _findclose(dp->handle);
+ free(dp->dir);
+ free(dp);
+
+ return 0;
+}
+#endif
+
+long KRB5_CALLCONV
+krb5int_open_plugin (const char *filepath, struct plugin_file_handle **h, struct errinfo *ep)
+{
+ long err = 0;
+ struct stat statbuf;
+ struct plugin_file_handle *htmp = NULL;
+ int got_plugin = 0;
+
+ if (!err) {
+ if (stat (filepath, &statbuf) < 0) {
+ err = errno;
+ Tprintf ("stat(%s): %s\n", filepath, strerror (err));
+ k5_set_error(ep, err, _("unable to find plugin [%s]: %s"),
+ filepath, strerror(err));
+ }
+ }
+
+ if (!err) {
+ htmp = calloc (1, sizeof (*htmp)); /* calloc initializes ptrs to NULL */
+ if (htmp == NULL) { err = ENOMEM; }
+ }
+
+#if USE_DLOPEN
+ if (!err && ((statbuf.st_mode & S_IFMT) == S_IFREG
+#if USE_CFBUNDLE
+ || (statbuf.st_mode & S_IFMT) == S_IFDIR
+#endif /* USE_CFBUNDLE */
+ )) {
+ void *handle = NULL;
+
+#if USE_CFBUNDLE
+ char executablepath[MAXPATHLEN];
+
+ if ((statbuf.st_mode & S_IFMT) == S_IFDIR) {
+ int lock_err = 0;
+ CFStringRef pluginString = NULL;
+ CFURLRef pluginURL = NULL;
+ CFBundleRef pluginBundle = NULL;
+ CFURLRef executableURL = NULL;
+
+ /* Lock around CoreFoundation calls since objects are refcounted
+ * and the refcounts are not thread-safe. Using pthreads directly
+ * because this code is Mac-specific */
+ lock_err = pthread_mutex_lock(&krb5int_bundle_mutex);
+ if (lock_err) { err = lock_err; }
+
+ if (!err) {
+ pluginString = CFStringCreateWithCString (kCFAllocatorDefault,
+ filepath,
+ kCFStringEncodingASCII);
+ if (pluginString == NULL) { err = ENOMEM; }
+ }
+
+ if (!err) {
+ pluginURL = CFURLCreateWithFileSystemPath (kCFAllocatorDefault,
+ pluginString,
+ kCFURLPOSIXPathStyle,
+ true);
+ if (pluginURL == NULL) { err = ENOMEM; }
+ }
+
+ if (!err) {
+ pluginBundle = CFBundleCreate (kCFAllocatorDefault, pluginURL);
+ if (pluginBundle == NULL) { err = ENOENT; } /* XXX need better error */
+ }
+
+ if (!err) {
+ executableURL = CFBundleCopyExecutableURL (pluginBundle);
+ if (executableURL == NULL) { err = ENOMEM; }
+ }
+
+ if (!err) {
+ if (!CFURLGetFileSystemRepresentation (executableURL,
+ true, /* absolute */
+ (UInt8 *)executablepath,
+ sizeof (executablepath))) {
+ err = ENOMEM;
+ }
+ }
+
+ if (!err) {
+ /* override the path the caller passed in */
+ filepath = executablepath;
+ }
+
+ if (executableURL != NULL) { CFRelease (executableURL); }
+ if (pluginBundle != NULL) { CFRelease (pluginBundle); }
+ if (pluginURL != NULL) { CFRelease (pluginURL); }
+ if (pluginString != NULL) { CFRelease (pluginString); }
+
+ /* unlock after CFRelease calls since they modify refcounts */
+ if (!lock_err) { pthread_mutex_unlock (&krb5int_bundle_mutex); }
+ }
+#endif /* USE_CFBUNDLE */
+
+ if (!err) {
+ handle = dlopen(filepath, PLUGIN_DLOPEN_FLAGS);
+ if (handle == NULL) {
+ const char *e = dlerror();
+ if (e == NULL)
+ e = _("unknown failure");
+ Tprintf ("dlopen(%s): %s\n", filepath, e);
+ err = ENOENT; /* XXX */
+ k5_set_error(ep, err, _("unable to load plugin [%s]: %s"),
+ filepath, e);
+ }
+ }
+
+ if (!err) {
+ got_plugin = 1;
+ htmp->dlhandle = handle;
+ handle = NULL;
+ }
+
+ if (handle != NULL) { dlclose (handle); }
+ }
+#endif /* USE_DLOPEN */
+
+#ifdef _WIN32
+ if (!err && (statbuf.st_mode & S_IFMT) == S_IFREG) {
+ HMODULE handle = NULL;
+
+ handle = LoadLibrary(filepath);
+ if (handle == NULL) {
+ Tprintf ("Unable to load dll: %s\n", filepath);
+ err = ENOENT; /* XXX */
+ k5_set_error(ep, err, _("unable to load DLL [%s]"), filepath);
+ }
+
+ if (!err) {
+ got_plugin = 1;
+ htmp->hinstPlugin = handle;
+ handle = NULL;
+ }
+
+ if (handle != NULL)
+ FreeLibrary(handle);
+ }
+#endif
+
+ if (!err && !got_plugin) {
+ err = ENOENT; /* no plugin or no way to load plugins */
+ k5_set_error(ep, err, _("plugin unavailable: %s"), strerror(err));
+ }
+
+ if (!err) {
+ *h = htmp;
+ htmp = NULL; /* h takes ownership */
+ }
+
+ free(htmp);
+
+ return err;
+}
+
+static long
+krb5int_get_plugin_sym (struct plugin_file_handle *h,
+ const char *csymname, int isfunc, void **ptr,
+ struct errinfo *ep)
+{
+ long err = 0;
+ void *sym = NULL;
+
+#if USE_DLOPEN
+ if (!err && !sym && (h->dlhandle != NULL)) {
+ /* XXX Do we need to add a leading "_" to the symbol name on any
+ modern platforms? */
+ sym = dlsym (h->dlhandle, csymname);
+ if (sym == NULL) {
+ const char *e = dlerror (); /* XXX copy and save away */
+ if (e == NULL)
+ e = "unknown failure";
+ Tprintf ("dlsym(%s): %s\n", csymname, e);
+ err = ENOENT; /* XXX */
+ k5_set_error(ep, err, "%s", e);
+ }
+ }
+#endif
+
+#ifdef _WIN32
+ LPVOID lpMsgBuf;
+ DWORD dw;
+
+ if (!err && !sym && (h->hinstPlugin != NULL)) {
+ sym = GetProcAddress(h->hinstPlugin, csymname);
+ if (sym == NULL) {
+ const char *e = "unable to get dll symbol"; /* XXX copy and save away */
+ Tprintf ("GetProcAddress(%s): %i\n", csymname, GetLastError());
+ err = ENOENT; /* XXX */
+ k5_set_error(ep, err, "%s", e);
+
+ dw = GetLastError();
+ if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
+ FORMAT_MESSAGE_FROM_SYSTEM,
+ NULL,
+ dw,
+ MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+ (LPTSTR) &lpMsgBuf,
+ 0, NULL )) {
+
+ fprintf (stderr, "unable to get dll symbol, %s\n", (LPCTSTR)lpMsgBuf);
+ LocalFree(lpMsgBuf);
+ }
+ }
+ }
+#endif
+
+ if (!err && (sym == NULL)) {
+ err = ENOENT; /* unimplemented */
+ }
+
+ if (!err) {
+ *ptr = sym;
+ }
+
+ return err;
+}
+
+long KRB5_CALLCONV
+krb5int_get_plugin_data (struct plugin_file_handle *h, const char *csymname,
+ void **ptr, struct errinfo *ep)
+{
+ return krb5int_get_plugin_sym (h, csymname, 0, ptr, ep);
+}
+
+long KRB5_CALLCONV
+krb5int_get_plugin_func (struct plugin_file_handle *h, const char *csymname,
+ void (**ptr)(), struct errinfo *ep)
+{
+ void *dptr = NULL;
+ long err = krb5int_get_plugin_sym (h, csymname, 1, &dptr, ep);
+ if (!err) {
+ /* Cast function pointers to avoid code duplication */
+ *ptr = (void (*)()) dptr;
+ }
+ return err;
+}
+
+void KRB5_CALLCONV
+krb5int_close_plugin (struct plugin_file_handle *h)
+{
+#if USE_DLOPEN
+ if (h->dlhandle != NULL) { dlclose(h->dlhandle); }
+#endif
+#ifdef _WIN32
+ if (h->hinstPlugin != NULL) { FreeLibrary(h->hinstPlugin); }
+#endif
+ free (h);
+}
+
+/* autoconf docs suggest using this preference order */
+#if HAVE_DIRENT_H || USE_DIRENT_H
+#include <dirent.h>
+#define NAMELEN(D) strlen((D)->d_name)
+#else
+#ifndef _WIN32
+#define dirent direct
+#define NAMELEN(D) ((D)->d->namlen)
+#else
+#define NAMELEN(D) strlen((D)->d_name)
+#endif
+#if HAVE_SYS_NDIR_H
+# include <sys/ndir.h>
+#elif HAVE_SYS_DIR_H
+# include <sys/dir.h>
+#elif HAVE_NDIR_H
+# include <ndir.h>
+#endif
+#endif
+
+static long
+krb5int_plugin_file_handle_array_init (struct plugin_file_handle ***harray)
+{
+ long err = 0;
+
+ *harray = calloc (1, sizeof (**harray)); /* calloc initializes to NULL */
+ if (*harray == NULL) { err = ENOMEM; }
+
+ return err;
+}
+
+static long
+krb5int_plugin_file_handle_array_add (struct plugin_file_handle ***harray, size_t *count,
+ struct plugin_file_handle *p)
+{
+ long err = 0;
+ struct plugin_file_handle **newharray = NULL;
+ size_t newcount = *count + 1;
+
+ newharray = realloc (*harray, ((newcount + 1) * sizeof (**harray))); /* +1 for NULL */
+ if (newharray == NULL) {
+ err = ENOMEM;
+ } else {
+ newharray[newcount - 1] = p;
+ newharray[newcount] = NULL;
+ *count = newcount;
+ *harray = newharray;
+ }
+
+ return err;
+}
+
+static void
+krb5int_plugin_file_handle_array_free (struct plugin_file_handle **harray)
+{
+ if (harray != NULL) {
+ int i;
+ for (i = 0; harray[i] != NULL; i++) {
+ krb5int_close_plugin (harray[i]);
+ }
+ free (harray);
+ }
+}
+
+#if TARGET_OS_MAC
+#define FILEEXTS { "", ".bundle", ".dylib", ".so", NULL }
+#elif defined(_WIN32)
+#define FILEEXTS { "", ".dll", NULL }
+#else
+#define FILEEXTS { "", ".so", NULL }
+#endif
+
+
+static void
+krb5int_free_plugin_filenames (char **filenames)
+{
+ if (filenames != NULL) {
+ int i;
+ for (i = 0; filenames[i] != NULL; i++) {
+ free (filenames[i]);
+ }
+ free (filenames);
+ }
+}
+
+
+static long
+krb5int_get_plugin_filenames (const char * const *filebases, char ***filenames)
+{
+ long err = 0;
+ static const char *const fileexts[] = FILEEXTS;
+ char **tempnames = NULL;
+ size_t bases_count = 0;
+ size_t exts_count = 0;
+ size_t i;
+
+ if (!filebases) { err = EINVAL; }
+ if (!filenames) { err = EINVAL; }
+
+ if (!err) {
+ for (i = 0; filebases[i]; i++) { bases_count++; }
+ for (i = 0; fileexts[i]; i++) { exts_count++; }
+ tempnames = calloc ((bases_count * exts_count)+1, sizeof (char *));
+ if (!tempnames) { err = ENOMEM; }
+ }
+
+ if (!err) {
+ size_t j;
+ for (i = 0; !err && filebases[i]; i++) {
+ for (j = 0; !err && fileexts[j]; j++) {
+ if (asprintf(&tempnames[(i*exts_count)+j], "%s%s",
+ filebases[i], fileexts[j]) < 0) {
+ tempnames[(i*exts_count)+j] = NULL;
+ err = ENOMEM;
+ }
+ }
+ }
+ tempnames[bases_count * exts_count] = NULL; /* NUL-terminate */
+ }
+
+ if (!err) {
+ *filenames = tempnames;
+ tempnames = NULL;
+ }
+
+ krb5int_free_plugin_filenames(tempnames);
+
+ return err;
+}
+
+
+/* Takes a NULL-terminated list of directories. If filebases is NULL, filebases is ignored
+ * all plugins in the directories are loaded. If filebases is a NULL-terminated array of names,
+ * only plugins in the directories with those name (plus any platform extension) are loaded. */
+
+long KRB5_CALLCONV
+krb5int_open_plugin_dirs (const char * const *dirnames,
+ const char * const *filebases,
+ struct plugin_dir_handle *dirhandle,
+ struct errinfo *ep)
+{
+ long err = 0;
+ struct plugin_file_handle **h = NULL;
+ size_t count = 0;
+ char **filenames = NULL;
+ int i;
+
+ if (!err) {
+ err = krb5int_plugin_file_handle_array_init (&h);
+ }
+
+ if (!err && (filebases != NULL)) {
+ err = krb5int_get_plugin_filenames (filebases, &filenames);
+ }
+
+ for (i = 0; !err && dirnames[i] != NULL; i++) {
+ if (filenames != NULL) {
+ /* load plugins with names from filenames from each directory */
+ int j;
+
+ for (j = 0; !err && filenames[j] != NULL; j++) {
+ struct plugin_file_handle *handle = NULL;
+ char *filepath = NULL;
+
+ if (!err) {
+ if (asprintf(&filepath, "%s/%s", dirnames[i], filenames[j]) < 0) {
+ filepath = NULL;
+ err = ENOMEM;
+ }
+ }
+
+ if (krb5int_open_plugin (filepath, &handle, ep) == 0) {
+ err = krb5int_plugin_file_handle_array_add (&h, &count, handle);
+ if (!err) { handle = NULL; } /* h takes ownership */
+ }
+
+ free(filepath);
+ if (handle != NULL) { krb5int_close_plugin (handle); }
+ }
+ } else {
+ /* load all plugins in each directory */
+ DIR *dir = opendir (dirnames[i]);
+
+ while (dir != NULL && !err) {
+ struct dirent *d = NULL;
+ char *filepath = NULL;
+ struct plugin_file_handle *handle = NULL;
+
+ d = readdir (dir);
+ if (d == NULL) { break; }
+
+ if ((strcmp (d->d_name, ".") == 0) ||
+ (strcmp (d->d_name, "..") == 0)) {
+ continue;
+ }
+
+ if (!err) {
+ int len = NAMELEN (d);
+ if (asprintf(&filepath, "%s/%*s", dirnames[i], len, d->d_name) < 0) {
+ filepath = NULL;
+ err = ENOMEM;
+ }
+ }
+
+ if (!err) {
+ if (krb5int_open_plugin (filepath, &handle, ep) == 0) {
+ err = krb5int_plugin_file_handle_array_add (&h, &count, handle);
+ if (!err) { handle = NULL; } /* h takes ownership */
+ }
+ }
+
+ free(filepath);
+ if (handle != NULL) { krb5int_close_plugin (handle); }
+ }
+
+ if (dir != NULL) { closedir (dir); }
+ }
+ }
+
+ if (err == ENOENT) {
+ err = 0; /* ran out of plugins -- do nothing */
+ }
+
+ if (!err) {
+ dirhandle->files = h;
+ h = NULL; /* dirhandle->files takes ownership */
+ }
+
+ if (filenames != NULL) { krb5int_free_plugin_filenames (filenames); }
+ if (h != NULL) { krb5int_plugin_file_handle_array_free (h); }
+
+ return err;
+}
+
+void KRB5_CALLCONV
+krb5int_close_plugin_dirs (struct plugin_dir_handle *dirhandle)
+{
+ if (dirhandle->files != NULL) {
+ int i;
+ for (i = 0; dirhandle->files[i] != NULL; i++) {
+ krb5int_close_plugin (dirhandle->files[i]);
+ }
+ free (dirhandle->files);
+ dirhandle->files = NULL;
+ }
+}
+
+void KRB5_CALLCONV
+krb5int_free_plugin_dir_data (void **ptrs)
+{
+ /* Nothing special to be done per pointer. */
+ free(ptrs);
+}
+
+long KRB5_CALLCONV
+krb5int_get_plugin_dir_data (struct plugin_dir_handle *dirhandle,
+ const char *symname,
+ void ***ptrs,
+ struct errinfo *ep)
+{
+ long err = 0;
+ void **p = NULL;
+ size_t count = 0;
+
+ /* XXX Do we need to add a leading "_" to the symbol name on any
+ modern platforms? */
+
+ Tprintf("get_plugin_data_sym(%s)\n", symname);
+
+ if (!err) {
+ p = calloc (1, sizeof (*p)); /* calloc initializes to NULL */
+ if (p == NULL) { err = ENOMEM; }
+ }
+
+ if (!err && (dirhandle != NULL) && (dirhandle->files != NULL)) {
+ int i = 0;
+
+ for (i = 0; !err && (dirhandle->files[i] != NULL); i++) {
+ void *sym = NULL;
+
+ if (krb5int_get_plugin_data (dirhandle->files[i], symname, &sym, ep) == 0) {
+ void **newp = NULL;
+
+ count++;
+ newp = realloc (p, ((count + 1) * sizeof (*p))); /* +1 for NULL */
+ if (newp == NULL) {
+ err = ENOMEM;
+ } else {
+ p = newp;
+ p[count - 1] = sym;
+ p[count] = NULL;
+ }
+ }
+ }
+ }
+
+ if (!err) {
+ *ptrs = p;
+ p = NULL; /* ptrs takes ownership */
+ }
+
+ free(p);
+
+ return err;
+}
+
+void KRB5_CALLCONV
+krb5int_free_plugin_dir_func (void (**ptrs)(void))
+{
+ /* Nothing special to be done per pointer. */
+ free(ptrs);
+}
+
+long KRB5_CALLCONV
+krb5int_get_plugin_dir_func (struct plugin_dir_handle *dirhandle,
+ const char *symname,
+ void (***ptrs)(void),
+ struct errinfo *ep)
+{
+ long err = 0;
+ void (**p)() = NULL;
+ size_t count = 0;
+
+ /* XXX Do we need to add a leading "_" to the symbol name on any
+ modern platforms? */
+
+ Tprintf("get_plugin_data_sym(%s)\n", symname);
+
+ if (!err) {
+ p = calloc (1, sizeof (*p)); /* calloc initializes to NULL */
+ if (p == NULL) { err = ENOMEM; }
+ }
+
+ if (!err && (dirhandle != NULL) && (dirhandle->files != NULL)) {
+ int i = 0;
+
+ for (i = 0; !err && (dirhandle->files[i] != NULL); i++) {
+ void (*sym)() = NULL;
+
+ if (krb5int_get_plugin_func (dirhandle->files[i], symname, &sym, ep) == 0) {
+ void (**newp)() = NULL;
+
+ count++;
+ newp = realloc (p, ((count + 1) * sizeof (*p))); /* +1 for NULL */
+ if (newp == NULL) {
+ err = ENOMEM;
+ } else {
+ p = newp;
+ p[count - 1] = sym;
+ p[count] = NULL;
+ }
+ }
+ }
+ }
+
+ if (!err) {
+ *ptrs = p;
+ p = NULL; /* ptrs takes ownership */
+ }
+
+ free(p);
+
+ return err;
+}