//roarpluginrunner.c: /* * Copyright (C) Philipp 'ph3-der-loewe' Schafft - 2011-2014 * * This file is part of roarclients a part of RoarAudio, * a cross-platform sound system for both, home and professional use. * See README for details. * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 * as published by the Free Software Foundation. * * RoarAudio 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 this software; see the file COPYING. If not, write to * the Free Software Foundation, 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * */ int g_verbose = 0; #define ROAR_DBG_INFOVAR g_verbose #include enum action { RUN, RUN_AS_APPLICATION, EXPLAIN }; #define OPTION_NONE 0x0000 #define OPTION_TOUCH 0x0001 #define OPTION_ABOUT 0x0100 #define OPTION_HELP 0x0200 #define OPTION_PREFERENCES 0x0400 static struct roar_dl_librarypara * g_para = NULL; static struct roar_scheduler * g_sched = NULL; static struct roar_scheduler_source g_s_service = {.type = ROAR_SCHEDULER_CPI_SERVICE}; static void usage (const char * progname) { fprintf(stderr, "Usage: %s [OPTIONS]... PLUGIN\n", progname); fprintf(stderr, "\nOptions:\n\n"); fprintf(stderr, " -h --help - This help.\n" " -v --verbose - Be verbose. Can be used multiple times.\n" " --server SERVER - Set default server to SERVER.\n" " --run - Run plugin.\n" " --run-as-application - Same as --run except all tailing arguments are\n" " passed to the plugin.\n" " --explain - Explain plugin.\n" " --appname NAME - Sets the appname.\n" " --abiversion ABI - Set the ABI version.\n" " --args ARGS - Set plugin arguments.\n" ); fprintf(stderr, "\nRunner Options:\n\n"); fprintf(stderr, " --option-touch - Do not keep the plugin running:\n" " Do a single UPDATE cycle.\n" " --option-no-touch - Disable touch option.\n" " --option-about - Show an about dialog after startup.\n" " --option-no-about - Disable about option.\n" " --option-help - Show onlion help after startup.\n" " --option-no-help - Disable help option.\n" " --option-preferences - Show preferences dialog after startup.\n" " --option-no-preferences\n" " - Disable preferences option.\n" ); fprintf(stderr, "\nCPI Options:\n\n"); fprintf(stderr, " -t --tcp - Use TCP listen socket\n" " -u --unix - Use UNIX Domain listen socket (default)\n" " -n --decnet - use DECnet listen socket\n" " --port PORT - TCP Port to bind to\n" " --bind ADDR - Node/Hostname/Path to bind to\n" " --proto PROTO - Use PROTO as protocol on Socket\n" " --new-sock - Parameters for new socket follow\n" " --client-fh FH - Comunicate with a client over this handle\n" "\n" ); } static int do_run(const char * name, int options) { struct roar_scheduler_source s_container; struct roar_plugincontainer * cont = roar_plugincontainer_new(g_para); int err; if ( cont == NULL ) return -1; memset(&s_container, 0, sizeof(s_container)); s_container.type = ROAR_SCHEDULER_PLUGINCONTAINER; s_container.handle.container = cont; roar_scheduler_source_add(g_sched, &s_container); if ( roar_plugincontainer_load(cont, name, NULL) == -1 ) { err = roar_error; roar_scheduler_source_del(g_sched, &s_container); roar_plugincontainer_unref(cont); roar_error = err; return -1; } roar_plugincontainer_appsched_trigger(cont, ROAR_DL_APPSCHED_INIT); if ( options & OPTION_ABOUT ) { if ( roar_plugincontainer_appsched_trigger(cont, ROAR_DL_APPSCHED_ABOUT) == -1 ) { ROAR_WARN("Can not call ABOUT trigger: %s", roar_errorstring); } } if ( options & OPTION_HELP ) { if ( roar_plugincontainer_appsched_trigger(cont, ROAR_DL_APPSCHED_HELP) == -1 ) { ROAR_WARN("Can not call HELP trigger: %s", roar_errorstring); } } if ( options & OPTION_PREFERENCES ) { if ( roar_plugincontainer_appsched_trigger(cont, ROAR_DL_APPSCHED_PREFERENCES) == -1 ) { ROAR_WARN("Can not call PREFERENCES trigger: %s", roar_errorstring); } } if ( !(options & OPTION_TOUCH) ) roar_scheduler_run(g_sched); roar_plugincontainer_appsched_trigger(cont, ROAR_DL_APPSCHED_UPDATE); roar_plugincontainer_appsched_trigger(cont, ROAR_DL_APPSCHED_FREE); roar_scheduler_source_del(g_sched, &s_container); roar_plugincontainer_unref(cont); return 0; } static const char * _ptr2str(const void * p) { #if defined(ROAR_HAVE_H_DLFCN) && defined(ROAR_HAVE_DLADDR) static char buf[80]; Dl_info info; #else static char buf[24]; #endif if ( p == NULL ) return ""; #if defined(ROAR_HAVE_H_DLFCN) && defined(ROAR_HAVE_DLADDR) if ( dladdr(p, &info) != 0 ) { if ( p == info.dli_saddr ) { snprintf(buf, sizeof(buf), "%p <%s in \"%s\">", p, info.dli_sname, info.dli_fname); return buf; } } #endif snprintf(buf, sizeof(buf), "%p", p); return buf; } static const char * _ptrrange2str(const void * p, size_t len) { static char buf[80]; if ( p == NULL ) return ""; if ( len == 0 ) return _ptr2str(p); snprintf(buf, sizeof(buf), "%p-%p", p, p + len); return buf; } static int do_explain(const char * name) { struct roar_dl_lhandle * lhandle = roar_dl_open(name, ROAR_DL_FLAG_LAZY|ROAR_DL_FLAG_PLUGINPATH, 0, g_para); struct roar_dl_libraryinst * (*func)(struct roar_dl_librarypara * para); struct roar_dl_libraryinst * lib; int libok = 0, libnameok = 0, libdepok = 0; int tmp; int i; size_t iter; char c; if ( lhandle == NULL ) return -1; func = roar_dl_getsym(lhandle, "_roaraudio_library_init", -1); if ( func == NULL ) { fprintf(stderr, "Warning: Not a RA lib: %s\n", name); roar_dl_unref(lhandle); return 0; } lib = func(g_para); if ( lib == NULL ) { fprintf(stderr, "Warning: Can not RA init: %s: %s\n", name, roar_error2str(roar_error)); roar_dl_unref(lhandle); return 0; } if ( lib->version == ROAR_DL_LIBINST_VERSION && lib->len == sizeof(*lib) ) { libok = 1; } printf("lib = %s\n", _ptr2str(lib)); if ( g_verbose || !libok ) { printf("|-> version = %i (%smatch)\n", lib->version, lib->version == ROAR_DL_LIBINST_VERSION ? "" : "no "); printf("%c-> len = %zu (%smatch)\n", libok ? '|' : '\\', lib->len, lib->len == sizeof(*lib) ? "" : "no "); } if ( libok ) { if ( g_verbose || lib->unload != NULL ) printf("|-> unload = %s\n", _ptr2str(lib->unload)); printf("|-> func = {"); i = 0; while (i < ROAR_DL_FN_MAX) { for (tmp = 0; i < ROAR_DL_FN_MAX; i++) { if ( lib->func[i] != NULL ) { break; } tmp++; } if (tmp) printf("%i x %s", tmp, i < (ROAR_DL_FN_MAX-1) ? ", " : ""); if ( i < ROAR_DL_FN_MAX ) { printf("[%i] = %s%s", i, _ptr2str(lib->func[i]), i < (ROAR_DL_FN_MAX-1) ? ", " : ""); i++; } } printf("}\n"); printf("|-> libname = %s\n", _ptr2str(lib->libname)); if ( lib->libname != NULL ) { if ( lib->libname->version == ROAR_DL_LIBNAME_VERSION && lib->libname->len == sizeof(*(lib->libname)) ) { libnameok = 1; } if ( g_verbose || !libnameok ) { printf("| |-> version = %i (%smatch)\n", lib->libname->version, lib->libname->version == ROAR_DL_LIBNAME_VERSION ? "" : "no "); printf("| %c-> len = %zu (%smatch)\n", libnameok ? '|' : '\\', lib->libname->len, lib->libname->len == sizeof(*(lib->libname)) ? "" : "no "); } if ( libnameok ) { #define _ps(k,name) \ tmp = (lib->libname->name) != NULL; \ if ( tmp || g_verbose || (k) == '\\' ) \ printf("| %c-> %-15s = %s%s%s\n", (k), #name, tmp ? "\"" : "", \ tmp ? (lib->libname->name) : "", tmp ? "\"" : ""); _ps('|', name); _ps('|', libname); _ps('|', libversion); _ps('|', abiversion); _ps('|', description); _ps('|', contact); _ps('|', authors); _ps('\\', license); #undef _ps } } if ( g_verbose || lib->global_data_pointer != NULL ) printf("|-> global_data_len = %zu\n", lib->global_data_len); if ( g_verbose || lib->global_data_init != NULL ) printf("|-> global_data_init = %s\n", _ptrrange2str(lib->global_data_init, lib->global_data_len)); if ( g_verbose || lib->global_data_pointer != NULL ) printf("|-> global_data_pointer = %s\n", _ptr2str(lib->global_data_pointer)); if ( lib->libdep != NULL && lib->libdep_len ) { printf("|-> libdep = %s\n", _ptr2str(lib->libdep)); for (iter = 0; iter < lib->libdep_len; iter++) { printf("| %c-> Table entry %zu = %p\n", iter == (lib->libdep_len-1) ? '\\' : '|', iter, &(lib->libdep[iter])); c = iter == (lib->libdep_len-1) ? ' ' : '|'; if ( lib->libdep[iter].version == ROAR_DL_LIBDEP_VERSION && lib->libdep[iter].len == sizeof(struct roar_dl_librarydep) ) { libdepok = 1; } else { libdepok = 0; } if ( g_verbose || !libdepok ) { printf("| %c |-> version = %i (%smatch)\n", c, lib->libdep[iter].version, lib->libdep[iter].version == ROAR_DL_LIBDEP_VERSION ? "" : "no "); printf("| %c %c-> len = %zu (%smatch)\n", c, libdepok ? '|' : '\\', lib->libdep[iter].len, lib->libdep[iter].len == sizeof(struct roar_dl_librarydep) ? "" : "no "); } if ( libdepok ) { printf("| %c |-> flags = 0x%.8lX\n", c, (unsigned long int)lib->libdep[iter].flags); #define _ps(k,name) \ tmp = (lib->libdep[iter].name) != NULL; \ if ( tmp || g_verbose || (k) == '\\' ) \ printf("| %c %c-> %-11s = %s%s%s\n", c, (k), #name, tmp ? "\"" : "", \ tmp ? (lib->libdep[iter].name) : "", tmp ? "\"" : ""); _ps('|', name); _ps('|', libname); _ps('\\', abiversion); #undef _ps } } printf("|-> libdep_len = %zu\n", lib->libdep_len); } else if ( (lib->libdep == NULL && lib->libdep_len) || (lib->libdep != NULL && !lib->libdep_len) ) { printf("|-> libdep = %s (invalid)\n", _ptr2str(lib->libdep)); printf("|-> libdep_len = %zu (invalid)\n", lib->libdep_len); } if ( g_verbose || lib->appsched != NULL ) { printf("|-> appsched = %s\n", _ptr2str(lib->appsched)); if ( lib->appsched != NULL ) { if ( g_verbose || lib->appsched->init != NULL ) printf("| |-> init = %s\n", _ptr2str(lib->appsched->init)); if ( g_verbose || lib->appsched->free != NULL ) printf("| |-> free = %s\n", _ptr2str(lib->appsched->free)); if ( g_verbose || lib->appsched->update != NULL ) printf("| |-> update = %s\n", _ptr2str(lib->appsched->update)); if ( g_verbose || lib->appsched->tick != NULL ) printf("| |-> tick = %s\n", _ptr2str(lib->appsched->tick)); printf("| \\-> wait = %s\n", _ptr2str(lib->appsched->wait)); } } #define _ps(k,name) \ tmp = (lib->name) != NULL; \ if ( tmp || g_verbose || (k) == '\\' ) \ printf("%c-> %-19s = %s%s%s\n", (k), #name, tmp ? "\"" : "", \ tmp ? (lib->name) : "", tmp ? "\"" : ""); _ps('|', host_appname); _ps('\\', host_abiversion); #undef _ps } roar_dl_unref(lhandle); return 0; } static int do_plugin(enum action action, const char * name, int options) { switch (action) { case EXPLAIN: return do_explain(name); break; case RUN: case RUN_AS_APPLICATION: return do_run(name, options); break; default: roar_err_set(ROAR_ERROR_BADRQC); return -1; break; } } static inline void _clear_para(void) { if ( g_para == NULL ) return; roar_dl_para_unref(g_para); g_para = NULL; } static inline int _add_para(struct roar_dl_librarypara * para, const char * pluginargs, size_t argc, char * argv[]) { struct roar_keyval * kv; ssize_t argslen, argvlen; ssize_t arglen; ssize_t pluginargc; size_t argv_phys = argc; size_t i; int error; char * sp, * c; int after_parser_end; if ( pluginargs == NULL ) pluginargs = ""; argslen = roar_mm_strlen(pluginargs) + 1 /* tailing '\0' */; after_parser_end = 0; argvlen = 0; for (i = 0; i < argv_phys; i++) { arglen = roar_mm_strlen(argv[i]); argvlen += arglen; if ( !after_parser_end ) { if ( !strcmp(argv[i], "--") ) { after_parser_end = 1; argc--; } else if ( arglen > 1 && argv[i][0] == '-' && !(arglen > 2 && argv[i][1] == '-') ) { argc += arglen - 2; } } } argvlen += argc; // the '\0's. para->args_store = roar_mm_malloc(argslen+argvlen); if ( para == NULL ) return -1; memcpy(para->args_store, pluginargs, argslen); pluginargc = roar_keyval_split(&kv, para->args_store, NULL, NULL, 1); if ( pluginargc == -1 ) { error = roar_error; roar_mm_free(para->args_store); para->args_store = NULL; roar_error = error; return -1; } para->argv = roar_mm_malloc((pluginargc+argc)*sizeof(struct roar_keyval)); if ( para->argv == NULL ) { error = roar_error; roar_mm_free(kv); roar_mm_free(para->args_store); para->args_store = NULL; roar_error = error; return -1; } para->argc = pluginargc + argc; memcpy(para->argv, kv, pluginargc*sizeof(struct roar_keyval)); roar_mm_free(kv); sp = para->args_store + argslen; kv = para->argv + pluginargc; after_parser_end = 0; for (i = 0; i < argv_phys; i++) { arglen = roar_mm_strlen(argv[i]) + 1; if ( after_parser_end || !(arglen > 2 && argv[i][0] == '-' && !(arglen > 3 && argv[i][1] == '-')) ) memcpy(sp, argv[i], arglen); if ( !after_parser_end && !strcmp(argv[i], "--") ) { after_parser_end = 1; continue; } else if ( !after_parser_end && arglen > 3 && sp[0] == '-' && sp[1] == '-' ) { kv->key = sp + 2; kv->value = NULL; for (c = sp + 2; *c; c++) { if (*c == '=') { *c = 0; c++; kv->value = c; break; } } } else if ( !after_parser_end && arglen > 2 && argv[i][0] == '-' ) { for (c = argv[i] + 1; *c; c++) { sp[0] = *c; sp[1] = 0; kv->key = sp; kv->value = NULL; sp += 2; kv++; } continue; } else { kv->key = NULL; kv->value = sp; } sp += arglen; kv++; } return 0; } static struct roar_scheduler_source * add_cpi_common(int proto) { struct roar_scheduler_source * source; int err; if ( proto == -1 ) { roar_err_set(ROAR_ERROR_INVAL); return NULL; } source = roar_mm_malloc(sizeof(struct roar_scheduler_source)); if ( source == NULL ) return NULL; memset(source, 0, sizeof(struct roar_scheduler_source)); source->vio = roar_mm_malloc(sizeof(struct roar_vio_calls)); if ( source->vio == NULL ) { err = roar_error; roar_mm_free(source); roar_error = err; return NULL; } source->flags = ROAR_SCHEDULER_FLAG_FREE; source->handle.cpi.proto = proto; return source; } static int add_cpi_listen(int proto, int type, const char * host, int port) { struct roar_scheduler_source * source = add_cpi_common(proto); int err; if ( source == NULL ) return -1; if ( roar_vio_open_socket_listen(source->vio, type, host, port) == -1 ) { err = roar_error; roar_mm_free(source->vio); roar_mm_free(source); roar_error = err; return -1; } source->type = ROAR_SCHEDULER_CPI_LISTEN; source->vio->flags |= ROAR_VIO_FLAGS_FREESELF; if ( roar_scheduler_source_add(g_sched, source) == -1 ) { err = roar_error; roar_vio_close(source->vio); roar_mm_free(source); roar_error = err; return -1; } return 0; } static int add_cpi_client(int proto, int fh) { struct roar_scheduler_source * source = add_cpi_common(proto); struct roar_dl_librarypara * para; int err; if ( source == NULL ) return -1; if ( roar_vio_open_fh(source->vio, fh) == -1 ) { err = roar_error; roar_mm_free(source->vio); roar_mm_free(source); roar_error = err; return -1; } source->type = ROAR_SCHEDULER_CPI_CLIENT; source->vio->flags |= ROAR_VIO_FLAGS_FREESELF; if ( roar_scheduler_source_add(g_sched, source) == -1 ) { err = roar_error; roar_vio_close(source->vio); roar_mm_free(source); roar_error = err; return -1; } if ( source->flags & ROAR_SCHEDULER_FLAG_STUB ) { ROAR_ERR("Can not handle proto STUB clients. Try to use --client-fh as last argument."); roar_scheduler_source_del(g_sched, source); return -1; } if ( source->handle.cpi.impl->set_proto != NULL ) { para = roar_dl_getpara(source->lhandle); if ( source->lhandle != NULL ) roar_dl_context_restore(source->lhandle); source->handle.cpi.impl->set_proto(source->handle.cpi.client, source->vio, &(source->handle.cpi.obuffer), &(source->handle.cpi.userdata), source->handle.cpi.protopara, source->handle.cpi.protoparalen, para); if ( source->lhandle != NULL ) roar_dl_context_store(source->lhandle); if ( para != NULL ) roar_dl_para_unref(para); } return 0; } int main (int argc, char * argv[]) { const char * appname = "roarpluginrunner " ROAR_VSTR_ROARAUDIO; const char * abiversion = "1.0beta0"; const char * pluginargs = NULL; enum action action = RUN; int ret = 0; int i; const char * k; int cpi_type = ROAR_SOCKET_TYPE_UNKNOWN; int cpi_proto = -1; int cpi_port = 0; int cpi_touched = 0; const char * cpi_host = NULL; int options = OPTION_NONE; g_sched = roar_scheduler_new(ROAR_SCHEDULER_FLAG_NONE, ROAR_SCHEDULER_STRATEGY_DEFAULT); if ( g_sched == NULL ) { fprintf(stderr, "Error creating scheduler object: %s\n", roar_error2str(roar_error)); return 1; } roar_scheduler_source_add(g_sched, &g_s_service); for (i = 1; i < argc; i++) { k = argv[i]; if ( !strcmp(k, "-h") || !strcmp(k, "--help") ) { usage(argv[0]); roar_scheduler_unref(g_sched); return 0; } else if ( !strcmp(k, "--run") ) { action = RUN; } else if ( !strcmp(k, "--run-as-application") ) { action = RUN_AS_APPLICATION; } else if ( !strcmp(k, "--explain") ) { action = EXPLAIN; } else if ( !strcmp(k, "-v") || !strcmp(k, "--verbose") ) { g_verbose++; } else if ( !strcmp(k, "--server") ) { ROAR_CKHAVEARGS(1); roar_libroar_set_server(argv[++i]); } else if ( !strcmp(k, "--tcp") || !strcmp(k, "-t") ) { cpi_type = ROAR_SOCKET_TYPE_TCP; cpi_touched++; } else if ( !strcmp(k, "--unix") || !strcmp(k, "-u") ) { cpi_type = ROAR_SOCKET_TYPE_UNIX; cpi_touched++; } else if ( !strcmp(k, "--decnet") || !strcmp(k, "-n") ) { cpi_type = ROAR_SOCKET_TYPE_DECNET; cpi_touched++; } else if ( !strcmp(k, "--port") ) { ROAR_CKHAVEARGS(1); cpi_port = atoi(argv[++i]); cpi_touched++; } else if ( !strcmp(k, "--bind") ) { ROAR_CKHAVEARGS(1); cpi_host = argv[++i]; cpi_touched++; } else if ( !strcmp(k, "--proto") ) { ROAR_CKHAVEARGS(1); cpi_proto = roar_str2proto(argv[++i]); if ( cpi_proto == -1 ) { fprintf(stderr, "Unknown protocol: %s: %s\n", argv[i], roar_error2str(roar_error)); return 1; } } else if ( !strcmp(k, "--new-sock") ) { if ( cpi_touched && add_cpi_listen(cpi_proto, cpi_type, cpi_host, cpi_port) == -1 ) { fprintf(stderr, "Can not open socket for CPI: %s\n", roar_error2str(roar_error)); return 1; } cpi_touched = 0; } else if ( !strcmp(k, "--client-fh") ) { ROAR_CKHAVEARGS(1); if ( add_cpi_client(cpi_proto, atoi(argv[++i])) == -1 ) { fprintf(stderr, "Can not add CPI client: %s\n", roar_error2str(roar_error)); return 1; } #define _option(name) \ } else if ( !strcasecmp(k, "--option-" #name) ) { \ options |= OPTION_ ## name; \ } else if ( !strcasecmp(k, "--option-no-" #name) ) { \ options |= OPTION_ ## name; \ options -= OPTION_ ## name; #define __fix_vim_syntax_highlight } _option(TOUCH) _option(ABOUT) _option(HELP) _option(PREFERENCES) } else if ( !strcmp(k, "--appname") ) { ROAR_CKHAVEARGS(1); appname = argv[++i]; _clear_para(); } else if ( !strcmp(k, "--abiversion") ) { ROAR_CKHAVEARGS(1); abiversion = argv[++i]; _clear_para(); } else if ( !strcmp(k, "--args") ) { ROAR_CKHAVEARGS(1); pluginargs = argv[++i]; _clear_para(); } else { if ( cpi_touched && add_cpi_listen(cpi_proto, cpi_type, cpi_host, cpi_port) == -1 ) { fprintf(stderr, "Can not open socket for CPI: %s\n", roar_error2str(roar_error)); return 1; } cpi_touched = 0; if ( g_para == NULL ) g_para = roar_dl_para_new(action == RUN_AS_APPLICATION ? NULL : pluginargs, NULL, appname, abiversion); if ( action == RUN_AS_APPLICATION && _add_para(g_para, pluginargs, argc - i - 1, &(argv[i+1])) == -1 ) { fprintf(stderr, "Error parsing plugin arguments: %s\n", roar_error2str(roar_error)); } else { roar_err_set(ROAR_ERROR_NONE); if ( do_plugin(action, k, options) == -1 ) { fprintf(stderr, "Error loading plugin: %s\n", roar_error != ROAR_ERROR_NONE ? roar_error2str(roar_error) : roar_dl_errstr(NULL)); ret = 1; } } if ( action == RUN_AS_APPLICATION ) break; // end looping over arguments } } _clear_para(); roar_scheduler_unref(g_sched); return ret; } //ll