Skip to content
Snippets Groups Projects
require.c 47 KiB
Newer Older
 * ld - load code dynamically
 *
 * $Author: zimoch $
 * $ID$
 * $Date: 2015/06/29 09:47:30 $
 *
 * DISCLAIMER: Use at your own risc and so on. No warranty, no refund.
 */

#ifdef __unix
/* for vasprintf and dl_iterate_phdr */
Dirk Zimoch's avatar
Dirk Zimoch committed
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#endif

/* for 64 bit (NFS) file systems */
#define _FILE_OFFSET_BITS 64
#include <ctype.h>
Anders Lindh Olsson's avatar
Anders Lindh Olsson committed
#include <epicsVersion.h>
Dirk Zimoch's avatar
Dirk Zimoch committed
#include <errno.h>
#include <initHooks.h>
#include <osiFileName.h>
Anders Lindh Olsson's avatar
Anders Lindh Olsson committed
#include <recSup.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
Dirk Zimoch's avatar
Dirk Zimoch committed

#include <dbAccess.h>
Anders Lindh Olsson's avatar
Anders Lindh Olsson committed
#include <iocsh.h>
Dirk Zimoch's avatar
Dirk Zimoch committed
/* This prototype is missing in older EPICS versions */
epicsShareFunc int epicsShareAPI iocshCmd(const char *cmd);
#include <epicsExit.h>
Anders Lindh Olsson's avatar
Anders Lindh Olsson committed
#include <epicsExport.h>
#include <epicsStdio.h>
Dirk Zimoch's avatar
Dirk Zimoch committed
#include <osiFileName.h>

#include "require.h"
Anders Lindh Olsson's avatar
Anders Lindh Olsson committed
#include "version.h"
#if defined(__unix)
#ifndef OS_CLASS
#ifdef __linux
#define OS_CLASS "Linux"
#endif

#ifdef SOLARIS
#define OS_CLASS "solaris"
#endif

#ifdef __rtems__
#define OS_CLASS "RTEMS"
#endif

#ifdef CYGWIN32
#define OS_CLASS "cygwin32"
#endif

#ifdef freebsd
#define OS_CLASS "freebsd"
#endif

#ifdef darwin
#define OS_CLASS "Darwin"
#endif

#ifdef _AIX32
#define OS_CLASS "AIX"
#endif
#endif

#include <dlfcn.h>
#define HMODULE void *

#define getAddress(module, name) dlsym(module, name)

#ifdef CYGWIN32
#define PREFIX
#define INFIX
#define EXT ".dll"
#else
#define PREFIX "lib"
#define INFIX
#define EXT ".so"
#endif

#elif defined(_WIN32)

#ifndef OS_CLASS
#define OS_CLASS "WIN32"
#endif

#include <Psapi.h>
Anders Lindh Olsson's avatar
Anders Lindh Olsson committed
#include <windows.h>
#pragma comment(lib, "kernel32.lib")
#pragma comment(lib, "psapi.lib")
#include "asprintf.h"
#define snprintf _snprintf
#define setenv(name, value, overwrite) _putenv_s(name, value)
#define PATH_MAX MAX_PATH

#define PREFIX
#define INFIX
#define EXT ".dll"

#define getAddress(module, name) GetProcAddress(module, name)

