#include "src/include/pmix_config.h"
#include <errno.h>
#include <locale.h>
#include <stdio.h>
#include <string.h>
#include "pmix.h"
#include "pmix_common.h"
#include "src/include/pmix_globals.h"
#include "src/mca/pinstalldirs/pinstalldirs.h"
#include "src/util/pmix_argv.h"
#include "src/util/pmix_os_path.h"
#include "src/util/pmix_output.h"
#include "src/util/pmix_printf.h"
#include "src/util/pmix_show_help.h"
bool pmix_show_help_enabled = false;
static time_t show_help_time_last_displayed = 0;
static bool show_help_timer_set = false;
static pmix_event_t show_help_timer_event;
static int output_stream = -1;
static pmix_status_t load_array(char ***array,
const char *filename,
const char *topic);
static struct timeval show_help_interval = {5, 0};
static void show_help_cbfunc(pmix_status_t status, void *cbdata)
{
pmix_shift_caddy_t *cd = (pmix_shift_caddy_t *) cbdata;
PMIX_HIDE_UNUSED_PARAMS(status);
if (NULL != cd->directives) {
PMIX_INFO_FREE(cd->directives, cd->ndirs);
}
if (NULL != cd->info) {
PMIX_INFO_FREE(cd->info, cd->ninfo);
}
PMIX_RELEASE(cd);
}
static void local_delivery(const char *file,
const char *topic,
const char *msg)
{
pmix_shift_caddy_t *cd;
if (!pmix_show_help_enabled) {
fprintf(stderr, "%s", msg);
return;
}
cd = PMIX_NEW(pmix_shift_caddy_t);
cd->ninfo = 1;
PMIX_INFO_CREATE(cd->info, cd->ninfo);
PMIX_INFO_LOAD(&cd->info[0], PMIX_LOG_STDERR, msg, PMIX_STRING);
cd->ndirs = 2;
PMIX_INFO_CREATE(cd->directives, cd->ndirs);
PMIX_INFO_LOAD(&cd->directives[0], PMIX_LOG_KEY, file, PMIX_STRING);
PMIX_INFO_LOAD(&cd->directives[1], PMIX_LOG_VAL, topic, PMIX_STRING);
cd->cbfunc.opcbfn = show_help_cbfunc;
cd->cbdata = cd;
cd->proc = NULL;
PMIX_THREADSHIFT(cd, pmix_log_local_op);
}
typedef struct {
pmix_list_item_t super;
char *tli_filename;
char *tli_topic;
pmix_list_t tli_processes;
time_t tli_time_displayed;
int tli_count_since_last_display;
bool tli_display;
} tuple_list_item_t;
static void tuple_list_item_constructor(tuple_list_item_t *obj)
{
obj->tli_filename = NULL;
obj->tli_topic = NULL;
PMIX_CONSTRUCT(&(obj->tli_processes), pmix_list_t);
obj->tli_time_displayed = time(NULL);
obj->tli_count_since_last_display = 0;
obj->tli_display = true;
}
static void tuple_list_item_destructor(tuple_list_item_t *obj)
{
if (NULL != obj->tli_filename) {
free(obj->tli_filename);
}
if (NULL != obj->tli_topic) {
free(obj->tli_topic);
}
PMIX_LIST_DESTRUCT(&(obj->tli_processes));
}
static PMIX_CLASS_INSTANCE(tuple_list_item_t, pmix_list_item_t, tuple_list_item_constructor,
tuple_list_item_destructor);
static pmix_list_t abd_tuples;
static const char *default_filename = "help-messages";
static const char *dash_line
= "--------------------------------------------------------------------------\n";
static char **search_dirs = NULL;
static pmix_status_t match(const char *a, const char *b)
{
int rc = PMIX_ERROR;
char *p1, *p2, *tmp1 = NULL, *tmp2 = NULL;
size_t min;
if (0 == strcmp(a, b))
return PMIX_SUCCESS;
if (NULL != strchr(a, '*') || NULL != strchr(b, '*')) {
tmp1 = strdup(a);
if (NULL == tmp1) {
return PMIX_ERR_OUT_OF_RESOURCE;
}
tmp2 = strdup(b);
if (NULL == tmp2) {
free(tmp1);
return PMIX_ERR_OUT_OF_RESOURCE;
}
p1 = strchr(tmp1, '*');
p2 = strchr(tmp2, '*');
if (NULL != p1) {
*p1 = '\0';
}
if (NULL != p2) {
*p2 = '\0';
}
min = strlen(tmp1);
if (strlen(tmp2) < min) {
min = strlen(tmp2);
}
if (0 == min || 0 == strncmp(tmp1, tmp2, min)) {
rc = PMIX_SUCCESS;
}
free(tmp1);
free(tmp2);
return rc;
}
return PMIX_ERROR;
}
static pmix_status_t pmix_get_tli(const char *filename,
const char *topic,
tuple_list_item_t **tli_)
{
tuple_list_item_t *tli = *tli_;
PMIX_LIST_FOREACH(tli, &abd_tuples, tuple_list_item_t)
{
if (PMIX_SUCCESS == match(tli->tli_filename, filename) &&
PMIX_SUCCESS == match(tli->tli_topic, topic)) {
*tli_ = tli;
return PMIX_SUCCESS;
}
}
tli = PMIX_NEW(tuple_list_item_t);
if (NULL == tli) {
return PMIX_ERR_OUT_OF_RESOURCE;
}
tli->tli_filename = strdup(filename);
tli->tli_topic = strdup(topic);
pmix_list_append(&abd_tuples, &(tli->super));
*tli_ = tli;
return PMIX_ERR_NOT_FOUND;
}
static void pmix_show_accumulated_duplicates(int fd, short event, void *context)
{
time_t now = time(NULL);
tuple_list_item_t *tli;
char *tmp;
PMIX_HIDE_UNUSED_PARAMS(fd, event, context);
PMIX_LIST_FOREACH(tli, &abd_tuples, tuple_list_item_t)
{
if (tli->tli_display && tli->tli_count_since_last_display > 0) {
static bool first = true;
pmix_asprintf(&tmp, "%d more process%s sent help message %s / %s\n",
tli->tli_count_since_last_display,
(tli->tli_count_since_last_display > 1) ? "es have" : " has",
tli->tli_filename, tli->tli_topic);
tli->tli_time_displayed = time(NULL);
char stamp[50] = {0};
strftime(stamp, 50, "%Y-%m-%d %H:%M:%S", localtime(&tli->tli_time_displayed));
char *buf;
pmix_asprintf(&buf, "%s-%s", tli->tli_filename, stamp);
local_delivery(buf, tli->tli_topic, tmp);
free(buf);
tli->tli_count_since_last_display = 0;
if (first) {
pmix_asprintf(&tmp, "%s", "Set MCA parameter \"base_help_aggregate\" to 0 to see all help / error messages\n");
local_delivery(tli->tli_filename, tli->tli_topic, tmp);
first = false;
}
}
}
show_help_time_last_displayed = now;
show_help_timer_set = false;
}
pmix_status_t pmix_help_check_dups(const char *filename, const char *topic)
{
tuple_list_item_t *tli;
time_t now = time(NULL);
int rc;
rc = pmix_get_tli(filename, topic, &tli);
if (PMIX_SUCCESS == rc) {
++tli->tli_count_since_last_display;
if (now > show_help_time_last_displayed + 5 && !show_help_timer_set) {
pmix_show_accumulated_duplicates(0, 0, NULL);
}
if (!show_help_timer_set) {
pmix_event_evtimer_set(pmix_globals.evbase, &show_help_timer_event,
pmix_show_accumulated_duplicates, NULL);
pmix_event_evtimer_add(&show_help_timer_event, &show_help_interval);
show_help_timer_set = true;
}
}
else if (PMIX_ERR_NOT_FOUND == rc) {
if (!show_help_timer_set) {
show_help_time_last_displayed = now;
}
}
else {
PMIX_ERROR_LOG(rc);
return rc;
}
return rc;
}
pmix_status_t pmix_show_help_init(char *helpdir)
{
pmix_output_stream_t lds;
PMIX_CONSTRUCT(&lds, pmix_output_stream_t);
lds.lds_want_stderr = true;
output_stream = pmix_output_open(&lds);
PMIX_CONSTRUCT(&abd_tuples, pmix_list_t);
PMIx_Argv_append_nosize(&search_dirs, pmix_pinstall_dirs.pmixdatadir);
if(NULL != helpdir) {
PMIx_Argv_append_nosize(&search_dirs, helpdir);
}
return PMIX_SUCCESS;
}
pmix_status_t pmix_show_help_finalize(void)
{
pmix_output_close(output_stream);
output_stream = -1;
if (NULL != search_dirs) {
PMIx_Argv_free(search_dirs);
search_dirs = NULL;
};
PMIX_LIST_DESTRUCT(&abd_tuples);
return PMIX_SUCCESS;
}
static pmix_status_t array2string(char **outstring, int want_error_header, char **lines)
{
int i, count;
size_t len;
len = want_error_header ? 2 * strlen(dash_line) : 0;
count = PMIx_Argv_count(lines);
for (i = 0; i < count; ++i) {
if (NULL == lines[i]) {
break;
}
len += strlen(lines[i]) + 1;
}
(*outstring) = (char *) malloc(len + 1);
if (NULL == *outstring) {
return PMIX_ERR_OUT_OF_RESOURCE;
}
*(*outstring) = '\0';
if (want_error_header) {
strcat(*outstring, dash_line);
}
for (i = 0; i < count; ++i) {
if (NULL == lines[i]) {
break;
}
strcat(*outstring, lines[i]);
strcat(*outstring, "\n");
}
if (want_error_header) {
strcat(*outstring, dash_line);
}
return PMIX_SUCCESS;
}
static pmix_status_t open_file(const char *base,
const char *topic,
FILE **fptr)
{
char *filename;
char **err_msg = NULL;
char *tmp;
size_t base_len;
int i;
FILE *fp = NULL;
if (NULL == base) {
base = default_filename;
}
if (NULL != search_dirs) {
for (i = 0; NULL != search_dirs[i]; i++) {
filename = pmix_os_path(false, search_dirs[i], base, NULL);
fp = fopen(filename, "r");
if (NULL == fp) {
if (0 > asprintf(&tmp, " %s: %s", filename, strerror(errno))) {
free(filename);
PMIx_Argv_free(err_msg);
return PMIX_ERR_OUT_OF_RESOURCE;
}
PMIx_Argv_append_nosize(&err_msg, tmp);
free(tmp);
base_len = strlen(base);
if (4 > base_len || 0 != strcmp(base + base_len - 4, ".txt")) {
free(filename);
if (0 > asprintf(&filename, "%s%s%s.txt", search_dirs[i], PMIX_PATH_SEP, base)) {
PMIx_Argv_free(err_msg);
return PMIX_ERR_OUT_OF_RESOURCE;
}
fp = fopen(filename, "r");
}
}
free(filename);
if (NULL != fp) {
break;
}
}
}
if (NULL == fp) {
char *msg;
tmp = PMIx_Argv_join(err_msg, '\n');
PMIx_Argv_free(err_msg);
pmix_asprintf(&msg, "%sSorry! You were supposed to get help about:\n %s\nBut I couldn't open "
"the help file:\n%s.\nSorry!\n%s", dash_line, topic, tmp, dash_line);
local_delivery(tmp, topic, msg);
free(tmp);
return PMIX_ERR_NOT_FOUND;
}
if (NULL != err_msg) {
free(err_msg);
}
*fptr = fp;
return PMIX_SUCCESS;
}
#define PMIX_MAX_LINE_LENGTH 1024
static char *localgetline(FILE *fp)
{
char *ret, *buff;
char input[PMIX_MAX_LINE_LENGTH];
int i = 0;
ret = fgets(input, PMIX_MAX_LINE_LENGTH, fp);
if (NULL != ret) {
if ('\0' != input[0]) {
input[strlen(input) - 1] = '\0';
}
buff = strdup(&input[i]);
return buff;
}
return NULL;
}
static pmix_status_t find_topic(FILE *fp,
const char *base,
const char *topic)
{
char *line, *cptr;
PMIX_HIDE_UNUSED_PARAMS(base);
while (NULL != (line = localgetline(fp))) {
if ('[' != line[0]) {
free(line);
continue;
}
cptr = strchr(line, ']');
if (NULL == cptr) {
free(line);
continue;
}
*cptr = '\0';
if (0 == strcmp(&line[1], topic)) {
free(line);
return PMIX_SUCCESS;
}
free(line);
}
return PMIX_ERR_NOT_FOUND;
}
static pmix_status_t read_topic(FILE *fp, char ***array)
{
int rc;
char *line, *file, *tp;
char **tmparray = NULL;
while (NULL != (line = localgetline(fp))) {
if (0 == strncmp(line, "#include#", strlen("#include#"))) {
file = &line[strlen("#include#")];
if (0 == strlen(file)) {
free(line);
return PMIX_ERR_BAD_PARAM;
}
tp = strchr(file, '#');
if (NULL != tp) {
*tp = '\0'; ++tp;
}
rc = load_array(&tmparray, file, tp);
if (PMIX_SUCCESS != rc) {
free(line);
return rc;
}
}
if ('#' == line[0]) {
free(line);
continue;
}
if ('[' == line[0]) {
free(line);
break;
}
rc = PMIx_Argv_append_nosize(&tmparray, line);
free(line);
if (rc != PMIX_SUCCESS) {
return rc;
}
}
int first_nonblank = -1;
int last_nonblank = -1;
for (int i = 0; NULL != tmparray[i]; ++i) {
if (tmparray[i][0] != '\0') {
if (-1 == first_nonblank) {
first_nonblank = i;
}
last_nonblank = i;
}
}
if (-1 == first_nonblank) {
PMIx_Argv_free(tmparray);
return PMIX_ERR_NOT_FOUND;
}
for (int i = first_nonblank; i <= last_nonblank; ++i) {
PMIx_Argv_append_nosize(array, tmparray[i]);
}
PMIx_Argv_free(tmparray);
return PMIX_SUCCESS;
}
static pmix_status_t load_array(char ***array,
const char *filename,
const char *topic)
{
int ret;
FILE *fp;
ret = open_file(filename, topic, &fp);
if (PMIX_SUCCESS != ret) {
return ret;
}
ret = find_topic(fp, filename, topic);
if (PMIX_SUCCESS == ret) {
ret = read_topic(fp, array);
}
fclose(fp);
if (PMIX_SUCCESS != ret) {
PMIx_Argv_free(*array);
}
return ret;
}
char *pmix_show_help_vstring(const char *filename,
const char *topic,
int want_error_header,
va_list arglist)
{
int rc;
char *single_string, *output, **array = NULL;
if (PMIX_SUCCESS != (rc = load_array(&array, filename, topic))) {
return NULL;
}
rc = array2string(&single_string, want_error_header, array);
if (PMIX_SUCCESS == rc) {
if (0 > vasprintf(&output, single_string, arglist)) {
output = NULL;
}
free(single_string);
}
PMIx_Argv_free(array);
return (PMIX_SUCCESS == rc) ? output : NULL;
}
char *pmix_show_help_string(const char *filename, const char *topic, int want_error_handler, ...)
{
char *output;
va_list arglist;
va_start(arglist, want_error_handler);
output = pmix_show_help_vstring(filename, topic, want_error_handler, arglist);
va_end(arglist);
return output;
}
pmix_status_t pmix_show_vhelp(const char *filename, const char *topic,
int want_error_header, va_list arglist)
{
char *output;
output = pmix_show_help_vstring(filename, topic, want_error_header, arglist);
if (NULL != output) {
local_delivery(filename, topic, output);
}
return (NULL == output) ? PMIX_ERROR : PMIX_SUCCESS;
}
pmix_status_t pmix_show_help(const char *filename, const char *topic,
int want_error_header, ...)
{
va_list arglist;
char *output;
va_start(arglist, want_error_header);
output = pmix_show_help_vstring(filename, topic, want_error_header, arglist);
va_end(arglist);
if (NULL == output) {
return PMIX_SUCCESS;
}
local_delivery(filename, topic, output);
return PMIX_SUCCESS;
}
pmix_status_t pmix_show_help_add_dir(const char *directory)
{
PMIx_Argv_append_nosize(&search_dirs, directory);
return PMIX_SUCCESS;
}
pmix_status_t pmix_show_help_norender(const char *filename,
const char *topic,
const char *output)
{
local_delivery(filename, topic, output);
return PMIX_SUCCESS;
}