diff --git a/include/maestro/core.h b/include/maestro/core.h index c22cad6a9e83eb98e79eb738ba9533a81f9f85c8..4954c18343e471514f1471f8cf1de7bbc5ad4453 100644 --- a/include/maestro/core.h +++ b/include/maestro/core.h @@ -111,6 +111,7 @@ extern "C" { #include "maestro/pool_manager.h" #include "maestro/attributes.h" #include "maestro/groups.h" +#include "maestro/env.h" #ifdef __cplusplus diff --git a/include/maestro/i_ketopt.h b/include/maestro/i_ketopt.h new file mode 100644 index 0000000000000000000000000000000000000000..166938b8311778941a0424916f59eed77a721f14 --- /dev/null +++ b/include/maestro/i_ketopt.h @@ -0,0 +1,146 @@ +/* taken from https://github.com/attractivechaos/klib/blob/master/ketopt.h */ + +/* This file is originally part of klib, which is licensed under the + * X11/MIT licnense: */ + +/* The MIT License + Copyright (c) 2008, 2009, 2011 by Attractive Chaos <attractor@live.co.uk> + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +#ifndef I_KETOPT_H +#define I_KETOPT_H + +#include <string.h> /* for strchr() and strncmp() */ + +#define ko_no_argument 0 +#define ko_required_argument 1 +#define ko_optional_argument 2 + +typedef struct { + int ind; /* equivalent to optind */ + int opt; /* equivalent to optopt */ + char *arg; /* equivalent to optarg */ + int longidx; /* index of a long option; or -1 if short */ + /* private variables not intended for external uses */ + int i, pos, n_args; +} ketopt_t; + +typedef struct { + char *name; + int has_arg; + int val; +} ko_longopt_t; + +static ketopt_t KETOPT_INIT = { 1, 0, 0, -1, 1, 0, 0 }; + +static void ketopt_permute(char *argv[], int j, int n) /* move argv[j] over n elements to the left */ +{ + int k; + char *p = argv[j]; + for (k = 0; k < n; ++k) + argv[j - k] = argv[j - k - 1]; + argv[j - k] = p; +} + +/** + * Parse command-line options and arguments + * + * This fuction has a similar interface to GNU's getopt_long(). Each call + * parses one option and returns the option name. s->arg points to the option + * argument if present. The function returns -1 when all command-line arguments + * are parsed. In this case, s->ind is the index of the first non-option + * argument. + * + * @param s status; shall be initialized to KETOPT_INIT on the first call + * @param argc length of argv[] + * @param argv list of command-line arguments; argv[0] is ignored + * @param permute non-zero to move options ahead of non-option arguments + * @param ostr option string + * @param longopts long options + * + * @return ASCII for a short option; ko_longopt_t::val for a long option; -1 if + * argv[] is fully processed; '?' for an unknown option or an ambiguous + * long option; ':' if an option argument is missing + */ +static int ketopt(ketopt_t *s, int argc, char *argv[], int permute, const char *ostr, const ko_longopt_t *longopts) +{ + int opt = -1, i0, j; + if (permute) { + while (s->i < argc && (argv[s->i][0] != '-' || argv[s->i][1] == '\0')) + ++s->i, ++s->n_args; + } + s->arg = 0, s->longidx = -1, i0 = s->i; + if (s->i >= argc || argv[s->i][0] != '-' || argv[s->i][1] == '\0') { + s->ind = s->i - s->n_args; + return -1; + } + if (argv[s->i][0] == '-' && argv[s->i][1] == '-') { /* "--" or a long option */ + if (argv[s->i][2] == '\0') { /* a bare "--" */ + ketopt_permute(argv, s->i, s->n_args); + ++s->i, s->ind = s->i - s->n_args; + return -1; + } + s->opt = 0, opt = '?', s->pos = -1; + if (longopts) { /* parse long options */ + int k, n_exact = 0, n_partial = 0; + const ko_longopt_t *o = 0, *o_exact = 0, *o_partial = 0; + for (j = 2; argv[s->i][j] != '\0' && argv[s->i][j] != '='; ++j) {} /* find the end of the option name */ + for (k = 0; longopts[k].name != 0; ++k) + if (strncmp(&argv[s->i][2], longopts[k].name, j - 2) == 0) { + if (longopts[k].name[j - 2] == 0) ++n_exact, o_exact = &longopts[k]; + else ++n_partial, o_partial = &longopts[k]; + } + if (n_exact > 1 || (n_exact == 0 && n_partial > 1)) return '?'; + o = n_exact == 1? o_exact : n_partial == 1? o_partial : 0; + if (o) { + s->opt = opt = o->val, s->longidx = o - longopts; + if (argv[s->i][j] == '=') s->arg = &argv[s->i][j + 1]; + if (o->has_arg == 1 && argv[s->i][j] == '\0') { + if (s->i < argc - 1) s->arg = argv[++s->i]; + else opt = ':'; /* missing option argument */ + } + } + } + } else { /* a short option */ + const char *p; + if (s->pos == 0) s->pos = 1; + opt = s->opt = argv[s->i][s->pos++]; + p = strchr((char*)ostr, opt); + if (p == 0) { + opt = '?'; /* unknown option */ + } else if (p[1] == ':') { + if (argv[s->i][s->pos] == 0) { + if (s->i < argc - 1) s->arg = argv[++s->i]; + else opt = ':'; /* missing option argument */ + } else s->arg = &argv[s->i][s->pos]; + s->pos = -1; + } + } + if (s->pos < 0 || argv[s->i][s->pos] == 0) { + ++s->i, s->pos = 0; + if (s->n_args > 0) /* permute */ + for (j = i0; j < s->i; ++j) + ketopt_permute(argv, j, s->n_args); + } + s->ind = s->i - s->n_args; + return opt; +} + +#endif diff --git a/maestro/ofi.c b/maestro/ofi.c index 8d77f63c5db7c703291904e6a2cebb3dc675fc9a..f842cab72f4844b1b8672355e3886bba1052dc88 100644 --- a/maestro/ofi.c +++ b/maestro/ofi.c @@ -2874,8 +2874,10 @@ mstro_pm_attach(const char *remote_pm_info) mstro_status s=mstro_ep_desc_deserialize(&pm_epd, remote_pm_info); if(s!=MSTRO_OK) { - ERR("Failed to parse pool manager info: %d (%s)\n", - s, mstro_status_description(s)); + ERR("Failed to parse pool manager info: %d (%s%s)\n", + s, mstro_status_description(s), + /* parser errrors also if a NULL result */ + s==MSTRO_NOMEM ? " or invalid pool-manager info data" : ""); goto BAILOUT; } diff --git a/tests/simple_telemetry_listener.c b/tests/simple_telemetry_listener.c index 795e48691a81b8bf5a13723bf7175a28b06a0708..fc30d5656111f285ac3f952b90463be0f6e266f8 100644 --- a/tests/simple_telemetry_listener.c +++ b/tests/simple_telemetry_listener.c @@ -36,21 +36,181 @@ #include "maestro.h" #include "maestro/i_statistics.h" +#include "maestro/i_ketopt.h" #include <unistd.h> +#include <errno.h> +#include <stdlib.h> -const mstro_nanosec_t MAX_WAIT = ((mstro_nanosec_t)15)*1000*1000*1000; /* 10s */ -int -main(int argc, char ** argv) +#define NSEC_PER_SEC ((mstro_nanosec_t)1000*1000*1000) +const mstro_nanosec_t MAX_WAIT = ((mstro_nanosec_t)15)*NSEC_PER_SEC; /* 15s */ + +/** Configurable settings */ +char *g_conf_workflow_name = NULL; +char *g_conf_component_name = NULL; +char *g_conf_terminate_after = NULL; +mstro_nanosec_t g_conf_max_wait = MAX_WAIT; +char *g_conf_pminfo = NULL; +#define LOGDST_STDOUT 1 +#define LOGDST_STDERR 2 +#define LOGDST_MAESTRO 3 +#define LOGDST_SYSLOG 4 +int g_conf_logdst = LOGDST_STDOUT; +bool g_verbose = true; + + +/** usage */ + +void +print_usage(const char *argv0) { - /* FIXME: parse arguments */ - char *component_name = argv[0]; + fprintf(stderr, "Usage: %s [OPTIONS]\n", argv0); + fprintf(stderr, " Initialization OPTIONS:\n"); + fprintf(stderr, " (environment variables that are used as default in {curly brackets})\n"); + fprintf(stderr, + " --workflow NAME, -w NAME Maestro Workflow name to attach to {MSTRO_WORKFLOW_NAME}\n" + " --component NAME, -c NAME Maestro component name for this listener {MSTRO_COMPONENT_NAME}\n" + " --terminate-after NAME, -q NAME Terminate after observing LEAVE of component NAME\n" + " --max-idle SECONDS, -i SECONDS Terminate after no events for SECONDS {%g}\n" + " --destination DEST, -d DEST Logging destination: 'syslog', 'stdout', 'stderr', or 'mstro' {stdout}\n" + " --pm-info PMINFO, -p PMINFO Pool Manager OOB info {MSTRO_POOL_MANAGER_INFO}\n" + " --help, -h This help\n" + " --verbose, -v Verbose output\n" + " --version, -V Version information\n" + ,((double)MAX_WAIT)/NSEC_PER_SEC); +} +/** argument parsing and setting configuration */ +mstro_status +parse_arguments(int argc, char **argv) +{ + /* default */ + g_conf_component_name = argv[0]; + + static ko_longopt_t longopts[] = { + { "workflow", ko_required_argument, 'w' }, + { "component", ko_required_argument, 'c' }, + { "terminate-after", ko_required_argument, 'q' }, + { "max-idle", ko_required_argument, 'i' }, + { "destination", ko_required_argument, 'd' }, + { "pm-info", ko_required_argument, 'p' }, + { "pm_info", ko_required_argument, 'p' }, + { "help", ko_no_argument, 'h' }, + { "verbose", ko_no_argument, 'v' }, + { "version", ko_no_argument, 'V' }, + { NULL, 0, 0 } + }; + ketopt_t opt = KETOPT_INIT; + int c; - mstro_status s=mstro_init(NULL, component_name, 0); + while ((c = ketopt(&opt, argc, argv, 1, "w:c:q:i:d:hv", longopts)) >= 0) { + switch(c) { + case 'w': + if(!opt.arg) { + fprintf(stderr, "--workflow option is missing its argument\n"); + print_usage(argv[0]); + exit(EXIT_FAILURE); + } + g_conf_workflow_name = opt.arg; + break; + case 'c': + if(!opt.arg) { + fprintf(stderr, "--component option is missing its argument\n"); + print_usage(argv[0]); + exit(EXIT_FAILURE); + } + g_conf_component_name = opt.arg; + break; + case 'q': + if(!opt.arg) { + fprintf(stderr, "--terminate-after option is missing its argument\n"); + print_usage(argv[0]); + exit(EXIT_FAILURE); + } + g_conf_terminate_after = opt.arg; + break; + case 'i': + if(!opt.arg) { + fprintf(stderr, "--max-idle option is missing its argument\n"); + print_usage(argv[0]); + exit(EXIT_FAILURE); + } + g_conf_max_wait = atoi(opt.arg)* NSEC_PER_SEC; + break; + case 'd': { + const char *dst = opt.arg; + if(strcasecmp(dst,"sy")==0) { + g_conf_logdst=LOGDST_SYSLOG; + } else if(strcasecmp(dst,"stdo")==0) { + g_conf_logdst=LOGDST_STDOUT; + } else if(strcasecmp(dst,"stde")==0) { + g_conf_logdst=LOGDST_STDERR; + } else if(strcasecmp(dst,"m")==0) { + g_conf_logdst=LOGDST_MAESTRO; + } else { + fprintf(stderr, "Illegal output destination (or none) specified: %s\n", dst); + print_usage(argv[0]); + return MSTRO_FAIL; + } + break; + } + case 'p': { + if(!opt.arg) { + fprintf(stderr, "--pm-info option is missing its argument\n"); + print_usage(argv[0]); + exit(EXIT_FAILURE); + } + size_t len = strlen(MSTRO_ENV_POOL_INFO) + 1 + strlen(opt.arg) + 1; + g_conf_pminfo = malloc(len); + if(g_conf_pminfo==NULL) { + fprintf(stderr, "pm-info argument too large\n"); + exit(EXIT_FAILURE); + } + strcpy(g_conf_pminfo, MSTRO_ENV_POOL_INFO); + strcat(g_conf_pminfo, "="); + strcat(g_conf_pminfo, opt.arg); + if(putenv(g_conf_pminfo)!=0) { + fprintf(stderr, "Failed to set PM-INFO: %d (%s)\n", errno, strerror(errno)); + exit(EXIT_FAILURE); + } + break; + } + case 'v': + g_verbose = true; + break; + case 'h': + print_usage(argv[0]); + exit(EXIT_SUCCESS); + case 'V': + fprintf(stdout, "%s, part of %s\n", argv[0], mstro_version()); + exit(EXIT_SUCCESS); + break; + default: + fprintf(stderr, "Unrecognized option: %c\n", optopt? optopt : ' '); + print_usage(argv[0]); + return MSTRO_FAIL; + } + } + if(opt.ind<argc) { + fprintf(stderr, "Unexpected non-option arguments: %s (...)\n", argv[opt.ind]); + print_usage(argv[0]); + return MSTRO_FAIL; + } + if(g_verbose) { + fprintf(stderr, "Configuration: %s/%s/%s/%llu/%d\n", + g_conf_workflow_name, g_conf_component_name, + g_conf_terminate_after, g_conf_max_wait, g_conf_logdst); + } + return MSTRO_OK; +} + +/** main loop: handle events and log them */ +mstro_status +event_loop(void) +{ /* wildcard selector: any CDO */ mstro_cdo_selector selector=NULL; - s=mstro_cdo_selector_create( + mstro_status s=mstro_cdo_selector_create( NULL, NULL, "(has .maestro.core.cdo.name)", &selector); @@ -156,10 +316,53 @@ main(int argc, char ** argv) } s= mstro_subscription_dispose(cdo_subscription); s= mstro_subscription_dispose(join_leave_subscription); - BAILOUT: - ; + return s; +} + +/** main entrypoint */ +char *g_default_loglevel=NULL; + +int +main(int argc, char ** argv) +{ + + mstro_status s = parse_arguments(argc, argv); + if(s!=MSTRO_OK) { + fprintf(stderr, "Failed to parse arguments\n"); + exit(EXIT_FAILURE); + } + + /** ensure pool info is set (by user or args) */ + if(getenv(MSTRO_ENV_POOL_INFO)==NULL ) { + fprintf(stderr, "No pool manager info provided, cannot attach to anything\n"); + exit(EXIT_FAILURE); + } + + /** try to reduce logging, unless user set something */ + if(getenv(MSTRO_ENV_LOG_LEVEL)==NULL) { + int l = strlen(MSTRO_ENV_LOG_LEVEL) + 1 + strlen("err") + 1; + g_default_loglevel=malloc(l); + if(g_default_loglevel!=NULL) { + strcpy(g_default_loglevel, MSTRO_ENV_LOG_LEVEL); + strcat(g_default_loglevel, "=err"); + putenv(g_default_loglevel); + } else { + ;/* well, then try without reduced logging */ + } + } + + /** start */ + s=mstro_init(g_conf_workflow_name, g_conf_component_name, 0); + if(s!=MSTRO_OK) { + fprintf(stderr, "Failed to initialize Maestro: %d (%s)\n", + s, mstro_status_description(s)); + exit(EXIT_FAILURE); + } + + s = event_loop(); + mstro_status s1=mstro_finalize(); if(s1!=MSTRO_OK || s!=MSTRO_OK)