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