/*! \fn static int getboolsetting(MYSQL *psql, const char *setting, int dflt) * \brief Obtains a boolean option from the database. * * Given the parameters for fetching a setting from the database, * do so for a *Boolean* value. We parse the usual set of words * meaning true/false, and if we don't get a value, or if we don't * understand what we fetched, we use the default value provided. * * \return boolean TRUE or FALSE based upon database setting or the DEFAULT if not found */ static int getboolsetting(MYSQL *psql, const char *setting, int dflt) { const char *rc; assert(psql != 0); assert(setting != 0); rc = getsetting(psql, setting); if (rc == 0) return dflt; if (STRIMATCH(rc, "on" ) || STRIMATCH(rc, "yes" ) || STRIMATCH(rc, "true") || STRIMATCH(rc, "1" ) ) { return TRUE; } if (STRIMATCH(rc, "off" ) || STRIMATCH(rc, "no" ) || STRIMATCH(rc, "false") || STRIMATCH(rc, "0" ) ) { return FALSE; } /* doesn't really match one of our keywords: what to do? */ return dflt; }
/*! \fn static const char *getsetting(MYSQL *psql, const char *setting) * \brief Returns a character pointer to a Cacti setting. * * Given a pointer to a database and the name of a setting, return the string * which represents the value from the settings table. Return NULL if we * can't find a setting for whatever reason. * * NOTE: if the user has provided one of these options on the command line, * it's intercepted here and returned, overriding the database setting. * * \return the database option setting * */ static const char *getsetting(MYSQL *psql, const char *setting) { char qstring[256]; MYSQL_RES *result; MYSQL_ROW mysql_row; int i; assert(psql != 0); assert(setting != 0); /* see if it's in the option table */ for (i=0; i<nopts; i++) { if (STRIMATCH(setting, opttable[i].opt)) { /* FOUND IT! */ return opttable[i].val; } } sprintf(qstring, "SELECT value FROM settings WHERE name = '%s'", setting); result = db_query(psql, qstring); if ((mysql_num_rows(result) > 0) && (mysql_row = mysql_fetch_row(result)) != 0) { return mysql_row[0]; }else{ return 0; } }
/*! \fn static const char *getglobalvariable(MYSQL *psql, const char *setting) * \brief Returns a character pointer to a MySQL global variable setting. * * Given a pointer to a database and the name of a global variable, return the string * which represents that value from the settings table. Return NULL if we * can't find a variable for whatever reason. * * \return the database global variable setting * */ static const char *getglobalvariable(MYSQL *psql, const char *setting) { char qstring[256]; MYSQL_RES *result; MYSQL_ROW mysql_row; int i; assert(psql != 0); assert(setting != 0); /* see if it's in the option table */ for (i=0; i<nopts; i++) { if (STRIMATCH(setting, opttable[i].opt)) { /* FOUND IT! */ return opttable[i].val; } } sprintf(qstring, "SHOW GLOBAL VARIABLES LIKE '%s'", setting); result = db_query(psql, qstring); if ((mysql_num_rows(result) > 0) && (mysql_row = mysql_fetch_row(result)) != 0) { return mysql_row[1]; }else{ return 0; } }
/*! \fn int read_spine_config(char *file) * \brief obtain default startup variables from the spine.conf file. * \param file the spine config file * * \return 0 if successful or -1 if the file could not be opened */ int read_spine_config(char *file) { FILE *fp; char buff[BUFSIZE]; char *buffer; char p1[BUFSIZE]; char p2[BUFSIZE]; if ((fp = fopen(file, "rb")) == NULL) { if (set.log_level == POLLER_VERBOSITY_DEBUG) { if (!set.stderr_notty) { fprintf(stderr, "ERROR: Could not open config file [%s]\n", file); } } return -1; }else{ if (!set.stdout_notty) { fprintf(stdout, "SPINE: Using spine config file [%s]\n", file); } while(!feof(fp)) { buffer = fgets(buff, BUFSIZE, fp); if (!feof(fp) && *buff != '#' && *buff != ' ' && *buff != '\n') { sscanf(buff, "%15s %255s", p1, p2); if (STRIMATCH(p1, "DB_Host")) STRNCOPY(set.dbhost, p2); else if (STRIMATCH(p1, "DB_Database")) STRNCOPY(set.dbdb, p2); else if (STRIMATCH(p1, "DB_User")) STRNCOPY(set.dbuser, p2); else if (STRIMATCH(p1, "DB_Pass")) STRNCOPY(set.dbpass, p2); else if (STRIMATCH(p1, "DB_Port")) set.dbport = atoi(p2); else if (STRIMATCH(p1, "Poller")) set.poller_id = atoi(p2); else if (STRIMATCH(p1, "DB_PreG")) { if (!set.stderr_notty) { fprintf(stderr,"WARNING: DB_PreG is no longer supported\n"); } } else if (STRIMATCH(p1, "SNMP_Clientaddr")) STRNCOPY(set.snmp_clientaddr, p2); else if (!set.stderr_notty) { fprintf(stderr,"WARNING: Unrecongized directive: %s=%s in %s\n", p1, p2, file); } *p1 = '\0'; *p2 = '\0'; } } if (strlen(set.dbpass) == 0) *set.dbpass = '******'; return 0; } }
/*! \fn int is_numeric(const char *string) * \brief check to see if a string is long or double * \param string the string to check * * \return TRUE if long or double, FALSE if not * */ int is_numeric(const char *string) { long local_lval; double local_dval; char *end_ptr_long, *end_ptr_double; int conv_base=10; int length; length = strlen(string); if (!length) { return FALSE; } /* check for an integer */ errno = 0; local_lval = strtol(string, &end_ptr_long, conv_base); if (errno != ERANGE) { if (end_ptr_long == string + length) { /* integer string */ return TRUE; }else if (end_ptr_long == string) { if (*end_ptr_long != '\0' && *end_ptr_long != '.' && *end_ptr_long != '-' && *end_ptr_long != '+') { /* ignore partial string matches but doubles can begin with '+', '-', '.' */ return FALSE; } } }else{ end_ptr_long = NULL; } /* check for a float */ errno = 0; local_dval = strtod(string, &end_ptr_double); if (errno != ERANGE) { if (end_ptr_double == string + length) { /* floating point string */ return TRUE; } }else{ end_ptr_double = NULL; } if (!errno) { if (STRIMATCH(string," ")) { return FALSE; }else{ return TRUE; } }else{ return FALSE; } }
/*! \fn void snmp_cactid_init() * \brief wrapper function for init_snmp * * Initializes snmp for the given application ID * */ void snmp_cactid_init(void) { #ifdef USE_NET_SNMP /* Only do numeric output */ #ifdef NETSNMP_DS_LIB_PRINT_NUMERIC_ENUM netsnmp_ds_set_boolean(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_PRINT_NUMERIC_ENUM, 1); #endif /* Prevent update of the snmpapp.conf file */ #ifdef NETSNMP_DS_LIB_DONT_PERSIST_STATE netsnmp_ds_set_boolean(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_DONT_PERSIST_STATE, 1); #endif /* Prevent update of the snmpapp.conf file */ #ifdef NETSNMP_DS_LIB_DISABLE_PERSISTENT_LOAD netsnmp_ds_set_boolean(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_DISABLE_PERSISTENT_LOAD, 1); #endif #ifdef NETSNMP_DS_LIB_DONT_PRINT_UNITS netsnmp_ds_set_boolean(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_DONT_PRINT_UNITS, 1); #endif netsnmp_ds_set_boolean(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_QUICK_PRINT, 1); netsnmp_ds_set_boolean(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_PRINT_BARE_VALUE, 1); netsnmp_ds_set_boolean(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_NUMERIC_TIMETICKS, 1); #ifdef PACKAGE_VERSION /* check that the headers we compiled with match the library we linked with - apparently not defined in UCD-SNMP... */ CACTID_LOG_DEBUG(("DEBUG: SNMP Header Version is %s\n", PACKAGE_VERSION)); CACTID_LOG_DEBUG(("DEBUG: SNMP Library Version is %s\n", netsnmp_get_version())); if(STRIMATCH(PACKAGE_VERSION,netsnmp_get_version())) { init_snmp("snmpapp"); }else{ /* report the error and quit cactid */ die("ERROR: SNMP Library Version Mismatch (%s vs %s)",PACKAGE_VERSION,netsnmp_get_version()); } #else CACTID_LOG_DEBUG(("DEBUG: Issues with SNMP Header Version information, assuming old version of Net-SNMP.\n")); init_snmp("snmpapp"); #endif #else ds_set_boolean(DS_LIBRARY_ID, DS_LIB_QUICK_PRINT, 1); ds_set_boolean(DS_LIBRARY_ID, DS_LIB_PRINT_BARE_VALUE, 1); ds_set_boolean(DS_LIBRARY_ID, DS_LIB_NUMERIC_TIMETICKS, 1); init_snmp("snmpapp"); #endif }
/*! \fn find_keyword_by_word(const struct keyword *tbl, const char *word, int dflt) * \brief takes a generic word and returns either TRUE or FALSE * \param tbl the table that contains the translation from text to boolean * \param word the word to compare against the table for the result * \param dflt the default value to be returned if the string can not be found * * Given a table of keywords and a user's word, look that word up in the * table and return the value associted with it. If the word is not found, * return the user-provide default value. * * The default-value parameter can be used for either the actual default * value of the parameter being searched for (say, LOGDEST_BOTH), or * a didn't-find-it value (say, -1) which the caller can key off of. * * NOTE: if the given word is all digits, it's parsed as a number and * returned numerically. * * \return TRUE, FALSE, or dflt depending on results of search * */ static int find_keyword_by_word(const struct keyword *tbl, const char *word, int dflt) { assert(tbl != 0); assert(word != 0); if (all_digits(word)) { return atoi(word); } for (; tbl->word; tbl++) { if (STRIMATCH(word, tbl->word)) { return tbl->value; } } return dflt; }
/*! \fn main(int argc, char *argv[]) * \brief The Cactid program entry point * \param argc The number of arguments passed to the function plus one (+1) * \param argv An array of the command line arguments * * The Cactid entry point. This function performs the following tasks. * 1) Processes command line input parameters * 2) Processes the Cactid configuration file to obtain database access information * 3) Process runtime parameters from the settings table * 4) Initialize the runtime threads and mutexes for the threaded environment * 5) Initialize Net-SNMP, MySQL, and the PHP Script Server (if required) * 6) Spawns X threads in order to process hosts * 7) Loop until either all hosts have been processed or until the poller runtime * has been exceeded * 8) Close database and free variables * 9) Log poller process statistics if required * 10) Exit * * Note: Command line runtime parameters override any database settings. * * \return 0 if SUCCESS, or -1 if FAILED * */ int main(int argc, char *argv[]) { struct timeval now; char *conf_file = NULL; double begin_time, end_time, current_time; int poller_interval; int num_rows; int device_counter = 0; int poller_counter = 0; int last_active_threads = 0; long int EXTERNAL_THREAD_SLEEP = 100000; long int internal_thread_sleep; time_t nowbin; struct tm now_time; struct tm *now_ptr; pthread_t* threads = NULL; pthread_attr_t attr; int* ids = NULL; MYSQL mysql; MYSQL_RES *result = NULL; MYSQL_ROW mysql_row; int canexit = 0; int host_id; int i; int mutex_status = 0; int thread_status = 0; UNUSED_PARAMETER(argc); /* we operate strictly with argv */ /* establish php processes and initialize space */ php_processes = (php_t*) calloc(MAX_PHP_SERVERS, sizeof(php_t)); for (i = 0; i < MAX_PHP_SERVERS; i++) { php_processes[i].php_state = PHP_BUSY; } /* set start time for cacti */ begin_time = get_time_as_double(); /* get time for poller_output table */ if (time(&nowbin) == (time_t) - 1) { die("ERROR: Could not get time of day from time()\n"); } localtime_r(&nowbin,&now_time); now_ptr = &now_time; if (strftime(start_datetime, sizeof(start_datetime), "%Y-%m-%d %H:%M:%S", now_ptr) == (size_t) 0) { die("ERROR: Could not get string from strftime()\n"); } /* set default verbosity */ set.log_level = POLLER_VERBOSITY_LOW; /* get static defaults for system */ config_defaults(); /*! ---------------------------------------------------------------- * PROCESS COMMAND LINE * * Run through the list of ARGV words looking for parameters we * know about. Most have two flavors (-C + --conf), and many * themselves take a parameter. * * These parameters can be structured in two ways: * * --conf=FILE both parts in one argv[] string * --conf FILE two separate argv[] strings * * We set "arg" to point to "--conf", and "opt" to point to FILE. * The helper routine * * In each loop we set "arg" to next argv[] string, then look * to see if it has an equal sign. If so, we split it in half * and point to the option separately. * * NOTE: most direction to the program is given with dash-type * parameters, but we also allow standalone numeric device IDs * in "first last" format: this is how poller.php calls this * program. */ /* initialize some global variables */ set.start_host_id = -1; set.end_host_id = -1; set.php_initialized = FALSE; set.logfile_processed = FALSE; set.parent_fork = CACTID_PARENT; for (argv++; *argv; argv++) { char *arg = *argv; char *opt = strchr(arg, '='); /* pick off the =VALUE part */ if (opt) *opt++ = '\0'; if (STRIMATCH(arg, "-f") || STRIMATCH(arg, "--first")) { if (HOSTID_DEFINED(set.start_host_id)) { die("ERROR: %s can only be used once", arg); } set.start_host_id = atoi(opt = getarg(opt, &argv)); if (!HOSTID_DEFINED(set.start_host_id)) { die("ERROR: '%s=%s' is invalid first-host ID", arg, opt); } } else if (STRIMATCH(arg, "-l") || STRIMATCH(arg, "--last")) { if (HOSTID_DEFINED(set.end_host_id)) { die("ERROR: %s can only be used once", arg); } set.end_host_id = atoi(opt = getarg(opt, &argv)); if (!HOSTID_DEFINED(set.end_host_id)) { die("ERROR: '%s=%s' is invalid last-host ID", arg, opt); } } else if (STRIMATCH(arg, "-p") || STRIMATCH(arg, "--poller")) { set.poller_id = atoi(getarg(opt, &argv)); } else if (STRIMATCH(arg, "-h") || STRIMATCH(arg, "-v") || STRIMATCH(arg, "--help") || STRIMATCH(arg, "--version")) { display_help(); exit(EXIT_SUCCESS); } else if (STRIMATCH(arg, "-O") || STRIMATCH(arg, "--option")) { char *setting = getarg(opt, &argv); char *value = strchr(setting, ':'); if (*value) { *value++ = '\0'; }else{ die("ERROR: -O requires setting:value"); } set_option(setting, value); } else if (STRIMATCH(arg, "-R") || STRIMATCH(arg, "--readonly") || STRIMATCH(arg, "--read-only")) { set.SQL_readonly = TRUE; } else if (STRIMATCH(arg, "-C") || STRIMATCH(arg, "--conf")) { conf_file = strdup(getarg(opt, &argv)); } else if (STRIMATCH(arg, "-S") || STRIMATCH(arg, "--stdout")) { set_option("log_destination", "STDOUT"); } else if (STRIMATCH(arg, "-L") || STRIMATCH(arg, "--log")) { set_option("log_destination", getarg(opt, &argv)); } else if (STRIMATCH(arg, "-V") || STRIMATCH(arg, "--verbosity")) { set_option("log_verbosity", getarg(opt, &argv)); } else if (STRIMATCH(arg, "--snmponly") || STRIMATCH(arg, "--snmp-only")) { set.snmponly = TRUE; } else if (!HOSTID_DEFINED(set.start_host_id) && all_digits(arg)) { set.start_host_id = atoi(arg); } else if (!HOSTID_DEFINED(set.end_host_id) && all_digits(arg)) { set.end_host_id = atoi(arg); } else { die("ERROR: %s is an unknown command-line parameter", arg); } } /* we require either both the first and last hosts, or niether host */ if (HOSTID_DEFINED(set.start_host_id) != HOSTID_DEFINED(set.end_host_id)) { die("ERROR: must provide both -f/-l, or neither"); } if (set.start_host_id > set.end_host_id) { die("ERROR: Invalid row spec; first host_id must be less than the second"); } /* read configuration file to establish local environment */ if (conf_file) { if ((read_cactid_config(conf_file)) < 0) { die("ERROR: Could not read config file: %s\n", conf_file); } }else{ if (!(conf_file = calloc(1, BUFSIZE))) { die("ERROR: Fatal malloc error: cactid.c conf_file!\n"); } for (i=0; i<CONFIG_PATHS; i++) { snprintf(conf_file, BUFSIZE-1, "%s%s", config_paths[i], DEFAULT_CONF_FILE); if (read_cactid_config(conf_file) >= 0) { break; } if (i == CONFIG_PATHS-1) { snprintf(conf_file, BUFSIZE-1, "%s%s", config_paths[0], DEFAULT_CONF_FILE); } } } /* read settings table from the database to further establish environment */ read_config_options(); /* set the poller interval for those who use less than 5 minute intervals */ if (set.poller_interval == 0) { poller_interval = 300; }else { poller_interval = set.poller_interval; } /* calculate the external_tread_sleep value */ internal_thread_sleep = EXTERNAL_THREAD_SLEEP * set.num_parent_processes / 2; if (set.log_level == POLLER_VERBOSITY_DEBUG) { CACTID_LOG_DEBUG(("CACTID: Version %s starting\n", VERSION)); }else{ printf("CACTID: Version %s starting\n", VERSION); } /* connect to database */ db_connect(set.dbdb, &mysql); /* initialize SNMP */ CACTID_LOG_DEBUG(("CACTID: Initializing Net-SNMP API\n")); snmp_cactid_init(); /* initialize PHP if required */ CACTID_LOG_DEBUG(("CACTID: Initializing PHP Script Server\n")); /* tell cactid that it is parent, and set the poller id */ set.parent_fork = CACTID_PARENT; set.poller_id = 0; /* initialize the script server */ if (set.php_required) { php_init(PHP_INIT); set.php_initialized = TRUE; set.php_current_server = 0; } /* obtain the list of hosts to poll */ { char querybuf[256], *qp = querybuf; qp += sprintf(qp, "SELECT id FROM host"); qp += sprintf(qp, " WHERE disabled=''"); qp += append_hostrange(qp, "id"); /* AND id BETWEEN a AND b */ qp += sprintf(qp, " ORDER BY id"); result = db_query(&mysql, querybuf); } num_rows = mysql_num_rows(result) + 1; /* add 1 for host = 0 */ if (!(threads = (pthread_t *)malloc(num_rows * sizeof(pthread_t)))) { die("ERROR: Fatal malloc error: cactid.c threads!\n"); } if (!(ids = (int *)malloc(num_rows * sizeof(int)))) { die("ERROR: Fatal malloc error: cactid.c host id's!\n"); } /* initialize threads and mutexes */ pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); init_mutexes(); CACTID_LOG_DEBUG(("DEBUG: Initial Value of Active Threads is %i\n", active_threads)); /* tell fork processes that they are now active */ set.parent_fork = CACTID_FORK; /* loop through devices until done */ while ((device_counter < num_rows) && (canexit == 0)) { mutex_status = thread_mutex_trylock(LOCK_THREAD); switch (mutex_status) { case 0: if (last_active_threads != active_threads) { last_active_threads = active_threads; } while ((active_threads < set.threads) && (device_counter < num_rows)) { if (device_counter > 0) { mysql_row = mysql_fetch_row(result); host_id = atoi(mysql_row[0]); ids[device_counter] = host_id; }else{ ids[device_counter] = 0; } /* create child process */ thread_status = pthread_create(&threads[device_counter], &attr, child, &ids[device_counter]); switch (thread_status) { case 0: CACTID_LOG_DEBUG(("DEBUG: Valid Thread to be Created\n")); device_counter++; active_threads++; CACTID_LOG_DEBUG(("DEBUG: The Value of Active Threads is %i\n", active_threads)); break; case EAGAIN: CACTID_LOG(("ERROR: The System Lacked the Resources to Create a Thread\n")); break; case EFAULT: CACTID_LOG(("ERROR: The Thread or Attribute Was Invalid\n")); break; case EINVAL: CACTID_LOG(("ERROR: The Thread Attribute is Not Initialized\n")); break; default: CACTID_LOG(("ERROR: Unknown Thread Creation Error\n")); break; } usleep(internal_thread_sleep); /* get current time and exit program if time limit exceeded */ if (poller_counter >= 20) { current_time = get_time_as_double(); if ((current_time - begin_time + 6) > poller_interval) { CACTID_LOG(("ERROR: Cactid Timed Out While Processing Hosts Internal\n")); canexit = 1; break; } poller_counter = 0; }else{ poller_counter++; } } thread_mutex_unlock(LOCK_THREAD); break; case EDEADLK: CACTID_LOG(("ERROR: Deadlock Occured\n")); break; case EBUSY: break; case EINVAL: CACTID_LOG(("ERROR: Attempt to Unlock an Uninitialized Mutex\n")); break; case EFAULT: CACTID_LOG(("ERROR: Attempt to Unlock an Invalid Mutex\n")); break; default: CACTID_LOG(("ERROR: Unknown Mutex Lock Error Code Returned\n")); break; } usleep(internal_thread_sleep); /* get current time and exit program if time limit exceeded */ if (poller_counter >= 20) { current_time = get_time_as_double(); if ((current_time - begin_time + 6) > poller_interval) { CACTID_LOG(("ERROR: Cactid Timed Out While Processing Hosts Internal\n")); canexit = 1; break; } poller_counter = 0; }else{ poller_counter++; } } /* wait for all threads to complete */ while (canexit == 0) { if (thread_mutex_trylock(LOCK_THREAD) == 0) { if (last_active_threads != active_threads) { last_active_threads = active_threads; } if (active_threads == 0) { canexit = 1; } thread_mutex_unlock(LOCK_THREAD); } usleep(EXTERNAL_THREAD_SLEEP); /* get current time and exit program if time limit exceeded */ if (poller_counter >= 20) { current_time = get_time_as_double(); if ((current_time - begin_time + 6) > poller_interval) { CACTID_LOG(("ERROR: Cactid Timed Out While Processing Hosts Internal\n")); canexit = 1; break; } poller_counter = 0; }else{ poller_counter++; } } /* tell Cactid that it is now parent */ set.parent_fork = CACTID_PARENT; /* print out stats */ gettimeofday(&now, NULL); /* update the db for |data_time| on graphs */ db_insert(&mysql, "replace into settings (name,value) values ('date',NOW())"); db_insert(&mysql, "insert into poller_time (poller_id, start_time, end_time) values (0, NOW(), NOW())"); /* cleanup and exit program */ pthread_attr_destroy(&attr); CACTID_LOG_DEBUG(("DEBUG: Thread Cleanup Complete\n")); /* close the php script server */ if (set.php_required) { php_close(PHP_INIT); } CACTID_LOG_DEBUG(("DEBUG: PHP Script Server Pipes Closed\n")); /* free malloc'd variables */ free(threads); free(ids); free(conf_file); CACTID_LOG_DEBUG(("DEBUG: Allocated Variable Memory Freed\n")); /* shutdown SNMP */ snmp_cactid_close(); CACTID_LOG_DEBUG(("CACTID: Net-SNMP API Shutdown Completed\n")); /* close mysql */ mysql_free_result(result); mysql_close(&mysql); CACTID_LOG_DEBUG(("DEBUG: MYSQL Free & Close Completed\n")); /* finally add some statistics to the log and exit */ end_time = TIMEVAL_TO_DOUBLE(now); CACTID_LOG_MEDIUM(("Time: %.4f s, Threads: %i, Hosts: %i\n", (end_time - begin_time), set.threads, num_rows)); exit(EXIT_SUCCESS); }