/* * Copyright (C) by Argonne National Laboratory * See COPYRIGHT in top-level directory */ /* * This file provides a set of routines that can be used to record debug * messages in a ring so that the may be dumped at a later time. For example, * this can be used to record debug messages without printing them. */ #include "mpl.h" #ifdef MPL_HAVE_UNISTD_H #include #endif #ifdef MPL_HAVE_ERRNO_H #include #endif #if defined(MPL_HAVE_MKSTEMP) && defined(MPL_NEEDS_MKSTEMP_DECL) extern int mkstemp(char *t); #endif #if defined(MPL_HAVE_FDOPEN) && defined(MPL_NEEDS_FDOPEN_DECL) extern FILE *fdopen(int fd, const char *mode); #endif #ifdef MPL_USE_DBG_LOGGING #ifndef MAXPATHLEN #define MAXPATHLEN 1024 #endif int MPL_dbg_active_classes = 0; int MPL_dbg_max_level = MPL_DBG_TYPICAL; static enum { DBG_UNINIT, DBG_PREINIT, DBG_INITIALIZED, DBG_ERROR } dbg_initialized = DBG_UNINIT; static char file_pattern_buf[MAXPATHLEN] = ""; static const char *file_pattern = "-stdout-"; /* "log%d.log"; */ static const char *default_file_pattern = "dbg@W%w-@%d@T-%t@.log"; static char temp_filename[MAXPATHLEN] = ""; static int world_num = 0; static int world_rank = -1; static int which_rank = -1; /* all ranks */ static double time_origin = 0.0; static int dbg_usage(const char *, const char *); static int dbg_openfile(FILE ** dbg_fp); static int dbg_set_class(const char *); static int dbg_set_level(const char *, const char *(names[])); static int dbg_get_filename(char *filename, int len); #if (MPL_THREAD_PACKAGE_NAME != MPL_THREAD_PACKAGE_NONE) static MPL_thread_tls_key_t dbg_tls_key; #endif static FILE *dbg_static_fp = 0; /* * This function finds the basename in a path (ala "man 1 basename"). * *basename will point to an element in path. * More formally: This function sets basename to the character just * after the last '/' in path. */ static void find_basename(char *path, char **basename) ATTRIBUTE((unused)); static void find_basename(char *path, char **basename) { char *c; c = *basename = path; while (*c) { if (*c == '/') *basename = c + 1; ++c; } } static int dbg_init_tls(void) { int err = 0; #if (MPL_THREAD_PACKAGE_NAME != MPL_THREAD_PACKAGE_NONE) MPL_thread_tls_create(NULL, &dbg_tls_key, &err); #endif return err; } static FILE *get_fp(void) { #if (MPL_THREAD_PACKAGE_NAME != MPL_THREAD_PACKAGE_NONE) int err; /* if we're not initialized, use the static fp, since there should * only be one thread in here until then */ if (dbg_initialized == DBG_INITIALIZED) { FILE *fp; MPL_thread_tls_get(&dbg_tls_key, (void **) &fp, &err); return fp; } #endif return dbg_static_fp; } static void set_fp(FILE * fp) { #if (MPL_THREAD_PACKAGE_NAME != MPL_THREAD_PACKAGE_NONE) int err; /* if we're not initialized, use the static fp, since there should * only be one thread in here until then */ if (dbg_initialized == DBG_INITIALIZED) { MPL_thread_tls_set(&dbg_tls_key, (void *) fp, &err); return; } #endif dbg_static_fp = fp; } int MPL_dbg_outevent(const char *file, int line, int class, int kind, const char *fmat, ...) { int mpl_errno = MPL_SUCCESS; va_list list; char *str, stmp[MPL_DBG_MAXLINE]; int i; void *p; MPL_time_t t; double curtime; unsigned long long int threadID = 0; int pid = -1; FILE *dbg_fp = NULL; if (dbg_initialized == DBG_UNINIT || dbg_initialized == DBG_ERROR) goto fn_exit; dbg_fp = get_fp(); #if (MPL_THREAD_PACKAGE_NAME != MPL_THREAD_PACKAGE_NONE) { /* the thread ID is not necessarily unique between processes, so a * (pid,tid) pair should be used to uniquely identify output from * particular threads on a system */ MPL_thread_id_t tid; MPL_thread_self(&tid); threadID = (unsigned long long int) tid; } #endif #if defined(MPL_HAVE_GETPID) pid = (int) getpid(); #endif /* MPL_HAVE_GETPID */ if (!dbg_fp) { mpl_errno = dbg_openfile(&dbg_fp); if (mpl_errno) goto fn_fail; set_fp(dbg_fp); } MPL_wtime(&t); MPL_wtime_todouble(&t, &curtime); curtime = curtime - time_origin; /* The kind values are used with the macros to simplify these cases */ switch (kind) { case 0: va_start(list, fmat); str = va_arg(list, char *); fprintf(dbg_fp, "%d\t%d\t%llx[%d]\t%d\t%f\t%s\t%d\t%s\n", world_num, world_rank, threadID, pid, class, curtime, file, line, str); va_end(list); break; case 1: va_start(list, fmat); str = va_arg(list, char *); MPL_snprintf(stmp, sizeof(stmp), fmat, str); va_end(list); fprintf(dbg_fp, "%d\t%d\t%llx[%d]\t%d\t%f\t%s\t%d\t%s\n", world_num, world_rank, threadID, pid, class, curtime, file, line, stmp); break; case 2: va_start(list, fmat); i = va_arg(list, int); MPL_snprintf(stmp, sizeof(stmp), fmat, i); va_end(list); fprintf(dbg_fp, "%d\t%d\t%llx[%d]\t%d\t%f\t%s\t%d\t%s\n", world_num, world_rank, threadID, pid, class, curtime, file, line, stmp); break; case 3: va_start(list, fmat); p = va_arg(list, void *); MPL_snprintf(stmp, sizeof(stmp), fmat, p); va_end(list); fprintf(dbg_fp, "%d\t%d\t%llx[%d]\t%d\t%f\t%s\t%d\t%s\n", world_num, world_rank, threadID, pid, class, curtime, file, line, stmp); break; default: break; } fflush(dbg_fp); fn_exit: fn_fail: return 0; } /* These are used to simplify the handling of options. To add a new name, add an dbg_classname element to the array classnames. The "classbits" values are defined by MPL_DBG_CLASS in mpl_dbg.h */ typedef struct dbg_classname { int classbits; const char *ucname, *lcname; } dbg_classname; #define MAX_DBG_CLASSNAMES (sizeof(unsigned int) * 8) static dbg_classname classnames[MAX_DBG_CLASSNAMES]; static int num_classnames = 0; static const char *unregistered_classes[MAX_DBG_CLASSNAMES]; static int num_unregistered_classes = 0; /* Because the level values are simpler and are rarely changed, these * use a simple set of parallel arrays */ static const int level_values[] = { MPL_DBG_TERSE, MPL_DBG_TYPICAL, MPL_DBG_VERBOSE, 100 }; static const char *level_name[] = { "TERSE", "TYPICAL", "VERBOSE", 0 }; static const char *lc_level_name[] = { "terse", "typical", "verbose", 0 }; void MPL_dbg_class_register(MPL_dbg_class class, const char *ucname, const char *lcname) { int i, j; classnames[num_classnames].classbits = class; classnames[num_classnames].ucname = ucname; classnames[num_classnames].lcname = lcname; num_classnames++; if (num_unregistered_classes) { /* there are some unregistered classes. look through to see * if any of them match this class. */ size_t len = strlen(lcname); for (i = 0; i < num_unregistered_classes; i++) { size_t slen = strlen(unregistered_classes[i]); if (len == slen && (!strncmp(unregistered_classes[i], lcname, len) || !strncmp(unregistered_classes[i], ucname, len))) { /* got a match */ MPL_dbg_active_classes |= class; for (j = i; j < num_unregistered_classes - 1; j++) unregistered_classes[j] = unregistered_classes[j + 1]; num_unregistered_classes--; break; } } } } MPL_dbg_class MPL_dbg_class_alloc(const char *ucname, const char *lcname) { static unsigned int class = 1; /* create a user handle for this class */ MPL_dbg_class_register(class, ucname, lcname); class <<= 1; return (class >> 1); } /* * Initialize the DBG_MSG system. This is called during the job * initialization to process command-line arguments as well as * checking either the MPICH_DBG or MPL_DBG environment variables. * The initialization * is split into two steps: a preinit and an init. This makes it * possible to enable most of the features before the full * initialization, where a significant amount of the initialization * takes place. */ static int dbg_process_args(int *argc_p, char ***argv_p) { int i, rc; /* Here's where we do the same thing with the command-line options */ if (argc_p) { for (i = 1; i < *argc_p; i++) { if (strncmp((*argv_p)[i], "-mpich-dbg", 10) == 0) { char *s = (*argv_p)[i] + 10; /* Found a command */ if (*s == 0) { /* Just -mpich-dbg */ MPL_dbg_max_level = MPL_DBG_TYPICAL; MPL_dbg_active_classes = MPL_DBG_ALL; } else if (*s == '=') { /* look for file */ MPL_dbg_max_level = MPL_DBG_TYPICAL; MPL_dbg_active_classes = MPL_DBG_ALL; s++; if (strncmp(s, "file", 4) == 0) { file_pattern = default_file_pattern; } } else if (strncmp(s, "-level", 6) == 0) { char *p = s + 6; if (*p == '=') { p++; rc = dbg_set_level(p, lc_level_name); if (rc) dbg_usage("-mpich-dbg-level", "terse, typical, verbose"); } } else if (strncmp(s, "-class", 6) == 0) { char *p = s + 6; if (*p == '=') { p++; rc = dbg_set_class(p); if (rc) dbg_usage("-mpich-dbg-class", 0); } } else if (strncmp(s, "-filename", 9) == 0) { char *p = s + 9; if (*p == '=') { p++; /* A special case for a filepattern of "-default", * use the predefined default pattern */ if (strcmp(p, "-default") == 0) { file_pattern = default_file_pattern; } else { strncpy(file_pattern_buf, p, sizeof(file_pattern_buf) - 1); /* Make sure the string is NULL-terminated */ file_pattern_buf[MAXPATHLEN - 1] = '\0'; file_pattern = file_pattern_buf; } } } else if (strncmp(s, "-rank", 5) == 0) { char *p = s + 5; if (*p == '=' && p[1] != 0) { char *sOut; p++; which_rank = (int) strtol(p, &sOut, 10); if (p == sOut) { dbg_usage("-mpich-dbg-rank", 0); which_rank = -1; } } } else { dbg_usage((*argv_p)[i], 0); } /* Eventually, should null it out and reduce argc value */ } } } return MPL_SUCCESS; } /* could two different environment variables control the same thing? sure they * could! consider MPICH: we moved all our logging code into MPL, so it should * have an MPL_ prefix, but all the documentation assumes an "MPICH_" prefix. * So we'll look for both. */ static char *getenv_either(const char *env_a, const char *env_b) { char *s; if ((s = getenv(env_a)) == NULL) s = getenv(env_b); return s; } static int dbg_process_env(void) { char *s; int rc; s = getenv_either("MPICH_DBG", "MPL_DBG"); if (s) { /* Set the defaults */ MPL_dbg_max_level = MPL_DBG_TYPICAL; MPL_dbg_active_classes = MPL_DBG_ALL; if (strncmp(s, "FILE", 4) == 0) { file_pattern = default_file_pattern; } } s = getenv_either("MPICH_DBG_LEVEL", "MPL_DBG_LEVEL"); if (s) { rc = dbg_set_level(s, level_name); if (rc) dbg_usage("MPL_DBG_LEVEL", "TERSE, TYPICAL, VERBOSE"); } s = getenv_either("MPICH_DBG_CLASS", "MPL_DBG_CLASS"); if (s) { rc = dbg_set_class(s); if (rc) dbg_usage("MPL_DBG_CLASS", 0); } s = getenv_either("MPICH_DBG_FILENAME", "MPL_DBG_FILENAME"); if (s) { strncpy(file_pattern_buf, s, sizeof(file_pattern_buf) - 1); /* Make sure the string is NULL-terminated */ file_pattern_buf[MAXPATHLEN - 1] = '\0'; file_pattern = file_pattern_buf; } s = getenv_either("MPICH_DBG_RANK", "MPL_DBG_RANK"); if (s) { char *sOut; which_rank = (int) strtol(s, &sOut, 10); if (s == sOut) { dbg_usage("MPL_DBG_RANK", 0); which_rank = -1; } } return MPL_SUCCESS; } MPL_dbg_class MPL_DBG_ROUTINE_ENTER; MPL_dbg_class MPL_DBG_ROUTINE_EXIT; MPL_dbg_class MPL_DBG_ROUTINE; MPL_dbg_class MPL_DBG_ALL = ~(0); /* pre-initialize the ALL class */ /* * Attempt to initialize the logging system. This works only if the * full initialization is not required for updating the environment * and/or command-line arguments. */ int MPL_dbg_pre_init(int *argc_p, char ***argv_p) { MPL_time_t t; /* if the DBG_MSG system was already initialized, say by the * device, then return immediately */ if (dbg_initialized != DBG_UNINIT) return MPL_SUCCESS; if (dbg_init_tls()) return MPL_ERR_DBG_OTHER; /* Check to see if any debugging was selected. The order of these * tests is important, as they allow general defaults to be set, * followed by more specific modifications */ /* First, the environment variables */ dbg_process_env(); dbg_process_args(argc_p, argv_p); MPL_wtime_init(); MPL_wtime(&t); MPL_wtime_todouble(&t, &time_origin); /* Allocate the predefined classes */ MPL_DBG_ROUTINE_ENTER = MPL_dbg_class_alloc("ROUTINE_ENTER", "routine_enter"); MPL_DBG_ROUTINE_EXIT = MPL_dbg_class_alloc("ROUTINE_EXIT", "routine_exit"); MPL_DBG_CLASS_CLR(MPL_DBG_ROUTINE); MPL_DBG_CLASS_APPEND(MPL_DBG_ROUTINE, MPL_DBG_ROUTINE_ENTER); MPL_DBG_CLASS_APPEND(MPL_DBG_ROUTINE, MPL_DBG_ROUTINE_EXIT); MPL_dbg_class_register(MPL_DBG_ROUTINE, "ROUTINE", "routine"); MPL_dbg_class_register(MPL_DBG_ALL, "ALL", "all"); dbg_initialized = DBG_PREINIT; return MPL_SUCCESS; } int MPL_dbg_init(int wnum, int wrank) { int ret; FILE *dbg_fp = NULL; /* if the DBG_MSG system was already initialized, say by the * device, then return immediately. Note that the device is then * responsible for handling the file mode (e.g., reopen when the * rank become available) */ if (dbg_initialized == DBG_INITIALIZED || dbg_initialized == DBG_ERROR) return MPL_SUCCESS; if (dbg_initialized != DBG_PREINIT) { if (dbg_init_tls()) return MPL_ERR_DBG_OTHER; } dbg_fp = get_fp(); world_num = wnum; world_rank = wrank; if (which_rank >= 0 && which_rank != wrank) { /* Turn off logging on this process */ MPL_dbg_active_classes = 0; } /* If the file has already been opened with a temp filename, * rename it. */ if (dbg_fp && dbg_fp != stdout && dbg_fp != stderr) { char filename[MAXPATHLEN] = ""; dbg_get_filename(filename, MAXPATHLEN); ret = rename(temp_filename, filename); if (ret) { /* Retry renaming file after closing it */ fclose(dbg_fp); ret = rename(temp_filename, filename); if (ret) { MPL_error_printf("Could not rename temp log file to %s\n", filename); goto fn_fail; } else { dbg_fp = fopen(filename, "a+"); set_fp(dbg_fp); if (dbg_fp == NULL) { MPL_error_printf("Error re-opening log file, %s\n", filename); goto fn_fail; } } } } dbg_initialized = DBG_INITIALIZED; /* updating dbg_initialized may alter where dbg_fp is stored, set it again */ set_fp(dbg_fp); fn_exit: return MPL_SUCCESS; fn_fail: dbg_initialized = DBG_ERROR; goto fn_exit; } /* Print the usage statement to stderr */ static int dbg_usage(const char *cmd, const char *vals) { if (vals) { fprintf(stderr, "Incorrect value for %s, should be one of %s\n", cmd, vals); } else { fprintf(stderr, "Incorrect value for %s\n", cmd); } fprintf(stderr, "Command line for debug switches\n\ -mpich-dbg-class=name[,name,...]\n\ -mpich-dbg-level=name (one of terse, typical, verbose)\n\ -mpich-dbg-filename=pattern (includes %%d for world rank, %%t for thread id\n\ -mpich-dbg-rank=val (only this rank in COMM_WORLD will be logged)\n\ -mpich-dbg (shorthand for -mpich-dbg-class=all -mpich-dbg-level=typical)\n\ -mpich-dbg=file (shorthand for -mpich-dbg -mpich-dbg-filename=%s)\n\ Environment variables\n\ MPICH_DBG_CLASS=NAME[,NAME...]\n\ MPICH_DBG_LEVEL=NAME\n\ MPICH_DBG_FILENAME=pattern\n\ MPICH_DBG_RANK=val\n\ MPICH_DBG=YES or FILE\n", default_file_pattern); fflush(stderr); return 0; } #if defined (MPL_HAVE_MKSTEMP) && defined (MPL_HAVE_FDOPEN) /* creates a temporary file in the same directory the user specified * for the log file */ static int dbg_open_tmpfile(FILE ** dbg_fp) { int mpl_errno = MPL_SUCCESS; const char temp_pattern[] = "templogXXXXXX"; int fd; char *basename; int ret; ret = MPL_strncpy(temp_filename, file_pattern, MAXPATHLEN); if (ret) goto fn_fail; find_basename(temp_filename, &basename); /* make sure there's enough room in temp_filename to store temp_pattern */ if (basename - temp_filename > MAXPATHLEN - sizeof(temp_pattern)) goto fn_fail; MPL_strncpy(basename, temp_pattern, sizeof(temp_pattern)); fd = mkstemp(temp_filename); if (fd == -1) goto fn_fail; *dbg_fp = fdopen(fd, "a+"); if (*dbg_fp == NULL) goto fn_fail; fn_exit: return mpl_errno; fn_fail: MPL_error_printf("Could not open log file %s\n", temp_filename); dbg_initialized = DBG_ERROR; mpl_errno = MPL_ERR_DBG_INTERN; goto fn_exit; } #elif defined(MPL_HAVE__MKTEMP_S) && defined(MPL_HAVE_FOPEN_S) /* creates a temporary file in the same directory the user specified * for the log file */ static int dbg_open_tmpfile(FILE ** dbg_fp) { int mpl_errno = MPL_SUCCESS; const char temp_pattern[] = "templogXXXXXX"; int fd; char *basename; int ret; errno_t ret_errno; ret = MPL_strncpy(temp_filename, file_pattern, MAXPATHLEN); if (ret) goto fn_fail; find_basename(temp_filename, &basename); /* make sure there's enough room in temp_filename to store temp_pattern */ if (basename - temp_filename > MAXPATHLEN - sizeof(temp_pattern)) goto fn_fail; MPL_strncpy(basename, temp_pattern, sizeof(temp_pattern)); ret_errno = _mktemp_s(temp_filename, MAXPATHLEN); if (ret_errno != 0) goto fn_fail; ret_errno = fopen_s(dbg_fp, temp_filename, "a+"); if (ret_errno != 0) goto fn_fail; fn_exit: return mpl_errno; fn_fail: MPL_error_printf("Could not open log file %s\n", temp_filename); dbg_initialized = DBG_ERROR; mpl_errno = MPL_ERR_DBG_INTERN; goto fn_exit; } #else /* creates a temporary file in some directory, which may not be where * the user wants the log file. When the file is renamed later, it * may require a copy. * * Note that this is not safe: By the time we call fopen(), another * file with the same name may exist. That file would get clobbered. */ static int dbg_open_tmpfile(FILE ** dbg_fp) { int mpl_errno = MPL_SUCCESS; char *cret; cret = tmpnam(temp_filename); if (cret == NULL) goto fn_fail; *dbg_fp = fopen(temp_filename, "w"); if (*dbg_fp == NULL) goto fn_fail; fn_exit: return mpl_errno; fn_fail: MPL_error_printf("Could not open log file %s\n", temp_filename); dbg_initialized = DBG_ERROR; mpl_errno = MPL_ERR_DBG_INTERN; goto fn_exit; } #endif /* This routine can make no MPI calls, since it may be logging those * calls. */ static int dbg_get_filename(char *filename, int len) { int withinMworld = 0, /* True if within an @W...@ */ withinMthread = 0; /* True if within an @T...@ */ /* FIXME: Need to know how many process groups are known */ #if (MPL_THREAD_PACKAGE_NAME != MPL_THREAD_PACKAGE_NONE) unsigned long long int threadID = 0; int nThread = 2; #else int nThread = 1; #endif static char world_numAsChar[10] = "0"; char *pDest; const char *p; if (world_num == 1) { world_numAsChar[0] = '1'; world_numAsChar[1] = '\0'; } p = file_pattern; pDest = filename; *filename = 0; while (*p && (pDest - filename) < len - 1) { /* There are two special cases that allow text to * be optionally included. Those patterns are * @T...@ (only if multi-threaded) and * @W...@ (only if more than one process group) * UNIMPLEMENTED/UNTESTED */ if (*p == '@') { /* Escaped @? */ if (p[1] == '@') { *pDest++ = *++p; continue; } /* If within an @...@, terminate it */ if (withinMworld) { withinMworld = 0; p++; } else if (withinMthread) { withinMthread = 0; p++; } else { /* Look for command */ p++; if (*p == 'W') { p++; withinMworld = 1; } else if (*p == 'T') { p++; withinMthread = 1; } else { /* Unrecognized char */ *pDest++ = *p++; } } } else if ((withinMworld && world_num == 0) || (withinMthread && nThread == 1)) { /* Simply skip this character since we're not showing * this string */ p++; } else if (*p == '%') { p++; if (*p == 'd') { char rankAsChar[20]; MPL_snprintf(rankAsChar, sizeof(rankAsChar), "%d", world_rank); *pDest = 0; MPL_strnapp(filename, rankAsChar, len); pDest += strlen(rankAsChar); } else if (*p == 't') { #if (MPL_THREAD_PACKAGE_NAME != MPL_THREAD_PACKAGE_NONE) char threadIDAsChar[30]; MPL_thread_id_t tid; MPL_thread_self(&tid); threadID = (unsigned long long int) tid; MPL_snprintf(threadIDAsChar, sizeof(threadIDAsChar), "%llx", threadID); *pDest = 0; MPL_strnapp(filename, threadIDAsChar, len); pDest += strlen(threadIDAsChar); #else *pDest++ = '0'; #endif /* MPL_THREAD_PACKAGE_NAME != MPL_THREAD_PACKAGE_NONE */ } else if (*p == 'w') { /* *pDest++ = '0'; */ *pDest = 0; MPL_strnapp(filename, world_numAsChar, len); pDest += strlen(world_numAsChar); } else if (*p == 'p') { /* Appends the pid of the proceess to the file name. */ char pidAsChar[20]; #if defined(MPL_HAVE_GETPID) pid_t pid = getpid(); #else int pid = -1; #endif /* MPL_HAVE_GETPID */ MPL_snprintf(pidAsChar, sizeof(pidAsChar), "%d", (int) pid); *pDest = 0; MPL_strnapp(filename, pidAsChar, len); pDest += strlen(pidAsChar); } else { *pDest++ = '%'; *pDest++ = *p; } p++; } else { *pDest++ = *p++; } } *pDest = 0; return 0; } /* This routine can make no MPI calls, since it may be logging those * calls. */ static int dbg_openfile(FILE ** dbg_fp) { int mpl_errno = MPL_SUCCESS; if (!file_pattern || *file_pattern == 0 || strcmp(file_pattern, "-stdout-") == 0) { *dbg_fp = stdout; } else if (strcmp(file_pattern, "-stderr-") == 0) { *dbg_fp = stderr; } else { char filename[MAXPATHLEN]; /* if we're not at DBG_INITIALIZED, we don't know our * rank yet, so we create a temp file, to be renamed later */ if (dbg_initialized != DBG_INITIALIZED) { mpl_errno = dbg_open_tmpfile(dbg_fp); if (mpl_errno) goto fn_fail; } else { mpl_errno = dbg_get_filename(filename, MAXPATHLEN); if (mpl_errno) goto fn_fail; *dbg_fp = fopen(filename, "w"); if (!*dbg_fp) { MPL_error_printf("Could not open log file %s\n", filename); goto fn_fail; } } } fn_exit: return mpl_errno; fn_fail: dbg_initialized = DBG_ERROR; mpl_errno = MPL_ERR_DBG_INTERN; goto fn_exit; } /* Support routines for processing mpich-dbg values */ /* Update the GLOBAL variable MPL_dbg_active_classes with the bits * corresponding to this name */ static int dbg_set_class(const char *s) { int i, found_match; size_t slen = 0; char *str; if (s && *s) slen = strlen(s); str = strtok((char *) s, ","); while (str) { found_match = 0; for (i = 0; i < num_classnames; i++) { size_t len = strlen(classnames[i].lcname); if (slen == len && (!strncmp(str, classnames[i].lcname, len) || !strncmp(str, classnames[i].ucname, len))) { /* we have a match */ MPL_dbg_active_classes |= classnames[i].classbits; found_match = 1; break; } } if (!found_match) { /* no match was found. the component might not have * registered yet. store the user string for later * access. */ unregistered_classes[num_unregistered_classes] = str; num_unregistered_classes++; } str = strtok(NULL, ","); } return 0; } /* Set the global MPL_dbg_max_level if there is a match with the known * level names */ static int dbg_set_level(const char *s, const char *(names[])) { int i; for (i = 0; names[i]; i++) { if (strcmp(names[i], s) == 0) { MPL_dbg_max_level = level_values[i]; return 0; } } return 1; } #endif /* MPL_USE_DBG_LOGGING */