diff --git a/libXmd/include/Xmd/Map.h b/libXmd/include/Xmd/Map.h index 06c6cc5..d9a723b 100644 --- a/libXmd/include/Xmd/Map.h +++ b/libXmd/include/Xmd/Map.h @@ -11,7 +11,10 @@ typedef long unsigned int XmdMapKey; typedef struct _XmdMap XmdMap; /* XmdMapIterator is a function that can iterate over a map. */ -typedef Bool (*XmdMapIterator) (XmdMap *map, XmdMapKey key, void *value); +typedef Bool (*XmdMapIterator) ( + XmdMap *map, + XmdMapKey key, void *value, + void *clientData); /* XmdMapNew creates a new map.*/ XmdMap *XmdMapNew (); @@ -27,7 +30,7 @@ void *XmdMapGet (XmdMap *map, XmdMapKey key); void *XmdMapSet (XmdMap *map, XmdMapKey key, void *value); /* XmdMapIterate calls iterator for each key/value pair in a map. */ -void XmdMapIterate (XmdMap *map, XmdMapIterator iterator); +void XmdMapIterate (XmdMap *map, XmdMapIterator iterator, void *clientData); /* XmdMapLength returns the number of key/value entries stored in a map. */ Cardinal XmdMapLength (XmdMap *map); diff --git a/libXmd/include/Xmd/Replicant.h b/libXmd/include/Xmd/Replicant.h new file mode 100644 index 0000000..2d94e92 --- /dev/null +++ b/libXmd/include/Xmd/Replicant.h @@ -0,0 +1,64 @@ +#ifndef _XmdReplicant_h +#define _XmdReplicant_h + +#include +#include +#include +#include + +#define XmdREPLICANT_VERSION 0 + +/* XmdReplicantState contains state information for a replicant instance. */ +typedef struct _XmdReplicantState XmdReplicantState; + +typedef int (*XmdReplicantVersion) (); +typedef Widget (*XmdReplicantCreate) (Widget parent, XmdReplicantState *state); + +/* XmdReplicantOpen opens a replicant from the file located at path. Replicant + files hold state and point to shared object files containing their code. + Replicant shared objects are searched for in $XMD_REPLICANT_PATH. Here is an + example file that describes a calculator replicant: + + [Calculator] + display=90 + history=1 + 2 = 3;9 * 10 = 90; + mode=scientific + + When a replicant's code is needed, it is automatically loaded by libXmd and + stays resident until all replicants using it have been freed. A system of + reference counting is used to accomplish this. = + + Replicants must implement these symbols, where NAME is the base name of the + replicant's shared object file (case sensitive, and excluding the .so + extention): + + int NAME_XmdReplicantVersion (); + Version must return the value of XmdREPLICANT_VERSION. This is used to detect + the API version that the replicant was compiled with, and applications with a + different API version will refuse to load it. + + Widget NAME_XmdReplicantCreate (Widget parent, XmdReplicantState *state); + Create instantiates a new widget representing the replicant given by state. + */ +Widget XmdReplicantOpen (Widget parent, ConstString path); + +/* XmdReplicantResolveName returns the file path of the given replicant shared + object name according to $XMD_REPLICANT_PATH. The returned string must be + freed using XtFree(). */ +String XmdReplicantResolveName (ConstString name); + +/* XmdReplicantStateQuery queries the replicant's state file for a named value. + The resulting string must be free'd using XtFree(). NULL is returned if there + is no value associated with the given name. This may perform a read from + disk. */ +String XmdReplicantStateQuery ( + XmdReplicantState *state, + ConstString name); + +/* XmdReplicantStateStore stores a named value in the replicant's state file. + This may perform a write to disk. */ +void XmdReplicantStateStore ( + XmdReplicantState *state, + ConstString name, ConstString value); + +#endif diff --git a/libXmd/include/Xmd/String.h b/libXmd/include/Xmd/String.h new file mode 100644 index 0000000..5e28c01 --- /dev/null +++ b/libXmd/include/Xmd/String.h @@ -0,0 +1,6 @@ +#ifndef _XmdString_h +#define _XmdString_h + +typedef const char *ConstString; + +#endif diff --git a/libXmd/include/Xmd/StringMap.h b/libXmd/include/Xmd/StringMap.h index 3d5aa9b..06ac583 100644 --- a/libXmd/include/Xmd/StringMap.h +++ b/libXmd/include/Xmd/StringMap.h @@ -2,6 +2,7 @@ #define _XmdStringMap_h #include +#include /* XmdStringMap is a hash map that is keyed by a string and can store any type of element. */ @@ -9,13 +10,15 @@ typedef struct _XmdStringMap XmdStringMap; /* XmdStringMapIterator is a function that can iterate over a string map. */ typedef Bool (*XmdStringMapIterator) ( - XmdStringMap *map, String key, void *value); + XmdStringMap *map, + ConstString key, void *value, + void *clientData); /* XmdStringMapNew creates a new map.*/ XmdStringMap *XmdStringMapNew (); /* XmdStringMapGet retrieves a value from a map. */ -void *XmdStringMapGet (XmdStringMap *map, String key); +void *XmdStringMapGet (XmdStringMap *map, ConstString key); /* XmdStringMapSet sets a value in a map. The previous value associated with the key, if it exists, is overwritten by the new value. If the value is set to @@ -23,10 +26,12 @@ void *XmdStringMapGet (XmdStringMap *map, String key); returns the previous value associated with the key so it can be free'd if necessary. */ -void *XmdStringMapSet (XmdStringMap *map, String key, void *value); +void *XmdStringMapSet (XmdStringMap *map, ConstString key, void *value); /* XmdStringMapIterate calls iterator for each key/value pair in a map. */ -void XmdStringMapIterate (XmdStringMap *map, XmdStringMapIterator iterator); +void XmdStringMapIterate ( + XmdStringMap *map, XmdStringMapIterator iterator, + void *clientData); /* XmdStringMapLength returns the number of key/value entries stored in a map. */ diff --git a/libXmd/src/Map.c b/libXmd/src/Map.c index 2810998..0af5971 100644 --- a/libXmd/src/Map.c +++ b/libXmd/src/Map.c @@ -88,12 +88,16 @@ void *XmdMapSet (XmdMap *map, XmdMapKey key, void *value) { return previous; } -void XmdMapIterate (XmdMap *map, XmdMapIterator iterator) { +void XmdMapIterate (XmdMap *map, XmdMapIterator iterator, void *clientData) { for (Cardinal index = 0; index < map->capacity; index ++) { XmdMapElement *element = map->data[index]; while (element != NULL) { - if(!iterator(map, element->key, element->value)) break; - element = element->sister; + XmdMapElement *sister = element->sister; + if (!iterator ( + map, + element->key, element->value, + clientData)) break; + element = sister; } } } diff --git a/libXmd/src/Replicant.c b/libXmd/src/Replicant.c new file mode 100644 index 0000000..678d1b3 --- /dev/null +++ b/libXmd/src/Replicant.c @@ -0,0 +1,332 @@ +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +typedef struct { + void *handle; + int references; + + struct { + XmdReplicantVersion version; + XmdReplicantCreate create; + } symbols; +} XmdReplicantSource; + +struct _XmdReplicantState { + String file; + String sourceName; + XmdReplicantSource *source; + XmdStringMap *map; +}; + +static void XmdReplicantHandleDestroy ( + Widget replicant, + XtPointer clientData, + XtPointer callData); +static void XmdReplicantStateSave (XmdReplicantState *state); +static void XmdReplicantStateLoad (XmdReplicantState *state); +static XmdReplicantState *XmdReplicantStateNew (); +static void XmdReplicantStateFree (XmdReplicantState *state); +static XmdReplicantSource *XmdReplicantSourceOpen (ConstString name); +static void XmdReplicantSourceClose (XmdReplicantSource *source); +static XmdReplicantSource *XmdReplicantSourceGet (ConstString name); +static String XmdReplicantScanDir ( + ConstString path, + ConstString name); + +static const String defaultReplicantPath = + "/usr/lib/Xmd/replicants:" + "/usr/local/lib/Xmd/replicants"; +static XmdStringMap *resident = NULL; /* source names to source structs */ + +Widget XmdReplicantOpen (Widget parent, ConstString path) { + /* create and load the state */ + XmdReplicantState *state = XmdReplicantStateNew(); + if (state == NULL) goto fail; + state->file = XtNewString(path); + if (state->file == NULL) goto fail; + XmdReplicantStateLoad(state); + + /* retrieve the replicant's source */ + XmdReplicantSource *source = XmdReplicantSourceGet(state->sourceName); + if (source == NULL) goto fail; + source->references ++; + state->source = source; + + /* create replicant and bind its state */ + Widget replicant = source->symbols.create(parent, state); + XtAddCallback ( + replicant, XmNdestroyCallback, + XmdReplicantHandleDestroy, state); + return replicant; + + fail: + XmdReplicantStateFree(state); + return NULL; +} + +static void XmdReplicantHandleDestroy ( + Widget replicant, + XtPointer clientData, + XtPointer callData +) { + (void)(replicant); + (void)(callData); + + XmdReplicantState *state = clientData; + if (state == NULL) return; + + state->source->references --; + if (state->source->references < 1) { + XmdReplicantSourceClose ( + XmdStringMapSet(resident, state->sourceName, NULL)); + } + XmdReplicantStateFree(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); + + XmdBufferPush(dirBuffer, &ch); + if (ch == ':' || ch == 0) { + String dir = XmdBufferBreak(dirBuffer); + dirBuffer = NULL; + file = XmdReplicantScanDir(dir, name); + XtFree(dir); + } + 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); + XmdReplicantStateSave(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 void XmdReplicantStateSave (XmdReplicantState *state) { + FILE *file = fopen(state->file, "w"); + if (file == NULL) return; + fprintf(file, "[%s]\n", state->sourceName); + XmdStringMapIterate(state->map, writeMapValue, file); + fclose(file); +} + +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 void XmdReplicantStateLoad (XmdReplicantState *state) { + FILE *file = fopen(state->file, "w"); + if (file == NULL) return; + + int ch = 0; + while (isspace((ch == fgetc(file)))); + if (ch != '[') return; + state->sourceName = readEscapedMapString(file, ']'); + ch = fgetc(file); + if (ch != '\n') return; + + while (1) { + String key = readEscapedMapString(file, '='); + String value = readEscapedMapString(file, '\n'); + XmdStringMapSet(state->map, key, value); + XtFree(key); + if (value == NULL) break; + } +} + +static XmdReplicantState *XmdReplicantStateNew () { + 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: + XmdReplicantStateFree(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 XmdReplicantStateFree (XmdReplicantState *state) { + if (state == NULL) return; + XtFree(state->file); + XtFree(state->sourceName); + XmdStringMapIterate(state->map, freeMapValue, NULL); + XmdStringMapFree(state->map); + XtFree((char *)(state)); +} + +static XmdReplicantSource *XmdReplicantSourceOpen (ConstString name) { + /* allocate */ + XmdReplicantSource *source = XtNew(XmdReplicantSource); + if (source == NULL) goto fail; + memset(source, 0, sizeof(XmdReplicantSource)); + + /* 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 versionName = NULL; + XtAsprintf(&versionName, "%s_XmdReplicantVersion", name); + source->symbols.version = (XmdReplicantVersion) ( + dlsym(source->handle, versionName)); + XtFree(versionName); + if (source->symbols.version == NULL) goto fail; + String createName = NULL; + XtAsprintf(&createName, "%s_XmdReplicantCreate", name); + source->symbols.create = (XmdReplicantCreate) ( + dlsym(source->handle, createName)); + XtFree(createName); + if (source->symbols.create == NULL) goto fail; + + /* check if the version matches */ + if (source->symbols.version() != XmdREPLICANT_VERSION) goto fail; + + return source; + + fail: + XmdReplicantSourceClose(source); + return NULL; +} + +static void XmdReplicantSourceClose (XmdReplicantSource *source) { + if (source == NULL) return; + if (source->handle != NULL) dlclose(source->handle); + XtFree((char *)(source)); +} + +static XmdReplicantSource *XmdReplicantSourceGet (ConstString name) { + /* check to see if it has already been loaded */ + XmdReplicantSource *source = XmdStringMapGet(resident, name); + if (source != NULL) return source; + + /* open the source */ + return XmdReplicantSourceOpen(name); +} + +static String XmdReplicantScanDir (ConstString path, ConstString name) { + DIR *dir = opendir(path); + 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; +} diff --git a/libXmd/src/StringMap.c b/libXmd/src/StringMap.c index e9bedf2..2c85622 100644 --- a/libXmd/src/StringMap.c +++ b/libXmd/src/StringMap.c @@ -17,7 +17,7 @@ struct _XmdStringMap { }; static void XmdStringMapResizeIfNeeded (XmdStringMap *map); -static Cardinal XmdStringMapHash (XmdStringMap *map, String key); +static Cardinal XmdStringMapHash (XmdStringMap *map, ConstString key); XmdStringMap *XmdStringMapNew () { XmdStringMap *map = XtNew(XmdStringMap); @@ -36,7 +36,9 @@ XmdStringMap *XmdStringMapNew () { return NULL; } -void *XmdStringMapGet (XmdStringMap *map, String key) { +void *XmdStringMapGet (XmdStringMap *map, ConstString key) { + if (key == NULL) return NULL; + XmdStringMapElement *element = map->data[XmdStringMapHash(map, key)]; while (element != NULL) { if (strcmp(element->key, key) == 0) return element->value; @@ -45,7 +47,7 @@ void *XmdStringMapGet (XmdStringMap *map, String key) { return NULL; } -void *XmdStringMapSetInternal (XmdStringMap *map, String key, void *value) { +void *XmdStringMapSetInternal (XmdStringMap *map, ConstString key, void *value) { /* find bucket */ Cardinal hash = XmdStringMapHash(map, key); XmdStringMapElement **destination = &map->data[hash]; @@ -78,7 +80,9 @@ void *XmdStringMapSetInternal (XmdStringMap *map, String key, void *value) { return NULL; } -void *XmdStringMapSet (XmdStringMap *map, String key, void *value) { +void *XmdStringMapSet (XmdStringMap *map, ConstString key, void *value) { + if (key == NULL) return NULL; + void *previous = XmdStringMapSetInternal(map, key, value); if (value == NULL) { if (previous != NULL) map->length --; @@ -89,12 +93,20 @@ void *XmdStringMapSet (XmdStringMap *map, String key, void *value) { return previous; } -void XmdStringMapIterate (XmdStringMap *map, XmdStringMapIterator iterator) { +void XmdStringMapIterate ( + XmdStringMap *map, + XmdStringMapIterator iterator, + void *clientData +) { for (Cardinal index = 0; index < map->capacity; index ++) { XmdStringMapElement *element = map->data[index]; while (element != NULL) { - if(!iterator(map, element->key, element->value)) break; - element = element->sister; + XmdStringMapElement *sister = element->sister; + if (!iterator ( + map, + element->key, element->value, + clientData)) break; + element = sister; } } } @@ -146,11 +158,11 @@ void XmdStringMapResizeIfNeeded (XmdStringMap *map) { XtFree((char *)(oldData)); } -static Cardinal XmdStringMapHash (XmdStringMap *map, String key) { +static Cardinal XmdStringMapHash (XmdStringMap *map, ConstString key) { /* http://www.cse.yorku.ca/~oz/hash.html */ Cardinal hash = 5381; Cardinal ch; - while ((ch = (Cardinal)(*key ++)) != 0) { + while ((ch = (Cardinal)(*(key ++))) != 0) { hash = ((hash << 5) + hash) + ch; } return hash % map->capacity; diff --git a/scripts/flags.sh b/scripts/flags.sh index 73c0ef7..38d9d40 100644 --- a/scripts/flags.sh +++ b/scripts/flags.sh @@ -1,5 +1,5 @@ #!/bin/sh -CFLAGS="-std=c99 -Wall -Wextra -Wpedantic -Werror -fPIC" +CFLAGS="-std=c99 -Wall -Wextra -Werror -fPIC" PREFIX="/usr/local" APP_LIBS="-lXmd -lXm -lXt -lX11"