Add untested replicant framework

This commit is contained in:
Sasha Koshka 2023-11-13 00:39:44 -05:00
parent 854fbf371c
commit 55dfca6341
8 changed files with 445 additions and 19 deletions

View File

@ -11,7 +11,10 @@ typedef long unsigned int XmdMapKey;
typedef struct _XmdMap XmdMap; typedef struct _XmdMap XmdMap;
/* XmdMapIterator is a function that can iterate over a map. */ /* 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.*/ /* XmdMapNew creates a new map.*/
XmdMap *XmdMapNew (); XmdMap *XmdMapNew ();
@ -27,7 +30,7 @@ void *XmdMapGet (XmdMap *map, XmdMapKey key);
void *XmdMapSet (XmdMap *map, XmdMapKey key, void *value); void *XmdMapSet (XmdMap *map, XmdMapKey key, void *value);
/* XmdMapIterate calls iterator for each key/value pair in a map. */ /* 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. */ /* XmdMapLength returns the number of key/value entries stored in a map. */
Cardinal XmdMapLength (XmdMap *map); Cardinal XmdMapLength (XmdMap *map);

View File

@ -0,0 +1,64 @@
#ifndef _XmdReplicant_h
#define _XmdReplicant_h
#include <X11/Xlib.h>
#include <X11/Intrinsic.h>
#include <Xm/Xm.h>
#include <Xmd/String.h>
#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

View File

@ -0,0 +1,6 @@
#ifndef _XmdString_h
#define _XmdString_h
typedef const char *ConstString;
#endif

View File

@ -2,6 +2,7 @@
#define _XmdStringMap_h #define _XmdStringMap_h
#include <X11/Intrinsic.h> #include <X11/Intrinsic.h>
#include <Xmd/String.h>
/* XmdStringMap is a hash map that is keyed by a string and can store any type /* XmdStringMap is a hash map that is keyed by a string and can store any type
of element. */ of element. */
@ -9,13 +10,15 @@ typedef struct _XmdStringMap XmdStringMap;
/* XmdStringMapIterator is a function that can iterate over a string map. */ /* XmdStringMapIterator is a function that can iterate over a string map. */
typedef Bool (*XmdStringMapIterator) ( typedef Bool (*XmdStringMapIterator) (
XmdStringMap *map, String key, void *value); XmdStringMap *map,
ConstString key, void *value,
void *clientData);
/* XmdStringMapNew creates a new map.*/ /* XmdStringMapNew creates a new map.*/
XmdStringMap *XmdStringMapNew (); XmdStringMap *XmdStringMapNew ();
/* XmdStringMapGet retrieves a value from a map. */ /* 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 /* 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 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 returns the previous value associated with the key so it can be free'd if
necessary. 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. */ /* 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. /* XmdStringMapLength returns the number of key/value entries stored in a map.
*/ */

View File

@ -88,12 +88,16 @@ void *XmdMapSet (XmdMap *map, XmdMapKey key, void *value) {
return previous; return previous;
} }
void XmdMapIterate (XmdMap *map, XmdMapIterator iterator) { void XmdMapIterate (XmdMap *map, XmdMapIterator iterator, void *clientData) {
for (Cardinal index = 0; index < map->capacity; index ++) { for (Cardinal index = 0; index < map->capacity; index ++) {
XmdMapElement *element = map->data[index]; XmdMapElement *element = map->data[index];
while (element != NULL) { while (element != NULL) {
if(!iterator(map, element->key, element->value)) break; XmdMapElement *sister = element->sister;
element = element->sister; if (!iterator (
map,
element->key, element->value,
clientData)) break;
element = sister;
} }
} }
} }

332
libXmd/src/Replicant.c Normal file
View File

@ -0,0 +1,332 @@
#include <Xmd/Replicant.h>
#include <Xmd/StringMap.h>
#include <Xmd/Map.h>
#include <Xmd/Buffer.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 {
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;
}

View File

@ -17,7 +17,7 @@ struct _XmdStringMap {
}; };
static void XmdStringMapResizeIfNeeded (XmdStringMap *map); static void XmdStringMapResizeIfNeeded (XmdStringMap *map);
static Cardinal XmdStringMapHash (XmdStringMap *map, String key); static Cardinal XmdStringMapHash (XmdStringMap *map, ConstString key);
XmdStringMap *XmdStringMapNew () { XmdStringMap *XmdStringMapNew () {
XmdStringMap *map = XtNew(XmdStringMap); XmdStringMap *map = XtNew(XmdStringMap);
@ -36,7 +36,9 @@ XmdStringMap *XmdStringMapNew () {
return NULL; 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)]; XmdStringMapElement *element = map->data[XmdStringMapHash(map, key)];
while (element != NULL) { while (element != NULL) {
if (strcmp(element->key, key) == 0) return element->value; if (strcmp(element->key, key) == 0) return element->value;
@ -45,7 +47,7 @@ void *XmdStringMapGet (XmdStringMap *map, String key) {
return NULL; return NULL;
} }
void *XmdStringMapSetInternal (XmdStringMap *map, String key, void *value) { void *XmdStringMapSetInternal (XmdStringMap *map, ConstString key, void *value) {
/* find bucket */ /* find bucket */
Cardinal hash = XmdStringMapHash(map, key); Cardinal hash = XmdStringMapHash(map, key);
XmdStringMapElement **destination = &map->data[hash]; XmdStringMapElement **destination = &map->data[hash];
@ -78,7 +80,9 @@ void *XmdStringMapSetInternal (XmdStringMap *map, String key, void *value) {
return NULL; 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); void *previous = XmdStringMapSetInternal(map, key, value);
if (value == NULL) { if (value == NULL) {
if (previous != NULL) map->length --; if (previous != NULL) map->length --;
@ -89,12 +93,20 @@ void *XmdStringMapSet (XmdStringMap *map, String key, void *value) {
return previous; return previous;
} }
void XmdStringMapIterate (XmdStringMap *map, XmdStringMapIterator iterator) { void XmdStringMapIterate (
XmdStringMap *map,
XmdStringMapIterator iterator,
void *clientData
) {
for (Cardinal index = 0; index < map->capacity; index ++) { for (Cardinal index = 0; index < map->capacity; index ++) {
XmdStringMapElement *element = map->data[index]; XmdStringMapElement *element = map->data[index];
while (element != NULL) { while (element != NULL) {
if(!iterator(map, element->key, element->value)) break; XmdStringMapElement *sister = element->sister;
element = element->sister; if (!iterator (
map,
element->key, element->value,
clientData)) break;
element = sister;
} }
} }
} }
@ -146,11 +158,11 @@ void XmdStringMapResizeIfNeeded (XmdStringMap *map) {
XtFree((char *)(oldData)); 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 */ /* http://www.cse.yorku.ca/~oz/hash.html */
Cardinal hash = 5381; Cardinal hash = 5381;
Cardinal ch; Cardinal ch;
while ((ch = (Cardinal)(*key ++)) != 0) { while ((ch = (Cardinal)(*(key ++))) != 0) {
hash = ((hash << 5) + hash) + ch; hash = ((hash << 5) + hash) + ch;
} }
return hash % map->capacity; return hash % map->capacity;

View File

@ -1,5 +1,5 @@
#!/bin/sh #!/bin/sh
CFLAGS="-std=c99 -Wall -Wextra -Wpedantic -Werror -fPIC" CFLAGS="-std=c99 -Wall -Wextra -Werror -fPIC"
PREFIX="/usr/local" PREFIX="/usr/local"
APP_LIBS="-lXmd -lXm -lXt -lX11" APP_LIBS="-lXmd -lXm -lXt -lX11"