/** * arif/src/arify.c - ARIF preload library * ---- * * Copyright (C) 2023 CismonX * * This file is part of ARIF, Another Readline Input Framework. * * ARIF is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * ARIF is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with ARIF. If not, see . */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include "arify.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "arif.h" #include "arif_defs.h" #ifdef HAVE_READLINE # include "arify_rl.h" #endif #ifdef HAVE_FUNC_ATTRIBUTE_CONSTRUCTOR # define ARIF_CTOR __attribute__((constructor)) #else # error "__attribute__((constructor)) not supported" #endif #ifdef HAVE_FUNC_ATTRIBUTE_DESTRUCTOR # define ARIF_DTOR __attribute__((destructor)) #else # error "__attribute__((destructor)) not supported" #endif #define ARIFY_ENGINE_PATH_(libdir, engine) \ #libdir "/arif/" engine ARIF_SHLIB_SUFFIX #define ARIFY_ENGINE_PATH(libdir, engine) ARIFY_ENGINE_PATH_(libdir, engine) #define ARIFY_ENGINE_SYM(engine) "arif_" engine "_engine" #ifndef ARIFY_MAX_PAGE_SIZE # define ARIFY_MAX_PAGE_SIZE 99 #endif #ifndef ARIFY_LOG_TIME_MAXLEN # define ARIFY_LOG_TIME_MAXLEN 16 #endif struct arify_engine { char const *lib_name; char const *var_name; void *handle; struct arif_engine *impl; void *data; struct arify_engine *next; }; // Forward declaration start static int config_engines (void); static int config_frontend (void); static int config_log_file (void); static int config_page_size (void); static char const * current_time (void); static void finalize (void) ARIF_DTOR; static void finalize_engines (struct arify_engine *); static char const * get_env (char const *, char const *); static int init_config (void); static void initialize (void) ARIF_CTOR; static int load_engine (struct arify_engine *); // Forward declaration end struct arify_ctx { struct arify_engine *engines; struct arify_engine *current_engine; char *engines_str; struct arify_frontend const *frontend; void *frontend_data; struct arif_ctx *ctx; FILE *log_file; int log_fd; int page_size; long pid; }; static struct arify_ctx ctx; static int config_engines (void) { char const *engines_str = get_env("ARIFY_ENGINES", ""); size_t engines_len = strlen(engines_str) + 1; char *names = malloc(sizeof(char) * engines_len); assert(names != NULL); ctx.engines_str = memcpy(names, engines_str, engines_len); char const *delim = " \n\t\r\v\f"; struct arify_engine dummy_engine = { .next = NULL }; struct arify_engine *engine = &dummy_engine; for (char *name = strtok(names, delim); name != NULL; name = strtok(NULL, delim) ) { engine = engine->next = malloc(sizeof(struct arify_engine)); assert(engine != NULL); char *var_name = strchr(name, ':'); if (var_name != NULL) { *(var_name++) = '\0'; } *engine = (struct arify_engine) { .lib_name = name, .var_name = var_name, }; } ctx.current_engine = ctx.engines = dummy_engine.next; if (ctx.engines == NULL) { arify_err_printf("%s", "at least one engine should be specified"); return -1; } return 0; } static int config_frontend (void) { char const *frontend_str = get_env("ARIFY_FRONTEND", "readline"); #ifdef HAVE_READLINE if (0 == strcmp("readline", frontend_str)) { ctx.frontend = &arify_frontend_readline; return 0; } #endif // defined(HAVE_READLINE) arify_err_printf("unsupported frontend '%s'", frontend_str); return -1; } static int config_log_file (void) { char const *log_file_path = get_env("ARIFY_LOG_FILE", "/dev/null"); ctx.log_file = fopen(log_file_path, "a"); if (ctx.log_file == NULL) { return -1; } setvbuf(ctx.log_file, NULL, _IOLBF, 0); int fd = fileno(ctx.log_file); if (fd < 0) { return -1; } ctx.log_fd = fd; ctx.pid = (long) getpid(); return 0; } static int config_page_size (void) { char const *page_size_str = get_env("ARIFY_PAGE_SIZE", "5"); errno = 0; long page_size = strtol(page_size_str, NULL, 10); if (errno != 0) { arify_err_printf("invalid page size '%s'", page_size_str); return -1; } if (page_size < 1 || page_size > ARIFY_MAX_PAGE_SIZE) { arify_err_printf("bad page size %d, should be in range 1~%d", page_size, ARIFY_MAX_PAGE_SIZE); return -1; } ctx.page_size = page_size; return 0; } static char const * current_time (void) { static char buf[ARIFY_LOG_TIME_MAXLEN]; time_t ts = time(NULL); assert(ts != ((time_t) -1)); struct tm const *tm = localtime(&ts); assert(tm != NULL); strftime(buf, ARIFY_LOG_TIME_MAXLEN, "%T", tm); return buf; } static void finalize (void) { if (ctx.frontend != NULL) { ctx.frontend->finalize(ctx.frontend_data); } finalize_engines(ctx.engines); arif_ctx_destroy(ctx.ctx); free(ctx.engines_str); arify_debug_printf("%s", "finalized"); if (ctx.log_file != NULL) { fclose(ctx.log_file); } } static void finalize_engines ( struct arify_engine *engine ) { for (struct arify_engine *next; engine != NULL; engine = next) { if (engine->impl != NULL) { engine->impl->finalize(engine->data); } if (engine->handle != NULL) { dlclose(engine->handle); } next = engine->next; free(engine); } } static char const * get_env ( char const *name, char const *default_val ) { char const *val = getenv(name); if (val == NULL || val[0] == '\0') { val = default_val; } return val; } static int init_config (void) { if (0 != config_log_file()) { return -1; } if (0 != config_frontend()) { return -1; } if (0 != config_engines()) { return -1; } if (0 != config_page_size()) { return -1; } return 0; } static void initialize (void) { if (0 != init_config()) { return; } struct arif_opts opts = { .page_size = ctx.page_size, }; ctx.ctx = arif_ctx_create(&opts); if (ctx.ctx == NULL) { arify_err_printf("%s", "failed to create input context"); return; } int result = ctx.frontend->init(ctx.ctx, &ctx.frontend_data); if (result != 0) { arify_err_printf("frontend init failed with return value %d", result); return; } arify_debug_printf("%s", "initialized"); } static int load_engine ( struct arify_engine *engine ) { char const *lib_name = engine->lib_name; char const *var_name = engine->var_name; char *lib_tmp = NULL; char *var_tmp = NULL; if (var_name == NULL) { size_t lib_name_len = sizeof ARIFY_ENGINE_PATH(ARIF_LIBDIR, "") + strlen(lib_name); lib_tmp = malloc(sizeof(char) * lib_name_len); assert(lib_tmp != NULL); sprintf(lib_tmp, ARIFY_ENGINE_PATH(ARIF_LIBDIR, "%s"), lib_name); size_t var_name_len = sizeof ARIFY_ENGINE_SYM("") + strlen(lib_name); var_tmp = malloc(sizeof(char) * var_name_len); assert(var_tmp != NULL); sprintf(var_tmp, ARIFY_ENGINE_SYM("%s"), lib_name); lib_name = lib_tmp; var_name = var_tmp; } int result = -1; void *handle = dlopen(lib_name, RTLD_NOW); if (handle == NULL) { arify_err_printf("failed to load engine %s: %s", lib_name, dlerror()); goto finish; } engine->handle = handle; struct arif_engine *engine_impl = dlsym(handle, var_name); if (engine_impl == NULL) { arify_err_printf("failed to find symbol %s for engine %s: %s", var_name, lib_name, dlerror()); goto finish; } int init_status = engine_impl->init(NULL, &engine->data); if (init_status != 0) { arify_err_printf("failed to initialize engine %s: %d", lib_name, init_status); goto finish; } engine->impl = engine_impl; result = 0; finish: free(lib_tmp); free(var_tmp); return result; } void arify_log_printf ( char const *fmt, char const *level, ... ) { struct flock lock = { .l_start = SEEK_SET, .l_len = 0, }; lock.l_type = F_WRLCK; fcntl(ctx.log_fd, F_SETLKW, &lock); fprintf(ctx.log_file, "[%s %ld %s] ", current_time(), ctx.pid, level); va_list args; va_start(args, level); vfprintf(ctx.log_file, fmt, args); va_end(args); fputc('\n', ctx.log_file); lock.l_type = F_UNLCK; fcntl(ctx.log_fd, F_SETLK, &lock); } struct arif_engine const * arify_next_engine ( void **engine_data_ptr ) { struct arify_engine *engine = ctx.current_engine; struct arif_engine *engine_impl = NULL; void *engine_data = NULL; while (engine != NULL) { ARIF_DEBUG_ASSERT(engine->impl == NULL); int result = load_engine(engine); engine_impl = engine->impl; engine_data = engine->data; engine = engine->next; if (result == 0) { break; } } ctx.current_engine = engine; *engine_data_ptr = engine_data; return engine_impl; }