SNMP Agent plugin: Add multiple key index support
[collectd.git] / src / snmp_agent.c
1 /**
2  * collectd - src/snmp_agent.c
3  *
4  * Copyright(c) 2017 Intel Corporation. All rights reserved.
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a copy
7  * of this software and associated documentation files (the "Software"), to deal
8  * in the Software without restriction, including without limitation the rights
9  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10  * copies of the Software, and to permit persons to whom the Software is
11  * furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be included in
14  * all copies or substantial portions of the Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22  * SOFTWARE.
23  *
24  * Authors:
25  *   Roman Korynkevych <romanx.korynkevych@intel.com>
26  *   Serhiy Pshyk <serhiyx.pshyk@intel.com>
27  *   Marcin Mozejko <marcinx.mozejko@intel.com>
28  **/
29
30 #include "collectd.h"
31
32 #include "common.h"
33 #include "utils_avltree.h"
34 #include "utils_cache.h"
35 #include "utils_llist.h"
36
37 #include <net-snmp/net-snmp-config.h>
38
39 #include <net-snmp/net-snmp-includes.h>
40
41 #include <net-snmp/agent/net-snmp-agent-includes.h>
42
43 #define PLUGIN_NAME "snmp_agent"
44 #define TYPE_STRING -1
45 #define MAX_INDEX_TYPES 5
46
47 /* Identifies index key origin */
48 enum index_key_e {
49   INDEX_HOST = 0,
50   INDEX_PLUGIN,
51   INDEX_PLUGIN_INSTANCE,
52   INDEX_TYPE,
53   INDEX_TYPE_INSTANCE
54 };
55 typedef enum index_key_e index_key_t;
56
57 struct oid_s {
58   oid oid[MAX_OID_LEN];
59   size_t oid_len;
60   u_char type;
61 };
62 typedef struct oid_s oid_t;
63
64 struct table_definition_s {
65   char *name;
66   oid_t index_oid;
67   oid_t size_oid;
68   llist_t *columns;
69   c_avl_tree_t *instance_index;
70   c_avl_tree_t *index_instance;
71   index_key_t indexes[MAX_INDEX_TYPES]; /* Stores information about what each
72                                            index key represents */
73   int indexes_len;
74   netsnmp_variable_list *index_list_cont; /* Index key container used for
75                                              generating as well as parsing
76                                              OIDs, not thread-safe */
77 };
78 typedef struct table_definition_s table_definition_t;
79
80 struct data_definition_s {
81   char *name;
82   char *plugin;
83   char *plugin_instance;
84   char *type;
85   char *type_instance;
86   const table_definition_t *table;
87   bool is_index_key; /* indicates if table column is an index key */
88   int index_key_pos; /* position in indexes list */
89   oid_t *oids;
90   size_t oids_len;
91   double scale;
92   double shift;
93 };
94 typedef struct data_definition_s data_definition_t;
95
96 struct snmp_agent_ctx_s {
97   pthread_t thread;
98   pthread_mutex_t lock;
99   pthread_mutex_t agentx_lock;
100   struct tree *tp;
101
102   llist_t *tables;
103   llist_t *scalars;
104 };
105 typedef struct snmp_agent_ctx_s snmp_agent_ctx_t;
106
107 static snmp_agent_ctx_t *g_agent;
108 const char *const index_opts[MAX_INDEX_TYPES] = {
109     "Hostname", "Plugin", "PluginInstance", "Type", "TypeInstance"};
110
111 #define CHECK_DD_TYPE(_dd, _p, _pi, _t, _ti)                                   \
112   (_dd->plugin ? !strcmp(_dd->plugin, _p) : 0) &&                              \
113       (_dd->plugin_instance ? !strcmp(_dd->plugin_instance, _pi) : 1) &&       \
114       (_dd->type ? !strcmp(_dd->type, _t) : 0) &&                              \
115       (_dd->type_instance ? !strcmp(_dd->type_instance, _ti) : 1)
116
117 static int snmp_agent_shutdown(void);
118 static void *snmp_agent_thread_run(void *arg);
119 static int snmp_agent_register_oid(oid_t *oid, Netsnmp_Node_Handler *handler);
120 static int snmp_agent_set_vardata(void *dst_buf, size_t *dst_buf_len,
121                                   u_char asn_type, double scale, double shift,
122                                   const void *value, size_t len, int type);
123 static int snmp_agent_unregister_oid_index(oid_t *oid, int index);
124
125 static u_char snmp_agent_get_asn_type(oid *oid, size_t oid_len) {
126   struct tree *node = get_tree(oid, oid_len, g_agent->tp);
127
128   return (node != NULL) ? mib_to_asn_type(node->type) : 0;
129 }
130
131 static char *snmp_agent_get_oid_name(oid *oid, size_t oid_len) {
132   struct tree *node = get_tree(oid, oid_len, g_agent->tp);
133
134   return (node != NULL) ? node->label : NULL;
135 }
136
137 static int snmp_agent_oid_to_string(char *buf, size_t buf_size,
138                                     oid_t const *o) {
139   char oid_str[MAX_OID_LEN][16];
140   char *oid_str_ptr[MAX_OID_LEN];
141
142   for (size_t i = 0; i < o->oid_len; i++) {
143     snprintf(oid_str[i], sizeof(oid_str[i]), "%lu", (unsigned long)o->oid[i]);
144     oid_str_ptr[i] = oid_str[i];
145   }
146
147   return strjoin(buf, buf_size, oid_str_ptr, o->oid_len, ".");
148 }
149
150 /* Prints a configuration storing list. It handles both table columns list
151    and scalars list */
152 #if COLLECT_DEBUG
153 static void snmp_agent_dump_data(llist_t *list) {
154   char oid_str[DATA_MAX_NAME_LEN];
155   for (llentry_t *de = llist_head(list); de != NULL; de = de->next) {
156     data_definition_t *dd = de->value;
157
158     if (dd->table != NULL)
159       DEBUG(PLUGIN_NAME ":   Column:");
160     else
161       DEBUG(PLUGIN_NAME ": Scalar:");
162
163     DEBUG(PLUGIN_NAME ":     Name: %s", dd->name);
164     if (dd->plugin)
165       DEBUG(PLUGIN_NAME ":     Plugin: %s", dd->plugin);
166     if (dd->plugin_instance)
167       DEBUG(PLUGIN_NAME ":     PluginInstance: %s", dd->plugin_instance);
168     if (dd->is_index_key)
169       DEBUG(PLUGIN_NAME ":     Index: %s", index_opts[dd->index_key_pos]);
170     if (dd->type)
171       DEBUG(PLUGIN_NAME ":     Type: %s", dd->type);
172     if (dd->type_instance)
173       DEBUG(PLUGIN_NAME ":     TypeInstance: %s", dd->type_instance);
174     for (size_t i = 0; i < dd->oids_len; i++) {
175       snmp_agent_oid_to_string(oid_str, sizeof(oid_str), &dd->oids[i]);
176       DEBUG(PLUGIN_NAME ":     OID[%" PRIsz "]: %s", i, oid_str);
177     }
178     DEBUG(PLUGIN_NAME ":   Scale: %g", dd->scale);
179     DEBUG(PLUGIN_NAME ":   Shift: %g", dd->shift);
180   }
181 }
182
183 /* Prints parsed configuration */
184 static void snmp_agent_dump_config(void) {
185   char oid_str[DATA_MAX_NAME_LEN];
186
187   /* Printing tables */
188   for (llentry_t *te = llist_head(g_agent->tables); te != NULL; te = te->next) {
189     table_definition_t *td = te->value;
190
191     DEBUG(PLUGIN_NAME ": Table:");
192     DEBUG(PLUGIN_NAME ":   Name: %s", td->name);
193     if (td->index_oid.oid_len != 0) {
194       snmp_agent_oid_to_string(oid_str, sizeof(oid_str), &td->index_oid);
195       DEBUG(PLUGIN_NAME ":   IndexOID: %s", oid_str);
196     }
197     if (td->size_oid.oid_len != 0) {
198       snmp_agent_oid_to_string(oid_str, sizeof(oid_str), &td->size_oid);
199       DEBUG(PLUGIN_NAME ":   SizeOID: %s", oid_str);
200     }
201
202     snmp_agent_dump_data(td->columns);
203   }
204
205   /* Printing scalars */
206   snmp_agent_dump_data(g_agent->scalars);
207 }
208 #endif /* COLLECT_DEBUG */
209
210 static int snmp_agent_validate_config(void) {
211
212 #if COLLECT_DEBUG
213   snmp_agent_dump_config();
214 #endif
215
216   for (llentry_t *te = llist_head(g_agent->tables); te != NULL; te = te->next) {
217     table_definition_t *td = te->value;
218
219     if (!td->indexes_len) {
220       ERROR(PLUGIN_NAME ": Index keys not defined for '%s'", td->name);
221       return -EINVAL;
222     }
223
224     for (llentry_t *de = llist_head(td->columns); de != NULL; de = de->next) {
225       data_definition_t *dd = de->value;
226
227       if (!dd->plugin) {
228         ERROR(PLUGIN_NAME ": Plugin not defined for '%s'.'%s'", td->name,
229               dd->name);
230         return -EINVAL;
231       }
232
233       if (dd->plugin_instance) {
234         ERROR(PLUGIN_NAME ": PluginInstance should not be defined for table "
235                           "data type '%s'.'%s'",
236               td->name, dd->name);
237         return -EINVAL;
238       }
239
240       if (dd->oids_len == 0) {
241         ERROR(PLUGIN_NAME ": No OIDs defined for '%s'.'%s'", td->name,
242               dd->name);
243         return -EINVAL;
244       }
245
246       if (dd->is_index_key) {
247         if (dd->type || dd->type_instance) {
248           ERROR(PLUGIN_NAME ": Type and TypeInstance are not valid for "
249                             "index data '%s'.'%s'",
250                 td->name, dd->name);
251           return -EINVAL;
252         }
253
254         if (dd->oids_len > 1) {
255           ERROR(
256               PLUGIN_NAME
257               ": Only one OID should be specified for instance data '%s'.'%s'",
258               td->name, dd->name);
259           return -EINVAL;
260         }
261       } else {
262
263         if (!dd->type) {
264           ERROR(PLUGIN_NAME ": Type not defined for data '%s'.'%s'", td->name,
265                 dd->name);
266           return -EINVAL;
267         }
268       }
269     }
270   }
271
272   for (llentry_t *e = llist_head(g_agent->scalars); e != NULL; e = e->next) {
273     data_definition_t *dd = e->value;
274
275     if (!dd->plugin) {
276       ERROR(PLUGIN_NAME ": Plugin not defined for '%s'", dd->name);
277       return -EINVAL;
278     }
279
280     if (dd->oids_len == 0) {
281       ERROR(PLUGIN_NAME ": No OIDs defined for '%s'", dd->name);
282       return -EINVAL;
283     }
284
285     if (dd->is_index_key) {
286       ERROR(PLUGIN_NAME ": Index field can't be specified for scalar data '%s'",
287             dd->name);
288       return -EINVAL;
289     }
290
291     if (!dd->type) {
292       ERROR(PLUGIN_NAME ": Type not defined for data '%s'", dd->name);
293       return -EINVAL;
294     }
295   }
296
297   return 0;
298 }
299
300 static int snmp_agent_fill_index_list(table_definition_t const *td,
301                                       value_list_t const *vl) {
302   int ret;
303   netsnmp_variable_list *key = td->index_list_cont;
304
305   for (int i = 0; i < td->indexes_len; i++) {
306     /* var should never be NULL */
307     assert(key != NULL);
308     /* Generating list filled with all data necessary to generate an OID */
309     switch (td->indexes[i]) {
310     case INDEX_HOST:
311       ret = snmp_set_var_value(key, vl->host, strlen(vl->host));
312       break;
313     case INDEX_PLUGIN:
314       ret = snmp_set_var_value(key, vl->plugin, strlen(vl->plugin));
315       break;
316     case INDEX_PLUGIN_INSTANCE:
317       ret = snmp_set_var_value(key, vl->plugin_instance,
318                                strlen(vl->plugin_instance));
319       break;
320     case INDEX_TYPE:
321       ret = snmp_set_var_value(key, vl->type, strlen(vl->type));
322       break;
323     case INDEX_TYPE_INSTANCE:
324       ret =
325           snmp_set_var_value(key, vl->type_instance, strlen(vl->type_instance));
326       break;
327     default:
328       ERROR(PLUGIN_NAME ": Unknown index key type provided");
329       return -EINVAL;
330     }
331     if (ret != 0)
332       return -EINVAL;
333     key = key->next_variable;
334   }
335   return 0;
336 }
337
338 static int snmp_agent_prep_index_list(table_definition_t const *td,
339                                       netsnmp_variable_list **index_list) {
340   /* Generating list having only the structure (with no values) letting us
341    * know how to parse an OID */
342   for (int i = 0; i < td->indexes_len; i++) {
343     switch (td->indexes[i]) {
344     case INDEX_HOST:
345     case INDEX_PLUGIN:
346     case INDEX_PLUGIN_INSTANCE:
347     case INDEX_TYPE:
348     case INDEX_TYPE_INSTANCE:
349       snmp_varlist_add_variable(index_list, NULL, 0, ASN_OCTET_STR, NULL, 0);
350       break;
351     default:
352       ERROR(PLUGIN_NAME ": Unknown index key type provided");
353       return -EINVAL;
354     }
355   }
356   return 0;
357 }
358
359 static int snmp_agent_generate_index(table_definition_t const *td,
360                                      value_list_t const *vl, oid_t *index_oid) {
361
362   /* According to given information by indexes list
363    * index OID is going to be built
364    */
365   int ret = snmp_agent_fill_index_list(td, vl);
366   if (ret != 0)
367     return -EINVAL;
368
369   /* Building only index part OID (without table prefix OID) */
370   ret = build_oid_noalloc(index_oid->oid, sizeof(index_oid->oid),
371                           &index_oid->oid_len, NULL, 0, td->index_list_cont);
372   if (ret != SNMPERR_SUCCESS) {
373     ERROR(PLUGIN_NAME ": Error building index OID");
374     return -EINVAL;
375   }
376
377   return 0;
378 }
379
380 /* It appends one OID to the end of another */
381 static int snmp_agent_append_oid(oid_t *out, const oid_t *in) {
382
383   if (out->oid_len + in->oid_len > MAX_OID_LEN) {
384     ERROR(PLUGIN_NAME ": Cannot create OID. Output length is too long!");
385     return -EINVAL;
386   }
387   memcpy(&out->oid[out->oid_len], in->oid, in->oid_len * sizeof(oid));
388   out->oid_len += in->oid_len;
389
390   return 0;
391 }
392
393 static int snmp_agent_register_oid_string(const oid_t *oid,
394                                           const oid_t *index_oid,
395                                           Netsnmp_Node_Handler *handler) {
396   oid_t new_oid;
397
398   memcpy(&new_oid, oid, sizeof(*oid));
399   /* Concatenating two string oids */
400   int ret = snmp_agent_append_oid(&new_oid, index_oid);
401   if (ret != 0)
402     return ret;
403
404   return snmp_agent_register_oid(&new_oid, handler);
405 }
406
407 static int snmp_agent_unregister_oid_string(oid_t *oid,
408                                             const oid_t *index_oid) {
409   oid_t new_oid;
410   char oid_str[DATA_MAX_NAME_LEN];
411
412   memcpy(&new_oid, oid, sizeof(*oid));
413   /* Concatenating two string oids */
414   int ret = snmp_agent_append_oid(&new_oid, index_oid);
415   if (ret != 0)
416     return ret;
417
418   snmp_agent_oid_to_string(oid_str, sizeof(oid_str), &new_oid);
419   DEBUG(PLUGIN_NAME ": Unregistered handler for OID (%s)", oid_str);
420
421   return unregister_mib(new_oid.oid, new_oid.oid_len);
422 }
423
424 static int snmp_agent_table_row_remove(table_definition_t *td,
425                                        oid_t *index_oid) {
426   int *index = NULL;
427   oid_t *ind_oid = NULL;
428
429   if (td->index_oid.oid_len) {
430     if ((c_avl_get(td->instance_index, index_oid, (void **)&index) != 0) ||
431         (c_avl_get(td->index_instance, index, NULL) != 0))
432       return 0;
433   } else {
434     if (c_avl_get(td->instance_index, index_oid, NULL) != 0)
435       return 0;
436   }
437
438   pthread_mutex_lock(&g_agent->agentx_lock);
439
440   if (td->index_oid.oid_len)
441     snmp_agent_unregister_oid_index(&td->index_oid, *index);
442
443   for (llentry_t *de = llist_head(td->columns); de != NULL; de = de->next) {
444     data_definition_t *dd = de->value;
445
446     for (size_t i = 0; i < dd->oids_len; i++)
447       if (td->index_oid.oid_len)
448         snmp_agent_unregister_oid_index(&dd->oids[i], *index);
449       else
450         snmp_agent_unregister_oid_string(&dd->oids[i], index_oid);
451   }
452
453   pthread_mutex_unlock(&g_agent->agentx_lock);
454
455   char index_str[DATA_MAX_NAME_LEN];
456
457   if (index == NULL)
458     snmp_agent_oid_to_string(index_str, sizeof(index_str), index_oid);
459   else
460     snprintf(index_str, sizeof(index_str), "%d", *index);
461
462   notification_t n = {
463       .severity = NOTIF_WARNING, .time = cdtime(), .plugin = PLUGIN_NAME};
464   sstrncpy(n.host, hostname_g, sizeof(n.host));
465   snprintf(n.message, sizeof(n.message),
466            "Removed data row from table %s with index %s", td->name, index_str);
467   DEBUG(PLUGIN_NAME ": %s", n.message);
468   plugin_dispatch_notification(&n);
469
470   if (td->index_oid.oid_len) {
471     c_avl_remove(td->index_instance, index, NULL, (void **)&ind_oid);
472     c_avl_remove(td->instance_index, index_oid, NULL, (void **)&index);
473     sfree(index);
474     sfree(ind_oid);
475   } else {
476     c_avl_remove(td->instance_index, index_oid, NULL, NULL);
477     sfree(index_oid);
478   }
479
480   return 0;
481 }
482
483 static int snmp_agent_clear_missing(const value_list_t *vl,
484                                     __attribute__((unused)) user_data_t *ud) {
485   if (vl == NULL)
486     return -EINVAL;
487
488   for (llentry_t *te = llist_head(g_agent->tables); te != NULL; te = te->next) {
489     table_definition_t *td = te->value;
490
491     for (llentry_t *de = llist_head(td->columns); de != NULL; de = de->next) {
492       data_definition_t *dd = de->value;
493       oid_t *index_oid = (oid_t *)calloc(1, sizeof(*index_oid));
494       int ret;
495
496       if (!dd->is_index_key) {
497         if (CHECK_DD_TYPE(dd, vl->plugin, vl->plugin_instance, vl->type,
498                           vl->type_instance)) {
499           ret = snmp_agent_generate_index(td, vl, index_oid);
500           if (ret == 0)
501             ret = snmp_agent_table_row_remove(td, index_oid);
502
503           return ret;
504         }
505       }
506     }
507   }
508
509   return 0;
510 }
511
512 static void snmp_agent_free_data(data_definition_t **dd) {
513
514   if (dd == NULL || *dd == NULL)
515     return;
516
517   /* unregister scalar type OID */
518   if ((*dd)->table == NULL) {
519     for (size_t i = 0; i < (*dd)->oids_len; i++)
520       unregister_mib((*dd)->oids[i].oid, (*dd)->oids[i].oid_len);
521   }
522
523   sfree((*dd)->name);
524   sfree((*dd)->plugin);
525   sfree((*dd)->plugin_instance);
526   sfree((*dd)->type);
527   sfree((*dd)->type_instance);
528   sfree((*dd)->oids);
529
530   sfree(*dd);
531
532   return;
533 }
534
535 static void snmp_agent_free_table_columns(table_definition_t *td) {
536   if (td->columns == NULL)
537     return;
538
539   for (llentry_t *de = llist_head(td->columns); de != NULL; de = de->next) {
540     data_definition_t *dd = de->value;
541
542     if (td->index_oid.oid_len) {
543       int *index;
544       oid_t *index_oid;
545
546       c_avl_iterator_t *iter = c_avl_get_iterator(td->index_instance);
547       while (c_avl_iterator_next(iter, (void *)&index, (void *)&index_oid) ==
548              0) {
549         for (size_t i = 0; i < dd->oids_len; i++)
550           snmp_agent_unregister_oid_index(&dd->oids[i], *index);
551       }
552       c_avl_iterator_destroy(iter);
553     } else {
554       oid_t *index_oid;
555
556       c_avl_iterator_t *iter = c_avl_get_iterator(dd->table->instance_index);
557       while (c_avl_iterator_next(iter, (void *)&index_oid, NULL) == 0) {
558         for (size_t i = 0; i < dd->oids_len; i++)
559           snmp_agent_unregister_oid_string(&dd->oids[i], index_oid);
560       }
561       c_avl_iterator_destroy(iter);
562     }
563
564     snmp_agent_free_data(&dd);
565   }
566
567   llist_destroy(td->columns);
568   td->columns = NULL;
569 } /* void snmp_agent_free_table_columns */
570
571 static void snmp_agent_free_table(table_definition_t **td) {
572
573   if (td == NULL || *td == NULL)
574     return;
575
576   if ((*td)->size_oid.oid_len)
577     unregister_mib((*td)->size_oid.oid, (*td)->size_oid.oid_len);
578
579   /* Unregister Index OIDs */
580   if ((*td)->index_oid.oid_len) {
581     int *index;
582     oid_t *index_oid;
583
584     c_avl_iterator_t *iter = c_avl_get_iterator((*td)->index_instance);
585     while (c_avl_iterator_next(iter, (void **)&index, (void **)&index_oid) == 0)
586       snmp_agent_unregister_oid_index(&(*td)->index_oid, *index);
587
588     c_avl_iterator_destroy(iter);
589   }
590
591   /* Unregister all table columns and their registered OIDs */
592   snmp_agent_free_table_columns(*td);
593
594   void *key = NULL;
595   void *value = NULL;
596
597   /* index_instance and instance_index contain the same pointers */
598   c_avl_destroy((*td)->index_instance);
599   (*td)->index_instance = NULL;
600
601   if ((*td)->instance_index != NULL) {
602     while (c_avl_pick((*td)->instance_index, &key, &value) == 0) {
603       if (key != value)
604         sfree(key);
605       sfree(value);
606     }
607     c_avl_destroy((*td)->instance_index);
608     (*td)->instance_index = NULL;
609   }
610
611   snmp_free_varbind((*td)->index_list_cont);
612   sfree((*td)->name);
613   sfree(*td);
614
615   return;
616 }
617
618 static int snmp_agent_parse_oid_indexes(const table_definition_t *td,
619                                         oid_t *index_oid) {
620   int ret = parse_oid_indexes(index_oid->oid, index_oid->oid_len,
621                               td->index_list_cont);
622   if (ret != SNMPERR_SUCCESS)
623     ERROR(PLUGIN_NAME ": index OID parse error!");
624   return ret;
625 }
626
627 static int snmp_agent_format_name(char *name, int name_len,
628                                   data_definition_t *dd, oid_t *index_oid) {
629
630   if (index_oid == NULL) {
631     /* It's a scalar */
632     format_name(name, name_len, hostname_g, dd->plugin, dd->plugin_instance,
633                 dd->type, dd->type_instance);
634   } else {
635     /* Need to parse string index OID */
636     const table_definition_t *td = dd->table;
637     int ret = snmp_agent_parse_oid_indexes(td, index_oid);
638     if (ret != 0)
639       return ret;
640
641     int i = 0;
642     netsnmp_variable_list *key = td->index_list_cont;
643     char *host = hostname_g;
644     char *plugin = dd->plugin;
645     char *plugin_instance = dd->plugin_instance;
646     char *type = dd->type;
647     char *type_instance = dd->type_instance;
648     while (key != NULL) {
649       switch (td->indexes[i]) {
650       case INDEX_HOST:
651         host = (char *)key->val.string;
652         break;
653       case INDEX_PLUGIN:
654         plugin = (char *)key->val.string;
655         break;
656       case INDEX_PLUGIN_INSTANCE:
657         plugin_instance = (char *)key->val.string;
658         break;
659       case INDEX_TYPE:
660         type = (char *)key->val.string;
661         break;
662       case INDEX_TYPE_INSTANCE:
663         type_instance = (char *)key->val.string;
664         break;
665       default:
666         ERROR(PLUGIN_NAME ": Unkown index type!");
667         return -EINVAL;
668       }
669       key = key->next_variable;
670       i++;
671     }
672
673     format_name(name, name_len, host, plugin, plugin_instance, type,
674                 type_instance);
675   }
676
677   return 0;
678 }
679
680 static int snmp_agent_form_reply(struct netsnmp_request_info_s *requests,
681                                  data_definition_t *dd, oid_t *index_oid,
682                                  int oid_index) {
683   int ret;
684
685   if (dd->is_index_key) {
686     const table_definition_t *td = dd->table;
687     ret = snmp_agent_parse_oid_indexes(td, index_oid);
688
689     if (ret != 0)
690       return ret;
691
692     netsnmp_variable_list *key = td->index_list_cont;
693     /* Searching index key */
694     for (int pos = 0; pos < dd->index_key_pos; pos++)
695       key = key->next_variable;
696
697     requests->requestvb->type = ASN_OCTET_STR;
698     snmp_set_var_typed_value(requests->requestvb, requests->requestvb->type,
699                              (const u_char *)key->val.string,
700                              strlen((const char *)key->val.string));
701
702     pthread_mutex_unlock(&g_agent->lock);
703
704     return SNMP_ERR_NOERROR;
705   }
706
707   char name[DATA_MAX_NAME_LEN];
708
709   ret = snmp_agent_format_name(name, sizeof(name), dd, index_oid);
710   if (ret != 0)
711     return ret;
712
713   DEBUG(PLUGIN_NAME ": Identifier '%s'", name);
714
715   value_t *values;
716   size_t values_num;
717   const data_set_t *ds = plugin_get_ds(dd->type);
718   if (ds == NULL) {
719     ERROR(PLUGIN_NAME ": Data set not found for '%s' type", dd->type);
720     return SNMP_NOSUCHINSTANCE;
721   }
722
723   ret = uc_get_value_by_name(name, &values, &values_num);
724
725   if (ret != 0) {
726     ERROR(PLUGIN_NAME ": Failed to get value for '%s'", name);
727     return SNMP_NOSUCHINSTANCE;
728   }
729
730   assert(ds->ds_num == values_num);
731   assert(oid_index < (int)values_num);
732
733   char data[DATA_MAX_NAME_LEN];
734   size_t data_len = sizeof(data);
735   ret = snmp_agent_set_vardata(
736       data, &data_len, dd->oids[oid_index].type, dd->scale, dd->shift,
737       &values[oid_index], sizeof(values[oid_index]), ds->ds[oid_index].type);
738
739   sfree(values);
740
741   if (ret != 0) {
742     ERROR(PLUGIN_NAME ": Failed to convert '%s' value to snmp data", name);
743     return SNMP_NOSUCHINSTANCE;
744   }
745
746   requests->requestvb->type = dd->oids[oid_index].type;
747   snmp_set_var_typed_value(requests->requestvb, requests->requestvb->type,
748                            (const u_char *)data, data_len);
749
750   return SNMP_ERR_NOERROR;
751 }
752
753 static int
754 snmp_agent_table_oid_handler(struct netsnmp_mib_handler_s *handler,
755                              struct netsnmp_handler_registration_s *reginfo,
756                              struct netsnmp_agent_request_info_s *reqinfo,
757                              struct netsnmp_request_info_s *requests) {
758
759   if (reqinfo->mode != MODE_GET) {
760     DEBUG(PLUGIN_NAME ": Not supported request mode (%d)", reqinfo->mode);
761     return SNMP_ERR_NOERROR;
762   }
763
764   pthread_mutex_lock(&g_agent->lock);
765
766   oid_t oid; /* Requested OID */
767   memcpy(oid.oid, requests->requestvb->name,
768          sizeof(oid.oid[0]) * requests->requestvb->name_length);
769   oid.oid_len = requests->requestvb->name_length;
770
771 #if COLLECT_DEBUG
772   char oid_str[DATA_MAX_NAME_LEN];
773   snmp_agent_oid_to_string(oid_str, sizeof(oid_str), &oid);
774   DEBUG(PLUGIN_NAME ": Get request received for table OID '%s'", oid_str);
775 #endif
776   oid_t index_oid; /* Index part of requested OID */
777
778   for (llentry_t *te = llist_head(g_agent->tables); te != NULL; te = te->next) {
779     table_definition_t *td = te->value;
780
781     for (llentry_t *de = llist_head(td->columns); de != NULL; de = de->next) {
782       data_definition_t *dd = de->value;
783
784       for (size_t i = 0; i < dd->oids_len; i++) {
785         int ret = snmp_oid_ncompare(oid.oid, oid.oid_len, dd->oids[i].oid,
786                                     dd->oids[i].oid_len,
787                                     SNMP_MIN(oid.oid_len, dd->oids[i].oid_len));
788         if (ret != 0)
789           continue;
790
791         /* Calculating OID length for index part */
792         index_oid.oid_len = oid.oid_len - dd->oids[i].oid_len;
793         /* Fetching index part of the OID */
794         memcpy(index_oid.oid, &oid.oid[dd->oids[i].oid_len],
795                index_oid.oid_len * sizeof(*oid.oid));
796
797         char index_str[DATA_MAX_NAME_LEN];
798         snmp_agent_oid_to_string(index_str, sizeof(index_str), &index_oid);
799
800         if (!td->index_oid.oid_len) {
801           ret = c_avl_get(td->instance_index, &index_oid, NULL);
802         } else {
803           oid_t *temp_oid;
804
805           assert(index_oid.oid_len == 1);
806           ret = c_avl_get(td->index_instance, (int *)&index_oid.oid[0],
807                           (void **)&temp_oid);
808           memcpy(&index_oid, temp_oid, sizeof(index_oid));
809         }
810
811         if (ret != 0) {
812           INFO(PLUGIN_NAME ": Non-existing index (%s) requested", index_str);
813           pthread_mutex_unlock(&g_agent->lock);
814           return SNMP_NOSUCHINSTANCE;
815         }
816
817         ret = snmp_agent_form_reply(requests, dd, &index_oid, i);
818         pthread_mutex_unlock(&g_agent->lock);
819
820         return ret;
821       }
822     }
823   }
824
825   pthread_mutex_unlock(&g_agent->lock);
826
827   return SNMP_NOSUCHINSTANCE;
828 }
829
830 static int snmp_agent_table_index_oid_handler(
831     struct netsnmp_mib_handler_s *handler,
832     struct netsnmp_handler_registration_s *reginfo,
833     struct netsnmp_agent_request_info_s *reqinfo,
834     struct netsnmp_request_info_s *requests) {
835
836   if (reqinfo->mode != MODE_GET) {
837     DEBUG(PLUGIN_NAME ": Not supported request mode (%d)", reqinfo->mode);
838     return SNMP_ERR_NOERROR;
839   }
840
841   pthread_mutex_lock(&g_agent->lock);
842
843   oid_t oid;
844   memcpy(oid.oid, requests->requestvb->name,
845          sizeof(oid.oid[0]) * requests->requestvb->name_length);
846   oid.oid_len = requests->requestvb->name_length;
847
848   for (llentry_t *te = llist_head(g_agent->tables); te != NULL; te = te->next) {
849     table_definition_t *td = te->value;
850
851     if (td->index_oid.oid_len &&
852         (snmp_oid_ncompare(
853              oid.oid, oid.oid_len, td->index_oid.oid, td->index_oid.oid_len,
854              SNMP_MIN(oid.oid_len, td->index_oid.oid_len)) == 0)) {
855
856       DEBUG(PLUGIN_NAME ": Handle '%s' table index OID", td->name);
857
858       int index = oid.oid[oid.oid_len - 1];
859
860       int ret = c_avl_get(td->index_instance, &index, NULL);
861       if (ret != 0) {
862         /* nonexisting index requested */
863         pthread_mutex_unlock(&g_agent->lock);
864         return SNMP_NOSUCHINSTANCE;
865       }
866
867       requests->requestvb->type = ASN_INTEGER;
868       snmp_set_var_typed_value(requests->requestvb, requests->requestvb->type,
869                                (const u_char *)&index, sizeof(index));
870
871       pthread_mutex_unlock(&g_agent->lock);
872
873       return SNMP_ERR_NOERROR;
874     }
875   }
876
877   pthread_mutex_unlock(&g_agent->lock);
878
879   return SNMP_NOSUCHINSTANCE;
880 }
881
882 static int snmp_agent_table_size_oid_handler(
883     struct netsnmp_mib_handler_s *handler,
884     struct netsnmp_handler_registration_s *reginfo,
885     struct netsnmp_agent_request_info_s *reqinfo,
886     struct netsnmp_request_info_s *requests) {
887
888   if (reqinfo->mode != MODE_GET) {
889     DEBUG(PLUGIN_NAME ": Not supported request mode (%d)", reqinfo->mode);
890     return SNMP_ERR_NOERROR;
891   }
892
893   pthread_mutex_lock(&g_agent->lock);
894
895   oid_t oid;
896   memcpy(oid.oid, requests->requestvb->name,
897          sizeof(oid.oid[0]) * requests->requestvb->name_length);
898   oid.oid_len = requests->requestvb->name_length;
899
900   DEBUG(PLUGIN_NAME ": Get request received for table size OID");
901
902   for (llentry_t *te = llist_head(g_agent->tables); te != NULL; te = te->next) {
903     table_definition_t *td = te->value;
904
905     if (td->size_oid.oid_len &&
906         (snmp_oid_ncompare(oid.oid, oid.oid_len, td->size_oid.oid,
907                            td->size_oid.oid_len,
908                            SNMP_MIN(oid.oid_len, td->size_oid.oid_len)) == 0)) {
909       DEBUG(PLUGIN_NAME ": Handle '%s' table size OID", td->name);
910
911       long size;
912       if (td->index_oid.oid_len)
913         size = c_avl_size(td->index_instance);
914       else
915         size = c_avl_size(td->instance_index);
916
917       requests->requestvb->type = ASN_INTEGER;
918       snmp_set_var_typed_value(requests->requestvb, requests->requestvb->type,
919                                (const u_char *)&size, sizeof(size));
920
921       pthread_mutex_unlock(&g_agent->lock);
922
923       return SNMP_ERR_NOERROR;
924     }
925   }
926
927   pthread_mutex_unlock(&g_agent->lock);
928
929   return SNMP_NOSUCHINSTANCE;
930 }
931
932 static int
933 snmp_agent_scalar_oid_handler(struct netsnmp_mib_handler_s *handler,
934                               struct netsnmp_handler_registration_s *reginfo,
935                               struct netsnmp_agent_request_info_s *reqinfo,
936                               struct netsnmp_request_info_s *requests) {
937
938   if (reqinfo->mode != MODE_GET) {
939     DEBUG(PLUGIN_NAME ": Not supported request mode (%d)", reqinfo->mode);
940     return SNMP_ERR_NOERROR;
941   }
942
943   pthread_mutex_lock(&g_agent->lock);
944
945   oid_t oid;
946   memcpy(oid.oid, requests->requestvb->name,
947          sizeof(oid.oid[0]) * requests->requestvb->name_length);
948   oid.oid_len = requests->requestvb->name_length;
949
950 #if COLLECT_DEBUG
951   char oid_str[DATA_MAX_NAME_LEN];
952   snmp_agent_oid_to_string(oid_str, sizeof(oid_str), &oid);
953   DEBUG(PLUGIN_NAME ": Get request received for scalar OID '%s'", oid_str);
954 #endif
955
956   for (llentry_t *de = llist_head(g_agent->scalars); de != NULL;
957        de = de->next) {
958     data_definition_t *dd = de->value;
959
960     for (size_t i = 0; i < dd->oids_len; i++) {
961
962       int ret = snmp_oid_compare(oid.oid, oid.oid_len, dd->oids[i].oid,
963                                  dd->oids[i].oid_len);
964       if (ret != 0)
965         continue;
966
967       ret = snmp_agent_form_reply(requests, dd, NULL, i);
968
969       pthread_mutex_unlock(&g_agent->lock);
970
971       return ret;
972     }
973   }
974
975   pthread_mutex_unlock(&g_agent->lock);
976
977   return SNMP_NOSUCHINSTANCE;
978 }
979
980 static int snmp_agent_register_table_oids(void) {
981
982   for (llentry_t *te = llist_head(g_agent->tables); te != NULL; te = te->next) {
983     table_definition_t *td = te->value;
984
985     if (td->size_oid.oid_len != 0) {
986       td->size_oid.type =
987           snmp_agent_get_asn_type(td->size_oid.oid, td->size_oid.oid_len);
988       td->size_oid.oid_len++;
989       int ret = snmp_agent_register_oid(&td->size_oid,
990                                         snmp_agent_table_size_oid_handler);
991       if (ret != 0)
992         return ret;
993     }
994
995     for (llentry_t *de = llist_head(td->columns); de != NULL; de = de->next) {
996       data_definition_t *dd = de->value;
997
998       for (size_t i = 0; i < dd->oids_len; i++) {
999         dd->oids[i].type =
1000             snmp_agent_get_asn_type(dd->oids[i].oid, dd->oids[i].oid_len);
1001       }
1002     }
1003   }
1004
1005   return 0;
1006 }
1007
1008 static int snmp_agent_register_scalar_oids(void) {
1009
1010   for (llentry_t *e = llist_head(g_agent->scalars); e != NULL; e = e->next) {
1011     data_definition_t *dd = e->value;
1012
1013     for (size_t i = 0; i < dd->oids_len; i++) {
1014
1015       dd->oids[i].type =
1016           snmp_agent_get_asn_type(dd->oids[i].oid, dd->oids[i].oid_len);
1017
1018       int ret =
1019           snmp_agent_register_oid(&dd->oids[i], snmp_agent_scalar_oid_handler);
1020       if (ret != 0)
1021         return ret;
1022     }
1023   }
1024
1025   return 0;
1026 }
1027
1028 static int snmp_agent_config_data_oids(data_definition_t *dd,
1029                                        oconfig_item_t *ci) {
1030   if (ci->values_num < 1) {
1031     WARNING(PLUGIN_NAME ": `OIDs' needs at least one argument");
1032     return -EINVAL;
1033   }
1034
1035   for (int i = 0; i < ci->values_num; i++)
1036     if (ci->values[i].type != OCONFIG_TYPE_STRING) {
1037       WARNING(PLUGIN_NAME ": `OIDs' needs only string argument");
1038       return -EINVAL;
1039     }
1040
1041   if (dd->oids != NULL)
1042     sfree(dd->oids);
1043   dd->oids_len = 0;
1044   dd->oids = calloc(ci->values_num, sizeof(*dd->oids));
1045   if (dd->oids == NULL)
1046     return -ENOMEM;
1047   dd->oids_len = (size_t)ci->values_num;
1048
1049   for (int i = 0; i < ci->values_num; i++) {
1050     dd->oids[i].oid_len = MAX_OID_LEN;
1051
1052     if (NULL == snmp_parse_oid(ci->values[i].value.string, dd->oids[i].oid,
1053                                &dd->oids[i].oid_len)) {
1054       ERROR(PLUGIN_NAME ": snmp_parse_oid (%s) failed",
1055             ci->values[i].value.string);
1056       sfree(dd->oids);
1057       dd->oids_len = 0;
1058       return -1;
1059     }
1060   }
1061
1062   return 0;
1063 }
1064
1065 static int snmp_agent_config_table_size_oid(table_definition_t *td,
1066                                             oconfig_item_t *ci) {
1067   if (ci->values_num < 1) {
1068     WARNING(PLUGIN_NAME ": `TableSizeOID' is empty");
1069     return -EINVAL;
1070   }
1071
1072   if (ci->values[0].type != OCONFIG_TYPE_STRING) {
1073     WARNING(PLUGIN_NAME ": `TableSizeOID' needs only string argument");
1074     return -EINVAL;
1075   }
1076
1077   td->size_oid.oid_len = MAX_OID_LEN;
1078
1079   if (NULL == snmp_parse_oid(ci->values[0].value.string, td->size_oid.oid,
1080                              &td->size_oid.oid_len)) {
1081     ERROR(PLUGIN_NAME ": Failed to parse table size OID (%s)",
1082           ci->values[0].value.string);
1083     td->size_oid.oid_len = 0;
1084     return -EINVAL;
1085   }
1086
1087   return 0;
1088 }
1089
1090 static int snmp_agent_config_table_index_oid(table_definition_t *td,
1091                                              oconfig_item_t *ci) {
1092
1093   if (ci->values_num < 1) {
1094     WARNING(PLUGIN_NAME ": `IndexOID' is empty");
1095     return -EINVAL;
1096   }
1097
1098   if (ci->values[0].type != OCONFIG_TYPE_STRING) {
1099     WARNING(PLUGIN_NAME ": `IndexOID' needs only string argument");
1100     return -EINVAL;
1101   }
1102
1103   td->index_oid.oid_len = MAX_OID_LEN;
1104
1105   if (NULL == snmp_parse_oid(ci->values[0].value.string, td->index_oid.oid,
1106                              &td->index_oid.oid_len)) {
1107     ERROR(PLUGIN_NAME ": Failed to parse table index OID (%s)",
1108           ci->values[0].value.string);
1109     td->index_oid.oid_len = 0;
1110     return -EINVAL;
1111   }
1112
1113   return 0;
1114 }
1115
1116 /* Parsing table column representing index key */
1117 static int snmp_agent_config_index(table_definition_t *td,
1118                                    data_definition_t *dd, oconfig_item_t *ci) {
1119   char *val = NULL;
1120
1121   int ret = cf_util_get_string(ci, &val);
1122   if (ret != 0)
1123     return -1;
1124
1125   _Bool match = 0;
1126
1127   for (int i = 0; i < MAX_INDEX_TYPES; i++) {
1128     if (strcasecmp(index_opts[i], (const char *)val) == 0) {
1129       td->indexes[td->indexes_len] = i;
1130       match = 1;
1131       break;
1132     }
1133   }
1134
1135   if (!match) {
1136     ERROR(PLUGIN_NAME ": Failed to parse index key source: '%s'", val);
1137     sfree(val);
1138     return -EINVAL;
1139   }
1140
1141   sfree(val);
1142   dd->index_key_pos = td->indexes_len++;
1143   dd->is_index_key = 1;
1144
1145   return 0;
1146 }
1147
1148 /* This function parses configuration of both scalar and table column
1149  * because they have nearly the same structure */
1150 static int snmp_agent_config_table_column(table_definition_t *td,
1151                                           oconfig_item_t *ci) {
1152   data_definition_t *dd;
1153   int ret = 0;
1154
1155   assert(ci != NULL);
1156
1157   dd = calloc(1, sizeof(*dd));
1158   if (dd == NULL) {
1159     ERROR(PLUGIN_NAME ": Failed to allocate memory for table data definition");
1160     return -ENOMEM;
1161   }
1162
1163   ret = cf_util_get_string(ci, &dd->name);
1164   if (ret != 0) {
1165     sfree(dd);
1166     return -1;
1167   }
1168
1169   dd->scale = 1.0;
1170   dd->shift = 0.0;
1171   /* NULL if it's a scalar */
1172   dd->table = td;
1173   dd->is_index_key = 0;
1174
1175   for (int i = 0; i < ci->children_num; i++) {
1176     oconfig_item_t *option = ci->children + i;
1177
1178     /* Instance option is reserved for table entry only */
1179     if (strcasecmp("Index", option->key) == 0 && td != NULL)
1180       ret = snmp_agent_config_index(td, dd, option);
1181     else if (strcasecmp("Plugin", option->key) == 0)
1182       ret = cf_util_get_string(option, &dd->plugin);
1183     else if (strcasecmp("PluginInstance", option->key) == 0)
1184       ret = cf_util_get_string(option, &dd->plugin_instance);
1185     else if (strcasecmp("Type", option->key) == 0)
1186       ret = cf_util_get_string(option, &dd->type);
1187     else if (strcasecmp("TypeInstance", option->key) == 0)
1188       ret = cf_util_get_string(option, &dd->type_instance);
1189     else if (strcasecmp("Shift", option->key) == 0)
1190       ret = cf_util_get_double(option, &dd->shift);
1191     else if (strcasecmp("Scale", option->key) == 0)
1192       ret = cf_util_get_double(option, &dd->scale);
1193     else if (strcasecmp("OIDs", option->key) == 0)
1194       ret = snmp_agent_config_data_oids(dd, option);
1195     else {
1196       WARNING(PLUGIN_NAME ": Option `%s' not allowed here", option->key);
1197       ret = -1;
1198     }
1199
1200     if (ret != 0) {
1201       snmp_agent_free_data(&dd);
1202       return -1;
1203     }
1204   }
1205
1206   llentry_t *entry = llentry_create(dd->name, dd);
1207   if (entry == NULL) {
1208     snmp_agent_free_data(&dd);
1209     return -ENOMEM;
1210   }
1211
1212   /* Append to column list in parent table */
1213   if (td != NULL)
1214     llist_append(td->columns, entry);
1215
1216   return 0;
1217 }
1218
1219 /* Parses scalar configuration entry */
1220 static int snmp_agent_config_scalar(oconfig_item_t *ci) {
1221   return snmp_agent_config_table_column(NULL, ci);
1222 }
1223
1224 static int num_compare(const int *a, const int *b) {
1225   assert((a != NULL) && (b != NULL));
1226   if (*a < *b)
1227     return -1;
1228   else if (*a > *b)
1229     return 1;
1230   else
1231     return 0;
1232 }
1233
1234 static int oid_compare(const oid_t *a, const oid_t *b) {
1235   return snmp_oid_compare(a->oid, a->oid_len, b->oid, b->oid_len);
1236 }
1237
1238 static int snmp_agent_config_table(oconfig_item_t *ci) {
1239   table_definition_t *td;
1240   int ret = 0;
1241
1242   assert(ci != NULL);
1243
1244   td = calloc(1, sizeof(*td));
1245   if (td == NULL) {
1246     ERROR(PLUGIN_NAME ": Failed to allocate memory for table definition");
1247     return -ENOMEM;
1248   }
1249
1250   ret = cf_util_get_string(ci, &td->name);
1251   if (ret != 0) {
1252     sfree(td);
1253     return -1;
1254   }
1255
1256   td->columns = llist_create();
1257   if (td->columns == NULL) {
1258     ERROR(PLUGIN_NAME ": Failed to allocate memory for columns list");
1259     snmp_agent_free_table(&td);
1260     return -ENOMEM;
1261   }
1262
1263   for (int i = 0; i < ci->children_num; i++) {
1264     oconfig_item_t *option = ci->children + i;
1265
1266     if (strcasecmp("IndexOID", option->key) == 0)
1267       ret = snmp_agent_config_table_index_oid(td, option);
1268     else if (strcasecmp("SizeOID", option->key) == 0)
1269       ret = snmp_agent_config_table_size_oid(td, option);
1270     else if (strcasecmp("Data", option->key) == 0)
1271       ret = snmp_agent_config_table_column(td, option);
1272     else {
1273       WARNING(PLUGIN_NAME ": Option `%s' not allowed here", option->key);
1274       ret = -1;
1275     }
1276
1277     if (ret != 0) {
1278       snmp_agent_free_table(&td);
1279       return -ENOMEM;
1280     }
1281   }
1282
1283   /* Preparing index list container */
1284   ret = snmp_agent_prep_index_list(td, &td->index_list_cont);
1285   if (ret != 0)
1286     return -EINVAL;
1287
1288   td->instance_index =
1289       c_avl_create((int (*)(const void *, const void *))oid_compare);
1290   if (td->instance_index == NULL) {
1291     snmp_agent_free_table(&td);
1292     return -ENOMEM;
1293   }
1294
1295   td->index_instance =
1296       c_avl_create((int (*)(const void *, const void *))num_compare);
1297   if (td->index_instance == NULL) {
1298     snmp_agent_free_table(&td);
1299     return -ENOMEM;
1300   }
1301
1302   llentry_t *entry = llentry_create(td->name, td);
1303   if (entry == NULL) {
1304     snmp_agent_free_table(&td);
1305     return -ENOMEM;
1306   }
1307   llist_append(g_agent->tables, entry);
1308
1309   return 0;
1310 }
1311
1312 static int snmp_agent_get_value_from_ds_type(const value_t *val, int type,
1313                                              double scale, double shift,
1314                                              long *value) {
1315   switch (type) {
1316   case DS_TYPE_COUNTER:
1317     *value = (long)((val->counter * scale) + shift);
1318     break;
1319   case DS_TYPE_ABSOLUTE:
1320     *value = (long)((val->absolute * scale) + shift);
1321     break;
1322   case DS_TYPE_DERIVE:
1323     *value = (long)((val->derive * scale) + shift);
1324     break;
1325   case DS_TYPE_GAUGE:
1326     *value = (long)((val->gauge * scale) + shift);
1327     break;
1328   case TYPE_STRING:
1329     break;
1330   default:
1331     ERROR(PLUGIN_NAME ": Unknown data source type: %i", type);
1332     return -EINVAL;
1333   }
1334
1335   return 0;
1336 }
1337
1338 static int snmp_agent_set_vardata(void *data, size_t *data_len, u_char asn_type,
1339                                   double scale, double shift, const void *value,
1340                                   size_t len, int type) {
1341
1342   int ret;
1343   netsnmp_vardata var;
1344   const value_t *val;
1345   long new_value = 0;
1346
1347   val = value;
1348   var.string = (u_char *)data;
1349
1350   ret = snmp_agent_get_value_from_ds_type(val, type, scale, shift, &new_value);
1351   if (ret != 0)
1352     return ret;
1353
1354   switch (asn_type) {
1355   case ASN_INTEGER:
1356   case ASN_UINTEGER:
1357   case ASN_COUNTER:
1358   case ASN_TIMETICKS:
1359   case ASN_GAUGE:
1360     if (*data_len < sizeof(*var.integer))
1361       return -EINVAL;
1362     *var.integer = new_value;
1363     *data_len = sizeof(*var.integer);
1364     break;
1365   case ASN_COUNTER64:
1366     if (*data_len < sizeof(*var.counter64))
1367       return -EINVAL;
1368     var.counter64->high = (u_long)((int64_t)new_value >> 32);
1369     var.counter64->low = (u_long)((int64_t)new_value & 0xFFFFFFFF);
1370     *data_len = sizeof(*var.counter64);
1371     break;
1372   case ASN_OCTET_STR:
1373     if (type == DS_TYPE_GAUGE) {
1374       char buf[DATA_MAX_NAME_LEN];
1375       snprintf(buf, sizeof(buf), "%.2f", val->gauge);
1376       if (*data_len < strlen(buf))
1377         return -EINVAL;
1378       *data_len = strlen(buf);
1379       memcpy(var.string, buf, *data_len);
1380     } else {
1381       ERROR(PLUGIN_NAME ": Failed to convert %d ds type to %d asn type", type,
1382             asn_type);
1383       return -EINVAL;
1384     }
1385     break;
1386   default:
1387     ERROR(PLUGIN_NAME ": Failed to convert %d ds type to %d asn type", type,
1388           asn_type);
1389     return -EINVAL;
1390   }
1391
1392   return 0;
1393 }
1394
1395 static int snmp_agent_register_oid_index(oid_t *oid, int index,
1396                                          Netsnmp_Node_Handler *handler) {
1397   oid_t new_oid;
1398   memcpy(&new_oid, oid, sizeof(*oid));
1399   new_oid.oid[new_oid.oid_len++] = index;
1400   return snmp_agent_register_oid(&new_oid, handler);
1401 }
1402
1403 static int snmp_agent_unregister_oid_index(oid_t *oid, int index) {
1404   oid_t new_oid;
1405   memcpy(&new_oid, oid, sizeof(*oid));
1406   new_oid.oid[new_oid.oid_len++] = index;
1407   return unregister_mib(new_oid.oid, new_oid.oid_len);
1408 }
1409
1410 static int snmp_agent_update_index(table_definition_t *td, oid_t *index_oid) {
1411
1412   if (c_avl_get(td->instance_index, index_oid, NULL) == 0)
1413     return 0;
1414
1415   int ret;
1416   int *index = NULL;
1417
1418   /* need to generate index for the table */
1419   if (td->index_oid.oid_len) {
1420     index = calloc(1, sizeof(*index));
1421     if (index == NULL) {
1422       sfree(index_oid);
1423       return -ENOMEM;
1424     }
1425
1426     *index = c_avl_size(td->instance_index) + 1;
1427
1428     ret = c_avl_insert(td->instance_index, index_oid, index);
1429     if (ret != 0) {
1430       sfree(index_oid);
1431       sfree(index);
1432       return ret;
1433     }
1434
1435     ret = c_avl_insert(td->index_instance, index, index_oid);
1436     if (ret < 0) {
1437       DEBUG(PLUGIN_NAME ": Failed to update index_instance for '%s' table",
1438             td->name);
1439       c_avl_remove(td->instance_index, index_oid, NULL, (void **)&index);
1440       sfree(index_oid);
1441       sfree(index);
1442       return ret;
1443     }
1444
1445     ret = snmp_agent_register_oid_index(&td->index_oid, *index,
1446                                         snmp_agent_table_index_oid_handler);
1447     if (ret != 0)
1448       return ret;
1449   } else {
1450     /* instance as a key is required for any table */
1451     ret = c_avl_insert(td->instance_index, index_oid, NULL);
1452     if (ret != 0) {
1453       sfree(index_oid);
1454       return ret;
1455     }
1456   }
1457
1458   /* register new oids for all columns */
1459   for (llentry_t *de = llist_head(td->columns); de != NULL; de = de->next) {
1460     data_definition_t *dd = de->value;
1461
1462     for (size_t i = 0; i < dd->oids_len; i++) {
1463       if (td->index_oid.oid_len)
1464         ret = snmp_agent_register_oid_index(&dd->oids[i], *index,
1465                                             snmp_agent_table_oid_handler);
1466       else
1467         ret = snmp_agent_register_oid_string(&dd->oids[i], index_oid,
1468                                              snmp_agent_table_oid_handler);
1469
1470       if (ret != 0)
1471         return ret;
1472     }
1473   }
1474
1475   char index_str[DATA_MAX_NAME_LEN];
1476
1477   if (index == NULL)
1478     snmp_agent_oid_to_string(index_str, sizeof(index_str), index_oid);
1479   else
1480     snprintf(index_str, sizeof(index_str), "%d", *index);
1481
1482   notification_t n = {
1483       .severity = NOTIF_OKAY, .time = cdtime(), .plugin = PLUGIN_NAME};
1484   sstrncpy(n.host, hostname_g, sizeof(n.host));
1485   snprintf(n.message, sizeof(n.message),
1486            "Data row added to table %s with index %s", td->name, index_str);
1487   DEBUG(PLUGIN_NAME ": %s", n.message);
1488
1489   plugin_dispatch_notification(&n);
1490
1491   return 0;
1492 }
1493
1494 static int snmp_agent_write(value_list_t const *vl) {
1495   if (vl == NULL)
1496     return -EINVAL;
1497
1498   for (llentry_t *te = llist_head(g_agent->tables); te != NULL; te = te->next) {
1499     table_definition_t *td = te->value;
1500
1501     for (llentry_t *de = llist_head(td->columns); de != NULL; de = de->next) {
1502       data_definition_t *dd = de->value;
1503       oid_t *index_oid = (oid_t *)calloc(1, sizeof(*index_oid));
1504       int ret;
1505
1506       if (index_oid == NULL)
1507         return -ENOMEM;
1508
1509       if (!dd->is_index_key) {
1510         if (CHECK_DD_TYPE(dd, vl->plugin, vl->plugin_instance, vl->type,
1511                           vl->type_instance)) {
1512           ret = snmp_agent_generate_index(td, vl, index_oid);
1513           if (ret == 0)
1514             ret = snmp_agent_update_index(td, index_oid);
1515
1516           return ret;
1517         }
1518       }
1519     }
1520   }
1521
1522   return 0;
1523 }
1524
1525 static int snmp_agent_collect(const data_set_t *ds, const value_list_t *vl,
1526                               user_data_t __attribute__((unused)) * user_data) {
1527
1528   pthread_mutex_lock(&g_agent->lock);
1529
1530   snmp_agent_write(vl);
1531
1532   pthread_mutex_unlock(&g_agent->lock);
1533
1534   return 0;
1535 }
1536
1537 static int snmp_agent_preinit(void) {
1538
1539   g_agent = calloc(1, sizeof(*g_agent));
1540   if (g_agent == NULL) {
1541     ERROR(PLUGIN_NAME ": Failed to allocate memory for snmp agent context");
1542     return -ENOMEM;
1543   }
1544
1545   g_agent->tables = llist_create();
1546   g_agent->scalars = llist_create();
1547
1548   if (g_agent->tables == NULL || g_agent->scalars == NULL) {
1549     ERROR(PLUGIN_NAME ": llist_create() failed");
1550     llist_destroy(g_agent->scalars);
1551     llist_destroy(g_agent->tables);
1552     return -ENOMEM;
1553   }
1554
1555   int err;
1556   /* make us an agentx client. */
1557   err = netsnmp_ds_set_boolean(NETSNMP_DS_APPLICATION_ID, NETSNMP_DS_AGENT_ROLE,
1558                                1);
1559   if (err != 0) {
1560     ERROR(PLUGIN_NAME ": Failed to set agent role (%d)", err);
1561     llist_destroy(g_agent->scalars);
1562     llist_destroy(g_agent->tables);
1563     return -1;
1564   }
1565
1566   /*
1567    *  For SNMP debug purposes uses snmp_set_do_debugging(1);
1568    */
1569
1570   /* initialize the agent library */
1571   err = init_agent(PLUGIN_NAME);
1572   if (err != 0) {
1573     ERROR(PLUGIN_NAME ": Failed to initialize the agent library (%d)", err);
1574     llist_destroy(g_agent->scalars);
1575     llist_destroy(g_agent->tables);
1576     return -1;
1577   }
1578
1579   init_snmp(PLUGIN_NAME);
1580
1581   g_agent->tp = read_all_mibs();
1582
1583   return 0;
1584 }
1585
1586 static int snmp_agent_init(void) {
1587   int ret;
1588
1589   if (g_agent == NULL || ((llist_head(g_agent->scalars) == NULL) &&
1590                           (llist_head(g_agent->tables) == NULL))) {
1591     ERROR(PLUGIN_NAME ": snmp_agent_init: plugin not configured");
1592     return -EINVAL;
1593   }
1594
1595   plugin_register_shutdown(PLUGIN_NAME, snmp_agent_shutdown);
1596
1597   ret = snmp_agent_register_scalar_oids();
1598   if (ret != 0)
1599     return ret;
1600
1601   ret = snmp_agent_register_table_oids();
1602   if (ret != 0)
1603     return ret;
1604
1605   ret = pthread_mutex_init(&g_agent->lock, NULL);
1606   if (ret != 0) {
1607     ERROR(PLUGIN_NAME ": Failed to initialize mutex, err %u", ret);
1608     return ret;
1609   }
1610
1611   ret = pthread_mutex_init(&g_agent->agentx_lock, NULL);
1612   if (ret != 0) {
1613     ERROR(PLUGIN_NAME ": Failed to initialize AgentX mutex, err %u", ret);
1614     return ret;
1615   }
1616
1617   /* create a second thread to listen for requests from AgentX*/
1618   ret = pthread_create(&g_agent->thread, NULL, &snmp_agent_thread_run, NULL);
1619   if (ret != 0) {
1620     ERROR(PLUGIN_NAME ": Failed to create a separate thread, err %u", ret);
1621     return ret;
1622   }
1623
1624   if (llist_head(g_agent->tables) != NULL) {
1625     plugin_register_write(PLUGIN_NAME, snmp_agent_collect, NULL);
1626     plugin_register_missing(PLUGIN_NAME, snmp_agent_clear_missing, NULL);
1627   }
1628
1629   return 0;
1630 }
1631
1632 static void *snmp_agent_thread_run(void __attribute__((unused)) * arg) {
1633   INFO(PLUGIN_NAME ": Thread is up and running");
1634
1635   for (;;) {
1636     pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
1637
1638     pthread_mutex_lock(&g_agent->agentx_lock);
1639     agent_check_and_process(0); /* 0 == don't block */
1640     pthread_mutex_unlock(&g_agent->agentx_lock);
1641
1642     pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
1643     usleep(10);
1644   }
1645
1646   pthread_exit(0);
1647 }
1648
1649 static int snmp_agent_register_oid(oid_t *oid, Netsnmp_Node_Handler *handler) {
1650   netsnmp_handler_registration *reg;
1651   char *oid_name = snmp_agent_get_oid_name(oid->oid, oid->oid_len - 1);
1652   char oid_str[DATA_MAX_NAME_LEN];
1653
1654   snmp_agent_oid_to_string(oid_str, sizeof(oid_str), oid);
1655
1656   if (oid_name == NULL) {
1657     WARNING(PLUGIN_NAME
1658             ": Skipped registration: OID (%s) is not found in main tree",
1659             oid_str);
1660     return 0;
1661   }
1662
1663   reg = netsnmp_create_handler_registration(oid_name, handler, oid->oid,
1664                                             oid->oid_len, HANDLER_CAN_RONLY);
1665   if (reg == NULL) {
1666     ERROR(PLUGIN_NAME ": Failed to create handler registration for OID (%s)",
1667           oid_str);
1668     return -1;
1669   }
1670
1671   pthread_mutex_lock(&g_agent->agentx_lock);
1672
1673   if (netsnmp_register_instance(reg) != MIB_REGISTERED_OK) {
1674     ERROR(PLUGIN_NAME ": Failed to register handler for OID (%s)", oid_str);
1675     pthread_mutex_unlock(&g_agent->agentx_lock);
1676     return -1;
1677   }
1678
1679   pthread_mutex_unlock(&g_agent->agentx_lock);
1680
1681   DEBUG(PLUGIN_NAME ": Registered handler for OID (%s)", oid_str);
1682
1683   return 0;
1684 }
1685
1686 static int snmp_agent_free_config(void) {
1687
1688   if (g_agent == NULL)
1689     return -EINVAL;
1690
1691   for (llentry_t *te = llist_head(g_agent->tables); te != NULL; te = te->next)
1692     snmp_agent_free_table((table_definition_t **)&te->value);
1693   llist_destroy(g_agent->tables);
1694
1695   for (llentry_t *de = llist_head(g_agent->scalars); de != NULL; de = de->next)
1696     snmp_agent_free_data((data_definition_t **)&de->value);
1697   llist_destroy(g_agent->scalars);
1698
1699   return 0;
1700 }
1701
1702 static int snmp_agent_shutdown(void) {
1703   int ret = 0;
1704
1705   DEBUG(PLUGIN_NAME ": snmp_agent_shutdown");
1706
1707   if (g_agent == NULL) {
1708     ERROR(PLUGIN_NAME ": snmp_agent_shutdown: plugin not initialized");
1709     return -EINVAL;
1710   }
1711
1712   if (pthread_cancel(g_agent->thread) != 0)
1713     ERROR(PLUGIN_NAME ": snmp_agent_shutdown: failed to cancel the thread");
1714
1715   if (pthread_join(g_agent->thread, NULL) != 0)
1716     ERROR(PLUGIN_NAME ": snmp_agent_shutdown: failed to join the thread");
1717
1718   snmp_agent_free_config();
1719
1720   snmp_shutdown(PLUGIN_NAME);
1721
1722   pthread_mutex_destroy(&g_agent->lock);
1723   pthread_mutex_destroy(&g_agent->agentx_lock);
1724
1725   sfree(g_agent);
1726
1727   return ret;
1728 }
1729
1730 static int snmp_agent_config(oconfig_item_t *ci) {
1731
1732   int ret = snmp_agent_preinit();
1733
1734   if (ret != 0) {
1735     sfree(g_agent);
1736     return -EINVAL;
1737   }
1738
1739   for (int i = 0; i < ci->children_num; i++) {
1740     oconfig_item_t *child = ci->children + i;
1741     if (strcasecmp("Data", child->key) == 0) {
1742       ret = snmp_agent_config_scalar(child);
1743     } else if (strcasecmp("Table", child->key) == 0) {
1744       ret = snmp_agent_config_table(child);
1745     } else {
1746       ERROR(PLUGIN_NAME ": Unknown configuration option `%s'", child->key);
1747       ret = (-EINVAL);
1748     }
1749
1750     if (ret != 0) {
1751       ERROR(PLUGIN_NAME ": Failed to parse configuration");
1752       snmp_agent_free_config();
1753       snmp_shutdown(PLUGIN_NAME);
1754       sfree(g_agent);
1755       return -EINVAL;
1756     }
1757   }
1758
1759   ret = snmp_agent_validate_config();
1760   if (ret != 0) {
1761     ERROR(PLUGIN_NAME ": Invalid configuration provided");
1762     snmp_agent_free_config();
1763     snmp_shutdown(PLUGIN_NAME);
1764     sfree(g_agent);
1765     return -EINVAL;
1766   }
1767
1768   return 0;
1769 }
1770
1771 void module_register(void) {
1772   plugin_register_init(PLUGIN_NAME, snmp_agent_init);
1773   plugin_register_complex_config(PLUGIN_NAME, snmp_agent_config);
1774 }