From d026e93405655129e46debfca2124ee132e9b134 Mon Sep 17 00:00:00 2001 From: bigfoot547 Date: Sun, 16 Nov 2025 20:52:57 -0600 Subject: build config + more plugin stuff --- config.h.in | 51 --------- connection.c | 45 -------- include/config.h.in | 54 +++++++++ include/connection.h | 4 +- include/macros.h | 12 ++ include/pipeline.h | 29 ++--- main.c | 140 ----------------------- meson.build | 31 ++++- pipeline.c | 316 --------------------------------------------------- plugins/meson.build | 5 +- plugins/skibidi.c | 3 +- src/connection.c | 45 ++++++++ src/main.c | 125 ++++++++++++++++++++ src/meson.build | 5 + src/pipeline.c | 316 +++++++++++++++++++++++++++++++++++++++++++++++++++ 15 files changed, 599 insertions(+), 582 deletions(-) delete mode 100644 config.h.in delete mode 100644 connection.c create mode 100644 include/config.h.in create mode 100644 include/macros.h delete mode 100644 main.c delete mode 100644 pipeline.c create mode 100644 src/connection.c create mode 100644 src/main.c create mode 100644 src/meson.build create mode 100644 src/pipeline.c diff --git a/config.h.in b/config.h.in deleted file mode 100644 index 59956d4..0000000 --- a/config.h.in +++ /dev/null @@ -1,51 +0,0 @@ -#ifndef PTXMC_CONFIG_H_INCLUDED -#define PTXMC_CONFIG_H_INCLUDED - -#mesondefine HAS_ATTR_WUR -#mesondefine HAS_ATTR_ALWAYS_INLINE -#mesondefine HAS_ATTR_MALLOC -#mesondefine HAS_ATTR_FORMAT -#mesondefine HAS_RESTRICT - -#ifdef HAS_ATTR_WUR -#define ATTR_WUR __attribute__((warn_unused_result)) -#else -#define ATTR_WUR -#endif - -#ifdef HAS_ATTR_ALWAYS_INLINE -#define ATTR_ALWAYS_INLINE __attribute__((always_inline)) -#else -#define ATTR_ALWAYS_INLINE -#endif - -#ifdef HAS_ATTR_MALLOC -#define ATTR_MALLOC(_x) -#else -#define ATTR_MALLOC(_x) __attribute__((malloc _x)) -#endif - -#ifdef HAS_ATTR_FORMAT -#define ATTR_FORMAT(_x) __attribute__((format _x)) -#else -#define ATTR_FORMAT(_x) -#endif - -#ifdef HAS_RESTRICT -#define PTX_RESTRICT restrict -#else -#define PTX_RESTRICT -#endif - -/* meson doesn't support this one yet :( */ -#ifdef __has_attribute -# if __has_attribute(access) -# define ATTR_ACCESS(_x) __attribute__((access _x)) -# else -# define ATTR_ACCESS(_x) -# endif -#else -#define ATTR_ACCESS(_x) -#endif - -#endif /* include guard */ diff --git a/connection.c b/connection.c deleted file mode 100644 index ecbb7fa..0000000 --- a/connection.c +++ /dev/null @@ -1,45 +0,0 @@ -#include "connection.h" -#include "pipeline.h" -#include - -struct ptx_connection *ptx_connection_new(void) -{ - struct ptx_connection *conn = malloc(sizeof(struct ptx_connection)); - if (!conn) return NULL; - - /* initialize these in case we goto cleanup later */ - conn->cli_read = NULL; - conn->cli_write = NULL; - conn->srv_read = NULL; - conn->srv_write = NULL; - - /* do initialization */ - - if (!(conn->cli_read = ptx_pipeline_new(4))) goto cleanup; - if (!(conn->cli_write = ptx_pipeline_new(4))) goto cleanup; - if (!(conn->srv_read = ptx_pipeline_new(4))) goto cleanup; - if (!(conn->srv_write = ptx_pipeline_new(4))) goto cleanup; - - return conn; - -cleanup: - ptx_connection_free(conn); - - return NULL; -} - -void ptx_connection_free(struct ptx_connection *conn) -{ - /* NOTE: this function might be called with a partially-constructed or inconsistent object */ - - if (!conn) return; - - /* do cleanup */ - - ptx_pipeline_free(conn->cli_read); - ptx_pipeline_free(conn->cli_write); - ptx_pipeline_free(conn->srv_read); - ptx_pipeline_free(conn->srv_write); - - free(conn); -} diff --git a/include/config.h.in b/include/config.h.in new file mode 100644 index 0000000..49431a6 --- /dev/null +++ b/include/config.h.in @@ -0,0 +1,54 @@ +#ifndef PTXMC_CONFIG_H_INCLUDED +#define PTXMC_CONFIG_H_INCLUDED + +#mesondefine PTX_IMPORT +#mesondefine PTX_EXPORT + +#mesondefine HAS_ATTR_WUR +#mesondefine HAS_ATTR_ALWAYS_INLINE +#mesondefine HAS_ATTR_MALLOC +#mesondefine HAS_ATTR_FORMAT +#mesondefine HAS_RESTRICT + +#ifdef HAS_ATTR_WUR +#define ATTR_WUR __attribute__((warn_unused_result)) +#else +#define ATTR_WUR +#endif + +#ifdef HAS_ATTR_ALWAYS_INLINE +#define ATTR_ALWAYS_INLINE __attribute__((always_inline)) +#else +#define ATTR_ALWAYS_INLINE +#endif + +#ifdef HAS_ATTR_MALLOC +#define ATTR_MALLOC(_x) +#else +#define ATTR_MALLOC(_x) __attribute__((malloc _x)) +#endif + +#ifdef HAS_ATTR_FORMAT +#define ATTR_FORMAT(_x) __attribute__((format _x)) +#else +#define ATTR_FORMAT(_x) +#endif + +#ifdef HAS_RESTRICT +#define PTX_RESTRICT restrict +#else +#define PTX_RESTRICT +#endif + +/* meson doesn't support this one yet :( */ +#ifdef __has_attribute +# if __has_attribute(access) +# define ATTR_ACCESS(_x) __attribute__((access _x)) +# else +# define ATTR_ACCESS(_x) +# endif +#else +#define ATTR_ACCESS(_x) +#endif + +#endif /* include guard */ diff --git a/include/connection.h b/include/connection.h index 416e076..98f0eca 100644 --- a/include/connection.h +++ b/include/connection.h @@ -9,7 +9,7 @@ struct ptx_connection ptx_pipeline_t *srv_read, *srv_write; }; -struct ptx_connection *ptx_connection_new(void); -void ptx_connection_free(struct ptx_connection *conn); +PTX_API struct ptx_connection *ptx_connection_new(void) ATTR_WUR ATTR_MALLOC() ATTR_MALLOC((ptx_connection_free, 1)); +PTX_API void ptx_connection_free(struct ptx_connection *conn); #endif /* include guard */ diff --git a/include/macros.h b/include/macros.h new file mode 100644 index 0000000..3702df7 --- /dev/null +++ b/include/macros.h @@ -0,0 +1,12 @@ +#ifndef PTX_MACROS_H_INCLDUED +#define PTX_MACROS_H_INCLDUED + +#include "config.h" + +#ifdef PTX_MODULE +#define PTX_API PTX_IMPORT +#else +#define PTX_API PTX_EXPORT +#endif + +#endif /* include guard */ diff --git a/include/pipeline.h b/include/pipeline.h index d662b09..f8c51dc 100644 --- a/include/pipeline.h +++ b/include/pipeline.h @@ -5,7 +5,7 @@ #include #include -#include "config.h" +#include "macros.h" #define PIPELINE_STAGE ATTR_WUR ATTR_ACCESS((read_only, 1)) #define PIPELINE_INIT ATTR_ACCESS((read_only, 1)) @@ -24,40 +24,33 @@ struct ptx_pipeline_stage_funcs { ptx_pipeline_cleanup_proc_t *cleanup; }; -#ifdef MODULE -#define MODEXP extern -#else -#define MODEXP \ -__attribute__((visibility("protected"))) -#endif - /* allocates a new pipeline, with space for `initial_size' new stages. */ -MODEXP ptx_pipeline_t *ptx_pipeline_new(size_t initial_size) ATTR_WUR ATTR_MALLOC() ATTR_MALLOC((ptx_pipeline_free, 1)); +PTX_API ptx_pipeline_t *ptx_pipeline_new(size_t initial_size) ATTR_WUR ATTR_MALLOC() ATTR_MALLOC((ptx_pipeline_free, 1)); /* frees an existing pipeline stage. note that this will call the cleanup function in the stages as well. */ -void ptx_pipeline_free(ptx_pipeline_t *pl); +PTX_API void ptx_pipeline_free(ptx_pipeline_t *pl); /* adds `stage' with `name' to `pipeline' after `after' * returns: number of stages added to pipeline (0 or 1), or -1 if there was an allocation or initialization error. */ -int ptx_pipeline_add_after(ptx_pipeline_t *pipeline, const struct ptx_pipeline_stage_funcs *stage, const char *name, const char *after, ...); +PTX_API int ptx_pipeline_add_after(ptx_pipeline_t *pipeline, const struct ptx_pipeline_stage_funcs *stage, const char *name, const char *after, ...) ATTR_WUR; /* adds `stage' with `name' to `pipeline' before `before' * returns: number of stages added to pipeline (0 or 1), or -1 if there was an allocation or initialization error. */ -int ptx_pipeline_add_before(ptx_pipeline_t *pipeline, const struct ptx_pipeline_stage_funcs *stage, const char *name, const char *before, ...); +PTX_API int ptx_pipeline_add_before(ptx_pipeline_t *pipeline, const struct ptx_pipeline_stage_funcs *stage, const char *name, const char *before, ...) ATTR_WUR; /* TODO: could theoretically have versions of the above functions that don't defensively copy `name' */ /* returns: number of stages removed from pipeline */ -int ptx_pipeline_remove_stage(ptx_pipeline_t *pipeline, const char *name); +PTX_API int ptx_pipeline_remove_stage(ptx_pipeline_t *pipeline, const char *name); /* this pointer is guaranteed to be valid as long as the pipeline context is active (i.e., within this handler function) * If you need to access user data of a stage from outside of a handler function, TODO */ -void **ptx_pipeline_ctx_get_user(const ptx_pipeline_ctx_t *ctx); -const char *ptx_pipeline_ctx_get_name(const ptx_pipeline_ctx_t *ctx); +PTX_API void **ptx_pipeline_ctx_get_user(const ptx_pipeline_ctx_t *ctx); +PTX_API const char *ptx_pipeline_ctx_get_name(const ptx_pipeline_ctx_t *ctx); -int ptx_pipeline_ctx_next(const ptx_pipeline_ctx_t *ctx, const void *nextdata, size_t nextsize); +PTX_API int ptx_pipeline_ctx_next(const ptx_pipeline_ctx_t *ctx, const void *nextdata, size_t nextsize) ATTR_WUR; -void ptx_pipeline_ctx_diag(const ptx_pipeline_ctx_t *ctx, int lvl, const char *fmt, ...) ATTR_FORMAT((printf, 3, 4)); /* TODO: pipeline stages should be able to send diagnostic messages */ +PTX_API void ptx_pipeline_ctx_diag(const ptx_pipeline_ctx_t *ctx, int lvl, const char *fmt, ...) ATTR_FORMAT((printf, 3, 4)); /* TODO: pipeline stages should be able to send diagnostic messages */ -int ptx_pipeline_handle(ptx_pipeline_t *pipeline, const void *data, size_t sz); /* TODO: diagnostic */ +PTX_API int ptx_pipeline_handle(ptx_pipeline_t *pipeline, const void *data, size_t sz); /* TODO: diagnostic */ #endif /* include guard */ diff --git a/main.c b/main.c deleted file mode 100644 index 273d797..0000000 --- a/main.c +++ /dev/null @@ -1,140 +0,0 @@ -#include -#include -#include -#include -#include - -#include "pipeline.h" - -#define UNUSED(_v) ((void)(_v)) - -PIPELINE_STAGE static int identity_stage_handle(const ptx_pipeline_ctx_t *ctx, const void *data, size_t sz) -{ - return ptx_pipeline_ctx_next(ctx, data, sz); -} - -PIPELINE_INIT static int debug_stage_init(const char *name, void **ppuser, va_list init_args) -{ - static const char *const messages[] = { "no continue :(", "continue :)" }; - - UNUSED(name); - - int config = !!va_arg(init_args, int); - *ppuser = (void *)(uintptr_t)config; - - printf("Debug stage %s (init): %s\n", name, messages[config]); - - return 0; -} - -PIPELINE_CLEANUP static void debug_stage_cleanup(const char *name, void *puser) -{ - printf("Debug stage %s (cleanup): %p\n", name, puser); -} - -PIPELINE_STAGE static int debug_stage_handle(const ptx_pipeline_ctx_t *ctx, const void *data, size_t sz) -{ - bool cont = !!(uintptr_t)(*ptx_pipeline_ctx_get_user(ctx)); - printf("Debug stage %s:\n Data: %p\n Size: %zu\n", ptx_pipeline_ctx_get_name(ctx), data, sz); - return cont ? ptx_pipeline_ctx_next(ctx, data, sz) : 0; -} - -static const struct ptx_pipeline_stage_funcs identity_stage = { - .init = NULL, - .handler = &identity_stage_handle, - .cleanup = NULL -}; - -static const struct ptx_pipeline_stage_funcs debug_stage = { - .init = &debug_stage_init, - .handler = &debug_stage_handle, - .cleanup = &debug_stage_cleanup -}; - -/* mockup: - * Pipeline signature: D -> D*1 - * int simple_1to1_stage(struct pipeline_ctx *ctx, void *data, size_t len) { - * void *out = ... transform data ...; - * size_t out_len = ^^^; - * return ptx_pipeline_next(ctx, out, out_len); - * } - * - * Pipeline signature: D -> D*(0,n) - * int complex_1tomany_stage(struct pipeline_ctx *ctx, void *data, size_t len) { - * int err; - * for (void *out; has_data(data, len); out = next_data(data, &len)) { - * if (!out) { - * return -1; ... next_data failed somehow ... - * } - * - * if ((err = ptx_pipeline_next(ctx, out, out_len)) < 0) { - * return err; - * } - * } - * return 0; - * } - * - * Pipeline signature: D -> D*1 - * int stateful_stage(struct pipeline_ctx *ctx, void *data, size_t len) { - * my_stage_t *stg = ctx->me; - * stg->stat_in += len; - * return ptx_pipeline_next(ctx, data, len); - * } - * - * Pipeline signature: D -> void (i.e., never calls next) - * int terminal_stage(struct pipeline_ctx *ctx, void *data, size_t len) { - * my_handler_t *handler = ctx->me; - * handler->do_handle(data, len); - * return 0; ... return values should indicate pipeline (i.e., parsing) errors, not application errors ... - * } - */ - -int main(void) { - ptx_pipeline_t *pl = ptx_pipeline_new(1); - if (!pl) { - fputs("Allocation failure: pipeline\n", stderr); - return 1; - } - - if (ptx_pipeline_add_before(pl, &identity_stage, "identity1", NULL) < 0) { - fputs("Failed to add identity1 to pipeline\n", stderr); - return 1; - } - - if (ptx_pipeline_add_after(pl, &debug_stage, "debug1", "identity1", true) < 0) { - fputs("Failed to add debug1 to pipeline\n", stderr); - return 1; - } - - if (ptx_pipeline_add_after(pl, &debug_stage, "debug2", "debug1", false) < 0) { - fputs("Failed to add debug2 to pipeline\n", stderr); - return 1; - } - - printf("Handle returned: %d\n", ptx_pipeline_handle(pl, "sussy", 10)); - - if (ptx_pipeline_remove_stage(pl, "debug1") < 1) { - fputs("Failed to remove debug1 from pipeline\n", stderr); - return 1; - } - - printf("Handle returned: %d\n", ptx_pipeline_handle(pl, "sussy2", 20)); - - ptx_pipeline_free(pl); - - void *mod; - if (!(mod = dlopen("plugins/libm_skibidi.so", RTLD_LAZY | RTLD_LOCAL))) { - printf("dlopen: %s\n", dlerror()); - return 1; - } - - int (*f)(void) = dlsym(mod, "do_something"); - if (!f) { - printf("dlsym: %s\n", dlerror()); - return 1; - } - - printf("f: %d\n", f()); - - return 0; -} diff --git a/meson.build b/meson.build index 03d51a4..e144325 100644 --- a/meson.build +++ b/meson.build @@ -4,21 +4,40 @@ add_project_arguments('-D_XOPEN_SOURCE=700', language : [ 'c' ]) c_comp = meson.get_compiler('c') conf_data = configuration_data() + +if not c_comp.has_function_attribute('visibility:default') \ + or not c_comp.has_function_attribute('visibility:hidden') + error('Your platform isn\'t supported. Use a gcc-like compiler on linux.') +else + conf_data.set('PTX_EXPORT', '__attribute__((visibility("default")))') + conf_data.set('PTX_IMPORT', 'extern') +endif + conf_data.set('HAS_ATTR_WUR', c_comp.has_function_attribute('warn_unused_result')) conf_data.set('HAS_ATTR_ALWAYS_INLINE', c_comp.has_function_attribute('always_inline')) conf_data.set('HAS_ATTR_MALLOC', c_comp.has_function_attribute('malloc')) conf_data.set('HAS_ATTR_FORMAT', c_comp.has_function_attribute('format')) + conf_data.set('HAS_RESTRICT', c_comp.has_type('int *restrict')) -configure_file(input : 'config.h.in', output : 'config.h', configuration : conf_data) + +configure_file(input : 'include/config.h.in', output : 'config.h', configuration : conf_data) conf_include = include_directories('.') src_include = include_directories('include') -executable('ptxmc', - 'pipeline.c', - 'connection.c', - 'main.c', +export_dynamic = [] + +if c_comp.get_argument_syntax() == 'gcc' + if c_comp.has_argument('-fvisibility=hidden') + add_project_arguments('-fvisibility=hidden', language : 'c') + endif +endif + +subdir('src') + +executable('ptxmc', prog_sources, include_directories : [ conf_include, src_include ], - link_args : [ '-rdynamic' ]) + gnu_symbol_visibility : 'hidden', + export_dynamic : true) subdir('plugins') diff --git a/pipeline.c b/pipeline.c deleted file mode 100644 index a5ddbf2..0000000 --- a/pipeline.c +++ /dev/null @@ -1,316 +0,0 @@ -#include "pipeline.h" - -#include -#include -#include -#include -#include - -struct ptx__pipeline_stage_tag -{ - struct ptx_pipeline_stage_funcs impl; - char *name; - void *user; -}; - -struct ptx__pipeline_ctx_tag -{ - ptx_pipeline_t *pipeline; - size_t curidx; -}; - -struct ptx__pipeline_tag -{ - size_t stages_len, stages_cap; - struct ptx__pipeline_stage_tag *stages; - bool handling; -}; - -/* returns: - * -1 : allocation failed - * 0 : nothing was allocated - * 1 : pipeline grown */ -static int ptx__pipeline_reserve(ptx_pipeline_t *pl, size_t req) -{ - assert(pl); - assert(req < SSIZE_MAX); - - if (req < pl->stages_cap) return 0; - - struct ptx__pipeline_stage_tag *temp = realloc(pl->stages, req * sizeof(struct ptx__pipeline_stage_tag)); - if (!temp) return -1; - - pl->stages = temp; - pl->stages_cap = req; - - return 1; -} - -/* allocates a new pipeline, with space for `initial_size' new stages. */ -ATTR_WUR ptx_pipeline_t *ptx_pipeline_new(size_t initial_size) -{ - ptx_pipeline_t *pl = malloc(sizeof(ptx_pipeline_t)); - if (!pl) return NULL; - - pl->stages_len = 0; - pl->stages_cap = 0; - pl->stages = NULL; - pl->handling = false; - - ptx__pipeline_reserve(pl, initial_size < 1 ? 1 : initial_size); - - return pl; -} - -static void ptx__cleanup_pipeline_stage(struct ptx__pipeline_stage_tag *stage) -{ - assert(stage); - - if (stage->impl.cleanup) { - (*stage->impl.cleanup)(stage->name, stage->user); - } - - free(stage->name); - - /* clobber the structure just cuz */ - memset(stage, 0, sizeof(*stage)); -} - -/* frees an existing pipeline stage. note that this will call the cleanup function in the stages as well. */ -void ptx_pipeline_free(ptx_pipeline_t *pl) -{ - if (!pl) return; - - for (size_t idx = 0; idx < pl->stages_len; ++idx) { - ptx__cleanup_pipeline_stage(pl->stages + idx); - } - - free(pl->stages); - free(pl); -} - -static int ptx__init_pipeline_stage( - struct ptx__pipeline_stage_tag *PTX_RESTRICT pstage, - const struct ptx_pipeline_stage_funcs *PTX_RESTRICT stage_impl, - const char *name, - va_list setup_args) -{ - assert(pstage); - assert(stage_impl); - assert(name); - - char *namedup = NULL; - void *user = NULL; - - /* I'd rather try to duplicate the name first. Otherwise, we'd have to clean up the - * stage's user data right after initializing it. */ - namedup = strdup(name); - if (!namedup) goto cleanup; - - if (stage_impl->init && (*stage_impl->init)(name, &user, setup_args) < 0) { - /* setup failed :((( */ - goto cleanup; - } - - /* yay it worked :) */ - pstage->user = user; - pstage->name = namedup; - memcpy(&pstage->impl, stage_impl, sizeof(*stage_impl)); - - return 0; - -cleanup: - free(namedup); - return -1; -} - -/* this will grow a pipeline to fit a new stage. - * this function also shifts entries in `pl->stages' such that `to_add' is an empty stage. */ -static int ptx__pipeline_prepare_insert_stage_at(ptx_pipeline_t *pipeline, size_t to_add) -{ - assert(to_add <= pipeline->stages_len); - if (ptx__pipeline_reserve(pipeline, pipeline->stages_len + 1) < 0) { - return -1; - } - - /* don't perform the move if we wouldn't be moving anything. */ - if (to_add < pipeline->stages_len) { - memmove(pipeline->stages + to_add + 1, - pipeline->stages + to_add, - sizeof(struct ptx__pipeline_stage_tag) * (pipeline->stages_len - to_add)); - } - - /* increment the length of the array here for consistency. - * NOTE: this function still leaves the pipeline in an inconsistent state: - * there is an uninitialized stage at `to_add' :( */ - ++pipeline->stages_len; - - return 0; -} - -static ssize_t ptx__pipeline_find_stage(ptx_pipeline_t *ptx, const char *name) -{ - assert(ptx); - assert(name); - - for (size_t idx = 0; idx < ptx->stages_len; ++idx) { - if (!strcmp(ptx->stages[idx].name, name)) { - assert(idx <= SSIZE_MAX); - return (ssize_t)idx; - } - } - - return -1; -} - -static int ptx__pipeline_add_at( - ptx_pipeline_t *pipeline, - const struct ptx_pipeline_stage_funcs *stage_impl, - const char *name, - size_t idx, - va_list init_args) -{ - struct ptx__pipeline_stage_tag temp_stage; - memset(&temp_stage, 0, sizeof(temp_stage)); - - /* first, initialize the stage itself */ - if (ptx__init_pipeline_stage(&temp_stage, stage_impl, name, init_args) < 0) { - return -1; - } - - /* next, grow the pipeline to fit the new stage */ - if (ptx__pipeline_prepare_insert_stage_at(pipeline, idx) < 0) { - /* uh oh, it failed. clean up the stage we just initialized */ - ptx__cleanup_pipeline_stage(&temp_stage); - return -1; - } - - /* finally, put the stage into the array */ - memcpy(pipeline->stages + idx, &temp_stage, sizeof(temp_stage)); - return 0; -} - -/* adds `stage' with `name' to `pipeline' after `after' * - * returns: number of stages added to pipeline (0 or 1), or -1 if there was an allocation or initialization error. */ -int ptx_pipeline_add_after( - ptx_pipeline_t *pipeline, - const struct ptx_pipeline_stage_funcs *stage, - const char *name, - const char *after, - ...) -{ - assert(pipeline); - assert(!pipeline->handling); - - size_t idx; - - if (after) { - ssize_t sidx = ptx__pipeline_find_stage(pipeline, after); - if (sidx < 0) return 0; - - idx = (size_t)(sidx + 1); /* after */ - } else { - idx = pipeline->stages_len; - } - - va_list init_args; - va_start(init_args, after); - - int ret = ptx__pipeline_add_at(pipeline, stage, name, idx, init_args); - - va_end(init_args); - return ret; -} - -/* adds `stage' with `name' to `pipeline' before `before' - * returns: number of stages added to pipeline (0 or 1), or -1 if there was an allocation or initialization error. */ -int ptx_pipeline_add_before( - ptx_pipeline_t *pipeline, - const struct ptx_pipeline_stage_funcs *stage, - const char *name, - const char *before, - ...) -{ - assert(pipeline); - assert(!pipeline->handling); - - size_t idx; - - if (before) { - ssize_t sidx = ptx__pipeline_find_stage(pipeline, before); - if (sidx < 0) return 0; - - idx = (size_t)sidx; /* before */ - } else { - idx = 0; - } - - va_list init_args; - va_start(init_args, before); - - int ret = ptx__pipeline_add_at(pipeline, stage, name, idx, init_args); - - va_end(init_args); - return ret; -} - -/* returns: number of stages removed from pipeline */ -int ptx_pipeline_remove_stage(ptx_pipeline_t *pipeline, const char *name) -{ - assert(pipeline); - assert(!pipeline->handling); - - ssize_t idx = ptx__pipeline_find_stage(pipeline, name); - if (idx < 0) return 0; - - ptx__cleanup_pipeline_stage(pipeline->stages + idx); - memmove(pipeline->stages + idx, pipeline->stages + idx + 1, sizeof(pipeline->stages[0]) * (pipeline->stages_len - (size_t)idx - 1)); - - --pipeline->stages_len; - return 1; -} - -void **ptx_pipeline_ctx_get_user(const ptx_pipeline_ctx_t *ctx) -{ - assert(ctx); - return &ctx->pipeline->stages[ctx->curidx].user; -} - -const char *ptx_pipeline_ctx_get_name(const ptx_pipeline_ctx_t *ctx) -{ - assert(ctx); - return ctx->pipeline->stages[ctx->curidx].name; -} - -int ptx_pipeline_ctx_next(const ptx_pipeline_ctx_t *ctx, const void *nextdata, size_t nextsize) -{ - assert(ctx->curidx + 1 < ctx->pipeline->stages_len); - - ptx_pipeline_ctx_t new_ctx; - new_ctx.pipeline = ctx->pipeline; - new_ctx.curidx = ctx->curidx + 1; - - assert(new_ctx.pipeline->stages[new_ctx.curidx].impl.handler); - - return (*new_ctx.pipeline->stages[new_ctx.curidx].impl.handler)(&new_ctx, nextdata, nextsize); -} - -int ptx_pipeline_handle(ptx_pipeline_t *pipeline, const void *data, size_t sz) -{ - assert(pipeline); - assert(!pipeline->handling); - - if (pipeline->stages_len == 0) return 0; /* pipeline does nothing... */ - - pipeline->handling = true; - - ptx_pipeline_ctx_t ctx; - ctx.pipeline = pipeline; - ctx.curidx = 0; - - assert(pipeline->stages[0].impl.handler); - int ret = (*pipeline->stages[0].impl.handler)(&ctx, data, sz); - - pipeline->handling = 0; - return ret; -} diff --git a/plugins/meson.build b/plugins/meson.build index efa8e5d..59b5da0 100644 --- a/plugins/meson.build +++ b/plugins/meson.build @@ -1,2 +1,3 @@ -shared_library('m_skibidi', 'skibidi.c', - include_directories : [ conf_include, src_include ]) +shared_module('m_skibidi', 'skibidi.c', + include_directories : [ conf_include, src_include ], + c_args : [ '-DMODULE=1' ]) diff --git a/plugins/skibidi.c b/plugins/skibidi.c index 4fa4c33..d763058 100644 --- a/plugins/skibidi.c +++ b/plugins/skibidi.c @@ -1,7 +1,6 @@ -#define MODULE 1 #include "pipeline.h" -__attribute__((visibility("default"))) int do_something(void) +PTX_EXPORT int do_something(void) { return !!ptx_pipeline_new(4); } diff --git a/src/connection.c b/src/connection.c new file mode 100644 index 0000000..ecbb7fa --- /dev/null +++ b/src/connection.c @@ -0,0 +1,45 @@ +#include "connection.h" +#include "pipeline.h" +#include + +struct ptx_connection *ptx_connection_new(void) +{ + struct ptx_connection *conn = malloc(sizeof(struct ptx_connection)); + if (!conn) return NULL; + + /* initialize these in case we goto cleanup later */ + conn->cli_read = NULL; + conn->cli_write = NULL; + conn->srv_read = NULL; + conn->srv_write = NULL; + + /* do initialization */ + + if (!(conn->cli_read = ptx_pipeline_new(4))) goto cleanup; + if (!(conn->cli_write = ptx_pipeline_new(4))) goto cleanup; + if (!(conn->srv_read = ptx_pipeline_new(4))) goto cleanup; + if (!(conn->srv_write = ptx_pipeline_new(4))) goto cleanup; + + return conn; + +cleanup: + ptx_connection_free(conn); + + return NULL; +} + +void ptx_connection_free(struct ptx_connection *conn) +{ + /* NOTE: this function might be called with a partially-constructed or inconsistent object */ + + if (!conn) return; + + /* do cleanup */ + + ptx_pipeline_free(conn->cli_read); + ptx_pipeline_free(conn->cli_write); + ptx_pipeline_free(conn->srv_read); + ptx_pipeline_free(conn->srv_write); + + free(conn); +} diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..83ed688 --- /dev/null +++ b/src/main.c @@ -0,0 +1,125 @@ +#include +#include +#include +#include + +#include "pipeline.h" + +#define UNUSED(_v) ((void)(_v)) + +PIPELINE_STAGE static int identity_stage_handle(const ptx_pipeline_ctx_t *ctx, const void *data, size_t sz) +{ + return ptx_pipeline_ctx_next(ctx, data, sz); +} + +PIPELINE_INIT static int debug_stage_init(const char *name, void **ppuser, va_list init_args) +{ + static const char *const messages[] = { "no continue :(", "continue :)" }; + + UNUSED(name); + + int config = !!va_arg(init_args, int); + *ppuser = (void *)(uintptr_t)config; + + printf("Debug stage %s (init): %s\n", name, messages[config]); + + return 0; +} + +PIPELINE_CLEANUP static void debug_stage_cleanup(const char *name, void *puser) +{ + printf("Debug stage %s (cleanup): %p\n", name, puser); +} + +PIPELINE_STAGE static int debug_stage_handle(const ptx_pipeline_ctx_t *ctx, const void *data, size_t sz) +{ + bool cont = !!(uintptr_t)(*ptx_pipeline_ctx_get_user(ctx)); + printf("Debug stage %s:\n Data: %p\n Size: %zu\n", ptx_pipeline_ctx_get_name(ctx), data, sz); + return cont ? ptx_pipeline_ctx_next(ctx, data, sz) : 0; +} + +static const struct ptx_pipeline_stage_funcs identity_stage = { + .init = NULL, + .handler = &identity_stage_handle, + .cleanup = NULL +}; + +static const struct ptx_pipeline_stage_funcs debug_stage = { + .init = &debug_stage_init, + .handler = &debug_stage_handle, + .cleanup = &debug_stage_cleanup +}; + +/* mockup: + * Pipeline signature: D -> D*1 + * int simple_1to1_stage(struct pipeline_ctx *ctx, void *data, size_t len) { + * void *out = ... transform data ...; + * size_t out_len = ^^^; + * return ptx_pipeline_next(ctx, out, out_len); + * } + * + * Pipeline signature: D -> D*(0,n) + * int complex_1tomany_stage(struct pipeline_ctx *ctx, void *data, size_t len) { + * int err; + * for (void *out; has_data(data, len); out = next_data(data, &len)) { + * if (!out) { + * return -1; ... next_data failed somehow ... + * } + * + * if ((err = ptx_pipeline_next(ctx, out, out_len)) < 0) { + * return err; + * } + * } + * return 0; + * } + * + * Pipeline signature: D -> D*1 + * int stateful_stage(struct pipeline_ctx *ctx, void *data, size_t len) { + * my_stage_t *stg = ctx->me; + * stg->stat_in += len; + * return ptx_pipeline_next(ctx, data, len); + * } + * + * Pipeline signature: D -> void (i.e., never calls next) + * int terminal_stage(struct pipeline_ctx *ctx, void *data, size_t len) { + * my_handler_t *handler = ctx->me; + * handler->do_handle(data, len); + * return 0; ... return values should indicate pipeline (i.e., parsing) errors, not application errors ... + * } + */ + +int main(void) { + ptx_pipeline_t *pl = ptx_pipeline_new(1); + if (!pl) { + fputs("Allocation failure: pipeline\n", stderr); + return 1; + } + + if (ptx_pipeline_add_before(pl, &identity_stage, "identity1", NULL) < 0) { + fputs("Failed to add identity1 to pipeline\n", stderr); + return 1; + } + + if (ptx_pipeline_add_after(pl, &debug_stage, "debug1", "identity1", true) < 0) { + fputs("Failed to add debug1 to pipeline\n", stderr); + return 1; + } + + if (ptx_pipeline_add_after(pl, &debug_stage, "debug2", "debug1", false) < 0) { + fputs("Failed to add debug2 to pipeline\n", stderr); + return 1; + } + + printf("Handle returned: %d\n", ptx_pipeline_handle(pl, "sussy", 10)); + + if (ptx_pipeline_remove_stage(pl, "debug1") < 1) { + fputs("Failed to remove debug1 from pipeline\n", stderr); + return 1; + } + + printf("Handle returned: %d\n", ptx_pipeline_handle(pl, "sussy2", 20)); + + ptx_pipeline_free(pl); + + return 0; +} diff --git a/src/meson.build b/src/meson.build new file mode 100644 index 0000000..d1a3033 --- /dev/null +++ b/src/meson.build @@ -0,0 +1,5 @@ +prog_sources = files( + 'pipeline.c', + 'connection.c', + 'main.c' +) diff --git a/src/pipeline.c b/src/pipeline.c new file mode 100644 index 0000000..8b54cca --- /dev/null +++ b/src/pipeline.c @@ -0,0 +1,316 @@ +#include "pipeline.h" + +#include +#include +#include +#include +#include + +struct ptx__pipeline_stage_tag +{ + struct ptx_pipeline_stage_funcs impl; + char *name; + void *user; +}; + +struct ptx__pipeline_ctx_tag +{ + ptx_pipeline_t *pipeline; + size_t curidx; +}; + +struct ptx__pipeline_tag +{ + size_t stages_len, stages_cap; + struct ptx__pipeline_stage_tag *stages; + bool handling; +}; + +/* returns: + * -1 : allocation failed + * 0 : nothing was allocated + * 1 : pipeline grown */ +static int ptx__pipeline_reserve(ptx_pipeline_t *pl, size_t req) +{ + assert(pl); + assert(req < SSIZE_MAX); + + if (req < pl->stages_cap) return 0; + + struct ptx__pipeline_stage_tag *temp = realloc(pl->stages, req * sizeof(struct ptx__pipeline_stage_tag)); + if (!temp) return -1; + + pl->stages = temp; + pl->stages_cap = req; + + return 1; +} + +/* allocates a new pipeline, with space for `initial_size' new stages. */ +PTX_API ptx_pipeline_t *ptx_pipeline_new(size_t initial_size) +{ + ptx_pipeline_t *pl = malloc(sizeof(ptx_pipeline_t)); + if (!pl) return NULL; + + pl->stages_len = 0; + pl->stages_cap = 0; + pl->stages = NULL; + pl->handling = false; + + ptx__pipeline_reserve(pl, initial_size < 1 ? 1 : initial_size); + + return pl; +} + +static void ptx__cleanup_pipeline_stage(struct ptx__pipeline_stage_tag *stage) +{ + assert(stage); + + if (stage->impl.cleanup) { + (*stage->impl.cleanup)(stage->name, stage->user); + } + + free(stage->name); + + /* clobber the structure just cuz */ + memset(stage, 0, sizeof(*stage)); +} + +/* frees an existing pipeline stage. note that this will call the cleanup function in the stages as well. */ +PTX_API void ptx_pipeline_free(ptx_pipeline_t *pl) +{ + if (!pl) return; + + for (size_t idx = 0; idx < pl->stages_len; ++idx) { + ptx__cleanup_pipeline_stage(pl->stages + idx); + } + + free(pl->stages); + free(pl); +} + +static int ptx__init_pipeline_stage( + struct ptx__pipeline_stage_tag *PTX_RESTRICT pstage, + const struct ptx_pipeline_stage_funcs *PTX_RESTRICT stage_impl, + const char *name, + va_list setup_args) +{ + assert(pstage); + assert(stage_impl); + assert(name); + + char *namedup = NULL; + void *user = NULL; + + /* I'd rather try to duplicate the name first. Otherwise, we'd have to clean up the + * stage's user data right after initializing it. */ + namedup = strdup(name); + if (!namedup) goto cleanup; + + if (stage_impl->init && (*stage_impl->init)(name, &user, setup_args) < 0) { + /* setup failed :((( */ + goto cleanup; + } + + /* yay it worked :) */ + pstage->user = user; + pstage->name = namedup; + memcpy(&pstage->impl, stage_impl, sizeof(*stage_impl)); + + return 0; + +cleanup: + free(namedup); + return -1; +} + +/* this will grow a pipeline to fit a new stage. + * this function also shifts entries in `pl->stages' such that `to_add' is an empty stage. */ +static int ptx__pipeline_prepare_insert_stage_at(ptx_pipeline_t *pipeline, size_t to_add) +{ + assert(to_add <= pipeline->stages_len); + if (ptx__pipeline_reserve(pipeline, pipeline->stages_len + 1) < 0) { + return -1; + } + + /* don't perform the move if we wouldn't be moving anything. */ + if (to_add < pipeline->stages_len) { + memmove(pipeline->stages + to_add + 1, + pipeline->stages + to_add, + sizeof(struct ptx__pipeline_stage_tag) * (pipeline->stages_len - to_add)); + } + + /* increment the length of the array here for consistency. + * NOTE: this function still leaves the pipeline in an inconsistent state: + * there is an uninitialized stage at `to_add' :( */ + ++pipeline->stages_len; + + return 0; +} + +static ssize_t ptx__pipeline_find_stage(ptx_pipeline_t *ptx, const char *name) +{ + assert(ptx); + assert(name); + + for (size_t idx = 0; idx < ptx->stages_len; ++idx) { + if (!strcmp(ptx->stages[idx].name, name)) { + assert(idx <= SSIZE_MAX); + return (ssize_t)idx; + } + } + + return -1; +} + +static int ptx__pipeline_add_at( + ptx_pipeline_t *pipeline, + const struct ptx_pipeline_stage_funcs *stage_impl, + const char *name, + size_t idx, + va_list init_args) +{ + struct ptx__pipeline_stage_tag temp_stage; + memset(&temp_stage, 0, sizeof(temp_stage)); + + /* first, initialize the stage itself */ + if (ptx__init_pipeline_stage(&temp_stage, stage_impl, name, init_args) < 0) { + return -1; + } + + /* next, grow the pipeline to fit the new stage */ + if (ptx__pipeline_prepare_insert_stage_at(pipeline, idx) < 0) { + /* uh oh, it failed. clean up the stage we just initialized */ + ptx__cleanup_pipeline_stage(&temp_stage); + return -1; + } + + /* finally, put the stage into the array */ + memcpy(pipeline->stages + idx, &temp_stage, sizeof(temp_stage)); + return 0; +} + +/* adds `stage' with `name' to `pipeline' after `after' * + * returns: number of stages added to pipeline (0 or 1), or -1 if there was an allocation or initialization error. */ +PTX_API int ptx_pipeline_add_after( + ptx_pipeline_t *pipeline, + const struct ptx_pipeline_stage_funcs *stage, + const char *name, + const char *after, + ...) +{ + assert(pipeline); + assert(!pipeline->handling); + + size_t idx; + + if (after) { + ssize_t sidx = ptx__pipeline_find_stage(pipeline, after); + if (sidx < 0) return 0; + + idx = (size_t)(sidx + 1); /* after */ + } else { + idx = pipeline->stages_len; + } + + va_list init_args; + va_start(init_args, after); + + int ret = ptx__pipeline_add_at(pipeline, stage, name, idx, init_args); + + va_end(init_args); + return ret; +} + +/* adds `stage' with `name' to `pipeline' before `before' + * returns: number of stages added to pipeline (0 or 1), or -1 if there was an allocation or initialization error. */ +PTX_API int ptx_pipeline_add_before( + ptx_pipeline_t *pipeline, + const struct ptx_pipeline_stage_funcs *stage, + const char *name, + const char *before, + ...) +{ + assert(pipeline); + assert(!pipeline->handling); + + size_t idx; + + if (before) { + ssize_t sidx = ptx__pipeline_find_stage(pipeline, before); + if (sidx < 0) return 0; + + idx = (size_t)sidx; /* before */ + } else { + idx = 0; + } + + va_list init_args; + va_start(init_args, before); + + int ret = ptx__pipeline_add_at(pipeline, stage, name, idx, init_args); + + va_end(init_args); + return ret; +} + +/* returns: number of stages removed from pipeline */ +PTX_API int ptx_pipeline_remove_stage(ptx_pipeline_t *pipeline, const char *name) +{ + assert(pipeline); + assert(!pipeline->handling); + + ssize_t idx = ptx__pipeline_find_stage(pipeline, name); + if (idx < 0) return 0; + + ptx__cleanup_pipeline_stage(pipeline->stages + idx); + memmove(pipeline->stages + idx, pipeline->stages + idx + 1, sizeof(pipeline->stages[0]) * (pipeline->stages_len - (size_t)idx - 1)); + + --pipeline->stages_len; + return 1; +} + +PTX_API void **ptx_pipeline_ctx_get_user(const ptx_pipeline_ctx_t *ctx) +{ + assert(ctx); + return &ctx->pipeline->stages[ctx->curidx].user; +} + +PTX_API const char *ptx_pipeline_ctx_get_name(const ptx_pipeline_ctx_t *ctx) +{ + assert(ctx); + return ctx->pipeline->stages[ctx->curidx].name; +} + +PTX_API int ptx_pipeline_ctx_next(const ptx_pipeline_ctx_t *ctx, const void *nextdata, size_t nextsize) +{ + assert(ctx->curidx + 1 < ctx->pipeline->stages_len); + + ptx_pipeline_ctx_t new_ctx; + new_ctx.pipeline = ctx->pipeline; + new_ctx.curidx = ctx->curidx + 1; + + assert(new_ctx.pipeline->stages[new_ctx.curidx].impl.handler); + + return (*new_ctx.pipeline->stages[new_ctx.curidx].impl.handler)(&new_ctx, nextdata, nextsize); +} + +PTX_API int ptx_pipeline_handle(ptx_pipeline_t *pipeline, const void *data, size_t sz) +{ + assert(pipeline); + assert(!pipeline->handling); + + if (pipeline->stages_len == 0) return 0; /* pipeline does nothing... */ + + pipeline->handling = true; + + ptx_pipeline_ctx_t ctx; + ctx.pipeline = pipeline; + ctx.curidx = 0; + + assert(pipeline->stages[0].impl.handler); + int ret = (*pipeline->stages[0].impl.handler)(&ctx, data, sz); + + pipeline->handling = 0; + return ret; +} -- cgit v1.2.3-70-g09d2