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;
/* 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);

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
#include <X11/Intrinsic.h>
#include <Xmd/String.h>
/* 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.
*/

View File

@ -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;
}
}
}

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 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;

View File

@ -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"