diff options
author | Joshua Tolley | 2010-11-26 17:22:01 +0000 |
---|---|---|
committer | Joshua Tolley | 2010-11-26 17:22:01 +0000 |
commit | fe9d8f21bbdfb442b8233b4627a0c67dc458a8bd (patch) | |
tree | a88daa49fc47e64777af3d331cf142e0604c2d48 /customquery.c |
Diffstat (limited to 'customquery.c')
-rw-r--r-- | customquery.c | 490 |
1 files changed, 490 insertions, 0 deletions
diff --git a/customquery.c b/customquery.c new file mode 100644 index 0000000..9828718 --- /dev/null +++ b/customquery.c @@ -0,0 +1,490 @@ +#include "pgsnmpd.h" +#include <net-snmp/net-snmp-config.h> +#include <net-snmp/net-snmp-includes.h> +#include <net-snmp/agent/net-snmp-agent-includes.h> +#include <net-snmp/library/snmp_assert.h> +#include <time.h> +#include "customquery.h" +#include "query_reader.h" + + /* TODO: if this works, move it to pgsnmpd.h */ +#define CQ_MAX_LEN 255 +/* TODO: change this when we support multiple connections */ +const int pgsnmpdConnID = 1; +/* TODO: change this when we support row slices in rdbmsDbTable */ +const int rdbmsDbIndex = 1; + +char *custom_query_config_file = NULL; + +/* This makes it easier to account for rdbmsDbIndex and pgsnmpdConnID */ +const int extra_idx_count = 2; + + /* + * Note: this code uses net-snmp containers. See + * http://net-snmp.sourceforge.net/wiki/index.php/Containers for a helpful + * reference on the subject + */ + +oid * +parse_oid(const char *oid_str_orig, int *oid_len) { + int len = strlen(oid_str_orig), i; + char *a, *b, *oid_str; + oid *result; + + oid_str = strdup((char *)oid_str_orig); + if (oid_str == NULL) { + snmp_log(LOG_ERR, "Couldn't allocate copy of OID string\n"); + exit(1); + } + a = oid_str; + + result = malloc(len * sizeof(oid)); + if (result == NULL) { + snmp_log(LOG_ERR, "Couldn't allocate array of %d OIDs\n", *oid_len); + exit(1); + } + i = 0; + + a = oid_str; + *oid_len = 0; + while (a - oid_str < len) { + b = strpbrk(a, "."); + if (b == NULL) { + result[i] = atoi(a); + (*oid_len)++; + break; + } + else { + *b = '\0'; + result[i++] = atoi(a); + a = b+1; + (*oid_len)++; + } + } + free(oid_str); + return result; +} + +typedef struct cust_query_row { + netsnmp_index row_index; + oid *myoids; + pgsnmpd_query *query; + int rownum; +} cust_query_row; + +pgsnmpd_query *head; + +pgsnmpd_query *read_custom_queries(void); +void free_query(pgsnmpd_query *query); +void fill_query_column_types(pgsnmpd_query *query); +void fill_query_container(pgsnmpd_query *query); + +int custom_query_get_value( + netsnmp_request_info *request, + netsnmp_index *item, + netsnmp_table_request_info *table_info ); + +int run_query(pgsnmpd_query *query); +int set_val_from_string(netsnmp_variable_list *var, u_char type, char *val); + +/* + * Frees memory associated with an allocated (or partially allocated) + * pgsnmpd_query structure, and all such structures following it in the + * query list +*/ +void free_query(pgsnmpd_query *query) { + if (query == NULL) return; + if (query->table_name != NULL) free(query->table_name); + if (query->query_text != NULL) free(query->query_text); + if (query->table_oid != NULL) free(query->table_oid); + if (query->types != NULL) free(query->types); + if (query->result != NULL) PQclear(query->result); + if (query->next != NULL) free_query(query->next); + free(query); +} + +/* Allocates a pgsnmpd_query struct */ +pgsnmpd_query * +alloc_custom_query(char *table_name, char *query_text, oid *table_oid, int oid_len) { + pgsnmpd_query *query; + int i; + + query = malloc(sizeof(pgsnmpd_query)); + if (query == NULL) return NULL; + query->result = NULL; + query->next = NULL; + query->last_refresh = 0; + query->cache_timeout = -1; + query->my_handler = NULL; + query->typeslen = 0; + + i = strnlen(table_name, CQ_MAX_LEN); + query->table_name = strndup(table_name, i); + if (query->table_name == NULL) { + free_query(query); + return NULL; + } + + i = strnlen(query_text, CQ_MAX_LEN); + query->query_text = strndup(query_text, i); + if (query->query_text == NULL) { + free_query(query); + return NULL; + } + + query->table_oid = malloc(sizeof(oid) * oid_len); + if (query->table_oid == NULL) { + free_query(query); + return NULL; + } + for (i = 0; i < oid_len; i++) { + query->table_oid[i] = table_oid[i]; + } + query->oid_len = oid_len; + + return query; +} + +int run_query(pgsnmpd_query *query) { + /* TODO: remember to destroy all old rows when updating query. */ + time_t curtime; + + if (query == NULL) return -1; + if (PQstatus(dbconn) != CONNECTION_OK) + return -1; + if (query->result != NULL) + PQclear(query->result); + + query->result = PQexec(dbconn, query->query_text); + if (PQresultStatus(query->result) != PGRES_TUPLES_OK) { + snmp_log(LOG_ERR, "Failed to run query \"%s\"\n", query->query_text); + PQclear(query->result); + return -1; + } + curtime = time(NULL); + query->last_refresh = curtime; + query->colcount = PQnfields(query->result); + query->rowcount = PQntuples(query->result); + + return 1; +} + +void fill_query_column_types(pgsnmpd_query *query) +{ + int i; + Oid type; /* NB! PostgreSQL's Oid, not Net-SNMP's oid */ + PGresult *res; + const char *values[1]; + char param[10]; + + /* This translates SQL types to SNMP types, as follows: + * Conversions for these four types are obvious + * ASN_INTEGER + * ASN_FLOAT + * ASN_BOOLEAN + * ASN_OBJECT_ID + * + * Everything else becomes a string: + * ASN_OCTET_STR + * + * Perhaps one day we'll also use ASN_DOUBLE + */ + + if (query->result == NULL) + return; + + values[0] = param; + + for (i = 0; i < query->colcount; i++) { + if (query->types[i] != 255) { + continue; + } + type = PQftype(query->result, i); + /* + * TODO: query pg_type table (including pg_type.h to use builtin + * constants got all kinds of errors I'd rather not deal with + */ + sprintf(param, "%d", type); + res = PQexecPrepared(dbconn, "TYPEQUERY", 1, values, NULL, NULL, 0); + if (PQresultStatus(res) != PGRES_TUPLES_OK) + snmp_log(LOG_ERR, "Couldn't determine column type\n"); + else { + switch (atoi(PQgetvalue(res, 0, 0))) { + case 0: + query->types[i] = ASN_INTEGER; + break; + case 1: + query->types[i] = ASN_FLOAT; + break; + case 2: + query->types[i] = ASN_BOOLEAN; + break; + case 3: + query->types[i] = ASN_OCTET_STR; + break; + default: /* If we get here, it's because the TYPEQUERY is b0rken */ + snmp_log(LOG_ERR, "Unknown column type translation. This is a bug.\n"); + } + } + PQclear(res); + } +} + +void +init_types_array(u_char *types, int start, int end) { + int i = (start < 0 ? 0 : start); + + for (; i <= end; i++) + types[i] = 255; +} + +/* General initialization */ +void init_custom_queries(void) +{ + netsnmp_table_registration_info *table_info; + pgsnmpd_query *curquery; + int i; + PGresult *res; + + if (custom_query_config_file == NULL) + return; + + /* + ASN_INTEGER = 0 + ASN_FLOAT = 1 + ASN_BOOLEAN = 2 + ASN_OCTET_STR = 3 + */ + + res = PQprepare(dbconn, "TYPEQUERY", + "SELECT CASE " + "WHEN typname LIKE 'int%' OR typname = 'xid' OR typname = 'oid'" + "THEN 0 " + "WHEN typname LIKE 'float%' THEN 1 " + "WHEN typname = 'bool' THEN 2 " + "ELSE 3 " + "END " + "FROM pg_catalog.pg_type WHERE oid = $1", 1, NULL); + if (PQresultStatus(res) != PGRES_COMMAND_OK) { + snmp_log(LOG_ERR, "Failed to prepare statement (error: %s)\n", PQresStatus(PQresultStatus(res))); + return; + } + PQclear(res); + + head = parse_config(custom_query_config_file); + if (head == NULL) { + snmp_log(LOG_INFO, "No custom queries initialized\n"); + return; + } + + for (curquery = head; curquery != NULL; curquery = curquery->next) { + run_query(curquery); + if (curquery->typeslen < curquery->colcount) + curquery->types = realloc(curquery->types, sizeof(u_char) * curquery->colcount); + if (curquery->types == NULL) { + snmp_log(LOG_ERR, "Memory allocation problem"); + return; + } + init_types_array(curquery->types, curquery->typeslen, curquery->colcount); + fill_query_column_types(curquery); + if (curquery->my_handler) { + snmp_log(LOG_ERR, + "init_custom_queries called again for query %s\n", + curquery->table_name); + return; + } + + memset(&(curquery->cb), 0x00, sizeof(curquery->cb)); + + /** create the table structure itself */ + snmp_log(LOG_INFO, "Initializing table name %s\n", curquery->table_name); + table_info = SNMP_MALLOC_TYPEDEF(netsnmp_table_registration_info); + curquery->my_handler = netsnmp_create_handler_registration( + curquery->table_name, + netsnmp_table_array_helper_handler, + curquery->table_oid, + curquery->oid_len, + HANDLER_CAN_RONLY + ); + + if (!curquery->my_handler || !table_info) { + snmp_log(LOG_ERR, "malloc failed in init_custom_queries\n"); + return; /** mallocs failed */ + } + + netsnmp_table_helper_add_index(table_info, ASN_INTEGER); /* pgsnmpdConnID */ + netsnmp_table_helper_add_index(table_info, ASN_INTEGER); /* rdbmsDbIndex */ + for (i = 0; i < curquery->num_indexes; i++) + netsnmp_table_helper_add_index(table_info, curquery->types[i]); + + table_info->min_column = curquery->min_colnum; + table_info->max_column = curquery->colcount; + + curquery->cb.get_value = custom_query_get_value; + curquery->cb.container = netsnmp_container_find("table_container"); + + DEBUGMSGTL(("init_custom_queries", + "Registering table for query " + "as a table array\n")); + switch (netsnmp_table_container_register(curquery->my_handler, + table_info, &curquery->cb, + curquery->cb.container, 1)) { + case MIB_REGISTRATION_FAILED: + snmp_log(LOG_INFO, "Failed to register table %s\n", curquery->table_name); + break; + case MIB_DUPLICATE_REGISTRATION: + snmp_log(LOG_INFO, "Duplicate registration for table %s\n", curquery->table_name); + break; + case MIB_REGISTERED_OK: + DEBUGMSGTL(("init_custom_queries", + "Successfully registered table %s\n", curquery->table_name)); + break; + default: + snmp_log(LOG_INFO, "Unknown registration result for table %s\n", curquery->table_name); + } + + /* Having set everything up, fill the table's container with data */ + fill_query_container(curquery); + } + snmp_log(LOG_DEBUG, "Finished intializing queries\n"); +} + +void fill_query_container(pgsnmpd_query *query) +{ + /* TODO: Make this work */ + cust_query_row *row; + int i, j, string_idx_count, string_idx_len; + netsnmp_variable_list var_rdbmsDbIndex, var_pgsnmpdConnID, *var_otherIndexes; + int err = SNMP_ERR_NOERROR; + char *val; + + memset( &var_pgsnmpdConnID, 0x00, sizeof(var_pgsnmpdConnID) ); + var_pgsnmpdConnID.type = ASN_INTEGER; + memset( &var_rdbmsDbIndex, 0x00, sizeof(var_rdbmsDbIndex) ); + var_rdbmsDbIndex.type = ASN_INTEGER; + + var_otherIndexes = malloc(sizeof(netsnmp_variable_list) * query->num_indexes); + if (var_otherIndexes == NULL) { + snmp_log(LOG_ERR, "Memory allocation error\n"); + return; + } + memset(var_otherIndexes, 0, sizeof(netsnmp_variable_list) * query->num_indexes); + + string_idx_count = 0; + for (i = 0; i < query->num_indexes; i++) { + if (i < query->num_indexes - 1) + var_otherIndexes[i].next_variable = &var_otherIndexes[i+1]; + var_otherIndexes[i].type = query->types[i]; + if (query->types[i] == ASN_OCTET_STR) + string_idx_count++; + } + + var_pgsnmpdConnID.next_variable = &var_rdbmsDbIndex; + var_rdbmsDbIndex.next_variable = var_otherIndexes; + + snmp_set_var_typed_value(&var_rdbmsDbIndex, ASN_INTEGER, (u_char *) &rdbmsDbIndex, sizeof(int)); + snmp_set_var_typed_value(&var_pgsnmpdConnID, ASN_INTEGER, (u_char *) &pgsnmpdConnID, sizeof(int)); + + for (i = 0; i < query->rowcount; i++) { + string_idx_len = 0; + for (j = 0; j < query->num_indexes; j++) { + val = PQgetvalue(query->result, i, j); + /* TODO: Floats and OIDs also need more than the usual memory. + * Learn to handle them. Until then we can expect problems using + * OIDs and Floats as indexes */ + if (query->types[j] == ASN_OCTET_STR) string_idx_len += strlen(val); + set_val_from_string(&var_otherIndexes[j], query->types[j], val); + } + row = SNMP_MALLOC_TYPEDEF(cust_query_row); + row->myoids = malloc(sizeof(oid) * + (query->num_indexes + extra_idx_count + string_idx_count + string_idx_len)); + if (row->myoids == NULL) { + snmp_log(LOG_ERR, "memory allocation problem \n"); + return; + } + + row->row_index.len = query->num_indexes + extra_idx_count + string_idx_len + string_idx_count; + row->row_index.oids = row->myoids; + + err = build_oid_noalloc(row->row_index.oids, row->row_index.len, + (size_t *) &(row->row_index.len), NULL, 0, &var_pgsnmpdConnID); + if (err) + snmp_log(LOG_ERR,"error %d converting index to oid, query %s\n", err, + query->table_name); + + row->query = query; + row->rownum = i; + + CONTAINER_INSERT(query->cb.container, row); + } +} + +/* + * custom_query_get_value + * + * This routine is called for get requests to copy the data + * from the context to the varbind for the request. If the + * context has been properly maintained, you don't need to + * change in code in this fuction. + */ +int custom_query_get_value( + netsnmp_request_info *request, + netsnmp_index *item, + netsnmp_table_request_info *table_info ) +{ + int column = table_info->colnum; + netsnmp_variable_list *var = request->requestvb; + cust_query_row *context = (cust_query_row *)item; + + if (context->query->result == NULL) { + snmp_log(LOG_ERR, "No valid result for table\n"); + /* TODO: Make this less fatal? */ + return SNMP_ERR_GENERR; + } + if (column > context->query->colcount + extra_idx_count) { + snmp_log(LOG_ERR, "Unknown column in requested table\n"); + return SNMP_ERR_GENERR; + } + + return + set_val_from_string(var, context->query->types[column - context->query->min_colnum], + PQgetvalue(context->query->result, context->rownum, column - context->query->min_colnum)); +} + +int set_val_from_string(netsnmp_variable_list *var, u_char type, char *val) { + int i; + float f; + u_char myoid[] = { 1, 3, 6, 1, 3, 1 }; + + switch (type) { + case ASN_INTEGER: + i = atoi(val); + snmp_set_var_typed_value(var, ASN_INTEGER, + (u_char *) &i, sizeof(int)); + break; + case ASN_FLOAT: + f = strtof(val, NULL); + snmp_set_var_typed_value(var, ASN_OPAQUE_FLOAT, + (u_char *) &f, sizeof(float)); + break; + case ASN_BOOLEAN: + if (val[0] == 't') + i = 1; + else i = 2; + snmp_set_var_typed_value(var, ASN_INTEGER, + (u_char *) &i, sizeof(int)); + break; + case ASN_OCTET_STR: + snmp_set_var_typed_value(var, type, + (u_char *) val, strlen(val) * sizeof(char)); + break; + case ASN_OBJECT_ID: + snmp_log(LOG_ERR, "Passing back an OBJECT_ID\n"); + snmp_set_var_typed_value(var, type, myoid, sizeof(u_char) * 6); /* (u_char *) val, 5); strlen(val) * sizeof(char)); */ + break; + default: + snmp_log(LOG_ERR, "Unknown data type returning result. This is a bug.\n"); + return SNMP_ERR_GENERR; + } + return SNMP_ERR_NOERROR; +} |