387 lines
9.3 KiB
C
387 lines
9.3 KiB
C
#include <Xmd/Replicant.h>
|
|
#include <Xmd/StringMap.h>
|
|
#include <Xmd/Map.h>
|
|
#include <Xmd/Buffer.h>
|
|
#include <Xmd/Error.h>
|
|
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <dlfcn.h>
|
|
#include <dirent.h>
|
|
#include <ctype.h>
|
|
|
|
typedef struct {
|
|
void *handle;
|
|
int references;
|
|
|
|
struct {
|
|
XmdReplicantFnVersion version;
|
|
XmdReplicantFnConstruct construct;
|
|
XmdReplicantFnDestruct destruct;
|
|
XmdReplicantFnCreate create;
|
|
} symbols;
|
|
} replicantSource;
|
|
|
|
struct _XmdReplicantState {
|
|
String file;
|
|
String sourceName;
|
|
replicantSource *source;
|
|
XmdStringMap *map;
|
|
};
|
|
|
|
/* internal function prototypes */
|
|
static void handleDestroy (Widget, XtPointer, XtPointer);
|
|
static XmdError stateSave (XmdReplicantState *state);
|
|
static XmdError stateLoad (XmdReplicantState *state);
|
|
static XmdReplicantState *stateNew ();
|
|
static void stateFree (XmdReplicantState *state);
|
|
static replicantSource *sourceOpen (ConstString name);
|
|
static void sourceClose (replicantSource *source);
|
|
static replicantSource *sourceGet (ConstString name);
|
|
static String scanDir (ConstString path, ConstString name);
|
|
static void ensure ();
|
|
static String readEscapedMapString (FILE *, char);
|
|
static void writeEscapedMapString (FILE *, ConstString);
|
|
static Bool freeMapValue (XmdStringMap *, ConstString, void *, void *);
|
|
static Bool writeMapValue (XmdStringMap *, ConstString, void *, void *);
|
|
|
|
/* resident maps source names to source structs */
|
|
static XmdStringMap *resident = NULL;
|
|
static const String defaultReplicantPath =
|
|
"/usr/local/lib/Xmd/replicants:"
|
|
"/usr/lib/Xmd/replicants";
|
|
|
|
Widget XmdReplicantOpen (XtAppContext app, Widget parent, ConstString path) {
|
|
/* create and load the state */
|
|
XmdReplicantState *state = stateNew();
|
|
if (state == NULL) goto fail;
|
|
state->file = XtNewString(path);
|
|
if (state->file == NULL) goto fail;
|
|
stateLoad(state);
|
|
|
|
/* retrieve the replicant's source */
|
|
replicantSource *source = sourceGet(state->sourceName);
|
|
if (source == NULL) goto fail;
|
|
source->references ++;
|
|
state->source = source;
|
|
|
|
/* create replicant and bind its state */
|
|
Widget replicant = source->symbols.create(app, parent, state);
|
|
XtAddCallback (
|
|
replicant, XmNdestroyCallback,
|
|
handleDestroy, state);
|
|
return replicant;
|
|
|
|
fail:
|
|
stateFree(state);
|
|
return NULL;
|
|
}
|
|
|
|
static void handleDestroy (
|
|
Widget replicant,
|
|
XtPointer clientData,
|
|
XtPointer callData
|
|
) {
|
|
(void)(replicant);
|
|
(void)(callData);
|
|
ensure();
|
|
|
|
XmdReplicantState *state = clientData;
|
|
if (state == NULL) return;
|
|
|
|
state->source->references --;
|
|
if (state->source->references < 1) {
|
|
sourceClose (
|
|
XmdStringMapSet(resident, state->sourceName, NULL));
|
|
}
|
|
stateFree(state);
|
|
}
|
|
|
|
String XmdReplicantResolveName (ConstString rawName) {
|
|
String list = getenv("XMD_REPLICANT_PATH");
|
|
XmdBuffer *dirBuffer = NULL;
|
|
String file = NULL;
|
|
String name = NULL;
|
|
XtAsprintf(&name, "%s.so", rawName);
|
|
if (list == NULL) list = defaultReplicantPath;
|
|
|
|
while (file == NULL) {
|
|
char ch = *list;
|
|
if (dirBuffer == NULL) dirBuffer = XmdBufferNew(char);
|
|
|
|
if (ch == ':' || ch == 0) {
|
|
char null = 0;
|
|
XmdBufferPush(dirBuffer, &null);
|
|
String dir = XmdBufferBreak(dirBuffer);
|
|
dirBuffer = NULL;
|
|
file = scanDir(dir, name);
|
|
XtFree(dir);
|
|
} else {
|
|
XmdBufferPush(dirBuffer, &ch);
|
|
}
|
|
if (ch == 0) break;
|
|
list ++;
|
|
}
|
|
XtFree(name);
|
|
return file;
|
|
}
|
|
|
|
String XmdReplicantStateQuery (XmdReplicantState *state, ConstString name) {
|
|
return XtNewString(XmdStringMapGet(state->map, name));
|
|
}
|
|
|
|
void XmdReplicantStateStore (
|
|
XmdReplicantState *state,
|
|
ConstString name, ConstString value
|
|
) {
|
|
String old = XmdStringMapSet(state->map, name, XtNewString(value));
|
|
XtFree(old);
|
|
stateSave(state);
|
|
}
|
|
|
|
static void writeEscapedMapString (FILE *file, ConstString string) {
|
|
while (1) {
|
|
char ch = *string ++;
|
|
switch (ch) {
|
|
case '=':
|
|
fputc('\\', file);
|
|
fputc('=', file);
|
|
break;
|
|
case '[':
|
|
fputc('\\', file);
|
|
fputc('[', file);
|
|
break;
|
|
case ']':
|
|
fputc('\\', file);
|
|
fputc(']', file);
|
|
break;
|
|
case '\n':
|
|
fputc('\\', file);
|
|
fputc('n', file);
|
|
break;
|
|
case 0:
|
|
return;
|
|
default:
|
|
fputc(ch, file);
|
|
}
|
|
}
|
|
}
|
|
|
|
static Bool writeMapValue (
|
|
XmdStringMap *map,
|
|
ConstString key, void *value,
|
|
void *clientData
|
|
) {
|
|
(void)(map);
|
|
FILE *file = clientData;
|
|
writeEscapedMapString(file, key);
|
|
fputc('=', file);
|
|
writeEscapedMapString(file, value);
|
|
fputc('\n', file);
|
|
return True;
|
|
}
|
|
|
|
static XmdError stateSave (XmdReplicantState *state) {
|
|
FILE *file = fopen(state->file, "w");
|
|
if (file == NULL) return XmdErrorCantOpenFile;
|
|
fprintf(file, "[%s]\n", state->sourceName);
|
|
XmdStringMapIterate(state->map, writeMapValue, file);
|
|
fclose(file);
|
|
return XmdErrorNone;
|
|
}
|
|
|
|
static String readEscapedMapString (FILE *file, char delimiter) {
|
|
int ch = fgetc(file);
|
|
if (ch == EOF) return NULL;
|
|
ungetc(ch, file);
|
|
|
|
XmdBuffer *string = XmdBufferNew(char);
|
|
while (1) {
|
|
ch = fgetc(file);
|
|
if (ch == EOF) break;
|
|
if (ch == delimiter) break;
|
|
|
|
if (ch == '\\') {
|
|
ch = fgetc(file);
|
|
if (ch == EOF) break;
|
|
if (ch == 'n') {
|
|
char newline = '\n';
|
|
XmdBufferPush(string, &newline);
|
|
continue;
|
|
}
|
|
}
|
|
XmdBufferPush(string, &ch);
|
|
}
|
|
|
|
char null = 0;
|
|
XmdBufferPush(string, &null);
|
|
return XmdBufferBreak(string);
|
|
}
|
|
|
|
static XmdError stateLoad (XmdReplicantState *state) {
|
|
if (state->sourceName != NULL) XtFree(state->sourceName);
|
|
state->sourceName = NULL;
|
|
|
|
FILE *file = fopen(state->file, "r");
|
|
if (file == NULL) {
|
|
return XmdErrorFileNotFound;
|
|
};
|
|
|
|
int ch = ' ';
|
|
while (isspace(ch)) ch = fgetc(file);
|
|
if (ch != '[') {
|
|
fclose(file);
|
|
return XmdErrorBadSyntax;
|
|
}
|
|
state->sourceName = readEscapedMapString(file, ']');
|
|
ch = fgetc(file);
|
|
if (ch != '\n') {
|
|
fclose(file);
|
|
return XmdErrorBadSyntax;
|
|
}
|
|
|
|
while (1) {
|
|
String key = readEscapedMapString(file, '=');
|
|
String value = readEscapedMapString(file, '\n');
|
|
XmdStringMapSet(state->map, key, value);
|
|
XtFree(key);
|
|
if (value == NULL) {
|
|
if (key == NULL) {
|
|
fclose(file);
|
|
return XmdErrorNone;
|
|
} else {
|
|
fclose(file);
|
|
return XmdErrorBadSyntax;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static XmdReplicantState *stateNew () {
|
|
XmdReplicantState *state = XtNew(XmdReplicantState);
|
|
if (state == NULL) return NULL;
|
|
|
|
memset(state, 0, sizeof(XmdReplicantState));
|
|
state->map = XmdStringMapNew();
|
|
if (state->map == NULL) goto fail;
|
|
|
|
return state;
|
|
fail:
|
|
stateFree(state);
|
|
return NULL;
|
|
}
|
|
|
|
static Bool freeMapValue (
|
|
XmdStringMap *map,
|
|
ConstString key, void *value,
|
|
void *clientData
|
|
) {
|
|
(void)(map);
|
|
(void)(key);
|
|
(void)(clientData);
|
|
XtFree(value);
|
|
return True;
|
|
}
|
|
|
|
static void stateFree (XmdReplicantState *state) {
|
|
if (state == NULL) return;
|
|
XtFree(state->file);
|
|
XtFree(state->sourceName);
|
|
XmdStringMapIterate(state->map, freeMapValue, NULL);
|
|
XmdStringMapFree(state->map);
|
|
XtFree((char *)(state));
|
|
}
|
|
|
|
static replicantSource *sourceOpen (ConstString name) {
|
|
/* allocate */
|
|
replicantSource *source = XtNew(replicantSource);
|
|
if (source == NULL) goto fail;
|
|
memset(source, 0, sizeof(replicantSource));
|
|
|
|
/* open library */
|
|
String path = XmdReplicantResolveName(name);
|
|
if (path == NULL) goto fail;
|
|
source->handle = dlopen(path, RTLD_NOW | RTLD_LOCAL);
|
|
XtFree(path);
|
|
if (source->handle == NULL) goto fail;
|
|
|
|
/* find symbols */
|
|
String symbolName = NULL;
|
|
XtAsprintf(&symbolName, "%s_XmdReplicantVersion", name);
|
|
source->symbols.version = (XmdReplicantFnVersion) (
|
|
dlsym(source->handle, symbolName));
|
|
XtFree(symbolName);
|
|
if (source->symbols.version == NULL) goto fail;
|
|
|
|
XtAsprintf(&symbolName, "%s_XmdReplicantConstruct", name);
|
|
source->symbols.construct = (XmdReplicantFnConstruct) (
|
|
dlsym(source->handle, symbolName));
|
|
XtFree(symbolName);
|
|
|
|
XtAsprintf(&symbolName, "%s_XmdReplicantDestruct", name);
|
|
source->symbols.destruct = (XmdReplicantFnDestruct) (
|
|
dlsym(source->handle, symbolName));
|
|
XtFree(symbolName);
|
|
|
|
XtAsprintf(&symbolName, "%s_XmdReplicantCreate", name);
|
|
source->symbols.create = (XmdReplicantFnCreate) (
|
|
dlsym(source->handle, symbolName));
|
|
XtFree(symbolName);
|
|
if (source->symbols.create == NULL) goto fail;
|
|
|
|
/* check if the version matches */
|
|
if (source->symbols.version() != XmdREPLICANT_VERSION) goto fail;
|
|
|
|
if (source->symbols.construct != NULL) source->symbols.construct();
|
|
return source;
|
|
|
|
fail:
|
|
sourceClose(source);
|
|
return NULL;
|
|
}
|
|
|
|
static void sourceClose (replicantSource *source) {
|
|
if (source == NULL) return;
|
|
if (source->symbols.destruct != NULL) source->symbols.destruct();
|
|
if (source->handle != NULL) dlclose(source->handle);
|
|
XtFree((char *)(source));
|
|
}
|
|
|
|
static replicantSource *sourceGet (ConstString name) {
|
|
ensure();
|
|
|
|
/* check to see if it has already been loaded */
|
|
replicantSource *source = XmdStringMapGet(resident, name);
|
|
if (source != NULL) return source;
|
|
|
|
/* open the source */
|
|
source = sourceOpen(name);
|
|
XmdStringMapSet(resident, name, source);
|
|
return source;
|
|
}
|
|
|
|
static String scanDir (ConstString path, ConstString name) {
|
|
DIR *dir = opendir(path);
|
|
if (dir == NULL) return NULL;
|
|
|
|
String result = NULL;
|
|
while (1) {
|
|
struct dirent *entry = readdir(dir);
|
|
if (entry == NULL) break;
|
|
|
|
if (strcmp(name, entry->d_name) == 0) {
|
|
XtAsprintf(&result, "%s/%s", path, entry->d_name);
|
|
}
|
|
}
|
|
|
|
closedir(dir);
|
|
return result;
|
|
}
|
|
|
|
static void ensure () {
|
|
if (resident == NULL) {
|
|
resident = XmdStringMapNew();
|
|
}
|
|
}
|