Anders Lindh Olsson's avatar
Anders Lindh Olsson committed
static char *realpath(const char *path, char *buffer) {
  int len = MAX_PATH;
  if (buffer == NULL) {
    len = GetFullPathName(path, 0, NULL, NULL);
    if (len == 0)
      return NULL;
    buffer = malloc(len);
Anders Lindh Olsson's avatar
Anders Lindh Olsson committed
      return NULL;
  }
  GetFullPathName(path, len, buffer, NULL);
  return buffer;
#warning unknown OS
#define PREFIX
#define INFIX
#define EXT
#define getAddress(module, name) NULL
/* for readdir: Windows or Posix */
#if defined(_WIN32)
#define DIR_HANDLE HANDLE
#define DIR_ENTRY WIN32_FIND_DATA
Anders Lindh Olsson's avatar
Anders Lindh Olsson committed
#define IF_OPEN_DIR(f)                                                         \
  if (snprintf(f + modulediroffs, sizeof(f) - modulediroffs, "\\*.*"),         \
      (dir = FindFirstFile(filename, &direntry)) != INVALID_HANDLE_VALUE ||    \
          (FindClose(dir), 0))
#define START_DIR_LOOP do
Anders Lindh Olsson's avatar
Anders Lindh Olsson committed
#define END_DIR_LOOP                                                           \
  while (FindNextFile(dir, &direntry))                                         \
    ;                                                                          \
  FindClose(dir);
#define SKIP_NON_DIR(e)                                                        \
  if (!(e.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ||                      \
      (e.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN))                            \
    continue;
#define FILENAME(e) e.cFileName
#include <dirent.h>
#define DIR_HANDLE DIR *
#define IF_OPEN_DIR(f) if ((dir = opendir(f)))
#define DIR_ENTRY struct dirent *
#define START_DIR_LOOP while ((errno = 0, direntry = readdir(dir)) != NULL)
Anders Lindh Olsson's avatar
Anders Lindh Olsson committed
#define END_DIR_LOOP                                                           \
  if (!direntry && errno)                                                      \
    fprintf(stderr, "error reading directory %s: %s\n", filename,              \
            strerror(errno));                                                  \
  if (dir)                                                                     \
    closedir(dir);
#ifdef _DIRENT_HAVE_D_TYPE
Anders Lindh Olsson's avatar
Anders Lindh Olsson committed
#define SKIP_NON_DIR(e)                                                        \
  if (e->d_type != DT_DIR && e->d_type != DT_UNKNOWN)                          \
    continue;
#else
#define SKIP_NON_DIR(e)
#define FILENAME(e) e->d_name
Dirk Zimoch's avatar
Dirk Zimoch committed
#define LIBDIR "lib" OSI_PATH_SEPARATOR
Dirk Zimoch's avatar
Dirk Zimoch committed
#define TEMPLATEDIR "db"
Jeong Han Lee's avatar
Jeong Han Lee committed
#define TOSTR(s) TOSTR2(s)
#define TOSTR2(s) #s
Anders Lindh Olsson's avatar
Anders Lindh Olsson committed
const char epicsRelease[] =
TOSTR(EPICS_VERSION)"."TOSTR(EPICS_REVISION)"."TOSTR(EPICS_MODIFICATION);
Jeong Han Lee's avatar
Jeong Han Lee committed
#ifndef T_A
#error T_A not defined: Compile with USR_CFLAGS += -DT_A='"${T_A}"'
#endif
const char targetArch[] = T_A;
      */

#ifndef OS_CLASS
#error OS_CLASS not defined: Try to compile with USR_CFLAGS += -DOS_CLASS='"${OS_CLASS}"'
#endif
Dirk Zimoch's avatar
Dirk Zimoch committed
const char osClass[] = OS_CLASS;
/* loadlib (library)
Find a loadable library by name and load it.
*/

char epicsRelease[80];
char *targetArch;

Anders Lindh Olsson's avatar
Anders Lindh Olsson committed
void set_require_env() {
  char *epics_version_major = getenv("EPICS_VERSION_MAJOR");
  char *epics_version_middle = getenv("EPICS_VERSION_MIDDLE");
  char *epics_version_minor = getenv("EPICS_VERSION_MINOR");
Anders Lindh Olsson's avatar
Anders Lindh Olsson committed
  sprintf(epicsRelease, "%s.%s.%s", epics_version_major, epics_version_middle,
          epics_version_minor);
  targetArch = getenv("EPICS_HOST_ARCH");
  return;
Anders Lindh Olsson's avatar
Anders Lindh Olsson committed
static HMODULE loadlib(const char *libname) {
  HMODULE libhandle = NULL;
Anders Lindh Olsson's avatar
Anders Lindh Olsson committed
  if (libname == NULL) {
    fprintf(stderr, "missing library name\n");
    return NULL;
  }
Anders Lindh Olsson's avatar
Anders Lindh Olsson committed
  if ((libhandle = dlopen(libname, RTLD_NOW | RTLD_GLOBAL)) == NULL) {
    fprintf(stderr, "Loading %s library failed: %s\n", libname, dlerror());
  }
#elif defined(_WIN32)
Anders Lindh Olsson's avatar
Anders Lindh Olsson committed
  if ((libhandle = LoadLibrary(libname)) == NULL) {
    LPVOID lpMsgBuf;

    FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
                  NULL, GetLastError(),
                  MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&lpMsgBuf,
                  0, NULL);
    fprintf(stderr, "Loading %s library failed: %s\n", libname, lpMsgBuf);
    LocalFree(lpMsgBuf);
  }
Anders Lindh Olsson's avatar
Anders Lindh Olsson committed
  fprintf(stderr, "cannot load libraries on this OS.\n");
Anders Lindh Olsson's avatar
Anders Lindh Olsson committed
  return libhandle;
Anders Lindh Olsson's avatar
Anders Lindh Olsson committed
typedef struct moduleitem {
  struct moduleitem *next;
  char content[0];
} moduleitem;

static moduleitem *loadedModules = NULL;
static unsigned long moduleCount = 0;
static unsigned long moduleListBufferSize = 1;
static unsigned long maxModuleNameLength = 0;
Anders Lindh Olsson's avatar
Anders Lindh Olsson committed
int putenvprintf(const char *format, ...) {
  va_list ap;
  char *var;
  char *val;
  int status = 0;

  if (!format)
    return -1;
  va_start(ap, format);
  if (vasprintf(&var, format, ap) < 0) {
    perror("require putenvprintf");
    return errno;
  }
  va_end(ap);

  if (requireDebug)
    printf("require: putenv(\"%s\")\n", var);

  val = strchr(var, '=');
  if (!val) {
    fprintf(stderr, "putenvprintf: string contains no =: %s\n", var);
    status = -1;
  } else {
    *val++ = 0;
    if (setenv(var, val, 1) != 0) {
      perror("require putenvprintf: setenv failed");
      status = errno;
Anders Lindh Olsson's avatar
Anders Lindh Olsson committed
  }
  free(var);
  return status;
Anders Lindh Olsson's avatar
Anders Lindh Olsson committed
void pathAdd(const char *varname, const char *dirname) {
  char *old_path;
Anders Lindh Olsson's avatar
Anders Lindh Olsson committed
  if (!varname || !dirname) {
    fprintf(stderr, "usage: pathAdd \"ENVIRONMENT_VARIABLE\",\"directory\"\n");
    fprintf(stderr, "       Adds or moves the directory to the front of the "
                    "ENVIRONMENT_VARIABLE\n");
    fprintf(stderr, "       but after a leading \".\".\n");
    return;
  }

  /* add directory to front */
  old_path = getenv(varname);
  if (old_path == NULL)
    putenvprintf("%s=." OSI_PATH_LIST_SEPARATOR "%s", varname, dirname);
  else {
    size_t len = strlen(dirname);
    char *p;
Anders Lindh Olsson's avatar
Anders Lindh Olsson committed
    /* skip over "." at the beginning */
    if (old_path[0] == '.' && old_path[1] == OSI_PATH_LIST_SEPARATOR[0])
      old_path += 2;

    /* If directory is already in path, move it to front */
    p = old_path;
    while ((p = strstr(p, dirname)) != NULL) {
      if ((p == old_path || *(p - 1) == OSI_PATH_LIST_SEPARATOR[0]) &&
          (p[len] == 0 || p[len] == OSI_PATH_LIST_SEPARATOR[0])) {
        if (p == old_path)
          break; /* already at front, nothing to do */
        memmove(old_path + len + 1, old_path, p - old_path - 1);
        strcpy(old_path, dirname);
        old_path[len] = OSI_PATH_LIST_SEPARATOR[0];
Anders Lindh Olsson's avatar
Anders Lindh Olsson committed
          printf("require: modified %s=%s\n", varname, old_path);
        break;
      }
      p += len;
Anders Lindh Olsson's avatar
Anders Lindh Olsson committed
    if (p == NULL)
      /* add new directory to the front (after "." )*/
      putenvprintf("%s=." OSI_PATH_LIST_SEPARATOR "%s" OSI_PATH_LIST_SEPARATOR
                   "%s",
                   varname, dirname, old_path);
  }
Anders Lindh Olsson's avatar
Anders Lindh Olsson committed
char *realpathSeparator(const char *location) {
  size_t ll;
  char *buffer = malloc(PATH_MAX + strlen(OSI_PATH_SEPARATOR));
  buffer = realpath(location, buffer);
  if (!buffer) {
    if (requireDebug)
Anders Lindh Olsson's avatar
Anders Lindh Olsson committed
      printf("require: realpath(%s) failed\n", location);
    return NULL;
  }
  ll = strlen(buffer);
  /* linux realpath removes trailing slash */
  if (buffer[ll - strlen(OSI_PATH_SEPARATOR)] != OSI_PATH_SEPARATOR[0]) {
    strcpy(buffer + ll + 1 - strlen(OSI_PATH_SEPARATOR), OSI_PATH_SEPARATOR);
  }
  return buffer;
int isModuleLoaded(const char *libname);

Anders Lindh Olsson's avatar
Anders Lindh Olsson committed
static int setupDbPath(const char *module, const char *dbdir) {
  char *absdir =
      realpathSeparator(dbdir); /* so we can change directory later safely */
  if (absdir == NULL) {
    if (requireDebug)
      printf("require: cannot resolve %s\n", dbdir);
    return -1;
  }

  if (requireDebug)
    printf("require: found template directory %s\n", absdir);

  /* set up db search path environment variables
    <module>_DB             template path of <module>
    TEMPLATES               template path of the current module (overwritten)
    EPICS_DB_INCLUDE_PATH   template path of all loaded modules (last in front
    after ".")
  */

  putenvprintf("%s_DB=%s", module, absdir);
  putenvprintf("TEMPLATES=%s", absdir);
  if (isModuleLoaded("stream")) {
    pathAdd("STREAM_PROTOCOL_PATH", absdir);
  }
Anders Lindh Olsson's avatar
Anders Lindh Olsson committed
  pathAdd("EPICS_DB_INCLUDE_PATH", absdir);
  free(absdir);
  return 0;
}
Anders Lindh Olsson's avatar
Anders Lindh Olsson committed
static int getRecordHandle(const char *namepart, short type, long minsize,
                           DBADDR *paddr) {
  /*
  #define PVNAME_STRINGSZ 61
  defined in EPICS_BASE/include/dbDefs.h
 */
  char recordname[PVNAME_STRINGSZ] = "";
  long dummy = 0L;
  long offset = 0L;

  sprintf(recordname, "%.*s%s", (int)(PVNAME_STRINGSZ - strlen(namepart) - 1),
          getenv("REQUIRE_IOC"), namepart);

  if (dbNameToAddr(recordname, paddr) != 0) {
    fprintf(stderr, "require:getRecordHandle : record %s not found\n",
            recordname);
    return -1;
  }
  if (paddr->field_type != type) {
    fprintf(
        stderr,
        "require:getRecordHandle : record %s has wrong type %s instead of %s\n",
        recordname, pamapdbfType[paddr->field_type].strvalue,
        pamapdbfType[type].strvalue);
    return -1;
  }
  if (paddr->no_elements < minsize) {
    fprintf(stderr,
            "require:getRecordHandle : record %s has not enough elements: %lu "
            "instead of %lu\n",
            recordname, paddr->no_elements, minsize);
    return -1;
  }
  if (paddr->pfield == NULL) {
    fprintf(
        stderr,
        "require:getRecordHandle : record %s has not yet allocated memory\n",
        recordname);
    return -1;
  }

  /* update array information */
  dbGetRset(paddr)->get_array_info(paddr, &dummy, &offset);

  return 0;
Anders Lindh Olsson's avatar
Anders Lindh Olsson committed
We can fill the records only after they have been initialized, at
initHookAfterFinishDevSup. But use double indirection here because in 3.13 we
must wait until initHooks is loaded before we can register the hook.
Anders Lindh Olsson's avatar
Anders Lindh Olsson committed
static void fillModuleListRecord(initHookState state) {
  if (state == initHookAfterFinishDevSup) /* MODULES record exists and has
                                             allocated memory */
  {
    DBADDR modules, versions, modver;
    int have_modules, have_versions, have_modver;
    moduleitem *m;
    int i = 0;
    long c = 0;
Anders Lindh Olsson's avatar
Anders Lindh Olsson committed
    if (requireDebug)
      printf("require: fillModuleListRecord\n");
Anders Lindh Olsson's avatar
Anders Lindh Olsson committed
    have_modules =
        (getRecordHandle(":MODULES", DBF_STRING, moduleCount, &modules) == 0);
    have_versions =
        (getRecordHandle(":VERSIONS", DBF_STRING, moduleCount, &versions) == 0);
Anders Lindh Olsson's avatar
Anders Lindh Olsson committed
    moduleListBufferSize += moduleCount * maxModuleNameLength;
    have_modver = (getRecordHandle(":MOD_VER", DBF_CHAR, moduleListBufferSize,
                                   &modver) == 0);
Anders Lindh Olsson's avatar
Anders Lindh Olsson committed
    for (m = loadedModules, i = 0; m; m = m->next, i++) {
      size_t lm = strlen(m->content) + 1;
      if (have_modules) {
        if (requireDebug)
          printf("require: %s[%d] = \"%.*s\"\n", modules.precord->name, i,
                 MAX_STRING_SIZE - 1, m->content);
        sprintf((char *)(modules.pfield) + i * MAX_STRING_SIZE, "%.*s",
                MAX_STRING_SIZE - 1, m->content);
      }
      if (have_versions) {
        if (requireDebug)
          printf("require: %s[%d] = \"%.*s\"\n", versions.precord->name, i,
                 MAX_STRING_SIZE - 1, m->content + lm);
        sprintf((char *)(versions.pfield) + i * MAX_STRING_SIZE, "%.*s",
                MAX_STRING_SIZE - 1, m->content + lm);
      }
      if (have_modver) {
        if (requireDebug)
          printf("require: %s+=\"%-*s%s\"\n", modver.precord->name,
                 (int)maxModuleNameLength, m->content, m->content + lm);
        c += sprintf((char *)(modver.pfield) + c, "%-*s%s\n",
                     (int)maxModuleNameLength, m->content, m->content + lm);
      }
Anders Lindh Olsson's avatar
Anders Lindh Olsson committed
    if (have_modules)
      dbGetRset(&modules)->put_array_info(&modules, i);
    if (have_versions)
      dbGetRset(&versions)->put_array_info(&versions, i);
    if (have_modver)
      dbGetRset(&modver)->put_array_info(&modver, c + 1);
  }
Anders Lindh Olsson's avatar
Anders Lindh Olsson committed
void registerModule(const char *module, const char *version,
                    const char *location) {
  moduleitem *m, **pm;
  size_t lm = strlen(module) + 1;
  size_t lv = (version ? strlen(version) : 0) + 1;
  size_t ll = 1;
  char *absLocation = NULL;
  char *absLocationRequire = NULL;
  char *argstring = NULL;
  const char *mylocation;
  static int firstTime = 1;

  if (requireDebug)
    printf("require: registerModule(%s,%s,%s)\n", module, version, location);

  if (firstTime) {
    initHookRegister(fillModuleListRecord);
Dirk Zimoch's avatar
Dirk Zimoch committed
    if (requireDebug)
Anders Lindh Olsson's avatar
Anders Lindh Olsson committed
      printf("require: initHookRegister\n");
    firstTime = 0;
  }

  if (!version)
    version = "";

  if (location) {
    absLocation = realpathSeparator(location);
    ll = strlen(absLocation) + 1;
  }
  m = (moduleitem *)malloc(sizeof(moduleitem) + lm + lv + ll);
  if (m == NULL) {
    fprintf(stderr, "require: out of memory\n");
    return;
  }
Anders Lindh Olsson's avatar
Anders Lindh Olsson committed
  m->next = NULL;
Anders Lindh Olsson's avatar
Anders Lindh Olsson committed
  strcpy(m->content, module);
  strcpy(m->content + lm, version);
  strcpy(m->content + lm + lv, absLocation ? absLocation : "");
Anders Lindh Olsson's avatar
Anders Lindh Olsson committed
  free(absLocation);
  for (pm = &loadedModules; *pm != NULL; pm = &(*pm)->next)
    ;
  *pm = m;
  if (lm > maxModuleNameLength)
    maxModuleNameLength = lm;
  moduleListBufferSize += lv;
  moduleCount++;

  putenvprintf("MODULE=%s", module);
  putenvprintf("%s_VERSION=%s", module, version);
  if (location) {
    putenvprintf("%s_DIR=%s", module, m->content + lm + lv);
    pathAdd("SCRIPT_PATH", m->content + lm + lv);
  }

  /* only do registration register stuff at init */
  if (interruptAccept)
    return;
Anders Lindh Olsson's avatar
Anders Lindh Olsson committed
  /* create a record with the version string */
  mylocation = getenv("require_DIR");
  if (mylocation == NULL)
    return;
  if (asprintf(&absLocationRequire,
               "%s" OSI_PATH_SEPARATOR "db" OSI_PATH_SEPARATOR
               "moduleversion.template",
               mylocation) < 0)
    return;
  /*
     Require DB has the following four PVs:
     - $(REQUIRE_IOC):$(MODULE)_VER
     - $(REQUIRE_IOC):MOD_VER
     - $(REQUIRE_IOC):VERSIONS
     - $(REQUIRE_IOC):MODULES
     We reserved 30 chars for :$(MODULE)_VER, so MODULE has the maximum 24
     chars. And we've reserved for 30 chars for $(REQUIRE_IOC). So, the whole PV
     and record name in moduleversion.template has 59 + 1.
   */
  if (asprintf(&argstring,
               "REQUIRE_IOC=%.30s, MODULE=%.24s, VERSION=%.39s, "
               "MODULE_COUNT=%lu, BUFFER_SIZE=%lu",
               getenv("REQUIRE_IOC"), module, version, moduleCount,
               moduleListBufferSize + maxModuleNameLength * moduleCount) < 0)
    return;
  printf("Loading module info records for %s\n", module);
  dbLoadRecords(absLocationRequire, argstring);
  free(argstring);
  free(absLocationRequire);
#if defined(__linux)
Dirk Zimoch's avatar
Dirk Zimoch committed
/* This is the Linux link.h, not the EPICS link.h ! */
#include <link.h>

Anders Lindh Olsson's avatar
Anders Lindh Olsson committed
static int findLibRelease(struct dl_phdr_info *info, /* shared library info */
                          size_t size, /* size of info structure */
                          void *data   /* user-supplied arg */
) {
  void *handle;
  char *location = NULL;
  char *p;
  char *version;
  char *symname;
  char name[PATH_MAX + 11]; /* get space for library path + "LibRelease" */

  (void)data; /* unused */
  if (size < sizeof(struct dl_phdr_info))
    return 0; /* wrong version of struct dl_phdr_info */
  /* find a symbol with a name like "_<module>LibRelease"
     where <module> is from the library name "<location>/lib<module>.so" */
  if (info->dlpi_name == NULL || info->dlpi_name[0] == 0)
    return 0;                    /* no library name */
  strcpy(name, info->dlpi_name); /* get a modifiable copy of the library name */
  handle =
      dlopen(info->dlpi_name, RTLD_LAZY); /* re-open already loaded library */
  p = strrchr(name,
              '/'); /* find file name part in "<location>/lib<module>.so" */
  if (p) {
    location = name;
    *++p = 0;
  } else
    p = name;               /* terminate "<location>/" (if exists) */
  *(symname = p + 2) = '_'; /* replace "lib" with "_" */
  p = strchr(symname, '.'); /* find ".so" extension */
  if (p == NULL)
    p = symname + strlen(symname);  /* no file extension ? */
  strcpy(p, "LibRelease");          /* append "LibRelease" to module name */
  version = dlsym(handle, symname); /* find symbol "_<module>LibRelease" */
  if (version) {
    *p = 0;
    symname++; /* get "<module>" from "_<module>LibRelease" */
    if ((p = strstr(name, "/" LIBDIR)) != NULL)
      p[1] = 0; /* cut "<location>" before LIBDIR */
    if (getLibVersion(symname) == NULL)
      registerModule(symname, version, location);
  }
  dlclose(handle);
  return 0;
Anders Lindh Olsson's avatar
Anders Lindh Olsson committed
static void registerExternalModules() {
  /* iterate over all loaded libraries */
  dl_iterate_phdr(findLibRelease, NULL);
#elif defined(_WIN32)
Anders Lindh Olsson's avatar
Anders Lindh Olsson committed
static void registerExternalModules() {
  HMODULE hMods[100];
  HANDLE hProcess = GetCurrentProcess();
  DWORD cbNeeded;
  char *location = NULL;
  char *p;
  char *version;
  char *symname;
  unsigned int i;
  char name[MAX_PATH + 11]; /* get space for library path + "LibRelease" */

  /* iterate over all loaded libraries */
  if (!EnumProcessModules(hProcess, hMods, sizeof(hMods), &cbNeeded))
    return;
  for (i = 0; i < (cbNeeded / sizeof(HMODULE)); i++) {
    /* Get the full path to the module's file. */
    if (!GetModuleFileName(hMods[i], name, MAX_PATH))
      continue;                 /* no library name */
    name[sizeof(name) - 1] = 0; /* WinXP may not terminate the string */
    p = strrchr(name,
                '\\'); /* find file name part in "<location>/<module>.dll" */
    if (p) {
      location = name;
    } else
      p = name; /* find end of "<location>\\" (if exists) */
    symname = p;
    p = strchr(symname, '.'); /* find ".dll" */
    if (p == NULL)
      p = symname + strlen(symname);            /* no file extension ? */
    memmove(symname + 2, symname, p - symname); /* make room for 0 and '_' */
    *symname++ = 0;                             /* terminate "<location>/" */
    *symname = '_';                 /* prefix module name with '_' */
    strcpy((p += 2), "LibRelease"); /* append "LibRelease" to module name */

    version = (char *)GetProcAddress(
        hMods[i], symname); /* find symbol "_<module>LibRelease" */
    if (version) {
      *p = 0;
      symname++; /* get "<module>" from "_<module>LibRelease" */
      if ((p = strstr(name, "\\" LIBDIR)) != NULL)
        p[1] = 0; /* cut "<location>" before LIBDIR */
      if (getLibVersion(symname) == NULL)
        registerModule(symname, version, location);
Anders Lindh Olsson's avatar
Anders Lindh Olsson committed
static void registerExternalModules() { ; }
Anders Lindh Olsson's avatar
Anders Lindh Olsson committed
size_t foreachLoadedLib(size_t (*func)(const char *name, const char *version,
                                       const char *path, void *arg),
                        void *arg) {
  moduleitem *m;
  int result;

  for (m = loadedModules; m; m = m->next) {
    const char *name = m->content;
    const char *version = name + strlen(name) + 1;
    const char *path = version + strlen(version) + 1;
    result = func(name, version, path, arg);
    if (result)
      return result;
  }
  return 0;
Anders Lindh Olsson's avatar
Anders Lindh Olsson committed
const char *getLibVersion(const char *libname) {
  moduleitem *m;
Anders Lindh Olsson's avatar
Anders Lindh Olsson committed
  for (m = loadedModules; m; m = m->next) {
    if (strcmp(m->content, libname) == 0) {
      return m->content + strlen(m->content) + 1;
Anders Lindh Olsson's avatar
Anders Lindh Olsson committed
  }
  return NULL;
Anders Lindh Olsson's avatar
Anders Lindh Olsson committed
const char *getLibLocation(const char *libname) {
  moduleitem *m;
  char *v;
Anders Lindh Olsson's avatar
Anders Lindh Olsson committed
  for (m = loadedModules; m; m = m->next) {
    if (strcmp(m->content, libname) == 0) {
      v = m->content + strlen(m->content) + 1;
      return v + strlen(v) + 1;
Anders Lindh Olsson's avatar
Anders Lindh Olsson committed
  }
  return NULL;
int isModuleLoaded(const char *libname) {
  moduleitem *m;

  for (m = loadedModules; m; m = m->next) {
    if (strcmp(m->content, libname) == 0)
      return TRUE;
  }
  return FALSE;
}

Anders Lindh Olsson's avatar
Anders Lindh Olsson committed
int libversionShow(const char *outfile) {
  moduleitem *m;
  size_t lm, lv;
Anders Lindh Olsson's avatar
Anders Lindh Olsson committed
  FILE *out = epicsGetStdout();
Anders Lindh Olsson's avatar
Anders Lindh Olsson committed
  if (outfile) {
    out = fopen(outfile, "w");
    if (out == NULL) {
      fprintf(stderr, "can't open %s: %s\n", outfile, strerror(errno));
      return -1;
Dirk Zimoch's avatar
Dirk Zimoch committed
    }
Anders Lindh Olsson's avatar
Anders Lindh Olsson committed
  }
  for (m = loadedModules; m; m = m->next) {
    lm = strlen(m->content) + 1;
    lv = strlen(m->content + lm) + 1;
    fprintf(out, "%-*s%-20s %s\n", (int)maxModuleNameLength, m->content,
            m->content + lm, m->content + lm + lv);
  }
  if (fflush(out) < 0 && outfile) {
    fprintf(stderr, "can't write to %s: %s\n", outfile, strerror(errno));
    return -1;
  }
  if (outfile)
    fclose(out);
  return 0;
#define MISMATCH -1
#define EXACT 0
#define MATCH 1
Anders Lindh Olsson's avatar
Anders Lindh Olsson committed
#define debug(...)                                                             \
  if (requireDebug)                                                            \
  printf(__VA_ARGS__)

static int compareDigit(int found, int requested, const char *name) {
  debug("require: compareDigit: found %d, requested %d for digit %s\n", found,
        requested, name);
  if (found < requested) {
    debug("require: compareVersions: MISMATCH too low %s number\n", name);
    return MISMATCH;
  }
  if (found > requested) {
    debug("require: compareVersions: HIGHER %s number\n", name);
    return HIGHER;
  }

  return MATCH;
Anders Lindh Olsson's avatar
Anders Lindh Olsson committed
static int compareNumericVersion(semver_t *sv_found, semver_t *sv_request,
                                 int already_matched) {
  int match;
Anders Lindh Olsson's avatar
Anders Lindh Olsson committed
  match = compareDigit(sv_found->major, sv_request->major, "major");
  if (match != MATCH) {
    return match;
  }
  match = compareDigit(sv_found->minor, sv_request->minor, "minor");
  if (match != MATCH) {
    return match;
  }
  match = compareDigit(sv_found->patch, sv_request->patch, "patch");
  if (match != MATCH) {
    return match;
  }

  if (sv_request->build == -1) {
    if (already_matched) {
      debug("require: compareVersions: No build number requested. Returning "
            "HIGHER\n");
      return HIGHER;
    } else {
      debug("require: compareVersions: No build number requested. Returning "
            "MATCH\n");
      return MATCH;
Anders Lindh Olsson's avatar
Anders Lindh Olsson committed
  }
  return compareDigit(sv_found->build, sv_request->build, "build");
/*
 * Returns if the version <found> is higher than <request>.
 */
Anders Lindh Olsson's avatar
Anders Lindh Olsson committed
static int compareVersions(const char *found, const char *request,
                           int already_matched) {
  semver_t *sv_found, *sv_request;
  int match;
Anders Lindh Olsson's avatar
Anders Lindh Olsson committed
  debug("require: compareVersions(found=%s, request=%s)\n", found, request);
Anders Lindh Olsson's avatar
Anders Lindh Olsson committed
  if (request == NULL || request[0] == 0) {
    debug("require: compareVersions: MATCH empty version requested\n");
    return MATCH;
  }
  if (found == NULL || found[0] == 0) {
    debug("require: compareVersions: MISMATCH empty version found\n");
    return MISMATCH;
  }

  sv_found = (semver_t *)calloc(1, sizeof(semver_t));
  sv_request = (semver_t *)calloc(1, sizeof(semver_t));

  parse_semver(found, sv_found);
  parse_semver(request, sv_request);

  // test version, look for exact.
  if (strlen(sv_request->test_str) > 0) {
    if (strcmp(found, request) == 0) {
      debug("require: compareVersions: Test version requested and found, "
            "matches exactly\n");
      match = EXACT;
    } else if (strlen(sv_found->test_str) > 0) {
      debug("require: compareVersions: Test versions requested and found, no "
            "match\n");
      match = MISMATCH;
    } else {
      debug("require: compareVersions: found numeric version, higher than "
            "test\n");
      match = HIGHER;
Anders Lindh Olsson's avatar
Anders Lindh Olsson committed
  } else if (strlen(sv_found->test_str) > 0) {
    debug("require: compareVersions: Numeric version requested, test version "
          "found\n");
    match = MISMATCH;
  } else {
    match = compareNumericVersion(sv_found, sv_request, already_matched);
  }
  cleanup_semver(sv_found);
  cleanup_semver(sv_request);
  return match;
/* require (module)
Look if module is already loaded.
If module is already loaded check for version mismatch.
If module is not yet loaded load the library with ld,
load <module>.dbd with dbLoadDatabase (if file exists)
and call <module>_registerRecordDeviceDriver function.

If require is called from the iocsh before iocInit and fails,
it calls epicsExit to abort the application.
*/

/* wrapper to abort statup script */
Anders Lindh Olsson's avatar
Anders Lindh Olsson committed
static int require_priv(const char *module, const char *version,
                        const char *args, const char *versionstr);

int require(const char *module, const char *version, const char *args) {
  int status;
  char *versionstr;
  static int firstTime = 1;

  if (firstTime) {
    firstTime = 0;

    set_require_env();

    putenvprintf("T_A=%s", targetArch);
    putenvprintf("EPICS_HOST_ARCH=%s", targetArch);
    putenvprintf("EPICS_RELEASE=%s", epicsRelease);
    putenvprintf("OS_CLASS=%s", osClass);
  }

  if (module == NULL) {
    printf("Usage: require \"<module>\" [, \"<version>\" | \"ifexists\"] [, "
           "\"<args>\"]\n");
    printf("Loads " PREFIX "<module>" INFIX EXT " and <libname>.dbd\n");
    printf("And calls <module>_registerRecordDeviceDriver\n");
    printf("If available, runs startup script snippet (only before iocInit)\n");
    return -1;
  }

  /* either order for version and args, either may be empty or NULL */
  if (version && strchr(version, '=')) {
    const char *v = version;
    version = args;
    args = v;
    if (requireDebug)
      printf("require: swap version and args\n");
  }
Anders Lindh Olsson's avatar
Anders Lindh Olsson committed
  if (version && version[0] == 0)
    version = NULL;
Anders Lindh Olsson's avatar
Anders Lindh Olsson committed
  if (version && strcmp(version, "none") == 0) {
Anders Lindh Olsson's avatar
Anders Lindh Olsson committed
      printf("require: skip version=none\n");
    return 0;
  }

  if (version) {
    /* needed for old style only: */
    if (asprintf(&versionstr, "-%s", version) < 0)
      return errno;
    if (isdigit((unsigned char)version[0]) &&
        version[strlen(version) - 1] == '+') {
      /*
          user may give a minimal version (e.g. "1.2.4+")
          load highest matching version (here "1.2") and check later
      */
      char *p = strrchr(versionstr, '.');
      if (p == NULL)
        p = versionstr;
      *p = 0;
    }
  } else
    versionstr = "";
  if (requireDebug)
    printf("require: versionstr = \"%s\"\n", versionstr);
Anders Lindh Olsson's avatar
Anders Lindh Olsson committed
  status = require_priv(module, version, args, versionstr);
Anders Lindh Olsson's avatar
Anders Lindh Olsson committed
  if (version)
    free(versionstr);
Anders Lindh Olsson's avatar
Anders Lindh Olsson committed
  if (status == 0)
    return 0;
  if (status != -1)
    perror("require");
  if (interruptAccept)
Dirk Zimoch's avatar
Dirk Zimoch committed
    return status;
Anders Lindh Olsson's avatar
Anders Lindh Olsson committed

  /* require failed in startup script before iocInit */
  fprintf(stderr, "Aborting startup script\n");
  epicsExit(1);
  return status;
Anders Lindh Olsson's avatar
Anders Lindh Olsson committed
static off_t fileSize(const char *filename) {
  struct stat filestat;
  if (stat(filename, &filestat) != 0) {
    if (requireDebug)
      printf("require: %s does not exist\n", filename);
    return -1;
  }
  switch (filestat.st_mode & S_IFMT) {
  case S_IFREG:
    if (requireDebug)
      printf("require: file %s exists, size %lld bytes\n", filename,
             (unsigned long long)filestat.st_size);
    return filestat.st_size;
  case S_IFDIR:
    if (requireDebug)
      printf("require: directory %s exists\n", filename);
    return 0;
Anders Lindh Olsson's avatar
Anders Lindh Olsson committed
  case S_IFBLK:
    if (requireDebug)
      printf("require: %s is a block device\n", filename);
    return -1;